feat: 新增dev分支作为0812版本改版

dev
donghao 1 month ago
parent 40f28bf4e3
commit 03cb778c4b

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

@ -0,0 +1,281 @@
<template>
<div class="chart1-container">
<!-- 主环形图 -->
<div class="main-chart" ref="mainChartRef"></div>
<!-- 右侧统计面板 -->
<div class="stats-panel">
<div class="chart-items">
<div class="chart-item" v-for="(item, index) in chartData" :key="index">
<!-- ECharts 小饼图容器 -->
<div class="mini-chart" :ref="chartRefs[index]"></div>
<!-- 文本内容 -->
<div class="item-text">
<span class="percent" :style="{ color: item.color }"
>{{ item.value }}%</span
>
<span class="desc">{{ item.name }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref, watchEffect } from "vue";
import * as echarts from "echarts";
interface ChartItem {
percent: number;
desc: string;
color: string;
pieData: number[]; // [65, 35]
pieColors: string[]; //
}
export default defineComponent({
name: "Chart1",
setup() {
//
const mainChartRef = ref<HTMLDivElement | null>(null);
const miniChartRef = ref<HTMLDivElement | null>(null);
//
let mainChart: echarts.ECharts | null = null;
let miniChart: echarts.ECharts | null = null;
// ECharts ref
const chartRefs = ref<Ref<HTMLDivElement | null>[]>([]);
// const chartData = [
// {
// percent: 65,
// desc: "",
// color: "#488AFE",
// pieData: [65, 35],
// pieColors: ["#488AFE", "#ffffff44"],
// },
// {
// percent: 35,
// desc: "",
// color: "#F7D36D",
// pieData: [35, 65],
// pieColors: ["#F7D36D", "#ffffff44"],
// },
// ];
//
const chartData = [
{ value: 65, name: "撑杆折断", color: "#488AFE" },
{ value: 35, name: "撑杆弯曲", color: "#F7D36D" },
];
//
const initMainChart = () => {
if (!mainChartRef.value) return;
//
if (mainChart) {
mainChart.dispose();
}
mainChart = echarts.init(mainChartRef.value);
//
const blueGradient = new echarts.graphic.LinearGradient(0, 0, 1, 1, [
{ offset: 0, color: "#488AFE" },
{ offset: 1, color: "#0E5BCC" },
]);
const yellowGradient = new echarts.graphic.LinearGradient(0, 0, 1, 1, [
{ offset: 0, color: "#F7D36D" },
{ offset: 1, color: "#E6B83D" },
]);
const option = {
backgroundColor: "transparent",
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b}: {c}%",
},
series: [
{
name: "状态占比",
type: "pie",
radius: ["50%", "80%"], //
center: ["50%", "50%"],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 8,
borderColor: "#0b1b3a", //
borderWidth: 2,
},
label: {
show: false,
position: "center",
},
emphasis: {
label: {
show: true,
fontSize: "20",
fontWeight: "bold",
color: "#fff",
},
},
labelLine: {
show: false,
},
data: [
{
value: chartData[0].value,
name: chartData[0].name,
itemStyle: { color: blueGradient },
},
{
value: chartData[1].value,
name: chartData[1].name,
itemStyle: { color: yellowGradient },
},
],
},
],
};
mainChart.setOption(option);
};
//
const handleResize = () => {
mainChart?.resize();
miniChart?.resize();
};
onMounted(() => {
initMainChart();
window.addEventListener("resize", handleResize);
chartData.forEach((item, index) => {
const myChart = echarts.init(
chartRefs.value[index].value as HTMLDivElement
);
const option = {
series: [
{
type: "pie",
radius: ["40%", "70%"],
center: ["50%", "50%"],
data: item.pieData,
itemStyle: {
color: (params: any) => item.pieColors[params.dataIndex],
borderColor: "#0b1b3a", //
borderWidth: 1,
},
label: { show: false },
labelLine: { show: false },
},
],
};
myChart.setOption(option);
});
});
//
const cleanup = () => {
window.removeEventListener("resize", handleResize);
mainChart?.dispose();
miniChart?.dispose();
};
//
watchEffect((onCleanup) => {
onCleanup(cleanup);
});
return {
mainChartRef,
miniChartRef,
chartRefs,
chartData,
};
},
});
</script>
<style lang="scss" scoped>
.chart1-container {
display: flex;
align-items: center;
gap: 24px;
padding: 20px;
border-radius: 12px;
width: 100%;
height: 100%;
//
.main-chart {
width: 100%;
height: 100%;
position: relative;
//
&::after {
content: "总计";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 18px;
font-weight: 500;
text-align: center;
line-height: 1.5;
}
}
//
.chart-items {
display: flex;
flex-direction: column;
gap: 10px;
.chart-item {
display: flex;
align-items: center;
gap: 8px;
.mini-chart {
width: 40px;
height: 40px;
}
.item-text {
display: flex;
flex-direction: column;
.percent {
font-weight: bold;
margin-bottom: 2px;
}
.desc {
font-size: 14px;
color: #ccc;
}
}
}
}
}
//
@media (max-width: 500px) {
.chart1-container {
flex-direction: column;
.main-chart {
width: 200px;
height: 200px;
}
}
}
</style>

