123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780 |
- <template>
- <div class="dashbord">
- <div class="chart-group">
- <el-card>
- <SubTitle title="设备运行" />
- <div class="chart-content">
- <img class="positionImg" src="@/assets/images/position.png" alt="" />
- </div>
- </el-card>
- </div>
- <div class="chart-group">
- <el-card>
- <SubTitle title="项目情况" />
- <div class="chart-content">
- <BaseChart width="100%" height="100%" :option="projectOption" />
- </div>
- </el-card>
- <el-card>
- <SubTitle title="设备总览" />
- <div class="chart-content">
- <BaseChart width="100%" height="100%" :option="equipOption" />
- </div>
- </el-card>
- <el-card>
- <SubTitle title="巡检人员" />
- <div class="chart-content">
- <div class="check-summary">
- <div>
- <img src="@/assets/images/home/banzu.svg" alt="" />
- <div class="check-name">
- <div>8</div>
- <div>班组</div>
- </div>
- </div>
- <div>
- <img src="@/assets/images/home/person.svg" alt="" />
- <div class="check-name">
- <div>40</div>
- <div>巡检人员</div>
- </div>
- </div>
- </div>
- <div class="check-rank">
- <div>当月巡检排名</div>
- <CustomTabs v-model:active="checkActive" :tabs="checkTabs" />
- </div>
- <el-table :data="tableData" style="width: 100%; margin-top: 10px" max-height="180">
- <el-table-column prop="name" label="巡检员">
- <template #default="scope">
- {{ `${scope.$index + 1}、${scope.row.name}` }}
- </template>
- </el-table-column>
- <el-table-column prop="group" label="所在班组" />
- <el-table-column prop="num" label="数量" />
- </el-table>
- </div>
- </el-card>
- <el-card>
- <SubTitle title="设备故障" />
- <div class="chart-content">
- <div style="display: flex; justify-content: flex-end; margin-top: 10px">
- <CustomTabs v-model:active="faultActive" :tabs="faultTabs" />
- </div>
- <BaseChart width="100%" height="100%" :option="rankOption" />
- </div>
- </el-card>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import CustomTabs from './components/CustomTabs.vue';
- const checkTabs = [
- {
- name: '按巡检设备数',
- value: '1'
- },
- {
- name: '按发现故障数',
- value: '2'
- }
- ];
- const faultTabs = [
- {
- name: '按设备类型',
- value: '1'
- },
- {
- name: '按故障类型',
- value: '2'
- }
- ];
- const checkActive = ref('1');
- const faultActive = ref('1');
- const tableData = [
- {
- name: '王刚',
- group: '班组4',
- num: '123'
- },
- {
- name: '李思',
- group: '班组2',
- num: '88'
- },
- {
- name: '张伟',
- group: '班组3',
- num: '65'
- },
- {
- name: '张强',
- group: '班组1',
- num: '54'
- }
- ];
- function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, height) {
- // 计算
- let midRatio = (startRatio + endRatio) / 2;
- let startRadian = startRatio * Math.PI * 2;
- let endRadian = endRatio * Math.PI * 2;
- let midRadian = midRatio * Math.PI * 2;
- // 如果只有一个扇形,则不实现选中效果。
- if (startRatio === 0 && endRatio === 1) {
- isSelected = false;
- }
- // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
- k = typeof k !== 'undefined' ? k : 1 / 3;
- // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
- let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
- let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
- // 计算高亮效果的放大比例(未高亮,则比例为 1)
- let hoverRate = isHovered ? 1.05 : 1;
- // 返回曲面参数方程
- return {
- u: {
- min: -Math.PI,
- max: Math.PI * 3,
- step: Math.PI / 32
- },
- v: {
- min: 0,
- max: Math.PI * 2,
- step: Math.PI / 20
- },
- x: function (u, v) {
- if (u < startRadian) {
- return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
- }
- if (u > endRadian) {
- return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
- }
- return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
- },
- y: function (u, v) {
- if (u < startRadian) {
- return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
- }
- if (u > endRadian) {
- return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
- }
- return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
- },
- z: function (u, v) {
- if (u < -Math.PI * 0.5) {
- return Math.sin(u);
- }
- if (u > Math.PI * 2.5) {
- return Math.sin(u);
- }
- return Math.sin(v) > 0 ? 0.2 * height : -1;
- }
- };
- }
- // 生成模拟 3D 饼图的配置项
- function getPie3D(pieData, internalDiameterRatio) {
- let series = [];
- let sumValue = 0;
- let startValue = 0;
- let endValue = 0;
- let legendData = [];
- let k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3;
- // 为每一个饼图数据,生成一个 series-surface 配置
- for (let i = 0; i < pieData.length; i++) {
- sumValue += pieData[i].value;
- let seriesItem = {
- name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
- type: 'surface',
- parametric: true,
- wireframe: {
- show: false
- },
- pieData: pieData[i],
- pieStatus: {
- selected: false,
- hovered: false,
- k: k
- }
- };
- if (typeof pieData[i].itemStyle != 'undefined') {
- let itemStyle = {};
- typeof pieData[i].itemStyle.color != 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
- typeof pieData[i].itemStyle.opacity != 'undefined' ? (itemStyle.opacity = pieData[i].itemStyle.opacity) : null;
- seriesItem.itemStyle = itemStyle;
- }
- series.push(seriesItem);
- }
- // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
- // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
- for (let i = 0; i < series.length; i++) {
- endValue = startValue + series[i].pieData.value;
- console.log(series[i]);
- series[i].pieData.startRatio = startValue / sumValue;
- series[i].pieData.endRatio = endValue / sumValue;
- series[i].parametricEquation = getParametricEquation(
- series[i].pieData.startRatio,
- series[i].pieData.endRatio,
- false,
- false,
- k,
- series[i].pieData.value
- );
- startValue = endValue;
- legendData.push(series[i].name);
- }
- // // 补充一个透明的圆环,用于支撑高亮功能的近似实现。
- series.push({
- name: 'mouseoutSeries',
- type: 'surface',
- parametric: true,
- wireframe: {
- show: false
- },
- itemStyle: {
- opacity: 0.1,
- color: '#8997DE'
- },
- parametricEquation: {
- u: {
- min: 0,
- max: Math.PI * 2,
- step: Math.PI / 20
- },
- v: {
- min: 0,
- max: Math.PI,
- step: Math.PI / 20
- },
- x: function (u, v) {
- return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2;
- },
- y: function (u, v) {
- return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2;
- },
- z: function (u, v) {
- return Math.cos(v) > 0 ? -0.5 : -5;
- }
- }
- });
- // // 补充一个透明的圆环,用于支撑高亮功能的近似实现。
- series.push({
- name: 'mouseoutSeries',
- type: 'surface',
- parametric: true,
- wireframe: {
- show: false
- },
- itemStyle: {
- opacity: 0.1,
- color: '#8997DE'
- },
- parametricEquation: {
- u: {
- min: 0,
- max: Math.PI * 2,
- step: Math.PI / 20
- },
- v: {
- min: 0,
- max: Math.PI,
- step: Math.PI / 20
- },
- x: function (u, v) {
- return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2;
- },
- y: function (u, v) {
- return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2;
- },
- z: function (u, v) {
- return Math.cos(v) > 0 ? -5 : -7;
- }
- }
- });
- series.push({
- name: 'mouseoutSeries',
- type: 'surface',
- parametric: true,
- wireframe: {
- show: false
- },
- itemStyle: {
- opacity: 0.1,
- color: '#8997DE'
- },
- parametricEquation: {
- u: {
- min: 0,
- max: Math.PI * 2,
- step: Math.PI / 20
- },
- v: {
- min: 0,
- max: Math.PI,
- step: Math.PI / 20
- },
- x: function (u, v) {
- return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2.2;
- },
- y: function (u, v) {
- return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2.2;
- },
- z: function (u, v) {
- return Math.cos(v) > 0 ? -7 : -7;
- }
- }
- });
- return series;
- }
- let colors = ['#085AC7', '#24525E', '#C3972E'];
- let xData = ['运营', '施工', '结束'];
- let yData = [568, 175, 396];
- // 传入数据生成 option
- let optionsData = [];
- let total = 0;
- yData.forEach((v) => {
- total += v;
- });
- for (let i = 0; i < xData.length; i++) {
- optionsData.push({
- name: xData[i],
- value: yData[i],
- itemStyle: {
- color: colors[i],
- opacity: 0.7
- }
- });
- }
- const series = getPie3D(optionsData, 0.8);
- const projectOption = computed(() => {
- return {
- legend: {
- tooltip: {
- show: true
- },
- data: xData,
- orient: 'vertial',
- bottom: '5%',
- left: 'center',
- itemGap: 14,
- itemHeight: 10,
- itemWidth: 15,
- formatter: (name) => {
- const res = optionsData.filter((n) => {
- return n.name === name;
- });
- if (!res.length) return;
- return `${name} ${res[0].value}个 ${res[0].value ? ((res[0].value / total) * 100).toFixed(2) : 0}%`;
- }
- },
- animation: true,
- tooltip: {
- formatter: (params) => {
- if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
- return `${params.seriesName}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>${projectOption.value.series[params.seriesIndex].pieData.value}个`;
- }
- },
- textStyle: {
- fontSize: 12
- }
- },
- xAxis3D: {
- min: -1,
- max: 1
- },
- yAxis3D: {
- min: -1,
- max: 1
- },
- zAxis3D: {
- min: -1,
- max: 1
- },
- grid3D: {
- show: false,
- boxHeight: 0.5,
- top: '-10%',
- viewControl: {
- distance: 240,
- alpha: 30,
- beta: 10,
- autoRotate: true // 自动旋转
- }
- },
- series: series
- };
- });
- const equipData = ref<any>([
- {
- name: '报废',
- value: 1546
- },
- {
- name: '维修中',
- value: 189
- },
- {
- name: '故障',
- value: 1452
- },
- {
- name: '其他',
- value: 189
- },
- {
- name: '正在运行',
- value: 600
- },
- {
- name: '停役',
- value: 200
- }
- ]);
- const color = ['#1990FF', '#8543E0', '#30C25B', '#16C2C2', '#FACC14', '#F04864'];
- equipData.value.forEach((item, index) => {
- const tag = index % 6;
- item.itemStyle = {
- color: color[tag] || ''
- };
- });
- let equipTotal = 0;
- equipData.value.forEach((v) => {
- equipTotal += v.value;
- });
- const equipOption = computed(() => {
- return {
- legend: {
- show: true,
- left: '0%',
- bottom: '5%',
- itemGap: 10,
- borderRadius: 5,
- itemWidth: 10,
- icon: 'circle',
- itemHeight: 10,
- data: equipData.value,
- formatter: function (name) {
- const res = equipData.value.filter((n) => {
- return n.name === name;
- });
- if (!res.length) return;
- return `${name} ${res[0].value ? ((res[0].value / equipTotal) * 100).toFixed(2) : 0}% ${res[0].value}`;
- }
- },
- tooltip: {
- trigger: 'item',
- backgroundColor: 'rgba(13,5,30,.6)',
- borderWidth: 1,
- borderColor: '#32A1FF',
- padding: 5,
- textStyle: {
- color: '#fff'
- },
- formatter: (params) => {
- return `${params.name}<br/>${params.marker}${params.value}`;
- }
- },
- title: {
- show: true,
- text: '设备总数',
- itemGap: 5, //主副标题之间的距离
- left: 'center',
- top: '28%',
- textStyle: {
- fontSize: 13,
- color: '#9E9E9E',
- fontWeight: 'normal'
- },
- subtext: '4226',
- subtextStyle: {
- fontSize: 18,
- fontWeight: 500,
- color: '#333'
- }
- },
- series: [
- {
- name: '',
- type: 'pie',
- radius: ['40%', '60%'],
- center: ['50%', '35%'],
- itemStyle: {
- borderWidth: 2, //描边线宽
- borderColor: '#fff'
- },
- label: {
- show: false
- },
- labelLine: {},
- data: equipData.value
- }
- ]
- };
- });
- const rankData = [
- { name: '设备类型一', value: 323 },
- { name: '设备类型二', value: 108 },
- { name: '设备类型三', value: 95 },
- { name: '设备类型四', value: 43 },
- { name: '设备类型五', value: 10 }
- ]; // 类别
- let rankTotal = 0; // 数据总数
- rankData.forEach((v) => {
- rankTotal += v.value;
- });
- const rankOption = computed(() => {
- return {
- grid: {
- left: '5%',
- top: '3%', // 设置条形图的边距
- right: '12%',
- bottom: '15%'
- },
- xAxis: {
- splitLine: {
- show: false,
- lineStyle: {
- color: 'rgba(255,255,255,0.2)',
- type: 'dashed'
- }
- },
- axisLine: {
- show: false
- },
- axisLabel: {
- show: false,
- color: '#ABBFE3'
- },
- axisTick: {
- show: false
- }
- },
- yAxis: [
- {
- type: 'category',
- inverse: true,
- data: rankData.map((item) => item.name),
- axisLine: {
- show: false
- },
- axisTick: {
- show: false
- },
- axisLabel: {
- show: true,
- textStyle: {
- verticalAlign: 'bottom',
- color: '#000',
- fontSize: 12,
- fontFamily: 'Microsoft YaHei',
- align: 'left',
- padding: [0, 0, 9, 5]
- },
- formatter: (name, index) => {
- const _index = index + 1;
- return `NO${_index}. ${name}`;
- }
- },
- offset: 0
- }
- ],
- series: [
- {
- // 内
- type: 'bar',
- barWidth: 10,
- barCateGoryGap: 20,
- legendHoverLink: false,
- silent: true,
- itemStyle: {
- normal: {
- barBorderRadius: 10,
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 1,
- y2: 0,
- colorStops: [
- {
- offset: 0,
- color: '#FFFFFF' // 0% 处的颜色
- },
- {
- offset: 1,
- color: '#0768FF' // 100% 处的颜色
- }
- ]
- }
- }
- },
- label: {
- normal: {
- show: false,
- position: '[0, 0, 15, 10]',
- formatter: '{b}',
- textStyle: {
- color: '#fff',
- fontSize: 14
- }
- }
- },
- data: rankData,
- z: 2,
- animationEasing: 'elasticOut'
- },
- {
- // 外边框
- type: 'pictorialBar',
- symbol: 'rect',
- symbolBoundingData: rankTotal,
- itemStyle: {
- barBorderRadius: 10,
- normal: {
- color: 'none'
- }
- },
- label: {
- normal: {
- padding: [0, 10, 0, 14],
- formatter: (params) => {
- return params.data;
- },
- color: '#03FF00',
- fontWeight: 'bold',
- position: 'right',
- distance: 1, // 向右偏移位置
- show: true
- }
- },
- data: rankData.map((item) => item.value),
- z: 0,
- animationEasing: 'elasticOut'
- },
- {
- name: '外框',
- type: 'bar',
- barCateGoryGap: 20,
- barGap: '-100%', // 设置外框粗细
- data: new Array(rankData.length).fill(rankTotal),
- barWidth: 10,
- itemStyle: {
- normal: {
- barBorderRadius: [0, 6, 6, 0],
- color: '#F2F2F2'
- },
- emphasis: {
- barBorderRadius: [0, 6, 6, 0],
- color: '#F2F2F2'
- }
- },
- z: 0
- }
- ]
- };
- });
- </script>
- <style lang="scss" scoped>
- .positionImg {
- width: 100%;
- height: 100%;
- padding: 10px 0;
- }
- .chart-group {
- display: flex;
- margin-top: 5px;
- :deep(.el-card__body) {
- height: 100%;
- padding: 15px 10px !important;
- }
- .el-card {
- flex: 1;
- height: 350px;
- background: #fff;
- &:not(:first-child) {
- margin-left: 5px;
- }
- }
- .chart-content {
- height: calc(100% - 10px);
- margin-top: 5px;
- border-top: 1px solid #eaebee;
- }
- }
- .check-summary {
- display: flex;
- margin-top: 10px;
- >div {
- flex: 1;
- padding: 5px 10px;
- display: flex;
- background: #fafbfc;
- border: 1px solid #e4e5e9;
- border-radius: 4px;
- &:not(:first-child) {
- margin-left: 10px;
- }
- img {
- height: 40px;
- }
- .check-name {
- margin-left: 10px;
- div:first-child {
- font-size: 16px;
- font-weight: 500;
- }
- div:last-child {
- font-size: 14px;
- }
- }
- }
- }
- .check-rank {
- margin-top: 10px;
- display: flex;
- justify-content: space-between;
- >div:first-child {
- font-size: 14px;
- }
- }
- </style>
|