feat: 外观检测详情页样式修改接口修改及车辆管理接口联调

main
JINGYJ 1 month ago
parent e40a1222bd
commit 8bcb493217

@ -84,3 +84,13 @@ export const getDeviceHistoryDetailApi = (params: {
}) => {
return request.get(`/api/v1/device/device_history/`, params);
};
// 车辆管理
export const getVehiclManagementApi = (params: any) => {
return request.get(`/api/v1/record/train_record/`, params);
};
// 外观检测故障前详情
export const getBeforeMonitorDetailApi = (params: any) => {
return request.get(`/api/v1/record/before_arrive_record_list/`, params);
};

@ -35,7 +35,7 @@
.appearance-monitor-left {
width: 48%;
width: 49%;
background-image: url("@/assets/common/boderBg.png");
background-size: 100% 100%;
background-position: center;

@ -86,7 +86,7 @@
</div>
<PointModal v-model:value="isPointOpen" :info="currentRow" @close="isPointOpen = false" />
<AlarmModal v-model:value="isAlarmOpen" :info="currentRow" :image="currFileList" @close="isAlarmOpen = false" />
<AppearanceAlarmModal v-model:value="isAlarmOpen" :info="currentRow" :image="currFileList" :beforeImage="currBeforeFileList" @close="isAlarmOpen = false" />
<DeleteModal v-model:value="isDeleteOpen" @delete-success="getList()" :info="currentRow" @close="isDeleteOpen = false" />
</div>
</template>
@ -97,10 +97,10 @@ import ContentHeader from "@/components/ContentHeader.vue";
import { BaseTable } from "@/components/CustomTable";
import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Scrollbar } from "swiper/modules";
import { getAppearanceMonitorApi, getAppearanceMonitorDetailApi } from '@/api/dashboard';
import { getAppearanceMonitorApi, getAppearanceMonitorDetailApi, getBeforeMonitorDetailApi } from '@/api/dashboard';
import { isSuccessApi } from "@/utils/forApi";
import PointModal from './components/PointModal.vue'
import AlarmModal from './components/AlarmModal.vue'
import AppearanceAlarmModal from './components/AppearanceAlarmModal.vue'
import DeleteModal from './components/DeleteModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
import { onBeforeRouteLeave } from 'vue-router';
@ -138,12 +138,12 @@ const columns = [
{
label: "站点",
property: "station",
width: 70,
width: 75,
},
{
label: "车号",
property: "train_number",
width: 120,
width: 155,
},
{
label: "车型",
@ -153,7 +153,7 @@ const columns = [
{
label: "车厢号",
property: "train_carriage_number",
width: 80,
width: 95,
},
{
label: "告警类型",
@ -163,7 +163,7 @@ const columns = [
{
label: "故障类型",
property: "fault_type",
width: 120,
width: 90,
},
{
label: "等级",
@ -233,9 +233,10 @@ const columns = [
color:"#009DFF"
},
onClick: (row) => {
// console.log(row.id);
console.log('row.id');
//
isAlarmOpen.value = true;
console.log(isAlarmOpen.value);
currentRow.value = row;
}
},
@ -270,6 +271,7 @@ const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const listData = ref([]); //
const currentRow = ref<Record<string, any>>({}); //
const currFileList = ref<Record<string, any>[]>([]); //
const currBeforeFileList = ref<Record<string, any>[]>([]); //
const currFile = ref<Record<string, any>>({}); //
const isPlaying = ref<boolean>(false); //
//
@ -318,6 +320,19 @@ const getFileList = async () => {
console.log(error, 'getDetailList_error')
}
}
//
const getBeforeFileList = async () => {
try {
const res = await getBeforeMonitorDetailApi({ id: currentRow.value?.id, current: 1, pageSize: 1000 })
console.log(res.data, 'getDetailList_data')
if (isSuccessApi(res)) {
currBeforeFileList.value = res.data.data;
}
} catch (error) {
console.log(error, 'getDetailList_error')
}
}
// TODO mock
// const getFileList = async () => {
// try {
@ -338,6 +353,7 @@ const getFileList = async () => {
function loadDetail() {
currentRow.value = listData.value[0]
getFileList()
getBeforeFileList()
}
//
@ -392,6 +408,7 @@ const handleRowClassName = ({ row }) => {
const handleRowClick = (row, event, rowIndex) => {
currentRow.value = row;
getFileList()
getBeforeFileList()
};
onBeforeRouteLeave(() => {

@ -121,12 +121,12 @@ const columns = [
{
label: "站点",
property: "station",
width: 70,
width: 80,
},
{
label: "车号",
property: "train_number",
width: 120,
width: 155,
},
{
label: "车型",
@ -136,12 +136,12 @@ const columns = [
{
label: "车厢号",
property: "train_carriage_number",
width: 80,
width: 95,
},
{
label: "告警类型",
property: "alarm_type",
width: 100,
width: 90,
},
{
label: "故障类型",

@ -33,7 +33,7 @@
</BaseTable>
</div>
</div>
<VehiclModal v-model:value="isVehiclOpen" :info="currentDetailRow" :image="currFileList" @close="isVehiclOpen = false" />
<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>
@ -43,7 +43,7 @@
<script setup lang="ts">
import { BaseTable } from "@/components/CustomTable";
import ContentHeader from '@/components/ContentHeader.vue';
import { getDeviceStatusApi } from '@/api/dashboard';
import { getVehiclManagementApi } from '@/api/dashboard';
import { isSuccessApi } from "@/utils/forApi";
import AlarmModal from './components/AlarmModal.vue'
import { useWebSocketStore } from '@/stores/websocketStore';
@ -72,23 +72,23 @@ watch(() => websocketStore.messages, (newMessages: string[], oldMessages: string
const columns = [
{
label: "车辆ID",
property: "device_name"
property: "train_id"
},
{
label: "车厢数量",
property: "device_number"
property: "carriage_account"
},
{
label: "入场时间",
property: "device_position"
property: "arrive_at"
},
{
label: "出场时间",
property: "device_position"
property: "leave_at"
},
{
label: "停留时间",
property: "device_position"
property: "stay_duration"
},
{
type: "action",
@ -100,7 +100,7 @@ const listData = ref([]);
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getDeviceStatusApi({ current: currentPage, pageSize })
const res = await getVehiclManagementApi({ current: currentPage, pageSize })
console.log(res.data, 'getList_data')
if (isSuccessApi(res)) {
listData.value = res.data.data;

@ -0,0 +1,431 @@
<template>
<el-dialog class="appearanceAlarmModal" v-model="show" @close="handleClose">
<!-- 自定义标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div class="flex items-center justify-between appearanceAlarm-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>
</template>
<!-- 图片区域 -->
<div class="appearanceAlarm-content">
<div class="appearanceAlarm-content-top">
<!-- <template v-for="(item,index) in image" :key="index">
<img :src="item.image_url" alt="" v-if="index < 4"></img>
</template> -->
<div class="appearanceAlarm-content-top-left">
<span class="appearanceAlarm-content-top-title">故障前</span>
<div class="appearanceAlarm-content-top-left-img">
<div class="appearanceAlarm-content-top-img">
<img :src="beforeImage[0]?.image_url" alt=""></img>
</div>
<div class="appearanceAlarm-content-top-img-slider">
<swiper ref="swiperModalRef" :modules="modules" :slides-per-view="3" :space-between="10" navigation
:scrollbar="{ draggable: false }" :centered-slides="false" :observer="true" :observeParents="true"
@swiper="onSwiper" @slideChange="onSlideChange">
<swiper-slide v-for="(file, index) in beforeImage" :key="index" @click="handleSlideClick(index)"
:class="{ 'active-slide': activeIndex === index }">
<img :src="file.image_url" class="cursor-pointer" v-if="file.image_url" />
</swiper-slide>
</swiper>
</div>
</div>
</div>
<div class="appearanceAlarm-content-top-right">
<span class="appearanceAlarm-content-top-title">故障后</span>
<div class="appearanceAlarm-content-top-right-img">
<div class="appearanceAlarm-content-top-img">
<img :src="image[0]?.image_url" alt=""></img>
</div>
<div class="appearanceAlarm-content-top-img-slider">
<swiper ref="swiperModalRef" :modules="modules" :slides-per-view="3" :space-between="10" navigation
:scrollbar="{ draggable: false }" :centered-slides="false" :observer="true" :observeParents="true"
@swiper="onSwiper" @slideChange="onSlideChange">
<swiper-slide v-for="(file, index) in image" :key="index" @click="handleSlideClick(index)"
:class="{ 'active-slide': activeIndex === index }">
<img :src="file.image_url" class="cursor-pointer" v-if="file.image_url" />
</swiper-slide>
</swiper>
</div>
</div>
</div>
</div>
<div class="appearanceAlarm-content-bottom">
<span class="appearanceAlarm-content-bottom-title">列车信息:</span>
<div class="appearanceAlarm-content-bottom-info">
<span class="mr-8">列车编号: <i>{{ info.train_number }}</i></span>
<span class="mr-8">车型: <i>{{ info.train_model }}</i></span>
<span>发生时间: <i>{{ info.created_at }}</i></span>
</div>
<div class="appearanceAlarm-content-bottom-info">
<span class="mr-8">告警类型: <i>{{ info.alarm_type }}</i></span>
<span>故障类型: <i>{{ info.fault_type }}</i></span>
</div>
<div class="appearanceAlarm-content-bottom-btn">
<el-button type="primary" @click="exportToExcel" class="appearanceAlarm-btn">
<span class="icon"></span> 导出
</el-button>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import ExcelJS from 'exceljs';
import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Scrollbar } from "swiper/modules";
import "swiper/css";
import 'swiper/scss';
import 'swiper/scss/navigation';
import { it } from 'element-plus/es/locale';
interface Props {
/** 弹窗显隐 */
value: boolean;
info: Record<string, any>;
image: any;
beforeImage: any;
}
interface Emits {
(e: "update:value", val: boolean): void;
}
const props = withDefaults(defineProps<Props>(), {
value: false,
info: {},
image: [],
beforeImage: []
});
const emit = defineEmits<Emits>();
const modules = [Navigation, Scrollbar];
const activeIndex = ref(-1);
const swiperModalRef = ref(null);
const loading = ref(true);
//
const handleClose = () => {
// emits('close');
};
const show = computed({
get() {
return props.value;
},
set(val: boolean) {
emit("update:value", val);
}
});
const onSwiper = (swiper) => {
swiperModalRef.value = swiper;
console.log('Swiper 实例已获取:', swiper);
};
const onSlideChange = () => {
console.log("slide change");
};
const handleSlideClick = (index) => {
activeIndex.value = index;
};
// Excel
const exportToExcel = async () => {
const { info, image } = props;
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
//
const data = [
['列车编号', '车型', '发生时间', '告警类型', '故障类型'],
[info.train_number, info.train_model, info.created_at, info.alarm_type, info.fault_type]
];
worksheet.addRows(data);
//
if (image.length > 0) {
for (let i = 0; i < Math.min(image.length, 4); i++) {
const imgUrl = image[i].image_url;
try {
console.log(`开始加载图片: ${imgUrl}`);
const response = await fetch(imgUrl);
if (!response.ok) {
console.error(`图片加载失败,状态码: ${response.status}URL: ${imgUrl}`);
continue;
}
const blob = await response.blob();
const arrayBuffer = await blob.arrayBuffer();
const imageId = workbook.addImage({
// 使 arrayBuffer
buffer: arrayBuffer,
extension: 'png',
});
worksheet.addImage(imageId, {
tl: { col: 0, row: i + 2 },
ext: { width: 200, height: 200 },
});
console.log(`图片 ${imgUrl} 已成功添加到 Excel`);
} catch (error) {
console.error(`图片处理过程中出错URL: ${imgUrl},错误信息:`, error);
}
}
}
try {
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'alarm_info.xlsx';
link.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error('导出 Excel 文件时出错:', error);
}
};
onMounted(() => {
//
setTimeout(() => {
loading.value = false;
}, 2000); // 2
});
</script>
<style lang="scss">
.appearanceAlarmModal.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: 816px;
height:609px;
padding: 0;
// margin-top: calc(50vh - 316px);
.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;
}
.appearanceAlarm-dialog-header {
color: white;
padding: 0;
padding-top: 8px;
margin-bottom: 10px;
.header-left {
padding: 0 24px;
font-weight: bold;
font-size: 18px;
.header-icon {
margin-top: 4px;
width: 24px;
height: 48px;
background-image: url("@/assets/common/alarm_title.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
}
}
.appearanceAlarm-content {
box-sizing: border-box;
padding: 0 24px;
width: 100%;
height: 100%;
.appearanceAlarm-content-top {
box-sizing: border-box;
display: flex;
// flex-wrap: wrap;
justify-content: space-between; /* 水平方向均匀分布间距 */
align-content: space-between; /* 垂直方向均匀分布间距 */
width: 766px;
height: 360px;
// img {
// width: 368px;
// height: 216px;
// }
.appearanceAlarm-content-top-title {
display: inline-block;
width: 42px;
height: 20px;
margin: 16px 0 12px;
font-weight: bold;
font-size: 14px;
color: #FFFFFF;
}
.appearanceAlarm-content-top-left {
.appearanceAlarm-content-top-left-img {
display: flex;
flex-direction: column;
.appearanceAlarm-content-top-img {
box-sizing: border-box;
width: 368px;
height: 216px;
margin-bottom: 16px;
border-radius:4px;
img {
width: 100%;
height: 100%;
border-radius:2px;
}
}
.appearanceAlarm-content-top-img-slider {
width: 368px;
img {
width: 118px;
height: 74px;
}
}
}
}
.appearanceAlarm-content-top-right {
.appearanceAlarm-content-top-right-img {
position: relative;
display: flex;
flex-direction: column;
.appearanceAlarm-content-top-img {
box-sizing: border-box;
width: 368px;
height: 216px;
margin-bottom: 16px;
border-radius:4px;
img {
width: 100%;
height: 100%;
border-radius:2px;
}
}
.appearanceAlarm-content-top-img-slider {
width: 368px;
img {
width: 118px;
height: 74px;
}
}
}
.appearanceAlarm-content-top-right-img::before {
content: "";
position: absolute;
top: 0;
left: -16px;
border-left: 2px dashed rgba(255,255,255,0.6);
// width: 2px; /* 线 */
height: 100%; /* 分割线高度,根据需要调整 */
// margin-right: 10px; /* 线 */
}
}
.swiper {
width: 100%;
height: 100%;
.swiper-slide {
width: 20%;
border-radius:2px;
height: 74px;
img {
width: 100%;
height: 74px;
border-radius:2px;
object-fit: cover
}
}
.active-slide img {
border-radius:2px;
border: 2px solid #2ECCE0;
}
.swiper-button-prev,
.swiper-button-next {
background-color: rgba(0, 0, 0, 0.5);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
top: 50px;
}
.swiper-button-prev::after,
.swiper-button-next::after {
font-size: 12px ;
color: #FFF;
}
/* 修改按钮悬停样式 */
.swiper-button-prev:hover,
.swiper-button-next:hover {
background-color: rgba(0, 0, 0, 0.8);
}
}
}
.appearanceAlarm-content-bottom {
box-sizing: border-box;
margin-top: 12px;
.appearanceAlarm-content-bottom-title {
box-sizing: border-box;
padding-left: 8px;
height: 22px;
font-weight: bold;
font-size: 14px;
color: #FFFFFF;
border-left: 3px solid #46A9ED;
line-height: 22px;
}
.appearanceAlarm-content-bottom-info {
box-sizing: border-box;
font-size: 14px;
color: #FFFFFF;
i {
font-style: normal;
font-weight: bold;
}
}
.appearanceAlarm-content-bottom-info:nth-of-type(1) {
margin: 4px 0;
}
.appearanceAlarm-content-bottom-btn {
margin-top: 24px;
text-align: end;
.appearanceAlarm-btn {
width: 96px;
height: 32px;
background: linear-gradient(180deg, #2589ff 0%, #46a9ed 100%);
border: 1px solid #42a5f5;
border-radius: 2px;
color: white;
margin-left: 0;
& .icon {
width: 14px;
height: 14px;
background-image: url("@/assets/common/export_icon.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
margin-right: 5px;
}
}
}
}
}
}
</style>

@ -16,12 +16,10 @@
<span class="vehicl-content-bottom-title">列车ID:</span>
<div class="vehicl-content-top-img">
<div class="vehicl-content-top-img-box">
<img src="https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg" alt=""></img>
<!-- <div>1</div> -->
<img :src="info.arrive_img_url" alt=""></img>
</div>
<div class="vehicl-content-top-img-box">
<img src="https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg" alt=""></img>
<!-- <div>1</div> -->
<img :src="info.leave_img_url" alt=""></img>
</div>
</div>
</div>
@ -29,12 +27,12 @@
<span class="vehicl-content-bottom-title">列车与车厢号</span>
<div class="vehicl-content-bottom-vehicl">
<div class="vehicl-content-bottom-vehicl-header"></div>
<div :class="['vehicl-content-bottom-vehicl-body-box', { 'high-height': itemCount > 15 }]">
<div class="vehicl-content-bottom-vehicl-body" v-for="(item, index) in itemCount" :key="index">
<div>C54K</div>
<div :class="['vehicl-content-bottom-vehicl-body-box', { 'high-height': info?.data?.train_data?.length > 15 }]">
<div class="vehicl-content-bottom-vehicl-body" v-for="(item, index) in info?.data?.train_data" :key="index">
<div>{{ item.model }}</div>
<div>
<span class="mr-3">04</span>
<span>15189</span>
<!-- <span class="mr-3">04</span> -->
<span>{{ item.carriage_number }}</span>
</div>
</div>
</div>

Loading…
Cancel
Save