@ -0,0 +1,320 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch, nextTick } from "vue";
import * as echarts from "echarts";
// props
const props = defineProps({
xData: {
type: Array as PropType<Array<string>>,
default: () => [],
},
legendArr: {
type: Array as PropType<Array<string>>,
default: () => [],
},
datas: {
type: Array as PropType<Array<Array<number>>>,
default: () => [],
},
colorArr: {
type: Array as PropType<Array<Array<string>>>,
default: () => [],
},
});
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
// 3D
const CubeLeft = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const { xAxisPoint } = shape;
const [c0, c1, c2, c3] = [
[shape.x, shape.y],
[shape.x - 12, shape.y - 6],
[xAxisPoint[0] - 12, xAxisPoint[1] - 6],
[xAxisPoint[0], xAxisPoint[1]],
];
ctx
.moveTo(c0[0], c0[1])
.lineTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.closePath();
},
});
const CubeRight = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const { xAxisPoint } = shape;
const [c1, c2, c3, c4] = [
[shape.x, shape.y],
[xAxisPoint[0], xAxisPoint[1]],
[xAxisPoint[0] + 12, xAxisPoint[1] - 6],
[shape.x + 12, shape.y - 6],
];
ctx
.moveTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.lineTo(c4[0], c4[1])
.closePath();
},
});
const CubeTop = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const [c1, c2, c3, c4] = [
[shape.x, shape.y],
[shape.x + 12, shape.y - 6],
[shape.x, shape.y - 12],
[shape.x - 12, shape.y - 6],
];
ctx
.moveTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.lineTo(c4[0], c4[1])
.closePath();
},
});
//
echarts.graphic.registerShape("CubeLeft", CubeLeft);
echarts.graphic.registerShape("CubeRight", CubeRight);
echarts.graphic.registerShape("CubeTop", CubeTop);
//
const initChart = () => {
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
setTimeout(() => {
chartInstance?.resize();
updateChart();
}, 500);
};
//
const updateChart = () => {
if (!chartInstance) return;
const series = props.datas
.map((item, index) => [
{
type: "custom",
name: props.legendArr[index],
renderItem: (params, api) => ({
type: "group",
x: (index - props.datas.length / 2) * 30 + 15,
children: [
{
type: "CubeLeft",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][0],
},
]),
},
},
{
type: "CubeRight",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][0],
},
]),
},
},
{
type: "CubeTop",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][1],
},
]),
},
},
],
}),
data: item,
},
{
type: "bar",
name: props.legendArr[index],
barWidth: 25,
// label: {
// normal: {
// show: true,
// position: "top",
// fontSize: 16,
// color: "#fff",
// offset: [0, -10],
// },
// },
itemStyle: { color: "transparent" },
data: item,
yAxisIndex: 0, // 使y
},
{
type: "bar",
name: props.legendArr[index],
barWidth: 25,
itemStyle: { color: "transparent" },
data: item,
yAxisIndex: 1, // 使y
},
])
.flat();
chartInstance.setOption({
tooltip: {
trigger: "axis",
borderWidth: 0,
backgroundColor: "rgba(8,36,68,.9)",
color: "#fff",
textStyle: { color: "#fff" },
formatter: (params) => {
let str = params[0].name + "</br>";
params.forEach((item, index) => {
if (item.seriesType === "custom") {
str += `
<div style='display:flex;justify-content:space-between;align-items:center'>
<div style='margin-right:20px;'>
<span style="display:inline-block;width:10px;height:10px;border-radius:5px;background-color:${
props.colorArr[index % props.colorArr.length][0]
}"></span>
&nbsp;${item.seriesName}
</div>
<span>&nbsp;${item.value ? item.value : "-"}</span>
</div>`;
}
});
return str;
},
},
grid: {
left: "5%",
right: "5%",
top: "5%",
bottom: "10%",
containLabel: true,
},
legend: {
left: "center",
top: "90%",
itemWidth: 12, //
itemHeight: 8,
textStyle: { color: "#fff", fontSize: 12 },
data: props.legendArr.map((name, index) => ({
name,
textStyle: { color: "#fff", fontSize: 12 },
itemStyle: { color: props.colorArr[index % props.colorArr.length][1] },
})),
},
xAxis: {
type: "category",
data: props.xData,
axisLine: { lineStyle: { color: "rgba(239, 247, 253, .1)" } },
axisLabel: { fontSize: 12, color: "#fff", margin: 12 },
},
yAxis: [
{
// name: "kWh",
// nameTextStyle: { color: "#fff", fontSize: 12 },
splitLine: {
lineStyle: { type: "dashed", color: "rgba(80,112,242,0.3)" },
},
axisLabel: { textStyle: { color: "#8C8C8C" }, fontSize: 12 },
// axisLine: { lineStyle: { color: "#8C8C8C" } },
// scale: true, //
},
{
// name: "kWh",
// nameTextStyle: { color: "#fff", fontSize: 12 },
splitLine: { lineStyle: { color: "transparent" } },
axisLabel: { textStyle: { color: "#8C8C8C" }, fontSize: 12 },
// axisLine: { lineStyle: { color: "#8C8C8C" } },
position: "right",
// scale: true, //
},
],
series,
});
};
const handleResize = () => {
chartInstance?.resize();
};
//
onMounted(async () => {
await nextTick();
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
chartInstance?.resize();
}
});
//
watch(
() => props,
async () => {
await nextTick();
updateChart();
// delay(600).then(() => resize());
},
{
deep: true,
immediate: true,
}
);
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 100%" />
</template>

