diff --git a/frontend/.env.development b/frontend/.env.development index 40dff0cf..b9d4b82d 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -7,7 +7,7 @@ VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台' VITE_APP_PORT = 3000 VITE_APP_BASE_API = '/dev-api' # 本地环境 -# VITE_APP_BASE_URL = 'http://10.84.121.122:8093' +# VITE_APP_BASE_URL = 'http://172.16.54.1:8093' # 测试环境 # VITE_APP_BASE_URL = 'http://172.16.21.142:8093' # VITE_APP_BASE_URL = 'http://172.16.21.142:8096' diff --git a/frontend/src/api/home/index.ts b/frontend/src/api/home/index.ts index 6c3732c4..6809e3f2 100644 --- a/frontend/src/api/home/index.ts +++ b/frontend/src/api/home/index.ts @@ -1,9 +1,8 @@ import request from '@/utils/request'; -// import { SERVICE_URLS } from '../config'; // 引入配置 // 基本情况介绍 export function getBaseWbsb(data: any) { return request({ - url: '/api/dec-lygk-base-server/base/wbsb/GetKendoList', + url: '/eq/wbsb/GetKendoList', method: 'post', data: data }); @@ -12,7 +11,16 @@ export function getBaseWbsb(data: any) { // 水电开发及环境监测 - 获取列表数据 export function getVmsstbprptKendoList(data: any) { return request({ - url: '/api/dec-lygk-base-server/base/vmsstbprpt/GetKendoList', + url: '/eq/vmsstbprpt/GetKendoList', + method: 'post', + data: data + }); +} + +// 水电开发及环境监测 - 获取列表数据(全基地模式) +export function getVmsstbprptKendoListCust(data: any) { + return request({ + url: '/eq/vmsstbprpt/GetKendoList', method: 'post', data: data }); @@ -21,7 +29,7 @@ export function getVmsstbprptKendoList(data: any) { // 环保设施建设情况 export function environmentalProtectionConstruction(data: any) { return request({ - url: '/api/wmp-env-server/env/operat/GetKendoListCust', + url: '/eng/base/operat/GetKendoListCust', method: 'post', data: data }); @@ -30,7 +38,7 @@ export function environmentalProtectionConstruction(data: any) { // 环保设施弹框表格 export function envOpdGetKendoListCust(data: any) { return request({ - url: '/api/wmp-env-server/env/oped/GetKendoListCust', + url: '/overview/oped/GetKendoListCust', method: 'post', data: data }); @@ -39,7 +47,7 @@ export function envOpdGetKendoListCust(data: any) { // 获取基地列表 export function getBaseList(data: any) { return request({ - url: '/api/dec-lygk-base-server/base/wbsb/GetKendoList', + url: '/eq/wbsb/GetKendoList', method: 'post', data: data }); @@ -48,7 +56,7 @@ export function getBaseList(data: any) { // 获取设施介绍列表 export function getMsstbprptKendoList(data: any) { return request({ - url: '/api/dec-lygk-base-server/base/msstbprpt/GetKendoList', + url: '/overview/msstbprpt/GetKendoList', method: 'post', data: data }); @@ -57,7 +65,7 @@ export function getMsstbprptKendoList(data: any) { // 环保自动监测工作开展情况 export function baseEvnmAutoMonitorGetKendoListCust(data: any) { return request({ - url: '/api/dec-lygk-base-server/base/evnmAutoMonitor/GetKendoListCust', + url: '/wte/base/evnmAutoMonitor/GetKendoListCust', method: 'post', data: data }); @@ -66,7 +74,7 @@ export function baseEvnmAutoMonitorGetKendoListCust(data: any) { // 环保自动监测弹窗列表 export function vmsstbprptGetKendoList(data: any) { return request({ - url: '/api/dec-lygk-base-server/base/vmsstbprpt/GetKendoList', + url: '/eq/vmsstbprpt/GetKendoList', method: 'post', data: data }); @@ -75,7 +83,7 @@ export function vmsstbprptGetKendoList(data: any) { // 获取基本信息 export function baseMsstbprptGetKendoList(data: any) { return request({ - url: '/api/dec-lygk-base-server/base/msstbprpt/GetKendoList', + url: '/overview/msstbprpt/GetKendoList', method: 'post', data: data }); @@ -106,4 +114,4 @@ export function getBaseStbprpDataWNDR(data: any) { method: 'post', data: data }); -} +} \ No newline at end of file diff --git a/frontend/src/api/mapModal/index.ts b/frontend/src/api/mapModal/index.ts index 8be8ff6c..3b38499a 100644 --- a/frontend/src/api/mapModal/index.ts +++ b/frontend/src/api/mapModal/index.ts @@ -28,7 +28,7 @@ export function postIdUrl(data: any) { method: 'post', data, headers: { - 'Content-Type': 'multipart/form-data' + 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' } }); } diff --git a/frontend/src/api/zngj/index.ts b/frontend/src/api/zngj/index.ts index c7b1bc6b..986deeec 100644 --- a/frontend/src/api/zngj/index.ts +++ b/frontend/src/api/zngj/index.ts @@ -3,13 +3,13 @@ import request from '@/utils/request'; // AI识别大坝环境-获取统计数据(Tab数量) export function getAisbdbyxList(data: any) { return request({ - url: '/api/wmp-env-server/env/ai/com/qgcDmRun/GetKendoListCust', + url: '/warn/ai/com/qgcDmRun/GetKendoListCust', method: 'post', data }); } - -// AI识别大坝环境-获取饼图+表格数据 + +// AI识别大坝环境-获取饼图+表格数据 /api/wmp-env-server/env/ai/com/GetKendoListCust export function getAiChartTable(data: any) { return request({ url: '/api/wmp-env-server/env/ai/com/GetKendoListCust', @@ -21,7 +21,7 @@ export function getAiChartTable(data: any) { // AI识别大坝环境-获取电站列表 export function getAiRstcd(data: any) { return request({ - url: '/api/wmp-env-server/env/ai/com/qgc/aiRstcd/GetKendoListCust', + url: '/warn/ai/com/qgc/aiRstcd/GetKendoListCust', method: 'post', data }); @@ -30,7 +30,7 @@ export function getAiRstcd(data: any) { // AI识别过鱼设施运行-获取统计数据 export function getAiGyssList(data: any) { return request({ - url: '/api/wmp-env-server/env/ai/com/qgcProtect/GetKendoListCust', + url: '/warn/ai/com/qgcProtect/GetKendoListCust', method: 'post', data }); @@ -39,7 +39,7 @@ export function getAiGyssList(data: any) { // 生态流量低于限值告警-获取统计数据 export function getStllGaojingList(data: any) { return request({ - url: '/api/wmp-env-server/env/warn/stcd/eng/GetKendoListCust', + url: '/warn/stcd/eng/GetKendoListCust', method: 'post', data }); @@ -48,7 +48,7 @@ export function getStllGaojingList(data: any) { // 生态流量低于限值告警-获取表格详情数据 export function getStllGaojingDetail(data: any) { return request({ - url: '/api/wmp-env-server/env/warn/stcd/GetKendoListCust', + url: '/warn/stcd/GetKendoListCust', method: 'post', data }); @@ -57,7 +57,7 @@ export function getStllGaojingDetail(data: any) { // 水质监测数据告警-获取统计数据 export function getShuizhiGaojingList(data: any) { return request({ - url: '/api/dec-lygk-base-server/base/msstbprpt/GetKendoList', + url: '/wq/msstbprpt/GetKendoList', method: 'post', data }); @@ -66,7 +66,7 @@ export function getShuizhiGaojingList(data: any) { // AI识别大坝环境-获取漂浮物/清漂日历数据 export function getAiPfwRecord(data: any) { return request({ - url: '/api/wmp-env-server/env/ai/com/qgc/aiPfwRecord/GetKendoListCust', + url: '/warn/ai/com/qgc/aiPfwRecord/GetKendoListCust', method: 'post', data }); @@ -75,13 +75,13 @@ export function getAiPfwRecord(data: any) { // AI识别大坝环境-获取叠梁门日历数据 export function getAiRecord(data: any) { return request({ - url: '/api/wmp-env-server/env/ai/com/qgc/aiRecord/GetKendoListCust', + url: '/warn/ai/com/qgc/aiRecord/GetKendoListCust', method: 'post', data }); } -// AI识别大坝环境-获取日历详情视频数据 +// AI识别大坝环境-获取日历详情视频数据 /warn/ai/com/qgc/aiFile/GetKendoListCust export function getAiFile(data: any) { return request({ url: '/api/wmp-env-server/env/ai/com/qgc/aiFile/GetKendoListCust', diff --git a/frontend/src/components/developStatusChart/index.vue b/frontend/src/components/developStatusChart/index.vue index 235a8a25..c9bb000d 100644 --- a/frontend/src/components/developStatusChart/index.vue +++ b/frontend/src/components/developStatusChart/index.vue @@ -2,6 +2,23 @@ @@ -10,21 +27,31 @@ import { ref, onMounted, onUnmounted, watch } from 'vue'; import * as echarts from 'echarts'; import { getVmsstbprptKendoList } from '@/api/home'; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent'; +import ShuiDianKaiFQKTwoLayer from '@/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue'; defineOptions({ name: 'developStatusChart' }); -const emit = defineEmits<{ - (e: 'chart-click', params: any): void; -}>(); - const JidiSelectEventStore = useJidiSelectEventStore(); const baseid = ref(''); const chartContainer = ref(null); let chartInstance: echarts.ECharts | null = null; const dataLoading = ref(false); +const modalVisible = ref(false); +const modalProps = ref({}); + +function openModal(props: any) { + modalProps.value = props; + modalVisible.value = true; +} + +defineExpose({ + openModal, + dataLoading +}); + onUnmounted(() => { if (chartInstance) { chartInstance.dispose(); @@ -75,7 +102,6 @@ const getData = async () => { const res1Data = res1?.data?.data || []; const res2Data = res2?.data?.data || []; - // debugger if (res1Data.length > 0 || res2Data.length > 0) { const ttpwrlist = res1Data.map((item: any) => ({ @@ -234,12 +260,16 @@ const initChart = () => { chartInstance.setOption(option); - // 添加点击事件 chartInstance.on('click', (params: any) => { - emit('chart-click', { + const currentJidiInfo = JidiSelectEventStore.selectedItem; + openModal({ + defaultJidiInfo: { + baseid: currentJidiInfo?.wbsCode || 'all', + basename: currentJidiInfo?.wbsName || '全部基地' + }, seriesName: params.seriesName, - name: params.name, - dataIndex: params.dataIndex + stateName: params.name, + defaultTab: params.name }); }); diff --git a/frontend/src/components/engEnvironmentData/index.vue b/frontend/src/components/engEnvironmentData/index.vue index 77bc1cd0..b368b924 100644 --- a/frontend/src/components/engEnvironmentData/index.vue +++ b/frontend/src/components/engEnvironmentData/index.vue @@ -38,6 +38,20 @@ + + + + @@ -45,20 +59,30 @@ import { ref, watch } from 'vue'; import { getVmsstbprptKendoList } from '@/api/home'; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent'; +import HuanJingSJJRQK from '@/modules/shuidianhuangjingjieruMod/TwoLayer/HuanJingSJJRQK.vue'; defineOptions({ name: 'EngEnvironmentData' }); -const emit = defineEmits<{ - (e: 'item-click', params: any): void; -}>(); - const JidiSelectEventStore = useJidiSelectEventStore(); const baseid = ref(''); const dataLoading = ref(false); const data = ref>({ one: 0, two: 0, three: 0 }); +const modalVisible = ref(false); +const modalProps = ref({}); + +function openModal(props: any) { + modalProps.value = props; + modalVisible.value = true; +} + +defineExpose({ + openModal, + dataLoading +}); + const getData = async () => { if (!baseid.value) return; @@ -77,7 +101,8 @@ const getData = async () => { ...baseFilters, { field: 'prsc', operator: 'in', dataType: 'string', value: ['1', '2', '3'] } ] - } + }, + group: [{ dir: 'desc', field: 'sttpCode' }] }; const params2 = { @@ -88,7 +113,8 @@ const getData = async () => { { field: 'runState', operator: 'eq', dataType: 'number', value: 4 }, { field: 'dtin', operator: 'eq', dataType: 'number', value: 1 } ] - } + }, + group: [{ dir: 'desc', field: 'sttpCode' }] }; const params3 = { @@ -99,7 +125,8 @@ const getData = async () => { { field: 'prsc', operator: 'in', dataType: 'string', value: ['1', '2', '3'] }, { field: 'dtinEnv', operator: 'in', dataType: 'string', value: ['2'] } ] - } + }, + group: [{ dir: 'desc', field: 'dtinEnv' }] }; try { @@ -113,21 +140,22 @@ const getData = async () => { const record = { one: 0, two: 0, three: 0 }; const responses = [res1, res2, res3]; responses.forEach((res, index) => { - const total = res?.data?.total ?? 0; - switch (index) { - case 0: - record.one = total; - break; - case 1: - record.two = total; - break; - case 2: - record.three = total; - break; + if (res?.data?.data && Array.isArray(res.data.data) && res.data.data.length > 0) { + const count = res.data.data[0].count ?? 0; + switch (index) { + case 0: + record.one = count; + break; + case 1: + record.two = count; + break; + case 2: + record.three = count; + break; + } } }); data.value = record; - // debugger } catch (error) { console.error('EngEnvironmentData getData error:', error); } finally { @@ -136,7 +164,11 @@ const getData = async () => { }; const handleItemClick = (name: string) => { - emit('item-click', { name }); + const currentJidiInfo = JidiSelectEventStore.selectedItem; + openModal({ + dataDimensionVal: currentJidiInfo?.wbsCode || 'all', + name + }); }; watch( diff --git a/frontend/src/modules/huanbaozdjcgzkzQK/huanbaozidongjianceEJ/index.vue b/frontend/src/modules/huanbaozdjcgzkzQK/huanbaozidongjianceEJ/index.vue index 706daa1b..d48f56b8 100644 --- a/frontend/src/modules/huanbaozdjcgzkzQK/huanbaozidongjianceEJ/index.vue +++ b/frontend/src/modules/huanbaozdjcgzkzQK/huanbaozidongjianceEJ/index.vue @@ -21,6 +21,14 @@ :options="jiDiList" @change="handleBaseChange" /> + - - diff --git a/frontend/src/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue b/frontend/src/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue index b432ef68..c19013f5 100644 --- a/frontend/src/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue +++ b/frontend/src/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue @@ -1,652 +1,379 @@ @@ -1313,15 +1036,15 @@ onUnmounted(() => { flex: 1; display: flex; flex-direction: column; - + :deep(.ant-tabs-content) { height: 100%; } - + :deep(.ant-tabs-tabpane) { height: 100%; } - + :deep(.ant-tabs-nav) { margin-bottom: 16px; } @@ -1332,23 +1055,23 @@ onUnmounted(() => { gap: 16px; height: calc(100% - 40px); min-height: 450px; - + .chart-section { flex: 0 0 541px; - background: #fafafa; + // background: #fafafa; border-radius: 4px; padding: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + // box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); display: flex; align-items: center; justify-content: center; - + .pie-chart { width: 541px; - height: 412px; + height: 482px; } } - + .table-section { flex: 1; overflow: hidden; @@ -1356,12 +1079,11 @@ onUnmounted(() => { border-radius: 4px; } } +.text-link { + color: #2f6b98; -.modal-content { - padding: 16px; - - :deep(.ant-descriptions) { - margin-bottom: 16px; - } + &:hover { + color: #40a9ff; + } } - \ No newline at end of file + diff --git a/frontend/src/modules/shuidianhuangjingjieruMod/index.vue b/frontend/src/modules/shuidianhuangjingjieruMod/index.vue index 834cb076..e35ccd73 100644 --- a/frontend/src/modules/shuidianhuangjingjieruMod/index.vue +++ b/frontend/src/modules/shuidianhuangjingjieruMod/index.vue @@ -1,58 +1,24 @@ - @@ -62,36 +28,21 @@ import SidePanelItem from '@/components/SidePanelItem/index.vue'; import DevelopStatusChart from "@/components/developStatusChart/index.vue" import EngEnvironmentData from "@/components/engEnvironmentData/index.vue" import DataTable from "./DataTable.vue" -import ShuiDianKaiFQKTwoLayer from "./TwoLayer/ShuiDianKaiFQKTwoLayer.vue" -import HuanJingSJJRQK from "./TwoLayer/HuanJingSJJRQK.vue" import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent'; +import { Spin } from 'ant-design-vue'; -// 定义组件名(便于调试和递归) defineOptions({ name: 'shuidianhuangjingjieruMod' }); const JidiSelectEventStore = useJidiSelectEventStore(); -const prompt = ref({ - show: true, - value: '统计大中型已建,在建水电站', -}) +const developStatusChartRef = ref | null>(null); +const engEnvironmentDataRef = ref | null>(null); -// 水电开发情况弹窗状态 -const shuidianModalVisible = ref(false); -const shuidianModalTitle = ref('水电开发情况'); -const shuidianModalProps = ref({}); - -// 环境监测数据接入情况弹窗状态 -const huanjingModalVisible = ref(false); -const huanjingModalTitle = ref('环境监测数据接入情况'); -const huanjingModalProps = ref({}); - -// DataTable 行点击事件 const handleRowClick = (record: any) => { const currentJidiInfo = JidiSelectEventStore.selectedItem; - shuidianModalProps.value = { + developStatusChartRef.value?.openModal({ defaultJidiInfo: { baseid: currentJidiInfo?.wbsCode || 'all', basename: currentJidiInfo?.wbsName || '全部基地' @@ -99,41 +50,10 @@ const handleRowClick = (record: any) => { seriesName: currentJidiInfo?.wbsCode === 'other' ? '数量(座)' : '装机容量(万kW)', stateName: record.bldsttCcode === '0' ? '未建' : record.bldsttCcode === '1' ? '在建' : '已建', defaultTab: record.bldsttCcode === '0' ? '未建' : record.bldsttCcode === '1' ? '在建' : '已建' - }; - shuidianModalTitle.value = '水电开发情况'; - shuidianModalVisible.value = true; + }); }; -// DevelopStatusChart 饼图点击事件 -const handleChartClick = (params: any) => { - const currentJidiInfo = JidiSelectEventStore.selectedItem; - shuidianModalProps.value = { - defaultJidiInfo: { - baseid: currentJidiInfo?.wbsCode || 'all', - basename: currentJidiInfo?.wbsName || '全部基地' - }, - seriesName: params.seriesName, - stateName: params.name, - defaultTab: params.name - }; - shuidianModalTitle.value = '水电开发情况'; - shuidianModalVisible.value = true; -}; - -// EngEnvironmentData 指标点击事件 -const handleEnvItemClick = (params: any) => { - const currentJidiInfo = JidiSelectEventStore.selectedItem; - huanjingModalProps.value = { - dataDimensionVal: currentJidiInfo?.wbsCode || 'all', - name: params.name - }; - huanjingModalTitle.value = '环境监测数据接入情况'; - huanjingModalVisible.value = true; -}; - -// 页面加载时执行的逻辑 onMounted(() => { - }); @@ -142,4 +62,4 @@ onMounted(() => { display: flex; justify-content: space-between; } - \ No newline at end of file + diff --git a/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/AISBTK.vue b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/AISBTK.vue index d5024398..78d074ea 100644 --- a/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/AISBTK.vue +++ b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/AISBTK.vue @@ -11,7 +11,7 @@ @change="handleBaseChange" /> - + + + + +
- -
+ +
无媒体
-
{{ item.ennm }}{{ item.tm }}
+
{{ item.stName }}{{ item.tm }}
- +
- + + @@ -267,56 +333,28 @@ width="80vw" :footer="null" destroyOnClose + :teleport="false" :bodyStyle="{ height: '70vh' }" > - -
-
-
-
- -
-
- -
-
- -
-
-
- - - -
-
+ +
+
@@ -345,7 +383,15 @@ 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'; -import { getAisbdbyxList, getAiChartTable, getAiPfwRecord, getAiRecord, getAiFile } from '@/api/zngj'; +import VideoPlayer from './components/VideoPlayer.vue'; +import { + getAisbdbyxList, + getAiChartTable, + getAiPfwRecord, + getAiRecord, + getAiFile, + getAiRstcd +} from '@/api/zngj'; import { postIdUrl } from '@/api/mapModal'; import dayjs from 'dayjs'; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent'; @@ -368,8 +414,10 @@ const jidiOptions = ref>([]); const searchData = ref({ baseid: props.baseid || 'all', stnm: '', + rstcd: '', month: props.month ? dayjs(props.month) : dayjs() }); +const stcdData = ref>([]); // ==================== 弹框状态 ==================== const attachmentModalVisible = ref(false); @@ -431,7 +479,10 @@ const detailVideoTake = ref(20); const detailVideoPageSizeOptions = ['20', '50', '100']; const detailVideoTotal = ref(0); const detailActiveFid = ref(''); -const detailActiveMedia = ref<{ type: string; src: string }>({ type: '', src: '' }); +const detailActiveMedia = ref<{ type: string; src: string }>({ + type: '', + src: '' +}); const detailTabItems = ref([]); const detailDate = ref(''); const detailType = ref(''); @@ -439,15 +490,53 @@ const detailDataType = ref(''); const qingpiaoModalVisible = ref(false); const qingpiaoFids = ref([]); -// 日历状态映射 -const calendarStatusMap: Record = { - '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' }, - '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' } +// 日历状态映射(与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' + } }; // 函数式 ref 绑定 @@ -714,12 +803,30 @@ const tableSearchParams = computed(() => ({ })); // ==================== 数据转换 ==================== +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; +}; + const customTransform = (res: any) => { - const dataObj = res?.data; - const records = Array.isArray(dataObj) ? dataObj : dataObj?.data || []; return { - records, - total: dataObj?.total || 0 + records: res?.data?.data || [], + total: res?.data?.total || 0 }; }; @@ -802,27 +909,63 @@ const getChartData = async () => { const params = { filter, group, - groupResultFlat: true + sort: [ + { + field: 'rstcdStepSort', + dir: 'asc' + }, + { + field: 'tm', + dir: 'desc' + } + ] }; const res = await getAiChartTable(params); const dataObj = res?.data?.data ?? {}; const rawData = Array.isArray(dataObj) ? dataObj : dataObj?.data || []; - const list = rawData.map((el: any) => { - if (searchData.value.baseid === 'all') { - return { - name: el?.baseName_value || el?.baseName || '-', - value: el?.count_baseName || el?.count_baseId || 0, - key: el?.baseId_value || el?.baseId - }; - } - return { - name: el?.ennm_value || el?.ennm || '-', - value: el?.count_rstcd || el?.count_ennm || 0, - key: el?.rstcd || el?.rstcd_value - }; - }); + 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 || '' + }); + }); + } chartData.value = list; } catch (error) { @@ -1103,64 +1246,74 @@ const fetchVideoList = async () => { }; const res = await getAiChartTable(params); - const dataObj = res?.data?.data ?? {}; - const rawData = dataObj?.data || []; - videoTotal.value = dataObj?.total || 0; + 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(); // 收集所有 fid - const allFids: string[] = []; - rawData.forEach((item: any) => { - if (item.fid) { - item.fid.split(',').forEach((f: string) => { - const trimmed = f.trim(); - if (trimmed) allFids.push(trimmed); - }); - } - }); + const allFids = expandedData + .filter((item: any) => item.fid) + .map((item: any) => item.fid) + .join(','); // 调用 postIdUrl 获取媒体详情 let mediaMap: Record = {}; - if (allFids.length > 0) { + if (allFids) { const formData = new FormData(); - formData.append('id', allFids.join(',')); + formData.append('id', allFids); const mediaRes: any = await postIdUrl(formData); mediaMap = mediaRes?.data || {}; } - // 按 fid 展开为独立项 - const flatList: any[] = []; - rawData.forEach((item: any) => { - const fids = item.fid - ? item.fid - .split(',') - .map((f: string) => f.trim()) - .filter(Boolean) - : []; - const tm = item.tm ? dayjs(item.tm).format('YYYY-MM-DD HH:mm:ss') : '-'; + // 构建列表(与 React 一致) + const list: any[] = []; + expandedData.forEach((vd: any) => { + const fileInfo = mediaMap[vd.fid]; + const hasValidExtension = + fileInfo && ['.jpg', '.png'].includes(fileInfo.ext); - fids.forEach((fid: string) => { - const m = mediaMap[fid]; - const ext = m?.ext || ''; - const isVideo = ext === '.mp4'; - const isImage = ext === '.jpg' || ext === '.png'; + let flpth = ''; + let imgPath = ''; + let type = ''; - flatList.push({ - ...item, - fid, - media: m || null, - thumbUrl: isImage ? (baseUrl + '?' + fid + '&view=jpg') : '', - isVideo, - isImage, - tm - }); + 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 }); }); - videoList.value = flatList; - + // 过滤掉无效项(与 React 一致) + const listMap = list.filter( + item => item.flpth !== '' || item.imgPath !== '' + ); + videoList.value = listMap; + // debugger // 默认选中第一条 - if (videoList.value.length > 0 && !activeFid.value) { - handleSelectMedia(videoList.value[0]); + if (listMap.length > 0 && !activeFid.value) { + handleSelectMedia(listMap[0]); } } catch (error) { console.error('获取视频列表失败:', error); @@ -1170,15 +1323,20 @@ const fetchVideoList = async () => { } }; -// 选择媒体 +// 选择媒体(与 React 一致) const handleSelectMedia = (item: any) => { activeFid.value = item.fid; - const { media, isVideo, isImage } = item; - if (isImage) { - activeMedia.value = { type: 'image', src: baseUrl + '?' + item.fid + '&view=jpg' }; - } else if (isVideo) { - activeMedia.value = { type: 'video', src: baseUrl + '?' + item.fid + '&view=jpg' }; + 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' + }; } else { activeMedia.value = { type: '', src: '' }; } @@ -1191,58 +1349,154 @@ const handleVideoPageChange = () => { fetchVideoList(); }; -// ==================== 日历视图函数 ==================== -// 判断是否是当前月份 -const isCurrentMonth = (current: any): boolean => { - const selectedDate = dayjs(searchData.value.month); - return current.month() === selectedDate.month() && current.year() === selectedDate.year(); -}; - -// 获取某天的日历数据 -const getCalendarDataForDay = (current: any): any => { - if (!isCurrentMonth(current)) return null; - const day = current.format('DD'); - return calendarData.value.find((item: any) => dayjs(item.dt).format('DD') === day); -}; - -// 获取状态样式类 -const getStatusClass = (status: string) => { - return calendarStatusMap[status]?.className || ''; -}; - -// 获取状态文本 -const getStatusText = (status: string) => { - return calendarStatusMap[status]?.name || ''; -}; - -// 获取日历数据 -const fetchCalendarData = async () => { - calendarLoading.value = true; +// ==================== 获取电站列表(用于日历视图的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, + 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') + 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') + 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); + const _data = res?.data?.data?.data ?? []; + 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); + } +}; + +// ==================== 日历视图函数 ==================== +// 判断是否是当前月份 +const isCurrentMonth = (current: any): boolean => { + return ( + current.month() === calendarValue.value.month() && + current.year() === calendarValue.value.year() + ); +}; + +// 获取某天的日历数据 +const getCalendarDataForDay = (current: any): any => { + return calendarData.value.find( + (item: any) => dayjs(item.dt).format('DD') === current.format('DD') + ); +}; + +// 获取状态样式类(支持数字和字符串类型) +const getStatusClass = (status: number | string) => { + const statusKey = String(status); + return calendarStatusMap[statusKey]?.className || ''; +}; + +// 获取状态文本(支持数字和字符串类型) +const getStatusText = (status: number | string) => { + const statusKey = String(status); + return calendarStatusMap[statusKey]?.name || ''; +}; + +// 获取日历数据(参考React端逻辑) +const fetchCalendarData = async () => { + calendarLoading.value = true; + try { + const type = getType(); + const filters: any[] = [ + // 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过滤 + { field: 'type', operator: 'eq', value: type }, + // 时间范围 + { + 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') + }, + // 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 + ].filter(Boolean); + + const params = { + filter: { logic: 'and', filters }, + sort: tabIndex.value === '1' ? [] : [{ field: 'tm', dir: 'desc' }] + }; + + // 根据类型调用不同接口(与React端一致:AI_5005用getAiRecord,其余用getAiPfwRecord) const apiFn = type === 'AI_5005' ? getAiRecord : getAiPfwRecord; const res: any = await apiFn(params); - calendarData.value = res?.data?.data || []; + // 接口返回结构: { code, msg, data: [...] } - data 直接是数组,不是 res.data.data + const payload = res?.data || {}; + calendarData.value = Array.isArray(payload) ? payload : payload.data || []; } catch (error) { console.error('获取日历数据失败:', error); calendarData.value = []; @@ -1254,9 +1508,15 @@ const fetchCalendarData = async () => { // 日历面板切换 const handleCalendarPanelChange = (date: any) => { calendarValue.value = date; + fetchCalendarData(); }; -// 点击日历日期 +// 日历日期选择 +const handleCalendarSelect = (date: any) => { + calendarValue.value = date; +}; + +// 点击日历日期(参考React端逻辑) const handleCalendarDayClick = async (current: any, dayData: any) => { const date = dayjs(current).format('YYYY-MM-DD'); const status = dayData.status; @@ -1270,7 +1530,10 @@ const handleCalendarDayClick = async (current: any, dayData: any) => { if (!fidStr) { return; } - qingpiaoFids.value = fidStr.split(',').map((s: string) => s.trim()).filter(Boolean); + qingpiaoFids.value = fidStr + .split(',') + .map((s: string) => s.trim()) + .filter(Boolean); qingpiaoModalVisible.value = true; return; } @@ -1280,18 +1543,20 @@ const handleCalendarDayClick = async (current: any, dayData: any) => { detailType.value = statusInfo.type; detailDataType.value = statusInfo.dataType; - // 设置 Tab 项 - if (tabIndex.value === '1') { + // 设置 Tab 项(与React端一致) + if (tabIndex.value === '2') { + // 叠梁门运行识别:运行 / 未运行 + detailTabItems.value = [ + { key: '2', label: '运行' }, + { key: '1', label: '未运行' } + ]; + } else { + // 漂浮物识别:无漂浮物 / 小面积漂浮物 / 大面积漂浮物 detailTabItems.value = [ { key: '3', label: '无漂浮物' }, { key: '4', label: '小面积漂浮物' }, { key: '5', label: '大面积漂浮物' } ]; - } else if (tabIndex.value === '2') { - detailTabItems.value = [ - { key: '2', label: '运行' }, - { key: '1', label: '未运行' } - ]; } detailActiveKey.value = statusInfo.dataType; @@ -1306,85 +1571,163 @@ const fetchDetailVideoList = async () => { detailVideoLoading.value = true; try { const filters: any[] = [ + // 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过滤 { field: 'type', operator: 'eq', value: detailType.value }, - searchData.value.baseid !== 'all' ? { field: 'baseId', operator: 'eq', value: searchData.value.baseid } : null, + // 时间范围 { field: 'tm', operator: 'gte', dataType: 'date', - value: dayjs(detailDate.value).startOf('day').format('YYYY-MM-DD 00:00:00') + value: dayjs(detailDate.value) + .startOf('day') + .format('YYYY-MM-DD 00:00:00') }, { field: 'tm', operator: 'lte', dataType: 'date', - value: dayjs(detailDate.value).endOf('day').format('YYYY-MM-DD 23:59:59') + value: dayjs(detailDate.value) + .endOf('day') + .format('YYYY-MM-DD 23:59:59') }, - { field: 'dataType', operator: 'eq', value: detailDataType.value } + // 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 ].filter(Boolean); const params = { filter: { logic: 'and', filters }, + sort: tabIndex.value === '1' ? [] : [{ field: 'tm', dir: 'desc' }], skip: (detailVideoPage.value - 1) * detailVideoTake.value, take: detailVideoTake.value }; const res: any = await getAiFile(params); - const dataObj = res?.data?.data || {}; - const rawData = dataObj?.data || []; - detailVideoTotal.value = dataObj?.total || 0; + 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(); // 收集所有 fid - const allFids: string[] = []; - rawData.forEach((item: any) => { - if (item.fid) { - item.fid.split(',').forEach((f: string) => { - const trimmed = f.trim(); - if (trimmed) allFids.push(trimmed); - }); - } - }); + const allFids = expandedData + .filter((item: any) => item.fid) + .map((item: any) => item.fid) + .join(','); // 调用 postIdUrl 获取媒体详情 let mediaMap: Record = {}; - if (allFids.length > 0) { + if (allFids) { const formData = new FormData(); - formData.append('id', allFids.join(',')); - const mediaRes: any = await postIdUrl(formData); - mediaMap = mediaRes?.data || {}; + 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 = {}; + } } - // 按 fid 展开为独立项 - const flatList: any[] = []; - rawData.forEach((item: any) => { - const fids = item.fid - ? item.fid.split(',').map((f: string) => f.trim()).filter(Boolean) - : []; - const tm = item.tm ? dayjs(item.tm).format('YYYY-MM-DD HH:mm:ss') : '-'; + // 构建列表(与 React 一致) + const list: any[] = []; + expandedData.forEach((vd: any) => { + const fileInfo = mediaMap[vd.fid]; + const hasValidExtension = + fileInfo && ['.jpg', '.png'].includes(fileInfo.ext); - fids.forEach((fid: string) => { - const m = mediaMap[fid]; - const ext = m?.ext || ''; - const isVideo = ext === '.mp4'; - const isImage = ext === '.jpg' || ext === '.png'; + let flpth = ''; + let imgPath = ''; + let type = ''; - flatList.push({ - ...item, - fid, - media: m || null, - thumbUrl: isImage ? (baseUrl + '?' + fid + '&view=jpg') : '', - isVideo, - isImage, - tm - }); + 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 }); }); - detailVideoList.value = flatList; + console.log('构建的列表(过滤前):', list); + + // 过滤掉无效项(与 React 一致) + const listMap = list.filter( + item => item.flpth !== '' || item.imgPath !== '' + ); + console.log('过滤后的列表:', listMap); + detailVideoList.value = listMap; // 默认选中第一条 - if (detailVideoList.value.length > 0) { - handleSelectDetailMedia(detailVideoList.value[0]); + 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: '' }; + } } } catch (error) { console.error('获取详情视频列表失败:', error); @@ -1394,22 +1737,29 @@ const fetchDetailVideoList = async () => { } }; -// 选择详情媒体 +// 选择详情媒体(与 React 一致) const handleSelectDetailMedia = (item: any) => { detailActiveFid.value = item.fid; - const { isVideo, isImage } = item; - if (isImage) { - detailActiveMedia.value = { type: 'image', src: baseUrl + '?' + item.fid + '&view=jpg' }; - } else if (isVideo) { - detailActiveMedia.value = { type: 'video', src: baseUrl + '?' + item.fid + '&view=jpg' }; + 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' + }; } else { detailActiveMedia.value = { type: '', src: '' }; } }; // 详情分页切换 -const handleDetailVideoPageChange = () => { +const handleDetailVideoPageChange = (page: number, pageSize: number) => { + detailVideoPage.value = page; + detailVideoTake.value = pageSize; detailActiveFid.value = ''; detailActiveMedia.value = { type: '', src: '' }; fetchDetailVideoList(); @@ -1451,6 +1801,31 @@ watch( { deep: true, immediate: true } ); +// ==================== 监听视图切换 ==================== +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(); + } +); + // ==================== 监听 Tab 切换 ==================== watch(tabIndex, () => { pieCode.value = []; @@ -1479,6 +1854,14 @@ watch(tabIndex, () => { }); }); +// ==================== 监听详情弹窗 Tab 切换 ==================== +watch(detailActiveKey, () => { + if (detailModalVisible.value) { + detailVideoPage.value = 1; + fetchDetailVideoList(); + } +}); + // ==================== 窗口 resize ==================== const handleResize = () => { const dom = chartRefs.value.get(tabIndex.value); @@ -1644,6 +2027,7 @@ onBeforeUnmount(() => { .media-empty { display: flex; + flex-direction: column; align-items: center; justify-content: center; height: 100%; @@ -1724,6 +2108,7 @@ onBeforeUnmount(() => { display: flex; align-items: center; justify-content: center; + flex-direction: column; } .sidebar-pagination { @@ -1733,5 +2118,179 @@ onBeforeUnmount(() => { } } } + + .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; + } } diff --git a/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/components/VideoPlayer.vue b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/components/VideoPlayer.vue new file mode 100644 index 00000000..708cc412 --- /dev/null +++ b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/components/VideoPlayer.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/composables/usePieChart.ts b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/composables/usePieChart.ts new file mode 100644 index 00000000..c6f29e19 --- /dev/null +++ b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/composables/usePieChart.ts @@ -0,0 +1,119 @@ +import * as echarts from 'echarts'; +import type { ECharts, EChartsOption } from 'echarts'; + +export interface ChartDataItem { + name: string; + value: number; + key: string; +} + +export function usePieChart( + chartRefs: Map, + chartData: { value: ChartDataItem[] }, + tabIndex: { value: string }, + typeMap: Record, + legendCallback?: (e: any) => void +) { + let chartInstance: ECharts | null = null; + + const initChart = (retryCount = 0) => { + const dom = chartRefs.get(tabIndex.value); + if (!dom) { + if (retryCount < 10) { + setTimeout(() => initChart(retryCount + 1), 100); + } + return; + } + + const rect = dom.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) { + if (retryCount < 10) { + setTimeout(() => initChart(retryCount + 1), 100); + } + return; + } + + if (!chartInstance) { + chartInstance = echarts.init(dom); + } + + const option: 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: ChartDataItem) => acc + (item.value || 0), 0); + 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: ChartDataItem) => ({ + 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); + + if (legendCallback) { + chartInstance.off('legendselectchanged'); + chartInstance.on('legendselectchanged', legendCallback); + } + }; + + const dispose = () => { + chartInstance?.dispose(); + chartInstance = null; + }; + + const resize = () => { + chartInstance?.resize(); + }; + + return { initChart, dispose, resize }; +} diff --git a/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/composables/useVideoList.ts b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/composables/useVideoList.ts new file mode 100644 index 00000000..3fd42b36 --- /dev/null +++ b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/composables/useVideoList.ts @@ -0,0 +1,131 @@ +import { ref } from 'vue'; +import { postIdUrl } from '@/api/mapModal'; +import dayjs from 'dayjs'; + +export interface VideoListItem { + fid: string; + media: any; + thumbUrl: string; + isVideo: boolean; + isImage: boolean; + tm: string; + ennm?: string; + [key: string]: any; +} + +export function useVideoList( + fetchFn: (params: any) => Promise, + baseUrl: string +) { + const page = ref(1); + const take = ref(20); + const total = ref(0); + const loading = ref(false); + const list = ref([]); + const activeFid = ref(''); + const activeMedia = ref<{ type: string; src: string }>({ type: '', src: '' }); + + const fetchList = async (filter: any, options: any = {}) => { + loading.value = true; + try { + const skip = (page.value - 1) * take.value; + const params = { filter, skip, take: take.value, ...options }; + const res = await fetchFn(params); + const dataObj = res?.data?.data ?? {}; + const rawData = dataObj?.data || []; + total.value = dataObj?.total || 0; + + const allFids: string[] = []; + rawData.forEach((item: any) => { + if (item.fid) { + item.fid.split(',').forEach((f: string) => { + const trimmed = f.trim(); + if (trimmed) allFids.push(trimmed); + }); + } + }); + + let mediaMap: Record = {}; + if (allFids.length > 0) { + const formData = new FormData(); + formData.append('id', allFids.join(',')); + const mediaRes: any = await postIdUrl(formData); + mediaMap = mediaRes?.data || {}; + } + + const flatList: VideoListItem[] = []; + rawData.forEach((item: any) => { + const fids = item.fid + ? item.fid.split(',').map((f: string) => f.trim()).filter(Boolean) + : []; + const tm = item.tm ? dayjs(item.tm).format('YYYY-MM-DD HH:mm:ss') : '-'; + + fids.forEach((fid: string) => { + const m = mediaMap[fid]; + const ext = m?.ext || ''; + const isVideo = ext === '.mp4'; + const isImage = ext === '.jpg' || ext === '.png'; + + flatList.push({ + ...item, + fid, + media: m || null, + thumbUrl: isImage ? baseUrl + '?' + fid + '&view=jpg' : '', + isVideo, + isImage, + tm + }); + }); + }); + + list.value = flatList; + + if (list.value.length > 0 && !activeFid.value) { + selectMedia(list.value[0]); + } + } catch (error) { + console.error('获取视频列表失败:', error); + list.value = []; + } finally { + loading.value = false; + } + }; + + const selectMedia = (item: VideoListItem) => { + activeFid.value = item.fid; + const { isVideo, isImage } = item; + + if (isImage) { + activeMedia.value = { type: 'image', src: baseUrl + '?' + item.fid + '&view=jpg' }; + } else if (isVideo) { + activeMedia.value = { type: 'video', src: baseUrl + '?' + item.fid + '&view=jpg' }; + } else { + activeMedia.value = { type: '', src: '' }; + } + }; + + const handlePageChange = () => { + activeFid.value = ''; + activeMedia.value = { type: '', src: '' }; + }; + + const reset = () => { + page.value = 1; + activeFid.value = ''; + activeMedia.value = { type: '', src: '' }; + }; + + return { + page, + take, + total, + loading, + list, + activeFid, + activeMedia, + fetchList, + selectMedia, + handlePageChange, + reset + }; +} diff --git a/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/types/index.ts b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/types/index.ts new file mode 100644 index 00000000..55970af8 --- /dev/null +++ b/frontend/src/modules/yunXingGaoJIng/Aisbdbyx/types/index.ts @@ -0,0 +1,60 @@ +export enum TabType { + PFW = '1', + DLM = '2', + QP = '3' +} + +export enum AiType { + PFW = 'AI_5001', + DLM = 'AI_5005', + QP = 'AI_5011' +} + +export interface ChartDataItem { + name: string; + value: number; + key: string; +} + +export interface VideoListItem { + fid: string; + media: any; + thumbUrl: string; + isVideo: boolean; + isImage: boolean; + tm: string; + ennm?: string; + [key: string]: any; +} + +export interface CalendarDayData { + dt: string; + status?: string; + qpStatus?: string; + fid?: string; +} + +export interface CalendarStatusInfo { + name: string; + className: string; + type: string; + dataType: string; +} + +export interface TabItem { + key: number; + value: string; + unit: string; + count: number; +} + +export interface SearchData { + baseid: string; + stnm: string; + month: any; +} + +export interface DetailTabItem { + key: string; + label: string; +} diff --git a/frontend/src/views/shiPinJianKong/components/LiveVideoBox.vue b/frontend/src/views/shiPinJianKong/components/LiveVideoBox.vue index 269ba916..167bcfe5 100644 --- a/frontend/src/views/shiPinJianKong/components/LiveVideoBox.vue +++ b/frontend/src/views/shiPinJianKong/components/LiveVideoBox.vue @@ -32,7 +32,7 @@