2026-07-01 18:23:41 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="aisbtk-page">
|
|
|
|
|
|
<!-- 搜索表单 -->
|
|
|
|
|
|
<a-form layout="inline" class="search-form">
|
|
|
|
|
|
<a-form-item label="基地选择">
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="searchData.baseid"
|
|
|
|
|
|
placeholder="请选择基地"
|
|
|
|
|
|
style="width: 180px"
|
|
|
|
|
|
:options="jidiOptions"
|
|
|
|
|
|
@change="handleBaseChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<a-form-item v-if="vdListShow !== '3'" label="电站名称">
|
2026-07-01 18:23:41 +08:00
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="searchData.stnm"
|
|
|
|
|
|
placeholder="请输入电站"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
style="width: 180px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<a-form-item v-else label="电站选择">
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="searchData.rstcd"
|
|
|
|
|
|
placeholder="请选择电站"
|
|
|
|
|
|
style="width: 180px"
|
|
|
|
|
|
:options="stcdData"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
2026-07-01 18:23:41 +08:00
|
|
|
|
<a-form-item label="时间">
|
|
|
|
|
|
<a-date-picker
|
|
|
|
|
|
v-model:value="searchData.month"
|
|
|
|
|
|
picker="month"
|
|
|
|
|
|
format="YYYY-MM"
|
|
|
|
|
|
:allow-clear="false"
|
|
|
|
|
|
style="width: 150px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
<a-form-item>
|
|
|
|
|
|
<a-button type="primary" @click="handleSearch">查询</a-button>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-form>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Tabs + 图表表格 -->
|
|
|
|
|
|
<a-tabs v-model:activeKey="tabIndex" class="aisbtk-tabs">
|
|
|
|
|
|
<!-- 视图切换按钮 -->
|
|
|
|
|
|
<div class="view-switch-buttons">
|
|
|
|
|
|
<a-tooltip placement="top" title="视频">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="view-btn"
|
|
|
|
|
|
:class="{ active: vdListShow === '1' }"
|
|
|
|
|
|
@click="vdListShow = '1'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="icon iconfont icon-video"></i>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
<a-tooltip placement="top" title="数据列表">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="view-btn"
|
|
|
|
|
|
:class="{ active: vdListShow === '2' }"
|
|
|
|
|
|
@click="vdListShow = '2'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="icon iconfont icon-dataTable"></i>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
<a-tooltip placement="top" title="日历">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="view-btn"
|
|
|
|
|
|
:class="{ active: vdListShow === '3' }"
|
|
|
|
|
|
@click="vdListShow = '3'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="icon iconfont icon-calendar"></i>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-tab-pane
|
|
|
|
|
|
v-for="item in tabDisplayList"
|
|
|
|
|
|
:key="String(item.key)"
|
|
|
|
|
|
:tab="item.tabLabel"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 数据列表视图(饼图+表格) -->
|
|
|
|
|
|
<div v-show="vdListShow === '2'" class="chart-table-layout">
|
|
|
|
|
|
<!-- 左侧饼图 -->
|
|
|
|
|
|
<div class="chart-section">
|
|
|
|
|
|
<a-spin :spinning="chartLoading">
|
|
|
|
|
|
<div
|
|
|
|
|
|
:ref="(el: any) => setChartRef(el, String(item.key))"
|
|
|
|
|
|
class="chart-container"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
</a-spin>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="!chartLoading && chartData.length === 0"
|
|
|
|
|
|
class="chart-empty-overlay"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-empty description="暂无数据" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧表格 -->
|
|
|
|
|
|
<div class="table-section">
|
|
|
|
|
|
<BasicTable
|
|
|
|
|
|
:ref="(el: any) => setTableRef(el, String(item.key))"
|
|
|
|
|
|
:columns="currentColumns"
|
|
|
|
|
|
:scroll-y="450"
|
|
|
|
|
|
:scroll-x="600"
|
|
|
|
|
|
:list-url="getAiChartTable"
|
|
|
|
|
|
:search-params="tableSearchParams"
|
|
|
|
|
|
:transform-data="customTransform"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #tm="{ record }">
|
|
|
|
|
|
{{ record.tm ? dayjs(record.tm).format('YYYY-MM-DD') : '-' }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #fid="{ record }">
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
style="padding: 4px"
|
|
|
|
|
|
:disabled="!record.fid"
|
|
|
|
|
|
@click="handleViewAttachment(record.fid)"
|
|
|
|
|
|
>
|
|
|
|
|
|
查看附件
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #isBigDebrisArea="{ record }">
|
|
|
|
|
|
<span v-if="record.isBigDebrisArea === 1" style="color: red"
|
|
|
|
|
|
>是</span
|
|
|
|
|
|
>
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-else-if="record.isBigDebrisArea === 0"
|
|
|
|
|
|
style="color: green"
|
|
|
|
|
|
>否</span
|
|
|
|
|
|
>
|
|
|
|
|
|
<span v-else>-</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #aiVal="{ record }">
|
|
|
|
|
|
{{
|
|
|
|
|
|
record.aiVal === 'open'
|
|
|
|
|
|
? '开启'
|
|
|
|
|
|
: record.aiVal === 'close'
|
|
|
|
|
|
? '关闭'
|
|
|
|
|
|
: record.aiVal ?? '-'
|
|
|
|
|
|
}}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</BasicTable>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 视频视图 -->
|
|
|
|
|
|
<div v-show="vdListShow === '1'" class="chart-table-layout">
|
|
|
|
|
|
<!-- 左侧饼图 -->
|
|
|
|
|
|
<div class="chart-section">
|
|
|
|
|
|
<a-spin :spinning="chartLoading">
|
|
|
|
|
|
<div
|
|
|
|
|
|
:ref="(el: any) => setVideoChartRef(el, String(item.key))"
|
|
|
|
|
|
class="chart-container"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
</a-spin>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="!chartLoading && chartData.length === 0"
|
|
|
|
|
|
class="chart-empty-overlay"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-empty description="暂无数据" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧视频列表 -->
|
|
|
|
|
|
<div class="video-section">
|
|
|
|
|
|
<div class="video-mode">
|
|
|
|
|
|
<div class="video-main" v-loading="videoLoading">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="activeMedia.type === 'image' && activeMedia.src"
|
|
|
|
|
|
class="media-display"
|
|
|
|
|
|
>
|
|
|
|
|
|
<img
|
|
|
|
|
|
:src="activeMedia.src"
|
|
|
|
|
|
style="width: 100%; height: 100%; object-fit: contain"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-else-if="activeMedia.type === 'video' && activeMedia.src"
|
|
|
|
|
|
class="media-display"
|
|
|
|
|
|
>
|
|
|
|
|
|
<LiveVideoBox
|
|
|
|
|
|
:videoData="{ url: activeMedia.src }"
|
|
|
|
|
|
height="100%"
|
|
|
|
|
|
:showTitle="false"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="media-empty">
|
|
|
|
|
|
<a-empty description="请选择右侧数据" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="video-sidebar" v-loading="videoLoading">
|
|
|
|
|
|
<div class="sidebar-list" v-if="videoList.length > 0">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="item in videoList"
|
|
|
|
|
|
:key="item.fid"
|
|
|
|
|
|
class="sidebar-item"
|
|
|
|
|
|
:class="{ active: activeFid === item.fid }"
|
|
|
|
|
|
@click="handleSelectMedia(item)"
|
|
|
|
|
|
>
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<!-- {{ item }}12132131313 -->
|
2026-07-01 18:23:41 +08:00
|
|
|
|
<div class="item-thumb">
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<img v-if="item.type === 'img'" :src="item.imgPath" />
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-else-if="item.type === 'vdsp'"
|
|
|
|
|
|
class="thumb-placeholder"
|
|
|
|
|
|
>
|
2026-07-01 18:23:41 +08:00
|
|
|
|
<img :src="videoCover" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span v-else class="no-media">无媒体</span>
|
|
|
|
|
|
</div>
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<div class="item-time">{{ item.stName }}{{ item.tm }}</div>
|
2026-07-01 18:23:41 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-empty v-else description="暂无数据" class="sidebar-empty" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="sidebar-pagination"
|
|
|
|
|
|
style="margin-top: 10px; display: flex; justify-content: end"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-pagination
|
|
|
|
|
|
v-model:current="videoPage"
|
|
|
|
|
|
v-model:pageSize="videoTake"
|
|
|
|
|
|
:page-size-options="videoPageSizeOptions"
|
|
|
|
|
|
:total="videoTotal"
|
|
|
|
|
|
show-size-changer
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@change="handleVideoPageChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2026-07-01 18:23:41 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 日历视图 -->
|
|
|
|
|
|
<div v-show="vdListShow === '3'" class="calendar-mode">
|
|
|
|
|
|
<a-spin :spinning="calendarLoading">
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<a-calendar
|
|
|
|
|
|
v-model:value="calendarValue"
|
|
|
|
|
|
@panelChange="handleCalendarPanelChange"
|
|
|
|
|
|
@select="handleCalendarSelect"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #headerRender="{ current }"> </template>
|
2026-07-01 18:23:41 +08:00
|
|
|
|
<template #dateCellRender="{ current }">
|
|
|
|
|
|
<div class="calendar-content">
|
|
|
|
|
|
<template v-if="isCurrentMonth(current)">
|
|
|
|
|
|
<div
|
2026-07-06 08:54:58 +08:00
|
|
|
|
class="content_state1"
|
|
|
|
|
|
v-if="getCalendarDataForDay(current)"
|
2026-07-01 18:23:41 +08:00
|
|
|
|
>
|
2026-07-06 08:54:58 +08:00
|
|
|
|
监测
|
2026-07-01 18:23:41 +08:00
|
|
|
|
</div>
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<div class="content_state4" v-else>未监测</div>
|
|
|
|
|
|
<!-- 漂浮物识别和清漂识别:显示漂浮物状态和清漂状态 -->
|
|
|
|
|
|
<template v-if="tabIndex === '1' || tabIndex === '3'">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="getCalendarDataForDay(current)?.status"
|
|
|
|
|
|
:class="
|
|
|
|
|
|
getStatusClass(getCalendarDataForDay(current)?.status)
|
|
|
|
|
|
"
|
|
|
|
|
|
@click.stop="
|
|
|
|
|
|
handleCalendarDayClick(
|
|
|
|
|
|
current,
|
|
|
|
|
|
getCalendarDataForDay(current)
|
|
|
|
|
|
)
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{
|
|
|
|
|
|
getStatusText(getCalendarDataForDay(current)?.status)
|
|
|
|
|
|
}}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="getCalendarDataForDay(current)?.qpStatus"
|
|
|
|
|
|
:class="
|
|
|
|
|
|
getStatusClass(
|
|
|
|
|
|
getCalendarDataForDay(current)?.qpStatus
|
|
|
|
|
|
)
|
|
|
|
|
|
"
|
|
|
|
|
|
@click.stop="
|
|
|
|
|
|
handleCalendarDayClick(
|
|
|
|
|
|
current,
|
|
|
|
|
|
getCalendarDataForDay(current)
|
|
|
|
|
|
)
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{
|
|
|
|
|
|
getStatusText(
|
|
|
|
|
|
getCalendarDataForDay(current)?.qpStatus
|
|
|
|
|
|
)
|
|
|
|
|
|
}}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 叠梁门运行识别:只显示运行状态 -->
|
|
|
|
|
|
<template v-else-if="tabIndex === '2'">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="getCalendarDataForDay(current)?.status"
|
|
|
|
|
|
:class="
|
|
|
|
|
|
getStatusClass(getCalendarDataForDay(current)?.status)
|
|
|
|
|
|
"
|
|
|
|
|
|
@click.stop="
|
|
|
|
|
|
handleCalendarDayClick(
|
|
|
|
|
|
current,
|
|
|
|
|
|
getCalendarDataForDay(current)
|
|
|
|
|
|
)
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{
|
|
|
|
|
|
getStatusText(getCalendarDataForDay(current)?.status)
|
|
|
|
|
|
}}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2026-07-01 18:23:41 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-calendar>
|
|
|
|
|
|
</a-spin>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-tab-pane>
|
|
|
|
|
|
</a-tabs>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 附件预览弹框 -->
|
|
|
|
|
|
<CommonAttachmentModal
|
|
|
|
|
|
v-model:open="attachmentModalVisible"
|
|
|
|
|
|
:fid="currentAttachmentFid"
|
|
|
|
|
|
title="查看附件"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 日历详情弹窗 -->
|
|
|
|
|
|
<a-modal
|
|
|
|
|
|
v-model:open="detailModalVisible"
|
|
|
|
|
|
title="视频详情"
|
|
|
|
|
|
width="80vw"
|
|
|
|
|
|
:footer="null"
|
|
|
|
|
|
destroyOnClose
|
2026-07-06 08:54:58 +08:00
|
|
|
|
:teleport="false"
|
2026-07-01 18:23:41 +08:00
|
|
|
|
:bodyStyle="{ height: '70vh' }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-tabs v-model:activeKey="detailActiveKey">
|
2026-07-06 08:54:58 +08:00
|
|
|
|
<a-tab-pane
|
|
|
|
|
|
v-for="item in detailTabItems"
|
|
|
|
|
|
:key="item.key"
|
|
|
|
|
|
:tab="item.label"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-show="detailActiveKey === item.key">
|
|
|
|
|
|
<VideoPlayer
|
|
|
|
|
|
:list="detailVideoList"
|
|
|
|
|
|
:activeFid="detailActiveFid"
|
|
|
|
|
|
:activeMedia="detailActiveMedia"
|
|
|
|
|
|
:loading="detailVideoLoading"
|
|
|
|
|
|
:page="detailVideoPage"
|
|
|
|
|
|
:pageSize="detailVideoTake"
|
|
|
|
|
|
:pageSizeOptions="detailVideoPageSizeOptions"
|
|
|
|
|
|
:total="detailVideoTotal"
|
|
|
|
|
|
@select="handleSelectDetailMedia"
|
|
|
|
|
|
@pageChange="handleDetailVideoPageChange"
|
|
|
|
|
|
/>
|
2026-07-01 18:23:41 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</a-tab-pane>
|
|
|
|
|
|
</a-tabs>
|
|
|
|
|
|
</a-modal>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 清漂附件预览弹框 -->
|
|
|
|
|
|
<CommonAttachmentModal
|
|
|
|
|
|
v-model:open="qingpiaoModalVisible"
|
|
|
|
|
|
:fid="qingpiaoFids.join(',')"
|
|
|
|
|
|
title="附件预览"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
|
import {
|
|
|
|
|
|
ref,
|
|
|
|
|
|
computed,
|
|
|
|
|
|
watch,
|
|
|
|
|
|
onMounted,
|
|
|
|
|
|
onBeforeUnmount,
|
|
|
|
|
|
nextTick
|
|
|
|
|
|
} from 'vue';
|
|
|
|
|
|
import * as echarts from 'echarts';
|
|
|
|
|
|
import type { ECharts } from 'echarts';
|
|
|
|
|
|
import BasicTable from '@/components/BasicTable/index.vue';
|
|
|
|
|
|
import CommonAttachmentModal from '@/components/MapModal/components/CommonAttachmentModal.vue';
|
|
|
|
|
|
import LiveVideoBox from '@/views/shiPinJianKong/components/LiveVideoBox.vue';
|
2026-07-06 08:54:58 +08:00
|
|
|
|
import VideoPlayer from './components/VideoPlayer.vue';
|
|
|
|
|
|
import {
|
|
|
|
|
|
getAisbdbyxList,
|
|
|
|
|
|
getAiChartTable,
|
|
|
|
|
|
getAiPfwRecord,
|
|
|
|
|
|
getAiRecord,
|
|
|
|
|
|
getAiFile,
|
|
|
|
|
|
getAiRstcd
|
|
|
|
|
|
} from '@/api/zngj';
|
2026-07-01 18:23:41 +08:00
|
|
|
|
import { postIdUrl } from '@/api/mapModal';
|
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
|
|
|
|
|
import videoCover from '@/assets/images/videFm.jpg';
|
|
|
|
|
|
|
|
|
|
|
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== Props ====================
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
month?: string;
|
|
|
|
|
|
tabIdx?: string;
|
|
|
|
|
|
baseid?: string;
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== Store ====================
|
|
|
|
|
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 搜索状态 ====================
|
|
|
|
|
|
const jidiOptions = ref<Array<{ label: string; value: string }>>([]);
|
|
|
|
|
|
const searchData = ref({
|
|
|
|
|
|
baseid: props.baseid || 'all',
|
|
|
|
|
|
stnm: '',
|
2026-07-06 08:54:58 +08:00
|
|
|
|
rstcd: '',
|
2026-07-01 18:23:41 +08:00
|
|
|
|
month: props.month ? dayjs(props.month) : dayjs()
|
|
|
|
|
|
});
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const stcdData = ref<Array<{ label: string; value: string }>>([]);
|
2026-07-01 18:23:41 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 弹框状态 ====================
|
|
|
|
|
|
const attachmentModalVisible = ref(false);
|
|
|
|
|
|
const currentAttachmentFid = ref('');
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== Tabs 状态 ====================
|
|
|
|
|
|
const tabIndex = ref(props.tabIdx ?? '1');
|
|
|
|
|
|
const tabList = ref<any[]>([
|
|
|
|
|
|
{ key: 1, value: '漂浮物识别', unit: '次', count: 0 },
|
|
|
|
|
|
{ key: 3, value: '清漂识别', unit: '次', count: 0 },
|
|
|
|
|
|
{ key: 2, value: '叠梁门运行识别', unit: '次', count: 0 }
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 视图切换状态 ====================
|
|
|
|
|
|
const vdListShow = ref<'1' | '2' | '3'>('2');
|
|
|
|
|
|
|
|
|
|
|
|
// Tab 显示列表(根据视图类型动态计算标题)
|
|
|
|
|
|
const tabDisplayList = computed(() =>
|
|
|
|
|
|
tabList.value.map(item => ({
|
|
|
|
|
|
...item,
|
|
|
|
|
|
tabLabel:
|
|
|
|
|
|
vdListShow.value === '3'
|
|
|
|
|
|
? item.value
|
|
|
|
|
|
: `${item.value}(${item.count}${item.unit})`
|
|
|
|
|
|
}))
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 图表状态 ====================
|
|
|
|
|
|
const chartRefs = ref<Map<string, HTMLElement>>(new Map());
|
|
|
|
|
|
let chartInstance: ECharts | null = null;
|
|
|
|
|
|
const chartData = ref<any[]>([]);
|
|
|
|
|
|
const chartLoading = ref(true);
|
|
|
|
|
|
const pieCode = ref<string[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 视频视图状态 ====================
|
|
|
|
|
|
const videoPage = ref(1);
|
|
|
|
|
|
const videoTake = ref(20);
|
|
|
|
|
|
const videoPageSizeOptions = ['20', '50', '100'];
|
|
|
|
|
|
const videoTotal = ref(0);
|
|
|
|
|
|
const videoLoading = ref(false);
|
|
|
|
|
|
const videoList = ref<any[]>([]);
|
|
|
|
|
|
const activeFid = ref('');
|
|
|
|
|
|
const activeMedia = ref<{ type: string; src: string }>({ type: '', src: '' });
|
|
|
|
|
|
|
|
|
|
|
|
// 视频视图饼图状态
|
|
|
|
|
|
const videoChartRefs = ref<Map<string, HTMLElement>>(new Map());
|
|
|
|
|
|
let videoChartInstance: ECharts | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 日历视图状态 ====================
|
|
|
|
|
|
const calendarLoading = ref(false);
|
|
|
|
|
|
const calendarData = ref<any[]>([]);
|
2026-07-06 10:27:41 +08:00
|
|
|
|
const calendarValue = ref(searchData.value.month);
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const detailModalVisible = ref(false);
|
|
|
|
|
|
const detailActiveKey = ref('');
|
|
|
|
|
|
const detailVideoLoading = ref(false);
|
|
|
|
|
|
const detailVideoList = ref<any[]>([]);
|
|
|
|
|
|
const detailVideoPage = ref(1);
|
|
|
|
|
|
const detailVideoTake = ref(20);
|
|
|
|
|
|
const detailVideoPageSizeOptions = ['20', '50', '100'];
|
|
|
|
|
|
const detailVideoTotal = ref(0);
|
|
|
|
|
|
const detailActiveFid = ref('');
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const detailActiveMedia = ref<{ type: string; src: string }>({
|
|
|
|
|
|
type: '',
|
|
|
|
|
|
src: ''
|
|
|
|
|
|
});
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const detailTabItems = ref<any[]>([]);
|
|
|
|
|
|
const detailDate = ref('');
|
|
|
|
|
|
const detailType = ref('');
|
2026-07-06 10:27:41 +08:00
|
|
|
|
const detailDataType = computed(() => detailActiveKey.value);
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const qingpiaoModalVisible = ref(false);
|
|
|
|
|
|
const qingpiaoFids = ref<string[]>([]);
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 日历状态映射(与React端一致)
|
|
|
|
|
|
const calendarStatusMap: Record<
|
|
|
|
|
|
string,
|
|
|
|
|
|
{ name: string; className: string; type: string; dataType: string }
|
|
|
|
|
|
> = {
|
|
|
|
|
|
'3': {
|
|
|
|
|
|
name: '运行',
|
|
|
|
|
|
className: 'content_state2',
|
|
|
|
|
|
type: 'AI_5005',
|
|
|
|
|
|
dataType: '2'
|
|
|
|
|
|
},
|
|
|
|
|
|
'4': {
|
|
|
|
|
|
name: '未运行',
|
|
|
|
|
|
className: 'content_state3',
|
|
|
|
|
|
type: 'AI_5005',
|
|
|
|
|
|
dataType: '1'
|
|
|
|
|
|
},
|
|
|
|
|
|
'5': {
|
|
|
|
|
|
name: '清漂',
|
|
|
|
|
|
className: 'content_state2',
|
|
|
|
|
|
type: 'AI_5011',
|
|
|
|
|
|
dataType: '3'
|
|
|
|
|
|
},
|
|
|
|
|
|
'6': {
|
|
|
|
|
|
name: '未清漂',
|
|
|
|
|
|
className: 'content_state3',
|
|
|
|
|
|
type: 'AI_5011',
|
|
|
|
|
|
dataType: '3'
|
|
|
|
|
|
},
|
|
|
|
|
|
'0': {
|
|
|
|
|
|
name: '无漂浮物',
|
|
|
|
|
|
className: 'content_state2',
|
|
|
|
|
|
type: 'AI_5001',
|
|
|
|
|
|
dataType: '3'
|
|
|
|
|
|
},
|
|
|
|
|
|
'1': {
|
|
|
|
|
|
name: '小面积漂浮物',
|
|
|
|
|
|
className: 'content_state5',
|
|
|
|
|
|
type: 'AI_5001',
|
|
|
|
|
|
dataType: '4'
|
|
|
|
|
|
},
|
|
|
|
|
|
'2': {
|
|
|
|
|
|
name: '大面积漂浮物',
|
|
|
|
|
|
className: 'content_state3',
|
|
|
|
|
|
type: 'AI_5001',
|
|
|
|
|
|
dataType: '5'
|
|
|
|
|
|
}
|
2026-07-01 18:23:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 函数式 ref 绑定
|
|
|
|
|
|
const setChartRef = (el: any, key: string) => {
|
|
|
|
|
|
if (el instanceof HTMLElement) {
|
|
|
|
|
|
chartRefs.value.set(key, el);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const setVideoChartRef = (el: any, key: string) => {
|
|
|
|
|
|
if (el instanceof HTMLElement) {
|
|
|
|
|
|
videoChartRefs.value.set(key, el);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 表格引用 ====================
|
|
|
|
|
|
const tableRefs = ref<Map<string, any>>(new Map());
|
|
|
|
|
|
|
|
|
|
|
|
const setTableRef = (el: any, key: string) => {
|
|
|
|
|
|
if (el && !(el instanceof HTMLElement)) {
|
|
|
|
|
|
tableRefs.value.set(key, el);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 安全获取 table 实例
|
|
|
|
|
|
const getTableInstance = () => {
|
|
|
|
|
|
return tableRefs.value.get(tabIndex.value) || null;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 表格列配置 ====================
|
|
|
|
|
|
const pfwColumns = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'ennm',
|
|
|
|
|
|
title: '所属电站',
|
|
|
|
|
|
dataIndex: 'ennm',
|
|
|
|
|
|
fixed: 'left' as const,
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
ellipsis: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'stnm',
|
|
|
|
|
|
title: '设施名称',
|
|
|
|
|
|
dataIndex: 'stnm',
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
ellipsis: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'hbRvcdName',
|
|
|
|
|
|
title: '所在河流',
|
|
|
|
|
|
dataIndex: 'hbRvcdName',
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
ellipsis: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'tm',
|
|
|
|
|
|
title: '时间',
|
|
|
|
|
|
dataIndex: 'tm',
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
slots: { customRender: 'tm' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'aiVal',
|
|
|
|
|
|
title: '漂浮物面积',
|
|
|
|
|
|
dataIndex: 'aiVal',
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
ellipsis: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'isBigDebrisArea',
|
|
|
|
|
|
title: '是否为大面积堆积',
|
|
|
|
|
|
dataIndex: 'isBigDebrisArea',
|
|
|
|
|
|
width: 140,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
slots: { customRender: 'isBigDebrisArea' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'fid',
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
dataIndex: 'fid',
|
|
|
|
|
|
width: 100,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
slots: { customRender: 'fid' }
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const dlmColumns = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'stnm',
|
|
|
|
|
|
title: '设施名称',
|
|
|
|
|
|
dataIndex: 'stnm',
|
|
|
|
|
|
fixed: 'left' as const,
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
ellipsis: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'ennm',
|
|
|
|
|
|
title: '所属电站',
|
|
|
|
|
|
dataIndex: 'ennm',
|
|
|
|
|
|
fixed: 'left' as const,
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
ellipsis: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'tm',
|
|
|
|
|
|
title: '操作时间',
|
|
|
|
|
|
dataIndex: 'tm',
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
slots: { customRender: 'tm' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'aiVal',
|
|
|
|
|
|
title: '操作状态',
|
|
|
|
|
|
dataIndex: 'aiVal',
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
slots: { customRender: 'aiVal' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'fid',
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
dataIndex: 'fid',
|
|
|
|
|
|
width: 100,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
slots: { customRender: 'fid' }
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const dlmCntColumns = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'hbRvcdName',
|
|
|
|
|
|
title: '所属河流',
|
|
|
|
|
|
dataIndex: 'hbRvcdName',
|
|
|
|
|
|
fixed: 'left' as const,
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
ellipsis: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'ennm',
|
|
|
|
|
|
title: '所属电站',
|
|
|
|
|
|
dataIndex: 'ennm',
|
|
|
|
|
|
fixed: 'left' as const,
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
ellipsis: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'tm',
|
|
|
|
|
|
title: '清漂时间',
|
|
|
|
|
|
dataIndex: 'tm',
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
slots: { customRender: 'tm' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'fid',
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
dataIndex: 'fid',
|
|
|
|
|
|
width: 100,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
slots: { customRender: 'fid' }
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const currentColumns = computed(() => {
|
|
|
|
|
|
switch (tabIndex.value) {
|
|
|
|
|
|
case '1':
|
|
|
|
|
|
return pfwColumns;
|
|
|
|
|
|
case '2':
|
|
|
|
|
|
return dlmColumns;
|
|
|
|
|
|
case '3':
|
|
|
|
|
|
return dlmCntColumns;
|
|
|
|
|
|
default:
|
|
|
|
|
|
return pfwColumns;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 获取 type ====================
|
|
|
|
|
|
const getType = () => {
|
|
|
|
|
|
switch (tabIndex.value) {
|
|
|
|
|
|
case '1':
|
|
|
|
|
|
return 'AI_5001';
|
|
|
|
|
|
case '2':
|
|
|
|
|
|
return 'AI_5005';
|
|
|
|
|
|
case '3':
|
|
|
|
|
|
return 'AI_5011';
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 'AI_5001';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 构建过滤条件 ====================
|
|
|
|
|
|
const buildFilter = () => {
|
|
|
|
|
|
const filters: any[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'type',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
value: getType()
|
|
|
|
|
|
},
|
|
|
|
|
|
pieCode.value.length > 0 && searchData.value.baseid === 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'baseId',
|
|
|
|
|
|
operator: 'in',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: pieCode.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
pieCode.value.length > 0 && searchData.value.baseid !== 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'rstcd',
|
|
|
|
|
|
operator: 'in',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: pieCode.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
searchData.value.baseid !== 'all' && pieCode.value.length === 0
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'baseId',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: searchData.value.baseid
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'tm',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(searchData.value.month)
|
|
|
|
|
|
.startOf('month')
|
|
|
|
|
|
.format('YYYY-MM-DD 00:00:00')
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'tm',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(searchData.value.month)
|
|
|
|
|
|
.endOf('month')
|
|
|
|
|
|
.format('YYYY-MM-DD 23:59:59')
|
|
|
|
|
|
},
|
|
|
|
|
|
searchData.value.stnm
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'stnm',
|
|
|
|
|
|
operator: 'contains',
|
|
|
|
|
|
value: searchData.value.stnm
|
|
|
|
|
|
}
|
|
|
|
|
|
: null
|
|
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
logic: 'and',
|
|
|
|
|
|
filters
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 表格搜索参数 ====================
|
|
|
|
|
|
const tableSearchParams = computed(() => ({
|
|
|
|
|
|
sort: [
|
|
|
|
|
|
{ field: 'rstcdStepSort', dir: 'asc' },
|
|
|
|
|
|
{ field: 'tm', dir: 'desc' }
|
|
|
|
|
|
],
|
|
|
|
|
|
group: [],
|
|
|
|
|
|
select: [],
|
|
|
|
|
|
groupResultFlat: false
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 数据转换 ====================
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const flattenNestedData = (data: any[]): any[] => {
|
|
|
|
|
|
const result: any[] = [];
|
|
|
|
|
|
data.forEach((level1: any) => {
|
|
|
|
|
|
if (level1?.items?.length) {
|
|
|
|
|
|
level1.items.forEach((level2: any) => {
|
|
|
|
|
|
if (level2?.items?.length) {
|
|
|
|
|
|
level2.items.forEach((level3: any) => {
|
|
|
|
|
|
if (level3?.items?.length) {
|
|
|
|
|
|
result.push(...level3.items);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
result.push(level3);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return result;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const customTransform = (res: any) => {
|
|
|
|
|
|
return {
|
2026-07-06 08:54:58 +08:00
|
|
|
|
records: res?.data?.data || [],
|
|
|
|
|
|
total: res?.data?.total || 0
|
2026-07-01 18:23:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 获取 Tab 数量统计 ====================
|
|
|
|
|
|
const getTabList = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
filter: {
|
|
|
|
|
|
logic: 'and',
|
|
|
|
|
|
filters: [
|
|
|
|
|
|
searchData.value.baseid !== 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'baseId',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
value: searchData.value.baseid
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'startTime',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(searchData.value.month)
|
|
|
|
|
|
.startOf('month')
|
|
|
|
|
|
.format('YYYY-MM-DD 00:00:00')
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'endTime',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(searchData.value.month)
|
|
|
|
|
|
.endOf('month')
|
|
|
|
|
|
.format('YYYY-MM-DD 23:59:59')
|
|
|
|
|
|
},
|
|
|
|
|
|
searchData.value.stnm
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'stnm',
|
|
|
|
|
|
operator: 'contains',
|
|
|
|
|
|
value: searchData.value.stnm
|
|
|
|
|
|
}
|
|
|
|
|
|
: null
|
|
|
|
|
|
].filter(Boolean)
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const res = await getAisbdbyxList(params);
|
|
|
|
|
|
const data = res?.data ?? {};
|
|
|
|
|
|
|
|
|
|
|
|
tabList.value = [
|
|
|
|
|
|
{ key: 1, value: '漂浮物识别', unit: '次', count: data?.pfwCnt ?? 0 },
|
|
|
|
|
|
{ key: 3, value: '清漂识别', unit: '次', count: data?.pfwqlCnt ?? 0 },
|
|
|
|
|
|
{ key: 2, value: '叠梁门运行识别', unit: '次', count: data?.dlmCnt ?? 0 }
|
|
|
|
|
|
];
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取Tab统计失败:', error);
|
|
|
|
|
|
tabList.value = [
|
|
|
|
|
|
{ key: 1, value: '漂浮物识别', unit: '次', count: 0 },
|
|
|
|
|
|
{ key: 3, value: '清漂识别', unit: '次', count: 0 },
|
|
|
|
|
|
{ key: 2, value: '叠梁门运行识别', unit: '次', count: 0 }
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 获取饼图数据 ====================
|
|
|
|
|
|
const getChartData = async () => {
|
|
|
|
|
|
chartLoading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const filter = buildFilter();
|
|
|
|
|
|
const group =
|
|
|
|
|
|
searchData.value.baseid === 'all'
|
|
|
|
|
|
? [
|
|
|
|
|
|
{ dir: 'asc', field: 'baseStepSort' },
|
|
|
|
|
|
{ dir: 'asc', field: 'baseId' },
|
|
|
|
|
|
{ dir: 'asc', field: 'baseName' }
|
|
|
|
|
|
]
|
|
|
|
|
|
: [
|
|
|
|
|
|
{ dir: 'asc', field: 'rstcd' },
|
|
|
|
|
|
{ dir: 'asc', field: 'ennm' }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
filter,
|
|
|
|
|
|
group,
|
2026-07-06 08:54:58 +08:00
|
|
|
|
sort: [
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'rstcdStepSort',
|
|
|
|
|
|
dir: 'asc'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'tm',
|
|
|
|
|
|
dir: 'desc'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2026-07-01 18:23:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const res = await getAiChartTable(params);
|
|
|
|
|
|
const dataObj = res?.data?.data ?? {};
|
|
|
|
|
|
const rawData = Array.isArray(dataObj) ? dataObj : dataObj?.data || [];
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const list: { name: string; value: number; key: string }[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (searchData.value.baseid === 'all') {
|
|
|
|
|
|
rawData.forEach((level1: any) => {
|
|
|
|
|
|
if (level1?.items?.length) {
|
|
|
|
|
|
level1.items.forEach((level2: any) => {
|
|
|
|
|
|
if (level2?.items?.length) {
|
|
|
|
|
|
level2.items.forEach((level3: any) => {
|
|
|
|
|
|
if (level3?.items?.length) {
|
|
|
|
|
|
level3.items.forEach((item: any) => {
|
|
|
|
|
|
list.push({
|
|
|
|
|
|
name: item?.BASENAME || level3.key || '-',
|
|
|
|
|
|
value:
|
|
|
|
|
|
item?.COUNT_BASENAME ||
|
|
|
|
|
|
item?.COUNT_BASEID ||
|
|
|
|
|
|
level3.count ||
|
|
|
|
|
|
0,
|
|
|
|
|
|
key: item?.BASEID || level2.key || ''
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
list.push({
|
|
|
|
|
|
name: level3.key || '-',
|
|
|
|
|
|
value: level3.count || 0,
|
|
|
|
|
|
key: level2.key || ''
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
rawData.forEach((el: any) => {
|
|
|
|
|
|
list.push({
|
|
|
|
|
|
name: el?.ennm_value || el?.ennm || '-',
|
|
|
|
|
|
value: el?.count_rstcd || el?.count_ennm || 0,
|
|
|
|
|
|
key: el?.rstcd || el?.rstcd_value || ''
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-07-01 18:23:41 +08:00
|
|
|
|
|
|
|
|
|
|
chartData.value = list;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取饼图数据失败:', error);
|
|
|
|
|
|
chartData.value = [];
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
chartLoading.value = false;
|
|
|
|
|
|
// 等待 DOM 更新后再初始化图表
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
initChart();
|
|
|
|
|
|
initVideoChart();
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 初始化饼图 ====================
|
|
|
|
|
|
const initChart = (retryCount = 0) => {
|
|
|
|
|
|
// 根据当前 tabIndex 获取对应 tab 的 DOM
|
|
|
|
|
|
const dom = chartRefs.value.get(tabIndex.value);
|
|
|
|
|
|
|
|
|
|
|
|
if (!dom) {
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
`[initChart] DOM not found for tab ${tabIndex.value}, retrying...`
|
|
|
|
|
|
);
|
|
|
|
|
|
if (retryCount < 10) {
|
|
|
|
|
|
setTimeout(() => initChart(retryCount + 1), 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 DOM 是否可见(宽高不为 0)
|
|
|
|
|
|
const rect = dom.getBoundingClientRect();
|
|
|
|
|
|
if (rect.width === 0 || rect.height === 0) {
|
|
|
|
|
|
if (retryCount < 10) {
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
`[initChart] DOM has 0 dimensions, retrying in 100ms... (attempt ${
|
|
|
|
|
|
retryCount + 1
|
|
|
|
|
|
}/10)`
|
|
|
|
|
|
);
|
|
|
|
|
|
setTimeout(() => initChart(retryCount + 1), 100);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
'[initChart] DOM still has 0 dimensions after 10 retries, giving up'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!chartInstance) {
|
|
|
|
|
|
chartInstance = echarts.init(dom);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const option: echarts.EChartsOption = {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
formatter: (params: any) =>
|
|
|
|
|
|
`${params.marker} ${params.name}: ${params.value}(次)`
|
|
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
orient: 'vertical',
|
|
|
|
|
|
right: 10,
|
|
|
|
|
|
top: 80,
|
|
|
|
|
|
textStyle: { fontSize: 12 }
|
|
|
|
|
|
},
|
|
|
|
|
|
series: []
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (!chartData.value || chartData.value.length === 0) {
|
|
|
|
|
|
chartInstance.setOption(option, true);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const total = chartData.value.reduce(
|
|
|
|
|
|
(acc: number, item: any) => acc + (item.value || 0),
|
|
|
|
|
|
0
|
|
|
|
|
|
);
|
|
|
|
|
|
const typeMap: Record<string, string> = {
|
|
|
|
|
|
'1': '清漂(次)',
|
|
|
|
|
|
'2': '叠梁门开启(次)',
|
|
|
|
|
|
'3': '清漂识别(次)'
|
|
|
|
|
|
};
|
|
|
|
|
|
const titleText = typeMap[tabIndex.value] || '识别(次)';
|
|
|
|
|
|
|
|
|
|
|
|
option.series = [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['26%', '35%'],
|
|
|
|
|
|
center: ['35%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
formatter: '{c}'
|
|
|
|
|
|
},
|
|
|
|
|
|
labelLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
length: 12,
|
|
|
|
|
|
length2: 12
|
|
|
|
|
|
},
|
|
|
|
|
|
data: chartData.value.map((item: any) => ({
|
|
|
|
|
|
name: item.name,
|
|
|
|
|
|
value: item.value
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
option.title = {
|
|
|
|
|
|
text: String(total),
|
|
|
|
|
|
left: '34%',
|
|
|
|
|
|
top: '44%',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 24,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
subtext: titleText,
|
|
|
|
|
|
subtextStyle: {
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
chartInstance.setOption(option, true);
|
|
|
|
|
|
|
|
|
|
|
|
// 图例点击联动过滤表格
|
|
|
|
|
|
chartInstance.off('legendselectchanged');
|
|
|
|
|
|
chartInstance.on('legendselectchanged', (e: any) => {
|
|
|
|
|
|
const trueRivers = Object.keys(e.selected).filter(
|
|
|
|
|
|
(key: string) => e.selected[key]
|
|
|
|
|
|
);
|
|
|
|
|
|
const allFalse = Object.values(e.selected).every(
|
|
|
|
|
|
(value: any) => value === false
|
|
|
|
|
|
);
|
|
|
|
|
|
const selectedKeys = chartData.value
|
|
|
|
|
|
.filter((item: any) => trueRivers.includes(item.name))
|
|
|
|
|
|
.map((item: any) => item.key);
|
|
|
|
|
|
pieCode.value = allFalse ? [] : selectedKeys;
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
const filter = buildFilter();
|
|
|
|
|
|
getTableInstance()?.getList(filter);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 初始化视频视图饼图 ====================
|
|
|
|
|
|
const initVideoChart = (retryCount = 0) => {
|
|
|
|
|
|
const dom = videoChartRefs.value.get(tabIndex.value);
|
|
|
|
|
|
|
|
|
|
|
|
if (!dom) {
|
|
|
|
|
|
if (retryCount < 10) {
|
|
|
|
|
|
setTimeout(() => initVideoChart(retryCount + 1), 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const rect = dom.getBoundingClientRect();
|
|
|
|
|
|
if (rect.width === 0 || rect.height === 0) {
|
|
|
|
|
|
if (retryCount < 10) {
|
|
|
|
|
|
setTimeout(() => initVideoChart(retryCount + 1), 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!videoChartInstance) {
|
|
|
|
|
|
videoChartInstance = echarts.init(dom);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const option: echarts.EChartsOption = {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
formatter: (params: any) =>
|
|
|
|
|
|
`${params.marker} ${params.name}: ${params.value}(次)`
|
|
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
orient: 'vertical',
|
|
|
|
|
|
right: 10,
|
|
|
|
|
|
top: 80,
|
|
|
|
|
|
textStyle: { fontSize: 12 }
|
|
|
|
|
|
},
|
|
|
|
|
|
series: []
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (!chartData.value || chartData.value.length === 0) {
|
|
|
|
|
|
videoChartInstance.setOption(option, true);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const total = chartData.value.reduce(
|
|
|
|
|
|
(acc: number, item: any) => acc + (item.value || 0),
|
|
|
|
|
|
0
|
|
|
|
|
|
);
|
|
|
|
|
|
const typeMap: Record<string, string> = {
|
|
|
|
|
|
'1': '清漂(次)',
|
|
|
|
|
|
'2': '叠梁门开启(次)',
|
|
|
|
|
|
'3': '清漂识别(次)'
|
|
|
|
|
|
};
|
|
|
|
|
|
const titleText = typeMap[tabIndex.value] || '识别(次)';
|
|
|
|
|
|
|
|
|
|
|
|
option.series = [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['26%', '35%'],
|
|
|
|
|
|
center: ['35%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
formatter: '{c}'
|
|
|
|
|
|
},
|
|
|
|
|
|
labelLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
length: 12,
|
|
|
|
|
|
length2: 12
|
|
|
|
|
|
},
|
|
|
|
|
|
data: chartData.value.map((item: any) => ({
|
|
|
|
|
|
name: item.name,
|
|
|
|
|
|
value: item.value
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
option.title = {
|
|
|
|
|
|
text: String(total),
|
|
|
|
|
|
left: '34%',
|
|
|
|
|
|
top: '44%',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 24,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
subtext: titleText,
|
|
|
|
|
|
subtextStyle: {
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
videoChartInstance.setOption(option, true);
|
|
|
|
|
|
|
|
|
|
|
|
// 图例点击联动过滤视频列表
|
|
|
|
|
|
videoChartInstance.off('legendselectchanged');
|
|
|
|
|
|
videoChartInstance.on('legendselectchanged', (e: any) => {
|
|
|
|
|
|
const trueRivers = Object.keys(e.selected).filter(
|
|
|
|
|
|
(key: string) => e.selected[key]
|
|
|
|
|
|
);
|
|
|
|
|
|
const allFalse = Object.values(e.selected).every(
|
|
|
|
|
|
(value: any) => value === false
|
|
|
|
|
|
);
|
|
|
|
|
|
const selectedKeys = chartData.value
|
|
|
|
|
|
.filter((item: any) => trueRivers.includes(item.name))
|
|
|
|
|
|
.map((item: any) => item.key);
|
|
|
|
|
|
pieCode.value = allFalse ? [] : selectedKeys;
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
videoPage.value = 1;
|
|
|
|
|
|
activeFid.value = '';
|
|
|
|
|
|
activeMedia.value = { type: '', src: '' };
|
|
|
|
|
|
fetchVideoList();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 获取视频列表 ====================
|
|
|
|
|
|
const fetchVideoList = async () => {
|
|
|
|
|
|
videoLoading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const filter = buildFilter();
|
|
|
|
|
|
const skip = (videoPage.value - 1) * videoTake.value;
|
|
|
|
|
|
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
filter,
|
|
|
|
|
|
skip,
|
|
|
|
|
|
take: videoTake.value,
|
|
|
|
|
|
sort: [
|
|
|
|
|
|
{ field: 'rstcdStepSort', dir: 'asc' },
|
|
|
|
|
|
{ field: 'tm', dir: 'desc' }
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const res = await getAiChartTable(params);
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const rData = res?.data ?? {};
|
|
|
|
|
|
const rawData = rData?.data || [];
|
|
|
|
|
|
videoTotal.value = rData?.total || rawData.length;
|
|
|
|
|
|
|
|
|
|
|
|
// 按 fid 展开(与 React 一致,支持逗号分隔的多个 fid)
|
|
|
|
|
|
const expandedData = rawData
|
|
|
|
|
|
.map((item: any) => {
|
|
|
|
|
|
const fids = item.fid ? item.fid.split(',') : [];
|
|
|
|
|
|
return fids.map((fid: string) => ({ ...item, fid: fid.trim() }));
|
|
|
|
|
|
})
|
|
|
|
|
|
.flat();
|
2026-07-01 18:23:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 收集所有 fid
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const allFids = expandedData
|
|
|
|
|
|
.filter((item: any) => item.fid)
|
|
|
|
|
|
.map((item: any) => item.fid)
|
|
|
|
|
|
.join(',');
|
2026-07-01 18:23:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 调用 postIdUrl 获取媒体详情
|
|
|
|
|
|
let mediaMap: Record<string, any> = {};
|
2026-07-06 08:54:58 +08:00
|
|
|
|
if (allFids) {
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const formData = new FormData();
|
2026-07-06 08:54:58 +08:00
|
|
|
|
formData.append('id', allFids);
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const mediaRes: any = await postIdUrl(formData);
|
|
|
|
|
|
mediaMap = mediaRes?.data || {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 构建列表(与 React 一致)
|
|
|
|
|
|
const list: any[] = [];
|
|
|
|
|
|
expandedData.forEach((vd: any) => {
|
|
|
|
|
|
const fileInfo = mediaMap[vd.fid];
|
|
|
|
|
|
const hasValidExtension =
|
|
|
|
|
|
fileInfo && ['.jpg', '.png'].includes(fileInfo.ext);
|
|
|
|
|
|
|
|
|
|
|
|
let flpth = '';
|
|
|
|
|
|
let imgPath = '';
|
|
|
|
|
|
let type = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (hasValidExtension !== undefined && !hasValidExtension) {
|
|
|
|
|
|
// 非图片(视频)
|
|
|
|
|
|
flpth = fileInfo.fullpath;
|
|
|
|
|
|
type = 'vdsp';
|
|
|
|
|
|
} else if (hasValidExtension !== undefined) {
|
|
|
|
|
|
// 图片
|
|
|
|
|
|
imgPath = fileInfo.fullpath;
|
|
|
|
|
|
type = 'img';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tm = vd.tm ? dayjs(vd.tm).format('YYYY-MM-DD HH:mm:ss') : '-';
|
|
|
|
|
|
list.push({
|
|
|
|
|
|
...vd,
|
|
|
|
|
|
flpth,
|
|
|
|
|
|
imgPath,
|
|
|
|
|
|
flnm: `${vd.ennm || ''}${tm}`,
|
|
|
|
|
|
type,
|
|
|
|
|
|
tm
|
2026-07-01 18:23:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 过滤掉无效项(与 React 一致)
|
|
|
|
|
|
const listMap = list.filter(
|
|
|
|
|
|
item => item.flpth !== '' || item.imgPath !== ''
|
|
|
|
|
|
);
|
|
|
|
|
|
videoList.value = listMap;
|
|
|
|
|
|
// debugger
|
2026-07-01 18:23:41 +08:00
|
|
|
|
// 默认选中第一条
|
2026-07-06 08:54:58 +08:00
|
|
|
|
if (listMap.length > 0 && !activeFid.value) {
|
|
|
|
|
|
handleSelectMedia(listMap[0]);
|
2026-07-01 18:23:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取视频列表失败:', error);
|
|
|
|
|
|
videoList.value = [];
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
videoLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 选择媒体(与 React 一致)
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const handleSelectMedia = (item: any) => {
|
|
|
|
|
|
activeFid.value = item.fid;
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
if (item.type === 'img') {
|
|
|
|
|
|
activeMedia.value = {
|
|
|
|
|
|
type: 'image',
|
|
|
|
|
|
src: baseUrl + '?' + item.fid + '&view=jpg'
|
|
|
|
|
|
};
|
|
|
|
|
|
} else if (item.type === 'vdsp') {
|
|
|
|
|
|
activeMedia.value = {
|
|
|
|
|
|
type: 'video',
|
|
|
|
|
|
src: baseUrl + '?' + item.fid + '&view=jpg'
|
|
|
|
|
|
};
|
2026-07-01 18:23:41 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
activeMedia.value = { type: '', src: '' };
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 分页切换
|
|
|
|
|
|
const handleVideoPageChange = () => {
|
|
|
|
|
|
activeFid.value = '';
|
|
|
|
|
|
activeMedia.value = { type: '', src: '' };
|
|
|
|
|
|
fetchVideoList();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// ==================== 获取电站列表(用于日历视图的rstcd下拉) ====================
|
|
|
|
|
|
const colStcdData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const type = getType();
|
|
|
|
|
|
const filters: any[] = [
|
|
|
|
|
|
{ field: 'type', operator: 'eq', value: type },
|
|
|
|
|
|
searchData.value.baseid !== 'all'
|
|
|
|
|
|
? { field: 'baseId', operator: 'eq', value: searchData.value.baseid }
|
|
|
|
|
|
: null,
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'startTime',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(searchData.value.month)
|
|
|
|
|
|
.startOf('month')
|
|
|
|
|
|
.format('YYYY-MM-DD 00:00:00')
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'endTime',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(searchData.value.month)
|
|
|
|
|
|
.endOf('month')
|
|
|
|
|
|
.format('YYYY-MM-DD 23:59:59')
|
|
|
|
|
|
}
|
|
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
const params = { filter: { logic: 'and', filters } };
|
|
|
|
|
|
const res: any = await getAiRstcd(params);
|
2026-07-06 10:27:41 +08:00
|
|
|
|
const _data = res?.data?.data ?? [];
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const list = _data.map((item: any) => ({
|
|
|
|
|
|
label: item.stnm,
|
|
|
|
|
|
value: item.stcd
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// 如果当前选中的rstcd不在新列表中,重置为第一个
|
|
|
|
|
|
const selected = list?.filter(
|
|
|
|
|
|
(item: any) => item.value === searchData.value.rstcd
|
|
|
|
|
|
);
|
|
|
|
|
|
if (selected?.length === 0) {
|
|
|
|
|
|
searchData.value.rstcd = list?.[0]?.value ?? '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stcdData.value = list;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取电站列表失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-01 18:23:41 +08:00
|
|
|
|
// ==================== 日历视图函数 ====================
|
|
|
|
|
|
// 判断是否是当前月份
|
|
|
|
|
|
const isCurrentMonth = (current: any): boolean => {
|
2026-07-06 08:54:58 +08:00
|
|
|
|
return (
|
|
|
|
|
|
current.month() === calendarValue.value.month() &&
|
|
|
|
|
|
current.year() === calendarValue.value.year()
|
|
|
|
|
|
);
|
2026-07-01 18:23:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取某天的日历数据
|
|
|
|
|
|
const getCalendarDataForDay = (current: any): any => {
|
2026-07-06 08:54:58 +08:00
|
|
|
|
return calendarData.value.find(
|
|
|
|
|
|
(item: any) => dayjs(item.dt).format('DD') === current.format('DD')
|
|
|
|
|
|
);
|
2026-07-01 18:23:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 获取状态样式类(支持数字和字符串类型)
|
|
|
|
|
|
const getStatusClass = (status: number | string) => {
|
|
|
|
|
|
const statusKey = String(status);
|
|
|
|
|
|
return calendarStatusMap[statusKey]?.className || '';
|
2026-07-01 18:23:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 获取状态文本(支持数字和字符串类型)
|
|
|
|
|
|
const getStatusText = (status: number | string) => {
|
|
|
|
|
|
const statusKey = String(status);
|
|
|
|
|
|
return calendarStatusMap[statusKey]?.name || '';
|
2026-07-01 18:23:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 获取日历数据(参考React端逻辑)
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const fetchCalendarData = async () => {
|
|
|
|
|
|
calendarLoading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const type = getType();
|
|
|
|
|
|
const filters: any[] = [
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// baseId过滤(支持饼图联动)
|
|
|
|
|
|
pieCode.value.length > 0 && searchData.value.baseid === 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'baseId',
|
|
|
|
|
|
operator: 'in',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: pieCode.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: searchData.value.baseid !== 'all'
|
|
|
|
|
|
? { field: 'baseId', operator: 'eq', value: searchData.value.baseid }
|
|
|
|
|
|
: null,
|
|
|
|
|
|
// rstcd过滤(饼图联动时)
|
|
|
|
|
|
pieCode.value.length > 0 && searchData.value.baseid !== 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'rstcd',
|
|
|
|
|
|
operator: 'in',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: pieCode.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
// type过滤
|
2026-07-01 18:23:41 +08:00
|
|
|
|
{ field: 'type', operator: 'eq', value: type },
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 时间范围
|
2026-07-01 18:23:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
field: 'startTime',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
dataType: 'date',
|
2026-07-06 08:54:58 +08:00
|
|
|
|
value: dayjs(searchData.value.month)
|
|
|
|
|
|
.startOf('month')
|
|
|
|
|
|
.format('YYYY-MM-DD 00:00:00')
|
2026-07-01 18:23:41 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'endTime',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
dataType: 'date',
|
2026-07-06 08:54:58 +08:00
|
|
|
|
value: dayjs(searchData.value.month)
|
|
|
|
|
|
.endOf('month')
|
|
|
|
|
|
.format('YYYY-MM-DD 23:59:59')
|
|
|
|
|
|
},
|
|
|
|
|
|
// stnm文本搜索
|
|
|
|
|
|
searchData.value.stnm
|
|
|
|
|
|
? { field: 'stnm', operator: 'contains', value: searchData.value.stnm }
|
|
|
|
|
|
: null,
|
|
|
|
|
|
// rstcd电站选择(日历视图下)
|
|
|
|
|
|
searchData.value.rstcd
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'rstcd',
|
|
|
|
|
|
operator: 'in',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: [searchData.value.rstcd]
|
|
|
|
|
|
}
|
|
|
|
|
|
: null
|
2026-07-01 18:23:41 +08:00
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const params = {
|
|
|
|
|
|
filter: { logic: 'and', filters },
|
|
|
|
|
|
sort: tabIndex.value === '1' ? [] : [{ field: 'tm', dir: 'desc' }]
|
|
|
|
|
|
};
|
2026-07-01 18:23:41 +08:00
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 根据类型调用不同接口(与React端一致:AI_5005用getAiRecord,其余用getAiPfwRecord)
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const apiFn = type === 'AI_5005' ? getAiRecord : getAiPfwRecord;
|
|
|
|
|
|
const res: any = await apiFn(params);
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 接口返回结构: { code, msg, data: [...] } - data 直接是数组,不是 res.data.data
|
|
|
|
|
|
const payload = res?.data || {};
|
|
|
|
|
|
calendarData.value = Array.isArray(payload) ? payload : payload.data || [];
|
2026-07-01 18:23:41 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取日历数据失败:', error);
|
|
|
|
|
|
calendarData.value = [];
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
calendarLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 日历面板切换
|
|
|
|
|
|
const handleCalendarPanelChange = (date: any) => {
|
|
|
|
|
|
calendarValue.value = date;
|
2026-07-06 08:54:58 +08:00
|
|
|
|
fetchCalendarData();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 日历日期选择
|
|
|
|
|
|
const handleCalendarSelect = (date: any) => {
|
|
|
|
|
|
calendarValue.value = date;
|
2026-07-01 18:23:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 点击日历日期(参考React端逻辑)
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const handleCalendarDayClick = async (current: any, dayData: any) => {
|
|
|
|
|
|
const date = dayjs(current).format('YYYY-MM-DD');
|
|
|
|
|
|
const status = dayData.status;
|
|
|
|
|
|
const statusInfo = calendarStatusMap[status];
|
|
|
|
|
|
if (!statusInfo) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 清漂识别直接打开附件预览
|
|
|
|
|
|
if (tabIndex.value === '3') {
|
|
|
|
|
|
const fidStr = dayData.fid;
|
|
|
|
|
|
if (!fidStr) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-07-06 08:54:58 +08:00
|
|
|
|
qingpiaoFids.value = fidStr
|
|
|
|
|
|
.split(',')
|
|
|
|
|
|
.map((s: string) => s.trim())
|
|
|
|
|
|
.filter(Boolean);
|
2026-07-01 18:23:41 +08:00
|
|
|
|
qingpiaoModalVisible.value = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 漂浮物/叠梁门打开视频详情弹窗
|
|
|
|
|
|
detailDate.value = date;
|
|
|
|
|
|
detailType.value = statusInfo.type;
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 设置 Tab 项(与React端一致)
|
|
|
|
|
|
if (tabIndex.value === '2') {
|
|
|
|
|
|
// 叠梁门运行识别:运行 / 未运行
|
|
|
|
|
|
detailTabItems.value = [
|
|
|
|
|
|
{ key: '2', label: '运行' },
|
|
|
|
|
|
{ key: '1', label: '未运行' }
|
|
|
|
|
|
];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 漂浮物识别:无漂浮物 / 小面积漂浮物 / 大面积漂浮物
|
2026-07-01 18:23:41 +08:00
|
|
|
|
detailTabItems.value = [
|
|
|
|
|
|
{ key: '3', label: '无漂浮物' },
|
|
|
|
|
|
{ key: '4', label: '小面积漂浮物' },
|
|
|
|
|
|
{ key: '5', label: '大面积漂浮物' }
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
detailActiveKey.value = statusInfo.dataType;
|
|
|
|
|
|
detailModalVisible.value = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取视频数据
|
|
|
|
|
|
fetchDetailVideoList();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取详情视频列表
|
|
|
|
|
|
const fetchDetailVideoList = async () => {
|
|
|
|
|
|
detailVideoLoading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const filters: any[] = [
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// baseId过滤(支持饼图联动)
|
|
|
|
|
|
pieCode.value.length > 0 && searchData.value.baseid === 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'baseId',
|
|
|
|
|
|
operator: 'in',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: pieCode.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: searchData.value.baseid !== 'all'
|
|
|
|
|
|
? { field: 'baseId', operator: 'eq', value: searchData.value.baseid }
|
|
|
|
|
|
: null,
|
|
|
|
|
|
// rstcd过滤(饼图联动时)
|
|
|
|
|
|
pieCode.value.length > 0 && searchData.value.baseid !== 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'rstcd',
|
|
|
|
|
|
operator: 'in',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: pieCode.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
// type过滤
|
2026-07-01 18:23:41 +08:00
|
|
|
|
{ field: 'type', operator: 'eq', value: detailType.value },
|
2026-07-06 10:27:41 +08:00
|
|
|
|
// dataType过滤
|
|
|
|
|
|
{ field: 'dataType', operator: 'eq', value: detailDataType.value },
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 时间范围
|
2026-07-01 18:23:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
field: 'tm',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
dataType: 'date',
|
2026-07-06 08:54:58 +08:00
|
|
|
|
value: dayjs(detailDate.value)
|
|
|
|
|
|
.startOf('day')
|
|
|
|
|
|
.format('YYYY-MM-DD 00:00:00')
|
2026-07-01 18:23:41 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'tm',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
dataType: 'date',
|
2026-07-06 08:54:58 +08:00
|
|
|
|
value: dayjs(detailDate.value)
|
|
|
|
|
|
.endOf('day')
|
|
|
|
|
|
.format('YYYY-MM-DD 23:59:59')
|
2026-07-01 18:23:41 +08:00
|
|
|
|
},
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// stnm文本搜索
|
|
|
|
|
|
searchData.value.stnm
|
|
|
|
|
|
? { field: 'stnm', operator: 'contains', value: searchData.value.stnm }
|
|
|
|
|
|
: null,
|
|
|
|
|
|
// rstcd电站选择(日历视图下)
|
|
|
|
|
|
searchData.value.rstcd
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'rstcd',
|
|
|
|
|
|
operator: 'in',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: [searchData.value.rstcd]
|
|
|
|
|
|
}
|
|
|
|
|
|
: null
|
2026-07-01 18:23:41 +08:00
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
filter: { logic: 'and', filters },
|
2026-07-06 08:54:58 +08:00
|
|
|
|
sort: tabIndex.value === '1' ? [] : [{ field: 'tm', dir: 'desc' }],
|
2026-07-01 18:23:41 +08:00
|
|
|
|
skip: (detailVideoPage.value - 1) * detailVideoTake.value,
|
|
|
|
|
|
take: detailVideoTake.value
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const res: any = await getAiFile(params);
|
2026-07-06 08:54:58 +08:00
|
|
|
|
console.log('二级弹框接口返回:', res);
|
|
|
|
|
|
const rData = res?.data ?? {};
|
|
|
|
|
|
const rawData = rData?.data || [];
|
|
|
|
|
|
console.log('提取的原始数据:', rawData);
|
|
|
|
|
|
detailVideoTotal.value = rData?.total || rawData.length;
|
|
|
|
|
|
|
|
|
|
|
|
// 按 fid 展开(与 React 一致,支持逗号分隔的多个 fid)
|
|
|
|
|
|
const expandedData = rawData
|
|
|
|
|
|
.map((item: any) => {
|
|
|
|
|
|
const fids = item.fid ? item.fid.split(',') : [];
|
|
|
|
|
|
return fids.map((fid: string) => ({ ...item, fid: fid.trim() }));
|
|
|
|
|
|
})
|
|
|
|
|
|
.flat();
|
2026-07-01 18:23:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 收集所有 fid
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const allFids = expandedData
|
|
|
|
|
|
.filter((item: any) => item.fid)
|
|
|
|
|
|
.map((item: any) => item.fid)
|
|
|
|
|
|
.join(',');
|
2026-07-01 18:23:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 调用 postIdUrl 获取媒体详情
|
|
|
|
|
|
let mediaMap: Record<string, any> = {};
|
2026-07-06 08:54:58 +08:00
|
|
|
|
if (allFids) {
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const formData = new FormData();
|
2026-07-06 08:54:58 +08:00
|
|
|
|
formData.append('id', allFids);
|
|
|
|
|
|
console.log('请求媒体详情, allFids:', allFids);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const mediaRes: any = await postIdUrl(formData);
|
|
|
|
|
|
console.log('媒体详情返回:', mediaRes);
|
|
|
|
|
|
mediaMap = mediaRes?.data || {};
|
|
|
|
|
|
console.log('mediaMap:', mediaMap);
|
|
|
|
|
|
} catch (mediaError) {
|
|
|
|
|
|
console.error('获取媒体详情失败:', mediaError);
|
|
|
|
|
|
mediaMap = {};
|
|
|
|
|
|
}
|
2026-07-01 18:23:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 构建列表(与 React 一致)
|
|
|
|
|
|
const list: any[] = [];
|
|
|
|
|
|
expandedData.forEach((vd: any) => {
|
|
|
|
|
|
const fileInfo = mediaMap[vd.fid];
|
|
|
|
|
|
const hasValidExtension =
|
|
|
|
|
|
fileInfo && ['.jpg', '.png'].includes(fileInfo.ext);
|
|
|
|
|
|
|
|
|
|
|
|
let flpth = '';
|
|
|
|
|
|
let imgPath = '';
|
|
|
|
|
|
let type = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (hasValidExtension !== undefined && !hasValidExtension) {
|
|
|
|
|
|
// 非图片(视频)
|
|
|
|
|
|
flpth = fileInfo.fullpath;
|
|
|
|
|
|
type = 'vdsp';
|
|
|
|
|
|
} else if (hasValidExtension !== undefined) {
|
|
|
|
|
|
// 图片
|
|
|
|
|
|
imgPath = fileInfo.fullpath;
|
|
|
|
|
|
type = 'img';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tm = vd.tm ? dayjs(vd.tm).format('YYYY-MM-DD HH:mm:ss') : '-';
|
|
|
|
|
|
list.push({
|
|
|
|
|
|
...vd,
|
|
|
|
|
|
flpth,
|
|
|
|
|
|
imgPath,
|
|
|
|
|
|
flnm: `${vd.ennm || ''}${tm}`,
|
|
|
|
|
|
type,
|
|
|
|
|
|
tm
|
2026-07-01 18:23:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
console.log('构建的列表(过滤前):', list);
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤掉无效项(与 React 一致)
|
|
|
|
|
|
const listMap = list.filter(
|
|
|
|
|
|
item => item.flpth !== '' || item.imgPath !== ''
|
|
|
|
|
|
);
|
|
|
|
|
|
console.log('过滤后的列表:', listMap);
|
|
|
|
|
|
detailVideoList.value = listMap;
|
2026-07-01 18:23:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 默认选中第一条
|
2026-07-06 08:54:58 +08:00
|
|
|
|
if (listMap.length > 0) {
|
|
|
|
|
|
const first = listMap[0];
|
|
|
|
|
|
detailActiveFid.value = first.fid;
|
|
|
|
|
|
if (first.type === 'img') {
|
|
|
|
|
|
detailActiveMedia.value = {
|
|
|
|
|
|
type: 'image',
|
|
|
|
|
|
src: baseUrl + '?' + first.fid + '&view=jpg'
|
|
|
|
|
|
};
|
|
|
|
|
|
} else if (first.type === 'vdsp') {
|
|
|
|
|
|
detailActiveMedia.value = {
|
|
|
|
|
|
type: 'video',
|
|
|
|
|
|
src: baseUrl + '?' + first.fid + '&view=jpg'
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
detailActiveMedia.value = { type: '', src: '' };
|
|
|
|
|
|
}
|
2026-07-01 18:23:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取详情视频列表失败:', error);
|
|
|
|
|
|
detailVideoList.value = [];
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
detailVideoLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// 选择详情媒体(与 React 一致)
|
2026-07-01 18:23:41 +08:00
|
|
|
|
const handleSelectDetailMedia = (item: any) => {
|
|
|
|
|
|
detailActiveFid.value = item.fid;
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
if (item.type === 'img') {
|
|
|
|
|
|
detailActiveMedia.value = {
|
|
|
|
|
|
type: 'image',
|
|
|
|
|
|
src: baseUrl + '?' + item.fid + '&view=jpg'
|
|
|
|
|
|
};
|
|
|
|
|
|
} else if (item.type === 'vdsp') {
|
|
|
|
|
|
detailActiveMedia.value = {
|
|
|
|
|
|
type: 'video',
|
|
|
|
|
|
src: baseUrl + '?' + item.fid + '&view=jpg'
|
|
|
|
|
|
};
|
2026-07-01 18:23:41 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
detailActiveMedia.value = { type: '', src: '' };
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 详情分页切换
|
2026-07-06 08:54:58 +08:00
|
|
|
|
const handleDetailVideoPageChange = (page: number, pageSize: number) => {
|
|
|
|
|
|
detailVideoPage.value = page;
|
|
|
|
|
|
detailVideoTake.value = pageSize;
|
2026-07-01 18:23:41 +08:00
|
|
|
|
detailActiveFid.value = '';
|
|
|
|
|
|
detailActiveMedia.value = { type: '', src: '' };
|
|
|
|
|
|
fetchDetailVideoList();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 事件处理 ====================
|
|
|
|
|
|
const handleBaseChange = () => {
|
|
|
|
|
|
pieCode.value = [];
|
|
|
|
|
|
handleSearch();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
|
|
|
getTabList();
|
|
|
|
|
|
getChartData();
|
|
|
|
|
|
videoPage.value = 1;
|
|
|
|
|
|
activeFid.value = '';
|
|
|
|
|
|
activeMedia.value = { type: '', src: '' };
|
|
|
|
|
|
fetchVideoList();
|
|
|
|
|
|
fetchCalendarData();
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
const filter = buildFilter();
|
|
|
|
|
|
getTableInstance()?.getList(filter);
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleViewAttachment = (fid: string) => {
|
|
|
|
|
|
currentAttachmentFid.value = fid;
|
|
|
|
|
|
attachmentModalVisible.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 监听基地变化 ====================
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => JidiSelectEventStore.selectedItem,
|
|
|
|
|
|
newVal => {
|
|
|
|
|
|
if (newVal && newVal.wbsCode) {
|
|
|
|
|
|
searchData.value.baseid = newVal.wbsCode;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true, immediate: true }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// ==================== 监听视图切换 ====================
|
|
|
|
|
|
watch(vdListShow, () => {
|
|
|
|
|
|
if (vdListShow.value === '1') {
|
|
|
|
|
|
fetchVideoList();
|
|
|
|
|
|
getChartData();
|
|
|
|
|
|
} else if (vdListShow.value === '2') {
|
|
|
|
|
|
getChartData();
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
const filter = buildFilter();
|
|
|
|
|
|
getTableInstance()?.getList(filter);
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (vdListShow.value === '3') {
|
|
|
|
|
|
colStcdData();
|
|
|
|
|
|
fetchCalendarData();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 监听baseid/tabIndex/month变化,更新电站列表 ====================
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => [searchData.value.baseid, tabIndex.value, searchData.value.month],
|
|
|
|
|
|
() => {
|
|
|
|
|
|
colStcdData();
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-07-06 10:27:41 +08:00
|
|
|
|
// ==================== 监听月份变化,同步日历显示 ====================
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => searchData.value.month,
|
|
|
|
|
|
(newMonth) => {
|
|
|
|
|
|
calendarValue.value = newMonth;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-07-01 18:23:41 +08:00
|
|
|
|
// ==================== 监听 Tab 切换 ====================
|
|
|
|
|
|
watch(tabIndex, () => {
|
|
|
|
|
|
pieCode.value = [];
|
|
|
|
|
|
// 清空视频状态
|
|
|
|
|
|
videoPage.value = 1;
|
|
|
|
|
|
activeFid.value = '';
|
|
|
|
|
|
activeMedia.value = { type: '', src: '' };
|
|
|
|
|
|
// 销毁旧实例,避免 setOption 到已隐藏的 DOM
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.dispose();
|
|
|
|
|
|
chartInstance = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (videoChartInstance) {
|
|
|
|
|
|
videoChartInstance.dispose();
|
|
|
|
|
|
videoChartInstance = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 等待新 tab DOM 渲染后再请求数据
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
getChartData();
|
|
|
|
|
|
fetchVideoList();
|
|
|
|
|
|
fetchCalendarData();
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
const filter = buildFilter();
|
|
|
|
|
|
getTableInstance()?.getList(filter);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-06 08:54:58 +08:00
|
|
|
|
// ==================== 监听详情弹窗 Tab 切换 ====================
|
|
|
|
|
|
watch(detailActiveKey, () => {
|
|
|
|
|
|
if (detailModalVisible.value) {
|
|
|
|
|
|
detailVideoPage.value = 1;
|
|
|
|
|
|
fetchDetailVideoList();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-01 18:23:41 +08:00
|
|
|
|
// ==================== 窗口 resize ====================
|
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
|
const dom = chartRefs.value.get(tabIndex.value);
|
|
|
|
|
|
if (dom) {
|
|
|
|
|
|
chartInstance?.resize();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 生命周期 ====================
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 初始化基地选项
|
|
|
|
|
|
const jidiList = JidiSelectEventStore.jidiData || [];
|
|
|
|
|
|
jidiOptions.value = jidiList.map((item: any) => ({
|
|
|
|
|
|
label: item.wbsName,
|
|
|
|
|
|
value: item.wbsCode
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
getTabList();
|
|
|
|
|
|
getChartData();
|
|
|
|
|
|
fetchVideoList();
|
|
|
|
|
|
fetchCalendarData();
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
const filter = buildFilter();
|
|
|
|
|
|
getTableInstance()?.getList(filter);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
|
|
|
chartInstance?.dispose();
|
|
|
|
|
|
chartInstance = null;
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.aisbtk-page {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
|
|
|
|
.search-form {
|
|
|
|
|
|
padding: 12px 0;
|
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.aisbtk-tabs {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-tabs-content) {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-tabs-tabpane) {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.view-switch-buttons {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
top: -60px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
|
|
|
|
|
|
.view-btn {
|
|
|
|
|
|
width: 30px;
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
|
border: 1px solid #ccdae7;
|
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #2f6b98;
|
|
|
|
|
|
color: #2f6b98;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
background: #2f6b98;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
border-color: #2f6b98;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.view-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-table-layout {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
.chart-section {
|
|
|
|
|
|
width: 35%;
|
|
|
|
|
|
min-width: 300px;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
.chart-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 480px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-empty-overlay {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.8);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.table-section {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.video-section {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.video-mode {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 520px;
|
|
|
|
|
|
|
|
|
|
|
|
.video-main {
|
|
|
|
|
|
flex: 0 0 80%;
|
|
|
|
|
|
background: #000;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
.media-display {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-empty {
|
|
|
|
|
|
display: flex;
|
2026-07-06 08:54:58 +08:00
|
|
|
|
flex-direction: column;
|
2026-07-01 18:23:41 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.video-sidebar {
|
|
|
|
|
|
flex: 0 0 20%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
border: 1px solid #e8e8e8;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
.sidebar-list {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
|
|
|
|
|
|
.sidebar-item {
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
|
transition: background 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
background: #e6f7ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-thumb {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 110px;
|
|
|
|
|
|
background: #1a1a1a;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
|
|
|
|
img {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.thumb-placeholder {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
|
|
|
|
|
|
.no-media {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-time {
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sidebar-empty {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2026-07-06 08:54:58 +08:00
|
|
|
|
flex-direction: column;
|
2026-07-01 18:23:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sidebar-pagination {
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
border-top: 1px solid #e8e8e8;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-06 08:54:58 +08:00
|
|
|
|
|
|
|
|
|
|
.calendar-mode {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
height:60vh;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
padding-top:200px;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-calendar) {
|
|
|
|
|
|
width: 90%;
|
|
|
|
|
|
max-width: 1200px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead th) {
|
|
|
|
|
|
font-size: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead th)::before {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
padding-right: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead tr th:nth-child(1))::before {
|
|
|
|
|
|
content: '周一';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead tr th:nth-child(2))::before {
|
|
|
|
|
|
content: '周二';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead tr th:nth-child(3))::before {
|
|
|
|
|
|
content: '周三';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead tr th:nth-child(4))::before {
|
|
|
|
|
|
content: '周四';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead tr th:nth-child(5))::before {
|
|
|
|
|
|
content: '周五';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead tr th:nth-child(6))::before {
|
|
|
|
|
|
content: '周六';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-content thead tr th:nth-child(7))::before {
|
|
|
|
|
|
content: '周日';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-picker-calendar) {
|
|
|
|
|
|
.ant-picker-calendar-date-value {
|
|
|
|
|
|
font-size: 18px !important;
|
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ant-picker-calendar-date {
|
|
|
|
|
|
border: 1px solid rgb(213, 225, 233) !important;
|
|
|
|
|
|
margin: 0px !important;
|
|
|
|
|
|
// height: 75px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ant-picker-panel {
|
|
|
|
|
|
.ant-picker-content {
|
|
|
|
|
|
thead {
|
|
|
|
|
|
th {
|
|
|
|
|
|
padding: 6px 0;
|
|
|
|
|
|
border: 1px solid rgb(213, 225, 233) !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
|
|
|
|
|
|
.ant-picker-calendar-date {
|
|
|
|
|
|
padding: 3px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.ant-picker-cell-selected .ant-picker-calendar-date {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.ant-picker-cell-today .ant-picker-calendar-date::before {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.calendar-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 1px;
|
|
|
|
|
|
padding: 2px;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
|
|
|
|
|
|
.content_state1 {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background-color: #67c23a;
|
|
|
|
|
|
color: #000;
|
|
|
|
|
|
padding: 2px 0;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
cursor: auto;
|
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content_state4 {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background-color: #e6a23c;
|
|
|
|
|
|
padding: 2px 0;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
cursor: auto;
|
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content_state2 {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background-color: #e1f3d8;
|
|
|
|
|
|
padding: 2px 0;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content_state3 {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background-color: #faecd8;
|
|
|
|
|
|
padding: 2px 0;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content_state5 {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background-color: #faecd8;
|
|
|
|
|
|
padding: 2px 0;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-empty {
|
|
|
|
|
|
height: 600px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
2026-07-01 18:23:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|