@ -0,0 +1,65 @@
<template>
<div id="pieChart" style="width: 100%; height: 100%"></div>
</template>
<script setup>
import * as echarts from "echarts";
function initChart() {
const chartDom = document.getElementById("pieChart");
const myChart = echarts.init(chartDom);
const option = {
backgroundColor: "",
tooltip: {
trigger: "item",
formatter: "{b}: {c} ({d}%)",
},
series: [
{
type: "pie",
radius: ["10%", "50%"],
avoidLabelOverlap: false,
label: {
show: true,
position: "outer",
formatter: "{b}: {d}%",
color: "#fff", // Set label text color to white
},
labelLine: {
show: true,
lineStyle: {
color: "#fff", // Set label line color to white
},
},
data: [
{ value: 45, name: "小门搭扣丢失", itemStyle: { color: "#29ABE2" } },
{ value: 21, name: "门折页座脱落", itemStyle: { color: "#1BBC9B" } },
{ value: 8, name: "小窗裂纹", itemStyle: { color: "#F64747" } },
{ value: 6, name: "搭扣未搭", itemStyle: { color: "#9B59B6" } },
{ value: 5, name: "搭扣未搭", itemStyle: { color: "#F9690E" } },
{ value: 5, name: "下侧门板缺失", itemStyle: { color: "#F7CA18" } },
{
value: 10,
name: "Other",
itemStyle: { color: "rgba(255,255,255,0.1)" },
label: { show: false },
labelLine: { show: false },
}, // Remaining percentage
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
myChart.setOption(option);
}
onMounted(() => {
initChart();
});
</script>

@ -97,7 +97,10 @@
/* full_table */
&.full_table {
.el-table--large .el-table__cell {
padding: 4px 0;
padding: 8.5px 0;
}
.el-table__body .el-table__cell {
padding: 10px 0;
}
.baseTable_box {
cursor: default;
@ -118,7 +121,6 @@
}
&:hover td {
background-color: transparent;
}
}
}

@ -0,0 +1,54 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 15:52:40
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-08-12 16:29:23
* @FilePath: \vite-ai\data-dashboard\src\components\contentHeader.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="flex items-center justify-between content_header_wrap"
:style="{ 'background-image': `url(${sub_title_bg})`, 'background-size': 'cover', 'background-repeat': 'no-repeat', 'background-position': 'left bottom' }">
<!-- 左侧标题区域 -->
<div class="flex items-center left-section pl-[16px]">
<img src="@/assets/common/alarm_title.png" class="w-[16px] h-[20px]">
<div class="fg-title ml-[12px] text-[18px]">
{{ title }}
<slot name="title"></slot>
</div>
</div>
<!-- 右侧内容 -->
<div>
<slot name="extra"></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import sub_title_bg from '@/assets/home/sub_title_bg.png';
// props
const props = withDefaults(defineProps<{
// bgLayout: number | string; // 1855 918 800 450
title: string; //
}>(), {
// bgLayout: 1855,
title: ""
});
//
// const bgImageUrl = computed(() => {
// switch (Number(props.bgLayout)) {
// default:
// return sub_title_bg;
// }
// });
</script>
<style scoped lang="scss">
.content_header_wrap {
width: 431px;
height: 38px;
// border-image: linear-gradient(90deg, rgba(25, 61, 72, 0), rgba(67, 215, 226, 1), rgba(29, 65, 76, 0)) 1 1;
}
</style>

@ -1,5 +1,5 @@
@import url('./fonts.scss');
@import url('./element-plus.scss');
@import url("./fonts.scss");
@import url("./element-plus.scss");
//
.bg_title {
@ -17,15 +17,86 @@
}
}
.bg_basic_content{
background: linear-gradient( 180deg, rgba(7,16,19,0) 0%, #081417 100%);;
.bg-basic-content {
background: linear-gradient(180deg, rgba(7, 16, 19, 0) 0%, #081417 100%);
}
.bg_error_picture {
.bg-error-picture {
width: 100%;
height: 100%;
background: url("@/assets/common/load_file_error.png") no-repeat center center;
background-size: 50%;
border: 1px dashed red;
}
}
//
.fg-title {
font-family: "DingTalk JinBuTi"; //
//
font-size: 16px;
font-weight: 700; //
color: #96e6ff; // ,
//
background: linear-gradient(to bottom, #ffffff, #96e6ff);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
//
.fg-icon {
width: 17px;
height: 17px;
background-image: url("@/assets/common/title_before_icon.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
//
.fg-button {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
height: 28px;
border-radius: 2px 2px 2px 2px;
}
.fg-button-primary {
@apply fg-button;
background: #009dff;
}
//
.fg-dialog {
background-color: transparent;
background-image: url("@/assets/common/dialog_bg1.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
padding: 0 !important;
&.fg-dialog2 {
width: 1202px;
height: 602px;
background-image: url("@/assets/common/dialog_bg2.png");
}
.fg-dialog-header {
position: relative;
background-color: transparent;
background-image: url("@/assets/common/dialog_head_bg.png");
background-repeat: no-repeat;
.fg-dialog-header-close {
width: 50px;
height: 50px;
position: absolute;
right: -17px;
top: -17px;
z-index: 999999 !important;
background-image: url("@/assets/common/dialog_close_icon.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
}
}

@ -1,7 +1,9 @@
// src/assets/fonts.scss
@font-face {
font-family: 'DingTalk JinBuTi'; //
src: url('@/assets/fonts/DingTalk JinBuTi.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
font-family: "DingTalk JinBuTi"; //
src: url("@/assets/fonts/DingTalk JinBuTi.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}

@ -1,72 +1,91 @@
.data-overview-wrap {
padding-top: 30px;
// background: #002a5c;
height: 849px;
color: white;
font-family: "Arial", sans-serif;
.grid-container {
display: flex;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 20px;
width: 100%;
display: grid;
gap: 16px;
.grid-item {
// background: rgba(74, 126, 191, 0.1);
background-image: url("@/assets/common/gridItemBg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
// border-radius: 8px;
width: 50%;
&>li{
width: 50%;
}
&>li:last-child{
margin-left: 12px;
}
.module-header {
color: #4a7ebf;
margin-bottom: 15px;
margin-bottom: 4px;
.month-btn,
.week-btn {
width: 70px;
height: 28px;
font-size: 14px;
color: #FFF;
background: #0F2839;
color: #fff;
background: #0f2839;
border: none;
}
.week-btn {
margin-right: 20px;
}
.active-btn {
background: #0B345E;
border: 1px solid #3FDDEB;
.active-btn {
background: #0b345e;
border: 1px solid #3fddeb;
}
}
.chart-container {
// margin-top: 0px;
width: 100%;
height: 300px;
width: 431px;
height: 221px;
background: url("@/assets/home/sub_content_bg.png") no-repeat center;
}
.chart-container-bar {
// margin-top: 0px;
}
}
.onTime-monitor-box {
// background: red;
margin: 0 24px;
}
.monitor-images {
display: flex;
box-sizing: border-box;
gap: 16px;
padding: 20px 16px;
flex-wrap: wrap;
.monitor-images-item {
width: calc(50% - 8px);
// background-color: red;
height: 349px;
border: 1px dashed #ccc;
}
.monitor-images-left,
.monitor-images-right {
// flex: 1;
// position: relative;
img {
width: 100%;
height: 340px;
// height: 100%;
}
.chart-pie-bg{
margin-top: 19px;
background-image: url("@/assets/common/gridItemPieBg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
.fault-info {
position: absolute;
padding: 0 10px;
// miomwidth: 80px;
height: 28px;
background: rgba(0, 0, 0, 0.6);
border-radius: 16px 16px 16px 16px;
top: 16px;
left: 16px;
font-size: 14px;
color: #fff;
text-align: center;
line-height: 28px;
}
}
.grid-item-pie {
background-image: none;
}
}
.device-info {
.device-info {
.total-device {
display: flex;
align-items: center;
@ -76,8 +95,7 @@
.device-icon {
width: 80px;
height: 90px;
background: url('@/assets/common/deviceTotal.png')
no-repeat center;
background: url("@/assets/common/deviceTotal.png") no-repeat center;
background-size: 100%;
}
@ -94,7 +112,7 @@
.device-list {
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(3, 1fr);
// grid-template-columns: repeat(3, 1fr);
padding: 0 16px 20px;
gap: 10px;
@ -114,36 +132,4 @@
}
}
}
.monitor-images {
display: flex;
box-sizing: border-box;
gap: 16px;
padding: 20px 16px 20px 16px;
.monitor-images-left,
.monitor-images-right {
flex: 1;
position: relative;
img {
width: 100%;
height: 256px;
}
.fault-info {
position: absolute;
padding: 0 10px;
// miomwidth: 80px;
height: 28px;
background: rgba(0,0,0,0.6);
border-radius: 16px 16px 16px 16px;
top: 16px;
left: 16px;
font-size: 14px;
color: #FFF;
text-align: center;
line-height: 28px;
}
}
}
}

@ -1,178 +1,28 @@
<template>
<div class="data-overview-wrap">
<!-- 检测总量汇总 -->
<div class="grid-container">
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_0"></div>
</template>
<template #extra>
<div>
<el-button type="primary" class="month-btn" @click="getList('month')"
:class="{ 'active-btn': activeBtn === 'month' }">
</el-button>
<el-button class="week-btn" @click="getList('week')" :class="{ 'active-btn': activeBtn === 'week' }">
</el-button>
</div>
</template>
</ContentHeader>
</div>
<div class="chart-container chart-container-bar">
<BarChart :xData="xData" :legendArr="legendArr" :datas="datas" :colorArr="colorArr" />
</div>
</div>
<!-- 设备信息 -->
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_1"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="device-info">
<div class="total-device">
<div class="device-icon"></div>
<div class="device-count">
<div>设备总数</div>
<div class="count-number">{{ deviceTotal }}</div>
</div>
</div>
<div class="device-list">
<div class="device-card">
<div class="mb-3">车体检测设备: {{ carDevice?.total || 0 }}</div>
<DeviceStatus :deviceStatus="carDevice" />
</div>
<div class="device-card">
<div class="mb-3">撑杆检测设备: {{ poleDevice?.total || 0 }}</div>
<DeviceStatus :deviceStatus="poleDevice" />
</div>
<div class="device-card">
<div class="mb-3">钩机检测设备: {{ excavatorDevice?.total || 0 }}</div>
<DeviceStatus :deviceStatus="excavatorDevice" />
</div>
</div>
</div>
</div>
</div>
<!-- 中部检测模块 -->
<div class="grid-container">
<ul class="flex grid-item grid-item-pie">
<li>
<div class="module-header">
<ContentHeader bgLayout="450">
<template #title>
<div class="w-[200px] bg_title bg_title_2"></div>
</template>
<template #extra>
<el-select v-model="searchForm.car" placeholder="时间" class="custom-select" @change="getCarFault()">
<el-option label="1月" value="1"></el-option>
<el-option label="2月" value="2"></el-option>
<el-option label="3月" value="3"></el-option>
<el-option label="4月" value="4"></el-option>
<el-option label="5月" value="5"></el-option>
<el-option label="6月" value="6"></el-option>
<el-option label="7月" value="7"></el-option>
<el-option label="8月" value="8"></el-option>
<el-option label="9月" value="9"></el-option>
<el-option label="10月" value="10"></el-option>
<el-option label="11月" value="11"></el-option>
<el-option label="12月" value="12"></el-option>
</el-select>
</template>
</ContentHeader>
</div>
<div class="chart-container chart-pie-bg">
<PieChart :data="carFaultTotal" :colors="[
'#FF7C09',
'#0032FF',
'#04FFF2',
'#D19EFF',
'#FF0103',
'#9EFFF3',
]" />
</div>
</li>
<li>
<div class="module-header">
<ContentHeader bgLayout="450">
<template #title>
<div class="w-[200px] bg_title bg_title_3"></div>
</template>
<template #extra>
<el-select v-model="searchForm.pole" placeholder="时间" class="custom-select" @change="getPoleFault()">
<el-option label="1月" value="1"></el-option>
<el-option label="2月" value="2"></el-option>
<el-option label="3月" value="3"></el-option>
<el-option label="4月" value="4"></el-option>
<el-option label="5月" value="5"></el-option>
<el-option label="6月" value="6"></el-option>
<el-option label="7月" value="7"></el-option>
<el-option label="8月" value="8"></el-option>
<el-option label="9月" value="9"></el-option>
<el-option label="10月" value="10"></el-option>
<el-option label="11月" value="11"></el-option>
<el-option label="12月" value="12"></el-option>
</el-select>
</template>
</ContentHeader>
</div>
<div class="chart-container chart-pie-bg">
<PieChartSmall :data="poleFaultTotal" :colors="['#9DFFF3', '#FFA179']" />
</div>
</li>
</ul>
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_4"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="monitor-images">
<div class="monitor-images-left">
<img :src="imageFault[0]?.url" />
<div class="fault-info">{{ imageFault[0]?.fault_type }}</div>
</div>
<div class="monitor-images-right">
<img :src="imageFault[1]?.url"/>
<div class="fault-info">{{ imageFault[1]?.fault_type }}</div>
</div>
</div>
</div>
</div>
<AlarmModal v-model:value="isAlarmOpen" :info="currentRow" :image="currFileList" @close="isAlarmOpen = false" />
</div>
</template>
// TODO
<script lang="ts" setup>
import * as echarts from "echarts";
import ContentHeader from "@/components/ContentHeader.vue";
import TotalChart from "@/components/Charts/totalChart.vue";
import PoleMonitorChart from "@/components/Charts/poleMonitorChart.vue";
import VehicleMonitorChart from "@/components/Charts/vehicleMonitorChart.vue";
import HomeSubTitle from "@/components/HeaderBar/homeSubTitle.vue";
// import HomeSubTitle from "@/components/HomeSubTitle.vue";
import BarChart from "./components/BarChart.vue";
import PieChart from "./components/PieChart.vue";
import PieChartSmall from "./components/PieChartSmall.vue";
import DeviceStatus from "./components/DeviceStatus.vue";
import { getDataOverviewApi, getDeviceInfowApi, getRecordFaultApi, getRealTimeApi } from '@/api/dashboard';
import {
getDataOverviewApi,
getDeviceInfowApi,
getRecordFaultApi,
getRealTimeApi,
} from "@/api/dashboard";
import { isSuccessApi } from "@/utils/forApi";
import AlarmModal from './components/AlarmModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
import { onBeforeRouteLeave } from 'vue-router';
import AlarmModal from "./components/AlarmModal.vue";
import { useWebSocketStore } from "@/stores/websocketStore";
import { onBeforeRouteLeave } from "vue-router";
defineOptions({
name: "DataOverviewWrap"
name: "DataOverviewWrap",
});
const xData = ref(["1月", "2月", "3月", "4月", "5月"]);
const legendArr = ["车体检测", "撑杆检测"];
@ -187,7 +37,7 @@ const colorArr = [
const deviceStatus = ref({
onlineCount: 50,
errorCount: 10,
outlineCount: 10
outlineCount: 10,
});
const searchForm = reactive({
car: "1",
@ -207,33 +57,36 @@ const currFileList = ref<Record<string, any>[]>([]); // 详情的文件列表
const websocketStore = useWebSocketStore();
// messages
watch(() => websocketStore.messages, (newMessages: string[], oldMessages: string[]) => {
if(newMessages?.length > 0 && !isAlarmOpen.value) {
currentRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
watch(
() => websocketStore.messages,
(newMessages: string[], oldMessages: string[]) => {
if (newMessages?.length > 0 && !isAlarmOpen.value) {
currentRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
}
}, { deep: true, immediate: true });
},
{ deep: true, immediate: true }
);
const getList = async (dateType: string = "month") => {
activeBtn.value = dateType
const res = await getDataOverviewApi({ dateType })
activeBtn.value = dateType;
const res = await getDataOverviewApi({ dateType });
if (isSuccessApi(res)) {
const { data } = res
datas.value[0] = data.appearance
datas.value[1] = data.pole
const { data } = res;
datas.value[0] = data.appearance;
datas.value[1] = data.pole;
if (dateType === "month") {
xData.value = data.dateArr.map((item: any) => {
if (dateType === 'month') {
return item + '月'
if (dateType === "month") {
return item + "月";
}
})
});
} else {
xData.value = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
xData.value = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
}
// console.log(data, 'getList_data')
}
};
const getDeviceInfo = async () => {
try {
@ -247,12 +100,16 @@ const getDeviceInfo = async () => {
// deviceStatus.value = data
}
} catch (error) {
console.error('获取设备信息出错:', error);
console.error("获取设备信息出错:", error);
}
}
};
const getPoleFault = async () => {
try {
const res = await getRecordFaultApi({ dateType: 'month', value: searchForm.pole, type: "pole" });
const res = await getRecordFaultApi({
dateType: "month",
value: searchForm.pole,
type: "pole",
});
if (isSuccessApi(res)) {
const { data } = res;
poleFaultTotal.value = data;
@ -260,12 +117,16 @@ const getPoleFault = async () => {
console.log(data);
}
} catch (error) {
console.error('获取设备信息出错:', error);
console.error("获取设备信息出错:", error);
}
}
};
const getCarFault = async () => {
try {
const res = await getRecordFaultApi({ dateType: 'month', value: searchForm.car, type: "appearance" });
const res = await getRecordFaultApi({
dateType: "month",
value: searchForm.car,
type: "appearance",
});
if (isSuccessApi(res)) {
const { data } = res;
carFaultTotal.value = data;
@ -273,13 +134,13 @@ const getCarFault = async () => {
console.log(data);
}
} catch (error) {
console.error('获取设备信息出错:', error);
console.error("获取设备信息出错:", error);
}
}
};
const getRealTime = async () => {
try {
const res = await getRealTimeApi({ deviceType: '' });
const res = await getRealTimeApi({ deviceType: "" });
if (isSuccessApi(res)) {
const { data } = res;
imageFault.value = data;
@ -287,23 +148,193 @@ const getRealTime = async () => {
console.log(data);
}
} catch (error) {
console.error('获取设备信息出错:', error);
console.error("获取设备信息出错:", error);
}
}
};
onBeforeRouteLeave(() => {
isAlarmOpen.value = false;
currentRow.value = {};
currFileList.value = [];
isAlarmOpen.value = false;
currentRow.value = {};
currFileList.value = [];
});
onMounted(() => {
getList()
getDeviceInfo()
getCarFault()
getPoleFault()
getRealTime()
getList();
getDeviceInfo();
getCarFault();
getPoleFault();
getRealTime();
});
</script>
<template>
<div class="flex data-overview-wrap">
<!-- 检测总量汇总 -->
<div class="grid-container">
<div class="grid-item">
<div class="module-header">
<HomeSubTitle title="检测总量汇总" >
<template #extra>
<div>
<el-button
type="primary"
class="month-btn"
@click="getList('month')"
:class="{ 'active-btn': activeBtn === 'month' }"
>
</el-button>
<el-button
class="week-btn"
@click="getList('week')"
:class="{ 'active-btn': activeBtn === 'week' }"
>
</el-button>
</div>
</template>
</HomeSubTitle>
</div>
<div class="chart-container">
<TotalChart
:xData="xData"
:legendArr="legendArr"
:datas="datas"
:colorArr="colorArr"
/>
</div>
</div>
<div class="grid-item">
<div class="module-header">
<HomeSubTitle title="车体监测" >
<template #extra>
<el-select
v-model="searchForm.car"
placeholder="时间"
class="custom-select"
@change="getCarFault()"
>
<el-option label="1月" value="1"></el-option>
<el-option label="2月" value="2"></el-option>
<el-option label="3月" value="3"></el-option>
<el-option label="4月" value="4"></el-option>
<el-option label="5月" value="5"></el-option>
<el-option label="6月" value="6"></el-option>
<el-option label="7月" value="7"></el-option>
<el-option label="8月" value="8"></el-option>
<el-option label="9月" value="9"></el-option>
<el-option label="10月" value="10"></el-option>
<el-option label="11月" value="11"></el-option>
<el-option label="12月" value="12"></el-option>
</el-select>
</template>
</HomeSubTitle>
</div>
<div class="chart-container chart-pie-bg">
<!-- <PieChart :data="carFaultTotal" :colors="[
'#FF7C09',
'#0032FF',
'#04FFF2',
'#D19EFF',
'#FF0103',
'#9EFFF3',
]" /> -->
<VehicleMonitorChart />
</div>
</div>
<div class="grid-item">
<div class="module-header">
<HomeSubTitle title="撑杆监测">
<template #extra>
<el-select
v-model="searchForm.pole"
placeholder="时间"
class="custom-select"
@change="getPoleFault()"
>
<el-option label="1月" value="1"></el-option>
<el-option label="2月" value="2"></el-option>
<el-option label="3月" value="3"></el-option>
<el-option label="4月" value="4"></el-option>
<el-option label="5月" value="5"></el-option>
<el-option label="6月" value="6"></el-option>
<el-option label="7月" value="7"></el-option>
<el-option label="8月" value="8"></el-option>
<el-option label="9月" value="9"></el-option>
<el-option label="10月" value="10"></el-option>
<el-option label="11月" value="11"></el-option>
<el-option label="12月" value="12"></el-option>
</el-select>
</template>
</HomeSubTitle>
</div>
<div class="chart-container chart-pie-bg">
<PoleMonitorChart />
<!-- <PieChartSmall :data="poleFaultTotal" :colors="['#9DFFF3', '#FFA179']" /> -->
</div>
</div>
</div>
<!-- 中部检测模块 -->
<div class="onTime-monitor-box">
<div class="module-header fg-title">
实时监测画面
</div>
<div class="monitor-images">
<div class="monitor-images-left monitor-images-item">
<img :src="imageFault[0]?.url" />
<div class="fault-info">{{ imageFault[0]?.fault_type }}</div>
</div>
<div class="monitor-images-right monitor-images-item">
<img :src="imageFault[1]?.url" />
<div class="fault-info">{{ imageFault[1]?.fault_type }}</div>
</div>
<div class="monitor-images-left monitor-images-item">
<img :src="imageFault[0]?.url" />
<div class="fault-info">{{ imageFault[0]?.fault_type }}</div>
</div>
<div class="monitor-images-right monitor-images-item">
<img :src="imageFault[1]?.url" />
<div class="fault-info">{{ imageFault[1]?.fault_type }}</div>
</div>
</div>
</div>
<!-- 设备信息 -->
<div class="grid-container">
<div class="module-header">
<HomeSubTitle title="设备信息">
</HomeSubTitle>
</div>
<div class="device-info">
<div class="total-device">
<div class="device-icon"></div>
<div class="device-count">
<div>设备总数</div>
<div class="count-number">{{ deviceTotal }}</div>
</div>
</div>
<div class="device-list">
<div class="device-card">
<div class="mb-3">车体检测设备: {{ carDevice?.total || 0 }}</div>
<DeviceStatus :deviceStatus="carDevice" />
</div>
<div class="device-card">
<div class="mb-3">撑杆检测设备: {{ poleDevice?.total || 0 }}</div>
<DeviceStatus :deviceStatus="poleDevice" />
</div>
<div class="device-card">
<div class="mb-3">
钩机检测设备: {{ excavatorDevice?.total || 0 }}
</div>
<DeviceStatus :deviceStatus="excavatorDevice" />
</div>
</div>
</div>
</div>
<AlarmModal
v-model:value="isAlarmOpen"
:info="currentRow"
:image="currFileList"
@close="isAlarmOpen = false"
/>
</div>
</template>
<style scoped lang="scss">
@import url("./DataOverview.scss");
</style>

@ -1,108 +1,125 @@
<template>
<div class="bg_basic_content">
<div class="device-status-wrap">
<div class="device-status-header mt-[32px]">
<ContentHeader bgLayout="1855">
<template #title>
<div class="w-[200px] bg_title bg_title_6">
</div>
</template>
</ContentHeader>
</div>
<div class="px-[16px] device-status-content-box">
<div class="bg-basic-content">
<div class="device-status-wrap">
<div class="device-status-header mt-[32px]">
<ContentHeader bgLayout="1855">
<template #title>
<div class="w-[200px] bg_title bg_title_6"></div>
</template>
</ContentHeader>
</div>
<div class="px-[16px] device-status-content-box">
<div class="mt-[16px] bg-transparent baseTable_wrap full_table">
<BaseTable
class="bg-transparent baseTable_box"
:total="pagination.total"
:pageSize="pagination.pageSize"
:dataSource="listData"
:isFixedPagination="true"
:columns="columns"
:page="pagination.currentPage"
@change="handleTableChange"
>
<template v-slot:actionBar="{ row }">
<ul class="flex table_action_box">
<li
class="flex items-center mr-[16px]"
@click="openCurrent(row)"
>
<el-button text>
<span
:style="{
fontSize: '14px',
color: '#37DBFF',
}"
>
即时视频
</span>
</el-button>
</li>
<div class="mt-[16px] bg-transparent baseTable_wrap full_table">
<BaseTable class="bg-transparent baseTable_box" :total="pagination.total"
:pageSize="pagination.pageSize" :dataSource="listData" :isFixedPagination="true"
:columns="columns" :page="pagination.currentPage" @change="handleTableChange">
<template v-slot:actionBar="{ row }">
<ul class="flex table_action_box">
<li class="flex items-center mr-[16px]" @click="openCurrent(row)">
<el-button text>
<span :style="{
fontSize: '14px',
color: '#37DBFF'
}">
即时视频
</span>
</el-button>
</li>
<li class="flex items-center" @click="openHistory(row)">
<el-button text>
<span :style="{
fontSize: '14px',
color: '#37DBFF'
}">
历史视频
</span>
</el-button>
</li>
</ul>
</template>
</BaseTable>
</div>
</div>
<RealVideoModal v-model:value="isRealOpen" :info="currentRow" @close="isRealOpen = false" />
<HistoryVideoModal ref="historyModalRef" v-model:value="isHistoryOpen" :info="currentRow"
:historyVideos="historyVideos" @close="isHistoryOpen = false" />
<AlarmModal v-model:value="isAlarmOpen" :info="currentDetailRow" :image="currFileList" @close="isAlarmOpen = false" />
<li class="flex items-center" @click="openHistory(row)">
<div class="fg-button-primary">查看详情</div>
</li>
</ul>
</template>
</BaseTable>
</div>
</div>
<RealVideoModal
v-model:value="isRealOpen"
:info="currentRow"
@close="isRealOpen = false"
/>
<HistoryVideoModal
ref="historyModalRef"
v-model:value="isHistoryOpen"
:info="currentRow"
:historyVideos="historyVideos"
@close="isHistoryOpen = false"
/>
<AlarmModal
v-model:value="isAlarmOpen"
:info="currentDetailRow"
:image="currFileList"
@close="isAlarmOpen = false"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { BaseTable } from "@/components/CustomTable";
import ContentHeader from '@/components/ContentHeader.vue';
import HistoryVideoModal from './components/HistoryVideoModal.vue';
import RealVideoModal from './components/RealVideoModal.vue';
import onLineIcon from '@/assets/common/online_icon.png';
import outLineIcon from '@/assets/common/outline_icon.png';
import errorIcon from '@/assets/common/error_icon.png';
import { getDeviceStatusApi } from '@/api/dashboard';
import ContentHeader from "@/components/ContentHeader.vue";
import HistoryVideoModal from "./components/HistoryVideoModal.vue";
import RealVideoModal from "./components/RealVideoModal.vue";
import onLineIcon from "@/assets/common/online_icon.png";
import outLineIcon from "@/assets/common/outline_icon.png";
import errorIcon from "@/assets/common/error_icon.png";
import { getDeviceStatusApi } from "@/api/dashboard";
import { isSuccessApi } from "@/utils/forApi";
import AlarmModal from './components/AlarmModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
import { onBeforeRouteLeave } from 'vue-router';
import AlarmModal from "./components/AlarmModal.vue";
import { useWebSocketStore } from "@/stores/websocketStore";
import { onBeforeRouteLeave } from "vue-router";
defineOptions({
name: "DeviceStatusIndex"
name: "DeviceStatusIndex",
});
const alarmLevelStatusEnum = {
'online': {
color: "#52C41A",
value: "1",
label: "在线",
isDelete: true,
icon: onLineIcon,
id: "1"
},
'offline': {
color: "#999999",
value: "2",
label: "离线",
isDelete: false,
icon: outLineIcon,
id: "2"
},
'error': {
color: "#E80D0D",
value: "3",
label: "故障",
isDelete: false,
icon: errorIcon,
id: "3"
},
'notFound': { //
color: "#E80D0D",
value: "4444",
label: "未知",
isDelete: false,
icon: errorIcon,
id: "4444"
}
}
online: {
color: "#52C41A",
value: "1",
label: "在线",
isDelete: true,
icon: onLineIcon,
id: "1",
},
offline: {
color: "#999999",
value: "2",
label: "离线",
isDelete: false,
icon: outLineIcon,
id: "2",
},
error: {
color: "#E80D0D",
value: "3",
label: "故障",
isDelete: false,
icon: errorIcon,
id: "3",
},
notFound: {
//
color: "#E80D0D",
value: "4444",
label: "未知",
isDelete: false,
icon: errorIcon,
id: "4444",
},
};
const currentRow = ref<Record<string, any>>({});
const isRealOpen = ref<Boolean>(false); //
const isHistoryOpen = ref<Boolean>(false); //
@ -114,102 +131,107 @@ const currFileList = ref<Record<string, any>[]>([]); // 详情的文件列表
const websocketStore = useWebSocketStore();
// messages
watch(() => websocketStore.messages, (newMessages: string[], oldMessages: string[]) => {
if(newMessages?.length > 0 && !isAlarmOpen.value) {
currentDetailRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
watch(
() => websocketStore.messages,
(newMessages: string[], oldMessages: string[]) => {
if (newMessages?.length > 0 && !isAlarmOpen.value) {
currentDetailRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
}
}, { deep: true, immediate: true });
},
{ deep: true, immediate: true }
);
const columns = [
{
label: "设备名称",
property: "device_name"
},
{
label: "设备ID",
property: "device_number"
{
label: "设备名称",
property: "device_name",
},
{
label: "设备ID",
property: "device_number",
},
{
label: "设备位置",
property: "device_position",
},
{
label: "设备状态",
property: "device_status",
formatter: (val) => {
console.log(val);
const currentLevelObj =
alarmLevelStatusEnum[val?.device_status] ||
alarmLevelStatusEnum.notFound;
return h(
"div",
{
style: {
fontSize: "14px",
display: "flex",
alignItems: "center",
lineHeight: "20px",
color: currentLevelObj?.color,
},
},
[
h("img", {
src: currentLevelObj?.icon,
style: {
width: "20px",
height: "20px",
marginRight: "12px",
},
}),
h(
"span",
{
fontSize: "14px",
},
currentLevelObj?.label
),
]
);
},
{
label: "设备位置",
property: "device_position"
},
{
label: "设备状态",
property: "device_status",
formatter: val => {
console.log(val);
const currentLevelObj =
alarmLevelStatusEnum[val?.device_status] || alarmLevelStatusEnum.notFound;
return h(
"div",
{
style: {
fontSize: "14px",
display: "flex",
alignItems: "center",
lineHeight: "20px",
color: currentLevelObj?.color
}
},
[
h('img', {
src: currentLevelObj?.icon,
style: {
width: '20px',
height: '20px',
marginRight: "12px"
}
}),
h(
"span",
{
fontSize: "14px",
},
currentLevelObj?.label
)
]
);
}
},
{
type: "action",
label: "操作"
}
},
{
type: "action",
label: "操作",
},
];
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const listData = ref([]);
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getDeviceStatusApi({ current: currentPage, pageSize })
console.log(res.data, 'getList_data')
if (isSuccessApi(res)) {
listData.value = res.data.data;
pagination.value = {
...pagination.value,
total: res.data.total
};
}
} catch (error) {
console.error('获取数据失败:', error)
try {
const { currentPage, pageSize } = pagination.value;
const res = await getDeviceStatusApi({ current: currentPage, pageSize });
console.log(res.data, "getList_data");
if (isSuccessApi(res)) {
listData.value = res.data.data;
pagination.value = {
...pagination.value,
total: res.data.total,
};
}
}
} catch (error) {
console.error("获取数据失败:", error);
}
};
function handleTableChange(record) {
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize
};
getList();
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize,
};
getList();
}
/**打开实时视频 */
function openCurrent(row) {
console.log(row, "openCurrent");
currentRow.value = row;
isRealOpen.value = true;
console.log(row, "openCurrent");
currentRow.value = row;
isRealOpen.value = true;
}
/**打开历史视频 */
// getDeviceHistoryDetailApi
@ -231,40 +253,39 @@ function openCurrent(row) {
// TODO mock
const fetchHistoryList = async () => {
try {
const resAll = await fetch('/api/v1/device/device_history/', {
method: 'POST'
})
const res = await resAll.json()
if (isSuccessApi(res)) {
historyVideos.value = res.data;
isHistoryOpen.value = true;
nextTick(() => {
historyModalRef.value.loadData()
})
console.log(res, 'fetchHistoryList_data')
}
} catch (error) {
console.error('获取数据失败:', error)
try {
const resAll = await fetch("/api/v1/device/device_history/", {
method: "POST",
});
const res = await resAll.json();
if (isSuccessApi(res)) {
historyVideos.value = res.data;
isHistoryOpen.value = true;
nextTick(() => {
historyModalRef.value.loadData();
});
console.log(res, "fetchHistoryList_data");
}
}
} catch (error) {
console.error("获取数据失败:", error);
}
};
function openHistory(row) {
console.log(row, "openHistory");
currentRow.value = row;
fetchHistoryList();
console.log(row, "openHistory");
currentRow.value = row;
fetchHistoryList();
}
onBeforeRouteLeave(() => {
isAlarmOpen.value = false;
currentDetailRow.value = {};
currFileList.value = [];
isAlarmOpen.value = false;
currentDetailRow.value = {};
currFileList.value = [];
});
onMounted(() => {
getList();
getList();
});
</script>
<style lang="scss">
@import url('./DeviceStatus.scss');
@import url("./DeviceStatus.scss");
</style>

@ -6,7 +6,7 @@
<div class="file-preview-screen">
<!-- // TODO -->
<img :src="currFileList?.[0]?.image_url" v-if="currFileList?.[0]?.image_url" />
<div v-else class="w-full h-full bg_error_picture"></div>
<div v-else class="w-full h-full bg-error-picture"></div>
</div>
</div>
</template>

@ -1,10 +1,11 @@
.vehicl-management-wrap{
height: 813px;
background-image: url("@/assets/common/device_status_bg_line.png");
background-image: url("@/assets/vehicleManage/vehicle_manage_bg.png");
background-size: 100% 100%;
background-position: bottom;
background-repeat: no-repeat;
padding: 0 43px;
}
.vehicl-management-content-box{

@ -1,59 +1,16 @@
<template>
<div class="bg_basic_content">
<div class="vehicl-management-wrap">
<div class="vehicl-management-header mt-[32px]">
<ContentHeader bgLayout="1855">
<template #title>
<div class="w-[200px] bg_title bg_title_7">
</div>
</template>
</ContentHeader>
</div>
<div class="px-[16px] vehicl-management-content-box">
<div class="mt-[16px] bg-transparent baseTable_wrap full_table">
<BaseTable class="bg-transparent baseTable_box" :total="pagination.total"
:pageSize="pagination.pageSize" :dataSource="listData" :isFixedPagination="true"
:columns="columns" :page="pagination.currentPage" @change="handleTableChange">
<template v-slot:actionBar="{ row }">
<ul class="flex table_action_box">
<li class="flex items-center mr-[16px]" @click="openCurrent(row)">
<el-button text>
<span :style="{
fontSize: '14px',
color: '#37DBFF'
}">
查看详情
</span>
</el-button>
</li>
</ul>
</template>
</BaseTable>
</div>
</div>
<VehiclModal v-model:value="isVehiclOpen" :info="currentRow" :image="currFileList" @close="isVehiclOpen = false" />
<AlarmModal v-model:value="isAlarmOpen" :info="currentDetailRow" :image="currFileList" @close="isAlarmOpen = false" />
</div>
</div>
</template>
<script setup lang="ts">
import { BaseTable } from "@/components/CustomTable";
import ContentHeader from '@/components/ContentHeader.vue';
import { getVehiclManagementApi } from '@/api/dashboard';
import { getVehiclManagementApi } from "@/api/dashboard";
import { isSuccessApi } from "@/utils/forApi";
import AlarmModal from './components/AlarmModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
import { onBeforeRouteLeave } from 'vue-router';
import AlarmModal from "./components/AlarmModal.vue";
import { useWebSocketStore } from "@/stores/websocketStore";
import { onBeforeRouteLeave } from "vue-router";
import VehiclModal from "./components/VehiclModal.vue";
defineOptions({
name: "VehiclManagementWrap"
name: "VehiclManagementWrap",
});
const currentRow = ref<Record<string, any>>({});
const currentRow = ref<Record<string, any>>({}); //
const isAlarmOpen = ref<Boolean>(false); //
const isVehiclOpen = ref<Boolean>(false); //
const currentDetailRow = ref<Record<string, any>>({}); //
@ -61,85 +18,141 @@ const currFileList = ref<Record<string, any>[]>([]); // 详情的文件列表
const websocketStore = useWebSocketStore();
// messages
watch(() => websocketStore.messages, (newMessages: string[], oldMessages: string[]) => {
if(newMessages?.length > 0 && !isAlarmOpen.value) {
currentDetailRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
watch(
() => websocketStore.messages,
(newMessages: string[], oldMessages: string[]) => {
if (newMessages?.length > 0 && !isAlarmOpen.value) {
currentDetailRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
}
}, { deep: true, immediate: true });
},
{ deep: true, immediate: true }
);
const columns = [
{
label: "车辆ID",
property: "train_id"
},
{
label: "车厢数量",
property: "carriage_account"
},
{
label: "入场时间",
property: "arrive_at"
},
{
label: "出场时间",
property: "leave_at"
},
{
label: "停留时间",
property: "stay_duration"
},
{
type: "action",
label: "操作"
}
{
label: "车辆ID",
property: "train_id",
},
{
label: "车厢数量",
property: "carriage_account",
},
{
label: "入场时间",
property: "arrive_at",
},
{
label: "出场时间",
property: "leave_at",
},
{
label: "停留时间",
property: "stay_duration",
},
{
type: "action",
label: "操作",
},
];
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const listData = ref([]);
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getVehiclManagementApi({ current: currentPage, pageSize })
console.log(res.data, 'getList_data')
if (isSuccessApi(res)) {
listData.value = res.data.data;
pagination.value = {
...pagination.value,
total: res.data.total
};
}
} catch (error) {
console.error('获取数据失败:', error)
try {
const { currentPage, pageSize } = pagination.value;
const res = await getVehiclManagementApi({
current: currentPage,
pageSize,
});
console.log(res.data, "getList_data");
if (isSuccessApi(res)) {
listData.value = res.data.data;
pagination.value = {
...pagination.value,
total: res.data.total,
};
}
}
} catch (error) {
console.error("获取数据失败:", error);
}
};
function handleTableChange(record) {
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize
};
getList();
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize,
};
getList();
}
/**查看详情 */
function openCurrent(row) {
console.log(row, "openCurrent");
currentRow.value = row;
isVehiclOpen.value = true;
console.log(row, "openCurrent");
currentRow.value = row;
isVehiclOpen.value = true;
}
onBeforeRouteLeave(() => {
isAlarmOpen.value = false;
currentDetailRow.value = {};
currFileList.value = [];
isAlarmOpen.value = false;
currentDetailRow.value = {};
currFileList.value = [];
});
onMounted(() => {
getList();
getList();
});
</script>
<template>
<div class="bg-basic-content">
<div class="vehicl-management-wrap">
<div class="vehicl-management-header mt-[24px] py-[16px] flex items-center">
<div class="fg-icon mr-[8px]"></div>
<div class="fg-title">车辆管理</div>
</div>
<div class="vehicl-management-content-box">
<div class="mt-[16px] bg-transparent baseTable_wrap full_table">
<BaseTable
class="bg-transparent baseTable_box"
:total="pagination.total"
:pageSize="pagination.pageSize"
:dataSource="listData"
:isFixedPagination="true"
:columns="columns"
:page="pagination.currentPage"
@change="handleTableChange"
>
<template v-slot:actionBar="{ row }">
<ul class="flex table_action_box">
<li
class="flex items-center mr-[16px]"
@click="openCurrent(row)"
>
<div class="fg-button-primary">
查看详情
</div>
</li>
</ul>
</template>
</BaseTable>
</div>
</div>
<VehiclModal
v-model:value="isVehiclOpen"
:info="currentRow"
:image="currFileList"
@close="isVehiclOpen = false"
/>
<AlarmModal
v-model:value="isAlarmOpen"
:info="currentDetailRow"
:image="currFileList"
@close="isAlarmOpen = false"
/>
</div>
</div>
</template>
<style lang="scss">
@import url('./VehiclManagement.scss');
@import url("./VehiclManagement.scss");
</style>

@ -1,5 +1,5 @@
<template>
<el-dialog class="digger-alarm-modal" v-model="show" @close="handleClose">
<el-dialog class="digger-alarm-modal fg-dialog" v-model="show" @close="handleClose">
<!-- 自定义标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div
@ -23,7 +23,7 @@
v-if="file?.image_url"
/> -->
<img :src="fileList?.[0]?.image_url" v-if="fileList?.[0]?.image_url" />
<div v-else class="w-full h-full bg_error_picture"></div>
<div v-else class="w-full h-full bg-error-picture"></div>
</div>
</el-dialog>
</template>

@ -1,17 +1,21 @@
<template>
<el-dialog class="vehiclModal-wrap" v-model="show" @close="handleClose">
<el-dialog class="vehiclModal-wrap fg-dialog fg-dialog2" v-model="show" @close="handleClose" :show-close="false" >
<!-- 自定义标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div class="flex items-center justify-between vehicl-dialog-header">
<div class="flex items-center justify-between vehicl-dialog-header fg-dialog-header">
<div class="flex items-center justify-center header-left">
<div class="header-icon mr-[12px]"></div>
<p class="overflow-hidden whitespace-nowrap text-ellipsis max-w-[650px]">{{
"列车图片" }}</p>
</div>
<div class="fg-dialog-header-close" @click="close">
</div>
</div>
</template>
<!-- 图片区域 -->
<div class="vehicl-content">
<!-- //TODO -->
<div class="vehicl-content-top">
<span class="vehicl-content-bottom-title">列车ID:</span>
<div class="vehicl-content-top-img">
@ -24,6 +28,7 @@
</div>
</div>
<div class="vehicl-content-bottom">
<!-- //TODO -->
<span class="vehicl-content-bottom-title">列车与车厢号</span>
<div class="vehicl-content-bottom-vehicl">
<div class="vehicl-content-bottom-vehicl-header"></div>
@ -82,36 +87,11 @@ const itemCount = 16; // 这里可以根据实际需求动态设置
<style lang="scss">
.vehiclModal-wrap.el-dialog {
border: none;
overflow: hidden;
box-shadow: none;
background-color: transparent;
background-image: url("@/assets/common/bg_real_dialog.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
width: 1200px;
height: 630px;
padding: 0;
.el-dialog__header.show-close {
padding: 0;
}
.el-dialog__close {
width: 56px;
height: 56px;
color: white;
font-size: 18px;
padding-top: 4px;
padding-right: 20px;
}
.vehicl-dialog-header {
color: white;
padding: 0;
padding-top: 8px;
margin-bottom: 20px;
// padding-top: 8px;
// margin-bottom: 20px;
.header-left {
padding: 0 24px;
font-weight: bold;

@ -0,0 +1,422 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>环形图组件设计</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Microsoft+YaHei:wght@400;700&display=swap">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Microsoft YaHei', sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #1a253a, #0d1425);
padding: 20px;
}
.dashboard {
display: flex;
flex-direction: column;
align-items: center;
max-width: 1200px;
width: 100%;
}
.title {
color: #fff;
font-size: 28px;
font-weight: bold;
margin-bottom: 40px;
text-align: center;
text-shadow: 0 0 10px rgba(52, 176, 255, 0.5);
letter-spacing: 1px;
}
.container {
display: flex;
background: linear-gradient(145deg, #0C1D45, #07122E);
padding: 25px;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
width: 100%;
max-width: 800px;
border: 1px solid rgba(52, 176, 255, 0.2);
position: relative;
overflow: hidden;
}
/* 科技感装饰 */
.container::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(52, 176, 255, 0.5), transparent);
}
.container::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(242, 214, 75, 0.5), transparent);
}
.chart-container {
position: relative;
width: 280px;
height: 280px;
display: flex;
justify-content: center;
align-items: center;
}
.chart-center {
position: absolute;
width: 140px;
height: 140px;
border-radius: 50%;
background: rgba(12, 29, 69, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
border: 3px solid rgba(52, 176, 255, 0.1);
}
.total-label {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
.total-value {
color: #fff;
font-size: 24px;
font-weight: bold;
margin-top: 5px;
text-shadow: 0 0 8px rgba(52, 176, 255, 0.6);
}
.legend-container {
display: flex;
flex-direction: column;
justify-content: center;
padding-left: 30px;
margin-left: 30px;
border-left: 1px solid rgba(52, 176, 255, 0.2);
width: calc(100% - 310px);
}
.legend-title {
color: #fff;
font-size: 18px;
font-weight: bold;
margin-bottom: 25px;
display: flex;
align-items: center;
}
.legend-title::before {
content: "";
width: 4px;
height: 20px;
background: linear-gradient(to bottom, #34B0FF, #0E8DE0);
border-radius: 2px;
margin-right: 10px;
}
.legend-item {
display: flex;
align-items: center;
margin: 12px 0;
padding: 15px 20px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
transition: all 0.3s ease;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.legend-item:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
background: rgba(255, 255, 255, 0.08);
border-color: rgba(52, 176, 255, 0.3);
}
.color-indicator {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 15px;
position: relative;
overflow: hidden;
}
.color-indicator::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
background: conic-gradient(var(--color-start) 0%, var(--color-end) var(--percentage), rgba(255,255,255,0.1) var(--percentage), rgba(255,255,255,0.1) 100%);
border-radius: 50%;
animation: rotate 1.2s ease-out;
}
@keyframes rotate {
from { transform: rotate(-90deg); }
to { transform: rotate(0); }
}
.legend-text {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.item-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.label {
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
letter-spacing: 0.5px;
}
.percentage {
color: #fff;
font-size: 20px;
font-weight: bold;
text-shadow: 0 0 5px rgba(52, 176, 255, 0.5);
}
.progress-container {
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
overflow: hidden;
margin-top: 8px;
}
.progress-bar {
height: 100%;
border-radius: 3px;
transition: width 1.5s ease-out;
}
.footer {
color: rgba(255, 255, 255, 0.6);
font-size: 14px;
text-align: center;
margin-top: 40px;
padding: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
width: 100%;
max-width: 800px;
}
/* 动画装饰元素 */
.decoration {
position: absolute;
border-radius: 50%;
opacity: 0.3;
z-index: 0;
}
.dec-1 {
width: 350px;
height: 350px;
top: -50px;
left: -50px;
background: radial-gradient(circle, #34B0FF, transparent 70%);
animation: pulse 4s infinite ease-in-out;
}
.dec-2 {
width: 200px;
height: 200px;
bottom: -70px;
right: -20px;
background: radial-gradient(circle, #F2D64B, transparent 70%);
animation: pulse 3s infinite ease-in-out 0.5s;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 0.2; }
50% { transform: scale(1.1); opacity: 0.3; }
}
@media (max-width: 768px) {
.container {
flex-direction: column;
align-items: center;
}
.chart-container {
margin-bottom: 30px;
}
.legend-container {
padding-left: 0;
margin-left: 0;
border-left: none;
border-top: 1px solid rgba(52, 176, 255, 0.2);
padding-top: 30px;
width: 100%;
}
}
</style>
</head>
<body>
<div class="dashboard">
<h1 class="title">撑杆状态监测分析</h1>
<div class="container">
<div class="decoration dec-1"></div>
<div class="decoration dec-2"></div>
<div class="chart-container">
<div id="chart" style="width: 100%; height: 100%; z-index: 5;"></div>
<div class="chart-center">
<div class="total-label">故障总量</div>
<div class="total-value">128</div>
</div>
</div>
<div class="legend-container">
<div class="legend-title">撑杆故障分类</div>
<div class="legend-item">
<div class="color-indicator" style="--color-start: #34B0FF; --color-end: #0E8DE0; --percentage: 65%">
<div class="inner-circle"></div>
</div>
<div class="legend-text">
<div class="item-title">
<div class="label">撑杆折断</div>
<div class="percentage">65%</div>
</div>
<div class="progress-container">
<div class="progress-bar" style="width: 65%; background: linear-gradient(90deg, #34B0FF, #0E8DE0);"></div>
</div>
</div>
</div>
<div class="legend-item">
<div class="color-indicator" style="--color-start: #F2D64B; --color-end: #E6B400; --percentage: 35%">
<div class="inner-circle"></div>
</div>
<div class="legend-text">
<div class="item-title">
<div class="label">撑杆弯曲</div>
<div class="percentage">35%</div>
</div>
<div class="progress-container">
<div class="progress-bar" style="width: 35%; background: linear-gradient(90deg, #F2D64B, #E6B400);"></div>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
数据更新时间: 2023年11月15日 | 监测系统版本 v2.4.1
</div>
</div>
<script>
// 初始化图表
const chartDom = document.getElementById('chart');
const myChart = echarts.init(chartDom);
// 创建渐变效果
const createGradient = (start, end) => {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: start },
{ offset: 1, color: end }
]);
};
// 图表配置
const option = {
backgroundColor: 'transparent',
series: [{
name: '撑杆状态',
type: 'pie',
radius: ['65%', '85%'],
center: ['50%', '50%'],
startAngle: 90,
avoidLabelOverlap: false,
itemStyle: {
borderColor: 'rgba(12, 29, 69, 0.7)',
borderWidth: 3
},
label: {
show: false
},
labelLine: {
show: false
},
data: [
{
value: 65,
name: '撑杆折断',
itemStyle: {
color: createGradient('#34B0FF', '#0E8DE0')
}
},
{
value: 35,
name: '撑杆弯曲',
itemStyle: {
color: createGradient('#F2D64B', '#E6B400')
}
}
],
animationType: 'scale',
animationEasing: 'elasticOut',
animationDelay: function (idx) {
return Math.random() * 200;
}
}]
};
// 应用配置
myChart.setOption(option);
// 响应式调整
window.addEventListener('resize', function() {
myChart.resize();
});
// 进度条动画
document.querySelectorAll('.progress-bar').forEach(bar => {
const width = bar.style.width;
bar.style.width = '0';
setTimeout(() => {
bar.style.width = width;
}, 300);
});
</script>
</body>
</html>
Loading…
Cancel
Save