WholeProcessPlatform/frontend/src/views/shiPinJianKong/shiPinJianKongZhuanTi.vue

504 lines
12 KiB
Vue
Raw Normal View History

2026-03-27 15:52:43 +08:00
<template>
<div class="shiPinJianKongZhuanTi-page">
<a-tabs
v-model:activeKey="activeTab"
type="card"
:tabBarGutter="8"
class="custom-tabs"
>
<template #tabBarExtraContent>
<a-button type="primary" size="small" @click="handleAction">
<template #icon><PlusOutlined /></template>
添加监控点
</a-button>
</template>
<a-tab-pane v-for="tab in tabs" :key="tab.key" :tab="tab.title" />
</a-tabs>
<div class="tabs-content">
<!-- 顶部标题和图标选择区域 -->
<div class="content-header">
<div class="header-left">
<span class="title">监控列表</span>
</div>
<div class="header-right">
<a-radio-group v-model:value="viewLayout" button-style="solid">
<a-radio-button value="1">
<AppstoreOutlined />
</a-radio-button>
<a-radio-button value="4">
<LayoutOutlined />
</a-radio-button>
<a-radio-button value="6">
<BorderOutlined />
</a-radio-button>
<a-radio-button value="9">
<GridOutlined />
</a-radio-button>
</a-radio-group>
</div>
</div>
<!-- 主体内容区域 -->
<div class="content-body">
<!-- 左侧监控列表 -->
<div class="left-panel">
<!-- Tab切换实时视频/录像 -->
<a-tabs v-model:activeKey="monitorType" size="small" class="monitor-tabs">
<a-tab-pane key="live" tab="实时视频" />
<a-tab-pane key="record" tab="录像" />
</a-tabs>
<!-- 查询和状态筛选 -->
<div class="search-bar">
<a-input-search
v-model:value="searchText"
placeholder="搜索监控点"
size="small"
style="flex: 1"
@search="handleSearch"
/>
<a-button-group size="small" class="status-filter">
<a-button
:type="statusFilter === 'online' ? 'primary' : 'default'"
@click="statusFilter = 'online'"
>
在线
</a-button>
<a-button
:type="statusFilter === 'offline' ? 'primary' : 'default'"
@click="statusFilter = 'offline'"
>
离线
</a-button>
</a-button-group>
</div>
<!-- 树形结构 -->
<div class="tree-container">
<a-tree
v-model:expandedKeys="expandedKeys"
v-model:selectedKeys="selectedKeys"
:tree-data="treeData"
:field-names="{ title: 'title', key: 'key', children: 'children' }"
block-node
show-icon
>
<template #switcherIcon="{ switcherCls }">
<DownOutlined :class="switcherCls" />
</template>
<template #title="{ title }">
<span>{{ title }}</span>
</template>
</a-tree>
</div>
</div>
<!-- 中间视频内容区域 -->
<div class="center-panel">
<div class="video-grid" :class="`layout-${viewLayout}`">
<div v-for="index in parseInt(viewLayout)" :key="index" class="video-item">
<div class="video-placeholder">
<VideoCameraOutlined class="video-icon" />
<span class="video-label">摄像头 {{ index }}</span>
</div>
</div>
</div>
</div>
<!-- 右侧回放面板仅录像模式显示 -->
<div v-if="monitorType === 'record'" class="right-panel">
<div class="replay-header">
<span class="replay-title">回放</span>
</div>
<!-- 时间选择 -->
<div class="date-picker">
<a-range-picker
v-model:value="dateRange"
format="YYYY-MM-DD"
style="width: 100%"
size="small"
/>
</div>
<!-- 录像列表 -->
<div class="record-list">
<a-list :data-source="recordList" :loading="recordLoading" size="small">
<template #renderItem="{ item }">
<a-list-item class="record-item">
<div class="record-info">
<div class="record-name">{{ item.name }}</div>
<div class="record-time">{{ item.time }}</div>
</div>
<a-button size="small" type="link">
<PlayCircleOutlined />
</a-button>
</a-list-item>
</template>
</a-list>
</div>
</div>
</div>
</div>
2026-03-27 15:52:43 +08:00
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
// import {
// PlusOutlined,
// AppstoreOutlined,
// LayoutOutlined,
// BorderOutlined,
// GridOutlined,
// DownOutlined,
// VideoCameraOutlined,
// PlayCircleOutlined
// } from '@ant-design/icons-vue'
import type { Dayjs } from "dayjs";
import dayjs from "dayjs";
// Tab相关
const tabs = ref([
{ key: "1", title: "实时监控" },
{ key: "2", title: "录像回放" },
{ key: "3", title: "告警记录" },
]);
const activeTab = ref("1");
// 视图布局选择1/4/6/9宫格
const viewLayout = ref("1");
// 监控类型(实时视频/录像)
const monitorType = ref("live");
// 搜索文本
const searchText = ref("");
// 状态筛选(在线/离线)
const statusFilter = ref<"online" | "offline">("online");
// 树形数据
const expandedKeys = ref<string[]>(["0-0", "0-1"]);
const selectedKeys = ref<string[]>([]);
const treeData = [
{
title: "区域一",
key: "0-0",
children: [
{ title: "摄像头01", key: "0-0-0", isLeaf: true },
{ title: "摄像头02", key: "0-0-1", isLeaf: true },
{ title: "摄像头03", key: "0-0-2", isLeaf: true },
],
},
{
title: "区域二",
key: "0-1",
children: [
{ title: "摄像头04", key: "0-1-0", isLeaf: true },
{ title: "摄像头05", key: "0-1-1", isLeaf: true },
],
},
{
title: "区域三",
key: "0-2",
children: [
{ title: "摄像头06", key: "0-2-0", isLeaf: true },
{ title: "摄像头07", key: "0-2-1", isLeaf: true },
{ title: "摄像头08", key: "0-2-2", isLeaf: true },
{ title: "摄像头09", key: "0-2-3", isLeaf: true },
],
},
];
// 日期范围默认前7天
const dateRange = ref<[Dayjs, Dayjs]>([dayjs().subtract(7, "day"), dayjs()]);
// 录像列表
const recordLoading = ref(false);
const recordList = ref([
{ name: "录像文件01", time: "2026-05-17 10:00:00 - 10:30:00" },
{ name: "录像文件02", time: "2026-05-17 09:00:00 - 09:30:00" },
{ name: "录像文件03", time: "2026-05-16 14:00:00 - 14:30:00" },
{ name: "录像文件04", time: "2026-05-16 10:00:00 - 10:30:00" },
{ name: "录像文件05", time: "2026-05-15 16:00:00 - 16:30:00" },
{ name: "录像文件06", time: "2026-05-15 10:00:00 - 10:30:00" },
{ name: "录像文件07", time: "2026-05-14 11:00:00 - 11:30:00" },
{ name: "录像文件08", time: "2026-05-13 15:00:00 - 15:30:00" },
{ name: "录像文件09", time: "2026-05-12 09:00:00 - 09:30:00" },
{ name: "录像文件10", time: "2026-05-11 14:00:00 - 14:30:00" },
]);
// 处理按钮点击
const handleAction = () => {
console.log("添加监控点");
};
// 处理搜索
const handleSearch = (value: string) => {
console.log("搜索:", value);
};
</script>
<style scoped lang="scss">
.shiPinJianKongZhuanTi-page {
position: relative;
z-index: 900;
pointer-events: all;
width: 100%;
height: 100%;
background-color: #ffffff;
padding: 20px;
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
}
.custom-tabs {
margin-bottom: 16px;
:deep(.ant-tabs-nav) {
margin-bottom: 0;
&::before {
border-bottom: none;
}
}
:deep(.ant-tabs-tab) {
border-radius: 4px 4px 0 0;
transition: all 0.2s ease;
&:hover {
color: #1890ff;
}
}
:deep(.ant-tabs-tab-active) {
background-color: #fff;
border-color: #d9d9d9;
border-bottom-color: #fff;
}
}
.tabs-content {
flex: 1;
background-color: #fafafa;
border-radius: 4px;
border: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
overflow: hidden;
}
// 顶部标题和图标选择区域
.content-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background-color: #fff;
border-bottom: 1px solid #e8e8e8;
.header-left {
.title {
font-size: 16px;
font-weight: 500;
color: #262626;
}
}
.header-right {
:deep(.ant-radio-group) {
.ant-radio-button-wrapper {
padding: 4px 12px;
.anticon {
font-size: 16px;
}
}
}
}
}
// 主体内容区域
.content-body {
flex: 1;
display: flex;
overflow: hidden;
}
// 左侧面板
.left-panel {
width: 280px;
background-color: #fff;
border-right: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
flex-shrink: 0;
.monitor-tabs {
padding: 8px 8px 0;
:deep(.ant-tabs-nav) {
margin-bottom: 0;
}
}
.search-bar {
display: flex;
gap: 8px;
padding: 12px;
border-bottom: 1px solid #f0f0f0;
.status-filter {
flex-shrink: 0;
}
}
.tree-container {
flex: 1;
overflow-y: auto;
padding: 8px;
:deep(.ant-tree) {
.ant-tree-node-content-wrapper {
&:hover {
background-color: #f5f5f5;
}
}
.ant-tree-node-selected {
.ant-tree-node-content-wrapper {
background-color: #e6f7ff;
}
}
}
}
}
// 中间视频区域
.center-panel {
flex: 1;
padding: 16px;
overflow: auto;
background-color: #f5f5f5;
.video-grid {
display: grid;
gap: 12px;
height: 100%;
&.layout-1 {
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
&.layout-4 {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
}
&.layout-6 {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
}
&.layout-9 {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
}
}
.video-item {
background-color: #000;
border-radius: 4px;
overflow: hidden;
min-height: 150px;
.video-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #666;
.video-icon {
font-size: 48px;
margin-bottom: 8px;
opacity: 0.5;
}
.video-label {
font-size: 14px;
color: #999;
}
}
}
}
// 右侧回放面板
.right-panel {
width: 280px;
background-color: #fff;
border-left: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
flex-shrink: 0;
.replay-header {
padding: 12px 16px;
border-bottom: 1px solid #e8e8e8;
.replay-title {
font-size: 14px;
font-weight: 500;
color: #262626;
}
}
.date-picker {
padding: 12px;
border-bottom: 1px solid #f0f0f0;
}
.record-list {
flex: 1;
overflow-y: auto;
padding: 8px;
.record-item {
padding: 8px 12px;
border-radius: 4px;
transition: background-color 0.2s;
&:hover {
background-color: #f5f5f5;
}
.record-info {
flex: 1;
min-width: 0;
.record-name {
font-size: 13px;
color: #262626;
margin-bottom: 4px;
}
.record-time {
font-size: 11px;
color: #8c8c8c;
}
}
}
}
}
</style>