基础弹框模块添加
This commit is contained in:
parent
65c4f4260c
commit
d4054ef786
@ -303,7 +303,7 @@ export function getAIComCalendarClick(data: any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 鱼类增殖站 - 运行数据 -获取年
|
// 鱼类增殖站 - 运行数据 -获取年 没接
|
||||||
export function getFishProgressionYear(data: any) {
|
export function getFishProgressionYear(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/wmp-env-server/fb/fbrdmr/getFbRelatedYrByStcd',
|
url: '/wmp-env-server/fb/fbrdmr/getFbRelatedYrByStcd',
|
||||||
@ -311,7 +311,7 @@ export function getFishProgressionYear(data: any) {
|
|||||||
params: data
|
params: data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//鱼类增殖站 - 运行数据 -获取鱼
|
//鱼类增殖站 - 运行数据 -获取鱼 没接
|
||||||
export function getFishProgressionFish(data: any) {
|
export function getFishProgressionFish(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/wmp-env-server/env/fishDic/stcd/GetKendoList',
|
url: '/wmp-env-server/env/fishDic/stcd/GetKendoList',
|
||||||
@ -319,6 +319,33 @@ export function getFishProgressionFish(data: any) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 过程图 - 亲鱼
|
||||||
|
export function getProcessDiagramParentFish(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/fb/bsmfr/fish/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过程图 - 产卵/孵化
|
||||||
|
export function getProcessDiagramHatch(data: any, type: 1 | 2) {
|
||||||
|
return request({
|
||||||
|
url: `/wmp-env-server/fb/fishhatchrecr/fish/GetKendoListCust?type=${type}`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过程图 - 鱼苗
|
||||||
|
export function getProcessDiagramFishFry(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/fb/fishbreedr/fish/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
//增值放流情况 获取年 没接
|
//增值放流情况 获取年 没接
|
||||||
export function getNormalAddedSituationYear(data: any) {
|
export function getNormalAddedSituationYear(data: any) {
|
||||||
return request({
|
return request({
|
||||||
@ -344,3 +371,125 @@ export function getNormalResearchSituation(data: any) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 珍稀植物园 - 运行数据 没接
|
||||||
|
export function getNormalDataMonitoring2Year(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/vpr/year/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 珍稀植物园 - 运行数据 列表 没接
|
||||||
|
export function getNormalDataMonitoring2List(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/vpr/basinVpIntDetail',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 珍稀动物园 - 监测数据 时间 没接
|
||||||
|
export function getNormal2Year(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/var/year/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 珍稀动物园 - 监测数据 列表 没接
|
||||||
|
export function getNormal2List(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/var/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 水生生态调查断面 - 监测数据 年份 没接
|
||||||
|
export function getEcologyYear(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/we/wer/getWeYr',
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 水生生态调查断面 - 监测数据 调查批次 没接
|
||||||
|
export function getEcologyBatch(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/we/fisht/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 水生生态调查断面 - 监测数据 保护类型 没接
|
||||||
|
export function getEcologyProtectType(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 水生生态调查断面 - 监测数据 调查鱼类列表 没接
|
||||||
|
export function getEcologyList(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/we/fisht/qgc/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 水生生态调查断面 - 监测数据 水温列表 没接
|
||||||
|
export function getEcologyList1(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/we/wewtr/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 水生生态调查断面 - 监测数据 水质列表 没接
|
||||||
|
export function getEcologyList2(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/we/wewqr/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 水生生态调查断面 - 监测数据 流速列表 没接
|
||||||
|
export function getEcologyList3(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/we/fvR/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 野生动物检测 -现场录像 - 年份 - 没接
|
||||||
|
export function getLiveFootageYear(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/wva/default/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 野生动物检测 -现场录像 - 没接
|
||||||
|
export function getLiveFootageList(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/wva/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 鱼类分布明细 - 年 没接
|
||||||
|
export function getFishDistributionYear(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/we/wer/getWeYr',
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 鱼类分布明细 - 列表 没接
|
||||||
|
export function getFishDistributionList(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/we/fisht/qgc/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,262 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:open="open"
|
||||||
|
:title="title"
|
||||||
|
width="80vw"
|
||||||
|
:footer="null"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@update:open="handleOpenChange"
|
||||||
|
>
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div class="attachment-modal">
|
||||||
|
<div v-if="fileList.length > 0" class="file-list">
|
||||||
|
<div
|
||||||
|
v-for="file in fileList"
|
||||||
|
:key="file.id"
|
||||||
|
:class="['file-item', { active: selectedFile?.id === file.id }]"
|
||||||
|
@click="selectFile(file)"
|
||||||
|
>
|
||||||
|
<span class="file-name" :title="file.name">{{ file.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="viewer-panel">
|
||||||
|
<template v-if="selectedFile">
|
||||||
|
<div v-if="selectedFile.fileType === 'image'" class="image-viewer">
|
||||||
|
<img :src="selectedFile.url" :alt="selectedFile.name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="selectedFile.fileType === 'video'" class="video-viewer">
|
||||||
|
<video :src="selectedFile.url" controls class="video-player"></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="selectedFile.fileType === 'pdf'" class="pdf-viewer">
|
||||||
|
<PdfView :file-id="String(selectedFile.id)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="empty-state">
|
||||||
|
<a-empty description="暂不支持该附件预览" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-else class="empty-state">
|
||||||
|
<a-empty description="暂无附件" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { getIdUrl } from '@/api/mapModal';
|
||||||
|
import PdfView from '@/components/pdf/pdfView.vue';
|
||||||
|
|
||||||
|
interface AttachmentFileItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
ext: string;
|
||||||
|
fileType: 'pdf' | 'image' | 'video' | 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
fid: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '查看附件'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:open', value: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const fileList = ref<AttachmentFileItem[]>([]);
|
||||||
|
const selectedFile = ref<AttachmentFileItem | null>(null);
|
||||||
|
|
||||||
|
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];
|
||||||
|
const VIDEO_EXTENSIONS = ['.mp4', '.mov', '.avi', '.wmv', '.m3u8'];
|
||||||
|
|
||||||
|
const getFileType = (ext: string): AttachmentFileItem['fileType'] => {
|
||||||
|
const normalizedExt = String(ext || '').toLowerCase();
|
||||||
|
if (normalizedExt === '.pdf') return 'pdf';
|
||||||
|
if (IMAGE_EXTENSIONS.includes(normalizedExt)) return 'image';
|
||||||
|
if (VIDEO_EXTENSIONS.includes(normalizedExt)) return 'video';
|
||||||
|
return 'unknown';
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetState = () => {
|
||||||
|
loading.value = false;
|
||||||
|
fileList.value = [];
|
||||||
|
selectedFile.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectFile = (file: AttachmentFileItem) => {
|
||||||
|
selectedFile.value = file;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchAttachmentList = async () => {
|
||||||
|
if (!props.fid) {
|
||||||
|
resetState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const fids = props.fid
|
||||||
|
.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (fids.length === 0) {
|
||||||
|
resetState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res: any = await getIdUrl({
|
||||||
|
id: fids.join(',')
|
||||||
|
});
|
||||||
|
const data = res?.data || {};
|
||||||
|
|
||||||
|
const files = fids
|
||||||
|
.map(fid => {
|
||||||
|
const item = data?.[fid];
|
||||||
|
if (!item) return null;
|
||||||
|
|
||||||
|
const ext = String(item.ext || '').toLowerCase();
|
||||||
|
return {
|
||||||
|
id: fid,
|
||||||
|
name: item.name || fid,
|
||||||
|
url: item.fullpath || '',
|
||||||
|
ext,
|
||||||
|
fileType: getFileType(ext)
|
||||||
|
} as AttachmentFileItem;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as AttachmentFileItem[];
|
||||||
|
|
||||||
|
fileList.value = files;
|
||||||
|
selectedFile.value = files[0] || null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取附件列表失败:', error);
|
||||||
|
resetState();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
emit('update:open', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.open, props.fid],
|
||||||
|
([open]) => {
|
||||||
|
if (!open) {
|
||||||
|
resetState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchAttachmentList();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.attachment-modal {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
width: 220px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-right: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e8f4fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #005293;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-panel {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-viewer,
|
||||||
|
.video-viewer,
|
||||||
|
.image-viewer,
|
||||||
|
.empty-state {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-viewer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #000;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-viewer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
316
frontend/src/components/MapModal/components/DZGJ.vue
Normal file
316
frontend/src/components/MapModal/components/DZGJ.vue
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dzgj">
|
||||||
|
<div class="search-bar mb-4">
|
||||||
|
<span>年份:</span>
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="searchParams.year"
|
||||||
|
picker="year"
|
||||||
|
style="width: 160px"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY"
|
||||||
|
/>
|
||||||
|
<a-button class="ml-4" type="primary" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-tabs v-model:activeKey="activeTabKey" @change="handleTabChange">
|
||||||
|
<a-tab-pane v-for="tab in tabsItems" :key="tab.key" :tab="tab.name" />
|
||||||
|
</a-tabs>
|
||||||
|
|
||||||
|
<a-spin :spinning="requestLoading">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:key="activeTabKey"
|
||||||
|
:scrollY="500"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="currentColumns"
|
||||||
|
:list-url="fetchTableData"
|
||||||
|
row-key="_rowKey"
|
||||||
|
:transform-data="transformTableData"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, nextTick, ref, watch } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { queryPostUrlList } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
|
||||||
|
type TabItem = {
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
type?: string;
|
||||||
|
tableUrl: string;
|
||||||
|
filterProps?: {
|
||||||
|
params?: Record<string, any>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tabsItems: TabItem[];
|
||||||
|
isActive: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
const tableRef = ref();
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const requestLoading = ref(false);
|
||||||
|
const activeTabKey = ref(props.tabsItems?.[0]?.key || '');
|
||||||
|
const searchParams = ref({
|
||||||
|
year: String(new Date().getFullYear())
|
||||||
|
});
|
||||||
|
|
||||||
|
const baseColumns = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
dataIndex: 'rowId',
|
||||||
|
width: 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '告警设施',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
width: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '告警时间',
|
||||||
|
dataIndex: 'tms',
|
||||||
|
width: 220,
|
||||||
|
customRender: ({ text }: any) => (text ? text : '-')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '异常值',
|
||||||
|
dataIndex: 'ssz',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '监测要素',
|
||||||
|
dataIndex: 'ysName',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '判断标准',
|
||||||
|
dataIndex: 'bzz',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属电站',
|
||||||
|
dataIndex: 'ennm',
|
||||||
|
width: 120
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const ecologicalFlowColumns = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
dataIndex: '_index',
|
||||||
|
width: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '告警时间',
|
||||||
|
dataIndex: 'btm',
|
||||||
|
width: 220,
|
||||||
|
customRender: ({ text }: any) => (text ? text : '-')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '异常值',
|
||||||
|
dataIndex: 'av',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '限值(m³/s)',
|
||||||
|
dataIndex: 'val',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属电站',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
width: 220
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const columnsMap: Record<string, any[]> = {
|
||||||
|
SZ: baseColumns,
|
||||||
|
STLL: ecologicalFlowColumns
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentTab = computed(
|
||||||
|
() => props.tabsItems.find(item => item.key === activeTabKey.value) || null
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentColumns = computed(
|
||||||
|
() => columnsMap[activeTabKey.value] || baseColumns
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableScrollX = computed(() =>
|
||||||
|
Math.max(
|
||||||
|
currentColumns.value.reduce(
|
||||||
|
(sum: number, col: any) => sum + (col.width || 180),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
600
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getYearRange = (year: string) => ({
|
||||||
|
startDate: `${year}-01-01 00:00:00`,
|
||||||
|
endDate: `${year}-12-31 23:59:59`
|
||||||
|
});
|
||||||
|
|
||||||
|
const getStationField = () => (activeTabKey.value === 'SZ' ? 'rstcd' : 'stcd');
|
||||||
|
|
||||||
|
const buildFilter = () => {
|
||||||
|
const year = searchParams.value.year;
|
||||||
|
const stcd = modelStore.params.stcd;
|
||||||
|
|
||||||
|
if (!year || !stcd) {
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { startDate, endDate } = getYearRange(String(year));
|
||||||
|
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: getStationField(),
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: [stcd]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'btm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'btm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: endDate
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildRequestData = (params: any) => {
|
||||||
|
const tab = currentTab.value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
take: Number(params?.take || DEFAULT_PAGE_SIZE),
|
||||||
|
skip: params?.skip || 1,
|
||||||
|
params: {
|
||||||
|
...(tab?.filterProps?.params || {})
|
||||||
|
},
|
||||||
|
filter: params?.filter || buildFilter()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchTableData = async (params: any) => {
|
||||||
|
const tab = currentTab.value;
|
||||||
|
if (!tab?.tableUrl || !modelStore.params.stcd || !searchParams.value.year) {
|
||||||
|
return { data: { records: [], total: 0 } };
|
||||||
|
}
|
||||||
|
|
||||||
|
requestLoading.value = true;
|
||||||
|
try {
|
||||||
|
return await queryPostUrlList(tab.tableUrl, buildRequestData(params));
|
||||||
|
} finally {
|
||||||
|
requestLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformTableData = (res: any) => {
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const total = res?.data?.data?.total || res?.data?.total || res?.total || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records.map((item: any, index: number) => ({
|
||||||
|
...item,
|
||||||
|
_index: index + 1,
|
||||||
|
_rowKey:
|
||||||
|
item?.id ||
|
||||||
|
item?.warnId ||
|
||||||
|
`${activeTabKey.value}-${
|
||||||
|
item?.stcd || modelStore.params.stcd || 'stcd'
|
||||||
|
}-${item?.btm || index}`
|
||||||
|
})),
|
||||||
|
total
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshTable = () => {
|
||||||
|
if (!modelStore.params.stcd || !searchParams.value.year) return;
|
||||||
|
tableRef.value?.getList(buildFilter());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!searchParams.value.year) {
|
||||||
|
message.warning('请选择年份');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabChange = async (key: string) => {
|
||||||
|
activeTabKey.value = key;
|
||||||
|
await nextTick();
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
refreshTable();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.tabsItems,
|
||||||
|
newTabs => {
|
||||||
|
if (newTabs?.length) {
|
||||||
|
activeTabKey.value = newTabs.some(item => item.key === activeTabKey.value)
|
||||||
|
? activeTabKey.value
|
||||||
|
: newTabs[0].key;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd && props.isActive) {
|
||||||
|
refreshTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dzgj {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
583
frontend/src/components/MapModal/components/Ecology.vue
Normal file
583
frontend/src/components/MapModal/components/Ecology.vue
Normal file
@ -0,0 +1,583 @@
|
|||||||
|
<template>
|
||||||
|
<div class="ecology">
|
||||||
|
<div class="search-bar mb-4">
|
||||||
|
<div class="search-item">
|
||||||
|
<span>年份:</span>
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="searchParams.tm"
|
||||||
|
picker="year"
|
||||||
|
style="width: 160px"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-item">
|
||||||
|
<span>调查批次:</span>
|
||||||
|
<a-select
|
||||||
|
v-model:value="searchParams.dcpc"
|
||||||
|
style="width: 180px"
|
||||||
|
:options="batchOptions"
|
||||||
|
placeholder="请选择调查批次"
|
||||||
|
:loading="batchLoading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-item">
|
||||||
|
<span>保护类型:</span>
|
||||||
|
<a-select
|
||||||
|
v-model:value="searchParams.ty"
|
||||||
|
style="width: 180px"
|
||||||
|
:options="protectTypeOptions"
|
||||||
|
placeholder="请选择保护类型"
|
||||||
|
:loading="protectTypeLoading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-item">
|
||||||
|
<span>渔获物种类:</span>
|
||||||
|
<a-input
|
||||||
|
v-model:value="searchParams.name"
|
||||||
|
style="width: 180px"
|
||||||
|
placeholder="请输入渔获物种类"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-tabs v-model:activeKey="activeTabKey" @change="handleTabChange">
|
||||||
|
<a-tab-pane v-for="tab in tabsList" :key="tab.key" :tab="tab.name" />
|
||||||
|
</a-tabs>
|
||||||
|
|
||||||
|
<BasicTable
|
||||||
|
:key="activeTabKey"
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="500"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="currentColumns"
|
||||||
|
:list-url="getTableList"
|
||||||
|
row-key="_rowKey"
|
||||||
|
:transform-data="transformTableData"
|
||||||
|
>
|
||||||
|
<template #fid="{ record }">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="!p-0"
|
||||||
|
:disabled="!hasAttachment(record?.fid)"
|
||||||
|
@click="handleViewAttachment(record)"
|
||||||
|
>
|
||||||
|
查看附件
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
|
||||||
|
<CommonAttachmentModal
|
||||||
|
v-model:open="attachmentModalVisible"
|
||||||
|
:fid="currentAttachmentFid"
|
||||||
|
title="查看附件"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, nextTick, ref, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
getEcologyBatch,
|
||||||
|
getEcologyList,
|
||||||
|
getEcologyList1,
|
||||||
|
getEcologyList2,
|
||||||
|
getEcologyList3,
|
||||||
|
getEcologyProtectType,
|
||||||
|
getEcologyYear
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import CommonAttachmentModal from './CommonAttachmentModal.vue';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: { type: Boolean, default: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
const ALL_VALUE = '';
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const tableRef = ref();
|
||||||
|
const batchLoading = ref(false);
|
||||||
|
const protectTypeLoading = ref(false);
|
||||||
|
const attachmentModalVisible = ref(false);
|
||||||
|
const currentAttachmentFid = ref('');
|
||||||
|
|
||||||
|
const searchParams = ref({
|
||||||
|
tm: null as string | null,
|
||||||
|
dcpc: ALL_VALUE,
|
||||||
|
ty: ALL_VALUE,
|
||||||
|
name: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeTabKey = ref('fish');
|
||||||
|
|
||||||
|
const tabsList = [
|
||||||
|
{ key: 'fish', name: '调查鱼类' },
|
||||||
|
{ key: 'waterTemp', name: '水温' },
|
||||||
|
{ key: 'waterQuality', name: '水质' },
|
||||||
|
{ key: 'flowRate', name: '流速(m/s)' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const columnsMap: Record<string, any[]> = {
|
||||||
|
fish: [
|
||||||
|
{
|
||||||
|
title: '调查鱼类',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '调查批次',
|
||||||
|
dataIndex: 'dcpc',
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '渔获物种类',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '鱼类规格',
|
||||||
|
dataIndex: 'fsz',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '渔获物数量(尾)',
|
||||||
|
dataIndex: 'fcnt',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '性腺发育期',
|
||||||
|
dataIndex: 'fwgh',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '早期资源量和种类',
|
||||||
|
dataIndex: 'fwgh',
|
||||||
|
width: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '现场影像',
|
||||||
|
dataIndex: 'fid',
|
||||||
|
width: 120,
|
||||||
|
slots: { customRender: 'fid' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
waterTemp: [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水温(℃)',
|
||||||
|
dataIndex: 'wt',
|
||||||
|
width: 160
|
||||||
|
}
|
||||||
|
],
|
||||||
|
waterQuality: [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'PH',
|
||||||
|
dataIndex: 'ph',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '溶解氧(mg/L)',
|
||||||
|
dataIndex: 'dox',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '电导率(μS/cm)',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 180
|
||||||
|
}
|
||||||
|
],
|
||||||
|
flowRate: [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '流速(m/s)',
|
||||||
|
dataIndex: 'flowrate',
|
||||||
|
width: 160
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const listUrlMap: Record<string, (params: any) => Promise<any>> = {
|
||||||
|
fish: getEcologyList,
|
||||||
|
waterTemp: getEcologyList1,
|
||||||
|
waterQuality: getEcologyList2,
|
||||||
|
flowRate: getEcologyList3
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentColumns = computed(() => columnsMap[activeTabKey.value] || []);
|
||||||
|
const currentListUrl = computed(
|
||||||
|
() => listUrlMap[activeTabKey.value] || getEcologyList
|
||||||
|
);
|
||||||
|
const tableScrollX = computed(() =>
|
||||||
|
Math.max(
|
||||||
|
currentColumns.value.reduce(
|
||||||
|
(sum: number, col: any) => sum + (col.width || 120),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
800
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const batchOptions = ref([
|
||||||
|
{
|
||||||
|
label: '全部',
|
||||||
|
value: ALL_VALUE
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const protectTypeOptions = ref([
|
||||||
|
{
|
||||||
|
label: '全部',
|
||||||
|
value: ALL_VALUE
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getYearRange = (year: string) => ({
|
||||||
|
startDate: `${year}-01-01 00:00:00`,
|
||||||
|
endDate: `${year}-12-31 23:59:59`
|
||||||
|
});
|
||||||
|
|
||||||
|
const getBatchDateRange = (year: string) => ({
|
||||||
|
startDate: `${year}-01-01`,
|
||||||
|
endDate: `${year}-12-31`
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDefaultYear = (yearList: any[]) => {
|
||||||
|
const firstItem = yearList?.[0];
|
||||||
|
if (typeof firstItem === 'string' || typeof firstItem === 'number') {
|
||||||
|
return String(firstItem);
|
||||||
|
}
|
||||||
|
return firstItem?.yr || firstItem?.tm || firstItem?.year || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasAttachment = (fid: unknown) =>
|
||||||
|
!!fid && String(fid).trim() !== '' && String(fid).trim() !== '-';
|
||||||
|
|
||||||
|
const handleViewAttachment = (record: any) => {
|
||||||
|
if (!hasAttachment(record?.fid)) {
|
||||||
|
message.warning('该记录无附件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentAttachmentFid.value = String(record.fid);
|
||||||
|
attachmentModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFilter = () => {
|
||||||
|
const filters: any[] = [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: getYearRange(String(searchParams.value.tm)).startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: getYearRange(String(searchParams.value.tm)).endDate
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (searchParams.value.dcpc) {
|
||||||
|
filters.push({
|
||||||
|
field: 'dcpc',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: searchParams.value.dcpc
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchParams.value.ty) {
|
||||||
|
filters.push({
|
||||||
|
field: 'ty',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: searchParams.value.ty
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
searchParams.value.name &&
|
||||||
|
String(searchParams.value.name).trim() !== ''
|
||||||
|
) {
|
||||||
|
filters.push({
|
||||||
|
field: 'name',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: searchParams.value.name.trim()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformTableData = (res: any) => {
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const total = res?.data?.data?.total || res?.data?.total || res?.total || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records.map((item: any, index: number) => ({
|
||||||
|
...item,
|
||||||
|
_rowKey:
|
||||||
|
item?.id ||
|
||||||
|
item?.tm ||
|
||||||
|
`${activeTabKey.value}-${
|
||||||
|
item?.stcd || modelStore.params.stcd || 'stcd'
|
||||||
|
}-${index}`
|
||||||
|
})),
|
||||||
|
total
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTableList = (params: any) => {
|
||||||
|
const take = Number(params?.take || DEFAULT_PAGE_SIZE);
|
||||||
|
const currentPage = Number(params?.skip || 1);
|
||||||
|
const skip = Math.max(currentPage - 1, 0) * take;
|
||||||
|
|
||||||
|
return currentListUrl.value({
|
||||||
|
...params,
|
||||||
|
take,
|
||||||
|
skip,
|
||||||
|
filter: params?.filter || buildFilter()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshTable = () => {
|
||||||
|
if (!modelStore.params.stcd || !searchParams.value.tm) return;
|
||||||
|
tableRef.value?.getList(buildFilter());
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadYearList = async () => {
|
||||||
|
const currentYear = String(new Date().getFullYear());
|
||||||
|
if (!modelStore.params.stcd) {
|
||||||
|
searchParams.value.tm = currentYear;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res: any = await getEcologyYear({
|
||||||
|
stcd: modelStore.params.stcd
|
||||||
|
});
|
||||||
|
const yearData = Array.isArray(res?.data?.data)
|
||||||
|
? res.data.data
|
||||||
|
: Array.isArray(res?.data?.records)
|
||||||
|
? res.data.records
|
||||||
|
: Array.isArray(res?.data)
|
||||||
|
? res.data
|
||||||
|
: [];
|
||||||
|
const defaultYear = getDefaultYear(yearData) || currentYear;
|
||||||
|
searchParams.value.tm = String(defaultYear);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取年份列表失败:', error);
|
||||||
|
searchParams.value.tm = currentYear;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadBatchOptions = async () => {
|
||||||
|
if (!modelStore.params.stcd || !searchParams.value.tm) return;
|
||||||
|
batchLoading.value = true;
|
||||||
|
try {
|
||||||
|
const yearRange = getBatchDateRange(String(searchParams.value.tm));
|
||||||
|
const res: any = await getEcologyBatch({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: yearRange.startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: yearRange.endDate
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
group: [{ dir: 'des', field: 'dcpc' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
const records = res?.data?.data || res?.data?.records || res?.data || [];
|
||||||
|
batchOptions.value = [
|
||||||
|
{
|
||||||
|
label: '全部',
|
||||||
|
value: ALL_VALUE
|
||||||
|
},
|
||||||
|
...records
|
||||||
|
.map((item: any) => {
|
||||||
|
const value = item?.items?.[0]?.DCPC || item?.key || item?.dcpc || '';
|
||||||
|
return value
|
||||||
|
? {
|
||||||
|
label: value,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
];
|
||||||
|
searchParams.value.dcpc = ALL_VALUE;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取调查批次失败:', error);
|
||||||
|
batchOptions.value = [
|
||||||
|
{
|
||||||
|
label: '全部',
|
||||||
|
value: ALL_VALUE
|
||||||
|
}
|
||||||
|
];
|
||||||
|
searchParams.value.dcpc = ALL_VALUE;
|
||||||
|
} finally {
|
||||||
|
batchLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadProtectTypeOptions = async () => {
|
||||||
|
protectTypeLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await getEcologyProtectType({
|
||||||
|
dictCode: 'FISHTY'
|
||||||
|
});
|
||||||
|
const records = res?.data?.data || res?.data?.records || res?.data || [];
|
||||||
|
|
||||||
|
protectTypeOptions.value = [
|
||||||
|
{
|
||||||
|
label: '全部',
|
||||||
|
value: ALL_VALUE
|
||||||
|
},
|
||||||
|
...records
|
||||||
|
.filter((item: any) => String(item?.level) !== '1')
|
||||||
|
.map((item: any) => ({
|
||||||
|
label: item?.dictMeaning,
|
||||||
|
value: item?.dictValue
|
||||||
|
}))
|
||||||
|
.filter((item: any) => item.label && item.value)
|
||||||
|
];
|
||||||
|
searchParams.value.ty = ALL_VALUE;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取保护类型失败:', error);
|
||||||
|
protectTypeOptions.value = [
|
||||||
|
{
|
||||||
|
label: '全部',
|
||||||
|
value: ALL_VALUE
|
||||||
|
}
|
||||||
|
];
|
||||||
|
searchParams.value.ty = ALL_VALUE;
|
||||||
|
} finally {
|
||||||
|
protectTypeLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initData = async () => {
|
||||||
|
await loadYearList();
|
||||||
|
await Promise.all([loadBatchOptions(), loadProtectTypeOptions()]);
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!searchParams.value.tm) {
|
||||||
|
message.warning('请选择年份');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabChange = async () => {
|
||||||
|
await nextTick();
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
initData();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => searchParams.value.tm,
|
||||||
|
(newYear, oldYear) => {
|
||||||
|
if (newYear && newYear !== oldYear) {
|
||||||
|
loadBatchOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd) {
|
||||||
|
hasLoaded.value = false;
|
||||||
|
searchParams.value = {
|
||||||
|
tm: null,
|
||||||
|
dcpc: ALL_VALUE,
|
||||||
|
ty: ALL_VALUE,
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ecology {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.search-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
259
frontend/src/components/MapModal/components/Normal.vue
Normal file
259
frontend/src/components/MapModal/components/Normal.vue
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
<template>
|
||||||
|
<div class="normal">
|
||||||
|
<div class="search-bar mb-4">
|
||||||
|
<span>年份:</span>
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="searchParams.tm"
|
||||||
|
picker="year"
|
||||||
|
style="width: 160px"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY"
|
||||||
|
/>
|
||||||
|
<a-button class="ml-4" type="primary" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="500"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:list-url="getTableList"
|
||||||
|
row-key="_rowKey"
|
||||||
|
:transform-data="transformTableData"
|
||||||
|
>
|
||||||
|
<template #fid="{ record }">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="!p-0"
|
||||||
|
:disabled="!hasAttachment(record?.fid)"
|
||||||
|
@click="handleViewAttachment(record)"
|
||||||
|
>
|
||||||
|
查看附件
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
|
||||||
|
<CommonAttachmentModal
|
||||||
|
v-model:open="attachmentModalVisible"
|
||||||
|
:fid="currentAttachmentFid"
|
||||||
|
title="查看附件"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { getNormal2List, getNormal2Year } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import CommonAttachmentModal from './CommonAttachmentModal.vue';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: { type: Boolean, default: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const tableRef = ref();
|
||||||
|
const attachmentModalVisible = ref(false);
|
||||||
|
const currentAttachmentFid = ref('');
|
||||||
|
|
||||||
|
const searchParams = ref({
|
||||||
|
tm: null as string | null
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableColumns = ref([
|
||||||
|
{
|
||||||
|
title: '种类',
|
||||||
|
dataIndex: 'tetp',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '放生日期',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '救助数量(只)',
|
||||||
|
dataIndex: 'tecnt',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '放生地点',
|
||||||
|
dataIndex: 'stlc',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '放生照片',
|
||||||
|
dataIndex: 'fid',
|
||||||
|
width: 120,
|
||||||
|
slots: { customRender: 'fid' }
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tableScrollX = ref(320);
|
||||||
|
|
||||||
|
const getDefaultYear = (yearList: any[]) => {
|
||||||
|
const firstItem = yearList?.[0];
|
||||||
|
if (typeof firstItem === 'string' || typeof firstItem === 'number') {
|
||||||
|
return String(firstItem);
|
||||||
|
}
|
||||||
|
return firstItem?.yr || firstItem?.tm || firstItem?.year || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getYearRange = (year: string) => ({
|
||||||
|
startDate: `${year}-01-01 00:00:00`,
|
||||||
|
endDate: `${year}-12-31 23:59:59`
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasAttachment = (fid: unknown) =>
|
||||||
|
!!fid && String(fid).trim() !== '' && String(fid).trim() !== '-';
|
||||||
|
|
||||||
|
const handleViewAttachment = (record: any) => {
|
||||||
|
if (!hasAttachment(record?.fid)) {
|
||||||
|
message.warning('该记录无附件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentAttachmentFid.value = String(record.fid);
|
||||||
|
attachmentModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformTableData = (res: any) => {
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const total = res?.data?.data?.total || res?.data?.total || res?.total || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records.map((item: any, index: number) => ({
|
||||||
|
...item,
|
||||||
|
_rowKey:
|
||||||
|
item?.id ||
|
||||||
|
item?.fid ||
|
||||||
|
`${item?.stcd || 'stcd'}-${
|
||||||
|
item?.tm || searchParams.value.tm || 'tm'
|
||||||
|
}-${index}`
|
||||||
|
})),
|
||||||
|
total
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getYearList = async () => {
|
||||||
|
if (!modelStore.params.stcd) return;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res: any = await getNormal2Year(params);
|
||||||
|
const yearData = res?.data?.data || res?.data?.records || res?.data || [];
|
||||||
|
const defaultYear =
|
||||||
|
getDefaultYear(yearData) || String(new Date().getFullYear());
|
||||||
|
|
||||||
|
searchParams.value.tm = String(defaultYear);
|
||||||
|
refreshTable();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取年份列表失败:', error);
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFilter = () => ({
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: getYearRange(String(searchParams.value.tm)).startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: getYearRange(String(searchParams.value.tm)).endDate
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTableList = (params: any) => {
|
||||||
|
const take = Number(params?.take || DEFAULT_PAGE_SIZE);
|
||||||
|
const currentPage = Number(params?.skip || 1);
|
||||||
|
const skip = Math.max(currentPage - 1, 0) * take;
|
||||||
|
|
||||||
|
return getNormal2List({
|
||||||
|
...params,
|
||||||
|
take,
|
||||||
|
skip,
|
||||||
|
filter: params?.filter || buildFilter()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshTable = () => {
|
||||||
|
if (!modelStore.params.stcd || !searchParams.value.tm) return;
|
||||||
|
tableRef.value?.getList(buildFilter());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!searchParams.value.tm) {
|
||||||
|
message.warning('请选择年份');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
getYearList();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd) {
|
||||||
|
hasLoaded.value = false;
|
||||||
|
searchParams.value = { tm: null };
|
||||||
|
getYearList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.normal {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { h, ref, watch } from 'vue';
|
||||||
import {
|
import {
|
||||||
getNormalAddedSituationYear,
|
getNormalAddedSituationYear,
|
||||||
getNormalAddedSituation
|
getNormalAddedSituation
|
||||||
@ -67,6 +67,33 @@ const searchParams = ref({
|
|||||||
const loadFtp = ref(false);
|
const loadFtp = ref(false);
|
||||||
const ftpList = ref<any[]>([]);
|
const ftpList = ref<any[]>([]);
|
||||||
|
|
||||||
|
const isEmptyValue = (value: unknown) =>
|
||||||
|
value == null || value === '-' || String(value).trim() === '';
|
||||||
|
|
||||||
|
const formatRangeOrDecimal = (value: unknown, digits: number) => {
|
||||||
|
if (isEmptyValue(value)) return '-';
|
||||||
|
|
||||||
|
const text = String(value).trim();
|
||||||
|
if (text.includes('~')) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numericValue = Number(text);
|
||||||
|
return Number.isNaN(numericValue) ? text : numericValue.toFixed(digits);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDirectionList = (list: any[]) => {
|
||||||
|
if (!Array.isArray(list) || list.length === 0) return '-';
|
||||||
|
|
||||||
|
return list.map(item => {
|
||||||
|
const name = item?.flstnm || '-';
|
||||||
|
const count = isEmptyValue(item?.flfnct)
|
||||||
|
? '-'
|
||||||
|
: `${(Number(item.flfnct) / 10000).toFixed(4)}万尾`;
|
||||||
|
return `${name}:${count}`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const tableColumns = ref([
|
const tableColumns = ref([
|
||||||
{
|
{
|
||||||
@ -97,11 +124,33 @@ const tableColumns = ref([
|
|||||||
dataIndex: 'directionVoList',
|
dataIndex: 'directionVoList',
|
||||||
width: 150,
|
width: 150,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (text?.length > 0) {
|
const lines = formatDirectionList(text);
|
||||||
return text[0].flstnm + ':' + Number(text[0].flfnct) / 10000;
|
if (lines === '-') return '-';
|
||||||
} else {
|
|
||||||
return '-';
|
return h(
|
||||||
}
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
lineHeight: '22px',
|
||||||
|
wordBreak: 'break-all'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lines.map((line, index) =>
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
key: `${line}-${index}`,
|
||||||
|
style: {
|
||||||
|
padding: '6px 0',
|
||||||
|
borderBottom:
|
||||||
|
index === lines.length - 1 ? 'none' : '1px solid #f0f0f0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
line
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -112,8 +161,7 @@ const tableColumns = ref([
|
|||||||
dataIndex: 'fsz',
|
dataIndex: 'fsz',
|
||||||
width: 150,
|
width: 150,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (text == null || text === '-') return '-';
|
return formatRangeOrDecimal(text, 2);
|
||||||
return Number(text).toFixed(2);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -121,8 +169,7 @@ const tableColumns = ref([
|
|||||||
dataIndex: 'fwet',
|
dataIndex: 'fwet',
|
||||||
width: 150,
|
width: 150,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
if (text == null || text === '-') return '-';
|
return text == null || text === '-' ? '-' : text;
|
||||||
return Number(text).toFixed(2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -0,0 +1,307 @@
|
|||||||
|
<template>
|
||||||
|
<div class="normal-data-monitoring2">
|
||||||
|
<div class="search-bar mb-4">
|
||||||
|
<span>年份:</span>
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="searchParams.tm"
|
||||||
|
picker="year"
|
||||||
|
style="width: 160px"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY"
|
||||||
|
/>
|
||||||
|
<a-button class="ml-4" type="primary" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="500"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:list-url="getTableList"
|
||||||
|
row-key="_rowKey"
|
||||||
|
:transform-data="transformTableData"
|
||||||
|
>
|
||||||
|
<template #fid="{ record }">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="!p-0"
|
||||||
|
:disabled="!hasAttachment(record?.fid)"
|
||||||
|
@click="handleViewAttachment(record)"
|
||||||
|
>
|
||||||
|
查看附件
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
|
||||||
|
<CommonAttachmentModal
|
||||||
|
v-model:open="attachmentModalVisible"
|
||||||
|
:fid="currentAttachmentFid"
|
||||||
|
title="查看附件"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
getNormalDataMonitoring2List,
|
||||||
|
getNormalDataMonitoring2Year
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import CommonAttachmentModal from './CommonAttachmentModal.vue';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: { type: Boolean, default: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const tableRef = ref();
|
||||||
|
const attachmentModalVisible = ref(false);
|
||||||
|
const currentAttachmentFid = ref('');
|
||||||
|
|
||||||
|
const searchParams = ref({
|
||||||
|
tm: null as string | null
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableColumns = ref([
|
||||||
|
{
|
||||||
|
title: '植物园名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '年份',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '保护种类',
|
||||||
|
dataIndex: 'tetp',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '单位',
|
||||||
|
dataIndex: 'unit',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实际种植/种植要求',
|
||||||
|
dataIndex: 'surnum',
|
||||||
|
width: 160,
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
const tecnt =
|
||||||
|
record?.tecnt !== undefined &&
|
||||||
|
record?.tecnt !== null &&
|
||||||
|
String(record.tecnt).trim() !== ''
|
||||||
|
? record.tecnt
|
||||||
|
: '-';
|
||||||
|
const transplant =
|
||||||
|
record?.transplant !== undefined &&
|
||||||
|
record?.transplant !== null &&
|
||||||
|
String(record.transplant).trim() !== ''
|
||||||
|
? record.transplant
|
||||||
|
: '-';
|
||||||
|
return `${tecnt}/${transplant}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '存活数量/移栽规模',
|
||||||
|
dataIndex: 'treeNum',
|
||||||
|
width: 160,
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
const surnum =
|
||||||
|
record?.surnum !== undefined &&
|
||||||
|
record?.surnum !== null &&
|
||||||
|
String(record.surnum).trim() !== ''
|
||||||
|
? record.surnum
|
||||||
|
: '-';
|
||||||
|
const plantask =
|
||||||
|
record?.plantask !== undefined &&
|
||||||
|
record?.plantask !== null &&
|
||||||
|
String(record.plantask).trim() !== ''
|
||||||
|
? record.plantask
|
||||||
|
: '-';
|
||||||
|
return `${surnum}/${plantask}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '存活率(%)',
|
||||||
|
dataIndex: 'rate',
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
if (
|
||||||
|
text === undefined ||
|
||||||
|
text === null ||
|
||||||
|
String(text).trim() === '' ||
|
||||||
|
Number.isNaN(Number(text))
|
||||||
|
) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
return Number(text).toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '附件',
|
||||||
|
dataIndex: 'fid',
|
||||||
|
width: 120,
|
||||||
|
slots: { customRender: 'fid' }
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tableScrollX = ref(300);
|
||||||
|
|
||||||
|
const getDefaultYear = (yearList: any[]) => {
|
||||||
|
const firstItem = yearList?.[0];
|
||||||
|
if (typeof firstItem === 'string' || typeof firstItem === 'number') {
|
||||||
|
return String(firstItem);
|
||||||
|
}
|
||||||
|
return firstItem?.yr || firstItem?.tm || firstItem?.year || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasAttachment = (fid: unknown) =>
|
||||||
|
!!fid && String(fid).trim() !== '' && String(fid).trim() !== '-';
|
||||||
|
|
||||||
|
const handleViewAttachment = (record: any) => {
|
||||||
|
if (!hasAttachment(record?.fid)) {
|
||||||
|
message.warning('该记录无附件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentAttachmentFid.value = String(record.fid);
|
||||||
|
attachmentModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformTableData = (res: any) => {
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const total = res?.data?.data?.total || res?.data?.total || res?.total || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records.map((item: any, index: number) => ({
|
||||||
|
...item,
|
||||||
|
_rowKey:
|
||||||
|
item?.id ||
|
||||||
|
item?.fid ||
|
||||||
|
`${item?.stcd || 'stcd'}-${
|
||||||
|
item?.tm || searchParams.value.tm || 'tm'
|
||||||
|
}-${index}`
|
||||||
|
})),
|
||||||
|
total
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getYearList = async () => {
|
||||||
|
if (!modelStore.params.stcd) return;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res: any = await getNormalDataMonitoring2Year(params);
|
||||||
|
const yearData = res?.data?.data || res?.data?.records || res?.data || [];
|
||||||
|
const defaultYear =
|
||||||
|
getDefaultYear(yearData) || String(new Date().getFullYear());
|
||||||
|
|
||||||
|
searchParams.value.tm = String(defaultYear);
|
||||||
|
refreshTable();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取年份列表失败:', error);
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFilter = () => ({
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: [modelStore.params.stcd]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: searchParams.value.tm
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTableList = (params: any) => {
|
||||||
|
const take = Number(params?.take || DEFAULT_PAGE_SIZE);
|
||||||
|
const currentPage = Number(params?.skip || 1);
|
||||||
|
const skip = Math.max(currentPage - 1, 0) * take;
|
||||||
|
|
||||||
|
return getNormalDataMonitoring2List({
|
||||||
|
...params,
|
||||||
|
take,
|
||||||
|
skip,
|
||||||
|
filter: params?.filter || buildFilter()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshTable = () => {
|
||||||
|
if (!modelStore.params.stcd || !searchParams.value.tm) return;
|
||||||
|
tableRef.value?.getList(buildFilter());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!searchParams.value.tm) {
|
||||||
|
message.warning('请选择年份');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
getYearList();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd) {
|
||||||
|
hasLoaded.value = false;
|
||||||
|
searchParams.value = { tm: null };
|
||||||
|
getYearList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.normal-data-monitoring2 {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -359,12 +359,12 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'fpitocintype',
|
name: 'fishsrc',
|
||||||
label: '催产剂种类',
|
label: '亲鱼来源',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'FPITOCINTYPE'
|
dictCode: 'BSSR'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1550,9 +1550,9 @@ export const qinYuPeiYuTouWei = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndexRaw: '',
|
dataIndexRaw: '',
|
||||||
key: 'ftype',
|
key: 'ftypeName',
|
||||||
title: '饲料种类',
|
title: '饲料种类',
|
||||||
dataIndex: 'ftype',
|
dataIndex: 'ftypeName',
|
||||||
children: [],
|
children: [],
|
||||||
visible: true,
|
visible: true,
|
||||||
fixed: '',
|
fixed: '',
|
||||||
@ -3868,9 +3868,9 @@ export const miaoZhongJiLu = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndexRaw: '',
|
dataIndexRaw: '',
|
||||||
key: 'feedtp',
|
key: 'ftypeName',
|
||||||
title: '饲料种类',
|
title: '饲料种类',
|
||||||
dataIndex: 'feedtp',
|
dataIndex: 'ftypeName',
|
||||||
children: [],
|
children: [],
|
||||||
visible: true,
|
visible: true,
|
||||||
fixed: '',
|
fixed: '',
|
||||||
|
|||||||
710
frontend/src/components/MapModal/components/ProcessDiagram.vue
Normal file
710
frontend/src/components/MapModal/components/ProcessDiagram.vue
Normal file
@ -0,0 +1,710 @@
|
|||||||
|
<template>
|
||||||
|
<div class="process-diagram">
|
||||||
|
<div class="top-panel">
|
||||||
|
<div class="stage-panel">
|
||||||
|
<div class="panel-header panel-inline">
|
||||||
|
<div class="panel-title w-[44px]">过程:</div>
|
||||||
|
<a-slider
|
||||||
|
class="stage-slider"
|
||||||
|
:min="0"
|
||||||
|
:max="stageList.length - 1"
|
||||||
|
:step="1"
|
||||||
|
:marks="sliderMarks"
|
||||||
|
:value="activeStageIndex"
|
||||||
|
:tooltip-open="false"
|
||||||
|
@change="handleStageClick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="month-panel">
|
||||||
|
<div class="panel-title">月份:</div>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="selectedRange"
|
||||||
|
class="month-picker"
|
||||||
|
:allow-clear="false"
|
||||||
|
picker="month"
|
||||||
|
format="YYYY-MM"
|
||||||
|
@change="handleRangeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="content-body">
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="chartData.length === 0"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<BasicTable
|
||||||
|
:scrollY="480"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:data="tableData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
ref,
|
||||||
|
watch
|
||||||
|
} from 'vue';
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import {
|
||||||
|
getProcessDiagramFishFry,
|
||||||
|
getProcessDiagramHatch,
|
||||||
|
getProcessDiagramParentFish
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
|
||||||
|
type StageKey = 'parentFish' | 'spawning' | 'hatching' | 'fishFry';
|
||||||
|
|
||||||
|
interface StageItem {
|
||||||
|
key: StageKey;
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
fetcher: (params: any) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoteColumn {
|
||||||
|
title: string;
|
||||||
|
dataIndex: string;
|
||||||
|
key?: string;
|
||||||
|
visible?: boolean;
|
||||||
|
dataType?: string;
|
||||||
|
dataFormat?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: { type: Boolean, default: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const stageList: StageItem[] = [
|
||||||
|
{
|
||||||
|
key: 'parentFish',
|
||||||
|
label: '亲鱼',
|
||||||
|
value: 0,
|
||||||
|
fetcher: params => getProcessDiagramParentFish(params)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'spawning',
|
||||||
|
label: '产卵',
|
||||||
|
value: 1,
|
||||||
|
fetcher: params => getProcessDiagramHatch(params, 1)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'hatching',
|
||||||
|
label: '孵化',
|
||||||
|
value: 2,
|
||||||
|
fetcher: params => getProcessDiagramHatch(params, 2)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fishFry',
|
||||||
|
label: '鱼苗',
|
||||||
|
value: 3,
|
||||||
|
fetcher: params => getProcessDiagramFishFry(params)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const sliderMarks = stageList.reduce<Record<number, string>>((result, item) => {
|
||||||
|
result[item.value] = item.label;
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const activeStageIndex = ref(0);
|
||||||
|
const createDefaultRange = () =>
|
||||||
|
[dayjs().startOf('month'), dayjs().startOf('month')] as [Dayjs, Dayjs];
|
||||||
|
const selectedRange = ref<[Dayjs, Dayjs]>(createDefaultRange());
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const tableData = ref<Record<string, any>[]>([]);
|
||||||
|
const chartData = ref<Record<string, any>[]>([]);
|
||||||
|
const tableColumns = ref<any[]>([]);
|
||||||
|
const tableScrollX = ref(0);
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
let currentRequestId = 0;
|
||||||
|
|
||||||
|
const currentStage = computed(
|
||||||
|
() =>
|
||||||
|
stageList.find(item => item.value === activeStageIndex.value) ||
|
||||||
|
stageList[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
const colorList = [
|
||||||
|
'#1677ff',
|
||||||
|
'#52c41a',
|
||||||
|
'#fa8c16',
|
||||||
|
'#722ed1',
|
||||||
|
'#13c2c2',
|
||||||
|
'#eb2f96',
|
||||||
|
'#2f54eb',
|
||||||
|
'#fa541c'
|
||||||
|
];
|
||||||
|
|
||||||
|
const clearData = () => {
|
||||||
|
tableData.value = [];
|
||||||
|
chartData.value = [];
|
||||||
|
tableColumns.value = [];
|
||||||
|
tableScrollX.value = 0;
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildParams = () => ({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: selectedRange.value[0]
|
||||||
|
.startOf('month')
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: selectedRange.value[1]
|
||||||
|
.endOf('month')
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'dt', dir: 'desc' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizeResult = (response: any) => {
|
||||||
|
const data = response?.data?.data;
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data[0] || {};
|
||||||
|
}
|
||||||
|
return data || response?.data || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValidDateValue = (value: unknown) => dayjs(value as any).isValid();
|
||||||
|
|
||||||
|
const isNumericValue = (value: unknown) => {
|
||||||
|
if (value == null || value === '') return false;
|
||||||
|
if (typeof value === 'number') return Number.isFinite(value);
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return false;
|
||||||
|
return !Number.isNaN(Number(trimmed));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const guessTimeField = (
|
||||||
|
columns: RemoteColumn[],
|
||||||
|
dataSource: Record<string, any>[]
|
||||||
|
) => {
|
||||||
|
const priorityColumns = columns.find(column =>
|
||||||
|
['dt', 'tm', 'date', 'time'].includes(
|
||||||
|
String(column.dataIndex).toLowerCase()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (priorityColumns) {
|
||||||
|
return priorityColumns.dataIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateTypeColumn = columns.find(
|
||||||
|
column => column.dataType === 'date' || column.dataType === 'datetime'
|
||||||
|
);
|
||||||
|
if (dateTypeColumn) {
|
||||||
|
return dateTypeColumn.dataIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstRow = dataSource[0];
|
||||||
|
if (!firstRow) return '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
Object.keys(firstRow).find(key => {
|
||||||
|
const lowerKey = key.toLowerCase();
|
||||||
|
return (
|
||||||
|
['dt', 'tm', 'date', 'time'].includes(lowerKey) ||
|
||||||
|
isValidDateValue(firstRow[key])
|
||||||
|
);
|
||||||
|
}) || ''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveChartColumns = (
|
||||||
|
columns: RemoteColumn[],
|
||||||
|
dataSource: Record<string, any>[],
|
||||||
|
timeField: string
|
||||||
|
) => {
|
||||||
|
return columns.filter(column => {
|
||||||
|
if (column.visible === false || column.dataIndex === timeField)
|
||||||
|
return false;
|
||||||
|
if (['date', 'datetime'].includes(String(column.dataType).toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSource.some(row => isNumericValue(row[column.dataIndex]));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatAxisLabel = (value: unknown) => {
|
||||||
|
const parsed = dayjs(value as any);
|
||||||
|
if (!parsed.isValid()) return String(value ?? '');
|
||||||
|
return parsed.format('YYYY-MM');
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTooltipTime = (value: unknown) => {
|
||||||
|
const parsed = dayjs(value as any);
|
||||||
|
if (!parsed.isValid()) return String(value ?? '');
|
||||||
|
return parsed.format('YYYY-MM');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSeriesName = (column: RemoteColumn) =>
|
||||||
|
column.title || column.key || column.dataIndex;
|
||||||
|
|
||||||
|
const aggregateChartDataByMonth = (
|
||||||
|
dataSource: Record<string, any>[],
|
||||||
|
timeField: string,
|
||||||
|
metricColumns: RemoteColumn[]
|
||||||
|
) => {
|
||||||
|
const groupedMap = new Map<string, Record<string, any>>();
|
||||||
|
|
||||||
|
dataSource.forEach(item => {
|
||||||
|
const monthKey = formatAxisLabel(item[timeField]);
|
||||||
|
const current = groupedMap.get(monthKey) || { [timeField]: monthKey };
|
||||||
|
|
||||||
|
metricColumns.forEach(column => {
|
||||||
|
const rawValue = item[column.dataIndex];
|
||||||
|
const nextValue = isNumericValue(rawValue) ? Number(rawValue) : 0;
|
||||||
|
current[column.dataIndex] =
|
||||||
|
Number(current[column.dataIndex] || 0) + nextValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
groupedMap.set(monthKey, current);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(groupedMap.entries())
|
||||||
|
.sort((a, b) => dayjs(a[0]).valueOf() - dayjs(b[0]).valueOf())
|
||||||
|
.map(([, value]) => value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeDateFormat = (format?: string | null) => {
|
||||||
|
if (!format) return 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
return format
|
||||||
|
.replace(/yyyy/g, 'YYYY')
|
||||||
|
.replace(/dd/g, 'DD')
|
||||||
|
.replace(/hh/g, 'HH');
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTableValue = (value: unknown, column: RemoteColumn) => {
|
||||||
|
if (value == null || value === '') return '-';
|
||||||
|
|
||||||
|
if (column.dataType === 'date' || column.dataType === 'datetime') {
|
||||||
|
const parsed = dayjs(value as any);
|
||||||
|
if (!parsed.isValid()) return String(value);
|
||||||
|
return parsed.format(normalizeDateFormat(column.dataFormat));
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildTableColumns = (columns: RemoteColumn[]) => {
|
||||||
|
const visibleColumns = columns.filter(column => column.visible !== false);
|
||||||
|
|
||||||
|
tableColumns.value = visibleColumns.map(column => ({
|
||||||
|
title: column.title,
|
||||||
|
dataIndex: column.dataIndex,
|
||||||
|
key: column.key || column.dataIndex,
|
||||||
|
width: column.dataType === 'date' ? 120 : 180,
|
||||||
|
customRender: ({ record, text }: any) => {
|
||||||
|
const cellValue =
|
||||||
|
record && column.dataIndex in record ? record[column.dataIndex] : text;
|
||||||
|
return formatTableValue(cellValue, column);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
tableScrollX.value = tableColumns.value.reduce(
|
||||||
|
(total, item) => total + (item.width || 120),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = (
|
||||||
|
dataSource: Record<string, any>[],
|
||||||
|
columns: RemoteColumn[]
|
||||||
|
) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!dataSource.length || !columns.length) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeField = guessTimeField(columns, dataSource);
|
||||||
|
if (!timeField) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metricColumns = resolveChartColumns(columns, dataSource, timeField);
|
||||||
|
if (!metricColumns.length) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartRows = aggregateChartDataByMonth(
|
||||||
|
dataSource,
|
||||||
|
timeField,
|
||||||
|
metricColumns
|
||||||
|
);
|
||||||
|
const option = {
|
||||||
|
color: colorList,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any[]) => {
|
||||||
|
if (!params?.length) return '';
|
||||||
|
const index = params[0].dataIndex;
|
||||||
|
let total = 0;
|
||||||
|
let html = `<div style="margin-bottom:8px;">${formatTooltipTime(
|
||||||
|
chartRows[index]?.[timeField]
|
||||||
|
)}</div>`;
|
||||||
|
|
||||||
|
params.forEach(param => {
|
||||||
|
if (param.value == null || param.value === '') return;
|
||||||
|
total += Number(param.value) || 0;
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${param.color};"></span>
|
||||||
|
<span>${param.seriesName}:${param.value}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div style="margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.2);font-weight:600;">
|
||||||
|
总计:${total}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
width: '80%',
|
||||||
|
right: 60,
|
||||||
|
top: 10,
|
||||||
|
data: metricColumns.map(getSeriesName)
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 70,
|
||||||
|
right: 30,
|
||||||
|
top: 70,
|
||||||
|
bottom: 60
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: chartRows.map(item => item[timeField]),
|
||||||
|
axisTick: { show: false }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: { color: '#f0f0f0' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
right: 20,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: metricColumns.map((column, index) => ({
|
||||||
|
name: getSeriesName(column),
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
barMaxWidth: 24,
|
||||||
|
itemStyle: {
|
||||||
|
color: colorList[index % colorList.length]
|
||||||
|
},
|
||||||
|
data: chartRows.map(item => {
|
||||||
|
const value = item[column.dataIndex];
|
||||||
|
return isNumericValue(value) ? Number(value) : 0;
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureChartReady = async () => {
|
||||||
|
await nextTick();
|
||||||
|
if (!chartInstance) {
|
||||||
|
initChart();
|
||||||
|
}
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!modelStore.params.stcd) {
|
||||||
|
clearData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestId = ++currentRequestId;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await currentStage.value.fetcher(buildParams());
|
||||||
|
if (requestId !== currentRequestId) return;
|
||||||
|
|
||||||
|
const result = normalizeResult(response);
|
||||||
|
const columns = Array.isArray(result.columns) ? result.columns : [];
|
||||||
|
const dataSource = Array.isArray(result.dataSource)
|
||||||
|
? result.dataSource
|
||||||
|
: [];
|
||||||
|
|
||||||
|
tableData.value = dataSource;
|
||||||
|
chartData.value = dataSource;
|
||||||
|
buildTableColumns(columns);
|
||||||
|
await ensureChartReady();
|
||||||
|
updateChart(dataSource, columns);
|
||||||
|
} catch (error) {
|
||||||
|
if (requestId !== currentRequestId) return;
|
||||||
|
clearData();
|
||||||
|
console.error('获取过程图数据失败:', error);
|
||||||
|
} finally {
|
||||||
|
if (requestId === currentRequestId) {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStageClick = (value: number) => {
|
||||||
|
if (value === activeStageIndex.value) return;
|
||||||
|
activeStageIndex.value = value;
|
||||||
|
if (props.isActive) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRangeChange = (value: [Dayjs, Dayjs] | null) => {
|
||||||
|
if (!value || value.length !== 2 || !value[0] || !value[1]) {
|
||||||
|
selectedRange.value = createDefaultRange();
|
||||||
|
}
|
||||||
|
if (props.isActive) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
async active => {
|
||||||
|
if (!active) return;
|
||||||
|
await ensureChartReady();
|
||||||
|
fetchData();
|
||||||
|
setTimeout(() => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
}, 200);
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newValue, oldValue) => {
|
||||||
|
if (!props.isActive || !newValue || newValue === oldValue) return;
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
currentRequestId += 1;
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.process-diagram {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 640px;
|
||||||
|
|
||||||
|
.top-panel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: #fafafa;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #262626;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-panel {
|
||||||
|
width: 50%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-inline {
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-slider {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-slider) {
|
||||||
|
.ant-slider-mark-text {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.ant-slider-handle {
|
||||||
|
background-color: rgb(18, 92, 223);
|
||||||
|
}
|
||||||
|
.ant-slider-track {
|
||||||
|
background-color: rgb(18, 92, 223);
|
||||||
|
}
|
||||||
|
.ant-slider-dot {
|
||||||
|
border-color: rgb(18, 92, 223);
|
||||||
|
}
|
||||||
|
.ant-slider-mark {
|
||||||
|
.ant-slider-mark-text-active:not(:has(~ .ant-slider-mark-text-active)) {
|
||||||
|
color: rgb(18, 92, 223);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-slider-handle {
|
||||||
|
&::after {
|
||||||
|
box-shadow: 0 0 0 2px #125cdf;
|
||||||
|
background-color: rgb(18, 92, 223);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-panel {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
align-items: center;
|
||||||
|
width: 50%;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-picker {
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-body {
|
||||||
|
display: flex;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
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;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
435
frontend/src/components/MapModal/components/liveFootage.vue
Normal file
435
frontend/src/components/MapModal/components/liveFootage.vue
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
<template>
|
||||||
|
<div class="live-footage">
|
||||||
|
<div class="search-bar mb-4">
|
||||||
|
<span class="mr-2">时间:</span>
|
||||||
|
<a-spin :spinning="monthLoading" size="small" class="mr-4">
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="selectedMonthRange"
|
||||||
|
picker="month"
|
||||||
|
style="width: 260px"
|
||||||
|
format="YYYY-MM"
|
||||||
|
value-format="YYYY-MM"
|
||||||
|
:allowClear="false"
|
||||||
|
:disabled="monthLoading"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
<a-button
|
||||||
|
class="ml-2"
|
||||||
|
type="primary"
|
||||||
|
:loading="monthLoading && !loading"
|
||||||
|
@click="handleSearch"
|
||||||
|
>
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-spin :spinning="loading" class="video-mode-spin">
|
||||||
|
<div v-if="mediaList.length > 0" class="video-mode">
|
||||||
|
<div class="viewer-spin">
|
||||||
|
<div class="video-main">
|
||||||
|
<div v-if="activeMedia.src" class="media-display">
|
||||||
|
<LiveVideoBox
|
||||||
|
:videoData="{ url: activeMedia.src }"
|
||||||
|
height="100%"
|
||||||
|
:showTitle="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="media-empty">
|
||||||
|
<a-empty description="请选择右侧数据" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-spin">
|
||||||
|
<div class="video-sidebar">
|
||||||
|
<div class="sidebar-list">
|
||||||
|
<div
|
||||||
|
v-for="item in mediaList"
|
||||||
|
:key="item._itemKey"
|
||||||
|
class="sidebar-item"
|
||||||
|
:class="{ active: activeMediaKey === item._itemKey }"
|
||||||
|
@click="handleSelectMedia(item)"
|
||||||
|
>
|
||||||
|
<div class="item-thumb">
|
||||||
|
<img v-if="item.thumbUrl" :src="item.thumbUrl" />
|
||||||
|
<div v-else class="thumb-placeholder">
|
||||||
|
<img :src="videoCover" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-text">
|
||||||
|
{{ item.flnm }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="shared-empty">
|
||||||
|
<a-empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import LiveVideoBox from '@/views/shiPinJianKong/components/LiveVideoBox.vue';
|
||||||
|
import { getLiveFootageList, getLiveFootageYear } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import videoCover from '@/assets/images/videFm.jpg';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: { type: Boolean, default: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
type MonthRange = [string, string];
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const monthLoading = ref(false);
|
||||||
|
const selectedMonthRange = ref<MonthRange>([
|
||||||
|
dayjs().format('YYYY-MM'),
|
||||||
|
dayjs().format('YYYY-MM')
|
||||||
|
]);
|
||||||
|
const mediaList = ref<any[]>([]);
|
||||||
|
const activeMediaKey = ref('');
|
||||||
|
const activeMedia = ref<{ src: string }>({ src: '' });
|
||||||
|
const attachmentBaseUrl = `${import.meta.env.VITE_APP_ATTACHMENT_URL}/?`;
|
||||||
|
|
||||||
|
const getMonthRange = (monthRange: MonthRange) => ({
|
||||||
|
startDate: dayjs(monthRange[0], 'YYYY-MM')
|
||||||
|
.startOf('month')
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
endDate: dayjs(monthRange[1], 'YYYY-MM')
|
||||||
|
.endOf('month')
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCurrentMonthRange = (): MonthRange => {
|
||||||
|
const currentMonth = dayjs().format('YYYY-MM');
|
||||||
|
return [currentMonth, currentMonth];
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildAttachmentUrl = (path: string) => {
|
||||||
|
const value = String(path || '').trim();
|
||||||
|
if (!value) return '';
|
||||||
|
return `${attachmentBaseUrl}${value}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetActiveMedia = () => {
|
||||||
|
activeMediaKey.value = '';
|
||||||
|
activeMedia.value = { src: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectMedia = (item: any) => {
|
||||||
|
activeMediaKey.value = item._itemKey;
|
||||||
|
activeMedia.value = { src: item.mediaUrl || '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFilter = () => {
|
||||||
|
const stcd = modelStore.params.stcd;
|
||||||
|
const { startDate, endDate } = getMonthRange(selectedMonthRange.value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: endDate
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMediaList = async (rawData: any[]) => {
|
||||||
|
return rawData
|
||||||
|
.filter((item: any) => String(item?.flpth || '').trim() !== '')
|
||||||
|
.map((item: any, index: number) => {
|
||||||
|
const formattedTm = item.tm
|
||||||
|
? dayjs(item.tm).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '-';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
tm: formattedTm,
|
||||||
|
thumbUrl: buildAttachmentUrl(item?.imgPath),
|
||||||
|
mediaUrl: buildAttachmentUrl(item?.flpth),
|
||||||
|
_itemKey: `${item?.flpth}-${index}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDefaultMonthRange = async () => {
|
||||||
|
if (!modelStore.params.stcd) {
|
||||||
|
selectedMonthRange.value = getCurrentMonthRange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
monthLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await getLiveFootageYear({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const defaultTm = records?.[0]?.tm;
|
||||||
|
|
||||||
|
if (defaultTm) {
|
||||||
|
const currentMonth = dayjs(defaultTm).format('YYYY-MM');
|
||||||
|
const previousMonth = dayjs(defaultTm)
|
||||||
|
.subtract(1, 'month')
|
||||||
|
.format('YYYY-MM');
|
||||||
|
selectedMonthRange.value = [previousMonth, currentMonth];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取现场录像默认月份失败:', error);
|
||||||
|
} finally {
|
||||||
|
monthLoading.value = false;
|
||||||
|
}
|
||||||
|
if (!selectedMonthRange.value?.[0] || !selectedMonthRange.value?.[1]) {
|
||||||
|
selectedMonthRange.value = getCurrentMonthRange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (
|
||||||
|
!modelStore.params.stcd ||
|
||||||
|
!selectedMonthRange.value?.[0] ||
|
||||||
|
!selectedMonthRange.value?.[1]
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
resetActiveMedia();
|
||||||
|
const res: any = await getLiveFootageList({
|
||||||
|
filter: buildFilter()
|
||||||
|
});
|
||||||
|
const rawData = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
mediaList.value = await buildMediaList(rawData);
|
||||||
|
|
||||||
|
if (mediaList.value.length > 0) {
|
||||||
|
handleSelectMedia(mediaList.value[0]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取现场录像失败:', error);
|
||||||
|
mediaList.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initData = async () => {
|
||||||
|
await loadDefaultMonthRange();
|
||||||
|
await fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!selectedMonthRange.value?.[0] || !selectedMonthRange.value?.[1]) {
|
||||||
|
message.warning('请选择时间范围');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
initData();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd && props.isActive) {
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.live-footage {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-mode-spin {
|
||||||
|
display: block;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-mode {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-spin {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-spin :deep(.ant-spin-container),
|
||||||
|
.sidebar-spin :deep(.ant-spin-container) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-spin {
|
||||||
|
width: 320px;
|
||||||
|
height: 100%;
|
||||||
|
flex: 0 0 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-main {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #000;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.media-display {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-empty {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-list {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #1677ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-thumb {
|
||||||
|
width: 100%;
|
||||||
|
height: 160px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fafafa;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-placeholder,
|
||||||
|
.no-media {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-media {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
line-height: 1.6;
|
||||||
|
word-break: break-all;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-item.active .item-text {
|
||||||
|
color: #1677ff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shared-empty {
|
||||||
|
height: 600px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
404
frontend/src/components/MapModal/components/onSitePhoto.vue
Normal file
404
frontend/src/components/MapModal/components/onSitePhoto.vue
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
<template>
|
||||||
|
<div class="on-site-photo">
|
||||||
|
<div class="search-bar mb-4">
|
||||||
|
<span class="mr-2">时间:</span>
|
||||||
|
<a-spin :spinning="monthLoading" size="small" class="mr-4">
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="selectedMonthRange"
|
||||||
|
picker="month"
|
||||||
|
style="width: 260px"
|
||||||
|
format="YYYY-MM"
|
||||||
|
value-format="YYYY-MM"
|
||||||
|
:allowClear="false"
|
||||||
|
:disabled="monthLoading"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
<a-button
|
||||||
|
class="ml-2"
|
||||||
|
type="primary"
|
||||||
|
:loading="monthLoading && !loading"
|
||||||
|
@click="handleSearch"
|
||||||
|
>
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-spin :spinning="loading" class="photo-spin">
|
||||||
|
<div v-if="photoList.length > 0" class="photo-carousel-wrapper">
|
||||||
|
<a-button
|
||||||
|
v-show="photoList.length > 1"
|
||||||
|
class="carousel-arrow carousel-arrow-left"
|
||||||
|
@click="handlePrev"
|
||||||
|
>
|
||||||
|
<span><left-circle-outlined /></span>
|
||||||
|
</a-button>
|
||||||
|
<a-carousel ref="carouselRef">
|
||||||
|
<div
|
||||||
|
v-for="item in photoList"
|
||||||
|
:key="item._itemKey"
|
||||||
|
class="carousel-item"
|
||||||
|
>
|
||||||
|
<div class="image-container">
|
||||||
|
<img :src="item.imageUrl" :alt="item.flnm || '现场图片'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-carousel>
|
||||||
|
<a-button
|
||||||
|
v-show="photoList.length > 1"
|
||||||
|
class="carousel-arrow carousel-arrow-right"
|
||||||
|
@click="handleNext"
|
||||||
|
>
|
||||||
|
<span><right-circle-outlined /></span>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="shared-empty">
|
||||||
|
<a-empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons-vue';
|
||||||
|
import {
|
||||||
|
getLiveFootageList,
|
||||||
|
getLiveFootageYear,
|
||||||
|
postIdUrl
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: { type: Boolean, default: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
type MonthRange = [string, string];
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const monthLoading = ref(false);
|
||||||
|
const selectedMonthRange = ref<MonthRange>([
|
||||||
|
dayjs().format('YYYY-MM'),
|
||||||
|
dayjs().format('YYYY-MM')
|
||||||
|
]);
|
||||||
|
const photoList = ref<any[]>([]);
|
||||||
|
const carouselRef = ref();
|
||||||
|
|
||||||
|
const getMonthRange = (monthRange: MonthRange) => ({
|
||||||
|
startDate: dayjs(monthRange[0], 'YYYY-MM')
|
||||||
|
.startOf('month')
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
endDate: dayjs(monthRange[1], 'YYYY-MM')
|
||||||
|
.endOf('month')
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCurrentMonthRange = (): MonthRange => {
|
||||||
|
const currentMonth = dayjs().format('YYYY-MM');
|
||||||
|
return [currentMonth, currentMonth];
|
||||||
|
};
|
||||||
|
|
||||||
|
const isImageExt = (ext: string) =>
|
||||||
|
['.jpg', '.jpeg', '.png', '.webp', '.gif'].includes(
|
||||||
|
String(ext || '').toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildFilter = () => {
|
||||||
|
const stcd = modelStore.params.stcd;
|
||||||
|
const { startDate, endDate } = getMonthRange(selectedMonthRange.value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: endDate
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildPhotoList = async (rawData: any[]) => {
|
||||||
|
const rowsWithFid = rawData.filter(
|
||||||
|
(item: any) => !!String(item?.fid || '').trim()
|
||||||
|
);
|
||||||
|
const allFids: string[] = [];
|
||||||
|
|
||||||
|
rowsWithFid.forEach((item: any) => {
|
||||||
|
String(item.fid)
|
||||||
|
.split(',')
|
||||||
|
.map((fid: string) => fid.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.forEach((fid: string) => allFids.push(fid));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allFids.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id', allFids.join(','));
|
||||||
|
const mediaRes: any = await postIdUrl(formData);
|
||||||
|
const mediaMap = mediaRes?.data || {};
|
||||||
|
|
||||||
|
const list: any[] = [];
|
||||||
|
|
||||||
|
rowsWithFid.forEach((item: any, index: number) => {
|
||||||
|
const formattedTm = item.tm
|
||||||
|
? dayjs(item.tm).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '-';
|
||||||
|
|
||||||
|
String(item.fid)
|
||||||
|
.split(',')
|
||||||
|
.map((fid: string) => fid.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.forEach((fid: string, fidIndex: number) => {
|
||||||
|
const media = mediaMap[fid];
|
||||||
|
if (!media?.fullpath || !isImageExt(media?.ext)) return;
|
||||||
|
|
||||||
|
list.push({
|
||||||
|
...item,
|
||||||
|
fid,
|
||||||
|
tm: formattedTm,
|
||||||
|
imageUrl: media.fullpath,
|
||||||
|
_itemKey: `${fid}-${index}-${fidIndex}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDefaultMonthRange = async () => {
|
||||||
|
if (!modelStore.params.stcd) {
|
||||||
|
selectedMonthRange.value = getCurrentMonthRange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
monthLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await getLiveFootageYear({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const defaultTm = records?.[0]?.tm;
|
||||||
|
|
||||||
|
if (defaultTm) {
|
||||||
|
const currentMonth = dayjs(defaultTm).format('YYYY-MM');
|
||||||
|
const previousMonth = dayjs(defaultTm)
|
||||||
|
.subtract(1, 'month')
|
||||||
|
.format('YYYY-MM');
|
||||||
|
selectedMonthRange.value = [previousMonth, currentMonth];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取现场图片默认月份失败:', error);
|
||||||
|
} finally {
|
||||||
|
monthLoading.value = false;
|
||||||
|
}
|
||||||
|
if (!selectedMonthRange.value?.[0] || !selectedMonthRange.value?.[1]) {
|
||||||
|
selectedMonthRange.value = getCurrentMonthRange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (
|
||||||
|
!modelStore.params.stcd ||
|
||||||
|
!selectedMonthRange.value?.[0] ||
|
||||||
|
!selectedMonthRange.value?.[1]
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await getLiveFootageList({
|
||||||
|
filter: buildFilter()
|
||||||
|
});
|
||||||
|
const rawData = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
photoList.value = await buildPhotoList(rawData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取现场图片失败:', error);
|
||||||
|
photoList.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initData = async () => {
|
||||||
|
await loadDefaultMonthRange();
|
||||||
|
await fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!selectedMonthRange.value?.[0] || !selectedMonthRange.value?.[1]) {
|
||||||
|
message.warning('请选择时间范围');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrev = () => {
|
||||||
|
carouselRef.value?.prev();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
carouselRef.value?.next();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) {
|
||||||
|
initData();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd && props.isActive) {
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.on-site-photo {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-spin {
|
||||||
|
display: block;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-spin :deep(.ant-spin-container) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-carousel-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-arrow-left {
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-arrow-right {
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-carousel),
|
||||||
|
:deep(.slick-slider),
|
||||||
|
:deep(.slick-list),
|
||||||
|
:deep(.slick-track) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.slick-dots li button) {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.slick-dots li.slick-active button) {
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shared-empty {
|
||||||
|
height: 600px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -20,7 +20,13 @@
|
|||||||
8. 栖息地-流量监测 没有水位视频和流量视频 字段没数据
|
8. 栖息地-流量监测 没有水位视频和流量视频 字段没数据
|
||||||
9. 栖息地 水温、水质、流量没有数据没法测
|
9. 栖息地 水温、水质、流量没有数据没法测
|
||||||
10.鱼类增殖站 - 运行数据 要添加字典
|
10.鱼类增殖站 - 运行数据 要添加字典
|
||||||
|
11.珍惜植物园 - 种植要求字段不知道
|
||||||
|
12.水生调查断面 - 监测数据 不知道电导率 ,性腺发育期,早期资源量和种类
|
||||||
|
13.水电告警情况 生态流量 不知道字段 接口传参有问题
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
没弄: 环保设施
|
||||||
-->
|
-->
|
||||||
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
|
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
|
||||||
<a-select
|
<a-select
|
||||||
@ -123,6 +129,12 @@
|
|||||||
"
|
"
|
||||||
:wq-show="modelStore.wqShow"
|
:wq-show="modelStore.wqShow"
|
||||||
/>
|
/>
|
||||||
|
<!-- 监测数据 - 水电站告警情况 -->
|
||||||
|
<DZGJ
|
||||||
|
v-show="currentActiveKey === 'DZGJ'"
|
||||||
|
:is-active="currentActiveKey === 'DZGJ'"
|
||||||
|
:tabs-items="getTabChildren('DZGJ')"
|
||||||
|
/>
|
||||||
<!-- 流量监测 -->
|
<!-- 流量监测 -->
|
||||||
<FlowMeasure
|
<FlowMeasure
|
||||||
v-show="currentActiveKey === 'FlowMeasure'"
|
v-show="currentActiveKey === 'FlowMeasure'"
|
||||||
@ -171,12 +183,16 @@
|
|||||||
v-show="currentActiveKey === 'WaterTemperatureRep'"
|
v-show="currentActiveKey === 'WaterTemperatureRep'"
|
||||||
:is-active="currentActiveKey === 'WaterTemperatureRep'"
|
:is-active="currentActiveKey === 'WaterTemperatureRep'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 鱼类增殖站 - 运行数据 -->
|
<!-- 鱼类增殖站 - 运行数据 -->
|
||||||
<NormalOperationData
|
<NormalOperationData
|
||||||
v-show="currentActiveKey === 'NormalOperationData'"
|
v-show="currentActiveKey === 'NormalOperationData'"
|
||||||
:is-active="currentActiveKey === 'NormalOperationData'"
|
:is-active="currentActiveKey === 'NormalOperationData'"
|
||||||
/>
|
/>
|
||||||
|
<!-- 过程图 -->
|
||||||
|
<ProcessDiagram
|
||||||
|
v-show="currentActiveKey === 'ProcessDiagram'"
|
||||||
|
:is-active="currentActiveKey === 'ProcessDiagram'"
|
||||||
|
/>
|
||||||
<!-- 增值放流情况 -->
|
<!-- 增值放流情况 -->
|
||||||
<NormalAddedSituation
|
<NormalAddedSituation
|
||||||
v-show="currentActiveKey === 'NormalAddedSituation'"
|
v-show="currentActiveKey === 'NormalAddedSituation'"
|
||||||
@ -187,6 +203,31 @@
|
|||||||
v-show="currentActiveKey === 'NormalResearchSituation'"
|
v-show="currentActiveKey === 'NormalResearchSituation'"
|
||||||
:is-active="currentActiveKey === 'NormalResearchSituation'"
|
:is-active="currentActiveKey === 'NormalResearchSituation'"
|
||||||
/>
|
/>
|
||||||
|
<!-- 珍稀植物园 - 运行数据 -->
|
||||||
|
<NormalDataMonitoring2
|
||||||
|
v-show="currentActiveKey === 'NormalDataMonitoring2'"
|
||||||
|
:is-active="currentActiveKey === 'NormalDataMonitoring2'"
|
||||||
|
/>
|
||||||
|
<!-- 动物救助站 - 监测数据 -->
|
||||||
|
<Normal
|
||||||
|
v-show="currentActiveKey === 'Normal'"
|
||||||
|
:is-active="currentActiveKey === 'Normal'"
|
||||||
|
/>
|
||||||
|
<!-- 水生生态调查断面 - 监测数据 -->
|
||||||
|
<Ecology
|
||||||
|
v-show="currentActiveKey === 'Ecology'"
|
||||||
|
:is-active="currentActiveKey === 'Ecology'"
|
||||||
|
/>
|
||||||
|
<!-- 野生动物检测 -现场录像 -->
|
||||||
|
<liveFootage
|
||||||
|
v-show="currentActiveKey === 'liveFootage'"
|
||||||
|
:is-active="currentActiveKey === 'liveFootage'"
|
||||||
|
/>
|
||||||
|
<!-- 野生动物检测 -现场照片 -->
|
||||||
|
<onSitePhoto
|
||||||
|
v-show="currentActiveKey === 'onSitePhoto'"
|
||||||
|
:is-active="currentActiveKey === 'onSitePhoto'"
|
||||||
|
/>
|
||||||
<!-- 查看报告 -->
|
<!-- 查看报告 -->
|
||||||
<Attachment
|
<Attachment
|
||||||
v-show="currentActiveKey === 'attachment'"
|
v-show="currentActiveKey === 'attachment'"
|
||||||
@ -225,8 +266,20 @@ import FishFacilityMonitorData from './components/FishFacilityMonitorData.vue';
|
|||||||
import FishFacilityRunState from './components/FishFacilityRunState.vue'; // 运行情况
|
import FishFacilityRunState from './components/FishFacilityRunState.vue'; // 运行情况
|
||||||
// 鱼类增殖站
|
// 鱼类增殖站
|
||||||
import NormalOperationData from './components/NormalOperationData/index.vue'; // 运行数据
|
import NormalOperationData from './components/NormalOperationData/index.vue'; // 运行数据
|
||||||
|
import ProcessDiagram from './components/ProcessDiagram.vue'; // 过程图
|
||||||
import NormalAddedSituation from './components/NormalAddedSituation.vue'; // 增值放流情况
|
import NormalAddedSituation from './components/NormalAddedSituation.vue'; // 增值放流情况
|
||||||
import NormalResearchSituation from './components/NormalResearchSituation.vue'; // 科研情况
|
import NormalResearchSituation from './components/NormalResearchSituation.vue'; // 科研情况
|
||||||
|
// 珍惜植物园
|
||||||
|
import NormalDataMonitoring2 from './components/NormalDataMonitoring2.vue'; // 运行数据
|
||||||
|
// 野生动物检测 -现场录像
|
||||||
|
import liveFootage from './components/liveFootage.vue'; // 现场录像
|
||||||
|
import onSitePhoto from './components/onSitePhoto.vue'; // 现场照片
|
||||||
|
/// 动物救助站
|
||||||
|
import Normal from './components/Normal.vue'; // 监测数据
|
||||||
|
// 水生生态调查断面
|
||||||
|
import Ecology from './components/Ecology.vue'; // 监测数据
|
||||||
|
// 水电站告警情况
|
||||||
|
import DZGJ from './components/DZGJ.vue'; // 水电站告警情况
|
||||||
|
|
||||||
import FishZHFX from './components/FishZHFX.vue'; // 鱼类繁殖适宜性分析
|
import FishZHFX from './components/FishZHFX.vue'; // 鱼类繁殖适宜性分析
|
||||||
import EcologicalFlow from './components/EcologicalFlow.vue'; // 生态流量
|
import EcologicalFlow from './components/EcologicalFlow.vue'; // 生态流量
|
||||||
@ -294,8 +347,7 @@ const checkPowerStationTopicData = async (stcd: string) => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
hasPowerStatData.value =
|
hasPowerStatData.value = res?.data?.data && res?.data?.data?.length > 0;
|
||||||
res?.data?.data && res?.data?.data?.length > 0;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('查询电站专题配置失败:', error);
|
console.error('查询电站专题配置失败:', error);
|
||||||
hasPowerStatData.value = false;
|
hasPowerStatData.value = false;
|
||||||
@ -605,10 +657,10 @@ 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?.records?.length > 0) ||
|
res2?.data?.data?.length > 0 ||
|
||||||
|
res2?.data?.records?.length > 0 ||
|
||||||
modelStore.params.sttp == 'WQ'
|
modelStore.params.sttp == 'WQ'
|
||||||
) {
|
) {
|
||||||
tabsConfig.value = newTabs;
|
tabsConfig.value = newTabs;
|
||||||
|
|||||||
@ -147,11 +147,16 @@ export class MapOl implements MapInterface {
|
|||||||
this.popupManager.showPopup(undefined, undefined);
|
this.popupManager.showPopup(undefined, undefined);
|
||||||
this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
|
this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
|
||||||
console.log(detectedFeature);
|
console.log(detectedFeature);
|
||||||
modelStore.modalVisible = true;
|
if (detectedFeature.values_.sttpMap == 'ylfb') {
|
||||||
modelStore.params = detectedFeature.values_;
|
modelStore.ylfbModalVisible = true;
|
||||||
modelStore.title =
|
modelStore.params = detectedFeature.values_;
|
||||||
detectedFeature.values_.titleName ||
|
} else {
|
||||||
detectedFeature.values_.stnm + ' 详情信息';
|
modelStore.modalVisible = true;
|
||||||
|
modelStore.params = detectedFeature.values_;
|
||||||
|
modelStore.title =
|
||||||
|
detectedFeature.values_.titleName ||
|
||||||
|
detectedFeature.values_.stnm + ' 详情信息';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -242,6 +247,12 @@ export class MapOl implements MapInterface {
|
|||||||
}
|
}
|
||||||
const dynamicScale = this.getDynamicPointScale(currentZoom);
|
const dynamicScale = this.getDynamicPointScale(currentZoom);
|
||||||
const fontSize = this.getPointFontSize(dynamicScale);
|
const fontSize = this.getPointFontSize(dynamicScale);
|
||||||
|
const formattedLabelText = this.formatPointLabelText(labelText);
|
||||||
|
const labelLineCount = formattedLabelText
|
||||||
|
? formattedLabelText.split('\n').length
|
||||||
|
: 1;
|
||||||
|
const labelOffsetY =
|
||||||
|
labelLineCount > 1 ? -30 * dynamicScale : -22 * dynamicScale;
|
||||||
|
|
||||||
const styleOptions: any = {
|
const styleOptions: any = {
|
||||||
image: new Icon({
|
image: new Icon({
|
||||||
@ -254,8 +265,8 @@ export class MapOl implements MapInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
styleOptions.text = new Text({
|
styleOptions.text = new Text({
|
||||||
text: labelText,
|
text: formattedLabelText,
|
||||||
offsetY: -20 * dynamicScale,
|
offsetY: labelOffsetY,
|
||||||
font: `${fontSize}px sans-serif`,
|
font: `${fontSize}px sans-serif`,
|
||||||
fill: new Fill({ color: '#fff' }),
|
fill: new Fill({ color: '#fff' }),
|
||||||
stroke: new Stroke({ color: 'rgba(0, 0, 0, .9)', width: 2 }),
|
stroke: new Stroke({ color: 'rgba(0, 0, 0, .9)', width: 2 }),
|
||||||
@ -265,6 +276,34 @@ export class MapOl implements MapInterface {
|
|||||||
return new Style(styleOptions);
|
return new Style(styleOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点位标注最多显示两行,每行 12 个字符;超出部分在第二行末尾显示省略号。
|
||||||
|
*/
|
||||||
|
private formatPointLabelText(text: string): string {
|
||||||
|
const normalizedText = String(text || '')
|
||||||
|
.replace(/[()()]/g, '')
|
||||||
|
.trim();
|
||||||
|
if (!normalizedText) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxLineLength = 12;
|
||||||
|
if (normalizedText.length <= maxLineLength) {
|
||||||
|
return normalizedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstLine = normalizedText.slice(0, maxLineLength);
|
||||||
|
if (normalizedText.length <= maxLineLength * 2) {
|
||||||
|
return `${firstLine}\n${normalizedText.slice(maxLineLength)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secondLine = `${normalizedText.slice(
|
||||||
|
maxLineLength,
|
||||||
|
maxLineLength + 9
|
||||||
|
)}...`;
|
||||||
|
return `${firstLine}\n${secondLine}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据缩放级别计算锚点图标缩放比例,统一复用,减少样式函数中的重复判断。
|
* 根据缩放级别计算锚点图标缩放比例,统一复用,减少样式函数中的重复判断。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -269,10 +269,12 @@ export class PointLayerManager {
|
|||||||
...item,
|
...item,
|
||||||
_layerKey: targetLayerKey
|
_layerKey: targetLayerKey
|
||||||
});
|
});
|
||||||
|
|
||||||
feature.setId(item.stcd || item._id);
|
feature.setId(item.stcd || item._id);
|
||||||
feature.set('_iconUrl', iconUrl);
|
feature.set('_iconUrl', iconUrl);
|
||||||
feature.set('_labelText', titleName || stnm || ennm || '');
|
feature.set(
|
||||||
|
'_labelText',
|
||||||
|
item.sttpMap === 'ylfb' ? item.ftp || '' : titleName || stnm || ennm || ''
|
||||||
|
);
|
||||||
feature.set('_legendVisible', true);
|
feature.set('_legendVisible', true);
|
||||||
feature.set('_sttpMap', item.sttpMap);
|
feature.set('_sttpMap', item.sttpMap);
|
||||||
if (item.popupHtml) {
|
if (item.popupHtml) {
|
||||||
|
|||||||
284
frontend/src/components/ylfbModal/index.vue
Normal file
284
frontend/src/components/ylfbModal/index.vue
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:open="visible"
|
||||||
|
title="鱼类分布明细"
|
||||||
|
width="80vw"
|
||||||
|
:footer="null"
|
||||||
|
:closable="true"
|
||||||
|
@cancel="handleClose"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
class="ylfb-modal"
|
||||||
|
>
|
||||||
|
<div class="ylfb-content">
|
||||||
|
<div class="search-bar mb-4">
|
||||||
|
<span>年份:</span>
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="searchParams.tm"
|
||||||
|
picker="year"
|
||||||
|
style="width: 160px"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY"
|
||||||
|
/>
|
||||||
|
<a-button class="ml-4" type="primary" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="500"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:list-url="getTableList"
|
||||||
|
row-key="_rowKey"
|
||||||
|
:transform-data="transformTableData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import {
|
||||||
|
getFishDistributionList,
|
||||||
|
getFishDistributionYear
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', value: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const tableRef = ref();
|
||||||
|
|
||||||
|
const searchParams = ref({
|
||||||
|
tm: null as string | null
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableColumns = ref([
|
||||||
|
{
|
||||||
|
title: '调查日期',
|
||||||
|
dataIndex: 'tm'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '科属',
|
||||||
|
dataIndex: 'family',
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
const family =
|
||||||
|
record?.family !== undefined &&
|
||||||
|
record?.family !== null &&
|
||||||
|
String(record.family).trim() !== '' &&
|
||||||
|
String(record.family).trim() !== '-'
|
||||||
|
? String(record.family).trim()
|
||||||
|
: '';
|
||||||
|
const genus =
|
||||||
|
record?.genus !== undefined &&
|
||||||
|
record?.genus !== null &&
|
||||||
|
String(record.genus).trim() !== '' &&
|
||||||
|
String(record.genus).trim() !== '-'
|
||||||
|
? String(record.genus).trim()
|
||||||
|
: '';
|
||||||
|
|
||||||
|
if (!family && !genus) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
if (!family) {
|
||||||
|
return genus;
|
||||||
|
}
|
||||||
|
if (!genus) {
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
return `${family}-${genus}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '渔获物种类',
|
||||||
|
dataIndex: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '鱼类规格(cm)',
|
||||||
|
dataIndex: 'fsz'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '渔获物数量(尾)',
|
||||||
|
dataIndex: 'fcnt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '渔获物重量(kg)',
|
||||||
|
dataIndex: 'fwgh'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tableScrollX = ref(800);
|
||||||
|
|
||||||
|
const getDefaultYear = (yearList: any[]) => {
|
||||||
|
const firstItem = yearList?.[0];
|
||||||
|
if (typeof firstItem === 'string' || typeof firstItem === 'number') {
|
||||||
|
return String(firstItem);
|
||||||
|
}
|
||||||
|
return firstItem?.yr || firstItem?.tm || firstItem?.year || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getYearRange = (year: string) => ({
|
||||||
|
startDate: `${year}-01-01 00:00:00`,
|
||||||
|
endDate: `${year}-12-31 23:59:59`
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildFilter = () => {
|
||||||
|
const filters: any[] = [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: getYearRange(String(searchParams.value.tm)).startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: getYearRange(String(searchParams.value.tm)).endDate
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformTableData = (res: any) => {
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const total = res?.data?.data?.total || res?.data?.total || res?.total || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records.map((item: any, index: number) => ({
|
||||||
|
...item,
|
||||||
|
_rowKey:
|
||||||
|
item?.id ||
|
||||||
|
`${item?.stcd || modelStore.params.stcd || 'stcd'}-${
|
||||||
|
item?.tm || searchParams.value.tm || 'tm'
|
||||||
|
}-${index}`
|
||||||
|
})),
|
||||||
|
total
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTableList = (params: any) => {
|
||||||
|
const take = Number(params?.take || DEFAULT_PAGE_SIZE);
|
||||||
|
const currentPage = Number(params?.skip || 1);
|
||||||
|
const skip = Math.max(currentPage - 1, 0) * take;
|
||||||
|
|
||||||
|
return getFishDistributionList({
|
||||||
|
...params,
|
||||||
|
take,
|
||||||
|
skip,
|
||||||
|
filter: params?.filter || buildFilter()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshTable = () => {
|
||||||
|
if (!modelStore.params.stcd || !searchParams.value.tm) return;
|
||||||
|
tableRef.value?.getList(buildFilter());
|
||||||
|
};
|
||||||
|
|
||||||
|
const getYearList = async () => {
|
||||||
|
const currentYear = String(new Date().getFullYear());
|
||||||
|
if (!modelStore.params.stcd) {
|
||||||
|
searchParams.value.tm = currentYear;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res: any = await getFishDistributionYear({
|
||||||
|
stcd: modelStore.params.stcd
|
||||||
|
});
|
||||||
|
const yearData = Array.isArray(res?.data?.data)
|
||||||
|
? res.data.data
|
||||||
|
: Array.isArray(res?.data?.records)
|
||||||
|
? res.data.records
|
||||||
|
: Array.isArray(res?.data)
|
||||||
|
? res.data
|
||||||
|
: [];
|
||||||
|
|
||||||
|
searchParams.value.tm = getDefaultYear(yearData) || currentYear;
|
||||||
|
refreshTable();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取年份列表失败:', error);
|
||||||
|
searchParams.value.tm = currentYear;
|
||||||
|
refreshTable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!searchParams.value.tm) {
|
||||||
|
message.warning('请选择调查日期');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
visible => {
|
||||||
|
if (visible && !hasLoaded.value) {
|
||||||
|
getYearList();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
}
|
||||||
|
if (!visible) {
|
||||||
|
hasLoaded.value = false;
|
||||||
|
searchParams.value = {
|
||||||
|
tm: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (props.visible && newStcd && newStcd !== oldStcd) {
|
||||||
|
hasLoaded.value = false;
|
||||||
|
searchParams.value = {
|
||||||
|
tm: null
|
||||||
|
};
|
||||||
|
getYearList();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ylfb-modal {
|
||||||
|
.ylfb-content {
|
||||||
|
min-height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -3,6 +3,7 @@ import { computed } from 'vue';
|
|||||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
import { useTagsViewStore } from '@/store/modules/tagsView';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import MapModal from '@/components/MapModal/index.vue';
|
import MapModal from '@/components/MapModal/index.vue';
|
||||||
|
import ylfbModal from '@/components/ylfbModal/index.vue';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import GisView from '@/components/gis/GisView.vue';
|
import GisView from '@/components/gis/GisView.vue';
|
||||||
|
|
||||||
@ -32,6 +33,9 @@ const routeKey = computed(() => router.path + Math.random());
|
|||||||
:title="modelStore.title"
|
:title="modelStore.title"
|
||||||
:tabs-config="modelStore.tabList"
|
:tabs-config="modelStore.tabList"
|
||||||
/>
|
/>
|
||||||
|
<!-- 鱼类分布 -->
|
||||||
|
|
||||||
|
<ylfbModal v-model:visible="modelStore.ylfbModalVisible" />
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -734,7 +734,7 @@ export const useMapStore = defineStore('map', () => {
|
|||||||
if (ylfbKeys.includes(layerKey)) {
|
if (ylfbKeys.includes(layerKey)) {
|
||||||
if (yearTime) {
|
if (yearTime) {
|
||||||
requestParams.filters.push({
|
requestParams.filters.push({
|
||||||
field: 'tm',
|
field: 'startTime',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
dataType: 'date',
|
dataType: 'date',
|
||||||
value: moment(yearTime)
|
value: moment(yearTime)
|
||||||
@ -742,7 +742,7 @@ export const useMapStore = defineStore('map', () => {
|
|||||||
.format('YYYY-MM-DD 00:00:00')
|
.format('YYYY-MM-DD 00:00:00')
|
||||||
});
|
});
|
||||||
requestParams.filters.push({
|
requestParams.filters.push({
|
||||||
field: 'tm',
|
field: 'endTime',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
dataType: 'date',
|
dataType: 'date',
|
||||||
value: moment(yearTime).endOf('year').format('YYYY-MM-DD 23:59:59')
|
value: moment(yearTime).endOf('year').format('YYYY-MM-DD 23:59:59')
|
||||||
|
|||||||
@ -4,12 +4,13 @@ import { ref } from 'vue';
|
|||||||
export const useModelStore = defineStore('model', () => {
|
export const useModelStore = defineStore('model', () => {
|
||||||
// state
|
// state
|
||||||
const modalVisible = ref(false);
|
const modalVisible = ref(false);
|
||||||
const currentTabKey = ref("basicInfo");
|
const ylfbModalVisible = ref(false); // 鱼类分布弹窗显示状态
|
||||||
|
const currentTabKey = ref('basicInfo');
|
||||||
const tabList = ref([]);
|
const tabList = ref([]);
|
||||||
const title = ref('');
|
const title = ref('');
|
||||||
const isBasicEdit = ref(false);
|
const isBasicEdit = ref(false);
|
||||||
const showStcdSelector = ref(false); // 是否显示下拉框
|
const showStcdSelector = ref(false); // 是否显示下拉框
|
||||||
const stcdOptions = ref<Array<{ label: string, value: string }>>([]); // 可选测站列表
|
const stcdOptions = ref<Array<{ label: string; value: string }>>([]); // 可选测站列表
|
||||||
|
|
||||||
// 新增:功能迁移所需的状态字段
|
// 新增:功能迁移所需的状态字段
|
||||||
const baseId = ref<string>(''); // 当前基地 ID
|
const baseId = ref<string>(''); // 当前基地 ID
|
||||||
@ -26,8 +27,8 @@ export const useModelStore = defineStore('model', () => {
|
|||||||
stcd: any;
|
stcd: any;
|
||||||
date: any;
|
date: any;
|
||||||
show: any;
|
show: any;
|
||||||
enfc:any;
|
enfc: any;
|
||||||
eqtp:any;
|
eqtp: any;
|
||||||
baseId?: string; // 新增:基地 ID
|
baseId?: string; // 新增:基地 ID
|
||||||
sttpCode?: string; // 新增:站点类型代码
|
sttpCode?: string; // 新增:站点类型代码
|
||||||
sttpMap?: string; // 新增:站点类型映射
|
sttpMap?: string; // 新增:站点类型映射
|
||||||
@ -38,24 +39,25 @@ export const useModelStore = defineStore('model', () => {
|
|||||||
stcd: '',
|
stcd: '',
|
||||||
date: '',
|
date: '',
|
||||||
show: true,
|
show: true,
|
||||||
enfc:'0',
|
enfc: '0',
|
||||||
eqtp:'',
|
eqtp: '',
|
||||||
baseId: '',
|
baseId: '',
|
||||||
sttpCode: '',
|
sttpCode: '',
|
||||||
sttpMap: '',
|
sttpMap: '',
|
||||||
dtinType:'',
|
dtinType: '',
|
||||||
bldstt:'',
|
bldstt: ''
|
||||||
});
|
});
|
||||||
const filterStll= ref('')
|
const filterStll = ref('');
|
||||||
return {
|
return {
|
||||||
params,
|
params,
|
||||||
modalVisible,
|
modalVisible,
|
||||||
|
ylfbModalVisible, // 鱼类分布弹窗显示状态
|
||||||
currentTabKey,
|
currentTabKey,
|
||||||
tabList,
|
tabList,
|
||||||
title,
|
title,
|
||||||
isBasicEdit,
|
isBasicEdit,
|
||||||
showStcdSelector, // 新增
|
showStcdSelector, // 新增
|
||||||
stcdOptions, // 新增
|
stcdOptions, // 新增
|
||||||
filterStll,
|
filterStll,
|
||||||
// 新增返回字段
|
// 新增返回字段
|
||||||
baseId,
|
baseId,
|
||||||
@ -65,6 +67,6 @@ export const useModelStore = defineStore('model', () => {
|
|||||||
aiSites,
|
aiSites,
|
||||||
wtConfig,
|
wtConfig,
|
||||||
wqShow,
|
wqShow,
|
||||||
fishTabInfo,
|
fishTabInfo
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue
Block a user