添加栖息地弹框,修改批复文件显示多个

This commit is contained in:
扈兆增 2026-06-10 15:45:16 +08:00
parent cc8c1e0647
commit 34502b7ed2
18 changed files with 7774 additions and 4450 deletions

View File

@ -1,6 +1,12 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { LoginData, TokenResult, VerifyCode } from './types';
export function getAccessToken() {
return request({
url: '/register/accessToken',
method: 'get'
});
}
/**
*
@ -52,8 +58,11 @@ export function registerUser(data: any): AxiosPromise<any> {
*/
export function getBasinList(): AxiosPromise<any[]> {
return request({
url: '/env/rvcd/regDropdown',
url: '/register/dropdown/rvcd',
method: 'get',
headers: {
'register-token': localStorage.getItem('register-token') || ''
}
// params: baseid ? { baseid } : {}
});
}
@ -63,9 +72,12 @@ export function getBasinList(): AxiosPromise<any[]> {
*/
export function getGroupList(): AxiosPromise<any[]> {
return request({
url: '/env/hycd/regDropdown',
url: '/register/dropdown/hycd',
method: 'get',
params:{lx:1}
params: { lx: 1 },
headers: {
'register-token': localStorage.getItem('register-token') || ''
}
});
}
@ -74,20 +86,26 @@ export function getGroupList(): AxiosPromise<any[]> {
*/
export function getCompanyList(): AxiosPromise<any[]> {
return request({
url: '/env/hycd/regDropdown',
url: '/register/dropdown/hycd',
method: 'get',
params: { lx:2 }
params: { lx: 2 },
headers: {
'register-token': localStorage.getItem('register-token') || ''
}
});
}
/**
*
*/
export function getStationList(params:any): AxiosPromise<any[]> {
export function getStationList(params: any): AxiosPromise<any[]> {
return request({
url: '/env/engInfo/regDropdown',
url: '/register/dropdown/engInfo',
method: 'post',
data: params
data: params,
headers: {
'register-token': localStorage.getItem('register-token') || ''
}
});
}
@ -110,7 +128,11 @@ export function sendSmsCode(phone: string, type = 1): AxiosPromise<any> {
* @param code
* @param type 1- 2-
*/
export function verifySmsCode(phone: string, code: string, type = 1): AxiosPromise<any> {
export function verifySmsCode(
phone: string,
code: string,
type = 1
): AxiosPromise<any> {
return request({
url: '/sms/verifyCode',
method: 'post',
@ -123,7 +145,10 @@ export function verifySmsCode(phone: string, code: string, type = 1): AxiosPromi
* @param phone
* @param code
*/
export function smsLoginApi(phone: string, code: string): AxiosPromise<TokenResult> {
export function smsLoginApi(
phone: string,
code: string
): AxiosPromise<TokenResult> {
return request({
url: '/sms/login',
method: 'post',
@ -137,7 +162,11 @@ export function smsLoginApi(phone: string, code: string): AxiosPromise<TokenResu
* @param code
* @param password
*/
export function resetPassword(phone: string, code: string, password: string): AxiosPromise<any> {
export function resetPassword(
phone: string,
code: string,
password: string
): AxiosPromise<any> {
return request({
url: '/sms/resetPassword',
method: 'post',

View File

@ -62,10 +62,33 @@ export function getMonitorData(data: any) {
data
});
}
// 监测数据 水温查询 栖息地
export function getMonitorDataWt(data: any) {
return request({
url: '/wt/msstbprpt/GetKendoList',
method: 'post',
data
});
}
export function getMonitorDataWq(data: any) {
return request({
url: '/wq/msstbprpt/GetKendoList',
method: 'post',
data
});
}
export function getMonitorDataZq(data: any) {
return request({
url: '/zq/msstbprpt/GetKendoList',
method: 'post',
data
});
}
// 监测数据 - 水温
export function getMonitorDataWaterTemp(data: any) {
return request({
url: '/api/wmp-env-server/sw/alongDetail/qgc/GetKendoListCust',
url: '/wt/alongDetail/qgc/GetKendoListCust',
method: 'post',
data
});
@ -73,7 +96,7 @@ export function getMonitorDataWaterTemp(data: any) {
// 监测数据 - 水温 - 查询显示 综合/等温
export function getMonitorDataWaterTempPowerStation(params: any) {
return request({
url: '/api/wmp-env-server/sw/wtrv/getIoWtrvFlag',
url: '/wt/wtrv/getIoWtrvFlag',
method: 'get',
params
});
@ -81,7 +104,7 @@ export function getMonitorDataWaterTempPowerStation(params: any) {
// 监测数据 - 水温 - 、综合分析导出数据
export function exportMonitorDataWaterTempPowerStation(params: any) {
return request({
url: '/api/wmp-env-server/sw/alongDetail/qgc/GetKendoListCust',
url: '/wt/alongDetail/qgc/GetKendoListCust',
method: 'post',
params,
responseType: 'blob'
@ -91,32 +114,55 @@ export function exportMonitorDataWaterTempPowerStation(params: any) {
// 监测数据 - 水温 - 查询是否显示 等温水深
export function getMonitorDataWaterTempPowerStation2(params: any) {
return request({
url: '/api/wmp-env-server/sw/alongDetail/qgc/stcdCheck2',
url: '/wt/alongDetail/qgc/stcdCheck2',
method: 'get',
params
});
}
// 监测数据 - 垂向水温
export function getMonitorDataWaterTempVertical(data: any) {
return request({
url: '/api/wmp-env-server/sw/cxDetail/GetKendoListCust',
url: '/wt/cxDetail/GetKendoListCust',
method: 'post',
data
});
}
// 监测数据 - 水质 判断是否显示综合分析
export function getMonitorDataWaterQuality(data: any) {
return request({
url: '/wq/vmsstbprpt/GetKendoList',
method: 'post',
data
});
}
// 监测数据 - 水质 - 获取水质参数
export function getMonitorDataWaterQualityDetail(data: any) {
return request({
url: '/wq/stbprp/GetStbprpYsByStcd',
method: 'get',
params: data
});
}
// 监测数据 - 水质 - 获取水质数据
export function getMonitorDataWaterQualityList(data: any) {
return request({
url: '/wq/data/noAuth/GetKendoListCust',
method: 'post',
data
});
}
// 生态流量 - 限制范围查询 - 日
export function getEngLimitDay(data: any) {
export function getMonitorDataWaterTempVerticalDay(data: any) {
return request({
url: '/eq/interval/qgc/day/GetKendoListCust',
url: '/eq/data/day/GetKendoListCust',
method: 'post',
data
});
}
// 生态流量 - 限制范围查询 - 月
export function getEngLimit(data: any) {
// 生态流量 - 限制范围查询 - 小时
export function getMonitorDataWaterTempVerticalHour(data: any) {
return request({
url: '/eq/interval/qgc/month/GetKendoListCust',
url: '/eq/data/GetKendoListCust',
method: 'post',
data
});

File diff suppressed because it is too large Load Diff

View File

@ -143,13 +143,29 @@ const loadApprovalData = () => {
loading.value = true;
queryPostUrlList(props.url, params).then(res => {
const data = res.data?.data || [];
const fileArray = data.map((item: any) => ({
id: item.flpath.split('?')[1] || '',
name: item.approvalFileNo || item.approvalFileTitle,
url: item.flpath || ''
}));
hasLoaded.value = true;
updateFileList(fileArray);
if (data[0].flid == null) {
const fileArray = data.map((item: any) => ({
id: item.flpath.split('?')[1] || '',
name: item.approvalFileNo || item.approvalFileTitle,
url: item.flpath || ''
}));
hasLoaded.value = true;
updateFileList(fileArray);
} else {
getIdUrl({
id: data[0].flid
}).then(res1 => {
const data = res1?.data || {};
const fileArray = Object.keys(data).map(key => ({
id: key,
name: data[key].name,
url: data[key].fullpath
}));
loading.value = false;
hasLoaded.value = true;
updateFileList(fileArray);
});
}
});
};

View File

@ -39,7 +39,15 @@
</a-tooltip>
</div>
<div class="col-right" v-if="item.visible">
<a-tooltip :title="!isEdit ? item.vlsr : ''">
<a-tooltip
:title="
!isEdit
? item.vlsr || String(item.value).length > 10
? item.value
: ''
: ''
"
>
<span v-if="!isEdit && item.filed != ''">{{
item.value || '-'
}}</span>
@ -250,6 +258,10 @@ const columnsConfig = ref([
{
type: 'DW_1',
columns: DwOnePointColumns
},
{
type: 'ZQ',
columns: FhZQPointColumns
}
//DW_1

View File

@ -45,9 +45,15 @@
</div>
<!-- 底部图表区 -->
<div class="bottom-section">
<a-spin :spinning="isChartLoading" tip="加载中...">
<a-spin v-show="hasData" :spinning="isChartLoading" tip="加载中...">
<div class="chart-wrapper" ref="chartRef"></div>
</a-spin>
<a-empty
v-show="!hasData && !isChartLoading"
description="暂无数据"
class="empty-wrapper h-full flex items-center justify-center"
style="flex-direction: column"
/>
</div>
</div>
</template>
@ -55,7 +61,10 @@
<script lang="ts" setup>
import { ref, onMounted, computed, watch, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import { getEngLimit, getEngLimitDay } from '@/api/mapModal';
import {
getMonitorDataWaterTempVerticalDay,
getMonitorDataWaterTempVerticalHour
} from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
import { DateSetting } from '@/utils/enumeration';
import dayjs from 'dayjs';
@ -74,6 +83,7 @@ const props = defineProps({
});
const hasLoaded = ref<boolean>(false); //
const isChartLoading = ref<boolean>(false);
const hasData = ref<boolean>(true); //
const unqualifiedCount = ref<number>(0);
const complianceRate = ref<string>('');
const limitInfo = ref<string>('');
@ -131,7 +141,7 @@ const fetchLimitInfo = async () => {
const stcd = modelStore.params.stcd;
const { startTime, endTime } = getDateRange();
const res = await getEngLimit({
const res = await getMonitorDataWaterTempVerticalHour({
filter: {
logic: 'and',
filters: [
@ -165,7 +175,7 @@ const fetchDayData = async () => {
const stcd = modelStore.params.stcd;
const { startTime, endTime } = getDateRange();
const res = await getEngLimitDay({
const res = await getMonitorDataWaterTempVerticalDay({
filter: {
logic: 'and',
filters: [
@ -206,27 +216,6 @@ const fetchDayData = async () => {
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
//
const getMockData = () => {
const now = dayjs();
const data: any[] = [];
const count = timeType.value === 'hour' ? 48 : 30; // 4830
for (let i = count - 1; i >= 0; i--) {
const tm =
timeType.value === 'hour'
? now.subtract(i, 'hour').format('YYYY-MM-DD HH:mm:ss')
: now.subtract(i, 'day').format('YYYY-MM-DD HH:mm:ss');
data.push({
tm,
qi: Math.round(Math.random() * 100 + 150), // 150-250
qec: Math.round(Math.random() * 50 + 100), // 100-150
qecLimit: 145, //
qecSameLastYear: Math.round(Math.random() * 60 + 90) // 90-150
});
}
return data;
};
//
const updateChart = (data: any[]) => {
if (!chartInstance) return;
@ -241,18 +230,22 @@ const updateChart = (data: any[]) => {
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
);
const xAxisData = sorted.map(item => dayjs(item.tm).format('MM-DD HH:mm'));
const xAxisData = sorted.map(item =>
dayjs(timeType.value === 'hour' ? item.tm : item.dt).format(
timeType.value === 'hour' ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD'
)
);
const qiData = sorted.map(item => item.qi);
const qecData = sorted.map(item => item.qec);
const qecLimitData = sorted.map(item => item.qecLimit);
const qecSameLastYearData = sorted.map(item => item.qecSameLastYear);
const beforeQecData = sorted.map(item => item.beforeQec);
// Y
const flowValues = [
...qiData,
...qecData,
...qecLimitData,
...qecSameLastYearData
...beforeQecData
].filter(v => v !== null && v !== undefined);
const flowMax = flowValues.length > 0 ? Math.max(...flowValues) : 200;
const flowYMax = Math.ceil(flowMax * 1.1);
@ -267,9 +260,19 @@ const updateChart = (data: any[]) => {
formatter: (params: any) => {
if (!params || params.length === 0) return '';
const dataIndex = params[0].dataIndex;
const fullTime = sorted[dataIndex]?.tm
? dayjs(sorted[dataIndex].tm).format('YYYY-MM-DD HH:mm:ss')
: '';
console.log(sorted[dataIndex]);
const fullTime =
timeType.value === 'hour'
? sorted[dataIndex]?.tm
: sorted[dataIndex]?.dt
? dayjs(
timeType.value === 'hour'
? sorted[dataIndex].tm
: sorted[dataIndex].dt
).format(
timeType.value === 'hour' ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD'
)
: '';
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
//
const formatRules: Record<
@ -369,7 +372,7 @@ const updateChart = (data: any[]) => {
{
name: '去年同期生态流量',
type: 'line',
data: qecSameLastYearData,
data: beforeQecData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
@ -405,7 +408,9 @@ const initChart = () => {
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
nextTick(() => {
chartInstance = echarts.init(chartRef.value);
});
};
const handleResize = () => {
@ -422,22 +427,45 @@ const destroyChart = () => {
window.removeEventListener('resize', handleResize);
};
//
//
const fetchChartData = async () => {
isChartLoading.value = true;
try {
// TODO:
// const stcd = modelStore.params.stcd;
// const { startTime, endTime } = getDateRange();
// const res = await getEngChartList({ ... });
// updateChart(res?.data?.data || []);
const stcd = modelStore.params.stcd;
const { startTime, endTime } = getDateRange();
// 2 loading
await new Promise(resolve => setTimeout(resolve, 2000));
const apiFn =
timeType.value === 'day'
? getMonitorDataWaterTempVerticalDay
: getMonitorDataWaterTempVerticalHour;
// 使
const mockData = getMockData();
updateChart(mockData);
const res = await apiFn({
filter: {
logic: 'and',
filters: [
{ field: 'stcd', operator: 'eq', dataType: 'string', value: stcd },
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: startTime
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: endTime
}
]
},
sort: [{ field: 'tm', dir: 'asc' }]
});
const rawData = res?.data?.data || [];
hasData.value = rawData.length > 0;
if (hasData.value) {
updateChart(rawData);
}
hasLoaded.value = true;
} catch (error) {
console.error('获取图表数据失败:', error);
@ -448,6 +476,7 @@ const fetchChartData = async () => {
//
const handleQuery = () => {
hasData.value = true;
fetchLimitInfo();
fetchDayData();
fetchChartData();
@ -456,6 +485,8 @@ const handleQuery = () => {
// timeType
watch(timeType, () => {
dateRange.value = initDateRange();
hasData.value = true;
fetchChartData();
});
watch(
@ -466,10 +497,8 @@ watch(
fetchDayData();
//
nextTick(() => {
setTimeout(() => {
initChart();
fetchChartData();
}, 2000);
initChart();
fetchChartData();
});
window.addEventListener('resize', handleResize);
}
@ -477,7 +506,9 @@ watch(
{ immediate: true }
);
onMounted(() => {});
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
destroyChart();

View File

@ -0,0 +1,626 @@
<template>
<div class="flow-measure">
<a-tabs v-model:activeKey="activeTabKey">
<a-tab-pane v-for="tab in tabsList" :key="tab.key" :tab="tab.name" />
<template #rightExtra>
<div class="search-bar">
<a-range-picker
v-model:value="dateRange"
format="YYYY-MM-DD HH:mm"
:show-time="showTimeConfig"
:allowClear="false"
:presets="DateSetting.RangeButton.month1"
:disabled-date="disabledDate"
/>
<a-button type="primary" class="search-btn" @click="handleSearch">
查询
</a-button>
</div>
</template>
</a-tabs>
<a-spin :spinning="isLoading" tip="加载中...">
<div class="tab-content">
<div class="content-body">
<div class="chart-wrapper">
<div ref="chartRef" class="chart-container"></div>
<a-empty
v-if="!chartData || chartData.length === 0"
description="暂无数据"
class="chart-empty"
/>
</div>
<div class="table-wrapper">
<BasicTable
:scrollY="480"
:scrollX="tableScrollX"
:columns="tableColumns"
:data="tableData"
/>
</div>
</div>
</div>
</a-spin>
<!-- 视频播放弹窗 -->
<a-modal
v-model:open="videoModalVisible"
:title="videoModalTitle"
:width="800"
:footer="null"
>
<div class="video-container">
<video
v-if="currentVideoUrl"
:src="currentVideoUrl"
controls
autoplay
class="video-player"
/>
<a-empty v-else description="暂无视频" />
</div>
</a-modal>
</div>
</template>
<script lang="ts" setup>
import {
ref,
watch,
onBeforeUnmount,
onMounted,
nextTick,
h,
computed
} from 'vue';
import dayjs, { Dayjs } from 'dayjs';
import * as echarts from 'echarts';
import { queryPostUrlList } from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
import BasicTable from '@/components/BasicTable/index.vue';
import { DateSetting } from '@/utils/enumeration';
const modelStore = useModelStore();
const props = defineProps({
isActive: { type: Boolean, default: false },
url: { type: String, default: '' },
code: { type: String, default: '' },
stcd: { type: String, default: '' }
});
const isQxdMode = computed(() => props.code === 'qxd.tabs.lcjs');
const hasLoaded = ref(false);
const isLoading = ref(false);
const tableData = ref<any[]>([]);
const chartData = ref<any[]>([]);
const chartRef = ref<HTMLElement>();
let chartInstance: echarts.ECharts | null = null;
//
const videoModalVisible = ref(false);
const videoModalTitle = ref('');
const currentVideoUrl = ref('');
const tabsList = [{ name: '流量监测', key: 'flowMeasure' }];
const activeTabKey = ref('flowMeasure');
//
const showTimeConfig = {
format: 'HH:mm',
hourStep: 1,
minuteStep: 5,
secondStep: 60
};
const disabledDate = (current: Dayjs) =>
current && current.isAfter(dayjs(), 'day');
const initDateRange = (): [Dayjs, Dayjs] => {
return [dayjs().subtract(7, 'day'), dayjs()];
};
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
//
const tableColumns = [
{
title: '时间',
dataIndex: 'tm',
width: 180,
fixed: 'left',
customRender: ({ text }: any) =>
text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
},
{
title: '水位(m)',
dataIndex: 'z',
width: 120,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
},
{
title: '流量(m³/s)',
dataIndex: 'q',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
},
{
title: '流速(m³/s)',
dataIndex: 'v',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
},
{
title: '水位视频',
dataIndex: 'rzVideoPath',
width: 120,
customRender: ({ record }: any) => {
if (!record.rzVideoPath) return '-';
return h(
'a-button',
{
type: 'link',
size: 'small',
onClick: () => playVideo(record.rzVideoPath, '水位视频')
},
'-'
);
}
},
{
title: '流速视频',
dataIndex: 'speedVideoPath',
width: 120,
customRender: ({ record }: any) => {
if (!record.speedVideoPath) return '-';
return h(
'a-button',
{
type: 'link',
size: 'small',
onClick: () => playVideo(record.speedVideoPath, '流速视频')
},
'-'
);
}
}
];
const tableScrollX = tableColumns.reduce((s, c) => s + (c.width || 100), 0);
//
const playVideo = (videoUrl: string, title: string) => {
currentVideoUrl.value = videoUrl;
videoModalTitle.value = title;
videoModalVisible.value = true;
};
//
const initChart = () => {
if (!chartRef.value) return;
if (chartInstance) chartInstance.dispose();
chartInstance = echarts.init(chartRef.value);
updateChart(chartData.value);
};
//
const updateChart = (data: any[]) => {
if (!chartInstance) return;
if (!data || data.length === 0) {
chartInstance.clear();
return;
}
const sorted = [...data].sort(
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
);
const xAxisData = sorted.map(item => {
const hm = dayjs(item.tm).format('HH:mm');
if (hm === '00:00') {
const date = dayjs(item.tm).format('MM-DD');
return `${hm}\n${date}`;
}
return hm;
});
const zData = sorted.map(item => item.z);
const qData = sorted.map(item => item.q);
const vData = sorted.map(item => item.v);
const waterValues = zData.filter(v => v !== null && v !== undefined);
const waterMin = waterValues.length > 0 ? Math.min(...waterValues) : 0;
const waterMax = waterValues.length > 0 ? Math.max(...waterValues) : 10;
const flowValues = [...qData, ...vData].filter(
v => v !== null && v !== undefined
);
const flowMin = flowValues.length > 0 ? Math.min(...flowValues) : 0;
const flowMax = flowValues.length > 0 ? Math.max(...flowValues) : 10;
const formatRules: Record<
string,
{ format: (v: number) => string; unit: string }
> = {
水位: { format: v => v.toFixed(2), unit: '(m)' },
流量: { format: v => String(Math.round(v)), unit: '(m³/s)' },
流速: { format: v => v.toFixed(2), unit: '(m³/s)' }
};
const option = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(50, 50, 50, 0.9)',
textStyle: { color: '#fff', fontSize: 12 },
axisPointer: { type: 'cross' },
formatter: (params: any) => {
if (!params || params.length === 0) return '';
const dataIndex = params[0].dataIndex;
const fullTime = sorted[dataIndex]?.tm
? dayjs(sorted[dataIndex].tm).format('YYYY-MM-DD HH:mm:ss')
: '';
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
params.forEach((param: any) => {
if (param.value == null) return;
const rule = Object.entries(formatRules).find(([key]) =>
param.seriesName.includes(key)
);
const displayValue = rule
? rule[1].format(Number(param.value))
: param.value;
const unit = rule ? rule[1].unit : '';
html += `
<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;">
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
<span style="flex:1;font-size:14px;text-align:left;margin-right:6px;">${param.seriesName}: </span>
<span style="font-size:14px;min-width:60px;text-align:right;"><strong>${displayValue}</strong> ${unit}</span>
</div>
`;
});
return html;
}
},
legend: {
type: 'scroll',
top: 10,
data: ['水位', '流量', '流速'],
textStyle: { fontSize: 12 },
selected: {
水位: true,
流量: true,
流速: true
}
},
grid: {
left: 60,
right: 100,
top: 80,
bottom: 60
},
xAxis: {
type: 'category',
data: xAxisData,
axisLine: { lineStyle: { color: '#000000' } },
axisTick: { show: false },
axisLabel: { fontSize: 12 },
splitLine: {
show: true,
lineStyle: { color: '#bfbfbf', type: 'solid' }
}
},
yAxis: [
{
name: '水位(m)',
type: 'value',
position: 'left',
axisLine: { show: true, lineStyle: { color: '#56C2E3' } },
axisLabel: { color: '#56C2E3' },
nameTextStyle: { color: '#56C2E3' },
alignTicks: true,
scale: true,
splitNumber: 9
},
{
name: '流量(m³/s)',
type: 'value',
position: 'right',
axisLine: { show: true, lineStyle: { color: '#9556A4' } },
axisLabel: { color: '#9556A4' },
nameTextStyle: { color: '#9556A4' },
alignTicks: true,
scale: true,
splitNumber: 9
},
{
name: '流速(m³/s)',
type: 'value',
position: 'right',
axisLine: { show: true, lineStyle: { color: '#78C300' } },
axisLabel: { color: '#78C300' },
nameTextStyle: { color: '#78C300' },
alignTicks: true,
scale: true,
splitNumber: 9,
offset: 60
}
],
series: [
{
name: '水位',
type: 'line',
yAxisIndex: 0,
data: zData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#56C2E3', width: 2 },
itemStyle: { color: '#56C2E3' }
},
{
name: '流量',
type: 'line',
yAxisIndex: 1,
data: qData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#9556A4', width: 2 },
itemStyle: { color: '#9556A4' }
},
{
name: '流速',
type: 'line',
yAxisIndex: 2,
data: vData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#78C300', width: 2 },
itemStyle: { color: '#78C300' }
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0],
throttle: 50,
start: 0,
end: 100
}
],
toolbox: {
show: true,
feature: {
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
},
right: 20,
top: 10
}
};
chartInstance.setOption(option, true);
};
//
const fetchData = async () => {
if (!dateRange.value) return;
// qxdstcd
if (isQxdMode.value && !props.stcd) {
return;
}
isLoading.value = true;
try {
// 使stcdqxd使props.stcd使modelStore.params.stcd
const currentStcd = isQxdMode.value ? props.stcd : modelStore.params.stcd;
const filterParams = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: currentStcd
},
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
}
]
},
sort: [{ field: 'tm', dir: 'asc' }]
};
// qxd使getMonitorDataZqqxd使queryPostUrlList
let rawData;
if (isQxdMode.value) {
const { getMonitorDataZq } = await import('@/api/mapModal');
const res = await getMonitorDataZq(filterParams);
rawData = res?.data?.data || res?.data?.records || [];
} else {
const res = await queryPostUrlList(props.url, filterParams);
rawData = res?.data?.data || res?.data?.records || [];
}
tableData.value = [...rawData].reverse();
chartData.value = rawData;
nextTick(() => updateChart(rawData));
} catch (error) {
console.error('获取数据失败:', error);
tableData.value = [];
chartData.value = [];
if (chartInstance) chartInstance.clear();
} finally {
isLoading.value = false;
}
};
const handleSearch = () => fetchData();
const initData = () => {
if (hasLoaded.value) return;
nextTick(() => initChart());
fetchData();
hasLoaded.value = true;
};
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) initData();
},
{ immediate: true }
);
watch(
() => modelStore.params.stcd,
(newStcd, oldStcd) => {
// qxdstorestcd
if (!isQxdMode.value && newStcd && newStcd !== oldStcd) fetchData();
}
);
// qxdprops.stcd
watch(
() => props.stcd,
(newStcd, oldStcd) => {
if (isQxdMode.value && newStcd && newStcd !== oldStcd) {
hasLoaded.value = false;
initData();
}
}
);
let resizeObserver: ResizeObserver | null = null;
watch(
() => props.isActive,
active => {
if (active && chartRef.value) {
setTimeout(() => {
if (chartInstance) chartInstance.resize();
}, 200);
if (!resizeObserver) {
resizeObserver = new ResizeObserver(() => {
if (chartInstance) chartInstance.resize();
});
resizeObserver.observe(chartRef.value);
}
}
}
);
const handleResize = () => {
if (chartInstance) chartInstance.resize();
};
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onBeforeUnmount(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
</script>
<style lang="scss" scoped>
.flow-measure {
width: 100%;
min-height: 600px;
position: relative;
:deep(.ant-tabs-nav) {
margin-bottom: 4px;
}
.search-bar {
width: 100%;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
.search-btn {
margin-left: 8px;
}
}
.tab-content {
width: 100%;
.content-body {
width: 100%;
display: flex;
height: 600px;
position: relative;
.chart-wrapper {
flex: 1;
height: 100%;
position: relative;
box-sizing: border-box;
padding-top: 10px;
.chart-container {
width: 100%;
height: 100%;
}
}
.chart-empty {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.table-wrapper {
flex: 1;
min-width: 500px;
height: 100%;
box-sizing: border-box;
padding: 10px;
overflow: hidden;
overflow-x: auto;
border-left: 1px solid #dcdfe6;
margin-left: 10px;
}
}
}
}
.video-container {
width: 100%;
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
.video-player {
width: 100%;
max-height: 600px;
border-radius: 4px;
}
}
</style>

View File

@ -321,7 +321,7 @@ const updateChart = (selectedRows: any[]) => {
fontSize: 12,
color: '#000000',
formatter: function (value) {
return value + '℃';
return value.toFixed(1) + '℃';
}
},
splitLine: { show: true }
@ -349,6 +349,13 @@ const updateChart = (selectedRows: any[]) => {
},
right: 20,
top: 10
},
dataZoom: {
type: 'inside',
xAxisIndex: [0],
throttle: 50,
start: 0,
end: 100
}
};

View File

@ -0,0 +1,778 @@
<template>
<div class="water-quality">
<a-tabs v-model:activeKey="activeTabKey">
<a-tab-pane v-for="tab in tabsList" :key="tab.key" :tab="tab.name" />
<template #rightExtra>
<div class="search-bar">
<a-range-picker
v-model:value="dateRange"
format="YYYY-MM-DD HH"
:show-time="showTimeConfig"
:allowClear="false"
:presets="DateSetting.RangeButton.hour"
:disabled-date="disabledDate"
/>
<a-button type="primary" class="search-btn" @click="handleSearch">
查询
</a-button>
<a-button
v-if="isSummaryMode"
class="search-btn"
@click="handleExport"
>
导出
</a-button>
</div>
</template>
</a-tabs>
<div class="tab-checkbox">
<a-checkbox-group
v-model:value="selectedColumns"
name="checkboxgroup"
:options="filteredCheckBoxOptions"
class="checkbox-group"
@change="handleCheckboxChange"
/>
</div>
<a-spin :spinning="isLoading" tip="加载中...">
<div class="tab-content">
<div class="content-body">
<div class="chart-wrapper">
<div ref="chartRef" class="chart-container"></div>
<a-empty
v-if="!chartData || chartData.length === 0"
description="暂无数据"
class="chart-empty"
/>
</div>
<div class="table-wrapper">
<BasicTable
:scrollY="480"
:scrollX="tableScrollX"
:columns="currentColumns"
:data="tableData"
/>
</div>
</div>
</div>
</a-spin>
</div>
</template>
<script lang="ts" setup>
import {
ref,
watch,
computed,
onMounted,
onBeforeUnmount,
nextTick
} from 'vue';
import dayjs, { Dayjs } from 'dayjs';
import * as echarts from 'echarts';
import {
getMonitorDataWaterQuality,
getMonitorDataWaterQualityDetail,
getMonitorDataWaterQualityList
} from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
import BasicTable from '@/components/BasicTable/index.vue';
import { DateSetting } from '@/utils/enumeration';
const modelStore = useModelStore();
const props = defineProps({
isActive: { type: Boolean, default: false },
code: { type: String, default: '' },
stcd: { type: String, default: '' }
});
const isQxdMode = computed(() => props.code === 'qxd.tabs.szjc');
const hasLoaded = ref(false);
const isLoading = ref(false);
const tableData = ref<any[]>([]);
const chartData = ref<any[]>([]);
const chartRef = ref<HTMLElement>();
let chartInstance: echarts.ECharts | null = null;
const tabsList = [{ name: '水质', key: 'szjc.tabs.jcsj' }];
const activeTabKey = ref('szjc.tabs.jcsj');
//
const showTimeConfig = {
format: 'HH',
hourStep: 1,
minuteStep: 5,
secondStep: 60
};
const disabledDate = (current: Dayjs) =>
current && current.isAfter(dayjs(), 'day');
const initDateRange = (): [Dayjs, Dayjs] => {
const startDate = dayjs().subtract(7, 'day').startOf('day');
return [startDate, dayjs()];
};
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
// ==================== Checkbox ====================
const selectedColumns = ref<any[]>([]);
const checkBoxOptions = [{ label: '综合分析', value: 'summary' }];
const showSummaryCheckbox = ref(false);
const isDataEmpty = computed(() => {
const data = Array.isArray(chartData.value) ? chartData.value : [];
return data.length === 0;
});
const filteredCheckBoxOptions = computed(() => {
if (isDataEmpty.value) return [];
const available = checkBoxOptions.filter(opt => {
if (opt.value === 'summary' && !showSummaryCheckbox.value) return false;
return true;
});
return available;
});
const isSummaryMode = computed(() => selectedColumns.value.includes('summary'));
const handleCheckboxChange = (values: any[]) => {
selectedColumns.value = values;
if (values.length === 0) {
selectedColumns.value = [];
}
fetchData();
};
// ==================== ====================
const qualityParams = ref<any[]>([]); // enable==1
const allDetailParams = ref<any[]>([]); //
const visibleSeriesNames = ref<string[]>([]); // series2
// showControlunit
const parseUnit = (showControl: string) => {
if (!showControl) return '';
try {
const config = JSON.parse(showControl);
return config.unit || '';
} catch {
return '';
}
};
// ==================== ====================
//
const fixedColumns = [
{
title: '时间',
dataIndex: 'tm',
fixed: 'left',
width: 160,
customRender: ({ text }: any) =>
text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '-'
},
{
title: '水质要求',
dataIndex: 'wwqtgName',
fixed: 'left',
width: 100
},
{
title: '水质等级',
dataIndex: 'wqgrdName',
fixed: 'left',
width: 100
},
{
title: '是否达标',
dataIndex: 'sfdb',
fixed: 'left',
width: 100,
customRender: ({ text }: any) => {
const isUnqualified = text == 0; // 01
return {
props: {
style: { color: isUnqualified ? 'red' : 'inherit' }
},
children: text == 0 ? '不达标' : text == 1 ? '达标' : '-'
};
}
}
];
//
const fixedQualityColumns = computed(() => {
const columns = allDetailParams.value.map((p: any) => {
const unit = parseUnit(p.showControl);
const title = unit ? `${p.ysShowName}(${unit})` : p.ysShowName;
const dataIndex = p.ys.toLowerCase();
const isEnabled = p.enable == 1;
// 10051406160
const titleLen = title.length;
let width = 100;
if (titleLen > 6) {
width = 160;
} else if (titleLen > 5) {
width = 140;
}
const fieldUpper = p.ys.toUpperCase(); // max/min
return {
title,
dataIndex,
enabled: isEnabled,
width,
customRender: ({ text, record }: any) => {
if (!isEnabled) return '-'; // enable=0 -
if (text === undefined || text === null || text === '') return '-';
const num = Number(text);
if (isNaN(num)) return '-';
// max/minmax/min record
const maxList = record?.max;
const minList = record?.min;
let isOutOfRange = false;
if (Array.isArray(maxList) && maxList.length > 0) {
const maxItem = maxList.find((m: any) => m[fieldUpper] !== undefined);
if (maxItem && num > maxItem[fieldUpper]) {
isOutOfRange = true;
}
}
if (Array.isArray(minList) && minList.length > 0) {
const minItem = minList.find((m: any) => m[fieldUpper] !== undefined);
if (minItem && num < minItem[fieldUpper]) {
isOutOfRange = true;
}
}
return {
props: {
style: { color: isOutOfRange ? 'red' : 'inherit' }
},
children: num
};
}
};
});
//
if (isSummaryMode.value) {
columns.push({
title: '出库流量(m³/s)',
dataIndex: 'qo',
width: 140,
customRender: ({ text }: any) => {
if (text === undefined || text === null || text === '') return '-';
const num = Number(text);
return isNaN(num) ? '-' : num;
}
});
}
return columns;
});
const swjcColumns = computed(() => {
return [...fixedColumns, ...fixedQualityColumns.value];
});
const currentColumns = swjcColumns;
const tableScrollX = computed(() => {
const columns = currentColumns.value;
const totalWidth = columns.reduce(
(sum: number, col: any) => sum + (col.width || 120),
0
);
return totalWidth > 600 ? totalWidth : undefined;
});
// ==================== ====================
function shouldStagger(width: number, dataLen: number) {
return dataLen > Math.floor(width / 100);
}
function getAxisLabelConfig(chartWidth: number, xAxisData: string[]) {
const needStagger = shouldStagger(chartWidth, xAxisData.length);
return {
fontSize: 12,
interval: 'auto',
formatter: (value: string, index: number) => {
const [date, time] = value.split(' ');
if (needStagger) {
return index % 2 === 0 ? `${date}\n${time}\n ` : ` \n${date}\n${time}`;
}
return `${date}\n${time}`;
}
};
}
const updateChart = (data: any[], enabledParams: any[]) => {
if (!chartInstance) return;
if (!data || data.length === 0) {
chartInstance.clear();
return;
}
const sorted = [...data].sort(
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
);
const xAxisData = sorted.map(item =>
dayjs(item.tm).format('YYYY-MM-DD HH:mm')
);
const width = chartRef.value?.clientWidth || 0;
const axisLabelConfig = getAxisLabelConfig(width, xAxisData);
// series
const allSeries = enabledParams.map((param: any, index: number) => {
const showControl = param.showControl || '{}';
let lineColor = '';
let unit = '';
try {
const config = JSON.parse(showControl);
lineColor = config.lineColor || '';
unit = config.unit || '';
} catch {
// 使
}
console.log(param);
const dataIndex = param.ys.toLowerCase();
const legendName = param.ysShowName; // legendunit
const yAxisName = unit ? `${param.ysShowName}\n${unit}` : param.ysShowName; // Y+unit
const color = lineColor;
return {
name: legendName,
ysShowName: param.ysShowName,
unit,
yAxisName,
type: 'line',
data: sorted.map(item => item[dataIndex]),
smooth: true,
symbol: 'none',
lineStyle: { color: color, width: 2 },
itemStyle: { color: color }
};
});
// series
if (isSummaryMode.value) {
allSeries.push({
name: '出库流量',
ysShowName: '出库流量',
unit: 'm³/s',
yAxisName: '出库流量\nm³/s',
type: 'line',
data: sorted.map(item => item.qo),
smooth: true,
symbol: 'none',
lineStyle: { color: '#4B79AB', width: 2 },
itemStyle: { color: '#4B79AB' }
});
}
const legendData = allSeries.map((s: any) => s.name);
// series
if (visibleSeriesNames.value.length === 0) {
const defaultName = '溶解氧';
if (legendData.includes(defaultName)) {
visibleSeriesNames.value = [defaultName];
} else if (legendData.length > 0) {
visibleSeriesNames.value = [legendData[0]];
}
}
// series legend selected
const series = allSeries;
// Y visibleSeriesNames
const yAxisConfig: any[] = [];
const visibleSeriesList = series.filter((s: any) =>
visibleSeriesNames.value.includes(s.name)
);
console.log(visibleSeriesList[0]);
if (visibleSeriesList.length >= 1) {
yAxisConfig.push({
name: visibleSeriesList[0].yAxisName,
// nameLocation: 'middle',
// nameGap: 30,
type: 'value',
position: 'left',
// axisLine: { lineStyle: { color: visibleSeriesList[0].itemStyle.color } },
scale: true,
splitLine: {
show: true,
lineStyle: { color: '#bfbfbf', type: 'solid' }
}
});
// seriesyAxisIndex
series.forEach((s: any) => {
s.yAxisIndex = visibleSeriesNames.value.includes(s.name) ? 0 : 0;
});
}
if (visibleSeriesList.length >= 2) {
yAxisConfig.push({
name: visibleSeriesList[1].yAxisName,
// nameLocation: 'middle',
// nameGap: 35,
type: 'value',
position: 'right',
// axisLine: { lineStyle: { color: visibleSeriesList[1].itemStyle.color } },
scale: true,
splitLine: { show: false }
});
visibleSeriesList[0].yAxisIndex = 0;
visibleSeriesList[1].yAxisIndex = 1;
}
// legend selected
const legendSelected = legendData.reduce((acc: any, name) => {
acc[name] = visibleSeriesNames.value.includes(name);
return acc;
}, {});
const option: any = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(50,50,50,0.9)',
textStyle: { color: '#fff', fontSize: 12 },
axisPointer: { type: 'cross' },
formatter: (params: any) => {
if (!params?.length) return '';
const idx = params[0].dataIndex;
const fullTime = sorted[idx]?.tm
? dayjs(sorted[idx].tm).format('YYYY-MM-DD HH:mm')
: '';
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
params.forEach((p: any) => {
const v = p.value != null ? Number(p.value).toFixed(2) : '-';
html += `<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${p.color};margin-right:8px;"></span><span style="flex:1;font-size:14px;">${p.seriesName}: </span><span style="font-size:14px;min-width:60px;text-align:right;"><strong>${v}</strong></span></div>`;
});
return html;
}
},
legend: {
type: 'scroll',
width: '80%',
right: 60,
top: 10,
data: legendData,
textStyle: { fontSize: 12 },
selected: legendSelected
},
grid: {
left: 60,
right: series.length >= 2 ? 60 : 40,
top: 80,
bottom: 80
},
xAxis: {
type: 'category',
data: xAxisData,
axisLine: { lineStyle: { color: '#000000' } },
axisTick: { show: false },
axisLabel: axisLabelConfig,
splitLine: {
show: true,
lineStyle: { color: '#bfbfbf', type: 'solid' }
}
},
yAxis: yAxisConfig.length === 1 ? yAxisConfig[0] : yAxisConfig,
series,
dataZoom: [
{ type: 'inside', xAxisIndex: [0], throttle: 50, start: 0, end: 100 }
],
toolbox: {
show: true,
feature: {
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
},
right: 20,
top: 10
}
};
chartInstance.setOption(option, true);
};
// legend
const bindLegendEvent = () => {
if (!chartInstance) return;
chartInstance.off('legendselectchanged');
chartInstance.on('legendselectchanged', (params: any) => {
const selected = params.selected;
const name = params.name;
const wasSelected = selected[name]; // echarts already updated internal state
if (wasSelected) {
//
const prevVisible = [...visibleSeriesNames.value];
if (prevVisible.length >= 2) {
// 2
visibleSeriesNames.value = [prevVisible[1], name];
} else {
visibleSeriesNames.value = [...new Set([...prevVisible, name])];
}
} else {
//
visibleSeriesNames.value = visibleSeriesNames.value.filter(
n => n !== name
);
}
// 使allDetailParams
updateChart(chartData.value, allDetailParams.value);
});
};
// ==================== ====================
const fetchData = async () => {
if (!dateRange.value) return;
// qxdstcd
if (isQxdMode.value && !props.stcd) {
return;
}
isLoading.value = true;
try {
// 使stcdqxd使props.stcd使modelStore.params.stcd
const currentStcd = isQxdMode.value ? props.stcd : modelStore.params.stcd;
const filterParams = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: currentStcd
},
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
}
]
},
sort: [{ field: 'tm', dir: 'asc' }]
};
//
const [qualityRes, detailRes, listRes] = await Promise.all([
getMonitorDataWaterQuality(filterParams),
getMonitorDataWaterQualityDetail({
stcd: currentStcd,
tbCode: 'WQ_R',
startTime: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss'),
endTime: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
}),
getMonitorDataWaterQualityList(filterParams)
]);
// datanull
const qualityData = qualityRes?.data?.data || qualityRes?.data?.records;
showSummaryCheckbox.value = !!qualityData && qualityData.length > 0;
// detail
const detailData = detailRes?.data?.data || detailRes?.data || [];
allDetailParams.value = detailData; //
qualityParams.value = detailData.filter((item: any) => item.enable == 1); // enable==1
// series
visibleSeriesNames.value = [];
//
const listData = listRes?.data?.data || listRes?.data?.records || [];
tableData.value = [...listData].reverse();
chartData.value = listData;
nextTick(() => updateChart(listData, allDetailParams.value));
} catch (error) {
console.error('获取水质数据失败:', error);
tableData.value = [];
chartData.value = [];
qualityParams.value = [];
if (chartInstance) chartInstance.clear();
} finally {
isLoading.value = false;
}
};
const handleSearch = () => fetchData();
const handleExport = () => {
/* 导出逻辑 */
};
const initData = () => {
if (hasLoaded.value) return;
nextTick(() => {
if (chartRef.value && !chartInstance)
chartInstance = echarts.init(chartRef.value);
// chart
nextTick(() => {
bindLegendEvent();
});
});
fetchData();
hasLoaded.value = true;
};
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) initData();
},
{ immediate: true }
);
watch(
() => modelStore.params.stcd,
(newStcd, oldStcd) => {
// qxdstorestcd
if (!isQxdMode.value && newStcd && newStcd !== oldStcd) fetchData();
}
);
// qxdprops.stcd
watch(
() => props.stcd,
(newStcd, oldStcd) => {
if (isQxdMode.value && newStcd && newStcd !== oldStcd) {
hasLoaded.value = false;
initData();
}
}
);
// ResizeObserver
let resizeObserver: ResizeObserver | null = null;
watch(
() => props.isActive,
active => {
if (active && chartRef.value) {
setTimeout(() => {
if (chartInstance) chartInstance.resize();
}, 200);
if (!resizeObserver) {
resizeObserver = new ResizeObserver(() => {
if (chartInstance) chartInstance.resize();
});
resizeObserver.observe(chartRef.value);
}
}
}
);
const handleResize = () => {
if (chartInstance) chartInstance.resize();
};
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onBeforeUnmount(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
</script>
<style lang="scss" scoped>
.water-quality {
width: 100%;
min-height: 600px;
position: relative;
:deep(.ant-tabs-nav) {
margin-bottom: 4px;
}
.search-bar {
width: 100%;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
.search-btn {
margin-left: 8px;
}
}
.tab-checkbox {
position: absolute;
top: 12px;
left: 30%;
z-index: 100;
}
.tab-content {
width: 100%;
.content-body {
width: 100%;
display: flex;
height: 600px;
position: relative;
.chart-wrapper {
flex: 1;
height: 100%;
position: relative;
box-sizing: border-box;
padding-top: 10px;
.chart-container {
width: 100%;
height: 100%;
}
}
.chart-empty {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.table-wrapper {
flex: 1;
min-width: 500px;
height: 100%;
box-sizing: border-box;
padding: 10px;
overflow: hidden;
overflow-x: auto;
border-left: 1px solid #dcdfe6;
margin-left: 10px;
}
}
}
}
</style>

View File

@ -26,7 +26,7 @@
</template>
</a-tabs>
<div class="tab-checkbox" v-if="activeTabKey === 'swjc.tabs.jcsj'">
<div class="tab-checkbox">
<a-checkbox-group
v-model:value="selectedColumns"
name="checkboxgroup"
@ -105,6 +105,7 @@ import dayjs, { Dayjs } from 'dayjs';
import * as echarts from 'echarts';
import { Tooltip } from 'ant-design-vue';
import {
// getMonitorDataWaterTempList,
getMonitorDataWaterTemp,
getMonitorDataWaterTempPowerStation,
getMonitorDataWaterTempPowerStation2
@ -117,7 +118,9 @@ import { InfoCircleOutlined } from '@ant-design/icons-vue';
const modelStore = useModelStore();
const props = defineProps({
isActive: { type: Boolean, default: false }
code: { type: String, default: '' },
isActive: { type: Boolean, default: false },
stcd: { type: String, default: '' }
});
const hasLoaded = ref(false);
@ -503,6 +506,7 @@ const updateChart = (data: any[]) => {
}
},
legend: {
width: '80%',
type: 'scroll',
top: 10,
data: ['水温'],
@ -685,6 +689,8 @@ const updateChart = (data: any[]) => {
},
legend: {
type: 'scroll',
width: '80%',
right: 60,
top: 10,
data: [
'出库水温',
@ -809,10 +815,21 @@ const updateChart = (data: any[]) => {
};
// ==================== ====================
const isQxdMode = computed(() => props.code === 'qxd.tabs.jcsj');
const fetchData = async () => {
if (!dateRange.value) return;
// qxdstcd
if (isQxdMode.value && !props.stcd) {
return;
}
isLoading.value = true;
try {
// 使stcdqxd使props.stcd使modelStore.params.stcd
const currentStcd = isQxdMode.value ? props.stcd : modelStore.params.stcd;
const filterParams = {
filter: {
logic: 'and',
@ -821,7 +838,7 @@ const fetchData = async () => {
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
value: currentStcd
},
{
field: 'tm',
@ -840,22 +857,35 @@ const fetchData = async () => {
sort: [{ field: 'tm', dir: 'asc' }]
};
const [mainRes, powerStationRes, powerStationRes2] = await Promise.all([
getMonitorDataWaterTemp(filterParams),
getMonitorDataWaterTempPowerStation({ stcd: modelStore.params.stcd }),
getMonitorDataWaterTempPowerStation2({ stcd: modelStore.params.stcd })
]);
// qxdgetMonitorDataWaterTemp
if (isQxdMode.value) {
const mainRes = await getMonitorDataWaterTemp(filterParams);
const mainData = mainRes?.data?.data || mainRes?.data?.records || [];
showSummaryCheckbox.value = false;
showDeepCheckbox.value = false;
selectedColumns.value = [];
tableData.value = [...mainData].reverse();
chartData.value = mainData;
nextTick(() => updateChart(mainData));
} else {
//
const [mainRes, powerStationRes, powerStationRes2] = await Promise.all([
getMonitorDataWaterTemp(filterParams),
getMonitorDataWaterTempPowerStation({ stcd: currentStcd }),
getMonitorDataWaterTempPowerStation2({ stcd: currentStcd })
]);
const mainData = mainRes?.data?.data || mainRes?.data?.records || [];
showSummaryCheckbox.value = !!powerStationRes?.data?.ioWtrv;
const powerStation2Data =
powerStationRes2?.data?.data || powerStationRes2?.data || null;
showDeepCheckbox.value =
!!powerStationRes?.data?.hasRstcdWtvt && powerStation2Data !== null;
const mainData = mainRes?.data?.data || mainRes?.data?.records || [];
showSummaryCheckbox.value = !!powerStationRes?.data?.ioWtrv;
const powerStation2Data =
powerStationRes2?.data?.data || powerStationRes2?.data || null;
showDeepCheckbox.value =
!!powerStationRes?.data?.hasRstcdWtvt && powerStation2Data !== null;
tableData.value = [...mainData].reverse();
chartData.value = mainData;
nextTick(() => updateChart(mainData));
tableData.value = [...mainData].reverse();
chartData.value = mainData;
nextTick(() => updateChart(mainData));
}
} catch (error) {
console.error('获取数据失败:', error);
tableData.value = [];
@ -892,7 +922,19 @@ watch(
watch(
() => modelStore.params.stcd,
(newStcd, oldStcd) => {
if (newStcd && newStcd !== oldStcd) fetchData();
// qxdstorestcd
if (!isQxdMode.value && newStcd && newStcd !== oldStcd) fetchData();
}
);
// qxdprops.stcd
watch(
() => props.stcd,
(newStcd, oldStcd) => {
if (isQxdMode.value && newStcd && newStcd !== oldStcd) {
hasLoaded.value = false;
initData();
}
}
);

View File

@ -19,6 +19,7 @@
<a-empty
v-if="!loading && videoList.length === 0"
description="暂无数据"
class="flex align-center justify-center h-[530px] flex-col"
/>
</a-spin>
</div>
@ -27,7 +28,11 @@
<!-- 中间视频区域 55% -->
<a-col flex="55%" class="center-panel">
<!-- 视频宫格 -->
<div class="video-grid" :class="`layout-${viewLayout}`">
<div
class="video-grid"
:class="`layout-${viewLayout}`"
v-show="videoList.length > 0"
>
<div
v-for="index in parseInt(viewLayout)"
:key="index"
@ -42,6 +47,11 @@
/>
</div>
</div>
<a-empty
v-show="videoList.length === 0"
description="暂无数据"
class="h-full flex align-center justify-center flex-col"
/>
</a-col>
<!-- 右侧录像列表 30% -->
@ -80,7 +90,9 @@
<div class="record-list">
<a-spin :spinning="recordLoading">
<div v-if="paginatedRecords.length === 0" class="empty-record">
<a-empty />
<a-empty
class="h-[410px] flex align-center justify-center flex-col"
/>
</div>
<div v-else class="record-grid">
<div

View File

@ -12,11 +12,14 @@
<!-- 问题
1.基础信息图片展示是什么逻辑
2. 电站专题展示逻辑
3. 实时视频回放 少接口
3. 实时视频回放 少接口
4. 预警提示 少接口
5. 生态流量 达标率查询不对
5. 生态流量 达标率查询不对()
6. 鱼类适应性繁殖同期对比NAN ()
7. 出库水温 综合分析 导出没做
8. 栖息地-流量监测 没有水位视频和流量视频 字段没数据
9. 栖息地 水温水质流量没有数据没法测
-->
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
<a-select
@ -33,7 +36,7 @@
<a-tabs :active-key="currentActiveKey" @change="onTabChange">
<a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.name">
</a-tab-pane>
<template #rightExtra>
<template #rightExtra v-if="anchor.sttp == 'ENG' || anchor.sttp == 'eng'">
<a-tooltip :title="!isEngConfig ? '' : '该电站无专题配置'">
<a-button type="primary" :disabled="isEngConfig">
<i class="icon iconfont icon-topic mr-[5px]"></i>
@ -45,6 +48,16 @@
<!-- 内容区域 - 使用动态组件 + keep-alive 实现按需加载和缓存 -->
<keep-alive>
<div class="content">
<div class="content-wrapper" v-show="isQxdMode(currentActiveKey)">
测站
<a-select
v-model:value="currentQxdSelectedStcd"
:options="qxdStationOptions"
style="width: 200px; margin-bottom: 12px"
placeholder="请选择站点"
@change="handleQxdStationChange"
/>
</div>
<!-- 基本信息 -->
<BasicInfo
v-show="currentActiveKey === 'basicInfo'"
@ -71,12 +84,41 @@
<WaterTemperature
v-show="currentActiveKey === 'WaterTemperature'"
:is-active="currentActiveKey === 'WaterTemperature'"
:code="getTabCode('WaterTemperature')"
:stcd="
getTabCode('WaterTemperature') == 'qxd.tabs.jcsj'
? qxdSelectedStcd
: ''
"
/>
<!-- 监测数据 - 垂向水温 -->
<VerticalWaterTemperature
v-show="currentActiveKey === 'VerticalWaterTemperature'"
:is-active="currentActiveKey === 'VerticalWaterTemperature'"
/>
<!-- 监测数据 - 水质 -->
<WaterQuality
v-show="currentActiveKey === 'WaterQuality'"
:is-active="currentActiveKey === 'WaterQuality'"
:code="getTabCode('WaterQuality')"
:stcd="
getTabCode('WaterQuality') == 'qxd.tabs.szjc'
? qxdSelectedStcdWq
: ''
"
/>
<!-- 流量监测 -->
<FlowMeasure
v-show="currentActiveKey === 'FlowMeasure'"
:is-active="currentActiveKey === 'FlowMeasure'"
:url="getTabUrl('FlowMeasure')"
:code="getTabCode('FlowMeasure')"
:stcd="
getTabCode('FlowMeasure') == 'qxd.tabs.lcjs'
? qxdSelectedStcdZq
: ''
"
/>
<!-- 预警提示 -->
<EarlyWarningAlert
v-show="currentActiveKey === 'tableTabs'"
@ -123,17 +165,160 @@ import PanoramaInfo from './components/PanoramaInfo.vue'; // 全景影像
import MonitorInfo from './components/MonitorInfo.vue'; // 线
import WaterTemperature from './components/WaterTemperature.vue'; //
import VerticalWaterTemperature from './components/VerticalWaterTemperature.vue'; //
import WaterQuality from './components/WaterQuality.vue'; //
import EarlyWarningAlert from './components/EarlyWarningAlert.vue'; //
import FlowMeasure from './components/FlowMeasure.vue'; //
import EcologicalFlow from './components/EcologicalFlow.vue'; //
import Attachment from './components/Attachment.vue'; // /
import WaterTemperatureRep from './components/WaterTemperatureRep.vue'; //
import { useModelStore } from '@/store/modules/model';
import { handleTabs } from './setting.config';
import {
getMonitorDataWt,
getMonitorDataWq,
getMonitorDataZq
} from '@/api/mapModal';
const modelStore = useModelStore();
const tabsConfig = ref([]);
//
const isEngConfig = ref(true);
// ==================== qxd ====================
// qxd
const qxdConfigMap: Record<
string,
{
sttpCode: string;
apiFn: (data: any) => Promise<any>;
selectProp: string;
}
> = {
'qxd.tabs.jcsj': {
//
sttpCode: 'WTRV',
apiFn: getMonitorDataWt,
selectProp: 'qxdSelectedStcd'
},
'qxd.tabs.szjc': {
//
sttpCode: 'WQ',
apiFn: getMonitorDataWq,
selectProp: 'qxdSelectedStcdWq'
},
'qxd.tabs.lcjs': {
//
sttpCode: 'ZQ',
apiFn: getMonitorDataZq,
selectProp: 'qxdSelectedStcdZq'
}
};
//
const qxdStationOptions = ref<{ label: string; value: string }[]>([]);
const qxdSelectedStcd = ref<string>('');
const qxdSelectedStcdWq = ref<string>('');
const qxdSelectedStcdZq = ref<string>('');
const qxdLoadedCodes = ref<Set<string>>(new Set());
// tabqxd
const getCurrentQxdConfig = (tabKey: string) => {
const code = getTabCode(tabKey);
return code && qxdConfigMap[code] ? qxdConfigMap[code] : null;
};
// stcd
const getQxdSelectedStcd = (tabKey: string) => {
const config = getCurrentQxdConfig(tabKey);
if (!config) return '';
switch (config.selectProp) {
case 'qxdSelectedStcd':
return qxdSelectedStcd.value;
case 'qxdSelectedStcdWq':
return qxdSelectedStcdWq.value;
case 'qxdSelectedStcdZq':
return qxdSelectedStcdZq.value;
default:
return '';
}
};
// stcd
const setQxdSelectedStcd = (tabKey: string, value: string) => {
const config = getCurrentQxdConfig(tabKey);
if (!config) return;
switch (config.selectProp) {
case 'qxdSelectedStcd':
qxdSelectedStcd.value = value;
break;
case 'qxdSelectedStcdWq':
qxdSelectedStcdWq.value = value;
break;
case 'qxdSelectedStcdZq':
qxdSelectedStcdZq.value = value;
break;
}
};
//
const loadQxdStationList = async (tabKey: string) => {
const code = getTabCode(tabKey);
if (!code || !qxdConfigMap[code]) return;
if (qxdLoadedCodes.value.has(code)) return;
const config = qxdConfigMap[code];
try {
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'fhstcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
},
{
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: config.sttpCode
}
]
},
select: ['stcd', 'stnm']
};
const res = await config.apiFn(params);
const stations = res?.data?.data || res?.data?.records || [];
if (stations.length > 0) {
qxdStationOptions.value = stations.map((item: any) => ({
label: item.stnm,
value: item.stcd
}));
setQxdSelectedStcd(tabKey, stations[0].stcd);
}
qxdLoadedCodes.value.add(code);
} catch (error) {
console.error(`获取站点列表失败(${code}):`, error);
}
};
// tabstcd (v-model)
const currentQxdSelectedStcd = computed({
get: () => getQxdSelectedStcd(currentActiveKey.value),
set: (val: string) => setQxdSelectedStcd(currentActiveKey.value, val)
});
// qxd
const isQxdMode = (tabKey: string) => {
const code = getTabCode(tabKey);
return code && qxdConfigMap[code] ? true : false;
};
//
const handleQxdStationChange = () => {
// watch stcd
};
// import MapView from './components/MapView.vue';
// import SurroundingInfo from './components/SurroundingInfo.vue';
@ -176,6 +361,7 @@ const emit = defineEmits<{
// activeKey
const currentActiveKey = ref<string>('');
const anchor = ref<string>('');
// activeKey
watch(
@ -192,12 +378,29 @@ watch(
newVal => {
if (!newVal || Object.keys(newVal).length === 0) return;
console.log(newVal);
tabsConfig.value = handleTabs(newVal);
anchor.value = newVal;
let params = newVal;
if (['VD', 'WTRV', 'ZQ', 'WQ'].includes(params?.sttpCode)) {
params.sttp = params.sttpCode;
}
tabsConfig.value = handleTabs({ ...params });
let value = tabsConfig.value.find((item: any) => item.default);
currentActiveKey.value = value?.key;
},
{ deep: true, immediate: true }
);
// tab qxd
watch(
() => currentActiveKey.value,
activeKey => {
const code = getTabCode(activeKey);
if (code && qxdConfigMap[code]) {
loadQxdStationList(activeKey);
}
},
{ immediate: true }
);
// tab
const onTabChange = (key: string) => {
currentActiveKey.value = key;
@ -213,6 +416,12 @@ const handleClose = () => {
// ,
modelStore.showStcdSelector = false;
modelStore.stcdOptions = [];
// qxd
qxdStationOptions.value = [];
qxdSelectedStcd.value = '';
qxdSelectedStcdWq.value = '';
qxdSelectedStcdZq.value = '';
qxdLoadedCodes.value = new Set();
};
// 便使 props.data

View File

@ -320,11 +320,10 @@ const FPTabs: Array<any> =
// code: 'gyss.tabs.sssp'
// }
{
name: '监测数据',
name: '在线监测数据',
key: 'FishFacilityMonitorData', //Normal
type: 'FishFacilityMonitorData', //Normal
code: 'gyss.tabs.jcsj',
default: true // 默认显示
code: 'gyss.tabs.jcsj'
// tabs: [
// {
// name: '',
@ -340,7 +339,8 @@ const FPTabs: Array<any> =
name: '运行情况',
key: 'FishFacilityRunState', //Normal1
type: 'FishFacilityRunState', //Normal
code: 'gyss.tabs.yxqk'
code: 'gyss.tabs.yxqk',
default: true // 默认显示
// tabs: [
// {
// name: '',
@ -504,119 +504,20 @@ const FHTabs: Array<any> = [
name: '水温监测',
key: 'WaterTemperature',
type: 'WaterTemperature',
tabs: [
{
url: '/wmp-env-server/sw/alongDetail/GetKendoListCust',
name: '水温',
type: 'WaterTemperature',
tableUrl: '/wmp-env-server/sw/alongDetail/GetKendoListCust',
chartType: 'line',
// filter: NormalStcdFilter,
filterProps: {
params: {
ftype: 'WE',
sttp: 'WT', //WT
dataDimensionType: 'hyBase',
dataDimensionVal: 'all'
}
}
}
]
code: 'qxd.tabs.jcsj'
},
{
name: '水质监测',
key: 'WaterQuality',
type: 'WaterQuality',
tabs: [
{
url: '/wmp-env-server/env/wq/data/GetKendoListCust',
name: '水质',
type: 'monitor',
key: 'WaterQuality',
tableUrl: '/wmp-env-server/env/wq/data/GetKendoListCust',
chartType: 'lines',
// chartEvent: {
// legendselectchanged: (object: any, instance: any) => {
// const selected = object.selected
// const options = instance.getOption()
// if (options.grid instanceof Array) {
// options.grid = options.grid[0]
// }
// if (selected != undefined && options) {
// let yAxis: any = options.yAxis
// yAxis.map((item: any, index: number) => {
// item.show = false
// for (let i in selected) {
// if (selected[i] == true && item.name.indexOf(i) !== -1) {
// item.show = true
// }
// }
// })
// options.legend && (options.legend.selected = selected)
// let offset = 30
// let padding = 60
// if (options.yAxis && options.yAxis instanceof Array && options.grid && !(options.grid instanceof Array)) {
// let showYAxis = yAxis.filter((x: any) => {
// return x.show
// })
// showYAxis.map((yAxis: any, index: number) => {
// yAxis.position = index % 2 === 0 ? "left" : "right"
// yAxis.offset = padding * Math.floor(index / 2)
// if (index >= 4) {
// yAxis.offset = padding * Math.floor(2 / 2)
// }
// })
// if (showYAxis.length % 2 === 0) {
// options.grid.left = (showYAxis.length >= 4 ? 4 : showYAxis.length) * offset
// options.grid.right = (showYAxis.length >= 4 ? 4 : showYAxis.length) * offset
// } else {
// const left = showYAxis.filter((x: any) => x.position === "left")
// const right = showYAxis.filter((x: any) => x.position === "right")
// const left_start = left.length ? 0 : 60
// const right_start = left.length ? 0 : 60
// options.grid.left = left_start + padding * (left.length >= 2 ? 2 : left.length)
// options.grid.right = right_start + padding * (right.length >= 2 ? 2 : right.length)
// }
// }
// instance.setOption(options, false, true)
// }
// }
// },
// filter: NormalStcdFilter,
filterProps: {
params: {
ftype: 'WE',
sttp: 'WQ',
dataDimensionType: 'hyBase',
dataDimensionVal: 'all'
}
}
}
]
code: 'qxd.tabs.szjc'
},
{
name: '流量监测',
key: 'FlowMeasure',
type: 'FlowMeasure'
// tabs: [
// {
// url: '/wmp-eng-server/eng/river/GetKendoListCust',
// name: '流量',
// type: 'monitor',
// tableUrl: '/wmp-eng-server/eng/river/GetKendoListCust',
// chartType: 'line',
// // filter: NormalStcdFilter,
// filterProps: {
// params: {
// ftype: 'WE',
// sttp: 'ZQ',
// dataDimensionType: 'hyBase',
// dataDimensionVal: 'all',
// maptype: '2'
// }
// }
// }
// ]
type: 'FlowMeasure',
code: 'qxd.tabs.lcjs',
url: '/zq/river/GetKendoListCust'
},
{
name: '实时视频',
@ -624,43 +525,22 @@ const FHTabs: Array<any> = [
type: 'video',
url: ''
}
// {
// name: "全景影像",
// key: "panoramaInfo",
// type: "panorama"
// }
];
// const ZQTabs: Array<any> = [
// {
// name: '基础信息',
// key: 'basicInfo',
// type: 'basic',
// url: '/bbi/siteBipc/getSiteBasicInfo'
// },
// {
// name: '流量监测',
// key: 'FlowMeasure',
// type: 'FlowMeasure',
// tabs: [
// {
// url: '/wmp-swqx-server/zq/river/GetKendoListCust',
// name: '流量',
// type: 'monitor',
// tableUrl: '/wmp-swqx-server/zq/river/GetKendoListCust',
// chartType: 'line',
// filterProps: {
// params: {
// ftype: 'FHT',
// sttp: 'ZQ',
// dataDimensionType: 'hyBase',
// dataDimensionVal: 'all',
// maptype: '2'
// }
// }
// }
// ]
// }
// ]
const ZQTabs: Array<any> = [
{
name: '基础信息',
key: 'basicInfo',
type: 'basic',
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo',
default: true // 默认显示
},
{
name: '流量监测',
key: 'FlowMeasure',
type: 'FlowMeasure',
url: '/zq/river/GetKendoListCust'
}
];
// //水质监测站 √
const WQFBTabs: Array<any> = [
@ -1212,8 +1092,8 @@ const handleTabs = (modaldata: any) => {
} else {
return ENGTabs;
}
case 'WT':
case 'WTRV':
case 'WT': // 水温
case 'WTRV': // 垂向水温
if (modaldata.enfc == '1') {
return WTTabs1;
} else {
@ -1340,8 +1220,8 @@ const handleTabs = (modaldata: any) => {
} else {
return FPTabs;
}
// case 'ZQ':
// return ZQTabs
case 'ZQ': // 流量检测站
return ZQTabs;
// case 'FB':
// if (modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return FBTabs1

View File

@ -146,9 +146,12 @@ export class MapOl implements MapInterface {
this.map.on('click', evt => {
this.popupManager.showPopup(undefined, undefined);
this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
console.log(detectedFeature);
modelStore.modalVisible = true;
modelStore.params = detectedFeature.values_;
modelStore.title = detectedFeature.values_.titleName + ' 详情信息';
modelStore.title =
detectedFeature.values_.titleName ||
detectedFeature.values_.stnm + ' 详情信息';
});
});

View File

@ -187,10 +187,11 @@ const handleControllerClick = (item: any) => {
switch (item.key) {
case 'fullScreen':
isFullScreen.value = !isFullScreen.value;
map.flyTopanto([92.39728, 29.24587], 14);
// console.log(mapClass.hasLayer('eng_point'));
// map.jdPanelControlShowAndHidden("01", false);
map.mdLayerTreeShowOrHidden('eng_point', false);
map.mdLayerTreeShowOrHidden('wq_countryWq_point', true);
// map.mdLayerTreeShowOrHidden('eng_point', false);
// map.mdLayerTreeShowOrHidden('wq_countryWq_point', true);
// map.controlBaseLayerTreeShowAndHidden(
// 'eng_point',
// 'eng_point',

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,6 @@ service.interceptors.request.use(
config.url.includes('/wmp-eng-server') ||
config.url.includes('/dec-modules-usm-springcloud-starter') ||
config.url.includes('/wmp-swqx-server')
) {
config.headers._appid = '974975A6-47FD-4C04-9ACD-68938D2992BD';
config.headers._isolateid = '5b34aecb-adfb-4dfc-ad95-21505a9eb388';
@ -34,7 +33,7 @@ service.interceptors.request.use(
config.headers._sysid = '10EC2E0B-AEA9-4757-83A2-201BA1BC54E9';
config.headers.authorization =
'bearer 7894f05d-7204-4117-bda5-8d2f850ae463';
'bearer f6ce5036-cefe-487b-b731-b6d7b8922e0f';
config.baseURL = '/';
} else {
const user = useUserStoreHook();

File diff suppressed because it is too large Load Diff