鱼类调查装置

This commit is contained in:
王兴凯 2026-06-18 09:30:48 +08:00
commit 5a0498af6b
36 changed files with 2127 additions and 583 deletions

View File

@ -0,0 +1,64 @@
import request from '@/utils/request';
//获取有数据的流域
export function wbsbGetKendoList(data: any) {
return request({
url: '/fpr/wbsb/GetKendoList',
method: 'post',
data
});
}
//获取鱼类调查装置
export function msstbprptGetKendoList(data: any) {
return request({
url: '/fpr/msstbprpt/GetKendoList',
method: 'post',
data
});
}
//获取鱼类介绍
export function fishDicGetKendoList(data: any) {
return request({
url: '/fpr/fishDic/GetKendoList',
method: 'post',
data
});
}
//获取鱼类调查统计数据
export function getFprdDataInfo(data: any) {
return request({
url: '/fpr/sdFprdR/getFprdDataInfo',
method: 'post',
data
});
}
//
export function sdFprdRGetKendoListCust(data: any) {
return request({
url: '/fpr/sdFprdR/GetKendoListCust',
method: 'post',
data
});
}
//装置名称 /api/dec-lygk-base-server/base/vmsstbprpt/GetKendoList
export function vmsstbprptGetKendoList(data: any) {
return request({
url: '/fpr/vmsstbprpt/GetKendoList',
method: 'post',
data
});
}
//鱼种类 /api/wmp-env-server/sdFprdR/getFishInfo
export function sdFprdRgetFishInfo() {
return request({
url: '/fpr/sdFprdR/getFishInfo',
method: 'post',
});
}
//获取鱼类变化信息
export function sdFprdRGetFtpChangeInfo(data: { baseId: string; year: string }) {
return request({
url: '/fpr/sdFprdR/getFtpChangeInfo',
method: 'post',
data
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -27,7 +27,12 @@
</div> </div>
<div v-else-if="selectedFile.fileType === 'video'" class="video-viewer"> <div v-else-if="selectedFile.fileType === 'video'" class="video-viewer">
<video :src="selectedFile.url" controls class="video-player"></video> <!-- <video :src="selectedFile.url" controls class="video-player"></video> -->
<LiveVideoBox
:videoData="{ url: selectedFile.url }"
height="100%"
:showTitle="false"
/>
</div> </div>
<div v-else-if="selectedFile.fileType === 'pdf'" class="pdf-viewer"> <div v-else-if="selectedFile.fileType === 'pdf'" class="pdf-viewer">
@ -52,7 +57,7 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { getIdUrl } from '@/api/mapModal'; import { getIdUrl } from '@/api/mapModal';
import PdfView from '@/components/pdf/pdfView.vue'; import PdfView from '@/components/pdf/pdfView.vue';
import LiveVideoBox from '@/views/shiPinJianKong/components/LiveVideoBox.vue';
interface AttachmentFileItem { interface AttachmentFileItem {
id: string; id: string;
name: string; name: string;

View File

@ -23,7 +23,7 @@
v-model:value="dateRange" v-model:value="dateRange"
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
style="width: 100%" style="width: 300px"
@change="handleDateChange" @change="handleDateChange"
/> />
<a-button type="primary" class="ml-2" @click="handleSearch"> <a-button type="primary" class="ml-2" @click="handleSearch">
@ -281,7 +281,9 @@ watch(
.video-mode { .video-mode {
display: flex; display: flex;
height: 600px; height: 60vh;
min-height: 400px;
max-height: 800px;
gap: 12px; gap: 12px;
.video-main { .video-main {
@ -309,16 +311,24 @@ watch(
flex-direction: column; flex-direction: column;
border: 1px solid #e8e8e8; border: 1px solid #e8e8e8;
border-radius: 4px; border-radius: 4px;
min-width: 0;
.sidebar-search { .sidebar-search {
padding: 8px; padding: 8px;
display: flex; display: flex;
align-items: center; align-items: center;
border-bottom: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8;
flex-wrap: wrap;
gap: 8px;
:deep(.ant-picker) {
max-width: 100%;
}
} }
.sidebar-list { .sidebar-list {
height: 600px; flex: 1;
min-height: 0;
overflow-y: auto; overflow-y: auto;
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
@ -339,13 +349,13 @@ watch(
} }
&.active { &.active {
border-color: #1890ff; // border-color: #1890ff;
background: #e6f7ff; // background: #e6f7ff;
} }
.item-thumb { .item-thumb {
width: 100%; width: 100%;
height: 105px; height: 143px;
background: #1a1a1a; background: #1a1a1a;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
@ -375,8 +385,8 @@ watch(
.item-time { .item-time {
padding: 4px; padding: 4px;
font-size: 11px; font-size: 14px;
color: #666; color: #000000D9;
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -393,4 +403,78 @@ watch(
} }
} }
} }
//
@media screen and (max-width: 1400px) {
.no-live-video {
.video-mode {
height: 55vh;
gap: 10px;
.video-main {
flex: 0 0 65%;
}
.video-sidebar {
flex: 0 0 35%;
.sidebar-list {
grid-template-columns: repeat(2, 1fr);
gap: 6px;
padding: 6px;
}
}
}
}
}
@media screen and (max-width: 1200px) {
.no-live-video {
.video-mode {
height: 50vh;
gap: 8px;
.video-main {
flex: 0 0 70%;
}
.video-sidebar {
flex: 0 0 30%;
.sidebar-list {
grid-template-columns: 1fr;
}
.sidebar-search {
:deep(.ant-picker-range) {
width: 100% !important;
}
}
}
}
}
}
@media screen and (max-width: 768px) {
.no-live-video {
.video-mode {
flex-direction: column;
height: auto;
.video-main {
flex: none;
height: 50vh;
}
.video-sidebar {
flex: none;
height: 40vh;
.sidebar-list {
grid-template-columns: repeat(2, 1fr);
}
}
}
}
}
</style> </style>

View File

@ -677,7 +677,7 @@ const setQGCTabList = async (_tabs: any, stcd: string) => {
}; };
const res2 = await getVideoList(params); const res2 = await getVideoList(params);
console.log('', modelStore.params.sttp); console.log('', modelStore.params.sttp);
// debugger //
if ( if (
res2?.data?.data?.length > 0 || res2?.data?.data?.length > 0 ||
res2?.data?.records?.length > 0 || res2?.data?.records?.length > 0 ||

View File

@ -310,7 +310,9 @@ const FPTabs1: Array<any> = [
name: '基础信息', name: '基础信息',
key: 'basicInfo', key: 'basicInfo',
type: 'basic', type: 'basic',
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo' default: true, // 默认显示
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo',
}, },
{ {
name: '实时视频', name: '实时视频',
@ -396,14 +398,14 @@ const FBTabs: Array<any> = [
} }
} }
] ]
},
{
name: '实时视频',
key: 'videoInfo',
type: 'video',
url: '/video/dataStcdFrame/getVideoMonitorList'
} }
// { // {
// name: '实时视频',
// key: 'videoInfo',
// type: 'video',
// url: '/video/dataStcdFrame/getVideoMonitorList'
// }
// {
// name: "全景影像", // name: "全景影像",
// key: "panoramaInfo", // key: "panoramaInfo",
// type: "panorama" // type: "panorama"
@ -873,7 +875,7 @@ const noLiveVideoTabs: Array<any> = [
}, },
{ {
name: '录像视频', name: '录像视频',
key: 'videoInfo', key: 'noLiveVideo',
type: 'noLiveVideo', type: 'noLiveVideo',
url: '/video/dataStcdFrame/getVideoMonitorList' url: '/video/dataStcdFrame/getVideoMonitorList'
} }

View File

@ -53,7 +53,12 @@
show-search show-search
placeholder="请选择" placeholder="请选择"
:size="'small'" :size="'small'"
style="width: 120px" :style="{ width: select.width || '80px' }"
:dropdown-style="{
maxHeight: '400px',
overflow: 'auto',
minWidth: '200px'
}"
:options="select.options" :options="select.options"
:filter-option="filterOption" :filter-option="filterOption"
@focus="handleFocus" @focus="handleFocus"
@ -84,7 +89,7 @@
value: 'value', value: 'value',
children: 'children' children: 'children'
}" }"
tree-node-filter-prop="label" tree-node-filter-prop="title"
popup-class-name="no-wrap-tree-select" popup-class-name="no-wrap-tree-select"
@select="handleTreeSelect" @select="handleTreeSelect"
@expand="handleTreeExpand" @expand="handleTreeExpand"
@ -177,11 +182,12 @@ interface PromptConfig {
} }
interface SelectConfig { interface SelectConfig {
picker: any; picker?: any;
format: any; format?: any;
show: boolean; show?: boolean;
value: string | undefined; value?: string | undefined;
options: SelectProps['options']; options?: SelectProps['options'];
width?: string;
} }
// 便 // 便
@ -239,7 +245,8 @@ const props = defineProps({
default: () => ({ default: () => ({
show: false, show: false,
value: undefined, value: undefined,
options: [] options: [],
width: ''
}) })
}, },
moreSelect: { moreSelect: {
@ -652,7 +659,7 @@ const handleTreeSelect = (selectedKeys: string | string[], info: any) => {
} }
moreSelectValue.value = selectedValue; moreSelectValue.value = selectedValue;
//
// lgtdlttd // lgtdlttd
if (info && typeof info === 'object') { if (info && typeof info === 'object') {
selectedNodeExtra.value = { selectedNodeExtra.value = {
@ -683,11 +690,16 @@ const handleFocus = () => {
// Handle focus // Handle focus
}; };
const filterOption = (input: string, option?: { label: string; value: string }) => { const filterOption = (
input: string,
option?: { label: string; value: string }
) => {
if (!option) return false; if (!option) return false;
const label = option.label?.toLowerCase() || ''; const label = option.label?.toLowerCase() || '';
const value = option.value?.toLowerCase() || ''; const value = option.value?.toLowerCase() || '';
return label.includes(input.toLowerCase()) || value.includes(input.toLowerCase()); return (
label.includes(input.toLowerCase()) || value.includes(input.toLowerCase())
);
}; };
// //
@ -759,7 +771,6 @@ const emitAllValues = () => {
} else { } else {
payload.scopeDate = []; payload.scopeDate = [];
} }
emit('update-values', payload); emit('update-values', payload);
}; };

View File

@ -596,7 +596,7 @@ const handleSearch = () => {
// console.log( typeOne.value ) // console.log( typeOne.value )
// console.log( formState.dtinType ) // console.log( formState.dtinType )
// console.log(typeOne.value == 'all' && formState.dtinType !='') // console.log(typeOne.value == 'all' && formState.dtinType !='')
// debugger //
const filters: any[] = [ const filters: any[] = [
formState.dataDimensionVal != 'all' ? { formState.dataDimensionVal != 'all' ? {
field: 'baseId', field: 'baseId',
@ -660,7 +660,7 @@ const handleSearch = () => {
// //
tableRef.value?.getList(filter) tableRef.value?.getList(filter)
if (kong.value) { if (kong.value) {
// debugger //
fetchPieData() fetchPieData()
} }
kong.value = true kong.value = true
@ -781,7 +781,7 @@ watch(
() => props.typeOne, () => props.typeOne,
(newVal) => { (newVal) => {
typeOne.value = newVal typeOne.value = newVal
// debugger //
} }
) )
@ -798,7 +798,7 @@ watch(
() => formState.types, () => formState.types,
(newType, oldType) => { (newType, oldType) => {
if (!formState.dateValues || newType === oldType) return if (!formState.dateValues || newType === oldType) return
// debugger //
const currentValue: any = formState.dateValues const currentValue: any = formState.dateValues
if (newType === 'year') { if (newType === 'year') {

View File

@ -564,7 +564,7 @@ watch(
if (!stcd) { if (!stcd) {
return; return;
} }
// debugger //
tableRef.value.tableData = []; tableRef.value.tableData = [];
getCol({ stcd, startTime, endTime }); getCol({ stcd, startTime, endTime });
}, },

View File

@ -319,7 +319,7 @@ const initChart = (): Promise<void> => {
const getEchartsData = async () => { const getEchartsData = async () => {
// 1. // 1.
const currentValue = datetimePicker.value.value; const currentValue = datetimePicker.value.value;
// debugger //
if (select.value.value === 'year') { if (select.value.value === 'year') {
// YYYY 4 // YYYY 4
if (!/^\d{4}$/.test(currentValue)) { if (!/^\d{4}$/.test(currentValue)) {
@ -361,7 +361,7 @@ const getEchartsData = async () => {
if( ! baseid.value){ if( ! baseid.value){
return return
} }
// debugger //
let params = { let params = {
"filter": { "filter": {
"logic": "and", "logic": "and",
@ -439,7 +439,7 @@ const getEchartsData = async () => {
] ]
}; };
console.log('请求参数:', params); console.log('请求参数:', params);
// debugger //
// 4. API // 4. API
loading.value = true; loading.value = true;
try { try {
@ -687,7 +687,7 @@ const updateChartData = (currentLabel: string, lastYearLabel: string) => {
if (barData) { if (barData) {
// baseid rstcd // baseid rstcd
const selectedId = chartDataIdMap.value[dataIndex] || ''; const selectedId = chartDataIdMap.value[dataIndex] || '';
// debugger //
clickedBarData.value = { clickedBarData.value = {
name: barData.name.replace('\n', ''), // name: barData.name.replace('\n', ''), //
current: barData.current, current: barData.current,

View File

@ -714,7 +714,7 @@ const getechartsdata = async (yearOne: string) => {
stopAutoPlay(); stopAutoPlay();
return; return;
} }
// debugger //
// //
cachedApiData.value = responseData; cachedApiData.value = responseData;
currentYear.value = yearOne; currentYear.value = yearOne;

View File

@ -37,7 +37,7 @@ import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent'; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { getChuiXiangShuiWenTreeStcd, getCxswList } from '@/api/sw'; import { getChuiXiangShuiWenTreeStcd, getCxswList } from '@/api/sw';
import { useModelStore } from '@/store/modules/model'; import { useModelStore } from '@/store/modules/model';
import { useMapModalStore } from '@/store/modules/mapModal'; // import { useMapModalStore } from '@/store/modules/mapModal';
import { MapClass } from '@/components/gis/map.class'; import { MapClass } from '@/components/gis/map.class';
const modelStore = useModelStore(); const modelStore = useModelStore();
@ -356,7 +356,6 @@ const selectOptions = async () => {
const data = { filter, sort }; const data = { filter, sort };
const res = await getChuiXiangShuiWenTreeStcd(data); const res = await getChuiXiangShuiWenTreeStcd(data);
let dataOne = res?.data?.data || res.data; let dataOne = res?.data?.data || res.data;
if (dataOne.length > 0) { if (dataOne.length > 0) {
const treeData = dataOne.map(item => { const treeData = dataOne.map(item => {
return { return {
@ -547,7 +546,7 @@ const initChart = () => {
)}-${lastDay} 23:59:59`; )}-${lastDay} 23:59:59`;
console.log(endTime); console.log(endTime);
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = 'WT'; modelStore.params.sttp = 'WTRV';
modelStore.params.enfc = '1'; modelStore.params.enfc = '1';
modelStore.title = stnm; modelStore.title = stnm;
// modelStore.isBasicEdit = true; // modelStore.isBasicEdit = true;
@ -663,53 +662,53 @@ const updateChart = () => {
chartInstance.setOption(option, { notMerge: true }); chartInstance.setOption(option, { notMerge: true });
}; };
const handleChartClick = (params: any) => { // const handleChartClick = (params: any) => {
if (!params || !params.seriesName) return; // if (!params || !params.seriesName) return;
const matchMonth = params.seriesName.match(/\d+/); // const matchMonth = params.seriesName.match(/\d+/);
const monthNum = matchMonth ? parseInt(matchMonth[0]) : 1; // const monthNum = matchMonth ? parseInt(matchMonth[0]) : 1;
const year = // const year =
typeof datetimePicker.value.value === 'string' // typeof datetimePicker.value.value === 'string'
? parseInt(datetimePicker.value.value) // ? parseInt(datetimePicker.value.value)
: datetimePicker.value.value; // : datetimePicker.value.value;
const startTime = `${year}-${String(monthNum).padStart(2, '0')}-01 00:00:00`; // const startTime = `${year}-${String(monthNum).padStart(2, '0')}-01 00:00:00`;
const lastDay = new Date(year, monthNum, 0).getDate(); // const lastDay = new Date(year, monthNum, 0).getDate();
const endTime = `${year}-${String(monthNum).padStart( // const endTime = `${year}-${String(monthNum).padStart(
2, // 2,
'0' // '0'
)}-${lastDay} 23:59:59`; // )}-${lastDay} 23:59:59`;
const currentStcd = select.value.value; // const currentStcd = select.value.value;
let currentStnm = ''; // let currentStnm = '';
select.value.options.forEach((station: any) => { // select.value.options.forEach((station: any) => {
station.children?.forEach((child: any) => { // station.children?.forEach((child: any) => {
if (child.value === currentStcd) { // if (child.value === currentStcd) {
currentStnm = child.title; // currentStnm = child.title;
} // }
}); // });
}); // });
import('@/store/modules/model').then(({ useModelStore }) => { // import('@/store/modules/model').then(({ useModelStore }) => {
import('@/components/MapModal/setting.config').then(({ handleTabs }) => { // import('@/components/MapModal/setting.config').then(({ handleTabs }) => {
const modelStore = useModelStore(); // const modelStore = useModelStore();
modelStore.params = { // modelStore.params = {
sttp: 'WT', // sttp: 'WT',
stcd: currentStcd, // stcd: currentStcd,
titleName: currentStnm, // titleName: currentStnm,
enfc: '1', // enfc: '1',
timeRange: [startTime, endTime] // timeRange: [startTime, endTime]
} as any; // } as any;
modelStore.title = currentStnm ? `${currentStnm} ` : ''; // modelStore.title = currentStnm ? `${currentStnm} ` : '';
modelStore.tabList = handleTabs(modelStore.params); // modelStore.tabList = handleTabs(modelStore.params);
modelStore.modalVisible = true; // modelStore.modalVisible = true;
}); // });
}); // });
}; // };
// ==================== ==================== // ==================== ====================
@ -750,19 +749,9 @@ watch(
// }, // },
// { deep: true } // { deep: true }
// ); // );
const handlePanelChange1 = (data: any, type: string) => { const handlePanelChange1 = (data: any) => {
console.log('当前所有控件状态:', data); console.log('当前所有控件状态:', data);
if (data.moreSelect && data.datetime) { if (data.moreSelect && data.datetime) {
if (type == 'click') {
const mapClass = MapClass.getInstance();
mapClass.flyTopanto(
[
Number(data.selectedNodeExtra.lgtd),
Number(data.selectedNodeExtra.lttd)
],
15
);
}
year.value = data.datetime; year.value = data.datetime;
fetchChartData(data.moreSelect, data.datetime); fetchChartData(data.moreSelect, data.datetime);
} else { } else {
@ -772,6 +761,16 @@ const handlePanelChange1 = (data: any, type: string) => {
selected.value = {}; selected.value = {};
showemit.value = false; showemit.value = false;
} }
if (data?.selectedNodeExtra?.lttd && data?.selectedNodeExtra?.lgtd) {
const mapClass = MapClass.getInstance();
mapClass.flyTopanto(
[
Number(data.selectedNodeExtra.lgtd),
Number(data.selectedNodeExtra.lttd)
],
15
);
}
}; };
</script> </script>

View File

@ -52,6 +52,7 @@ import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { getVmsstbprpt, inOutOneGetKendoListCust } from '@/api/sw'; import { getVmsstbprpt, inOutOneGetKendoListCust } from '@/api/sw';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent'; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import churukushuiwen from './churukushuiwen.vue'; import churukushuiwen from './churukushuiwen.vue';
import { MapClass } from '@/components/gis/map.class';
// ==================== ==================== // ==================== ====================
// (便) // (便)
defineOptions({ defineOptions({
@ -608,10 +609,12 @@ const handleModalClose = () => {
*/ */
const handlePanelChange1 = (data: any, type: string) => { const handlePanelChange1 = (data: any, type: string) => {
// TODO: // TODO:
if (data.moreSelect) { if (data.moreSelect && data.scopeDate) {
paramsOne.value.value = data.moreSelect; paramsOne.value.value = data.moreSelect;
paramsOne.value.tm = data.scopeDate;
fetchChartData();
} }
if (type == 'click') { if (data?.selectedNodeExtra?.lttd && data?.selectedNodeExtra?.lgtd) {
const mapClass = MapClass.getInstance(); const mapClass = MapClass.getInstance();
mapClass.flyTopanto( mapClass.flyTopanto(
[ [
@ -621,8 +624,6 @@ const handlePanelChange1 = (data: any, type: string) => {
13 13
); );
} }
paramsOne.value.tm = data.scopeDate;
fetchChartData();
}; };
/** /**
@ -695,7 +696,6 @@ const getselsectData = async () => {
console.log('res', res); console.log('res', res);
const data = res?.data?.data || res?.data; const data = res?.data?.data || res?.data;
const obj: any = {}; const obj: any = {};
if (data) { if (data) {
// () // ()
data.map(e => { data.map(e => {
@ -733,7 +733,6 @@ const getselsectData = async () => {
return indexA - indexB; return indexA - indexB;
}); });
console.log('treeData', treeData); console.log('treeData', treeData);
// //
select.value.options = treeData; select.value.options = treeData;

View File

@ -0,0 +1,338 @@
<template>
<SidePanelItem
title="鱼类变化情况"
:datetimePicker="datetimePicker"
@update-values="handlePanelChange"
>
<a-spin :spinning="loading">
<div v-show="!sourceData.length && !loading" class="empty-wrapper">
<a-empty description="暂无数据" />
</div>
<div v-show="sourceData.length" class="chart-wrapper">
<div ref="chartContainer" class="chart-container"></div>
</div>
</a-spin>
</SidePanelItem>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { sdFprdRGetFtpChangeInfo } from '@/api/yldc';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
defineOptions({
name: 'FishChangeTu'
});
const JidiSelectEventStore = useJidiSelectEventStore();
// const BASE_ID = '07';
//
const datetimePicker = ref({
show: true,
value: `${new Date().getFullYear()}`,
format: 'YYYY',
picker: 'year' as const,
options: []
});
//
const sourceData = ref<
Array<{
year: number;
month: number;
ftpCount: number;
fishCount: number;
}>
>([]);
const loading = ref(false);
//
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
let resizeObserver: ResizeObserver | null = null;
// 1
function getMonthsUntilNow(year: number) {
const now = new Date();
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
const months: Array<{ name: string; value: number }> = [];
const maxMonth = year >= currentYear ? currentMonth : 12;
for (let i = 1; i <= maxMonth; i++) {
months.push({
name: i + '月',
value: i
});
}
return months;
}
//
const fishData = computed(() => {
const dates = getMonthsUntilNow(parseInt(datetimePicker.value.value));
if (sourceData.value.length > 0) {
const speciesCount = dates.map(item => {
const info = sourceData.value.find(v => v.month === item.value);
return info ? Number(info.ftpCount) : 0;
});
const fishCount = dates.map(item => {
const info = sourceData.value.find(v => v.month === item.value);
return info ? Number(info.fishCount) : 0;
});
return {
dates: dates.map(item => item.name),
speciesCount,
fishCount
};
}
return {
dates: dates.map(item => item.name),
speciesCount: [],
fishCount: []
};
});
//
const option = computed<EChartsOption>(() => {
const data = fishData.value;
const baseFontSize = 12;
// Y
const speciesMax = Math.max(...data.speciesCount, 1);
const fishMax = Math.max(...data.fishCount, 1);
return {
yAxisSplit: 5,
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
backgroundColor: 'rgba(50,50,50,0.9)',
textStyle: { fontSize: baseFontSize, color: '#fff' }
},
legend: {
data: ['种类', '总数'],
top: 0,
textStyle: { fontSize: baseFontSize }
},
grid: {
left: '50px',
right: '60px',
top: '45px',
bottom: '40px'
},
xAxis: {
type: 'category',
data: data.dates,
axisLine: { lineStyle: { color: '#ccc' } },
splitLine: { show: true },
axisLabel: {
color: '#666',
fontSize: baseFontSize,
rotate: 0,
interval: 0
}
},
yAxis: [
{
type: 'value',
name: '种类',
nameTextStyle: { fontSize: baseFontSize },
min: 0,
max: Math.ceil(speciesMax * 1.2),
axisLine: { lineStyle: { color: '#165DFF' } },
axisLabel: {
color: '#666',
fontSize: baseFontSize,
formatter: (value: number) => Math.floor(value).toString()
},
splitLine: { lineStyle: { color: '#eee' } }
},
{
type: 'value',
name: '总数',
nameTextStyle: { fontSize: baseFontSize },
min: 0,
max: Math.ceil(fishMax * 1.2),
axisLine: { lineStyle: { color: '#36CFC9' } },
axisLabel: {
color: '#666',
fontSize: baseFontSize,
formatter: (value: number) => Math.floor(value).toString()
}
}
],
series: [
{
name: '种类',
type: 'line',
data: data.speciesCount,
yAxisIndex: 0,
color: '#165DFF',
smooth: true,
symbol: 'circle',
symbolSize: 8,
label: {
show: true,
position: 'top',
fontSize: baseFontSize - 2
}
},
{
name: '总数',
type: 'line',
data: data.fishCount,
yAxisIndex: 1,
color: '#36CFC9',
smooth: true,
symbol: 'circle',
symbolSize: 8,
label: {
show: true,
position: 'top',
fontSize: baseFontSize - 2
}
}
]
};
});
//
const getData = async () => {
try {
loading.value = true;
const year = datetimePicker.value.value;
const res = await sdFprdRGetFtpChangeInfo({
baseId: baseid.value,
year
});
if (res && (res as any).data) {
sourceData.value = (res as any).data;
} else {
sourceData.value = [];
}
} catch (error) {
console.error('获取鱼类变化数据失败:', error);
sourceData.value = [];
} finally {
loading.value = false;
}
};
//
const initChart = () => {
if (!chartContainer.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartContainer.value);
chartInstance.setOption(option.value, true);
};
//
const updateChart = () => {
if (chartInstance) {
chartInstance.setOption(option.value, true);
chartInstance.resize();
}
};
//
const handlePanelChange = (data: any) => {
if (data.datetime) {
datetimePicker.value.value = data.datetime;
getData();
}
};
//
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
let baseid = ref('');
watch(
() => JidiSelectEventStore.selectedItem,
newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
}
},
{ deep: true, immediate: true }
);
onMounted(() => {
getData();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
if (resizeObserver) {
resizeObserver.disconnect();
}
window.removeEventListener('resize', handleResize);
});
//
watch(sourceData, newData => {
if (newData && newData.length > 0) {
// observer
if (resizeObserver) {
resizeObserver.disconnect();
}
// 使 ResizeObserver
resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
if (width > 0 && height > 0) {
nextTick(() => {
if (!chartInstance && chartContainer.value) {
initChart();
} else {
updateChart();
}
});
resizeObserver?.disconnect();
resizeObserver = null;
}
}
});
nextTick(() => {
if (chartContainer.value) {
resizeObserver.observe(chartContainer.value);
}
});
}
});
</script>
<style lang="scss" scoped>
.empty-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
}
.chart-wrapper {
margin: 0;
width: 100%;
position: relative;
}
.chart-container {
width: 100%;
height: 200px;
}
</style>

View File

@ -1,76 +1,98 @@
<!-- SidePanelItem.vue --> <!-- SidePanelItem.vue -->
<template> <template>
<SidePanelItem title="鱼类介绍"> <SidePanelItem title="鱼类介绍">
<div class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"> <a-spin :spinning="loading">
<!-- 跑马灯轨道容器 --> <template v-if="originalMediaData.length > 0">
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }" <div
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"> class="container"
<!-- 遍历所有媒体项包含克隆项 --> @mouseenter="handleMouseEnter"
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item"> @mouseleave="handleMouseLeave"
<!-- 图片 --> >
<img :src="item.url" alt="" /> <!-- 跑马灯轨道容器 -->
<!-- 说明文字随媒体项移动 --> <div
<div class="text">{{ item.text }}</div> class="carousel-track"
</div> :class="{ 'no-transition': isTransitioning }"
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
>
<!-- 遍历所有媒体项包含克隆项 -->
<div
v-for="(item, index) in renderMediaData"
:key="index"
class="carousel-item"
@click="handleItemClick"
>
<!-- 图片 -->
<img :src="item.url" alt="" />
<!-- 说明文字随媒体项移动 -->
<div class="text">{{ item.text }}</div>
</div> </div>
</div>
<!-- 面板指示器固定在底部右侧 --> <!-- 面板指示器固定在底部右侧 -->
<div class="pagination-dots-fixed"> <div class="pagination-dots-fixed">
<span <span
v-for="(dot, index) in originalMediaData" v-for="(dot, index) in originalMediaData"
:key="index" :key="index"
class="dot" class="dot"
:class="{ active: getCurrentRealIndex() === index }" :class="{ active: getCurrentRealIndex() === index }"
@click="goToSlide(index)" @click="goToSlide(index)"
></span> ></span>
</div> </div>
</div> </div>
<!-- 独立的文字说明区域随跑马灯切换而变化 --> <!-- 独立的文字说明区域随跑马灯切换而变化 -->
<div class="description-text"> <div class="description-text" @click="handleItemClick">
{{ currentDescription }} {{ currentDescription }}
</div> </div>
</SidePanelItem> </template>
<a-empty v-else description="暂无数据" />
</a-spin>
</SidePanelItem>
<!-- 设施详情弹框 -->
<a-modal
v-model:open="modalVisible"
:title="'设施类型介绍'"
width="1536px"
:footer="null"
>
<div v-if="currentItem">
<ArtsDetail :dataSource="currentItem" :index="getCurrentRealIndex()" />
</div>
</a-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'; import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue'; import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { fishDicGetKendoList, wbsbGetKendoList } from '@/api/yldc';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue';
// 便 // 便
defineOptions({ defineOptions({
name: 'zengZhiZhanJieShaoMod' name: 'zengZhiZhanJieShaoMod'
}); });
const JidiSelectEventStore = useJidiSelectEventStore();
// //
interface MediaItem { interface MediaItem {
type: 'image' | 'video'; type: 'image' | 'video';
url: string; url: string;
text: string; text: string;
description: string; description: string;
id?: string;
} }
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
let baseid = ref('');
let rvcd = ref('');
//
const modalVisible = ref(false);
const currentItem = ref<any>(null);
// 3 //
const originalMediaData = ref<MediaItem[]>([ const loading = ref(false);
{
type: 'image', //
url: 'https://211.99.26.225:12125/?20230814205611342377136845462200&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920', const originalMediaData = ref<MediaItem[]>([]);
text: '松岗鱼类增殖站',
description: '松岗鱼类增殖放流站位于四川省阿坝藏族羌族自治州马尔康市松岗镇,主要服务于大渡河上游的双江口和金川两座水电站,同时还承担多种珍稀特有鱼类的救护和科研任务,实现工程建设与生态环境共同推进、相互促进。'
},
{
type: 'video',
url: 'https://211.99.26.225:12125/?20230805205848575430105387253710&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920', // URL
text: '猴子岩鱼类增殖站',
description: '猴子岩水电站鱼类增殖放流站位于猴子岩水电站坝址下游约7.0km(业主营地下游约1.5km)大渡河左岸桃花渣场顶部平台上紧邻枢纽桃花大桥下游侧占地面积47.3亩其中一期工程27.0亩预留二期工程用地20.3亩(二期工程目前为丹巴、巴底水电站预留工程)增殖放流站工作流程为:亲鱼收集购买、亲鱼驯养培育、人工催产和授精、人工孵化、苗种培育和放流。 猴子岩鱼类增殖放流站近期放流对象中齐口裂腹鱼、重口裂腹鱼、大渡软刺裸裂尻鱼增殖放流技术水平已趋于熟化,中期放流对象大渡软刺裸裂尻鱼人工繁殖技术逐渐趋于熟化'
},
{
type: 'image',
url: 'https://211.99.26.225:12125/?20230805205924378504010675106305&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920',
text: '黑马鱼类增殖站',
description: '大渡河黑马鱼类增殖放流站位于四川省甘洛县黑马乡黑马业主营地内距离甘洛县城45km,区域交通路况较好。主要承担瀑布沟、深溪沟、大岗山、枕头坝一级、沙坪二级等五座水电站鱼类增殖放流的重任。放流鱼类包含:齐口裂腹鱼、重口裂腹鱼、鲈鲤、长薄鳅、白甲鱼、中华倒刺、长吻脆、稀有鮊鲫、华鲮、侧沟爬岩鳅等10个种类共计约918.07万尾珍稀特有鱼苗。'
}
]);
// //
// [, ..., ] // [, ..., ]
@ -90,197 +112,301 @@ const isTransitioning = ref(false);
// //
const initRenderData = () => { const initRenderData = () => {
const length = originalMediaData.value.length; const length = originalMediaData.value.length;
if (length === 0) return; if (length === 0) return;
renderMediaData.value = [ renderMediaData.value = [
originalMediaData.value[length - 1], // originalMediaData.value[length - 1], //
...originalMediaData.value, // ...originalMediaData.value, //
originalMediaData.value[0] // originalMediaData.value[0] //
]; ];
}; };
// //
const startAutoPlay = () => { const startAutoPlay = () => {
if (timer) clearInterval(timer); if (timer) clearInterval(timer);
timer = setInterval(() => { timer = setInterval(() => {
if (!isHovering.value && !isTransitioning.value) { if (!isHovering.value && !isTransitioning.value) {
nextSlide(); nextSlide();
} }
}, 4000); }, 4000);
}; };
// //
const nextSlide = () => { const nextSlide = () => {
currentIndex.value++; currentIndex.value++;
// //
setTimeout(() => { setTimeout(() => {
checkSeamlessJump(); checkSeamlessJump();
}, 500); // transition }, 500); // transition
}; };
// //
const checkSeamlessJump = () => { const checkSeamlessJump = () => {
const realLength = originalMediaData.value.length; const realLength = originalMediaData.value.length;
// = realLength + 1 // = realLength + 1
if (currentIndex.value >= realLength + 1) { if (currentIndex.value >= realLength + 1) {
// 1. // 1.
isTransitioning.value = true; isTransitioning.value = true;
// 2. 1 // 2. 1
currentIndex.value = 1; currentIndex.value = 1;
// 3. DOM // 3. DOM
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
isTransitioning.value = false; isTransitioning.value = false;
}); });
}); });
} }
}; };
// //
const handleMouseEnter = () => { const handleMouseEnter = () => {
isHovering.value = true; isHovering.value = true;
}; };
// //
const handleMouseLeave = () => { const handleMouseLeave = () => {
isHovering.value = false; isHovering.value = false;
}; };
// //
const currentDescription = computed(() => { const currentDescription = computed(() => {
const realIndex = getCurrentRealIndex(); const realIndex = getCurrentRealIndex();
return originalMediaData.value[realIndex]?.description || ''; return originalMediaData.value[realIndex]?.description || '';
}); });
const getData = async () => {
loading.value = true;
try {
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'isDeleted',
operator: 'eq',
dataType: 'string',
value: 0
},
{
field: 'logo',
operator: 'isnotnull'
},
{
field: 'introduce',
operator: 'isnotnull'
},
rvcd.value
? {
field: 'rvcd',
operator: 'eq',
dataType: 'string',
value: rvcd.value
}
: null
].filter(Boolean)
},
select: ['id', 'code', 'name', 'introduce', 'logo']
};
const res = await fishDicGetKendoList(params);
const data = res?.data?.data;
originalMediaData.value = data.map((item: any) => ({
type: 'image' as const,
url: baseUrl + '?' + item.logo + '&view=jpg',
text: item.name,
description: item.introduce,
id: item.id
}));
} finally {
loading.value = false;
}
};
// //
onMounted(() => { onMounted(async () => {});
initRenderData();
startAutoPlay();
});
// //
onUnmounted(() => { onUnmounted(() => {
if (timer) clearInterval(timer); if (timer) clearInterval(timer);
}); });
// //
const getCurrentRealIndex = () => { const getCurrentRealIndex = () => {
const realLength = originalMediaData.value.length; const realLength = originalMediaData.value.length;
let realIndex = currentIndex.value - 1; // let realIndex = currentIndex.value - 1; //
// //
if (realIndex < 0) realIndex = realLength - 1; if (realIndex < 0) realIndex = realLength - 1;
if (realIndex >= realLength) realIndex = 0; if (realIndex >= realLength) realIndex = 0;
return realIndex; return realIndex;
}; };
// //
const goToSlide = (targetIndex: number) => { const goToSlide = (targetIndex: number) => {
if (isTransitioning.value) return; if (isTransitioning.value) return;
// //
currentIndex.value = targetIndex + 1; currentIndex.value = targetIndex + 1;
}; };
//
const handleItemClick = () => {
currentItem.value = originalMediaData.value.map((item: any) => ({
url: item.url || '',
description: item.description || '',
title: item.text || ''
}));
modalVisible.value = true;
};
const getrvcd = async baseid => {
let params = {
filter: {
logic: 'and',
filters: [
{
field: 'isDeleted',
operator: 'eq',
dataType: 'string',
value: 0
},
{
field: 'wbsType',
operator: 'eq',
dataType: 'string',
value: 'PSB_RVCD'
},
{
field: 'objId',
operator: 'eq',
dataType: 'string',
value: baseid
}
]
},
select: ['wbsCode', 'wbsName']
};
let res = await wbsbGetKendoList(params);
let data = res?.data?.data?.wbsCode;
rvcd.value = data;
await getData();
initRenderData();
startAutoPlay();
};
watch(
() => JidiSelectEventStore.selectedItem,
newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
if (newVal.wbsCode != 'all') {
getrvcd(newVal.wbsCode);
}
}
},
{ deep: true, immediate: true }
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
width: 100%; width: 100%;
height: 228px; height: 228px;
// border: 1px solid #7fd6ff; // border: 1px solid #7fd6ff;
// border-radius: 5px; // border-radius: 5px;
position: relative; position: relative;
overflow: hidden; // overflow: hidden; //
// //
.carousel-track { .carousel-track {
display: flex; // display: flex; //
width: 100%;
height: 100%;
transition: transform 0.5s ease-in-out; // 0.5
//
&.no-transition {
transition: none;
}
//
.carousel-item {
min-width: 100%; //
max-width: 100%;
height: 100%;
position: relative;
flex-shrink: 0; //
cursor: pointer;
img,
video {
width: 100%; width: 100%;
height: 100%; height: 100%;
transition: transform 0.5s ease-in-out; // 0.5 object-fit: cover; //
}
// .text {
&.no-transition {
transition: none;
}
//
.carousel-item {
min-width: 100%; //
height: 100%;
position: relative;
flex-shrink: 0; //
img,
video {
width: 100%;
height: 100%;
object-fit: cover; //
}
.text {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 22px;
line-height: 22px;
background: rgba(0, 0, 0, 0.1);
color: #fff;
padding-left: 10px;
}
}
}
//
.pagination-dots-fixed {
position: absolute; position: absolute;
bottom: 10px; bottom: 0;
right: 10px; left: 0;
display: flex; width: 100%;
gap: 6px; height: 22px;
z-index: 10; // line-height: 22px;
background: rgba(0, 0, 0, 0.1);
.dot { color: #fff;
width: 5px; padding-left: 10px;
height: 5px; }
border-radius: 50%;
background-color: #D8D8D8;
cursor: pointer;
transition: background-color 0.3s ease;
&.active {
background-color: #005293;
}
&:hover {
opacity: 0.8;
}
}
} }
}
//
.pagination-dots-fixed {
position: absolute;
bottom: 10px;
right: 10px;
display: flex;
gap: 6px;
z-index: 10; //
.dot {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #d8d8d8;
cursor: pointer;
transition: background-color 0.3s ease;
&.active {
background-color: #005293;
}
&:hover {
opacity: 0.8;
}
}
}
} }
// //
.description-text { .description-text {
// padding: 8px 12px; // padding: 8px 12px;
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
// min-height: 40px; // min-height: 40px;
transition: all 0.3s ease; transition: all 0.3s ease;
margin-bottom: 20px; margin-bottom: 20px;
// //
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
word-break: break-word; word-break: break-word;
} }
</style> </style>

View File

@ -1,76 +1,144 @@
<!-- SidePanelItem.vue --> <!-- SidePanelItem.vue -->
<template> <template>
<SidePanelItem title="视频" :select="select"> <SidePanelItem
<div class="video-container"> title="视频"
<video :select="select"
ref="videoRef" @update-values="handlePanelChange1"
class="video-player" >
controls <div class="video-container">
autoplay <LiveVideoBox
muted :videoData="{ url: videoUrl1 }"
playsinline height="100%"
preload="auto" :showTitle="false"
> monitorType="record"
<source src="" type="video/mp4"> />
您的浏览器不支持视频播放 <video
</video> ref="videoRef"
</div> class="video-player"
</SidePanelItem> controls
autoplay
muted
playsinline
preload="auto"
>
<source src="" type="video/mp4" />
您的浏览器不支持视频播放
</video>
</div>
</SidePanelItem>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'; import { ref, onMounted, onUnmounted, computed, reactive,watch } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue'; import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { msstbprptGetKendoList } from '@/api/yldc';
import LiveVideoBox from '@/views/shiPinJianKong/components/LiveVideoBox.vue';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
// 便 // 便
defineOptions({ defineOptions({
name: 'zengZhiZhanJieShaoMod' name: 'zengZhiZhanJieShaoMod'
}); });
const JidiSelectEventStore = useJidiSelectEventStore();
// //
const videoRef = ref<HTMLVideoElement>(); const videoRef = ref<HTMLVideoElement>();
const videoUrl = ref([]);
// //
const select = ref({ const select = ref({
show: true, show: true,
value: undefined, value: undefined,
options: [], options: [],
picker: undefined, picker: undefined,
format: undefined format: undefined,
width: '200px'
}); });
const getVideoSelect = async () => {
let params = {
filter: {
logic: 'and',
filters: [
{
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: 'VD_FPRD'
},
{
field: 'isDeleted',
operator: 'eq',
dataType: 'string',
value: 0
},
{
field: 'url',
operator: 'isnotnull'
},
baseid.value != 'all'
? {
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: baseid.value
}
: null
].filter(Boolean)
},
select: ['stcd', 'stnm', 'url']
};
let res = await msstbprptGetKendoList(params);
let resData = res.data.data; //stnm stcd url
select.value.options = resData.map(item => {
return {
value: item.stcd,
label: item.stnm
};
});
select.value.value = resData[0].stcd;
videoUrl.value = resData;
};
const videoUrl1 = ref('');
const handlePanelChange1 = async data => {
if (data.select) {
videoUrl.value.forEach(item => {
if (item.stcd === data.select) {
videoUrl1.value = item.url;
}
});
}
};
let baseid = ref('');
watch(
() => JidiSelectEventStore.selectedItem,
newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
}
},
{ deep: true, immediate: true }
);
// //
onMounted(() => { onMounted(() => {
getVideoSelect();
}); });
// //
onUnmounted(() => { onUnmounted(() => {});
if (videoRef.value) {
videoRef.value.pause();
videoRef.value.src = '';
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.video-container{ .video-container {
height: 300px; height: 300px;
width: 100%;
.video-player {
width: 100%; width: 100%;
height: 100%;
object-fit: contain;
background-color: #000;
.video-player { //
width: 100%; &::-webkit-media-controls-panel {
height: 100%; background-color: rgba(0, 0, 0, 0.7);
object-fit: contain;
background-color: #000;
//
&::-webkit-media-controls-panel {
background-color: rgba(0, 0, 0, 0.7);
}
} }
}
} }
</style> </style>

View File

@ -0,0 +1,407 @@
<template>
<a-modal
:open="visible"
title="鱼类调查装置概况详情"
width="90%"
:body-style="{ height: '700px', padding: '16px' }"
:footer="null"
@cancel="handleClose"
>
<div class="modal-content">
<!-- 搜索表单 -->
<a-form layout="inline" class="search-form" @finish="handleSearch">
<a-form-item label="装置名称">
<a-select
v-model:value="searchForm.stcd"
placeholder="请选择装置名称"
style="width: 180px"
allow-clear
show-search
:filter-option="filterOption"
:options="deviceOptions"
/>
</a-form-item>
<a-form-item label="鱼种类">
<a-select
v-model:value="searchForm.name"
placeholder="请选择鱼种类"
style="width: 150px"
allow-clear
show-search
:filter-option="filterOption"
:options="fishTypeOptions"
/>
</a-form-item>
<a-form-item label="年份">
<a-date-picker
v-model:value="searchForm.year"
picker="year"
format="YYYY"
style="width: 120px"
allow-clear
/>
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleSearch">查询</a-button>
</a-form-item>
</a-form>
<!-- 表格 -->
<div class="table-wrapper">
<BasicTable
ref="tableRef"
:columns="columns"
:scroll-y="500"
:list-url="sdFprdRGetKendoListCust"
:search-params="tableSearchParams"
:transform-data="customTransform"
:enable-sort="true"
@sort-change="handleSortChange"
>
<template #fid="{ column, record }">
<a-button
type="link"
size="small"
:disabled="!record.fid"
@click="handleViewAttachment(record)"
>
查看附件
</a-button>
</template>
</BasicTable>
</div>
</div>
</a-modal>
<CommonAttachmentModal
v-model:open="attachmentModalVisible"
:fid="currentAttachmentFid"
title="查看附件"
/>
</template>
<script lang="ts" setup>
import { ref, computed, reactive, onMounted, nextTick, watch } from 'vue';
import dayjs from 'dayjs';
import BasicTable from '@/components/BasicTable/index.vue';
import { vmsstbprptGetKendoList, sdFprdRGetKendoListCust,sdFprdRgetFishInfo } from '@/api/yldc';
import CommonAttachmentModal from '@/components/MapModal/components/CommonAttachmentModal.vue';
defineOptions({
name: 'DeviceTableModal'
});
interface Props {
visible: boolean;
currentYear?: string;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
currentYear: () => `${new Date().getFullYear()}`
});
const emit = defineEmits(['update:visible', 'close']);
const attachmentModalVisible = ref(false);
const currentAttachmentFid = ref('');
//
const searchForm = reactive({
stcd: undefined,
name: undefined,
year: props.currentYear ? dayjs().year(parseInt(props.currentYear)) : dayjs()
});
//
const sortInfo = ref<{ field: string; order: string | null }>({
field: 'tm',
order: 'descend'
});
//
const deviceOptions = ref<any[]>([]);
//
const fishTypeOptions = ref<any[]>([]);
//
const fetchFishTypeList = async () => {
try {
const res = await sdFprdRgetFishInfo();
// debugger
if (res && res?.data) {
fishTypeOptions.value = res.data.map((item: any) => ({
label: item,
value: item
}));
}
} catch (error) {
console.error('获取鱼种类列表失败:', error);
fishTypeOptions.value = [];
}
};
// Select
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
//
watch(
() => props.visible,
(newVisible) => {
if (newVisible && props.currentYear) {
//
searchForm.year = dayjs().year(parseInt(props.currentYear));
// DOM
nextTick(() => {
handleSearch();
});
}
}
);
// currentYear
watch(
() => props.currentYear,
(newYear) => {
if (props.visible && newYear) {
searchForm.year = dayjs().year(parseInt(newYear));
handleSearch();
}
}
);
//
const fetchDeviceList = async () => {
try {
let params = {
"filter": {
"logic": "and",
"filters": [
{
"field": "sttpCode",
"operator": "eq",
"dataType": "string",
"value": "FPRD"
}
]
},
"sort": [
{
"field": "baseStepSort",
"dir": "asc"
},
{
"field": "rvcdStepSort",
"dir": "asc"
},
{
"field": "siteStepSort",
"dir": "asc"
},
{
"field": "ttpwr",
"dir": "desc"
}
],
"group": [],
"select": [
"stnm",
"stcd"
]
}
const res = await vmsstbprptGetKendoList(params);
if (res && res?.data?.data) {
deviceOptions.value = res.data.data.map((item: any) => ({
label: item.stnm,
value: item.stcd
}));
}
} catch (error) {
console.error('获取装置列表失败:', error);
deviceOptions.value = [];
}
};
//
const columns = [
{
title: '装置名称',
key: 'stnm',
dataIndex: 'stnm',
merge: true,
width: '150px'
},
{
title: '时间',
key: 'tm',
dataIndex: 'tm',
width: '120px',
sorter: true, //
customRender: ({ text }: any) => {
if (!text) return '-';
return dayjs(text).format('YYYY');
}
},
{
title: '鱼种类(种)',
key: 'name',
dataIndex: 'name',
width: '120px'
},
{
title: '数量(尾)',
key: 'num',
dataIndex: 'num',
width: '100px'
},
{
title: '规格(cm)',
key: 'fsz',
dataIndex: 'fsz',
width: '100px'
},
{
title: '附件',
key: 'fid',
dataIndex: 'fid',
width: '100px',
slots: { customRender: 'fid' }
}
];
//
const tableSearchParams = computed(() => {
const baseSort =
sortInfo.value.field && sortInfo.value.order
? [
{
field: sortInfo.value.field,
dir: sortInfo.value.order === 'ascend' ? 'asc' : 'desc'
}
]
: [];
return {
sort: baseSort,
group: [],
select: [],
groupResultFlat: false
};
});
//
const handleSortChange = (info: {
field: string;
order: 'ascend' | 'descend' | null;
}) => {
sortInfo.value.field = info.field || '';
sortInfo.value.order = info.order || null;
// DOM
nextTick(() => {
handleSearch();
});
};
//
const customTransform = (res: any) => {
return {
records: res?.data?.data || [],
total: res?.data?.total || 0
};
};
//
const tableRef = ref();
//
const handleClose = () => {
emit('update:visible', false);
emit('close');
};
//
const handleSearch = () => {
const filters: any[] = [];
if (searchForm.stcd) {
filters.push({
field: 'stcd',
operator: 'eq',
value: searchForm.stcd
});
}
if (searchForm.name) {
filters.push({
field: 'name',
operator: 'eq',
value: searchForm.name
});
}
if (searchForm.year) {
const year = dayjs(searchForm.year).format('YYYY');
filters.push(
{
field: 'startTime',
operator: 'gte',
value: `${year}-01-01 00:00:00`
},
{
field: 'endTime',
operator: 'lte',
value: `${year}-12-31 23:59:59`
}
);
}
let filter = {
logic: 'and',
filters: filters.filter((f: any) => f.value !== undefined)
};
tableRef.value?.getList(filter);
};
//
const handleViewAttachment = (record: any) => {
// TODO:
console.log('查看附件:', record.fid);
currentAttachmentFid.value = record.fid;
attachmentModalVisible.value = true;
};
//
const resetSearch = (year: string) => {
searchForm.stcd = undefined;
searchForm.name = undefined;
searchForm.year = year ? dayjs(year) : dayjs();
};
//
defineExpose({
resetSearch
});
onMounted(async () => {
//
await Promise.all([fetchDeviceList(), fetchFishTypeList()]);
});
</script>
<style lang="scss" scoped>
.modal-content {
display: flex;
flex-direction: column;
height: 100%;
.search-form {
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.table-wrapper {
flex: 1;
overflow: hidden;
}
}
</style>

View File

@ -0,0 +1,177 @@
<template>
<SidePanelItem
title="鱼类调查装置概况"
:datetimePicker="datetimePicker"
@update-values="handlePanelChange"
>
<div v-loading="loading" class="gird">
<div v-for="item in statItems" :key="item.name" class="item" @click="handleCardClick">
<div class="item-icon">
<img :src="item.icon" alt="" />
</div>
<div class="item-name">{{ item.name }}</div>
<div class="item-value">
<span class="bold">{{ item.total }}</span>
<span class="unit">{{ item.unit }}</span>
</div>
</div>
</div>
</SidePanelItem>
<!-- 详情弹窗 -->
<DeviceTableModal
ref="deviceTableModalRef"
:visible="modalVisible"
:current-year="datetimePicker.value"
@update:visible="handleModalChange"
/>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import DeviceTableModal from './DeviceTableModal.vue';
import { getFprdDataInfo } from '@/api/yldc';
import fishShuIcon from '@/assets/images/fish_survey_shu.png';
import fishZhongIcon from '@/assets/images/fish_survey_zhong.png';
import fishLiangIcon from '@/assets/images/fish_survey_liang.png';
defineOptions({
name: 'SurveyQingKuang'
});
const BASE_ID = '07';
// ==================== ====================
const loading = ref(false);
const sourceData = ref<any>({});
const datetimePicker = ref({
show: true,
value: `${new Date().getFullYear()}`,
format: 'YYYY',
picker: 'year' as const,
options: []
});
const statItems = computed(() => [
{
icon: fishShuIcon,
name: '装置数量',
unit: '座',
total: sourceData.value?.fprdCount || 0
},
{
icon: fishZhongIcon,
name: '鱼种类',
unit: '种',
total: sourceData.value?.ftpCount || 0
},
{
icon: fishLiangIcon,
name: '鱼数量',
unit: '尾',
total: sourceData.value?.fishCount || 0
}
]);
//
const fetchStatData = async (year: string) => {
try {
loading.value = true;
const res = await getFprdDataInfo({
baseId: BASE_ID,
year
});
if (res && res.data) {
sourceData.value = res.data;
} else {
sourceData.value = {};
}
} catch (error) {
console.error('获取统计数据失败:', error);
sourceData.value = {};
} finally {
loading.value = false;
}
};
const handlePanelChange = (data: any) => {
if (data.datetime) {
datetimePicker.value.value = data.datetime;
fetchStatData(data.datetime);
}
};
// ==================== ====================
const modalVisible = ref(false);
const deviceTableModalRef = ref();
//
const handleCardClick = () => {
modalVisible.value = true;
};
//
const handleModalChange = (visible: boolean) => {
modalVisible.value = visible;
};
onMounted(() => {
//
fetchStatData(datetimePicker.value.value);
});
</script>
<style lang="scss" scoped>
.gird {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
height: 190px;
.item {
cursor: pointer;
flex: 1;
height: 100%;
border: 1px solid rgba(108, 164, 247, 0.35);
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
transition: all 0.3s;
&:hover {
border-color: rgba(108, 164, 247, 0.6);
box-shadow: 0 2px 8px rgba(108, 164, 247, 0.2);
}
.item-icon {
img {
width: 60px;
height: 60px;
}
}
.item-name {
font-size: 16px;
color: #333;
}
.item-value {
.bold {
color: #00aeff;
font-size: 22px;
font-weight: 500;
}
.unit {
font-size: 14px;
color: #666;
margin-left: 4px;
}
}
}
}
</style>

View File

@ -1,75 +1,111 @@
<!-- SidePanelItem.vue --> <!-- SidePanelItem.vue -->
<template> <template>
<SidePanelItem title="鱼类调查装置介绍"> <SidePanelItem title="鱼类调查装置介绍">
<div class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"> <a-spin :spinning="loading">
<!-- 跑马灯轨道容器 --> <template v-if="originalMediaData.length > 0">
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }" <div
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"> class="container"
<!-- 遍历所有媒体项包含克隆项 --> @mouseenter="handleMouseEnter"
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item"> @mouseleave="handleMouseLeave"
<!-- 图片 --> >
<img :src="item.url" alt="" /> <!-- 跑马灯轨道容器 -->
<!-- 说明文字随媒体项移动 --> <div
<div class="text">{{ item.text }}</div> class="carousel-track"
</div> :class="{ 'no-transition': isTransitioning }"
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
>
<!-- 遍历所有媒体项包含克隆项 -->
<div
v-for="(item, index) in renderMediaData"
:key="index"
class="carousel-item"
@click="handleItemClick"
>
<!-- 图片 -->
<img :src="item.url" alt="" />
<!-- 说明文字随媒体项移动 -->
<div class="text">{{ item.text }}</div>
</div> </div>
</div>
<!-- 面板指示器固定在底部右侧 --> <!-- 面板指示器固定在底部右侧 -->
<div class="pagination-dots-fixed"> <div class="pagination-dots-fixed">
<span <span
v-for="(dot, index) in originalMediaData" v-for="(dot, index) in originalMediaData"
:key="index" :key="index"
class="dot" class="dot"
:class="{ active: getCurrentRealIndex() === index }" :class="{ active: getCurrentRealIndex() === index }"
@click="goToSlide(index)" @click="goToSlide(index)"
></span> ></span>
</div> </div>
</div> </div>
<!-- 独立的文字说明区域随跑马灯切换而变化 --> <!-- 独立的文字说明区域随跑马灯切换而变化 -->
<div class="description-text"> <div class="description-text" @click="handleItemClick">
{{ currentDescription }} {{ currentDescription }}
</div> </div>
</SidePanelItem> </template>
<a-empty v-else description="暂无数据" />
</a-spin>
</SidePanelItem>
<!-- 设施详情弹框 -->
<a-modal v-model:open="modalVisible" :title="'设施类型介绍'" width="1536px" :footer="null">
<div v-if="currentItem">
<ArtsDetail :dataSource="currentItem" :index="getCurrentRealIndex()" />
</div>
</a-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'; import { ref, onMounted, onUnmounted, computed } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue'; import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { msstbprptGetKendoList } from '@/api/yldc';
import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue';
// 便 // 便
defineOptions({ defineOptions({
name: 'zengZhiZhanJieShaoMod' name: 'zengZhiZhanJieShaoMod'
}); });
// //
interface MediaItem { interface MediaItem {
type: 'image' | 'video'; type: 'image' | 'video';
url: string; url: string;
text: string; text: string;
description: string; description: string;
} }
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
//
const modalVisible = ref(false);
const currentItem = ref<any>(null);
//
const loading = ref(false);
// 3 // 3
const originalMediaData = ref<MediaItem[]>([ const originalMediaData = ref<MediaItem[]>([
{ {
type: 'image', type: 'image',
url: 'https://211.99.26.225:12125/?20230814205611342377136845462200&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920', url: 'https://211.99.26.225:12125/?20230814205611342377136845462200&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920',
text: '松岗鱼类增殖站', text: '松岗鱼类增殖站',
description: '松岗鱼类增殖放流站位于四川省阿坝藏族羌族自治州马尔康市松岗镇,主要服务于大渡河上游的双江口和金川两座水电站,同时还承担多种珍稀特有鱼类的救护和科研任务,实现工程建设与生态环境共同推进、相互促进。' description:
}, '松岗鱼类增殖放流站位于四川省阿坝藏族羌族自治州马尔康市松岗镇,主要服务于大渡河上游的双江口和金川两座水电站,同时还承担多种珍稀特有鱼类的救护和科研任务,实现工程建设与生态环境共同推进、相互促进。'
{ },
type: 'video', {
url: 'https://211.99.26.225:12125/?20230805205848575430105387253710&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920', // URL type: 'video',
text: '猴子岩鱼类增殖站', url: 'https://211.99.26.225:12125/?20230805205848575430105387253710&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920', // URL
description: '猴子岩水电站鱼类增殖放流站位于猴子岩水电站坝址下游约7.0km(业主营地下游约1.5km)大渡河左岸桃花渣场顶部平台上紧邻枢纽桃花大桥下游侧占地面积47.3亩其中一期工程27.0亩预留二期工程用地20.3亩(二期工程目前为丹巴、巴底水电站预留工程)增殖放流站工作流程为:亲鱼收集购买、亲鱼驯养培育、人工催产和授精、人工孵化、苗种培育和放流。 猴子岩鱼类增殖放流站近期放流对象中齐口裂腹鱼、重口裂腹鱼、大渡软刺裸裂尻鱼增殖放流技术水平已趋于熟化,中期放流对象大渡软刺裸裂尻鱼人工繁殖技术逐渐趋于熟化' text: '猴子岩鱼类增殖站',
}, description:
{ '猴子岩水电站鱼类增殖放流站位于猴子岩水电站坝址下游约7.0km(业主营地下游约1.5km)大渡河左岸桃花渣场顶部平台上紧邻枢纽桃花大桥下游侧占地面积47.3亩其中一期工程27.0亩预留二期工程用地20.3亩(二期工程目前为丹巴、巴底水电站预留工程)增殖放流站工作流程为:亲鱼收集购买、亲鱼驯养培育、人工催产和授精、人工孵化、苗种培育和放流。 猴子岩鱼类增殖放流站近期放流对象中齐口裂腹鱼、重口裂腹鱼、大渡软刺裸裂尻鱼增殖放流技术水平已趋于熟化,中期放流对象大渡软刺裸裂尻鱼人工繁殖技术逐渐趋于熟化'
type: 'image', },
url: 'https://211.99.26.225:12125/?20230805205924378504010675106305&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920', {
text: '黑马鱼类增殖站', type: 'image',
description: '大渡河黑马鱼类增殖放流站位于四川省甘洛县黑马乡黑马业主营地内距离甘洛县城45km,区域交通路况较好。主要承担瀑布沟、深溪沟、大岗山、枕头坝一级、沙坪二级等五座水电站鱼类增殖放流的重任。放流鱼类包含:齐口裂腹鱼、重口裂腹鱼、鲈鲤、长薄鳅、白甲鱼、中华倒刺、长吻脆、稀有鮊鲫、华鲮、侧沟爬岩鳅等10个种类共计约918.07万尾珍稀特有鱼苗。' url: 'https://211.99.26.225:12125/?20230805205924378504010675106305&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920',
} text: '黑马鱼类增殖站',
description:
'大渡河黑马鱼类增殖放流站位于四川省甘洛县黑马乡黑马业主营地内距离甘洛县城45km,区域交通路况较好。主要承担瀑布沟、深溪沟、大岗山、枕头坝一级、沙坪二级等五座水电站鱼类增殖放流的重任。放流鱼类包含:齐口裂腹鱼、重口裂腹鱼、鲈鲤、长薄鳅、白甲鱼、中华倒刺、长吻脆、稀有鮊鲫、华鲮、侧沟爬岩鳅等10个种类共计约918.07万尾珍稀特有鱼苗。'
}
]); ]);
// //
@ -90,197 +126,259 @@ const isTransitioning = ref(false);
// //
const initRenderData = () => { const initRenderData = () => {
const length = originalMediaData.value.length; const length = originalMediaData.value.length;
if (length === 0) return; if (length === 0) return;
renderMediaData.value = [ renderMediaData.value = [
originalMediaData.value[length - 1], // originalMediaData.value[length - 1], //
...originalMediaData.value, // ...originalMediaData.value, //
originalMediaData.value[0] // originalMediaData.value[0] //
]; ];
}; };
// //
const startAutoPlay = () => { const startAutoPlay = () => {
if (timer) clearInterval(timer); if (timer) clearInterval(timer);
timer = setInterval(() => { timer = setInterval(() => {
if (!isHovering.value && !isTransitioning.value) { if (!isHovering.value && !isTransitioning.value) {
nextSlide(); nextSlide();
} }
}, 4000); }, 4000);
}; };
// //
const nextSlide = () => { const nextSlide = () => {
currentIndex.value++; currentIndex.value++;
// //
setTimeout(() => { setTimeout(() => {
checkSeamlessJump(); checkSeamlessJump();
}, 500); // transition }, 500); // transition
}; };
// //
const checkSeamlessJump = () => { const checkSeamlessJump = () => {
const realLength = originalMediaData.value.length; const realLength = originalMediaData.value.length;
// = realLength + 1 // = realLength + 1
if (currentIndex.value >= realLength + 1) { if (currentIndex.value >= realLength + 1) {
// 1. // 1.
isTransitioning.value = true; isTransitioning.value = true;
// 2. 1 // 2. 1
currentIndex.value = 1; currentIndex.value = 1;
// 3. DOM // 3. DOM
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
isTransitioning.value = false; isTransitioning.value = false;
}); });
}); });
} }
}; };
// //
const handleMouseEnter = () => { const handleMouseEnter = () => {
isHovering.value = true; isHovering.value = true;
}; };
// //
const handleMouseLeave = () => { const handleMouseLeave = () => {
isHovering.value = false; isHovering.value = false;
}; };
// //
const currentDescription = computed(() => { const currentDescription = computed(() => {
const realIndex = getCurrentRealIndex(); const realIndex = getCurrentRealIndex();
return originalMediaData.value[realIndex]?.description || ''; return originalMediaData.value[realIndex]?.description || '';
}); });
const getData = async () => {
loading.value = true;
try {
let params = {
filter: {
logic: 'and',
filters: [
{
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: 'FPRD'
},
{
field: 'isDeleted',
operator: 'eq',
dataType: 'string',
value: 0
},
{
field: 'logo',
operator: 'isnotnull'
},
{
field: 'introduce',
operator: 'isnotnull'
},
{
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: '07'
}
]
},
select: ['stcd', 'introduce', 'stnm', 'logo']
};
let res = await msstbprptGetKendoList(params);
let data = res?.data?.data;
originalMediaData.value = data.map((item: any) => {
return {
url: baseUrl + '?' + item.logo + '&view=jpg',
text: item.stnm,
description: item.introduce,
sctd: item.sctd
};
});
} finally {
loading.value = false;
}
};
// //
onMounted(() => { onMounted(async () => {
initRenderData(); await getData();
startAutoPlay(); initRenderData();
startAutoPlay();
}); });
// //
onUnmounted(() => { onUnmounted(() => {
if (timer) clearInterval(timer); if (timer) clearInterval(timer);
}); });
// //
const getCurrentRealIndex = () => { const getCurrentRealIndex = () => {
const realLength = originalMediaData.value.length; const realLength = originalMediaData.value.length;
let realIndex = currentIndex.value - 1; // let realIndex = currentIndex.value - 1; //
// //
if (realIndex < 0) realIndex = realLength - 1; if (realIndex < 0) realIndex = realLength - 1;
if (realIndex >= realLength) realIndex = 0; if (realIndex >= realLength) realIndex = 0;
return realIndex; return realIndex;
}; };
// //
const goToSlide = (targetIndex: number) => { const goToSlide = (targetIndex: number) => {
if (isTransitioning.value) return; if (isTransitioning.value) return;
// //
currentIndex.value = targetIndex + 1; currentIndex.value = targetIndex + 1;
};
//
const handleItemClick = () => {
currentItem.value = originalMediaData.value.map((item: any) => ({
url: item.url || '',
description: item.description || '',
title: item.text || ''
}));
modalVisible.value = true;
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
width: 100%; width: 100%;
height: 228px; height: 228px;
// border: 1px solid #7fd6ff; // border: 1px solid #7fd6ff;
// border-radius: 5px; // border-radius: 5px;
position: relative; position: relative;
overflow: hidden; // overflow: hidden; //
// //
.carousel-track { .carousel-track {
display: flex; // display: flex; //
width: 100%;
height: 100%;
transition: transform 0.5s ease-in-out; // 0.5
//
&.no-transition {
transition: none;
}
//
.carousel-item {
min-width: 100%; //
height: 100%;
position: relative;
flex-shrink: 0; //
cursor: pointer;
img,
video {
width: 100%; width: 100%;
height: 100%; height: 100%;
transition: transform 0.5s ease-in-out; // 0.5 object-fit: cover; //
}
// .text {
&.no-transition {
transition: none;
}
//
.carousel-item {
min-width: 100%; //
height: 100%;
position: relative;
flex-shrink: 0; //
img,
video {
width: 100%;
height: 100%;
object-fit: cover; //
}
.text {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 22px;
line-height: 22px;
background: rgba(0, 0, 0, 0.1);
color: #fff;
padding-left: 10px;
}
}
}
//
.pagination-dots-fixed {
position: absolute; position: absolute;
bottom: 10px; bottom: 0;
right: 10px; left: 0;
display: flex; width: 100%;
gap: 6px; height: 22px;
z-index: 10; // line-height: 22px;
background: rgba(0, 0, 0, 0.1);
.dot { color: #fff;
width: 5px; padding-left: 10px;
height: 5px; }
border-radius: 50%;
background-color: #D8D8D8;
cursor: pointer;
transition: background-color 0.3s ease;
&.active {
background-color: #005293;
}
&:hover {
opacity: 0.8;
}
}
} }
}
//
.pagination-dots-fixed {
position: absolute;
bottom: 10px;
right: 10px;
display: flex;
gap: 6px;
z-index: 10; //
.dot {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #d8d8d8;
cursor: pointer;
transition: background-color 0.3s ease;
&.active {
background-color: #005293;
}
&:hover {
opacity: 0.8;
}
}
}
} }
// //
.description-text { .description-text {
// padding: 8px 12px; // padding: 8px 12px;
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
// min-height: 40px; // min-height: 40px;
transition: all 0.3s ease; transition: all 0.3s ease;
margin-bottom: 20px; margin-bottom: 20px;
// //
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
word-break: break-word; word-break: break-word;
} }
</style> </style>

View File

@ -308,7 +308,7 @@ const buildFilter = () => {
// //
const handleSortChange = (info: { field: string; order: string | null }) => { const handleSortChange = (info: { field: string; order: string | null }) => {
// debugger //
if (info.field && info.order) { if (info.field && info.order) {
sortState.value = info; sortState.value = info;
} else { } else {
@ -356,6 +356,8 @@ const handleViewDetail = (record: any, type: string) => {
modelStore.params.sttp = lastValue modelStore.params.sttp = lastValue
modelStore.title = record.stnm; modelStore.title = record.stnm;
modelStore.params.stcd = record.stcd; modelStore.params.stcd = record.stcd;
modelStore.params.bldsttCcode = record.bldsttCcode;
//
} }
// console.log('', record) // console.log('', record)
}; };

View File

@ -609,13 +609,13 @@ const getselsectData = async () => {
value: item.stcd value: item.stcd
}; };
}); });
// debugger //
}; };
const convertDateRange = (dateRange: string[]) => { const convertDateRange = (dateRange: string[]) => {
if (!dateRange || dateRange.length !== 2) { if (!dateRange || dateRange.length !== 2) {
return null; return null;
} }
// debugger //
const [startStr, endStr] = dateRange; const [startStr, endStr] = dateRange;

View File

@ -514,7 +514,7 @@ const convertDateRange = (dateRange: string[]) => {
if (!dateRange || dateRange.length !== 2) { if (!dateRange || dateRange.length !== 2) {
return null; return null;
} }
// debugger //
const [startStr, endStr] = dateRange; const [startStr, endStr] = dateRange;
@ -634,7 +634,7 @@ const getselsectData = async () => {
value: item.stcd value: item.stcd
}; };
}); });
// debugger //
}; };
//// ////
watch( watch(

View File

@ -170,7 +170,7 @@ const chartRef = ref<HTMLDivElement>();
// //
const handleChartClick = (params: any) => { const handleChartClick = (params: any) => {
// debugger //
console.log('点击数据点:', params); console.log('点击数据点:', params);
const { name, value, data } = params; const { name, value, data } = params;
modelStore.modalVisible = true; modelStore.modalVisible = true;

View File

@ -404,7 +404,7 @@ const fetchData = async () => {
}); });
data.value = processedData; data.value = processedData;
// debugger //
// X // X
echartsXData.value = [ echartsXData.value = [
...new Set(processedData.map(item => item.basename)) ...new Set(processedData.map(item => item.basename))

View File

@ -37,6 +37,7 @@
:allow-clear="false" :allow-clear="false"
format="YYYY-MM-DD" format="YYYY-MM-DD"
style="width: 260px" style="width: 260px"
:presets="DateSetting.RangeButton.days1"
/> />
</a-form-item> </a-form-item>
@ -103,7 +104,7 @@ import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { dayGetKendoListCust, hourGetKendoListCust } from '@/api/stll'; import { dayGetKendoListCust, hourGetKendoListCust } from '@/api/stll';
import BasicTable from '@/components/BasicTable/index.vue'; import BasicTable from '@/components/BasicTable/index.vue';
import { useModelStore } from '@/store/modules/model'; import { useModelStore } from '@/store/modules/model';
import { DateSetting } from '@/utils/enumeration';
const modelStore = useModelStore(); const modelStore = useModelStore();
const props = defineProps<{ const props = defineProps<{
options?: any[]; options?: any[];

View File

@ -244,7 +244,7 @@ const getData = async () => {
// API // API
if (res.data && res.data.list) { if (res.data && res.data.list) {
const apiList = res.data.list; const apiList = res.data.list;
// debugger //
// 使type: 1 type: 2 // 使type: 1 type: 2
if (apiList && apiList.length > 0) { if (apiList && apiList.length > 0) {
dataArr.value = apiList.slice(0, 2).map((item: any) => ({ dataArr.value = apiList.slice(0, 2).map((item: any) => ({
@ -311,7 +311,7 @@ const getcont = async () => {
if (list) { if (list) {
titleData.value = list titleData.value = list
} }
// debugger //
}; };
// //
onMounted(() => { onMounted(() => {

View File

@ -307,7 +307,7 @@ const initChart = () => {
const handleChartClick = (params: any) => { const handleChartClick = (params: any) => {
const categoryName = params.name; const categoryName = params.name;
const currentIndex = params.dataIndex; const currentIndex = params.dataIndex;
// debugger //
// //
detailModalTitle.value = detailModalTitle.value =
@ -318,7 +318,7 @@ const handleChartClick = (params: any) => {
// //
const baseId = mode.value === 'top' ? chartData.value.baseIds[currentIndex] : undefined; const baseId = mode.value === 'top' ? chartData.value.baseIds[currentIndex] : undefined;
const qecPerformance = mode.value === 'left' ? chartData.value.qecPerformances[currentIndex] : undefined; const qecPerformance = mode.value === 'left' ? chartData.value.qecPerformances[currentIndex] : undefined;
// debugger //
const currentItem = { const currentItem = {
baseId: baseId, baseId: baseId,
@ -505,7 +505,7 @@ const getcont = async () => {
if (list) { if (list) {
titleData.value = list; titleData.value = list;
} }
// debugger //
}; };
// //
onMounted(() => { onMounted(() => {

View File

@ -224,6 +224,7 @@ const getselectData = async () => {
// //
select.value.options = [...dataMapNameArr, ...otherArr]; select.value.options = [...dataMapNameArr, ...otherArr];
//
// if (baseid.value == 'all') { // if (baseid.value == 'all') {
// select.value.value = '008640202300001021' // select.value.value = '008640202300001021'
@ -536,23 +537,23 @@ onBeforeUnmount(() => {
// //
const handlePanelChange1 = (data: any, type: string) => { const handlePanelChange1 = (data: any, type: string) => {
console.log('当前所有控件状态:', data); console.log('当前所有控件状态:', data);
if (type == 'click') { //
if (data.moreSelect || data.datetime) {
select.value.value = data.moreSelect;
datetimePicker.value.value = data.datetime;
//
getshuiwenList();
}
if (data?.selectedNodeExtra?.lttd && data?.selectedNodeExtra?.lgtd) {
const mapClass = MapClass.getInstance(); const mapClass = MapClass.getInstance();
mapClass.flyTopanto( mapClass.flyTopanto(
[ [
Number(data.selectedNodeExtra.lgtd), Number(data.selectedNodeExtra.lgtd),
Number(data.selectedNodeExtra.lttd) Number(data.selectedNodeExtra.lttd)
], ],
15 15
); );
} }
//
if (data.moreSelect || data.datetime) {
select.value.value = data.moreSelect;
datetimePicker.value.value = data.datetime;
// debugger
getshuiwenList();
}
}; };
const baseid = ref(''); const baseid = ref('');
watch( watch(

View File

@ -208,7 +208,7 @@ const getDataList = async () => {
} }
// const params = [data1, data2] // const params = [data1, data2]
const res1 = await msstbprptGetKendoList(data1); const res1 = await msstbprptGetKendoList(data1);
// debugger //
let finalData = facilities.value.map((item) => ({ ...item, count: 0 })) let finalData = facilities.value.map((item) => ({ ...item, count: 0 }))
if (res1.data) { if (res1.data) {
const { data } = res1 const { data } = res1
@ -220,7 +220,7 @@ const getDataList = async () => {
}) })
} }
console.log(finalData) console.log(finalData)
// debugger/ // /
const res2 = await msstbprptGetKendoList(data2); const res2 = await msstbprptGetKendoList(data2);
if (res2.data) { if (res2.data) {
const responseData = res2?.data?.data const responseData = res2?.data?.data
@ -231,7 +231,7 @@ const getDataList = async () => {
} }
facilities.value = finalData facilities.value = finalData
console.log(finalData) console.log(finalData)
// debugger //
} catch (error) { } catch (error) {
console.error('获取数据失败:', error); console.error('获取数据失败:', error);
} finally { } finally {

View File

@ -166,7 +166,7 @@ const getSelect = async () => {
} }
if (data && data.length > 0) { if (data && data.length > 0) {
// debugger; // ;
select.value.value = data[0].wbsCode; select.value.value = data[0].wbsCode;
selectData.value = data[0].wbsCode; // selectData.value = data[0].wbsCode; //
select.value.options = data.map(item => ({ select.value.options = data.map(item => ({

View File

@ -34,6 +34,7 @@ export const useModelStore = defineStore('model', () => {
sttpMap?: string; // 新增:站点类型映射 sttpMap?: string; // 新增:站点类型映射
dtinType?: string; dtinType?: string;
bldstt: string; bldstt: string;
bldsttCcode: string;
}>({ }>({
sttp: 'eng', sttp: 'eng',
stcd: '', stcd: '',
@ -45,7 +46,8 @@ export const useModelStore = defineStore('model', () => {
sttpCode: '', sttpCode: '',
sttpMap: '', sttpMap: '',
dtinType: '', dtinType: '',
bldstt: '' bldstt: '',
bldsttCcode:''
}); });
const filter = ref({ const filter = ref({
stllgzlx: '', // 生态流量 - 规则类型 stllgzlx: '', // 生态流量 - 规则类型

View File

@ -1,9 +1,73 @@
<script setup lang="ts"> <script setup lang="ts">
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue"; import { ref, onMounted, watch } from 'vue';
import RightDrawer from "@/components/RightDrawer/index.vue"; import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
import ZhuanZhiIntroduce from '@/modules/fishSurvey/ZhuanZhiIntroduce/index.vue' import RightDrawer from '@/components/RightDrawer/index.vue';
import FishIntroduce from '@/modules/fishSurvey/FishIntroduce/index.vue' import ZhuanZhiIntroduce from '@/modules/fishSurvey/ZhuanZhiIntroduce/index.vue';
import FishVideo from '@/modules/fishSurvey/FishVideo/index.vue' import FishIntroduce from '@/modules/fishSurvey/FishIntroduce/index.vue';
import FishVideo from '@/modules/fishSurvey/FishVideo/index.vue';
import SurveyQingKuang from '@/modules/fishSurvey/SurveyQingKuang/index.vue'; //<!-- -->
import FishChangeTu from '@/modules/fishSurvey/FishChangeTu/index.vue'
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { useUiStore } from '@/store/modules/ui';
const uiStore = useUiStore();
const JidiSelectEventStore = useJidiSelectEventStore();
const baseid = ref('');
//
const fixedPRef = ref<HTMLElement | null>(null);
let isDragging = false;
let startX = 0;
let startY = 0;
let initialLeft = 0;
let initialTop = 0;
const onMouseDown = (e: MouseEvent) => {
;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = fixedPRef.value?.getBoundingClientRect();
initialLeft = rect?.left || 0;
initialTop = rect?.top || 0;
fixedPRef.value!.style.cursor = 'move';
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
const onMouseMove = (e: MouseEvent) => {
if (!isDragging || !fixedPRef.value) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
fixedPRef.value.style.left = `${initialLeft + dx}px`;
fixedPRef.value.style.top = `${initialTop + dy}px`;
fixedPRef.value.style.bottom = 'auto';
};
const onMouseUp = () => {
isDragging = false;
fixedPRef.value!.style.cursor = 'default';
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
watch(
() => JidiSelectEventStore.selectedItem,
newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
if (newVal.wbsCode == '07') {
uiStore.setDrawerOpen(true);
} else {
uiStore.setDrawerOpen(false);
}
}
},
{ deep: true, immediate: true }
);
onMounted(() => {
JidiSelectEventStore.updataJidiData(11);
});
</script> </script>
<template> <template>
@ -12,12 +76,40 @@ import FishVideo from '@/modules/fishSurvey/FishVideo/index.vue'
<JidiSelectorMod /> <JidiSelectorMod />
</div> </div>
<div class="rightContent"> <div class="rightContent">
<RightDrawer> <RightDrawer v-if="baseid == '07'">
<ZhuanZhiIntroduce /> <ZhuanZhiIntroduce />
<FishIntroduce /> <FishIntroduce />
<FishVideo /> <FishVideo />
</RightDrawer> </RightDrawer>
</div> </div>
<div class="fixedP" ref="fixedPRef">
<div class="zhuai" @mousedown="onMouseDown"></div>
<SurveyQingKuang />
<FishChangeTu />
</div>
</div> </div>
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss">
.fixedP {
position: fixed;
bottom: 20px;
left: 220px;
width: 450px;
z-index: 999;
pointer-events: all;
padding: 0px 16px;
border: 2px solid rgb(197, 214, 226);
box-shadow: rgb(229, 237, 243) 0px 0px 3px 6px;
.zhuai {
width: 100%;
height: 16px;
cursor: grab;
cursor: grab;
user-select: none;
background-color: rgba(0, 0, 0, 0.5);
}
.zhuai:active {
cursor: grabbing;
}
}
</style>

View File

@ -14,6 +14,13 @@
</div> </div>
</div> </div>
<div :id="playerId" class="video-player"></div> <div :id="playerId" class="video-player"></div>
<!-- 自定义错误提示 -->
<div v-if="showError" class="custom-error-overlay">
<div class="error-content">
<i class="iconfont icon-jiankong error-icon"></i>
<div class="error-text">数据源错误无法加载视频</div>
</div>
</div>
</div> </div>
<!-- 无视频时显示占位 --> <!-- 无视频时显示占位 -->
@ -31,6 +38,9 @@ import videojs from 'video.js';
import 'video.js/dist/video-js.css'; import 'video.js/dist/video-js.css';
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL; const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
//
const showError = ref(false);
// HLS video.js 8.x HLS // HLS video.js 8.x HLS
interface VideoData { interface VideoData {
@ -89,6 +99,11 @@ const initPlayer = () => {
player.dispose(); player.dispose();
player = null; player = null;
} }
//
let retryCount = 0;
const maxRetries = 5;
try { try {
// video // video
const videoElement = document.createElement('video'); const videoElement = document.createElement('video');
@ -195,36 +210,55 @@ const initPlayer = () => {
const error = player.error(); const error = player.error();
console.error('播放器错误:', error); console.error('播放器错误:', error);
// //
// if (retryCount >= maxRetries) {
let retryCount = 0; console.warn(`已达到最大重试次数(${maxRetries}次),停止重试`);
const maxRetries = 10; //
showError.value = true;
return;
}
const attemptReload = () => { //
if (retryCount < maxRetries && player && videoUrl.value) { retryCount++;
retryCount++; console.log(`视频加载失败,正在进行第 ${retryCount}/${maxRetries} 次重试...`);
setTimeout(() => {
try { //
player.src({ showError.value = true;
src: videoUrl.value,
type: videoType // //
}); setTimeout(() => {
player.load(); try {
player.play(); if (player && videoUrl.value) {
} catch (e) { //
if (retryCount < maxRetries) { showError.value = false;
attemptReload(); player.src({
src: videoUrl.value,
type: videoType
});
player.load();
player.play();
}
} catch (e) {
console.error('重试加载视频失败:', e);
//
if (retryCount < maxRetries) {
setTimeout(() => {
if (player) {
player.error(); //
} }
} }, 100);
}, 3000); }
} }
}; }, 3000);
attemptReload();
}); });
// //
player.on('loadeddata', () => {}); player.on('loadeddata', () => {
//
retryCount = 0;
//
showError.value = false;
});
// //
player.on('play', () => {}); player.on('play', () => {});
@ -339,6 +373,40 @@ onUnmounted(() => {
} }
} }
.custom-error-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
.error-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.error-icon {
font-size: 48px;
margin-bottom: 12px;
opacity: 0.5;
color: #999;
}
.error-text {
font-size: 14px;
color: #fff;
text-align: center;
margin-top: 12px;
}
}
}
.video-empty { .video-empty {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -12,7 +12,7 @@ watch(
() => JidiSelectEventStore.selectedItem, () => JidiSelectEventStore.selectedItem,
(newVal) => { (newVal) => {
console.log(newVal); console.log(newVal);
// debugger //
wbsCode.value = newVal.wbsCode; wbsCode.value = newVal.wbsCode;
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }