---初始化后台管理web页面项目

This commit is contained in:
2025-08-20 14:39:30 +08:00
parent ad49711a7e
commit 87545a8baf
2057 changed files with 282864 additions and 213 deletions

View File

@ -0,0 +1,621 @@
<template>
<div style="padding: 10px; box-sizing: border-box">
<div>
<div class="mumber-box">
<div class="mumber" v-for="(it, index) in data.Statistics" :key="index">
<div class="mum-left">
<img class="index-img" :src="'/src/assets/erp/index-icon' + (index + 1) + '.svg'" />
</div>
<div class="mum-right">
<div class="mum-title">{{ it.name }}{{ it.unit }}</div>
<div class="mum" :style="{ color: data.colors[index] }">{{ it.num }}</div>
<div class="mum-than">
{{ index > 1 ? '环比' : '同比' }}&nbsp;&nbsp;&nbsp;&nbsp;{{ it.pecent }}%
<Icon
:icon="it.isUp ? 'ant-design:caret-up-outlined' : 'ant-design:caret-down-outlined'"
:color="it.isUp ? '#3dba84' : '#ee3b3b'"
:size="14"
class="icon"
/>
</div>
</div>
</div>
</div>
<a-row :gutter="10">
<a-col :span="7">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">月入库单分析</span>
</div>
<div class="item" ref="homePieChart"></div>
</div>
</a-col>
<a-col :span="11">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">入库数量增长趋势</span>
</div>
<div class="item" ref="homeLine1Chart"></div>
</div>
</a-col>
<a-col :span="6">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">预警类型占比</span>
</div>
<div class="item" ref="homePieChart2"></div>
</div>
</a-col>
<a-col :span="7">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">月出库单分析</span>
</div>
<div class="item" ref="homeRadarChart"></div>
</div>
</a-col>
<a-col :span="11">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">出库数量增长趋势</span>
</div>
<div class="item" ref="homeLineChart"></div>
</div>
</a-col>
<a-col :span="6">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">在库原材料占比</span>
</div>
<div class="item" ref="homePieChart3"></div>
</div>
</a-col>
</a-row>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, nextTick, reactive, ref, unref, markRaw } from 'vue';
import {
getDataInfo,
getDocInfo,
getDocOutInfo,
getTrendInfo,
getTrendOutInfo,
getWarnInfo,
getMaterialsInfo,
} from '/@/api/erp/report/inventory';
import Icon from '/@/components/Icon/index';
import * as echarts from 'echarts';
const data = reactive({
colors: ['#5E95FF', '#00CACF', '#FF9100', '#995EFF', '#5E6EFF'],
Statistics: [] as any[],
bar2Option: {
color: ['#5E95FF', '#00CACF', '#5E6EFF', '#995EFF', '#2C8DD6'],
title: {
text: '单位:单',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
lineHeight: 30,
},
},
legend: {
type: 'scroll',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
left: 90,
textStyle: {
color: '#85878e',
fontSize: 12,
lineHeight: 30,
},
},
grid: {
left: 10,
right: 40,
bottom: 20,
containLabel: true,
},
xAxis: {
type: 'value',
},
yAxis: {
type: 'category',
data: [] as any[],
},
series: [] as any[],
},
line1Option: {
color: ['#00CACF', '#995EFF', '#FF8080', '#5E95FF', '#E8D316', '#FF9100'],
title: {
text: '单位:万元',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
},
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(51, 51, 51, 0.7)',
formatter: (params) => {
let str = '';
params.forEach((o) => {
str +=
o.marker +
'<span style="display:inline-flex;justify-content: space-between;width:138px;color:#fff">' +
o.seriesName +
'<span>' +
o.data +
'万元</span></span><br/>';
});
return str;
},
},
legend: {
itemWidth: 20,
itemHeight: 10,
itemGap: 20,
right: 20,
textStyle: {
color: '#85878e',
},
data: [] as any[],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [] as any[],
},
yAxis: {
type: 'value',
},
series: [] as any[],
},
// 占比饼图
pie2Option: {
color: ['#5E95FF', '#FF9100', '#995EFF', '#00CACF'],
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(51, 51, 51, 0.7)',
textStyle: {
color: '#fff',
},
formatter: '{b}<br/>{c}' + '条' + ' ({d}%)',
},
legend: {
bottom: '10%',
left: 'center',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
textStyle: {
color: '#85878e',
},
},
series: {
type: 'pie',
radius: ['40%', '60%'],
center: ['50%', '38%'],
avoidLabelOverlap: false,
// selectedMode: 'single',
emphasis: {
label: {
show: true,
fontSize: '20',
fontWeight: 'bold',
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
labelLine: {
show: false,
},
data: [] as any[],
},
},
barOption: {
color: ['#5E95FF', '#00CACF', '#5E6EFF', '#995EFF', '#2C8DD6'],
title: {
text: '单位:单',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
lineHeight: 30,
},
},
legend: {
type: 'scroll',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
left: 90,
textStyle: {
color: '#85878e',
fontSize: 12,
lineHeight: 30,
},
},
grid: {
left: 10,
right: 40,
bottom: 20,
containLabel: true,
},
xAxis: {
type: 'value',
},
yAxis: {
type: 'category',
data: [] as any[],
},
series: [] as any[],
},
// 折线图
lineOption: {
color: ['#00CACF', '#995EFF', '#FF8080', '#5E95FF', '#E8D316', '#FF9100'],
title: {
text: '单位:万元',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
},
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(51, 51, 51, 0.7)',
formatter: (params) => {
let str = '';
params.forEach((o) => {
str +=
o.marker +
'<span style="display:inline-flex;justify-content: space-between;width:138px;color:#fff">' +
o.seriesName +
'<span>' +
o.data +
'万元</span></span><br/>';
});
return str;
},
},
legend: {
itemWidth: 20,
itemHeight: 10,
itemGap: 20,
right: 20,
textStyle: {
color: '#85878e',
},
data: [] as any[],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [] as any[],
},
yAxis: {
type: 'value',
},
series: [] as any[],
},
// 饼图
pie3Option: {
color: ['#FF8080', '#9863C1', '#FF9100', '#5E95FF', '#00CACF'],
series: {
name: '',
type: 'pie',
radius: '50%',
selectedMode: 'single',
center: ['50%', '48%'],
label: {
color: '#6e7079',
overflow: 'none',
formatter: '{b} {d}%\n{c}',
},
data: [] as any[],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
},
});
const homePieChart = ref<HTMLDivElement>();
const homePieChart2 = ref<HTMLDivElement>();
const homePieChart3 = ref<HTMLDivElement>();
const homeLine1Chart = ref<HTMLDivElement>();
const homeRadarChart = ref<HTMLDivElement>();
const homeLineChart = ref<HTMLDivElement>();
onMounted(async () => {
let res = await getDataInfo();
data.Statistics = res || [];
let res1 = await getDocInfo();
res1.forEach((o, i) => {
let arr: any[] = [];
o.data.forEach((k: any) => {
arr.push(k.value);
if (i == 1) data.bar2Option.yAxis.data.push(k.name);
});
let obj = {
name: o.name,
type: 'bar',
stack: 'total',
label: {
show: true,
color: '#fff',
},
emphasis: {
focus: 'series',
},
data: arr,
};
data.bar2Option.series.push(obj);
});
let res2 = await getWarnInfo();
res2.forEach((o, i) => {
if (i === 0) {
data.pie2Option.series.data.push({
value: o.value,
name: o.name,
label: {
show: false,
position: 'center',
normal: {
show: true,
formatter: '预警类型',
position: 'center',
fontSize: 16,
color: '#5E95FF',
fontWeight: 'bold',
},
},
});
} else {
data.pie2Option.series.data.push({
value: o.value,
name: o.name,
label: {
show: false,
position: 'center',
normal: {
show: false,
},
},
});
}
});
let res3 = await getMaterialsInfo();
data.pie3Option.series.data = res3;
data.pie3Option.series.data[0].selected = true;
let res4 = await getDocOutInfo();
if (res4) {
res4.forEach((o, i) => {
let arr: any[] = [];
o.data.forEach((k: any) => {
arr.push(k.value);
if (i == 1) data.barOption.yAxis.data.push(k.name);
});
let obj = {
name: o.name,
type: 'bar',
stack: 'total',
label: {
show: true,
color: '#fff',
},
emphasis: {
focus: 'series',
},
data: arr,
};
data.barOption.series.push(obj);
});
}
let res5 = await getTrendInfo();
res5.forEach((o, i) => {
data.line1Option.legend.data.push(o.name);
let arr: any[] = [];
o.data.forEach((k: any) => {
arr.push(k.value);
if (i == 1) data.line1Option.xAxis.data.push(k.name);
});
let obj = {
name: o.name,
type: 'line',
stack: 'Total',
smooth: true,
showSymbol: false,
data: arr,
};
data.line1Option.series.push(obj);
});
let res6 = await getTrendOutInfo();
res6.forEach((o, i) => {
data.lineOption.legend.data.push(o.name);
let arr: any[] = [];
o.data.forEach((k: any) => {
arr.push(k.value);
if (i == 1) data.lineOption.xAxis.data.push(k.name);
});
let obj = {
name: o.name,
type: 'line',
stack: 'Total',
smooth: true,
showSymbol: false,
data: arr,
};
data.lineOption.series.push(obj);
});
nextTick(() => {
let myChart = markRaw(echarts.init(unref(homePieChart) as HTMLDivElement));
myChart.setOption(data.bar2Option, true);
myChart.resize(); //显示区域大小发生改变更新图表
let myChart2 = markRaw(echarts.init(unref(homePieChart2) as HTMLDivElement));
myChart2.setOption(data.pie2Option, true);
myChart2.resize(); //显示区域大小发生改变更新图表
let myChart3 = markRaw(echarts.init(unref(homePieChart3) as HTMLDivElement));
myChart3.setOption(data.pie3Option, true);
myChart3.resize(); //显示区域大小发生改变更新图表
let myChart4 = markRaw(echarts.init(unref(homeLine1Chart) as HTMLDivElement));
myChart4.setOption(data.line1Option, true);
myChart4.resize(); //显示区域大小发生改变更新图表
let myChart5 = markRaw(echarts.init(unref(homeRadarChart) as HTMLDivElement));
myChart5.setOption(data.barOption, true);
myChart5.resize(); //显示区域大小发生改变更新图表
let myChart6 = markRaw(echarts.init(unref(homeLineChart) as HTMLDivElement));
myChart6.setOption(data.lineOption, true);
myChart6.resize(); //显示区域大小发生改变更新图表
});
});
</script>
<style lang="less" scoped>
.title-chose {
float: right;
margin-right: 20px;
span {
text-align: center;
padding: 0 12px;
display: inline-block;
color: #333;
height: 30px;
line-height: 30px;
font-size: 14px;
cursor: pointer;
margin-left: 10px;
&.cur,
&:active {
background-color: #5e95ff;
color: #fff;
}
}
}
.box-bg {
background-color: #fff;
box-sizing: border-box;
border-radius: 5px;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 10%);
.box-bg-title {
border-bottom: 1px solid #eee;
height: 50px;
line-height: 50px;
font-size: 16px;
box-sizing: border-box;
}
.title-text {
padding: 0 15px;
}
.item {
padding: 10px 10px 0;
height: 312px;
}
}
.mumber-box {
margin: 2px -5px 10px;
display: flex;
justify-content: space-between;
.mumber {
padding: 20px;
box-sizing: border-box;
margin: 0 5px;
flex: 1;
height: 120px;
background: #fff;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 10%);
border-radius: 5px;
.mum-left {
float: left;
width: 24%;
margin-top: 6px;
.index-img {
display: inline-block;
width: 100%;
}
}
.mum-right {
margin-left: 6%;
float: left;
width: 70%;
.mum-title {
font-size: 14px;
}
.mum {
font-size: 24px;
font-weight: bolder;
line-height: 40px;
}
.mum-than {
font-size: 14px;
.icon {
padding-left: 8px;
}
}
}
}
}
.ant-col {
margin-bottom: 10px;
}
</style>

