feat: 钩机监测模块创建完成

main
donghao 3 months ago
parent 3cefc64cfa
commit ce5cea7a11

975
package-lock.json generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 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: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

@ -1,3 +1,14 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-06-23 15:50:30
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-06-24 10:39:23
* @FilePath: \5G-Web\src\components\CustomTable\index.ts
* @Description:
*/
import baseDelete from "./src/baseDelete.vue";
import baseTable from "./src/baseTable";
export const BaseDelete = baseDelete;
export const BaseTable = baseTable;

@ -0,0 +1,152 @@
<template>
<el-dialog class="deleteModal-wrap" v-model="show" @close="handleClose">
<!-- 标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div class="flex items-center justify-between delete-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="delete-content">
<div v-html="deleteContent"></div>
<p v-if="!deleteContent">.</p>
</div>
<template #footer>
<div class="delete-footer">
<el-button class="delete-cancel" @click="handleClose"></el-button>
<el-button class="delete-confirm" @click="deleteData()">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
interface Props {
/** 弹窗显隐 */
value: boolean;
info: Record<string, any>;
deleteContent: string;
}
interface Emits {
(e: "update:value", val: boolean): void;
(e: "delete-confirm"): void;
}
const props = withDefaults(defineProps<Props>(), {
value: false,
info: {},
deleteContent: null,
});
const emit = defineEmits<Emits>();
//
const handleClose = () => {
emit("update:value", false);
};
//
const deleteData = () => {
emit("delete-confirm", props.info);
};
const show = computed({
get() {
return props.value;
},
set(val: boolean) {
emit("update:value", val);
},
});
</script>
<style lang="scss">
.deleteModal-wrap.el-dialog {
border: none;
overflow: hidden;
box-shadow: none;
background-color: transparent;
background-image: url("@/assets/common/bg_delete_dialog.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
width: 560px;
height: 226px;
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;
}
.delete-dialog-header {
color: white;
padding: 0;
padding-top: 32px;
padding-left: 16px;
.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/warn_icon.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
}
}
.delete-content {
box-sizing: border-box;
margin-bottom: 30px;
padding-left: 76px;
font-weight: 400;
font-size: 14px;
color: #ffffff;
span {
font-weight: bold;
}
}
.delete-footer {
box-sizing: border-box;
padding-right: 32px;
.delete-cancel,
.delete-confirm {
width: 64px;
height: 32px;
font-size: 16px;
color: #ffffff;
background: transparent;
border-radius: 4px 4px 4px 4px;
border: 1px solid #cccccc;
}
.delete-confirm {
border: none;
background: linear-gradient(180deg, #2589ff 0%, #46a9ed 100%);
}
}
}
</style>

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 15:00:26
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-07 14:16:20
* @LastEditTime: 2025-06-23 15:58:27
* @FilePath: \vite-ai\data-dashboard\src\views\dashboard\components\footer.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
@ -56,7 +56,7 @@ const isActive = (path: string) => {
transition: color 0.3s;
// 1 4
@for $i from 0 through 4 {
@for $i from 0 through 5 {
// .menu-icon-1.menu-icon-2
.nav-icon-#{$i} {

@ -1,3 +1,11 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-06-23 15:50:30
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-06-24 09:23:48
* @FilePath: \5G-Web\src\router\dashboard.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
// 路由配置示例router/index.ts
// import PoleMonitor from "../views/PoleMonitor.vue";
// import DeviceStatus from "../views/DeviceStatus.vue";
@ -30,7 +38,13 @@ export const dashboardRoutes = {
component: () => import("@/views/dashboard/PoleMonitor.vue"),
meta: { title: "撑杆监测" },
},
{
path: "digger",
name: "DiggerMonitor",
fullPath: "/dashboard/digger",
component: () => import("@/views/dashboard/DiggerMonitor.vue"),
meta: { title: "钩机监测" },
},
{
path: "device",
name: "DeviceStatus",

@ -9,7 +9,7 @@
margin-left: 8px;
}
//
@for $i from 0 through 7 {
@for $i from 0 through 8 {
//
.bg_title_#{$i} {
//
@ -20,3 +20,12 @@
.bg_basic_content{
background: linear-gradient( 180deg, rgba(7,16,19,0) 0%, #081417 100%);;
}
.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;
}

@ -0,0 +1,122 @@
.digger-monitor-warp {
box-sizing: border-box;
padding-top: 32px;
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
gap: 20px;
// align-items: center;
.digger-monitor-right {
box-sizing: border-box;
width:970px;
// display: flex;
background-image: url("@/assets/common/carbtmBg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.digger-monitor-search-box {
display: flex;
align-items: center;
gap: 12px;
margin: 16px 0;
}
.right-panel{
.el-scrollbar__view {
background: transparent !important;
height: 600px;
}
.fixed_pagination{
padding: 12px 20px 15px;
}
}
.digger-monitor-left {
width: 49%;
background-image: url("@/assets/common/boderBg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
.monitor-left-top {
box-sizing: border-box;
padding: 32px 16px 20px;
min-height: 600px;
.file-preview-screen {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 492px;
// background-color: red;
img {
max-width: 100%;
max-height: 100%;
object-fit: cover;
}
video {
width: 100%;
max-height: calc(100%);
}
}
}
.monitor-left-bottom {
width: 100%;
padding: 0 16px;
margin-bottom: 29px;
overflow: visible;
.swiper {
width: 100%;
height: 100%;
.swiper-slide {
width: 20%;
border-radius:4px;
height: 144px;
img {
width: 100%;
height: 144px;
border-radius:4px;
object-fit: cover
}
}
.active-slide img,
.active-slide video {
border-radius:4px;
border: 2px solid #2ECCE0;
}
.swiper-button-prev,
.swiper-button-next {
background-color: rgba(0, 0, 0, 0.5);
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
}
.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);
}
}
}
.empty-bg {
box-sizing: border-box;
width: 892px;
height: 815px;
background-image: url("@/assets/common/emptyBg.png");
background-size: 156px 102px;
background-position: center;
background-repeat: no-repeat;
}
}
}

@ -0,0 +1,446 @@
<template>
<div class="digger-monitor-warp">
<div class="digger-monitor-left">
<template v-if="currFileList?.length">
<div class="flex items-center justify-center h-full monitor-left-top">
<div class="file-preview-screen">
<!-- // TODO -->
<img :src="currFileList?.[0]?.image_url" v-if="false" />
<div v-else class="w-full h-full bg_error_picture"></div>
</div>
</div>
</template>
<div class="empty-bg" v-else></div>
</div>
<div class="digger-monitor-right h-[100%]">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_8"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<!-- 表格区域 -->
<!-- 搜索区域 -->
<div class="px-[16px]">
<div class="digger-monitor-search-box">
<!-- <el-select
v-model="searchForm.station"
placeholder="钩机编号"
class="custom-select"
>
<el-option label="小觉站" value="小觉站"></el-option>
<el-option label="东西站" value="东西站"></el-option>
<el-option label="立杆区" value="立杆区"></el-option>
</el-select> -->
<el-input
v-model="searchForm.train_number"
placeholder="请输入列车号"
class="custom-input"
clearable
/>
<el-input
v-model="searchForm.train_carriage_number"
placeholder="请输入车厢号"
class="custom-input"
clearable
/>
<!-- <el-select
v-model="searchForm.fault_type"
placeholder="故障类型"
class="custom-select"
>
<el-option label="下侧门板缺失" value="下侧门板缺失"></el-option>
<el-option label="门折页座脱落" value="门折页座脱落"></el-option>
<el-option label="小门塔扣丢失" value="小门塔扣丢失"></el-option>
<el-option label="小窗裂纹" value="小窗裂纹"></el-option>
<el-option label="搭扣未搭" value="搭扣未搭"></el-option>
<el-option label="小门外胀" value="小门外胀"></el-option>
</el-select> -->
<el-button
type="primary"
@click="handleQuery"
class="basic-btn query-btn"
>
<span class="icon"></span> 查询
</el-button>
<el-button @click="handleReset" class="basic-btn reset-btn">
<span class="icon"></span> 重置
</el-button>
</div>
<!-- 右侧表格区域 -->
<div class="w-full right-panel">
<div class="bg-transparent baseTable_wrap">
<template v-if="pagination.total > 0">
<BaseTable
class="bg-transparent baseTable_box"
:total="pagination.total"
:pageSize="pagination.pageSize"
:dataSource="listData"
:isFixedPagination="true"
:columns="columns"
:page="pagination.currentPage"
@change="handleTableChange"
:row-class-name="handleRowClassName"
@row-click="handleRowClick"
>
<template #created_at="{ row }">
<div>{{ row }}</div>
</template>
</BaseTable>
</template>
</div>
</div>
</div>
</div>
<DiggerAlarmModal
v-model:value="isAlarmOpen"
:info="currentRow"
@close="isAlarmOpen = false"
/>
<BaseDelete
v-model:value="isDeleteOpen"
:deleteContent="`
<p>
确定删除
<span>${currentRow.alarm_type}</span>
相关告警记录吗删除后将找不到此记录请谨慎操作.
</p>
`"
@delete-confirm="handleDeleteConfirm"
:info="currentRow"
@close="isDeleteOpen = false"
/>
</div>
</template>
<script lang="ts" setup>
import Player from "@/components/videoPlayer/Player.vue";
import SwiperPlayer from "./components/SwiperPlayer.vue";
import ContentHeader from "@/components/ContentHeader.vue";
import { BaseDelete, BaseTable } from "@/components/CustomTable";
import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Scrollbar } from "swiper/modules";
import {
getAppearanceMonitorApi,
getAppearanceMonitorDetailApi,
getBeforeMonitorDetailApi,
deleteAppearanceMonitorApi,
} from "@/api/dashboard";
import { isSuccessApi } from "@/utils/forApi";
import DiggerAlarmModal from "./components/DiggerAlarmModal.vue";
import { useWebSocketStore } from "@/stores/websocketStore";
import { onBeforeRouteLeave } from "vue-router";
import "swiper/css";
import "swiper/scss";
import "swiper/scss/navigation";
import { color } from "echarts";
const modules = [Navigation, Scrollbar];
const activeIndex = ref(-1);
const swiperRef = ref(null);
const isAlarmOpen = ref<Boolean>(false); //
const isDeleteOpen = ref<Boolean>(false); //
const websocketStore = useWebSocketStore();
// messages
watch(
() => websocketStore.messages,
(newMessages: string[], oldMessages: string[]) => {
// console.log(':', newMessages[newMessages?.length - 1].content);
// console.log(':', oldMessages);
// if (newMessages?.length > oldMessages?.length) {
// //
// const newMessage = newMessages[newMessages?.length - 1];
// // console.log(' WebSocket :', newMessage);
// //
// }
if (newMessages?.length > 0 && !isAlarmOpen.value) {
console.log(
newMessages[newMessages?.length - 1],
"newMessages[newMessages?.length - 1]"
);
currentRow.value = newMessages[newMessages?.length - 1];
currFileList.value = newMessages[newMessages?.length - 1]?.images;
isAlarmOpen.value = true;
}
},
{ deep: true, immediate: true }
);
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const columns = [
{
label: "钩机编号",
type: "index",
index: (index: number) =>
`No.${
index +
1 +
(pagination.value.currentPage - 1) * pagination.value.pageSize
}`,
width: 90,
},
{
label: "车号",
property: "train_number",
width: 185,
},
{
label: "车型",
property: "train_model",
width: 70,
},
{
label: "车厢号",
property: "train_carriage_number",
width: 125,
},
{
label: "告警",
property: "alarm_type",
width: 120,
},
// {
// label: "",
// property: "level",
// width: 60,
// },
// {
// label: "",
// property: "is_reviewed",
// formatter: ({ is_reviewed }) => {
// return is_reviewed === true ? "" : "";
// },
// width: 60,
// },
{
label: "时间",
property: "created_at",
},
{
slot: "operation",
label: "操作",
width: 140,
formatter: (row) => {
return h(
"div",
{
style: {
fontSize: "14px",
color: "#37EBFF",
},
},
[
h(
"span",
{
fontSize: "14px",
class: "pf-1",
},
[
h(
"i",
{
style: {
fontSize: "14px",
letterSpacing: "2px",
marginRight: "4px",
color: "#009DFF",
},
onClick: (row) => {
console.log("row.id");
//
isAlarmOpen.value = true;
console.log(isAlarmOpen.value);
currentRow.value = row;
},
},
"详情"
),
h(
"i",
{
style: {
fontSize: "14px",
letterSpacing: "2px",
marginRight: "4px",
color: "#FF2727",
},
onClick: (row) => {
// console.log(row.id);
//
isDeleteOpen.value = true;
currentRow.value = row;
},
},
"删除"
),
]
),
]
);
},
},
];
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 searchForm = reactive({
train_number: "",
train_carriage_number: "",
fault_type: "",
station: "",
type: "", // TODO distance
});
const dataLoading = ref(true);
//
const getFileList = async () => {
try {
const res = await getAppearanceMonitorDetailApi({
id: currentRow.value?.id,
current: 1,
pageSize: 1000,
});
console.log(res.data, "getDetailList_data");
if (isSuccessApi(res)) {
currFileList.value = res.data.data;
currFile.value = res.data.data[0];
activeIndex.value = 0;
}
} catch (error) {
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 {
// const resAll = await fetch('/api/v1/record/record_detail_list/', {
// method: 'POST'
// })
// const res = await resAll.json()
// if (isSuccessApi(res)) {
// currFileList.value = res.data.data;
// currFile.value = res.data.data[0];
// activeIndex.value = 0;
// }
// } catch (error) {
// console.error(':', error)
// }
// }
function loadDetail() {
currentRow.value = listData.value[0];
getFileList();
getBeforeFileList();
}
//
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getAppearanceMonitorApi({
...searchForm,
current: currentPage,
pageSize,
});
console.log(res.data, "getList_data");
if (isSuccessApi(res)) {
listData.value = res.data.data;
loadDetail();
pagination.value = {
...pagination.value,
total: res.data.total,
};
}
} catch (error) {
console.error("获取数据失败:", error);
}
};
//
const handleQuery = () => {
getList();
};
//
const handleReset = () => {
searchForm.train_number = "";
searchForm.station = "";
searchForm.train_carriage_number = "";
searchForm.fault_type = "";
getList();
};
function handleTableChange(record) {
console.log("handleTableChange_record", record);
pagination.value = {
...pagination.value,
currentPage: record.page,
pageSize: record.pageSize,
};
getList();
}
//
const handleDeleteConfirm = async (row) => {
try {
const res = await deleteAppearanceMonitorApi({ id: row.id });
console.log(res, "deleteData");
if (res.code === 200) {
isDeleteOpen.value = false;
}
} catch (error) {
console.error("获取数据失败:", error);
}
};
//
const handleRowClassName = ({ row }) => {
return row.id === currentRow.value.id ? "selected-row" : "";
};
//
const handleRowClick = (row, event, rowIndex) => {
currentRow.value = row;
getFileList();
getBeforeFileList();
};
onBeforeRouteLeave(() => {
isAlarmOpen.value = false;
currentRow.value = {};
currFileList.value = [];
});
onMounted(() => {
getList();
});
</script>
<style lang="scss">
@import url("./DiggerMonitor.scss");
</style>

@ -0,0 +1,122 @@
<template>
<el-dialog class="digger-alarm-modal" v-model="show" @close="handleClose">
<!-- 自定义标题栏 -->
<template #header="{ close, titleId, titleClass }">
<div
class="flex items-center justify-between digger-detail-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="digger-detail-content">
<!-- <img
:src="file?.image_url"
class="cursor-pointer"
v-if="file?.image_url"
/> -->
<img :src="file?.image_url" v-if="false" />
<div v-else class="w-full h-full bg_error_picture"></div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { Navigation, Scrollbar } from "swiper/modules";
interface Props {
/** 弹窗显隐 */
value: boolean;
info: Record<string, any>;
}
interface Emits {
(e: "update:value", val: boolean): void;
(e: "close"): void;
}
const props = withDefaults(defineProps<Props>(), {
value: false,
info: {},
});
const emit = defineEmits<Emits>();
//
const handleClose = () => {
emits("close");
};
const show = computed({
get() {
return props.value;
},
set(val: boolean) {
emit("update:value", val);
},
});
</script>
<style lang="scss">
.digger-alarm-modal.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;
}
.digger-detail-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;
}
}
}
.digger-detail-content {
box-sizing: border-box;
padding: 0 24px;
width: 100%;
height: 500px;
}
}
</style>
Loading…
Cancel
Save