右侧模块增加
This commit is contained in:
parent
7ab91f158e
commit
48262cc162
@ -9,9 +9,9 @@ VITE_APP_BASE_API = '/dev-api'
|
||||
# 本地环境
|
||||
# VITE_APP_BASE_URL = 'http://localhost:8093'
|
||||
# 测试环境
|
||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
||||
VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
||||
# 汤伟
|
||||
VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
|
||||
# VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
|
||||
|
||||
|
||||
# 开发环境导入预览地址
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"version": "1.2.0",
|
||||
"scripts": {
|
||||
"dev": "vite serve --mode development",
|
||||
"build": "vue-tsc --noEmit && vite build --mode production",
|
||||
"build": "vite build --mode production",
|
||||
"build:mvn": "vite build --mode production",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint src/**/*.{ts,js,vue} --fix",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
frontend/src/assets/components/tishi.png
Normal file
BIN
frontend/src/assets/components/tishi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 353 B |
BIN
frontend/src/assets/components/tishi1.png
Normal file
BIN
frontend/src/assets/components/tishi1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 478 B |
@ -19,8 +19,8 @@
|
||||
<div class="title_right">
|
||||
<div v-if="select.show">
|
||||
<a-select v-model:value="selectValue" show-search placeholder="请选择" :size="'small'"
|
||||
:options="select.options" :filter-option="filterOption" @focus="handleFocus" @blur="handleBlur"
|
||||
@change="handleChange"></a-select>
|
||||
style="width: 120px" :options="select.options" :filter-option="filterOption"
|
||||
@focus="handleFocus" @blur="handleBlur" @change="handleChange"></a-select>
|
||||
</div>
|
||||
<div v-if="shrink" class="title_shrink" @click="isExpand = !isExpand">
|
||||
<img v-if="isExpand" src="@/assets/components/arrow-up.png" alt="">
|
||||
@ -36,14 +36,20 @@
|
||||
<div v-if="datetimePicker.show">
|
||||
<!-- 添加 locale 属性来设置语言 -->
|
||||
<a-date-picker v-model:value="datetimeValue" show-time
|
||||
:style="{ width: datetimePicker.picker === 'year' ? '80px' : '' }"
|
||||
:format="datetimePicker.format !== null ? datetimePicker.format : undefined"
|
||||
:picker="datetimePicker.picker" placeholder="请选择时间"
|
||||
@change="handleDateTimeChange" :size="'small'" />
|
||||
:picker="datetimePicker.picker" placeholder=" " @change="handleDateTimeChange"
|
||||
:size="'small'" />
|
||||
<!-- 修改为 locale 变量 -->
|
||||
</div>
|
||||
<div v-if="scopeDate.show">
|
||||
<div v-if="scopeDate.show" class="title_scopeDate">
|
||||
<a-range-picker v-model:value="scopeDateValue" :picker="scopeDate.picker"
|
||||
:format="'YYYY-MM-DD'" :range-separator="' 至 '" style="width: 200px" />
|
||||
:style="{ width: scopeDate.picker === 'year' ? '80px' : (scopeDate.picker === 'month' ? '150px' : '') }"
|
||||
:format="scopeDate.format" :range-separator="' 至 '" :size="'small'" />
|
||||
</div>
|
||||
<div v-if="tabs.show" class="typeOne">
|
||||
<div @click="handleTabClick('one')" :class="tabsValue == 'one' ? 'typezhong' : ''">图片</div>
|
||||
<div @click="handleTabClick('two')" :class="tabsValue == 'two' ? 'typezhong' : ''">视频</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -136,16 +142,29 @@ const props = defineProps({
|
||||
default: () => ({
|
||||
show: false,
|
||||
value: undefined,
|
||||
format: null, //YYYY-MM-DD HH
|
||||
picker: 'month' //date | week | month | quarter | year
|
||||
})
|
||||
},
|
||||
tabs: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
show: false,
|
||||
value: 'one',
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['tab-change']);
|
||||
const isExpand = ref(true);
|
||||
const selectValue = ref(props.select.value)
|
||||
const moreSelectValue = ref(props.select.value)
|
||||
const datetimeValue = ref<Dayjs | null>(props.datetimePicker.value ? dayjs(props.datetimePicker.value) : null);
|
||||
const scopeDateValue = ref(props.scopeDate.value);
|
||||
const scopeDateValue = ref<[Dayjs, Dayjs] | undefined>(
|
||||
props.scopeDate.value && Array.isArray(props.scopeDate.value)
|
||||
? [dayjs(props.scopeDate.value[0]), dayjs(props.scopeDate.value[1])]
|
||||
: undefined
|
||||
);
|
||||
const tabsValue = ref(props.tabs.value)
|
||||
// // 定义 locale 变量
|
||||
// const locale = zhCN;
|
||||
|
||||
@ -178,6 +197,14 @@ const handleDateTimeChange = (date: any | null, dateString: string) => {
|
||||
console.log('Selected DateTime:', date, dateString);
|
||||
};
|
||||
|
||||
const handleTabClick = (value: string) => {
|
||||
tabsValue.value = value;
|
||||
// 向父组件传递参数
|
||||
emit('tab-change', {
|
||||
tabValue: value,
|
||||
tabLabel: value === 'one' ? '图片' : '视频'
|
||||
});
|
||||
};
|
||||
// 页面加载时执行的逻辑
|
||||
onMounted(() => {
|
||||
console.log(props.select);
|
||||
@ -212,10 +239,12 @@ onMounted(() => {
|
||||
.title_left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.texttitle {
|
||||
display: inline-block;
|
||||
width:180px;
|
||||
//width:180px;
|
||||
}
|
||||
|
||||
.title_icon {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
@ -227,11 +256,36 @@ onMounted(() => {
|
||||
.title_right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div {
|
||||
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.typeOne {
|
||||
display: flex;
|
||||
// width: 84px;
|
||||
height: 24px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
div {
|
||||
width: 38px;
|
||||
height: 24px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
margin: 0px;
|
||||
color: rgba(0, 0, 0, .85);
|
||||
}
|
||||
|
||||
.typezhong {
|
||||
background: #40a9ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qgc_title:before {
|
||||
@ -263,4 +317,10 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title_scopeDate {
|
||||
:deep(.ant-picker-range-separator) {
|
||||
padding: 0px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -48,7 +48,7 @@ const props = defineProps({
|
||||
});
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
value: `${new Date().getFullYear() - 1}`, // 上一年
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
@ -61,7 +61,8 @@ interface ChartDataItem {
|
||||
}
|
||||
|
||||
// 原始图表数据(多组数据用于跑马灯)
|
||||
const originalChartData = ref<ChartDataItem[]>([
|
||||
const originalChartData = ref<ChartDataItem[]>(
|
||||
[
|
||||
{
|
||||
title: '过鱼总量统计 - 2025年',
|
||||
data: [
|
||||
|
||||
320
frontend/src/modules/Ysdwjc/index.vue
Normal file
320
frontend/src/modules/Ysdwjc/index.vue
Normal file
@ -0,0 +1,320 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="野生动物监测" :select="select" :datetimePicker="datetimePicker" :tabs="tabs"
|
||||
@tab-change="handleTabChange">
|
||||
<div v-if="tabsa == 'one'" class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
||||
<!-- 跑马灯轨道容器 -->
|
||||
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }"
|
||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }">
|
||||
<!-- 遍历所有媒体项(包含克隆项) -->
|
||||
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item">
|
||||
<!-- 图片 -->
|
||||
<a-image :src="item.url" />
|
||||
<!-- 说明文字 -->
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container video-carousel-container" @mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave">
|
||||
<!-- 跑马灯轨道容器 -->
|
||||
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }"
|
||||
:style="{ transform: `translateX(-${currentIndex * 30}%)` }">
|
||||
<!-- 遍历所有媒体项(包含克隆项) -->
|
||||
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item1">
|
||||
<!-- 图片 -->
|
||||
<a-image :src="item.url" />
|
||||
<div class="biaozji">
|
||||
<span class="icon iconfont icon-play" ></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: 'ZhiWuYuanJianSheJiJieRuQingKuangBar'
|
||||
});
|
||||
// ==================== 响应式变量定义 ====================// 选择器配置
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
});
|
||||
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: `2024`,
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
});
|
||||
const tabs = ref({
|
||||
show: true,
|
||||
value: 'one',
|
||||
|
||||
});
|
||||
|
||||
|
||||
// 媒体类型定义
|
||||
interface MediaItem {
|
||||
type: 'image' | 'video';
|
||||
url: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
// 原始媒体数据(5条测试数据,包含图片和视频混合)
|
||||
const originalMediaData = ref<MediaItem[]>([
|
||||
{
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
|
||||
text: '糯扎渡动物救护站 2025-04-11 15'
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
|
||||
text: '糯扎渡动物救护站 2025-04-11 15'
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
|
||||
text: '糯扎渡动物救护站 2025-04-11 15'
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
|
||||
text: '糯扎渡动物救护站 2025-04-11 15'
|
||||
},
|
||||
]);
|
||||
const tabsa = ref('one')
|
||||
|
||||
// 克隆首尾项后的渲染数组(用于无缝循环)
|
||||
// 结构:[最后一项克隆, 原始数据..., 第一项克隆]
|
||||
const renderMediaData = ref<MediaItem[]>([]);
|
||||
|
||||
// 当前显示索引(指向renderMediaData)
|
||||
const currentIndex = ref(1); // 从1开始,跳过克隆的首项
|
||||
|
||||
// 定时器引用
|
||||
let timer: any = null;
|
||||
|
||||
// 鼠标悬停状态
|
||||
const isHovering = ref(false);
|
||||
|
||||
// 是否正在切换动画中(用于禁用transition)
|
||||
const isTransitioning = ref(false);
|
||||
|
||||
// 初始化渲染数组(克隆首尾项)
|
||||
const initRenderData = () => {
|
||||
const length = originalMediaData.value.length;
|
||||
if (length === 0) return;
|
||||
|
||||
renderMediaData.value = [
|
||||
originalMediaData.value[length - 1], // 克隆最后一项
|
||||
...originalMediaData.value, // 原始数据
|
||||
originalMediaData.value[0] // 克隆第一项
|
||||
];
|
||||
};
|
||||
|
||||
// 启动自动轮播
|
||||
const startAutoPlay = () => {
|
||||
if (timer) clearInterval(timer);
|
||||
timer = setInterval(() => {
|
||||
if (!isHovering.value && !isTransitioning.value) {
|
||||
nextSlide();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
// 切换到下一张
|
||||
const nextSlide = () => {
|
||||
currentIndex.value++;
|
||||
|
||||
// 延迟检查是否需要无缝跳转
|
||||
setTimeout(() => {
|
||||
checkSeamlessJump();
|
||||
}, 500); // 与transition时间一致
|
||||
};
|
||||
|
||||
// 检查是否需要无缝跳转(克隆项处理)
|
||||
const checkSeamlessJump = () => {
|
||||
const realLength = originalMediaData.value.length;
|
||||
|
||||
// 如果到达克隆的最后一项(索引 = realLength + 1)
|
||||
if (currentIndex.value >= realLength + 1) {
|
||||
// 1. 禁用过渡动画,实现瞬间跳转
|
||||
isTransitioning.value = true;
|
||||
|
||||
// 2. 瞬间跳转到真实的第二项(索引1),用户看不到跳变
|
||||
currentIndex.value = 1;
|
||||
|
||||
// 3. 等待两帧后恢复过渡动画(确保DOM已更新)
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
isTransitioning.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理鼠标进入
|
||||
const handleMouseEnter = () => {
|
||||
isHovering.value = true;
|
||||
};
|
||||
|
||||
// 处理鼠标离开
|
||||
const handleMouseLeave = () => {
|
||||
isHovering.value = false;
|
||||
};
|
||||
const handleTabChange = (data: { tabValue: string; tabLabel: string }) => {
|
||||
console.log('当前选中标签:', data.tabValue);
|
||||
console.log('标签名称:', data.tabLabel);
|
||||
// 执行相应业务逻辑
|
||||
tabsa.value = data.tabValue;
|
||||
};
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
initRenderData();
|
||||
startAutoPlay();
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 290px;
|
||||
border: 1px solid #7fd6ff;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
overflow: hidden; // 隐藏超出容器的内容
|
||||
|
||||
// 跑马灯轨道
|
||||
.carousel-track {
|
||||
display: flex; // 横向排列所有媒体项
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.5s ease-in-out; // 0.5秒平滑过渡
|
||||
|
||||
// 禁用过渡动画(用于无缝跳转)
|
||||
&.no-transition {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
// 单个媒体项
|
||||
.carousel-item {
|
||||
width: 100% !important; // 每个项目占满容器宽度
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex-shrink: 0; // 防止被压缩
|
||||
|
||||
img,
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; // 保持比例填充
|
||||
}
|
||||
|
||||
// Ant Design Vue 的 a-image 组件样式
|
||||
:deep(.ant-image) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
line-height: 40px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
color: #fff;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-carousel-container {
|
||||
border: 0px solid #7fd6ff !important;
|
||||
border-radius: 0px !important;
|
||||
.carousel-item1 {
|
||||
position: relative;
|
||||
width: 40% !important; // 每个项目占满容器宽度
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex-shrink: 0; // 防止被压缩
|
||||
// margin-right: 10px;
|
||||
padding: 6px;
|
||||
border-top:#f0f0f0 solid 1px;
|
||||
border-bottom:#f0f0f0 solid 1px;
|
||||
border-right:#f0f0f0 solid 1px;
|
||||
|
||||
img,
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; // 保持比例填充
|
||||
}
|
||||
|
||||
// Ant Design Vue 的 a-image 组件样式
|
||||
:deep(.ant-image) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.biaozji{
|
||||
position: absolute;
|
||||
width:40px;
|
||||
height:40px;
|
||||
border-radius:50%;
|
||||
background:#0c33586c;
|
||||
z-index: 9999;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
display:flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
span{
|
||||
width:16px;
|
||||
height:16px;
|
||||
font-size: 16px;
|
||||
color: #abacad;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,40 +1,14 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="动物救助情况">
|
||||
<div
|
||||
class="container"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<div class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
||||
<!-- 跑马灯轨道容器 -->
|
||||
<div
|
||||
class="carousel-track"
|
||||
:class="{ 'no-transition': isTransitioning }"
|
||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
|
||||
>
|
||||
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }"
|
||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }">
|
||||
<!-- 遍历所有媒体项(包含克隆项) -->
|
||||
<div
|
||||
v-for="(item, index) in renderMediaData"
|
||||
:key="index"
|
||||
class="carousel-item"
|
||||
>
|
||||
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item">
|
||||
<!-- 图片 -->
|
||||
<img
|
||||
v-if="item.type === 'image'"
|
||||
:src="item.url"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<!-- 视频 -->
|
||||
<video
|
||||
v-else
|
||||
:src="item.url"
|
||||
autoplay
|
||||
muted
|
||||
loop
|
||||
playsinline
|
||||
></video>
|
||||
|
||||
<a-image :src="item.url" />
|
||||
<!-- 说明文字 -->
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
@ -67,8 +41,8 @@ const originalMediaData = ref<MediaItem[]>([
|
||||
text: '糯扎渡动物救护站 2025-04-11 15'
|
||||
},
|
||||
{
|
||||
type: 'video',
|
||||
url: 'https://example.com/video1.mp4', // 视频URL示例,您可替换为真实地址
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20250508094042468361071230016027&view=jpg&token=bearer fee092f0-b748-4019-bcbb-1386fa8c4fa4', // 视频URL示例,您可替换为真实地址
|
||||
text: '野生动物救助中心 2025-04-12 10'
|
||||
},
|
||||
{
|
||||
@ -192,17 +166,30 @@ onUnmounted(() => {
|
||||
|
||||
// 单个媒体项
|
||||
.carousel-item {
|
||||
min-width: 100%; // 每个项目占满容器宽度
|
||||
width: 100% !important; // 每个项目占满容器宽度
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex-shrink: 0; // 防止被压缩
|
||||
|
||||
img, video {
|
||||
img,
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; // 保持比例填充
|
||||
}
|
||||
|
||||
// Ant Design Vue 的 a-image 组件样式
|
||||
:deep(.ant-image) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
209
frontend/src/modules/alongFishMod/index.vue
Normal file
209
frontend/src/modules/alongFishMod/index.vue
Normal file
@ -0,0 +1,209 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="水生生态调查" :select="select" :datetimePicker="datetimePicker">
|
||||
<div ref="chartRef" class="chart-container"></div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import type { EChartsOption } from 'echarts';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名
|
||||
defineOptions({
|
||||
name: 'shuiShengShengTaiDiaoCha'
|
||||
});
|
||||
|
||||
// ==================== 图表相关 ====================
|
||||
const chartRef = ref<HTMLDivElement>();
|
||||
let chartInstance: any = null;
|
||||
|
||||
// 选择器配置
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
});
|
||||
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: '2020',
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
});
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
// 销毁已存在的实例
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
|
||||
// 创建新实例
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
// 图表配置
|
||||
const option: EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
left: '2%',
|
||||
right: '4%',
|
||||
bottom: 30,
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
bottom: 0,
|
||||
itemWidth: 10,
|
||||
itemHeight: 10
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: 1,
|
||||
name: '种',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666'
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
rotate: 0,
|
||||
color: '#666',
|
||||
rich: {
|
||||
a: {
|
||||
height: 24,
|
||||
align: 'center'
|
||||
},
|
||||
b: {
|
||||
height: 30,
|
||||
align: 'center'
|
||||
}
|
||||
}
|
||||
},
|
||||
name: '数量(个)',
|
||||
data: [
|
||||
'两河口',
|
||||
'杨房沟',
|
||||
'锦屏一级',
|
||||
'锦屏二级',
|
||||
'官地',
|
||||
'二滩',
|
||||
'桐子林'
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '外来鱼类',
|
||||
type: 'bar',
|
||||
stack: 'stack',
|
||||
data: [0, 0, 0, 0, 0, 1, 2],
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#FFF'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '本土受威胁鱼类',
|
||||
type: 'bar',
|
||||
stack: 'stack',
|
||||
data: [1, 10, 10, 5, 4, 6, 11],
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#FFF'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '本土无危鱼类',
|
||||
type: 'bar',
|
||||
stack: 'stack',
|
||||
data: [1, 10, 6, 5, 2, 13, 23],
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#FFF'
|
||||
}
|
||||
}
|
||||
],
|
||||
barWidth: 15,
|
||||
color: ['#4B79AB', '#78C300', '#9556A4']
|
||||
};
|
||||
|
||||
// 设置图表配置
|
||||
chartInstance.setOption(option);
|
||||
};
|
||||
|
||||
// 处理窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.resize();
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 生命周期钩子 ====================
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
initChart();
|
||||
window.addEventListener('resize', handleResize);
|
||||
}, 50);
|
||||
});
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
// 移除事件监听
|
||||
window.removeEventListener('resize', handleResize);
|
||||
|
||||
// 销毁图表实例
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
chartInstance = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 231px;
|
||||
min-height: 231px;
|
||||
}
|
||||
</style>
|
||||
286
frontend/src/modules/fishSurvey/FishIntroduce/index.vue
Normal file
286
frontend/src/modules/fishSurvey/FishIntroduce/index.vue
Normal file
@ -0,0 +1,286 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="鱼类介绍">
|
||||
<div class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
||||
<!-- 跑马灯轨道容器 -->
|
||||
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }"
|
||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }">
|
||||
<!-- 遍历所有媒体项(包含克隆项) -->
|
||||
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item">
|
||||
<!-- 图片 -->
|
||||
<img :src="item.url" alt="" />
|
||||
<!-- 说明文字(随媒体项移动) -->
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 面板指示器(固定在底部右侧) -->
|
||||
<div class="pagination-dots-fixed">
|
||||
<span
|
||||
v-for="(dot, index) in originalMediaData"
|
||||
:key="index"
|
||||
class="dot"
|
||||
:class="{ active: getCurrentRealIndex() === index }"
|
||||
@click="goToSlide(index)"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 独立的文字说明区域(随跑马灯切换而变化) -->
|
||||
<div class="description-text">
|
||||
{{ currentDescription }}
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: 'zengZhiZhanJieShaoMod'
|
||||
});
|
||||
|
||||
// 媒体类型定义
|
||||
interface MediaItem {
|
||||
type: 'image' | 'video';
|
||||
url: string;
|
||||
text: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// 原始媒体数据(3条测试数据)
|
||||
const originalMediaData = ref<MediaItem[]>([
|
||||
{
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20230814205611342377136845462200&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920',
|
||||
text: '松岗鱼类增殖站',
|
||||
description: '松岗鱼类增殖放流站位于四川省阿坝藏族羌族自治州马尔康市松岗镇,主要服务于大渡河上游的双江口和金川两座水电站,同时还承担多种珍稀特有鱼类的救护和科研任务,实现工程建设与生态环境共同推进、相互促进。'
|
||||
},
|
||||
{
|
||||
type: 'video',
|
||||
url: 'https://211.99.26.225:12125/?20230805205848575430105387253710&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920', // 视频URL示例,您可替换为真实地址
|
||||
text: '猴子岩鱼类增殖站',
|
||||
description: '猴子岩水电站鱼类增殖放流站位于猴子岩水电站坝址下游约7.0km(业主营地下游约1.5km),大渡河左岸桃花渣场顶部平台上,紧邻枢纽桃花大桥下游侧,占地面积47.3亩,其中一期工程27.0亩,预留二期工程用地20.3亩(二期工程目前为丹巴、巴底水电站预留工程)增殖放流站工作流程为:亲鱼收集购买、亲鱼驯养培育、人工催产和授精、人工孵化、苗种培育和放流。 猴子岩鱼类增殖放流站近期放流对象中齐口裂腹鱼、重口裂腹鱼、大渡软刺裸裂尻鱼增殖放流技术水平已趋于熟化,中期放流对象大渡软刺裸裂尻鱼人工繁殖技术逐渐趋于熟化'
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20230805205924378504010675106305&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920',
|
||||
text: '黑马鱼类增殖站',
|
||||
description: '大渡河黑马鱼类增殖放流站位于四川省甘洛县黑马乡黑马业主营地内,距离甘洛县城45km,区域交通路况较好。主要承担瀑布沟、深溪沟、大岗山、枕头坝一级、沙坪二级等五座水电站鱼类增殖放流的重任。放流鱼类包含:齐口裂腹鱼、重口裂腹鱼、鲈鲤、长薄鳅、白甲鱼、中华倒刺?、长吻脆、稀有鮊鲫、华鲮、侧沟爬岩鳅等10个种类,共计约918.07万尾珍稀特有鱼苗。'
|
||||
}
|
||||
]);
|
||||
|
||||
// 克隆首尾项后的渲染数组(用于无缝循环)
|
||||
// 结构:[最后一项克隆, 原始数据..., 第一项克隆]
|
||||
const renderMediaData = ref<MediaItem[]>([]);
|
||||
|
||||
// 当前显示索引(指向renderMediaData)
|
||||
const currentIndex = ref(1); // 从1开始,跳过克隆的首项
|
||||
|
||||
// 定时器引用
|
||||
let timer: any = null;
|
||||
|
||||
// 鼠标悬停状态
|
||||
const isHovering = ref(false);
|
||||
|
||||
// 是否正在切换动画中(用于禁用transition)
|
||||
const isTransitioning = ref(false);
|
||||
|
||||
// 初始化渲染数组(克隆首尾项)
|
||||
const initRenderData = () => {
|
||||
const length = originalMediaData.value.length;
|
||||
if (length === 0) return;
|
||||
|
||||
renderMediaData.value = [
|
||||
originalMediaData.value[length - 1], // 克隆最后一项
|
||||
...originalMediaData.value, // 原始数据
|
||||
originalMediaData.value[0] // 克隆第一项
|
||||
];
|
||||
};
|
||||
|
||||
// 启动自动轮播
|
||||
const startAutoPlay = () => {
|
||||
if (timer) clearInterval(timer);
|
||||
timer = setInterval(() => {
|
||||
if (!isHovering.value && !isTransitioning.value) {
|
||||
nextSlide();
|
||||
}
|
||||
}, 4000);
|
||||
};
|
||||
|
||||
// 切换到下一张
|
||||
const nextSlide = () => {
|
||||
currentIndex.value++;
|
||||
|
||||
// 延迟检查是否需要无缝跳转
|
||||
setTimeout(() => {
|
||||
checkSeamlessJump();
|
||||
}, 500); // 与transition时间一致
|
||||
};
|
||||
|
||||
// 检查是否需要无缝跳转(克隆项处理)
|
||||
const checkSeamlessJump = () => {
|
||||
const realLength = originalMediaData.value.length;
|
||||
|
||||
// 如果到达克隆的最后一项(索引 = realLength + 1)
|
||||
if (currentIndex.value >= realLength + 1) {
|
||||
// 1. 禁用过渡动画,实现瞬间跳转
|
||||
isTransitioning.value = true;
|
||||
|
||||
// 2. 瞬间跳转到真实的第二项(索引1),用户看不到跳变
|
||||
currentIndex.value = 1;
|
||||
|
||||
// 3. 等待两帧后恢复过渡动画(确保DOM已更新)
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
isTransitioning.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理鼠标进入
|
||||
const handleMouseEnter = () => {
|
||||
isHovering.value = true;
|
||||
};
|
||||
|
||||
// 处理鼠标离开
|
||||
const handleMouseLeave = () => {
|
||||
isHovering.value = false;
|
||||
};
|
||||
|
||||
// 计算当前显示的描述文字
|
||||
const currentDescription = computed(() => {
|
||||
const realIndex = getCurrentRealIndex();
|
||||
return originalMediaData.value[realIndex]?.description || '';
|
||||
});
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
initRenderData();
|
||||
startAutoPlay();
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
|
||||
// 获取当前真实的索引(排除克隆项的影响)
|
||||
const getCurrentRealIndex = () => {
|
||||
const realLength = originalMediaData.value.length;
|
||||
let realIndex = currentIndex.value - 1; // 减去克隆的首项偏移
|
||||
|
||||
// 处理边界情况(无缝循环时的克隆项)
|
||||
if (realIndex < 0) realIndex = realLength - 1;
|
||||
if (realIndex >= realLength) realIndex = 0;
|
||||
|
||||
return realIndex;
|
||||
};
|
||||
|
||||
// 跳转到指定幻灯片
|
||||
const goToSlide = (targetIndex: number) => {
|
||||
if (isTransitioning.value) return;
|
||||
|
||||
// 计算目标索引(考虑克隆项偏移)
|
||||
currentIndex.value = targetIndex + 1;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 228px;
|
||||
// border: 1px solid #7fd6ff;
|
||||
// border-radius: 5px;
|
||||
position: relative;
|
||||
overflow: hidden; // 隐藏超出容器的内容
|
||||
|
||||
// 跑马灯轨道
|
||||
.carousel-track {
|
||||
display: flex; // 横向排列所有媒体项
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.5s ease-in-out; // 0.5秒平滑过渡
|
||||
|
||||
// 禁用过渡动画(用于无缝跳转)
|
||||
&.no-transition {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
// 单个媒体项
|
||||
.carousel-item {
|
||||
min-width: 100%; // 每个项目占满容器宽度
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex-shrink: 0; // 防止被压缩
|
||||
|
||||
img,
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; // 保持比例填充
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
color: #fff;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 固定的面板指示器
|
||||
.pagination-dots-fixed {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
z-index: 10; // 确保在文字上方
|
||||
|
||||
.dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background-color: #D8D8D8;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background-color: #005293;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 独立的文字说明区域
|
||||
.description-text {
|
||||
// padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
// min-height: 40px;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 20px;
|
||||
|
||||
// 多行文本溢出省略
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
76
frontend/src/modules/fishSurvey/FishVideo/index.vue
Normal file
76
frontend/src/modules/fishSurvey/FishVideo/index.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="视频" :select="select">
|
||||
<div class="video-container">
|
||||
<video
|
||||
ref="videoRef"
|
||||
class="video-player"
|
||||
controls
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
preload="auto"
|
||||
>
|
||||
<source src="" type="video/mp4">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: 'zengZhiZhanJieShaoMod'
|
||||
});
|
||||
|
||||
// 视频元素引用
|
||||
const videoRef = ref<HTMLVideoElement>();
|
||||
|
||||
// 选择器配置
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
});
|
||||
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (videoRef.value) {
|
||||
videoRef.value.pause();
|
||||
videoRef.value.src = '';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.video-container{
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
|
||||
.video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background-color: #000;
|
||||
|
||||
// 自定义视频控件样式(可选)
|
||||
&::-webkit-media-controls-panel {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
286
frontend/src/modules/fishSurvey/ZhuanZhiIntroduce/index.vue
Normal file
286
frontend/src/modules/fishSurvey/ZhuanZhiIntroduce/index.vue
Normal file
@ -0,0 +1,286 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="鱼类调查装置介绍">
|
||||
<div class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
||||
<!-- 跑马灯轨道容器 -->
|
||||
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }"
|
||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }">
|
||||
<!-- 遍历所有媒体项(包含克隆项) -->
|
||||
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item">
|
||||
<!-- 图片 -->
|
||||
<img :src="item.url" alt="" />
|
||||
<!-- 说明文字(随媒体项移动) -->
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 面板指示器(固定在底部右侧) -->
|
||||
<div class="pagination-dots-fixed">
|
||||
<span
|
||||
v-for="(dot, index) in originalMediaData"
|
||||
:key="index"
|
||||
class="dot"
|
||||
:class="{ active: getCurrentRealIndex() === index }"
|
||||
@click="goToSlide(index)"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 独立的文字说明区域(随跑马灯切换而变化) -->
|
||||
<div class="description-text">
|
||||
{{ currentDescription }}
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: 'zengZhiZhanJieShaoMod'
|
||||
});
|
||||
|
||||
// 媒体类型定义
|
||||
interface MediaItem {
|
||||
type: 'image' | 'video';
|
||||
url: string;
|
||||
text: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// 原始媒体数据(3条测试数据)
|
||||
const originalMediaData = ref<MediaItem[]>([
|
||||
{
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20230814205611342377136845462200&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920',
|
||||
text: '松岗鱼类增殖站',
|
||||
description: '松岗鱼类增殖放流站位于四川省阿坝藏族羌族自治州马尔康市松岗镇,主要服务于大渡河上游的双江口和金川两座水电站,同时还承担多种珍稀特有鱼类的救护和科研任务,实现工程建设与生态环境共同推进、相互促进。'
|
||||
},
|
||||
{
|
||||
type: 'video',
|
||||
url: 'https://211.99.26.225:12125/?20230805205848575430105387253710&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920', // 视频URL示例,您可替换为真实地址
|
||||
text: '猴子岩鱼类增殖站',
|
||||
description: '猴子岩水电站鱼类增殖放流站位于猴子岩水电站坝址下游约7.0km(业主营地下游约1.5km),大渡河左岸桃花渣场顶部平台上,紧邻枢纽桃花大桥下游侧,占地面积47.3亩,其中一期工程27.0亩,预留二期工程用地20.3亩(二期工程目前为丹巴、巴底水电站预留工程)增殖放流站工作流程为:亲鱼收集购买、亲鱼驯养培育、人工催产和授精、人工孵化、苗种培育和放流。 猴子岩鱼类增殖放流站近期放流对象中齐口裂腹鱼、重口裂腹鱼、大渡软刺裸裂尻鱼增殖放流技术水平已趋于熟化,中期放流对象大渡软刺裸裂尻鱼人工繁殖技术逐渐趋于熟化'
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
url: 'https://211.99.26.225:12125/?20230805205924378504010675106305&view=jpg&token=bearer c2e76c28-14db-4a0f-9ff2-10cc3f835920',
|
||||
text: '黑马鱼类增殖站',
|
||||
description: '大渡河黑马鱼类增殖放流站位于四川省甘洛县黑马乡黑马业主营地内,距离甘洛县城45km,区域交通路况较好。主要承担瀑布沟、深溪沟、大岗山、枕头坝一级、沙坪二级等五座水电站鱼类增殖放流的重任。放流鱼类包含:齐口裂腹鱼、重口裂腹鱼、鲈鲤、长薄鳅、白甲鱼、中华倒刺?、长吻脆、稀有鮊鲫、华鲮、侧沟爬岩鳅等10个种类,共计约918.07万尾珍稀特有鱼苗。'
|
||||
}
|
||||
]);
|
||||
|
||||
// 克隆首尾项后的渲染数组(用于无缝循环)
|
||||
// 结构:[最后一项克隆, 原始数据..., 第一项克隆]
|
||||
const renderMediaData = ref<MediaItem[]>([]);
|
||||
|
||||
// 当前显示索引(指向renderMediaData)
|
||||
const currentIndex = ref(1); // 从1开始,跳过克隆的首项
|
||||
|
||||
// 定时器引用
|
||||
let timer: any = null;
|
||||
|
||||
// 鼠标悬停状态
|
||||
const isHovering = ref(false);
|
||||
|
||||
// 是否正在切换动画中(用于禁用transition)
|
||||
const isTransitioning = ref(false);
|
||||
|
||||
// 初始化渲染数组(克隆首尾项)
|
||||
const initRenderData = () => {
|
||||
const length = originalMediaData.value.length;
|
||||
if (length === 0) return;
|
||||
|
||||
renderMediaData.value = [
|
||||
originalMediaData.value[length - 1], // 克隆最后一项
|
||||
...originalMediaData.value, // 原始数据
|
||||
originalMediaData.value[0] // 克隆第一项
|
||||
];
|
||||
};
|
||||
|
||||
// 启动自动轮播
|
||||
const startAutoPlay = () => {
|
||||
if (timer) clearInterval(timer);
|
||||
timer = setInterval(() => {
|
||||
if (!isHovering.value && !isTransitioning.value) {
|
||||
nextSlide();
|
||||
}
|
||||
}, 4000);
|
||||
};
|
||||
|
||||
// 切换到下一张
|
||||
const nextSlide = () => {
|
||||
currentIndex.value++;
|
||||
|
||||
// 延迟检查是否需要无缝跳转
|
||||
setTimeout(() => {
|
||||
checkSeamlessJump();
|
||||
}, 500); // 与transition时间一致
|
||||
};
|
||||
|
||||
// 检查是否需要无缝跳转(克隆项处理)
|
||||
const checkSeamlessJump = () => {
|
||||
const realLength = originalMediaData.value.length;
|
||||
|
||||
// 如果到达克隆的最后一项(索引 = realLength + 1)
|
||||
if (currentIndex.value >= realLength + 1) {
|
||||
// 1. 禁用过渡动画,实现瞬间跳转
|
||||
isTransitioning.value = true;
|
||||
|
||||
// 2. 瞬间跳转到真实的第二项(索引1),用户看不到跳变
|
||||
currentIndex.value = 1;
|
||||
|
||||
// 3. 等待两帧后恢复过渡动画(确保DOM已更新)
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
isTransitioning.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理鼠标进入
|
||||
const handleMouseEnter = () => {
|
||||
isHovering.value = true;
|
||||
};
|
||||
|
||||
// 处理鼠标离开
|
||||
const handleMouseLeave = () => {
|
||||
isHovering.value = false;
|
||||
};
|
||||
|
||||
// 计算当前显示的描述文字
|
||||
const currentDescription = computed(() => {
|
||||
const realIndex = getCurrentRealIndex();
|
||||
return originalMediaData.value[realIndex]?.description || '';
|
||||
});
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
initRenderData();
|
||||
startAutoPlay();
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
|
||||
// 获取当前真实的索引(排除克隆项的影响)
|
||||
const getCurrentRealIndex = () => {
|
||||
const realLength = originalMediaData.value.length;
|
||||
let realIndex = currentIndex.value - 1; // 减去克隆的首项偏移
|
||||
|
||||
// 处理边界情况(无缝循环时的克隆项)
|
||||
if (realIndex < 0) realIndex = realLength - 1;
|
||||
if (realIndex >= realLength) realIndex = 0;
|
||||
|
||||
return realIndex;
|
||||
};
|
||||
|
||||
// 跳转到指定幻灯片
|
||||
const goToSlide = (targetIndex: number) => {
|
||||
if (isTransitioning.value) return;
|
||||
|
||||
// 计算目标索引(考虑克隆项偏移)
|
||||
currentIndex.value = targetIndex + 1;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 228px;
|
||||
// border: 1px solid #7fd6ff;
|
||||
// border-radius: 5px;
|
||||
position: relative;
|
||||
overflow: hidden; // 隐藏超出容器的内容
|
||||
|
||||
// 跑马灯轨道
|
||||
.carousel-track {
|
||||
display: flex; // 横向排列所有媒体项
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.5s ease-in-out; // 0.5秒平滑过渡
|
||||
|
||||
// 禁用过渡动画(用于无缝跳转)
|
||||
&.no-transition {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
// 单个媒体项
|
||||
.carousel-item {
|
||||
min-width: 100%; // 每个项目占满容器宽度
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex-shrink: 0; // 防止被压缩
|
||||
|
||||
img,
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; // 保持比例填充
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
color: #fff;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 固定的面板指示器
|
||||
.pagination-dots-fixed {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
z-index: 10; // 确保在文字上方
|
||||
|
||||
.dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background-color: #D8D8D8;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background-color: #005293;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 独立的文字说明区域
|
||||
.description-text {
|
||||
// padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
// min-height: 40px;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 20px;
|
||||
|
||||
// 多行文本溢出省略
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
@ -12,7 +12,7 @@
|
||||
<div class="facility-info">
|
||||
<div class="facility-name">{{ facility.name }}</div>
|
||||
<div style="font-size: 16px;"> <span class="facility-count">{{ facility.count
|
||||
}}</span><span>座</span></div>
|
||||
}}</span><span>个</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
140
frontend/src/modules/liuyu/ai/spdzwaijc/index.vue
Normal file
140
frontend/src/modules/liuyu/ai/spdzwaijc/index.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="AI识别过鱼设施运行" :datetimePicker="datetimePicker">
|
||||
<div class="facility-grid">
|
||||
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||
<div style="width: 70px;height: 62px;display: flex;align-items: center;justify-content: center;">
|
||||
<div class="facility-icon">
|
||||
<i style="color: #fff;" :class="facility.icon" type="icon-shengtailiuliang2"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="facility-info">
|
||||
<div class="facility-name">{{ facility.name }}</div>
|
||||
<div style="font-size: 14px;"> <span class="facility-count">{{ facility.count }}</span>
|
||||
<span>次</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: 'zengzhizhanjiansheyunxing'
|
||||
});
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: `${new Date().getFullYear() - 1}`, // 上一年
|
||||
format: 'YYYY-MM',
|
||||
picker: 'month' as const,
|
||||
options: []
|
||||
});
|
||||
// 设施数据
|
||||
const facilities = ref([
|
||||
{
|
||||
name: '赶鱼栅运行',
|
||||
count: "32",
|
||||
icon: 'icon iconfont icon-ganyushan'
|
||||
},
|
||||
{
|
||||
name: '升鱼斗运行',
|
||||
count: '66',
|
||||
icon: 'icon iconfont icon-liuliangjiancezhan1'
|
||||
},
|
||||
{
|
||||
name: 'AGV小车',
|
||||
count: '0',
|
||||
icon: 'icon iconfont icon-agvxiaoche'
|
||||
},
|
||||
{
|
||||
name: '运鱼车运行',
|
||||
count: '0',
|
||||
icon: 'icon iconfont icon-yunyuche'
|
||||
},
|
||||
{
|
||||
name: '运鱼船出港',
|
||||
count: '32',
|
||||
icon: 'icon iconfont icon-yunyuchuan'
|
||||
},
|
||||
{
|
||||
name: '鱼道放流',
|
||||
count: '0',
|
||||
icon: 'icon iconfont icon-yudaofangliu'
|
||||
}
|
||||
]);
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
// 延迟初始化,确保容器已渲染
|
||||
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.facility-grid {
|
||||
width: 406px;
|
||||
flex-flow: wrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
}
|
||||
|
||||
.facility-card {
|
||||
width: 200px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.facility-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
// margin-right: 8px;
|
||||
background: rgb(47, 107, 152);
|
||||
border-radius: 50%;
|
||||
|
||||
.anticon {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.facility-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.facility-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
// margin-bottom: 4px;
|
||||
// font-weight: 500;
|
||||
}
|
||||
|
||||
.facility-count {
|
||||
font-size: 18px;
|
||||
color: #2f6b98;
|
||||
// font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,88 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<SidePanelItem title="生态流量低于限值告警情况">
|
||||
<div class="body_box">
|
||||
<div class="box" v-for="(item, index) in data" :key="index">
|
||||
<div>
|
||||
<div :class="item.icon" :style="{background:item.background}"></div>
|
||||
<div class="title_text">{{ item.name }}</div>
|
||||
</div>
|
||||
<div><span class="text_num">{{ item.num }}</span><span>座</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import SidePanelItem from "@/components/SidePanelItem/index.vue";
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: "jidiInfoMod",
|
||||
});
|
||||
const data = ref([
|
||||
{
|
||||
name:'正常',
|
||||
num:'447',
|
||||
icon:'iconfont icon-shuidianzhan1 zhuangji',
|
||||
background:'#78c300',
|
||||
},
|
||||
{
|
||||
name:'告警',
|
||||
num:'0',
|
||||
icon:'iconfont icon-shuidianzhanGaojing shebeiZhengchang',
|
||||
background:'#e55555',
|
||||
},
|
||||
{
|
||||
name:'无数据',
|
||||
num:'6',
|
||||
icon:'iconfont icon-shuidianzhanWushujv shebeiLixian',
|
||||
background:'#8d8d8d',
|
||||
},
|
||||
])
|
||||
// 页面加载时执行的逻辑
|
||||
onMounted(() => { });
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.body_box {
|
||||
width: 100%;
|
||||
height: 132px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.box {
|
||||
width: 126px;
|
||||
height: 116px;
|
||||
border: 1px solid rgba(108, 164, 247, 0.35);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.iconfont {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #78c300;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
.title_text{
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.text_num{
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,95 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<SidePanelItem title="水质监测数据告警情况" :select="select" >
|
||||
<div class="body_box">
|
||||
<div class="box" v-for="(item, index) in data" :key="index">
|
||||
<div>
|
||||
<div :class="item.icon" :style="{background:item.background}"></div>
|
||||
<div class="title_text">{{ item.name }}</div>
|
||||
</div>
|
||||
<div><span class="text_num">{{ item.num }}</span><span>座</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import SidePanelItem from "@/components/SidePanelItem/index.vue";
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: "jidiInfoMod",
|
||||
});
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
});
|
||||
const data = ref([
|
||||
{
|
||||
name:'正常',
|
||||
num:'56',
|
||||
icon:'iconfont icon-shuidianzhan1 zhuangji',
|
||||
background:'#78c300',
|
||||
},
|
||||
{
|
||||
name:'告警',
|
||||
num:'0',
|
||||
icon:'iconfont icon-shuidianzhanGaojing shebeiZhengchang',
|
||||
background:'#e55555',
|
||||
},
|
||||
{
|
||||
name:'无数据',
|
||||
num:'0',
|
||||
icon:'iconfont icon-shuidianzhanWushujv shebeiLixian',
|
||||
background:'#8d8d8d',
|
||||
},
|
||||
])
|
||||
// 页面加载时执行的逻辑
|
||||
onMounted(() => { });
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.body_box {
|
||||
width: 100%;
|
||||
height: 132px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.box {
|
||||
width: 126px;
|
||||
height: 116px;
|
||||
border: 1px solid rgba(108, 164, 247, 0.35);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.iconfont {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #78c300;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
.title_text{
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.text_num{
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
133
frontend/src/modules/lushengshengtaijiance/index.vue
Normal file
133
frontend/src/modules/lushengshengtaijiance/index.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="水生生态调查情况" :select="select" :datetimePicker="datetimePicker">
|
||||
<div class="facility-grid">
|
||||
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||
<div style="width: 60px;height: 62px;display: flex;align-items: center;justify-content: center;">
|
||||
<div class="facility-icon">
|
||||
<i style="color: #fff;" :class="facility.icon" type="icon-shengtailiuliang2"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="facility-info">
|
||||
<div class="facility-name">{{ facility.name }}</div>
|
||||
<div style="font-size: 16px;"> <span class="facility-count">{{ facility.count
|
||||
}}</span><span>个</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import type { EChartsOption } from 'echarts';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名
|
||||
defineOptions({
|
||||
name: 'shuishengshengtaijiance'
|
||||
});
|
||||
|
||||
// ==================== 响应式变量定义 ====================// 选择器配置
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
});
|
||||
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: `${new Date().getFullYear()}`,
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
});
|
||||
|
||||
const facilities = ref([
|
||||
{
|
||||
name: '红外相机数量',
|
||||
count: 31,
|
||||
icon: 'icon iconfont icon-shipinjiankongshebei'
|
||||
},
|
||||
{
|
||||
name: '重点陆生动物',
|
||||
count: 24,
|
||||
icon: 'icon iconfont icon-dongwujiuzhuzhan'
|
||||
},
|
||||
|
||||
]);
|
||||
// ==================== 生命周期钩子 ====================
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.facility-grid {
|
||||
width: 406px;
|
||||
flex-flow: wrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
}
|
||||
|
||||
.facility-card {
|
||||
width: 200px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.facility-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
// margin-right: 8px;
|
||||
background: #2f6b98;
|
||||
border-radius: 50%;
|
||||
|
||||
.anticon {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.facility-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.facility-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
// margin-bottom: 4px;
|
||||
// font-weight: 500;
|
||||
}
|
||||
|
||||
.facility-count {
|
||||
font-size: 18px;
|
||||
color: #2f6b98;
|
||||
// font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@ -2,25 +2,23 @@
|
||||
<template>
|
||||
<SidePanelItem title="主要栖息地基本信息" :select="select">
|
||||
<div class="table-container">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="pagination"
|
||||
:scroll="{ y: 450 }"
|
||||
size="small"
|
||||
>
|
||||
<a-table :columns="columns" :data-source="tableData" :pagination="pagination" :scroll="{ y: 450 }"
|
||||
size="small">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- 仅栖息地列需要自定义渲染 -->
|
||||
<template v-if="column.key === 'fhstnm'">
|
||||
<a-tooltip :title="record.fhstnm">
|
||||
<span
|
||||
class="habitat-cell"
|
||||
@click="handleHabitatClick(record)"
|
||||
>
|
||||
{{ record.fhstnm }}
|
||||
<span class="habitat-cell" @click="handleHabitatClick(record)">
|
||||
{{ formatValue(record.fhstnm) }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<!-- 其他列统一处理空值 -->
|
||||
<template v-else>
|
||||
<a-tooltip :title="formatValue(record[column.dataIndex])">
|
||||
<span>{{ formatValue(record[column.dataIndex]) }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
@ -92,153 +90,175 @@ const pagination = {
|
||||
}
|
||||
|
||||
// 模拟数据
|
||||
const tableData = ref([
|
||||
const tableData = ref(
|
||||
[
|
||||
{
|
||||
key: '1',
|
||||
fhstnm: '长江上游珍稀鱼类栖息地A区',
|
||||
bhhl: '金沙江',
|
||||
qxdbhdx: '中华鲟',
|
||||
qxdbhcd: 120.5,
|
||||
qxdbhmj: 85.3
|
||||
key: "1",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "泽曲河鱼类栖息地",
|
||||
bhhl: "泽曲河",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
fhstnm: '岷江特有鱼类保护区',
|
||||
bhhl: '岷江',
|
||||
qxdbhdx: '胭脂鱼',
|
||||
qxdbhcd: 95.2,
|
||||
qxdbhmj: 62.7
|
||||
key: "2",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "黄河干流羊曲库尾河段鱼类栖息地",
|
||||
bhhl: "黄河干流",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
fhstnm: '雅砻江冷水鱼栖息地',
|
||||
bhhl: '雅砻江',
|
||||
qxdbhdx: '齐口裂腹鱼',
|
||||
qxdbhcd: 110.8,
|
||||
qxdbhmj: 78.4
|
||||
key: "3",
|
||||
qxdbhdx: "裂腹鱼、高原鳅类、鲇类",
|
||||
fhstnm: "翠依河鱼类栖息地",
|
||||
bhhl: "翠依河",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
fhstnm: '大渡河高原鳅保护区',
|
||||
bhhl: '大渡河',
|
||||
qxdbhdx: '高原鳅',
|
||||
qxdbhcd: 88.6,
|
||||
qxdbhmj: 55.9
|
||||
key: "4",
|
||||
qxdbhdx: "裂腹鱼、高原鳅类、鲇类",
|
||||
fhstnm: "金棉河鱼类栖息地",
|
||||
bhhl: "金棉河",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
fhstnm: '乌江珍稀鱼类核心栖息地',
|
||||
bhhl: '乌江',
|
||||
qxdbhdx: '岩原鲤',
|
||||
qxdbhcd: 135.4,
|
||||
qxdbhmj: 92.1
|
||||
key: "5",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "斜曲河口以上河段鱼类栖息地",
|
||||
bhhl: "斜曲",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
fhstnm: '嘉陵江鱼类自然保护区',
|
||||
bhhl: '嘉陵江',
|
||||
qxdbhdx: '白甲鱼',
|
||||
qxdbhcd: 102.3,
|
||||
qxdbhmj: 71.5
|
||||
key: "6",
|
||||
qxdbhdx: "白鲟、达氏鲟、胭脂鱼等珍稀特有鱼类",
|
||||
fhstnm: "长江上游珍稀、特有鱼类国家级自然保护区",
|
||||
bhhl: "金沙江下游",
|
||||
qxdbhcd: 681,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
fhstnm: '沱江特有鱼类栖息地B段',
|
||||
bhhl: '沱江',
|
||||
qxdbhdx: '圆口铜鱼',
|
||||
qxdbhcd: 76.9,
|
||||
qxdbhmj: 48.2
|
||||
key: "7",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "六冲河种质资源保护区",
|
||||
bhhl: "六冲河、红岩河、后河、伍佐河",
|
||||
qxdbhcd: 146,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '8',
|
||||
fhstnm: '赤水河珍稀鱼类保护区',
|
||||
bhhl: '赤水河',
|
||||
qxdbhdx: '赤水河独鱼',
|
||||
qxdbhcd: 118.7,
|
||||
qxdbhmj: 82.6
|
||||
key: "8",
|
||||
qxdbhdx: "黄颡鱼、中华倒刺鲃、白甲鱼、鲤鱼、鲶鱼(鲇)、草鱼",
|
||||
fhstnm: "白果树水电站坝址下游至河口段鱼类栖息地",
|
||||
bhhl: "乌江干流",
|
||||
qxdbhcd: 13,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '9',
|
||||
fhstnm: '涪江高原鱼类栖息地',
|
||||
bhhl: '涪江',
|
||||
qxdbhdx: '松潘裸鲤',
|
||||
qxdbhcd: 92.4,
|
||||
qxdbhmj: 64.8
|
||||
key: "9",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "红水河岩滩、大化、百龙滩、桥巩河段鱼类栖息地",
|
||||
bhhl: "红水河",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
fhstnm: '渠江特有鱼类核心保护区',
|
||||
bhhl: '渠江',
|
||||
qxdbhdx: '华鳊',
|
||||
qxdbhcd: 105.6,
|
||||
qxdbhmj: 73.9
|
||||
key: "10",
|
||||
qxdbhdx: "长身鱖、鯮、湖南吻鮈、湘华鲮、湘江蛇鮈",
|
||||
fhstnm: "白洋河鱼类栖息地",
|
||||
bhhl: "白洋河",
|
||||
qxdbhcd: 121,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '11',
|
||||
fhstnm: '汉江上游珍稀鱼类栖息地',
|
||||
bhhl: '汉江',
|
||||
qxdbhdx: '多鳞铲颌鱼',
|
||||
qxdbhcd: 128.3,
|
||||
qxdbhmj: 89.7
|
||||
key: "11",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "锡潭水电站以下河段鱼类栖息地",
|
||||
bhhl: "麻溪",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '12',
|
||||
fhstnm: '丹江口库区鱼类保护区',
|
||||
bhhl: '丹江',
|
||||
qxdbhdx: '青鱼',
|
||||
qxdbhcd: 142.5,
|
||||
qxdbhmj: 98.4
|
||||
key: "12",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "西曲二级电站厂址至河口段鱼类栖息地",
|
||||
bhhl: "西曲",
|
||||
qxdbhcd: 23,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '13',
|
||||
fhstnm: '清江土著鱼类栖息地',
|
||||
bhhl: '清江',
|
||||
qxdbhdx: '清江银鱼',
|
||||
qxdbhcd: 85.7,
|
||||
qxdbhmj: 59.3
|
||||
key: "13",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "落漏河、达旦河段鱼类栖息地",
|
||||
bhhl: "支流落漏河、达旦河",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '14',
|
||||
fhstnm: '湘江珍稀鱼类核心栖息地C区',
|
||||
bhhl: '湘江',
|
||||
qxdbhdx: '湘鲫',
|
||||
qxdbhcd: 112.9,
|
||||
qxdbhmj: 79.6
|
||||
key: "14",
|
||||
qxdbhdx: "长身鱖、鯮、湖南吻鮈、湘华鲮、湘江蛇鮈",
|
||||
fhstnm: "陬溪鱼类栖息地",
|
||||
bhhl: "陬溪",
|
||||
qxdbhcd: 38.9,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '15',
|
||||
fhstnm: '赣江特有鱼类保护区',
|
||||
bhhl: '赣江',
|
||||
qxdbhdx: '赣江鮰',
|
||||
qxdbhcd: 98.4,
|
||||
qxdbhmj: 68.2
|
||||
key: "15",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "资水金塘冲至马迹塘段鱼类栖息地",
|
||||
bhhl: "资水",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '16',
|
||||
fhstnm: '闽江珍稀鱼类栖息地D段',
|
||||
bhhl: '闽江',
|
||||
qxdbhdx: '闽江小鳔鮈',
|
||||
qxdbhcd: 106.8,
|
||||
qxdbhmj: 74.5
|
||||
key: "16",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "西科河鱼类栖息地",
|
||||
bhhl: "西科河",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '17',
|
||||
fhstnm: '珠江三角洲鱼类核心保护区',
|
||||
bhhl: '珠江',
|
||||
qxdbhdx: '唐鱼',
|
||||
qxdbhcd: 132.6,
|
||||
qxdbhmj: 91.8
|
||||
key: "17",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "尕柯河鱼类栖息地",
|
||||
bhhl: "尕柯河",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: '18',
|
||||
fhstnm: '西江上游珍稀鱼类栖息地',
|
||||
bhhl: '西江',
|
||||
qxdbhdx: '光倒刺鲃',
|
||||
qxdbhcd: 115.3,
|
||||
qxdbhmj: 80.7
|
||||
key: "18",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "赛欠沟鱼类栖息地",
|
||||
bhhl: "赛欠沟",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: "19",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "得科河鱼类栖息地",
|
||||
bhhl: "得科河",
|
||||
qxdbhcd: null,
|
||||
qxdbhmj: null
|
||||
},
|
||||
{
|
||||
key: "20",
|
||||
qxdbhdx: null,
|
||||
fhstnm: "黄河班多坝下及巴沟河段鱼类栖息地",
|
||||
bhhl: "黄河干流、巴沟河",
|
||||
qxdbhcd: 11,
|
||||
qxdbhmj: null
|
||||
}
|
||||
])
|
||||
|
||||
// 格式化空值显示
|
||||
const formatValue = (value: any): string => {
|
||||
return value === null || value === undefined || value === '' ? '-' : String(value);
|
||||
}
|
||||
|
||||
// 点击事件处理(预留功能)
|
||||
const handleHabitatClick = (record: any) => {
|
||||
console.log('点击栖息地:', record)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="水文监测" :select="select">
|
||||
<SidePanelItem title="水文监测" :select="select" :scopeDate="datetimePicker" >
|
||||
<div class="chart-container">
|
||||
<div ref="chartRef" class="echarts-chart"></div>
|
||||
</div>
|
||||
@ -26,6 +26,20 @@ const select = ref({
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
});
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: (() => {
|
||||
const now = new Date();
|
||||
const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const lastMonthStr = `${lastMonth.getFullYear()}-${String(lastMonth.getMonth() + 1).padStart(2, '0')}`;
|
||||
return [lastMonthStr, currentMonth];
|
||||
})(),
|
||||
format: 'YY-MM',
|
||||
picker: 'month' as const,
|
||||
options: []
|
||||
});
|
||||
|
||||
// ==================== 图表相关响应式数据 ====================
|
||||
const chartRef = ref<HTMLElement | null>(null);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="水温监测" :select="select" :datetimePicker="datetimePicker">
|
||||
<SidePanelItem title="水温监测" :select="select" :scopeDate="datetimePicker">
|
||||
<div ref="chartRef" class="water-temp-chart"></div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
@ -34,9 +34,15 @@ const select = ref({
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
value: (() => {
|
||||
const now = new Date();
|
||||
const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const lastMonthStr = `${lastMonth.getFullYear()}-${String(lastMonth.getMonth() + 1).padStart(2, '0')}`;
|
||||
return [lastMonthStr, currentMonth];
|
||||
})(),
|
||||
format: 'YY-MM',
|
||||
picker: 'month' as const,
|
||||
options: []
|
||||
});
|
||||
|
||||
|
||||
196
frontend/src/modules/shuiShengShengTaiDiaoCha/index.vue
Normal file
196
frontend/src/modules/shuiShengShengTaiDiaoCha/index.vue
Normal file
@ -0,0 +1,196 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="水生生态调查" :select="select" :datetimePicker="datetimePicker">
|
||||
<div ref="chartRef" class="echarts-container"></div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import type { EChartsOption } from 'echarts';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名
|
||||
defineOptions({
|
||||
name: 'shuiShengShengTaiDiaoCha'
|
||||
});
|
||||
|
||||
// ==================== 响应式变量定义 ====================
|
||||
const chartRef = ref<HTMLDivElement>();
|
||||
let chartInstance: any = null;
|
||||
|
||||
// 选择器配置
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
});
|
||||
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: '2020',
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
});
|
||||
|
||||
// ==================== 模拟数据 ====================
|
||||
const mockData = [
|
||||
{ name: '鱼类', value: 320 },
|
||||
{ name: '底栖动物', value: 280 },
|
||||
{ name: '浮游植物', value: 250 },
|
||||
{ name: '浮游动物', value: 220 },
|
||||
{ name: '水生植物', value: 190 },
|
||||
{ name: '两栖类', value: 150 },
|
||||
{ name: '爬行类', value: 120 },
|
||||
{ name: '鸟类', value: 100 },
|
||||
{ name: '哺乳类', value: 80 },
|
||||
{ name: '其他', value: 60 }
|
||||
];
|
||||
|
||||
// 计算总数
|
||||
const totalValue = mockData.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
// ==================== 图表初始化函数 ====================
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
// 检查容器尺寸
|
||||
const rect = chartRef.value.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) {
|
||||
console.warn('图表容器尺寸为0,稍后重试...');
|
||||
setTimeout(() => initChart(), 50);
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化图表实例
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
// 图表配置
|
||||
const option: EChartsOption = {
|
||||
color: [
|
||||
'#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de',
|
||||
'#3ba272', '#fc8452', '#9a60b4', '#ea7ccc', '#8dd3a7'
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff'
|
||||
},
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'horizontal',
|
||||
bottom: '5%',
|
||||
data: mockData.map(item => item.name),
|
||||
icon: 'rect',
|
||||
itemWidth: 14,
|
||||
itemHeight: 14,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
height: 8
|
||||
},
|
||||
pageTextStyle: {
|
||||
color: '#333'
|
||||
},
|
||||
pageIconColor: '#2F6B98',
|
||||
pageIconInactiveColor: '#aaa'
|
||||
},
|
||||
title: [
|
||||
{
|
||||
text: `(类)`,
|
||||
top: '42%',
|
||||
textAlign: 'center',
|
||||
left: '50%',
|
||||
textStyle: {
|
||||
color: '#333',
|
||||
fontSize: 13,
|
||||
fontWeight: 500
|
||||
}
|
||||
},
|
||||
{
|
||||
text: totalValue.toString(),
|
||||
left: 'center',
|
||||
top: '32%',
|
||||
textStyle: {
|
||||
color: '#2F6B98',
|
||||
fontSize: 24
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '生态类别',
|
||||
type: 'pie',
|
||||
radius: ['35%', '65%'],
|
||||
center: ['50%', '40%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
formatter: '{b}: {c}'
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 8,
|
||||
length2: 10
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
data: mockData
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 设置配置项
|
||||
chartInstance.setOption(option);
|
||||
};
|
||||
|
||||
// ==================== 窗口resize处理 ====================
|
||||
const handleResize = () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.resize();
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 生命周期钩子 ====================
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
initChart();
|
||||
window.addEventListener('resize', handleResize);
|
||||
}, 50);
|
||||
});
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
chartInstance = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.echarts-container {
|
||||
width: 100%;
|
||||
height: 270px;
|
||||
min-height: 270px;
|
||||
}
|
||||
</style>
|
||||
133
frontend/src/modules/shuishengshengtaijiance/index.vue
Normal file
133
frontend/src/modules/shuishengshengtaijiance/index.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="水生生态调查情况" :select="select" :datetimePicker="datetimePicker">
|
||||
<div class="facility-grid">
|
||||
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||
<div style="width: 60px;height: 62px;display: flex;align-items: center;justify-content: center;">
|
||||
<div class="facility-icon">
|
||||
<i style="color: #fff;" :class="facility.icon" type="icon-shengtailiuliang2"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="facility-info">
|
||||
<div class="facility-name">{{ facility.name }}</div>
|
||||
<div style="font-size: 16px;"> <span class="facility-count">{{ facility.count
|
||||
}}</span><span>个</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import type { EChartsOption } from 'echarts';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名
|
||||
defineOptions({
|
||||
name: 'shuishengshengtaijiance'
|
||||
});
|
||||
|
||||
// ==================== 响应式变量定义 ====================// 选择器配置
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
});
|
||||
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: '2023',
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
});
|
||||
|
||||
const facilities = ref([
|
||||
{
|
||||
name: '水生态调查断面',
|
||||
count: 145,
|
||||
icon: 'icon iconfont icon-liuliangjiancezhan'
|
||||
},
|
||||
{
|
||||
name: '调查鱼类统计',
|
||||
count: 24,
|
||||
icon: 'icon iconfont icon-yuleizengzhizhan'
|
||||
},
|
||||
|
||||
]);
|
||||
// ==================== 生命周期钩子 ====================
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.facility-grid {
|
||||
width: 406px;
|
||||
flex-flow: wrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
}
|
||||
|
||||
.facility-card {
|
||||
width: 200px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.facility-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
// margin-right: 8px;
|
||||
background: #2f6b98;
|
||||
border-radius: 50%;
|
||||
|
||||
.anticon {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.facility-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.facility-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
// margin-bottom: 4px;
|
||||
// font-weight: 500;
|
||||
}
|
||||
|
||||
.facility-count {
|
||||
font-size: 18px;
|
||||
color: #2f6b98;
|
||||
// font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
121
frontend/src/modules/yunXingGaoJIng/Aisbdbyx/index.vue
Normal file
121
frontend/src/modules/yunXingGaoJIng/Aisbdbyx/index.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="AI识别大坝环境" :datetimePicker="datetimePicker">
|
||||
<div class="facility-grid">
|
||||
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||
<div style="width: 70px;height: 62px;display: flex;align-items: center;justify-content: center;">
|
||||
<div class="facility-icon">
|
||||
<i style="color: #fff;" :class="facility.icon" type="icon-shengtailiuliang2"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="facility-info">
|
||||
<div class="facility-name">{{ facility.name }}</div>
|
||||
<div style="font-size: 14px;"> <span class="facility-count">{{ facility.count }}</span>
|
||||
<span>次</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: 'zengzhizhanjiansheyunxing'
|
||||
});
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: `${new Date().getFullYear() - 1}`, // 上一年
|
||||
format: 'YYYY-MM',
|
||||
picker: 'month' as const,
|
||||
options: []
|
||||
});
|
||||
// 设施数据
|
||||
const facilities = ref([
|
||||
{
|
||||
name: '漂浮物识别',
|
||||
count: "34",
|
||||
icon: 'icon iconfont icon-piaofuwu'
|
||||
},
|
||||
{
|
||||
name: '叠梁门运行识别',
|
||||
count: '0',
|
||||
icon: 'icon iconfont icon-dieliangmen1'
|
||||
},
|
||||
|
||||
]);
|
||||
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
// 延迟初始化,确保容器已渲染
|
||||
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.facility-grid {
|
||||
width: 406px;
|
||||
flex-flow: wrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
}
|
||||
|
||||
.facility-card {
|
||||
width: 200px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.facility-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
// margin-right: 8px;
|
||||
background: rgb(47, 107, 152);
|
||||
border-radius: 50%;
|
||||
|
||||
.anticon {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.facility-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.facility-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
// margin-bottom: 4px;
|
||||
// font-weight: 500;
|
||||
}
|
||||
|
||||
.facility-count {
|
||||
font-size: 18px;
|
||||
color: #2f6b98;
|
||||
// font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@ -31,7 +31,7 @@ const select = ref({
|
||||
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
value: `${new Date().getFullYear() }`,
|
||||
format: 'YYYY', // YYYY-MM-DD HH
|
||||
picker: 'year' as const, // date | week | month | quarter | year
|
||||
options: []
|
||||
|
||||
@ -32,7 +32,7 @@ defineOptions({
|
||||
});
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
value: `${new Date().getFullYear() - 1}`, // 上一年
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
|
||||
@ -18,7 +18,7 @@ defineOptions({
|
||||
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
value: `${new Date().getFullYear() - 1}`, // 上一年
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
|
||||
@ -55,6 +55,8 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
if (userStore.Token) {
|
||||
// 登录成功,跳转到首页
|
||||
if (to.path === '/login') {
|
||||
//login
|
||||
if (to.path === '/login') {
|
||||
//login
|
||||
next({ path: '/' });
|
||||
@ -87,6 +89,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
accessRoutes = normalizeRoutes(accessRoutes);
|
||||
|
||||
accessRoutes.forEach((route: any) => {
|
||||
console.log('Adding Route:', route.path);
|
||||
router.addRoute(route);
|
||||
});
|
||||
|
||||
|
||||
@ -1,5 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
||||
import RightDrawer from "@/components/RightDrawer/index.vue";
|
||||
import ZhuanZhiIntroduce from '@/modules/fishSurvey/ZhuanZhiIntroduce/index.vue'
|
||||
import FishIntroduce from '@/modules/fishSurvey/FishIntroduce/index.vue'
|
||||
import FishVideo from '@/modules/fishSurvey/FishVideo/index.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>鱼类调查装置</h2>
|
||||
<div class="moduleContent">
|
||||
<div class="leftContent">
|
||||
<JidiSelectorMod />
|
||||
</div>
|
||||
<div class="rightContent">
|
||||
<RightDrawer>
|
||||
<ZhuanZhiIntroduce />
|
||||
<FishIntroduce />
|
||||
<FishVideo />
|
||||
</RightDrawer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@ -1,5 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
||||
import RightDrawer from "@/components/RightDrawer/index.vue";
|
||||
import LSSTJCQKTT from "@/modules/lushengshengtaijiance/index.vue"
|
||||
import Ysdwjc from "@/modules/Ysdwjc/index.vue"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>陆生生态调查</h2>
|
||||
<div class="moduleContent">
|
||||
<div class="leftContent">
|
||||
<JidiSelectorMod />
|
||||
</div>
|
||||
<div class="rightContent">
|
||||
<RightDrawer>
|
||||
<LSSTJCQKTT />
|
||||
<Ysdwjc />
|
||||
</RightDrawer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@ -1,5 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
||||
import RightDrawer from "@/components/RightDrawer/index.vue";
|
||||
import SSSTJCQKTT from "@/modules/shuishengshengtaijiance/index.vue"
|
||||
import SSSTDCQK from "@/modules/shuiShengShengTaiDiaoCha/index.vue" // 水生生态监测情况模块
|
||||
// import SSSTJCQKTT from "@/modules/shuishengshengtaijiance"
|
||||
import AlongFishMod from "@/modules/alongFishMod/index.vue" //沿程鱼类变化情况
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>水生生态调查</h2>
|
||||
<div class="moduleContent">
|
||||
<div class="leftContent">
|
||||
<JidiSelectorMod />
|
||||
</div>
|
||||
<div class="rightContent">
|
||||
<RightDrawer>
|
||||
<SSSTJCQKTT />
|
||||
<SSSTDCQK />
|
||||
<AlongFishMod />
|
||||
</RightDrawer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@ -1,5 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
||||
import RightDrawer from "@/components/RightDrawer/index.vue";
|
||||
import Aisbdbyx from '@/modules/yunXingGaoJIng/Aisbdbyx/index.vue'
|
||||
import SPDZWAIJC from '@/modules/liuyu/ai/spdzwaijc/index.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>智能分析</h2>
|
||||
<div class="moduleContent">
|
||||
<div class="leftContent">
|
||||
<JidiSelectorMod />
|
||||
</div>
|
||||
<div class="rightContent">
|
||||
<RightDrawer>
|
||||
<Aisbdbyx />
|
||||
<SPDZWAIJC />
|
||||
</RightDrawer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@ -1,5 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
||||
import RightDrawer from "@/components/RightDrawer/index.vue";
|
||||
import Shengtailiuliangxianzhigaojing from '@/modules/liuyu/yichangyujing/shengtailiuliangxianzhigaojing/shengtailiuliangxianshuju.vue'
|
||||
import Shuizhijiancegaojing from '@/modules/liuyu/yichangyujing/shuizhijiancegaojing/shuizhijianceshuju.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>监测告警</h2>
|
||||
<div class="moduleContent">
|
||||
<div class="leftContent">
|
||||
<JidiSelectorMod />
|
||||
</div>
|
||||
<div class="rightContent">
|
||||
<RightDrawer>
|
||||
<Shengtailiuliangxianzhigaojing />
|
||||
<Shuizhijiancegaojing />
|
||||
<div class="text"><img src="@/assets/components/tishi1.png" alt=""><span> 基于水电站及监测站最新监测数据进行告警统计</span></div>
|
||||
</RightDrawer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #969696;
|
||||
font-size: 14px;
|
||||
span{
|
||||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -34,7 +34,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
||||
},
|
||||
'/geoserver': {
|
||||
// target: 'https://211.99.26.225:18085', // 地图-线上API地址
|
||||
target: 'http://172.16.31.112:18084', // 地图-本地API地址
|
||||
// target: 'http://172.16.31.112:8093', // 地图-本地API地址
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: path => path.replace(new RegExp('^/geoserver'), '/geoserver')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user