View File

@ -0,0 +1,783 @@
<template>
<div style="padding: 10px; box-sizing: border-box">
<div>
<div class="mumber-box">
<div class="mumber" v-for="(it, index) in data.Statistics" :key="index">
<div class="mum-title">{{ it.name }}</div>
<div class="mum-right">
<div class="mum"> <div>计划采购数量</div>{{ it.plan }} </div>
<div class="mum"> <div>实际采购数量</div>{{ it.actual }} </div>
<div class="mum-than">
<div class="bor">
<div :style="{ color: it.isUp ? '#12B700' : '#FF8080' }">
{{ it.pecent }}%
<Icon :icon="it.isUp ? 'fa-level-up' : 'fa-level-down'" :size="14" class="icon" />
</div>
偏差值
</div>
<div class="bor border1"></div>
<div class="bor border2"></div>
<div class="bor border3"></div>
<div class="bor border4"></div>
</div>
</div>
</div>
</div>
<a-row :gutter="10">
<a-col :span="7">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">采购成本分析金额</span>
</div>
<div class="item">
<div class="unit">单位万元</div>
<div>
<div class="pucharse" v-for="it in data.barData" :key="it.name">
<span class="labels">{{ it.name }}</span>
<div class="values">
<div class="actual" :style="{ width: (it.actual / it.plan) * 100 + '%' }"></div>
<div style="position: absolute; right: 10px">{{ it.actual }}/{{ it.plan }}</div>
</div>
</div>
</div>
</div>
</div>
</a-col>
<a-col :span="11">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">本年采购价格偏差分析</span>
<div class="title-chose">
<span
v-for="item in data.priceArray"
:key="item.key"
:class="item.key == data.cur ? 'cur' : ''"
@click="changePrice(item, 1)"
>{{ item.name }}</span
>
</div>
</div>
<div class="item" ref="homeBarlineChart"></div>
</div>
</a-col>
<a-col :span="6">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">采购合同管理</span>
</div>
<div class="item" ref="homePieChart2"></div>
</div>
</a-col>
<a-col :span="7">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">原材料采购价格环比</span>
</div>
<div class="item" ref="homeLineChart2"></div>
</div>
</a-col>
<a-col :span="11">
<div class="box-bg" style="position: relative">
<div class="box-bg-title">
<span class="title-text">供应链趋势</span>
<div class="title-chose">
<span
v-for="item in data.priceArray2"
:key="item.key"
:class="item.key == data.cur2 ? 'cur' : ''"
@click="changePrice(item, 2)"
>{{ item.name }}</span
>
</div>
</div>
<div class="item" ref="homeLineChart"></div>
</div>
</a-col>
<a-col :span="6">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">采购合同类型占比</span>
</div>
<div class="item" ref="homePieChart"></div>
</div>
</a-col>
</a-row>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, nextTick, reactive, ref, unref, markRaw, computed } from 'vue';
import {
getDataInfo,
getPriceInfo,
getTrendInfo,
getTypeInfo,
getCostInfo,
getDeviationInfo,
getContractInfo,
} from '/@/api/erp/report/purchase';
import Icon from '/@/components/Icon/index';
import * as echarts from 'echarts';
const formatePie = (name) => {
let str = name + '';
data.pieData.forEach((o: any) => {
if (name == o.name) {
str += o.value + '';
}
});
return str;
};
const data = reactive({
colors: ['#5E95FF', '#00CACF', '#FF9100', '#995EFF', '#5E6EFF'],
Statistics: [] as any[],
barData: [] as any[],
pieData: [],
priceArray: [] as any[],
priceArray2: [] as any[],
cur: 0,
cur2: 0,
myChart: null as any,
myChart4: null as any,
// 玫瑰饼图
pieOption: {
color: ['#FF8080', '#9863C1', '#00CACF', '#5E95FF', '#FF9100'],
series: {
name: '',
type: 'pie',
radius: '50%',
selectedMode: 'single',
center: ['50%', '50%'],
label: {
color: '#6e7079',
overflow: 'none',
formatter: '{b} {d}%\n{c}',
},
data: [] as any,
},
},
// 占比饼图
pie2Option: {
color: ['#5E95FF', '#FF9100', '#995EFF', '#00CACF'],
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(51, 51, 51, 0.7)',
textStyle: {
color: '#fff',
},
formatter: '{b} {d}%<br/>' + '{c}',
},
legend: {
bottom: '10%',
left: 'center',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
textStyle: {
color: '#85878e',
},
formatter: formatePie,
},
series: {
type: 'pie',
radius: ['40%', '60%'],
center: ['50%', '38%'],
avoidLabelOverlap: false,
labelLine: {
show: false,
},
data: [] as any[],
},
},
barOption2: {
color: ['#00CACF', '#5E95FF'],
title: {
text: '单位:万元',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
},
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(51, 51, 51, 0.7)',
formatter: (params) => {
let str = '';
params.forEach((o) => {
str +=
o.marker +
'<span style="display:inline-flex;justify-content: space-between;width:138px;color:#fff">' +
o.seriesName +
'<span>' +
o.data +
'万元</span></span><br/>';
});
return str;
},
},
legend: {
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
right: 20,
textStyle: {
color: '#85878e',
},
data: ['2021年', '2022年'],
},
xAxis: {
type: 'category',
data: [],
axisPointer: {
type: 'shadow',
},
},
yAxis: [
{
type: 'value',
name: '',
min: 0,
max: 45,
interval: 15,
},
{
type: 'value',
name: '',
axisLabel: {
formatter: '{value}%',
},
},
],
series: [] as any,
},
});
const homePieChart2 = ref<HTMLDivElement>();
const homeBarlineChart = ref<HTMLDivElement>();
const homeLineChart2 = ref<HTMLDivElement>();
const homeLineChart = ref<HTMLDivElement>();
const homePieChart = ref<HTMLDivElement>();
const lineOption = computed(() => {
let access = data.priceArray2[data.cur2].data.access;
let dieOut = data.priceArray2[data.cur2].data.dieOut;
let total = dieOut ? access.concat(dieOut) : [10];
let max = Math.max.apply(null, total);
return {
color: ['#00CACF', '#995EFF', '#FF9100'],
title: {
text: '单位:万元',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'none',
},
},
legend: {
itemWidth: 20,
itemHeight: 10,
itemGap: 20,
right: 20,
textStyle: {
color: '#85878e',
},
data: ['供应链准入', '供应链淘汰/冻结', '偏差率'],
},
xAxis: [
{
type: 'category',
data: data.priceArray2[data.cur2].category,
axisPointer: {
type: 'shadow',
},
},
],
yAxis: [
{
type: 'value',
name: '',
min: 0,
max: max,
interval: 50,
},
{
type: 'value',
name: '',
min: 0,
max: max / 10,
interval: 5,
axisLabel: {
formatter: '{value}%',
},
},
],
series: [
{
name: '供应链准入',
type: 'line',
stack: 'Total',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 202, 207, .7)',
},
{
offset: 1,
color: 'rgba(255, 255, 255, .7)',
},
]),
},
smooth: true,
showSymbol: false,
tooltip: {
valueFormatter: function (value) {
return value + ' 万元';
},
},
data: access,
},
{
name: '供应链淘汰/冻结',
type: 'line',
stack: 'Total',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(152, 99, 193, .7)',
},
{
offset: 1,
color: 'rgba(255, 255, 255, .7)',
},
]),
},
smooth: true,
showSymbol: false,
tooltip: {
valueFormatter: function (value) {
return value + ' 万元';
},
},
data: dieOut,
},
{
name: '偏差率',
type: 'line',
yAxisIndex: 1,
showSymbol: false,
tooltip: {
valueFormatter: function (value) {
return value + ' 万元';
},
},
data: data.priceArray2[data.cur2].data.deviationRate,
},
],
};
});
const barOption = computed(() => {
let plan = data.priceArray[data.cur].data.plan;
let actual = data.priceArray[data.cur].data.actual;
let deviationRate = data.priceArray[data.cur].data.deviationRate;
let total = actual ? plan.concat(actual) : [10];
let max = Math.max.apply(null, total);
return {
color: ['#00CACF', '#5E95FF', '#FF9100'],
title: {
text: '单位:万元',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
},
},
grid: {
left: 20,
right: 30,
bottom: 20,
containLabel: true,
},
legend: {
itemWidth: 20,
itemHeight: 10,
itemGap: 20,
right: 20,
textStyle: {
color: '#85878e',
},
data: ['预计利润', '实际利润', '偏差率'],
},
xAxis: [
{
type: 'category',
data: data.priceArray[data.cur].category,
axisPointer: {
type: 'shadow',
},
},
],
yAxis: [
{
type: 'value',
name: '',
// minInterval: 1,
min: 0,
max: max,
interval: 50,
},
{
type: 'value',
name: '',
min: 0,
max: max / 10,
interval: 5,
axisLabel: {
formatter: '{value} %',
},
},
],
series: [
{
name: '预计利润',
type: 'bar',
barWidth: data.cur === 1 ? 20 : 'auto',
data: plan,
},
{
name: '实际利润',
type: 'bar',
barWidth: data.cur === 1 ? 20 : 'auto',
data: actual,
},
{
name: '偏差率',
type: 'line',
yAxisIndex: 1,
data: deviationRate,
},
],
};
});
onMounted(async () => {
let res = await getDataInfo();
data.Statistics = res;
let res1 = await getCostInfo();
data.barData = res1;
let res2 = await getDeviationInfo();
data.priceArray = res2;
let res3 = await getContractInfo();
data.pieData = res3;
data.pie2Option.series.data = [];
data.pieData.forEach((o: any) => {
let obj: any = {
value: o.value,
name: o.name,
label: {
show: false,
},
};
if (o.name == '已完成') {
obj.label = {
show: false,
position: 'center',
normal: {
show: true,
formatter: '合同完成率\n\n{d}%',
position: 'center',
lineHeight: 15,
fontSize: 16,
color: '#5E95FF',
fontWeight: 'bold',
},
};
}
data.pie2Option.series.data.push(obj);
});
let res4 = await getTypeInfo();
data.pieOption.series.data = res4;
data.pieOption.series.data[0].selected = true;
let res5 = await getPriceInfo();
data.barOption2.xAxis.data = res5.category;
res5.series.forEach((o) => {
if (o.name == '偏差率') {
data.barOption2.series.push({
name: '',
type: 'line',
yAxisIndex: 1,
lineStyle: {
color: 'transparent',
},
showSymbol: false,
tooltip: {
show: false,
},
data: o.data,
});
} else {
data.barOption2.series.push({
name: o.name,
type: 'bar',
tooltip: {
valueFormatter: function (value) {
return value + ' 万元';
},
},
data: o.data,
});
}
});
let res6 = await getTrendInfo();
data.priceArray2 = res6;
nextTick(() => {
let myChart3 = markRaw(echarts.init(unref(homePieChart2) as HTMLDivElement));
myChart3.setOption(data.pie2Option, true);
myChart3.resize(); //显示区域大小发生改变更新图表
data.myChart = markRaw(echarts.init(unref(homeBarlineChart) as HTMLDivElement));
data.myChart.setOption(unref(barOption), true);
data.myChart.resize(); //显示区域大小发生改变更新图表
let myChart2 = markRaw(echarts.init(unref(homeLineChart2) as HTMLDivElement));
myChart2.setOption(data.barOption2, true);
myChart2.resize(); //显示区域大小发生改变更新图表
data.myChart4 = markRaw(echarts.init(unref(homeLineChart) as HTMLDivElement));
data.myChart4.setOption(unref(lineOption), true);
data.myChart4.resize(); //显示区域大小发生改变更新图表
let myChart5 = markRaw(echarts.init(unref(homePieChart) as HTMLDivElement));
myChart5.setOption(data.pieOption, true);
myChart5.resize(); //显示区域大小发生改变更新图表
});
});
const changePrice = (o, i) => {
if (i == 1) {
data.cur = o.key;
data.myChart.setOption(unref(barOption), true);
data.myChart.resize(); //显示区域大小发生改变更新图表
} else {
data.cur2 = o.key;
data.myChart4.setOption(unref(lineOption), true);
data.myChart4.resize(); //显示区域大小发生改变更新图表
}
};
</script>
<style lang="less" scoped>
.title-chose {
float: right;
margin-right: 20px;
span {
text-align: center;
padding: 0 12px;
display: inline-block;
color: #333;
height: 30px;
line-height: 30px;
font-size: 14px;
cursor: pointer;
margin-left: 10px;
&.cur,
&:active {
background-color: #5e95ff;
color: #fff;
}
}
}
.box-bg {
background-color: #fff;
box-sizing: border-box;
border-radius: 5px;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 10%);
.box-bg-title {
border-bottom: 1px solid #eee;
height: 50px;
line-height: 50px;
font-size: 16px;
box-sizing: border-box;
}
.title-text {
padding: 0 15px;
}
.item {
padding: 10px 10px 0;
width: 100%;
height: 300px;
box-sizing: border-box;
.unit {
font-size: 12px;
color: #707070;
margin-bottom: 30px;
}
.pucharse {
display: flex;
margin-bottom: 24px;
padding: 0 10px;
.labels {
width: 60px;
font-size: 12px;
color: #707070;
line-height: 24px;
text-align: right;
padding-right: 10px;
}
.values {
background: rgb(94 149 255 / 50%);
height: 24px;
line-height: 24px;
flex: 1;
font-size: 12px;
color: #fff;
text-align: right;
display: flex;
justify-content: space-between;
position: relative;
.actual {
background: linear-gradient(270deg, #5e95ff 0%, #695eff 100%);
height: 24px;
}
&:hover {
background: rgb(255 196 96 / 50%);
.actual {
background: linear-gradient(270deg, #ffc45e 0%, #ff9100 100%);
}
}
}
}
}
}
.mumber-box {
margin: 2px -5px 10px;
display: flex;
justify-content: space-between;
.mumber {
padding: 20px;
box-sizing: border-box;
margin: 0 5px;
flex: 1;
height: 164px;
background: #fff;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 10%);
border-radius: 5px;
.mum-title {
font-size: 16px;
}
.mum-right {
display: flex;
justify-content: space-around;
margin-top: 25px;
.mum {
font-size: 24px;
font-weight: bolder;
line-height: 40px;
color: #5e95ff;
div {
font-size: 14px;
color: #333;
font-weight: normal;
}
}
.mum-than {
font-size: 14px;
width: 77px;
height: 59px;
background: linear-gradient(
180deg,
rgb(255 255 255 / 50%) 0%,
rgb(94 149 255 / 50%) 100%
);
margin-top: 14px;
color: #5e95ff;
position: relative;
border: 1px solid #5e95ff;
.bor {
position: absolute;
}
div {
text-align: center;
width: 100%;
& > div {
font-size: 16px;
margin: 4px 0 2px;
}
}
.border1 {
border-left: 1px solid #fff;
height: 38px;
width: 0;
left: -1px;
top: 8px;
}
.border2 {
border-left: 1px solid #fff;
height: 38px;
width: 0;
right: -1px;
top: 8px;
}
.border3 {
border-top: 1px solid #fff;
width: 61px;
top: -1px;
left: 8px;
}
.border4 {
border-top: 1px solid #fff;
width: 61px;
bottom: -1px;
left: 8px;
}
}
}
}
}
.ant-col {
margin-bottom: 10px;
}
</style>

View File

@ -0,0 +1,580 @@
<template>
<div style="padding: 10px; box-sizing: border-box">
<div>
<div class="mumber-box">
<div class="mumber" v-for="(it, index) in data.Statistics" :key="index">
<div class="mum-left">
<img class="index-img" :src="'/src/assets/erp/index-icon' + (index + 1) + '.svg'" />
</div>
<div class="mum-right">
<div class="mum-title">{{ it.name }}万元</div>
<div class="mum">{{ it.num }}</div>
<div class="mum-than">
{{ index > 1 ? '环比' : '同比' }}&nbsp;&nbsp;&nbsp;&nbsp;{{ it.pecent }}%
<Icon
:icon="it.isUp ? 'ant-design:caret-up-outlined' : 'ant-design:caret-down-outlined'"
:color="it.isUp ? '#3dba84' : '#ee3b3b'"
:size="14"
class="icon"
/>
</div>
</div>
</div>
</div>
<a-row :gutter="10">
<a-col :span="6">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">本月销售占比</span>
</div>
<div class="item" ref="homePieChart"></div>
</div>
</a-col>
<a-col :span="18">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">销售分析</span>
</div>
<div class="item" ref="homeLineChart"></div>
</div>
</a-col>
<a-col :span="6">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">本月收入占比</span>
</div>
<div class="item" ref="homePieChart2"></div>
</div>
</a-col>
<a-col :span="7">
<div class="box-bg" style="position: relative">
<div class="box-bg-title">
<span class="title-text">出库产品分析</span>
</div>
<div class="total">
<div>{{ data.total }}万元</div>
<div class="text">总出库产品金额</div>
</div>
<div class="item" ref="homeBar2Chart"></div>
</div>
</a-col>
<a-col :span="11">
<div class="box-bg">
<div class="box-bg-title">
<span class="title-text">本年利润偏差分析</span>
</div>
<div class="item" ref="homeBarChart"></div>
</div>
</a-col>
</a-row>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, nextTick, reactive, ref, unref, markRaw } from 'vue';
import {
getSaleDataInfo,
getProportionSaleInfo,
getSaleAnalysisInfo,
getProportionIncomeInfo,
getProductAnalysisInfo,
getProfitInfo,
} from '/@/api/erp/report/sales';
import Icon from '/@/components/Icon/index';
import * as echarts from 'echarts';
const formate = (name) => {
let str = name + '';
data.dataOne.forEach((o: any) => {
if (name == o.name) {
str += o.value + '万元';
}
});
return str;
};
const data = reactive({
colors: ['#5E95FF', '#00CACF', '#FF9100', '#995EFF', '#5E6EFF'],
Statistics: [] as any[],
dataOne: [],
// 玫瑰饼图
pieOption: {
color: ['#7EAAFF', '#5887E3', '#5E75E6', '#6D68F8', '#9E9BFC', '#8CB1F9'],
legend: {
orient: 'horizontal',
x: 'center',
y: 'bottom',
top: '78%',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
padding: [5, 5, 5, 5],
textStyle: {
color: '#85878e',
},
formatter: formate,
},
series: {
name: '',
center: ['50%', '32%'],
type: 'pie',
radius: [0, 85],
roseType: 'area',
itemStyle: {
borderRadius: 0,
},
label: {
color: '#6e7079',
overflow: 'none',
formatter: '{b}\n{d}%',
},
data: [],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
},
lineOption: {
color: ['#5E95FF', '#995EFF'],
title: {
text: '单位:%',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
},
},
legend: {
itemWidth: 20,
itemHeight: 10,
itemGap: 20,
right: 20,
textStyle: {
color: '#85878e',
},
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [] as any[],
},
yAxis: {
type: 'value',
},
series: [] as any[],
},
// 占比饼图
pie2Option: {
color: ['#00FFE5', '#5E95FF', '#995EFF'],
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(51, 51, 51, 0.7)',
textStyle: {
color: '#fff',
},
formatter: '{b}<br/>{c}' + '万元<br/>' + '{d}%',
},
legend: {
bottom: '10%',
left: 'center',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
textStyle: {
color: '#85878e',
},
},
series: {
type: 'pie',
radius: ['40%', '60%'],
center: ['50%', '38%'],
avoidLabelOverlap: false,
labelLine: {
show: false,
},
data: [] as any[],
},
},
bar2Option: {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'none',
},
backgroundColor: 'rgba(51, 51, 51, 0.7)',
textStyle: {
color: '#fff',
},
formatter: '{b}<br/>{c}' + '万元',
},
grid: {
top: 90,
left: 20,
right: 30,
bottom: 20,
containLabel: true,
},
xAxis: {
data: [] as any[],
axisTick: {
show: false,
},
},
yAxis: {},
series: {
name: '',
type: 'pictorialBar',
showBackground: true,
backgroundStyle: {
color: 'rgba(221, 221, 221, 0.2)',
},
symbol: 'path://M0,10 L10,10 C5.5,10 5.5,5 5,0 C4.5,5 4.5,10 0,10 z',
itemStyle: {
opacity: 1,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#5E95FF', // 0% 处的颜色
},
{
offset: 1,
color: '#695EFF', // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
emphasis: {
itemStyle: {
opacity: 1,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#FFF45E', // 0% 处的颜色
},
{
offset: 1,
color: '#FF9100', // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
},
data: [] as any[],
z: 10,
},
},
total: 0,
barOption: {
color: ['#00CACF', '#5E95FF', '#FF9100'],
title: {
text: '单位:万元',
left: 20,
textStyle: {
color: '#333',
fontWeight: 'normal',
fontSize: 12,
},
},
grid: {
left: 20,
right: 30,
bottom: 20,
containLabel: true,
},
legend: {
itemWidth: 20,
itemHeight: 10,
itemGap: 20,
right: 20,
textStyle: {
color: '#85878e',
},
data: ['预计利润', '实际利润', '偏差率'],
},
xAxis: {
type: 'category',
data: [] as any[],
axisPointer: {
type: 'shadow',
},
},
yAxis: [
{
type: 'value',
name: '',
min: 0,
max: 0,
interval: 50,
},
{
type: 'value',
name: '',
axisLabel: {
formatter: '{value} %',
},
},
],
series: [] as any[],
},
});
const homePieChart = ref<HTMLDivElement>();
const homePieChart2 = ref<HTMLDivElement>();
const homeLineChart = ref<HTMLDivElement>();
const homeBar2Chart = ref<HTMLDivElement>();
const homeBarChart = ref<HTMLDivElement>();
onMounted(async () => {
let res = await getSaleDataInfo();
data.Statistics = res || [];
let res1 = await getProportionSaleInfo();
data.dataOne = res1 || [];
data.pieOption.series.data = data.dataOne;
let res2 = await getSaleAnalysisInfo();
data.lineOption.series = [];
data.lineOption.xAxis.data = [];
res2.forEach((o, i) => {
let arr: any[] = [];
o.data.forEach((k: any) => {
arr.push(k.value);
if (i == 1) data.lineOption.xAxis.data.push(k.name);
});
let obj = {
name: o.name,
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: i === 0 ? '#5E95FF' : '#995EFF',
},
{
offset: 1,
color: '#fff',
},
]),
},
smooth: true,
data: arr,
};
data.lineOption.series.push(obj);
});
let res3 = await getProportionIncomeInfo();
res3.forEach((o) => {
let obj = {
name: o.name,
value: o.value,
label: {
show: false,
},
};
data.pie2Option.series.data.push(obj);
});
let res4 = await getProductAnalysisInfo();
res4.forEach((o) => {
data.bar2Option.xAxis.data.push(o.name);
data.bar2Option.series.data.push(o.value);
data.total += o.value;
});
let res5 = await getProfitInfo();
let total: any = [];
res5.forEach((o, i) => {
let arr: any[] = [];
o.data.forEach((k: any) => {
arr.push(k.value);
if (i == 1) data.barOption.xAxis.data.push(k.name);
});
let obj: any = {
name: o.name,
type: o.name == '偏差率' ? 'line' : 'bar',
data: arr,
};
if (o.name == '偏差率') {
obj.yAxisIndex = 1;
} else {
total = total.concat(arr);
}
data.barOption.series.push(obj);
});
data.barOption.yAxis[0].max = Math.max.apply(null, total);
nextTick(() => {
let myChart = markRaw(echarts.init(unref(homePieChart) as HTMLDivElement));
myChart.setOption(data.pieOption, true);
myChart.resize(); //显示区域大小发生改变更新图表
let myChart2 = markRaw(echarts.init(unref(homePieChart2) as HTMLDivElement));
myChart2.setOption(data.pie2Option, true);
myChart2.resize(); //显示区域大小发生改变更新图表
let myChart4 = markRaw(echarts.init(unref(homeLineChart) as HTMLDivElement));
myChart4.setOption(data.lineOption, true);
myChart4.resize(); //显示区域大小发生改变更新图表
let myChart5 = markRaw(echarts.init(unref(homeBar2Chart) as HTMLDivElement));
myChart5.setOption(data.bar2Option, true);
myChart5.resize(); //显示区域大小发生改变更新图表
let myChart6 = markRaw(echarts.init(unref(homeBarChart) as HTMLDivElement));
myChart6.setOption(data.barOption, true);
myChart6.resize(); //显示区域大小发生改变更新图表
});
});
</script>
<style lang="less" scoped>
.title-chose {
float: right;
margin-right: 20px;
span {
text-align: center;
padding: 0 12px;
display: inline-block;
color: #333;
height: 30px;
line-height: 30px;
font-size: 14px;
cursor: pointer;
margin-left: 10px;
&.cur,
&:active {
background-color: #5e95ff;
color: #fff;
}
}
}
.box-bg {
background-color: #fff;
box-sizing: border-box;
border-radius: 5px;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 10%);
.total {
margin: 20px 20px 0;
height: 46px;
padding: 0 10px;
position: absolute;
left: 0;
right: 0;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 16%);
display: flex;
justify-content: space-between;
font-size: 30px;
font-weight: 400;
align-items: center;
color: #5e95ff;
.text {
font-size: 18px;
font-weight: 400;
color: #707070;
}
}
.box-bg-title {
border-bottom: 1px solid #eee;
height: 50px;
line-height: 50px;
font-size: 16px;
box-sizing: border-box;
}
.title-text {
padding: 0 15px;
}
.item {
padding: 10px 10px 0;
height: 312px;
}
}
.mumber-box {
margin: 2px -5px 10px;
display: flex;
justify-content: space-between;
.mumber {
padding: 20px;
box-sizing: border-box;
margin: 0 5px;
flex: 1;
height: 120px;
background: #fff;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 10%);
border-radius: 5px;
.mum-left {
float: left;
width: 24%;
margin-top: 6px;
.index-img {
display: inline-block;
width: 100%;
}
}
.mum-right {
margin-left: 6%;
float: left;
width: 70%;
.mum-title {
font-size: 14px;
}
.mum {
font-size: 24px;
font-weight: bolder;
line-height: 40px;
color: #5e95ff;
}
.mum-than {
font-size: 14px;
.icon {
padding-left: 8px;
}
}
}
}
}
.ant-col {
margin-bottom: 10px;
}
</style>