电站专题-初版
This commit is contained in:
parent
22c00106c1
commit
d6ef2e3495
@ -11,9 +11,9 @@ VITE_APP_BASE_URL = 'http://localhost:8093'
|
|||||||
# 测试环境
|
# 测试环境
|
||||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
||||||
# 汤伟
|
# 汤伟
|
||||||
VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
|
# VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
|
||||||
# 李林
|
# 李林
|
||||||
# VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
|
VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
|
||||||
|
|
||||||
## 开发环境 附件服务地址
|
## 开发环境 附件服务地址
|
||||||
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
|
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export class MapCesium implements MapInterface {
|
|||||||
this.containerId = container.id;
|
this.containerId = container.id;
|
||||||
this.containerElement = container;
|
this.containerElement = container;
|
||||||
this.showLoadingOverlay(container);
|
this.showLoadingOverlay(container);
|
||||||
const token = 'bearer fa8aa37c-1e52-4631-a699-625b4147ace8';
|
const token = 'bearer b734a443-2c8f-4f4a-8698-44828cc5f709';
|
||||||
|
|
||||||
this.viewer = new Cesium.Viewer(container, {
|
this.viewer = new Cesium.Viewer(container, {
|
||||||
animation: false,
|
animation: false,
|
||||||
|
|||||||
@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 第一步:输入修改依据 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="dataSourceVisible"
|
||||||
|
:title="title"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@ok="handleDataSourceConfirm"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p style="color: red; margin-bottom: 8px">请输入修改依据以继续删除操作</p>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="dataSource"
|
||||||
|
placeholder="请输入修改依据"
|
||||||
|
:rows="4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 第二步:确认删除 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="confirmVisible"
|
||||||
|
title="确认删除"
|
||||||
|
ok-text="确认删除"
|
||||||
|
cancel-text="取消"
|
||||||
|
:ok-button-props="{ danger: true }"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleFinalDelete"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p style="color: red; font-weight: bold">请慎重操作!</p>
|
||||||
|
<p>{{ label }}名称:{{ deleteRecord?.stnm }}</p>
|
||||||
|
<p>修改依据:{{ dataSource || '无' }}</p>
|
||||||
|
<p>确定要删除该{{ label }}吗?</p>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { deletePowerInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
/** 自定义删除函数,接收 (record, reason) 参数 */
|
||||||
|
deleteFn?: (record: any, reason: string) => Promise<any>;
|
||||||
|
/** 弹窗标题 */
|
||||||
|
title?: string;
|
||||||
|
/** 显示标签(如"电站"、"数据") */
|
||||||
|
label?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
title: '删除电站',
|
||||||
|
label: '电站'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const dataSourceVisible = ref(false);
|
||||||
|
const confirmVisible = ref(false);
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
const dataSource = ref('');
|
||||||
|
const deleteRecord = ref<any>(null);
|
||||||
|
const onSuccess = ref<Function>(() => {});
|
||||||
|
|
||||||
|
// 打开删除弹窗
|
||||||
|
const open = (record: any, callback: Function) => {
|
||||||
|
deleteRecord.value = record;
|
||||||
|
dataSource.value = '';
|
||||||
|
dataSourceVisible.value = true;
|
||||||
|
onSuccess.value = callback;
|
||||||
|
};
|
||||||
|
useDraggable(dataSourceVisible, { boundary: true, resetOnOpen: true });
|
||||||
|
useDraggable(confirmVisible, { boundary: true, resetOnOpen: true });
|
||||||
|
// 修改依据确认
|
||||||
|
const handleDataSourceConfirm = () => {
|
||||||
|
dataSourceVisible.value = false;
|
||||||
|
confirmVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 最终删除
|
||||||
|
const handleFinalDelete = async () => {
|
||||||
|
confirmLoading.value = true; // 开始加载
|
||||||
|
try {
|
||||||
|
let res: any;
|
||||||
|
if (props.deleteFn) {
|
||||||
|
// 使用外部传入的删除函数
|
||||||
|
res = await props.deleteFn(deleteRecord.value, dataSource.value);
|
||||||
|
} else {
|
||||||
|
// 默认使用电站删除接口
|
||||||
|
const params = {
|
||||||
|
ids: [deleteRecord.value.stcd],
|
||||||
|
source: dataSource.value
|
||||||
|
};
|
||||||
|
res = await deletePowerInfo(params);
|
||||||
|
}
|
||||||
|
if (res?.code == 0 || res?.success) {
|
||||||
|
message.success('删除成功');
|
||||||
|
confirmVisible.value = false;
|
||||||
|
onSuccess.value();
|
||||||
|
} else {
|
||||||
|
message.error(res?.msg || '删除失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败,请重试');
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false; // 结束加载
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
@ -103,7 +103,7 @@ const buildGroupedColumns = () => {
|
|||||||
const groupMap = new Map<string, ColumnItem[]>();
|
const groupMap = new Map<string, ColumnItem[]>();
|
||||||
|
|
||||||
// 过滤掉 defaultConfig: true 的项(兼容字符串类型)
|
// 过滤掉 defaultConfig: true 的项(兼容字符串类型)
|
||||||
const filteredList = props.userColumnList.filter(item => item.defaultConfig !== true);
|
const filteredList = props.userColumnList.filter(item => item.enable == 1);
|
||||||
|
|
||||||
// 按 groupType 分组
|
// 按 groupType 分组
|
||||||
for (const item of filteredList) {
|
for (const item of filteredList) {
|
||||||
|
|||||||
@ -19,6 +19,16 @@
|
|||||||
<!-- ===== 概述 ===== -->
|
<!-- ===== 概述 ===== -->
|
||||||
<a-col :span="24"><div class="form-group-title">概述</div></a-col>
|
<a-col :span="24"><div class="form-group-title">概述</div></a-col>
|
||||||
|
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="电站编码" name="stcd">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.stcd"
|
||||||
|
placeholder="请输入电站编码"
|
||||||
|
:disabled="!isAdd"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="电站名称" name="ennm">
|
<a-form-item label="电站名称" name="ennm">
|
||||||
<a-input
|
<a-input
|
||||||
@ -1121,11 +1131,14 @@ const formRef = ref();
|
|||||||
const confirmLoading = ref(false);
|
const confirmLoading = ref(false);
|
||||||
const formData = ref<any>({});
|
const formData = ref<any>({});
|
||||||
const originalRecord = ref<any>({});
|
const originalRecord = ref<any>({});
|
||||||
const formRules = ref<any>({
|
const formRules = computed(() => ({
|
||||||
|
stcd: props.isAdd
|
||||||
|
? [{ required: true, message: '请输入电站编码', trigger: 'blur' }]
|
||||||
|
: [],
|
||||||
ennm: [{ required: true, message: '请输入电站名称', trigger: 'blur' }],
|
ennm: [{ required: true, message: '请输入电站名称', trigger: 'blur' }],
|
||||||
baseId: [{ required: true, message: '请选择基地', trigger: 'change' }],
|
baseId: [{ required: true, message: '请选择基地', trigger: 'change' }],
|
||||||
reachcd: [{ required: true, message: '请选择所在河段', trigger: 'change' }]
|
reachcd: [{ required: true, message: '请选择所在河段', trigger: 'change' }]
|
||||||
});
|
}));
|
||||||
|
|
||||||
// 确认弹框状态
|
// 确认弹框状态
|
||||||
const confirmModalVisible = ref(false);
|
const confirmModalVisible = ref(false);
|
||||||
|
|||||||
@ -60,6 +60,7 @@
|
|||||||
<!-- 编辑电站 Modal -->
|
<!-- 编辑电站 Modal -->
|
||||||
<EditPowerModal
|
<EditPowerModal
|
||||||
v-model:open="editVisible"
|
v-model:open="editVisible"
|
||||||
|
:is-add="isAdd"
|
||||||
:dvtp-list="dvtpList"
|
:dvtp-list="dvtpList"
|
||||||
:record="editRecord"
|
:record="editRecord"
|
||||||
:top-hynm-list="topHynmList"
|
:top-hynm-list="topHynmList"
|
||||||
@ -491,7 +492,7 @@ const fetchColumnConfig = () => {
|
|||||||
|
|
||||||
// 根据 API 数据动态构建表格列,按 orderIndex 排序,只保留 checked 为 1 的列
|
// 根据 API 数据动态构建表格列,按 orderIndex 排序,只保留 checked 为 1 的列
|
||||||
const sorted = [...list]
|
const sorted = [...list]
|
||||||
.filter((item: any) => item.checked === 1 && item.defaultConfig !== true)
|
.filter((item: any) => item.checked === 1 && item.enable == 1)
|
||||||
.sort((a: any, b: any) => a.orderIndex - b.orderIndex);
|
.sort((a: any, b: any) => a.orderIndex - b.orderIndex);
|
||||||
|
|
||||||
const cols = sorted.map((item: any) => {
|
const cols = sorted.map((item: any) => {
|
||||||
|
|||||||
@ -42,7 +42,7 @@ const columns = ref<any[]>([
|
|||||||
{ key: 'baseName', title: '水电基地', dataIndex: 'baseName', visible: true, width: 120, ellipsis: true },
|
{ key: 'baseName', title: '水电基地', dataIndex: 'baseName', visible: true, width: 120, ellipsis: true },
|
||||||
{ key: 'ennm', title: '所属电站', dataIndex: 'ennm', visible: true, width: 100, ellipsis: true },
|
{ key: 'ennm', title: '所属电站', dataIndex: 'ennm', visible: true, width: 100, ellipsis: true },
|
||||||
|
|
||||||
{ key: 'zzfldxName', title: '放流对象', dataIndex: 'zzfldxName', visible: true, width: 120, ellipsis: true },
|
{ key: 'zzfldx', title: '放流对象', dataIndex: 'zzfldx', visible: true, width: 120, ellipsis: true },
|
||||||
{ key: 'zzflcnt', title: '放流规模(尾)', dataIndex: 'zzflcnt', visible: true, width: 120 },
|
{ key: 'zzflcnt', title: '放流规模(尾)', dataIndex: 'zzflcnt', visible: true, width: 120 },
|
||||||
|
|
||||||
{ key: 'zzflbjfs', title: '标记方式', dataIndex: 'zzflbjfs', visible: true, width: 100, ellipsis: true },
|
{ key: 'zzflbjfs', title: '标记方式', dataIndex: 'zzflbjfs', visible: true, width: 100, ellipsis: true },
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="export-container">
|
<div class="export-container body_one">
|
||||||
<a-form
|
<a-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
@ -321,4 +321,9 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.body_one {
|
||||||
|
position: relative;
|
||||||
|
z-index: 900;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -548,4 +548,9 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
.body_one {
|
||||||
|
position: relative;
|
||||||
|
z-index: 900;
|
||||||
|
pointer-events: all;
|
||||||
|
}</style>
|
||||||
|
|||||||
@ -205,4 +205,9 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
.body_one {
|
||||||
|
position: relative;
|
||||||
|
z-index: 900;
|
||||||
|
pointer-events: all;
|
||||||
|
}</style>
|
||||||
|
|||||||
@ -14,9 +14,13 @@ VITE_APP_BASE_API = '/dev-api'
|
|||||||
VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
|
VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
|
||||||
# 李林
|
# 李林
|
||||||
# VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
|
# VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
|
||||||
|
# 小扈
|
||||||
|
# VITE_APP_BASE_URL = 'http://10.84.111.182:8093'
|
||||||
|
|
||||||
## 开发环境 附件服务地址
|
## 开发环境 附件服务地址
|
||||||
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
|
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
|
||||||
|
## 生产环境 附件服务地址
|
||||||
|
VITE_APP_ATTACHMENT_URL1 = 'http://211.99.26.225:12127'
|
||||||
# 地图服务地址
|
# 地图服务地址
|
||||||
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'
|
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'
|
||||||
# ?menu=systemManageMenu&page=disposeManage
|
# ?menu=systemManageMenu&page=disposeManage
|
||||||
@ -10,5 +10,7 @@ VITE_APP_BASE_URL = 'http://localhost:8093'
|
|||||||
VITE_APP_BASE_API_URL = 'https://211.99.26.225:12130/prod-api'
|
VITE_APP_BASE_API_URL = 'https://211.99.26.225:12130/prod-api'
|
||||||
## 生产环境 附件服务地址
|
## 生产环境 附件服务地址
|
||||||
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
|
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
|
||||||
|
## 生产环境 附件服务地址
|
||||||
|
VITE_APP_ATTACHMENT_URL1 = 'http://211.99.26.225:12127'
|
||||||
# 地图服务地址
|
# 地图服务地址
|
||||||
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'
|
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'
|
||||||
47
frontend/src/api/ZXZWYYunXingShuJu/index.ts
Normal file
47
frontend/src/api/ZXZWYYunXingShuJu/index.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取珍稀植物园下拉列表(植物园 + 年份)
|
||||||
|
* POST /wmp-env-server/env/vp/basin/GetKendoListCust
|
||||||
|
* @param params -- { filter: { rstcd }, group, groupResultFlat }
|
||||||
|
*/
|
||||||
|
export function getBotanicalGardenList(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/vp/basin/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取珍稀植物园统计数据(卡片展示用)
|
||||||
|
* POST /wmp-env-server/env/vp/bcount/GetKendoListCust
|
||||||
|
* @param params -- { filter: { year, stcd } }
|
||||||
|
*/
|
||||||
|
export function getBotanicalGardenStatistic(params: any) {
|
||||||
|
const { filter, group, groupResultFlat } = params;
|
||||||
|
return request({
|
||||||
|
url: '/wmp-env-server/env/vp/bcount/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
filter,
|
||||||
|
group,
|
||||||
|
groupResultFlat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 珍稀植物园详情表格(BasicTable listUrl 用)
|
||||||
|
* POST /wmp-env-server/env/vpr/basinVpIntDetail
|
||||||
|
* @param params -- BasicTable 传入的 { filter, skip, take, sort, ... }
|
||||||
|
*/
|
||||||
|
export function getBotanicalGardenDetailData(params: any) {
|
||||||
|
return request({
|
||||||
|
url: '/vap/vpr/basinVpIntDetail',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
154
frontend/src/api/dianZhanZhuanTi/index.ts
Normal file
154
frontend/src/api/dianZhanZhuanTi/index.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电站树形数据(用于 TreeSelect)
|
||||||
|
* POST /sys/psbmodulelbb/getTreeConfiguredps
|
||||||
|
* 与告警规则模块 getStationList 同接口
|
||||||
|
*/
|
||||||
|
export function getTreeConfiguredps() {
|
||||||
|
return request({
|
||||||
|
url: '/sys/psbmodulelbb/getTreeConfiguredps',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'wbsType',
|
||||||
|
operator: 'eq',
|
||||||
|
value: 'PSB_RVCD'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'baseId',
|
||||||
|
dir: 'asc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电站专题的布局配置(psbmodulelbb)
|
||||||
|
* POST /wmp-sys-server/sys/psbmodulelbb/GetKendoListCust
|
||||||
|
*/
|
||||||
|
export function getPowerSubjectLayout(
|
||||||
|
stcd: string,
|
||||||
|
templateId?: string,
|
||||||
|
ext = 'true'
|
||||||
|
) {
|
||||||
|
const filters = {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'templateId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: templateId || null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ext',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: ext
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return request({
|
||||||
|
url: '/sys/psbmodulelbb/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data: { filter: filters }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电站放流统计 - 年份聚合(获取数据年份范围)
|
||||||
|
* POST /wmp-env-server/fb/station/eng/GetAggregateData
|
||||||
|
*/
|
||||||
|
export function getPowerStationReleaseYear(data) {
|
||||||
|
return request({
|
||||||
|
url: '/fb/station/eng/GetAggregateData',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电站放流统计 - 明细数据
|
||||||
|
* POST /wmp-env-server/fb/station/eng/GetKendoListCust
|
||||||
|
*/
|
||||||
|
export function getPowerStationReleaseData(params: any) {
|
||||||
|
return request({
|
||||||
|
url: '/fb/station/eng/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data:params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取社会投资扶贫就业统计数据
|
||||||
|
* POST /wmp-eng-server/common/societyeffb/GetKendoListCust
|
||||||
|
*/
|
||||||
|
export function getSocialInvestmentData(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/wmp-eng-server/common/societyeffb/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取增殖站下拉列表
|
||||||
|
* POST /dec-lygk-base-server/base/msstbprpt/GetKendoList
|
||||||
|
*/
|
||||||
|
export function getZenZhiZhanList(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/fb/msstbprpt/GetKendoList',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取增殖站各步骤运行状态
|
||||||
|
* POST /wmp-env-server/fb/breedStage/GetKendoListCust
|
||||||
|
*/
|
||||||
|
export function getHatcheryOperationData(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/fb/breedStage/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取鱼类字典
|
||||||
|
* POST /wmp-env-server/env/fishDic/GetKendoList
|
||||||
|
*/
|
||||||
|
export function getFishDic(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/fpr/fishDic/GetKendoList',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电站放流统计 - 详情弹窗列表
|
||||||
|
* POST /wmp-env-server/fb/msfbrd/qgc/GetKendoListCust
|
||||||
|
*/
|
||||||
|
export function getPowerStationReleaseDetail(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/fb/msfbrdm/qgc/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,26 +1,98 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
|
||||||
// 获取所有倾斜摄影
|
//
|
||||||
export function warnruleGetKendoList(data: any) {
|
export function warnruleGetKendoList(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/wmp-sys-server/sys/warn/rule/GetKendoList',
|
url: '/sys/warnRule/GetKendoListCust',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data
|
data: data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//预警类型下拉框 //水质等级下拉框
|
export function bindGetKendoList(data: any) {
|
||||||
export function dictGetRemoteDictValue(data: any) {
|
|
||||||
return request({
|
return request({
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictValue',
|
url: '/sys/warnRule/bind/GetKendoList',
|
||||||
method: 'get',
|
method: 'post',
|
||||||
params: data
|
data: data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//新增修改获取相关输入数据
|
//新增修改获取相关输入数据
|
||||||
export function ruleysList(data: any) {
|
export function ruleysList(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/wmp-sys-server/sys/warn/rule/ysList',
|
url: '/sys/warnRule/ysList',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data
|
data: data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增/修改预警规则
|
||||||
|
export function warnruleAddOrUpdate(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/sys/warnRule/addOrUpdate',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除预警规则
|
||||||
|
export function warnruleDelete(params: { id: string }) {
|
||||||
|
return request({
|
||||||
|
url: '/sys/warnRule/delete',
|
||||||
|
method: 'get',
|
||||||
|
params: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所属电站列表
|
||||||
|
export function getStationList() {
|
||||||
|
return request({
|
||||||
|
url: '/sys/psbmodulelbb/getTreeConfiguredps',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'wbsType',
|
||||||
|
operator: 'eq',
|
||||||
|
value: 'PSB_RVCD'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除绑定预警规则
|
||||||
|
export function warnruleBindDelete(params: { id: string }) {
|
||||||
|
return request({
|
||||||
|
url: '/sys/warnRule/bind/delete',
|
||||||
|
method: 'get',
|
||||||
|
params: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据电站获取预警规则列表
|
||||||
|
export function getRuleListByStcd(params: { stcd: string; ruleType: string }) {
|
||||||
|
return request({
|
||||||
|
url: '/sys/warnRule/getRuleListByStcd',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据预警规则ID获取详情(含 detail 数据)
|
||||||
|
export function getDetailById(params: { ruleId: string }) {
|
||||||
|
return request({
|
||||||
|
url: '/sys/warnRule/getDetailById',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 修改规则是否展示
|
||||||
|
export function updateShow(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/sys/warnRule/updateShow',
|
||||||
|
method: 'post',
|
||||||
|
params:data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
25
frontend/src/api/video/index.ts
Normal file
25
frontend/src/api/video/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监控视频站点查询
|
||||||
|
* 根据 rstcd 和 sttpCode 查询电站下的监控视频站点列表
|
||||||
|
*/
|
||||||
|
export function videoSurveillance(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/vd/msstbprpt/GetKendoList',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时视频数据查询
|
||||||
|
* 根据 stcd 列表查询实时视频数据(截图、视频流地址等)
|
||||||
|
*/
|
||||||
|
export function realTimeVideo(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/vd/runData/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
27
frontend/src/assets/icons/arrowDownLine2.svg
Normal file
27
frontend/src/assets/icons/arrowDownLine2.svg
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="20px" height="8px" viewBox="0 0 20 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>arrowDownLine2</title>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter-1">
|
||||||
|
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0.184314 0 0 0 0 0.419608 0 0 0 0 0.596078 0 0 0 1.000000 0"></feColorMatrix>
|
||||||
|
</filter>
|
||||||
|
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
|
||||||
|
<stop stop-color="#00F4FE" stop-opacity="0" offset="0%"></stop>
|
||||||
|
<stop stop-color="#00EDFD" offset="50.3632549%"></stop>
|
||||||
|
<stop stop-color="#00E6FC" stop-opacity="0" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="1-首页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="1.1.1-电站专题" transform="translate(-1544.000000, -1239.000000)">
|
||||||
|
<g id="编组备份-4" transform="translate(1476.000000, 70.000000)">
|
||||||
|
<g id="编组-3" transform="translate(16.000000, 1072.000000)">
|
||||||
|
<g id="arrowDownLine2" transform="translate(51.571254, 96.742752)" filter="url(#filter-1)">
|
||||||
|
<g>
|
||||||
|
<polygon id="ArrowDownLine" fill="url(#linearGradient-2)" fill-rule="nonzero" transform="translate(10.505921, 4.257248) scale(-1, 1) rotate(-270.000000) translate(-10.505921, -4.257248) " points="7 -5.48550424 7.85749293 -6 14.0118417 4.25724788 7.85749293 14.5144958 7 14 12.8457465 4.25724788"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
34
frontend/src/assets/icons/fishStation2.svg
Normal file
34
frontend/src/assets/icons/fishStation2.svg
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="56px" height="56px" viewBox="0 0 56 56" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>FishStation</title>
|
||||||
|
<defs>
|
||||||
|
<path d="M9.0836405,48.6439317 C20.4848773,59.0912405 38.196623,58.3179289 48.6439317,46.9166921 C59.0912405,35.5154553 58.3179289,17.8037096 46.9166921,7.35640088 C35.5154553,-3.09090785 17.8037096,-2.31759633 7.35640088,9.0836405 C-3.09090785,20.4848773 -2.31759633,38.196623 9.0836405,48.6439317 Z" id="path-1"></path>
|
||||||
|
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="56.0003326" height="56.0003326" fill="white">
|
||||||
|
<use xlink:href="#path-1"></use>
|
||||||
|
</mask>
|
||||||
|
<circle id="path-3" cx="28.0001663" cy="28.0001663" r="21"></circle>
|
||||||
|
<mask id="mask-4" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="42" height="42" fill="white">
|
||||||
|
<use xlink:href="#path-3"></use>
|
||||||
|
</mask>
|
||||||
|
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-5">
|
||||||
|
<stop stop-color="#05D9FF" offset="0%"></stop>
|
||||||
|
<stop stop-color="#02B2FF" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="08_增殖放流" transform="translate(-1512.000000, -537.000000)">
|
||||||
|
<g id="BGSM备份-4" transform="translate(1464.000000, 436.000000)">
|
||||||
|
<g id="编组-3" transform="translate(16.000000, 61.000000)">
|
||||||
|
<g id="FishStation" transform="translate(32.000000, 40.000000)">
|
||||||
|
<use id="椭圆形" stroke-opacity="0.45" stroke="#007FCC" mask="url(#mask-2)" stroke-width="8.75" stroke-dasharray="42,1.75" xlink:href="#path-1"></use>
|
||||||
|
<circle id="椭圆形" stroke-opacity="0.65" stroke="#02B2FF" stroke-width="0.875" fill="#014380" cx="28.0001663" cy="28.0001663" r="24.0625"></circle>
|
||||||
|
<use id="椭圆形备份-3" stroke="#02B2FF" mask="url(#mask-4)" stroke-width="3.5" stroke-dasharray="3.5,0.875" xlink:href="#path-3"></use>
|
||||||
|
<path d="M23.6237633,32.0387229 L23.6237633,35.0001891 L26.7401593,33.2949884 C36.8668397,35.0001891 42.0001663,28.7188456 42.0001663,28.7188456 C39.2962975,25.3989847 32.606162,24.0521677 32.606162,24.0521677 L26.464835,21.0001663 L26.4191017,24.2761678 C23.5323113,24.634568 19.2697742,27.7313803 19.2697742,27.7313803 C18.6295063,27.1023125 14.0001663,24.0960544 14.0001663,24.0960544 C16.2000349,25.7116552 16.2000349,31.7689877 14.0001663,33.3845939 L19.2697742,29.7949868 C19.8204292,30.3335222 23.6237633,32.0387229 23.6237633,32.0387229 Z M36.6838963,26.9231206 C37.5089642,26.9231206 38.151097,27.5960533 38.151097,28.4043213 C38.151097,29.166854 37.5089642,29.8397895 36.6838963,29.8397895 C35.8597635,29.8397895 35.2185622,29.1668568 35.2185622,28.4043213 C35.217628,27.5960533 35.8597608,26.9231206 36.6838963,26.9231206 Z" id="形状" fill="url(#linearGradient-5)" fill-rule="nonzero"></path>
|
||||||
|
<circle id="椭圆形" fill="#FDDD60" cx="21" cy="22.75" r="1"></circle>
|
||||||
|
<circle id="椭圆形备份-5" fill="#FDDD60" cx="40.2501663" cy="21.8751663" r="1.75"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
id="map-baselayer"
|
||||||
class="baselayer-switcher"
|
class="baselayer-switcher"
|
||||||
:style="{ right: drawerOpen ? '480px' : '12px' }"
|
:style="{ right: drawerOpen ? '480px' : '12px' }"
|
||||||
v-if="uiStore.mapType == '2D'"
|
v-if="uiStore.mapType == '2D'"
|
||||||
@ -62,6 +63,16 @@ const nineSectionsImg: any = {
|
|||||||
|
|
||||||
const activeKey = ref(layers[0].key);
|
const activeKey = ref(layers[0].key);
|
||||||
|
|
||||||
|
// 监听外部通过 store 切换底图(如电站专题页)
|
||||||
|
watch(
|
||||||
|
() => mapViewStore.activeBaseLayerKey,
|
||||||
|
(newKey) => {
|
||||||
|
if (newKey && layers.some(l => l.key === newKey)) {
|
||||||
|
activeKey.value = newKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断图层管理中的基础底图(customBaseLayer)是否被选中
|
* 判断图层管理中的基础底图(customBaseLayer)是否被选中
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -974,7 +974,8 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
background-color: #fff;
|
||||||
|
// box-shadow: 0 1px 2px #00000026;
|
||||||
.qgc_title {
|
.qgc_title {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #e5edf3;
|
background-color: #e5edf3;
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
drawDotImg5,
|
drawDotImg5,
|
||||||
offset5
|
offset5
|
||||||
} from '@/utils/GisUrlList';
|
} from '@/utils/GisUrlList';
|
||||||
|
import { LAYOUT_GRID_SKELETONS } from '@/views/dianZhanZhuanTi/layoutGridSkeletons';
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__lyConfigs?: {
|
__lyConfigs?: {
|
||||||
@ -321,162 +322,82 @@ const getListByPosition = (data: any, position: string) =>
|
|||||||
/**
|
/**
|
||||||
* 设置地图组件位置
|
* 设置地图组件位置
|
||||||
* @param layoutType - 布局类型
|
* @param layoutType - 布局类型
|
||||||
* @param data - 布局数据
|
* @param data - 布局数据({ type, data: BclDataItem[] })
|
||||||
* @param offset - 偏移量
|
* @param offset - 面板宽度 + padding + gap,默认 460(440px + 10px + 10px)
|
||||||
|
* @param bottomRowHeight - 底部行高度(如 '200px'),无底部模块时不传
|
||||||
*/
|
*/
|
||||||
export const setMapLegendPos = (
|
export const setMapLegendPos = (
|
||||||
layoutType: string,
|
layoutType: string,
|
||||||
data: any,
|
data: any,
|
||||||
offset = 456
|
offset = 460,
|
||||||
|
bottomRowHeight?: string
|
||||||
) => {
|
) => {
|
||||||
const menuStateString = localStorage.getItem('menuState'); //处理澜沧江左侧菜单状态
|
const menuStateString = localStorage.getItem('menuState');
|
||||||
const menuState =
|
const menuState =
|
||||||
menuStateString !== null ? JSON.parse(menuStateString) : true;
|
menuStateString !== null ? JSON.parse(menuStateString) : true;
|
||||||
const _theme = localStorage.getItem('ly-theme') || window.__lyConfigs?.theme;
|
const _theme = localStorage.getItem('ly-theme') || window.__lyConfigs?.theme;
|
||||||
const leftEle = document.querySelector('#page-layout-left') as HTMLElement;
|
|
||||||
const rightEle = document.querySelector('#page-layout-right') as HTMLElement;
|
const legend = document.querySelector('#qgc-legendtl') as HTMLElement;
|
||||||
const bottomEle = document.querySelector(
|
const filter = document.querySelector('#map-filter-container') as HTMLElement;
|
||||||
'#page-layout-bottom'
|
const compassControl = document.querySelector('#map-compassControl') as HTMLElement;
|
||||||
) as HTMLElement;
|
const controller = document.querySelector('#map-controller') as HTMLElement;
|
||||||
const legend = document.querySelector('#qgc-legendtl') as HTMLElement; // 图例
|
const monitor = document.querySelector('#map-monitor') as HTMLElement;
|
||||||
const filter = document.querySelector('#map-filter-container') as HTMLElement; // 全局表单
|
const baselayer = document.querySelector('#map-baselayer') as HTMLElement;
|
||||||
const compassControl = document.querySelector(
|
|
||||||
'#map-compassControl'
|
|
||||||
) as HTMLElement; // 全局表单
|
|
||||||
const controller = document.querySelector('#map-controller') as HTMLElement; // 地图工具栏
|
|
||||||
const monitor = document.querySelector('#map-monitor') as HTMLElement; // 地图工具栏
|
|
||||||
const baselayer = document.querySelector('#map-baselayer') as HTMLElement; // 底图模式切换
|
|
||||||
// const vd = document.querySelector('#vd_operate') as HTMLElement // 底部视频
|
|
||||||
const left = [
|
|
||||||
'layout1',
|
|
||||||
'layout2',
|
|
||||||
'layout3',
|
|
||||||
'layout4',
|
|
||||||
'layout6',
|
|
||||||
'layout7',
|
|
||||||
'layout8',
|
|
||||||
'layout9',
|
|
||||||
'layout10',
|
|
||||||
'layout11',
|
|
||||||
'layout14',
|
|
||||||
'layout15',
|
|
||||||
'layout16',
|
|
||||||
'layout17'
|
|
||||||
]; // 左侧布局
|
|
||||||
const right = [
|
|
||||||
'layout1',
|
|
||||||
'layout2',
|
|
||||||
'layout3',
|
|
||||||
'layout4',
|
|
||||||
'layout5',
|
|
||||||
'layout6',
|
|
||||||
'layout8',
|
|
||||||
'layout10',
|
|
||||||
'layout11',
|
|
||||||
'layout15',
|
|
||||||
'layout16',
|
|
||||||
'layout17'
|
|
||||||
]; // 右侧布局
|
|
||||||
const bottom1 = [
|
|
||||||
'layout1',
|
|
||||||
'layout6',
|
|
||||||
'layout8',
|
|
||||||
'layout9',
|
|
||||||
'layout10',
|
|
||||||
'layout16'
|
|
||||||
]; // 三行底部布局
|
|
||||||
const bottom2 = ['layout2', 'layout15']; // 四行底部布局
|
|
||||||
const w = `${offset}px`;
|
|
||||||
const l = `${_theme === 'ly-8' ? (menuState ? 643 : 510) : offset}px`;
|
|
||||||
let b = `0px`;
|
|
||||||
const le = ['layout17', 'layout10'].includes(layoutType) ? 0 : 1;
|
|
||||||
const leftList = getListByPosition(data, 'left');
|
const leftList = getListByPosition(data, 'left');
|
||||||
const rightList = getListByPosition(data, 'right');
|
const rightList = getListByPosition(data, 'right');
|
||||||
const bottomList = getListByPosition(data, 'bottom');
|
const bottomList = getListByPosition(data, 'bottom');
|
||||||
let bottom = '0';
|
|
||||||
|
const hasLeft = leftList?.length > 0;
|
||||||
|
const hasRight = rightList?.length > 0;
|
||||||
|
const hasBottom = bottomList?.length > 0;
|
||||||
|
|
||||||
|
console.log('[图例] hasLeft:', hasLeft, 'hasBottom:', hasBottom, 'bottomRowHeight:', bottomRowHeight);
|
||||||
|
|
||||||
|
const w = `${offset}px`;
|
||||||
|
const l = `${_theme === 'ly-8' ? (menuState ? 643 : 510) : offset}px`;
|
||||||
|
let b = `0px`;
|
||||||
|
|
||||||
if (_theme === 'ly-8') {
|
if (_theme === 'ly-8') {
|
||||||
if (window.__mapMode === '3D') {
|
b = window.__mapMode === '3D' ? '200px' : `${menuState ? 200 : 50}px`;
|
||||||
b = `200px`;
|
}
|
||||||
} else {
|
|
||||||
b = `${menuState ? 200 : 50}px`;
|
// 底部偏移
|
||||||
|
// px 值:直接 + padding(10px)
|
||||||
|
// fr 值:按占比算,如 3 行各 1fr = calc((100% - 40px) / 3 + 10px)
|
||||||
|
let bottomOffset = '12px';
|
||||||
|
if (hasBottom && bottomRowHeight) {
|
||||||
|
const pxMatch = bottomRowHeight.match(/^(\d+)px$/);
|
||||||
|
if (pxMatch) {
|
||||||
|
bottomOffset = `${parseInt(pxMatch[1]) + 10}px`;
|
||||||
|
} else if (bottomRowHeight.endsWith('fr')) {
|
||||||
|
const rows = (LAYOUT_GRID_SKELETONS[layoutType]?.gridTemplateRows || '').split(' ').filter(Boolean);
|
||||||
|
const n = rows.length; // 总行数
|
||||||
|
const gaps = (n - 1) * 10; // 行间距
|
||||||
|
bottomOffset = `calc((100% - 20px - ${gaps}px) / ${n} + 10px)`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bottomList?.length > 0) {
|
|
||||||
if (bottom1.includes(layoutType)) {
|
|
||||||
bottom = 'calc((100% - 16px) / 3 + 8px)';
|
|
||||||
}
|
|
||||||
if (bottom2.includes(layoutType)) {
|
|
||||||
bottom = 'calc((100% - 24px) / 4 + 8px)';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 没有底部布局时,底部高度为
|
|
||||||
bottom = '28px';
|
|
||||||
}
|
|
||||||
|
|
||||||
const rle = ['layout6'].includes(layoutType) || bottom != '28px' ? 0 : 1;
|
|
||||||
|
|
||||||
const leftHide = leftEle?.classList?.contains('hide');
|
|
||||||
const rightHide = rightEle?.classList?.contains('hide');
|
|
||||||
const bottomHide = bottomEle?.classList?.contains('hide');
|
|
||||||
if (legend) {
|
if (legend) {
|
||||||
legend.style.left =
|
legend.style.left = hasLeft ? `calc(${l} - 10px)` : b;
|
||||||
!leftHide && left.includes(layoutType) && leftList?.length > le ? l : b;
|
legend.style.bottom = bottomOffset;
|
||||||
legend.style.bottom = bottomHide
|
|
||||||
? '0'
|
|
||||||
: bottomList?.length > 0
|
|
||||||
? bottom
|
|
||||||
: '12px';
|
|
||||||
}
|
}
|
||||||
if (filter) {
|
if (filter) {
|
||||||
if (layoutType === 'layout10') {
|
filter.style.left = hasLeft ? l : b;
|
||||||
filter.style.left =
|
|
||||||
!leftHide && left.includes(layoutType) && leftList?.length > 1 ? l : b;
|
|
||||||
} else {
|
|
||||||
filter.style.left =
|
|
||||||
!leftHide &&
|
|
||||||
left.includes(layoutType) &&
|
|
||||||
leftList?.length > 0 &&
|
|
||||||
layoutType !== 'layout17'
|
|
||||||
? l
|
|
||||||
: b;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (compassControl) {
|
if (compassControl) {
|
||||||
if (layoutType === 'layout10') {
|
compassControl.style.left = hasLeft ? l : b;
|
||||||
compassControl.style.left =
|
|
||||||
!leftHide && left.includes(layoutType) && leftList?.length > 1 ? l : b;
|
|
||||||
} else {
|
|
||||||
compassControl.style.left =
|
|
||||||
!leftHide &&
|
|
||||||
left.includes(layoutType) &&
|
|
||||||
leftList?.length > 0 &&
|
|
||||||
layoutType !== 'layout17'
|
|
||||||
? l
|
|
||||||
: b;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (controller) {
|
if (controller) {
|
||||||
controller.style.right =
|
controller.style.right = hasRight ? w : '0';
|
||||||
!rightHide && right.includes(layoutType) && rightList?.length > rle
|
controller.style.bottom = hasBottom ? `calc(${bottomOffset} + 10px)` : '0';
|
||||||
? w
|
|
||||||
: '0';
|
|
||||||
controller.style.bottom = bottomHide ? '0' : bottom;
|
|
||||||
}
|
}
|
||||||
if (monitor) {
|
if (monitor) {
|
||||||
monitor.style.right =
|
monitor.style.right = hasRight ? w : '0';
|
||||||
!rightHide && right.includes(layoutType) && rightList?.length > rle
|
|
||||||
? w
|
|
||||||
: '0';
|
|
||||||
// monitor.style.bottom = bottomHide ? '0' : bottom
|
|
||||||
}
|
}
|
||||||
if (baselayer) {
|
if (baselayer) {
|
||||||
baselayer.style.right =
|
baselayer.style.right = hasRight ? `calc(${w} + 60px)` : '60px';
|
||||||
!rightHide && right.includes(layoutType) && rightList?.length > rle
|
baselayer.style.bottom = hasBottom ? bottomOffset : '0';
|
||||||
? `calc(${w} + 60px)`
|
|
||||||
: '60px';
|
|
||||||
baselayer.style.bottom = bottomHide ? '0' : bottom;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -142,7 +142,7 @@ export class MapCesium implements MapInterface {
|
|||||||
try {
|
try {
|
||||||
this.containerId = container.id;
|
this.containerId = container.id;
|
||||||
this.containerElement = container;
|
this.containerElement = container;
|
||||||
const token = 'bearer fa8aa37c-1e52-4631-a699-625b4147ace8';
|
const token = 'bearer b734a443-2c8f-4f4a-8698-44828cc5f709';
|
||||||
|
|
||||||
this.viewer = new Cesium.Viewer(container, {
|
this.viewer = new Cesium.Viewer(container, {
|
||||||
animation: false,
|
animation: false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="map-controller" :style="{ right: drawerOpen ? '480px' : '12px' }">
|
<div id="map-controller" class="map-controller" :style="{ right: drawerOpen ? '480px' : '12px' }">
|
||||||
<div
|
<div
|
||||||
class="map-controller-group"
|
class="map-controller-group"
|
||||||
v-for="(item, index) in controllers"
|
v-for="(item, index) in controllers"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mapLegendView" v-show="!uiStore.isRoaming">
|
<div id="qgc-legendtl" class="mapLegendView" v-show="!uiStore.isRoaming">
|
||||||
<div class="legendTitle">
|
<div class="legendTitle">
|
||||||
图例
|
图例
|
||||||
<span class="legendBtn" @click="isOpen = !isOpen">
|
<span class="legendBtn" @click="isOpen = !isOpen">
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue';
|
import { ref, onMounted, onUnmounted, watch, nextTick, computed, inject } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import { wqGetKendoListCust } from '@/api/sz'
|
import { wqGetKendoListCust } from '@/api/sz'
|
||||||
@ -247,8 +247,12 @@ watch(tabs, (newVal) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal && newVal.wbsCode) {
|
if (newVal && newVal.wbsCode) {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
@ -751,8 +755,9 @@ onMounted(() => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initChart();
|
initChart();
|
||||||
// 如果已有 selectedItem,触发数据加载
|
// 如果已有 selectedItem,触发数据加载
|
||||||
if (JidiSelectEventStore.selectedItem?.wbsCode) {
|
const currentStation = injectedStation?.value || JidiSelectEventStore.selectedItem;
|
||||||
baseid.value = JidiSelectEventStore.selectedItem.wbsCode;
|
if (currentStation?.wbsCode) {
|
||||||
|
baseid.value = currentStation.wbsCode;
|
||||||
getEchartsData();
|
getEchartsData();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|||||||
@ -29,7 +29,8 @@ import {
|
|||||||
onMounted,
|
onMounted,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
watch,
|
watch,
|
||||||
nextTick
|
nextTick,
|
||||||
|
inject
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import type { ECharts } from 'echarts';
|
import type { ECharts } from 'echarts';
|
||||||
@ -727,8 +728,12 @@ onBeforeUnmount(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
async newVal => {
|
async newVal => {
|
||||||
if (!newVal || !newVal.wbsCode) {
|
if (!newVal || !newVal.wbsCode) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch, inject, computed } from 'vue';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import { environmentalProtectionConstruction } from '@/api/home';
|
import { environmentalProtectionConstruction } from '@/api/home';
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
@ -142,9 +142,12 @@ const handleCardClick = (facility: any) => {
|
|||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听基地变化
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal && newVal.wbsCode) {
|
if (newVal && newVal.wbsCode) {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
|
|||||||
@ -62,8 +62,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick,watch } from 'vue';
|
import { ref, onMounted, onUnmounted, nextTick, watch, inject, computed } from 'vue';
|
||||||
// import { ref, watch } from 'vue';
|
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import LsstjkTk from './LsstjkTk.vue';
|
import LsstjkTk from './LsstjkTk.vue';
|
||||||
@ -288,9 +287,13 @@ const getSelectData = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
// ==================== 监听基地变化 ====================
|
// ==================== 监听基地变化 ====================
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
async newVal => {
|
async newVal => {
|
||||||
if (newVal && newVal.wbsCode) {
|
if (newVal && newVal.wbsCode) {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const useMapViewStore = defineStore('map-view', () => {
|
|||||||
const searchTimeRange = ref<[any, any]>([dayjs().subtract(1, 'M'), dayjs()]);
|
const searchTimeRange = ref<[any, any]>([dayjs().subtract(1, 'M'), dayjs()]);
|
||||||
const selectedBaseId = ref('');
|
const selectedBaseId = ref('');
|
||||||
const currentZoomLevel = ref(4.5);
|
const currentZoomLevel = ref(4.5);
|
||||||
|
const activeBaseLayerKey = ref('s_province_boundaries');
|
||||||
|
|
||||||
// 备注:统一写入当前选中的图层 key,始终保持去重后的结果。
|
// 备注:统一写入当前选中的图层 key,始终保持去重后的结果。
|
||||||
const setCheckedLayerKeys = (keys: string[] = []) => {
|
const setCheckedLayerKeys = (keys: string[] = []) => {
|
||||||
@ -72,12 +73,18 @@ export const useMapViewStore = defineStore('map-view', () => {
|
|||||||
currentZoomLevel.value = 4.5;
|
currentZoomLevel.value = 4.5;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setActiveBaseLayerKey = (key: string) => {
|
||||||
|
activeBaseLayerKey.value = key;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
checkedLayerKeys,
|
checkedLayerKeys,
|
||||||
legendCheckedState,
|
legendCheckedState,
|
||||||
searchTimeRange,
|
searchTimeRange,
|
||||||
selectedBaseId,
|
selectedBaseId,
|
||||||
currentZoomLevel,
|
currentZoomLevel,
|
||||||
|
activeBaseLayerKey,
|
||||||
|
setActiveBaseLayerKey,
|
||||||
setCheckedLayerKeys,
|
setCheckedLayerKeys,
|
||||||
getCheckedLayerKeys,
|
getCheckedLayerKeys,
|
||||||
setLegendCheckedState,
|
setLegendCheckedState,
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
import { ref, onMounted, onUnmounted, nextTick, watch, inject, computed } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import type { EChartsOption } from 'echarts';
|
import type { EChartsOption } from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
@ -637,8 +637,12 @@ const getselsectData = async () => {
|
|||||||
//
|
//
|
||||||
};
|
};
|
||||||
////
|
////
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
newVal => {
|
newVal => {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
getselsectData();
|
getselsectData();
|
||||||
|
|||||||
@ -98,7 +98,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, watch, inject, computed } from 'vue';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import {
|
import {
|
||||||
qgcetQgcStaticData,
|
qgcetQgcStaticData,
|
||||||
@ -301,8 +301,12 @@ const getData = async () => {
|
|||||||
spinning.value = false;
|
spinning.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
newVal => {
|
newVal => {
|
||||||
if (newVal && newVal.wbsCode) {
|
if (newVal && newVal.wbsCode) {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
|
|||||||
@ -61,7 +61,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch, inject, computed } from 'vue';
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import SsstdcgkTk from './SsstdcgkTk.vue';
|
import SsstdcgkTk from './SsstdcgkTk.vue';
|
||||||
@ -322,9 +322,13 @@ const getSelectData = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
// ==================== 监听基地变化 ====================
|
// ==================== 监听基地变化 ====================
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
async newVal => {
|
async newVal => {
|
||||||
if (newVal && newVal.wbsCode) {
|
if (newVal && newVal.wbsCode) {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, nextTick, watch, inject, computed } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import type { EChartsOption } from 'echarts';
|
import type { EChartsOption } from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
@ -1170,9 +1170,13 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
const wbsCode = ref('');
|
const wbsCode = ref('');
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
newVal => {
|
newVal => {
|
||||||
console.log(newVal);
|
console.log(newVal);
|
||||||
wbsCode.value = newVal.wbsCode;
|
wbsCode.value = newVal.wbsCode;
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
import { ref, onMounted, onUnmounted, nextTick, watch, inject, computed } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import type { ECharts } from 'echarts';
|
import type { ECharts } from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
@ -282,8 +282,12 @@ const handlePanelChange1 = async data => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => stationSource.value,
|
||||||
newVal => {
|
newVal => {
|
||||||
if (newVal && newVal.wbsCode) {
|
if (newVal && newVal.wbsCode) {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
|
|||||||
@ -103,7 +103,7 @@ const buildGroupedColumns = () => {
|
|||||||
const groupMap = new Map<string, ColumnItem[]>();
|
const groupMap = new Map<string, ColumnItem[]>();
|
||||||
|
|
||||||
// 过滤掉 defaultConfig: true 的项(兼容字符串类型)
|
// 过滤掉 defaultConfig: true 的项(兼容字符串类型)
|
||||||
const filteredList = props.userColumnList.filter(item => item.defaultConfig !== true);
|
const filteredList = props.userColumnList.filter(item => item.enable == 1);
|
||||||
|
|
||||||
// 按 groupType 分组
|
// 按 groupType 分组
|
||||||
for (const item of filteredList) {
|
for (const item of filteredList) {
|
||||||
|
|||||||
@ -481,7 +481,7 @@ const fetchColumnConfig = () => {
|
|||||||
|
|
||||||
// 根据 API 数据动态构建表格列,按 orderIndex 排序,只保留 checked 为 1 的列
|
// 根据 API 数据动态构建表格列,按 orderIndex 排序,只保留 checked 为 1 的列
|
||||||
const sorted = [...list]
|
const sorted = [...list]
|
||||||
.filter((item: any) => item.checked === 1 && item.defaultConfig !== true)
|
.filter((item: any) => item.checked === 1 && item.enable == 1)
|
||||||
.sort((a: any, b: any) => a.orderIndex - b.orderIndex);
|
.sort((a: any, b: any) => a.orderIndex - b.orderIndex);
|
||||||
|
|
||||||
const cols = sorted.map((item: any) => {
|
const cols = sorted.map((item: any) => {
|
||||||
|
|||||||
@ -97,12 +97,12 @@ const previewVideoUrl = ref('');
|
|||||||
useDraggable(imagePreviewVisible, { boundary: true, resetOnOpen: true });
|
useDraggable(imagePreviewVisible, { boundary: true, resetOnOpen: true });
|
||||||
useDraggable(videoPreviewVisible, { boundary: true, resetOnOpen: true });
|
useDraggable(videoPreviewVisible, { boundary: true, resetOnOpen: true });
|
||||||
const openImagePreview = (url: string) => {
|
const openImagePreview = (url: string) => {
|
||||||
previewImageUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL + '/?' + url;
|
previewImageUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL1 + '/?' + url;
|
||||||
imagePreviewVisible.value = true;
|
imagePreviewVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const openVideoPreview = (url: string) => {
|
const openVideoPreview = (url: string) => {
|
||||||
previewVideoUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL + '/?' + url;
|
previewVideoUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL1 + '/?' + url;
|
||||||
videoPreviewVisible.value = true;
|
videoPreviewVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,161 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="社会投资扶贫就业">
|
||||||
|
<a-spin v-if="loading" class="spin-center" />
|
||||||
|
|
||||||
|
<a-empty v-else-if="!hasData" description="暂无数据" class="empty-center" />
|
||||||
|
|
||||||
|
<div v-else class="card-list">
|
||||||
|
<a-card
|
||||||
|
v-for="item in cardItems"
|
||||||
|
:key="item.title"
|
||||||
|
class="stat-card"
|
||||||
|
hoverable
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
<i class="icon iconfont" :class="item.icon" />
|
||||||
|
<span class="card-title">{{ item.title }}</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="card-value"
|
||||||
|
:style="item.color ? { color: item.color } : {}"
|
||||||
|
>
|
||||||
|
{{ item.totalNum ?? '-' }}
|
||||||
|
</span>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch, inject } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import { getSocialInvestmentData } from '@/api/dianZhanZhuanTi';
|
||||||
|
|
||||||
|
defineOptions({ name: 'DZSheHuiTouZiFP' });
|
||||||
|
|
||||||
|
// ==================== 注入电站上下文 ====================
|
||||||
|
const dianZhanStation = inject<any>('dianZhanStation', ref(null));
|
||||||
|
const stcd = computed(() => dianZhanStation.value?.stcd || '');
|
||||||
|
|
||||||
|
// ==================== 状态 ====================
|
||||||
|
const loading = ref(false);
|
||||||
|
const hasData = ref(false);
|
||||||
|
const cardItems = ref<
|
||||||
|
Array<{ icon: string; title: string; totalNum: number | string | null; color?: string }>
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
// ==================== API ====================
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!stcd.value) return;
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{ field: 'stcd', operator: 'eq', dataType: 'string', value: stcd.value }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
group: [{ dir: 'des', field: 'stcd' }],
|
||||||
|
aggregate: [
|
||||||
|
{ aggregate: 'sum', field: 'stjtz' },
|
||||||
|
{ aggregate: 'sum', field: 'dirjob' },
|
||||||
|
{ aggregate: 'sum', field: 'indjob' },
|
||||||
|
{ aggregate: 'sum', field: 'poorjobeff' },
|
||||||
|
{ aggregate: 'sum', field: 'ldtz' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getSocialInvestmentData(params);
|
||||||
|
const data = res?.data?.data?.[0];
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
hasData.value = true;
|
||||||
|
const { dirjob, indjob, poorjobeff, ldtz, stjtz } = data;
|
||||||
|
cardItems.value = [
|
||||||
|
{ icon: 'icon-sum', title: '扶贫就业收益(万元)', totalNum: poorjobeff, color: '#FCA900' },
|
||||||
|
{ icon: 'icon-people', title: '直接就业人口(人)', totalNum: dirjob },
|
||||||
|
{ icon: 'icon-people', title: '间接就业人口(人)', totalNum: indjob },
|
||||||
|
{ icon: 'icon-sum', title: '总投资(万元)', totalNum: stjtz, color: '#FCA900' },
|
||||||
|
{ icon: 'icon-sum', title: '总拉动投资(万元)', totalNum: ldtz, color: '#FCA900' }
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
hasData.value = false;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
hasData.value = false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 监听 ====================
|
||||||
|
watch(stcd, (newStcd) => {
|
||||||
|
if (newStcd) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.spin-center,
|
||||||
|
.empty-center {
|
||||||
|
/* display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center; */
|
||||||
|
min-height: 197px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
overflow: auto;
|
||||||
|
height: 197px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
cursor: default;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card :deep(.ant-card-body) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content .icon {
|
||||||
|
font-size: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 17px;
|
||||||
|
color: #2f6b98;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000000d9;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-value {
|
||||||
|
width: 50%;
|
||||||
|
font-size: 24px;
|
||||||
|
/* font-weight: 600; */
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,498 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dz-video-monitor">
|
||||||
|
<!-- 标题栏 -->
|
||||||
|
<div class="video-header">
|
||||||
|
<span class="video-title">水电站视频监控</span>
|
||||||
|
<span class="more-btn" @click="openMoreModal">
|
||||||
|
<i class="iconfont icon-moreVideo" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 视频缩略图跑马灯 -->
|
||||||
|
<div class="video-carousel-wrapper" ref="carouselRef">
|
||||||
|
<div v-if="videoList.length === 0" class="video-empty">
|
||||||
|
<a-empty description="暂无视频数据" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<!-- 左箭头 -->
|
||||||
|
<div
|
||||||
|
v-if="videoList.length > visibleCount"
|
||||||
|
class="carousel-arrow arrow-left"
|
||||||
|
@click="prevSlide"
|
||||||
|
>
|
||||||
|
<LeftOutlined />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 视频轨道 -->
|
||||||
|
<div
|
||||||
|
class="carousel-track"
|
||||||
|
:style="{ transform: `translateX(-${scrollOffset}px)` }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="item in videoList"
|
||||||
|
:key="item.vdid"
|
||||||
|
class="video-item"
|
||||||
|
:style="{ width: itemWidth + 'px' }"
|
||||||
|
>
|
||||||
|
<div class="video-thumb">
|
||||||
|
<img :src="item.imgPath" :alt="item.vdnm || item.flnm" />
|
||||||
|
<div class="play-overlay" @click="playVideo(item)">
|
||||||
|
<i class="iconfont icon-play" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-tooltip :title="item.vdnm || item.flnm">
|
||||||
|
<div class="video-name">{{ item.vdnm || item.flnm }}</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右箭头 -->
|
||||||
|
<div
|
||||||
|
v-if="videoList.length > visibleCount"
|
||||||
|
class="carousel-arrow arrow-right"
|
||||||
|
@click="nextSlide"
|
||||||
|
>
|
||||||
|
<RightOutlined />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 单个视频播放弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="playModalVisible"
|
||||||
|
title="视频播放"
|
||||||
|
width="900px"
|
||||||
|
:footer="null"
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<VideoPlayer
|
||||||
|
:list="videoList"
|
||||||
|
:activeFid="activeFid"
|
||||||
|
:activeMedia="activeMedia"
|
||||||
|
:loading="false"
|
||||||
|
:page="1"
|
||||||
|
:pageSize="20"
|
||||||
|
:pageSizeOptions="['20', '50', '100']"
|
||||||
|
:total="videoList.length"
|
||||||
|
@select="handleSelectMedia"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 更多视频弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="moreModalVisible"
|
||||||
|
title="视频监控"
|
||||||
|
width="90%"
|
||||||
|
:footer="null"
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<VideoInfo :is-active="moreModalVisible" />
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
inject,
|
||||||
|
watch,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
type Ref
|
||||||
|
} from 'vue';
|
||||||
|
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
|
||||||
|
import VideoPlayer from '@/modules/yunXingGaoJIng/Aisbdbyx/components/VideoPlayer.vue';
|
||||||
|
import VideoInfo from '@/components/MapModal/components/videoInfo.vue';
|
||||||
|
import { videoSurveillance, realTimeVideo } from '@/api/video';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
defineOptions({
|
||||||
|
name: 'DZShuiDianZhanSPJK'
|
||||||
|
});
|
||||||
|
|
||||||
|
interface VideoItem {
|
||||||
|
vdid: string;
|
||||||
|
vdnm?: string;
|
||||||
|
flnm?: string;
|
||||||
|
imgPath: string;
|
||||||
|
url?: string;
|
||||||
|
/** VideoPlayer 兼容字段 */
|
||||||
|
fid: string;
|
||||||
|
type: string;
|
||||||
|
flpth: string;
|
||||||
|
tm: string;
|
||||||
|
ennm: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DianZhanStation {
|
||||||
|
wbsCode: string;
|
||||||
|
stcd: string;
|
||||||
|
ennm: string;
|
||||||
|
lgtd: number;
|
||||||
|
lttd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dianZhanStation = inject<Ref<DianZhanStation | null>>('dianZhanStation');
|
||||||
|
|
||||||
|
// 视频列表
|
||||||
|
const videoList = ref<VideoItem[]>([]);
|
||||||
|
|
||||||
|
// 走马灯相关
|
||||||
|
const carouselRef = ref<HTMLElement | null>(null);
|
||||||
|
const visibleCount = ref(4);
|
||||||
|
const itemWidth = ref(160);
|
||||||
|
const scrollOffset = ref(0);
|
||||||
|
const currentSlide = ref(0);
|
||||||
|
|
||||||
|
// 单个播放弹窗
|
||||||
|
const playModalVisible = ref(false);
|
||||||
|
const activeFid = ref('');
|
||||||
|
const activeMedia = ref<{ type: string; src: string }>({ type: '', src: '' });
|
||||||
|
|
||||||
|
// 更多视频弹窗
|
||||||
|
const moreModalVisible = ref(false);
|
||||||
|
|
||||||
|
// 计算可见数量
|
||||||
|
const updateVisibleCount = () => {
|
||||||
|
if (carouselRef.value) {
|
||||||
|
const width = carouselRef.value.offsetWidth;
|
||||||
|
visibleCount.value = Math.max(
|
||||||
|
1,
|
||||||
|
Math.floor((width - 60) / (itemWidth.value + 8))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 走马灯翻页
|
||||||
|
const nextSlide = () => {
|
||||||
|
const maxSlide = Math.max(0, videoList.value.length - visibleCount.value);
|
||||||
|
if (currentSlide.value < maxSlide) {
|
||||||
|
currentSlide.value++;
|
||||||
|
scrollOffset.value = currentSlide.value * (itemWidth.value + 8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevSlide = () => {
|
||||||
|
if (currentSlide.value > 0) {
|
||||||
|
currentSlide.value--;
|
||||||
|
scrollOffset.value = currentSlide.value * (itemWidth.value + 8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 播放视频
|
||||||
|
const playVideo = (item: VideoItem) => {
|
||||||
|
setActiveVideo(item);
|
||||||
|
playModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置当前播放视频
|
||||||
|
const setActiveVideo = (item: VideoItem) => {
|
||||||
|
activeFid.value = item.fid;
|
||||||
|
activeMedia.value = {
|
||||||
|
type: 'video',
|
||||||
|
src: item.flpth || item.url || ''
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// VideoPlayer 选中回调
|
||||||
|
const handleSelectMedia = (item: any) => {
|
||||||
|
activeFid.value = item.fid;
|
||||||
|
activeMedia.value = {
|
||||||
|
type: 'video',
|
||||||
|
src: item.flpth || item.src || ''
|
||||||
|
};
|
||||||
|
// debugger
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开更多视频弹窗
|
||||||
|
const openMoreModal = () => {
|
||||||
|
moreModalVisible.value = true;
|
||||||
|
// 同步 stcd 到 modelStore,VideoInfo 组件依赖 modelStore.params.stcd
|
||||||
|
if (dianZhanStation?.value?.stcd) {
|
||||||
|
modelStore.params.stcd = dianZhanStation.value.stcd;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取视频数据
|
||||||
|
const fetchVideoData = async (stcd: string) => {
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: ['VD_FBFM', 'VD_EQS']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 第一步:查询电站下有哪些监控视频站点
|
||||||
|
const surveillanceRes = await videoSurveillance(params);
|
||||||
|
const stcdList = surveillanceRes?.data?.data || [];
|
||||||
|
if (!stcdList.length) {
|
||||||
|
videoList.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集所有 stcd
|
||||||
|
const stcds = stcdList.map((item: any) => item.stcd).filter(Boolean);
|
||||||
|
if (!stcds.length) {
|
||||||
|
videoList.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二步:查询这些站点的实时视频数据
|
||||||
|
const timeRange = [
|
||||||
|
dayjs().subtract(7, 'day').format('YYYY-MM-DD 00:00:00'),
|
||||||
|
dayjs().format('YYYY-MM-DD 23:59:59')
|
||||||
|
];
|
||||||
|
const params1 = {
|
||||||
|
take: 100,
|
||||||
|
skip: 0,
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcds
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: timeRange[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: timeRange[1]
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
// field: 'tm',
|
||||||
|
// operator: 'gte',
|
||||||
|
// dataType: 'date',
|
||||||
|
// value: '2024-05-01 00:00:00'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// field: 'tm',
|
||||||
|
// operator: 'lte',
|
||||||
|
// dataType: 'date',
|
||||||
|
// value: '2026-05-31 23:59:59'
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
dir: 'desc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const videoRes = await realTimeVideo(params1);
|
||||||
|
const videoData = videoRes?.data?.data || videoRes?.data || [];
|
||||||
|
// debugger
|
||||||
|
const videoItems: VideoItem[] = (
|
||||||
|
Array.isArray(videoData) ? videoData : []
|
||||||
|
).map((item: any) => ({
|
||||||
|
vdid: item.vdid,
|
||||||
|
vdnm: item.vdnm,
|
||||||
|
flnm: item.flnm,
|
||||||
|
imgPath: item.imgPath || '',
|
||||||
|
url: item.url || item.flv || item.hls,
|
||||||
|
// VideoPlayer 兼容字段
|
||||||
|
fid: item.vdid,
|
||||||
|
type: 'vdsp',
|
||||||
|
flpth: item.url || item.flv || item.hls || '',
|
||||||
|
tm: item.tm ? dayjs(item.tm).format('YYYY-MM-DD HH:mm:ss') : '-',
|
||||||
|
ennm: item.ennm || ''
|
||||||
|
}));
|
||||||
|
|
||||||
|
videoList.value = videoItems;
|
||||||
|
// debugger
|
||||||
|
currentSlide.value = 0;
|
||||||
|
scrollOffset.value = 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取视频数据失败:', error);
|
||||||
|
videoList.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听电站变化
|
||||||
|
watch(
|
||||||
|
() => dianZhanStation?.value,
|
||||||
|
newVal => {
|
||||||
|
if (newVal?.stcd) {
|
||||||
|
fetchVideoData(newVal.stcd);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateVisibleCount();
|
||||||
|
window.addEventListener('resize', updateVisibleCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', updateVisibleCount);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dz-video-monitor {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: rgba(255, 255, 255);
|
||||||
|
|
||||||
|
.video-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 3px 12px 3px 0px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #e5edf3;
|
||||||
|
|
||||||
|
.video-title {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #2f6b98;
|
||||||
|
font-weight: 500;
|
||||||
|
display: inline-block;
|
||||||
|
border-left:2px solid #2f6b98;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #005293;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-carousel-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 28px;
|
||||||
|
|
||||||
|
.video-empty {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-track {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.video-item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.video-thumb {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 110px;
|
||||||
|
background: #1a1a1a;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
i {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-name {
|
||||||
|
padding: 4px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 5;
|
||||||
|
width: 24px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arrow-left {
|
||||||
|
left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arrow-right {
|
||||||
|
right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
299
frontend/src/views/dianZhanZhuanTi/components/EcologicalFlow.vue
Normal file
299
frontend/src/views/dianZhanZhuanTi/components/EcologicalFlow.vue
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="生态流量数据分析">
|
||||||
|
<div class="ecological-flow">
|
||||||
|
<!-- 底部图表区 -->
|
||||||
|
<div class="bottom-section">
|
||||||
|
<a-spin v-show="hasData" :spinning="isChartLoading" tip="加载中...">
|
||||||
|
<div class="chart-wrapper" ref="chartRef"></div>
|
||||||
|
</a-spin>
|
||||||
|
<a-empty
|
||||||
|
v-show="!hasData && !isChartLoading"
|
||||||
|
description="暂无数据"
|
||||||
|
class="empty-wrapper h-full flex items-center justify-center"
|
||||||
|
style="flex-direction: column"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch, onMounted, onUnmounted, nextTick, inject } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { getMonitorDataWaterTempVerticalDayList } from '@/api/mapModal';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'EcologicalFlow'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注入父组件提供的电站上下文
|
||||||
|
const dianZhanStation = inject<any>('dianZhanStation', ref(null));
|
||||||
|
|
||||||
|
const stcd = computed(() => dianZhanStation.value?.stcd || '');
|
||||||
|
|
||||||
|
const hasLoaded = ref<boolean>(false);
|
||||||
|
const isChartLoading = ref<boolean>(false);
|
||||||
|
const hasData = ref<boolean>(true);
|
||||||
|
|
||||||
|
const chartRef = ref<HTMLDivElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 固定获取最近7天的时间范围
|
||||||
|
const getDateRange = (): { startTime: string; endTime: string } => {
|
||||||
|
const endDate = dayjs();
|
||||||
|
const startDate = dayjs().subtract(7, 'day');
|
||||||
|
return {
|
||||||
|
startTime: startDate.format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
endTime: endDate.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
const updateChart = (data: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const xAxisData = sorted.map(item => dayjs(item.dt).format('YYYY-MM-DD'));
|
||||||
|
const qiData = sorted.map(item => item.qi);
|
||||||
|
const qecData = sorted.map(item => item.qec);
|
||||||
|
const qecLimitData = sorted.map(item => item.qecLimit);
|
||||||
|
const beforeQecData = sorted.map(item => item.beforeQec);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
const dataIndex = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[dataIndex]?.dt
|
||||||
|
? dayjs(sorted[dataIndex].dt).format('YYYY-MM-DD')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
const formatRules: Record<
|
||||||
|
string,
|
||||||
|
{ format: (v: number) => string; unit: string }
|
||||||
|
> = {
|
||||||
|
入库流量: { format: v => String(Math.round(v)), unit: '(m³/s)' },
|
||||||
|
生态流量: { format: v => v.toFixed(1), unit: '(m³/s)' },
|
||||||
|
生态流量限值: { format: v => v.toFixed(1), unit: '(m³/s)' },
|
||||||
|
};
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
if (param.value == null) return;
|
||||||
|
const rule = Object.entries(formatRules).find(([key]) =>
|
||||||
|
param.seriesName.includes(key)
|
||||||
|
);
|
||||||
|
const displayValue = rule
|
||||||
|
? rule[1].format(Number(param.value))
|
||||||
|
: param.value;
|
||||||
|
const unit = rule ? rule[1].unit : '';
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;">
|
||||||
|
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
|
||||||
|
<span style="flex:1;font-size:14px;text-align:left;margin-right:6px;">${param.seriesName}: </span>
|
||||||
|
<span style="font-size:14px;min-width:60px;text-align:right;"><strong>${displayValue}</strong> ${unit}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
top: -5,
|
||||||
|
data: ['入库流量', '生态流量', '生态流量限值'],
|
||||||
|
textStyle: { fontSize: 12, color: '#000' }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 40,
|
||||||
|
right: 20,
|
||||||
|
top: 50,
|
||||||
|
bottom: 30
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { fontSize: 12 },
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '流量(m³/s)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'left',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9,
|
||||||
|
boundaryGap: ['10%', 0]
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '入库流量',
|
||||||
|
type: 'line',
|
||||||
|
data: qiData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#4B79AB', width: 2 },
|
||||||
|
itemStyle: { color: '#4B79AB' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '生态流量',
|
||||||
|
type: 'line',
|
||||||
|
data: qecData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#00A050', width: 2 },
|
||||||
|
itemStyle: { color: '#00A050' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '生态流量限值',
|
||||||
|
type: 'line',
|
||||||
|
data: qecLimitData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#F7A737', width: 2 },
|
||||||
|
itemStyle: { color: '#F7A737' }
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const destroyChart = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 请求图表数据(固定最近7天日数据)
|
||||||
|
const fetchChartData = async () => {
|
||||||
|
if (!stcd.value) return;
|
||||||
|
isChartLoading.value = true;
|
||||||
|
try {
|
||||||
|
const { startTime, endTime } = getDateRange();
|
||||||
|
|
||||||
|
const res = await getMonitorDataWaterTempVerticalDayList({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{ field: 'stcd', operator: 'eq', dataType: 'string', value: stcd.value },
|
||||||
|
// { field: 'tm', operator: 'gte', dataType: 'date', value: '2021-01-01' },
|
||||||
|
// { field: 'tm', operator: 'lte', dataType: 'date', value: '2026-07-01' }
|
||||||
|
{ field: 'tm', operator: 'gte', dataType: 'date', value: startTime },
|
||||||
|
{ field: 'tm', operator: 'lte', dataType: 'date', value: endTime }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawData = res?.data?.data || [];
|
||||||
|
hasData.value = rawData.length > 0;
|
||||||
|
if (hasData.value) {
|
||||||
|
updateChart(rawData);
|
||||||
|
}
|
||||||
|
hasLoaded.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取图表数据失败:', error);
|
||||||
|
} finally {
|
||||||
|
isChartLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听电站变化,重新加载图表
|
||||||
|
watch(
|
||||||
|
() => dianZhanStation.value?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd) {
|
||||||
|
hasLoaded.value = false;
|
||||||
|
destroyChart();
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
fetchChartData();
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
destroyChart();
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ecological-flow {
|
||||||
|
.bottom-section {
|
||||||
|
height: 213px;
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
:deep(.ant-spin-nested-loading) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
:deep(.ant-spin-container) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,381 @@
|
|||||||
|
<template>
|
||||||
|
<div class="detail-container">
|
||||||
|
<BasicSearch
|
||||||
|
ref="formRef"
|
||||||
|
:searchList="searchList"
|
||||||
|
:initialValues="initialValues"
|
||||||
|
@finish="handleSearch"
|
||||||
|
@reset="handleReset"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<a-button @click="handleExport" :loading="exportLoading">
|
||||||
|
<template #icon><DownloadOutlined /></template>
|
||||||
|
导出
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicSearch>
|
||||||
|
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="columns"
|
||||||
|
:listUrl="getPowerStationReleaseDetail"
|
||||||
|
:searchParams="tableSearchParams"
|
||||||
|
:transformData="customTransform"
|
||||||
|
:defaultPageSize="20"
|
||||||
|
:scrollY="400"
|
||||||
|
>
|
||||||
|
<template #ennm="{ record }">
|
||||||
|
<a @click="handleStationClick(record)">{{ record.ennm }}</a>
|
||||||
|
</template>
|
||||||
|
<template #fid="{ record }">
|
||||||
|
<a
|
||||||
|
v-if="record.fid && record.fid.length > 1"
|
||||||
|
@click="handlePreview(record.fid)"
|
||||||
|
>查看</a
|
||||||
|
>
|
||||||
|
<span v-else class="no-data-link">查看</span>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
|
||||||
|
<!-- 视频预览弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="previewVisible"
|
||||||
|
title="视频资料"
|
||||||
|
:footer="null"
|
||||||
|
width="800px"
|
||||||
|
>
|
||||||
|
<template v-if="previewData && previewData.length">
|
||||||
|
<a-empty v-if="!previewVisible" description="暂无视频数据" />
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, reactive, onMounted, nextTick } from 'vue';
|
||||||
|
import { DownloadOutlined } from '@ant-design/icons-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { getPowerStationReleaseDetail } from '@/api/dianZhanZhuanTi';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
|
const props = defineProps<{
|
||||||
|
year: string;
|
||||||
|
stcdSelectOptions: { label: string; value: string }[];
|
||||||
|
stcd: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const currentBaseId = (window as any).__lyConfigs?.baseId ?? '02';
|
||||||
|
|
||||||
|
// ==================== 工作开展情况字典 ====================
|
||||||
|
const runStateList: any = ref([]);
|
||||||
|
|
||||||
|
const getdict = async () => {
|
||||||
|
const res = await getDictItemsByCode({ dictCode: 'EngFl' });
|
||||||
|
runStateList.value = (res.data || []).map((item: any) => ({
|
||||||
|
label: item.dictName,
|
||||||
|
value: item.itemCode
|
||||||
|
}));
|
||||||
|
runStateList.value.unshift({
|
||||||
|
label: '全部',
|
||||||
|
value: 'all'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getdict();
|
||||||
|
nextTick(() => {
|
||||||
|
tableRef.value?.getList(buildFilter.value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 状态 ====================
|
||||||
|
const formRef = ref();
|
||||||
|
const tableRef = ref();
|
||||||
|
const exportLoading = ref(false);
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
years: props.year,
|
||||||
|
stcd: props.stcd,
|
||||||
|
runState: 'all',
|
||||||
|
search: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 视频预览 ====================
|
||||||
|
const previewVisible = ref(false);
|
||||||
|
const previewData = ref<string[]>([]);
|
||||||
|
|
||||||
|
function handlePreview(fid: string) {
|
||||||
|
if (fid && fid.length > 1) {
|
||||||
|
previewData.value = [fid];
|
||||||
|
previewVisible.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 搜索列表 ====================
|
||||||
|
const searchList = computed(() => [
|
||||||
|
{
|
||||||
|
type: 'DataPicker',
|
||||||
|
name: 'years',
|
||||||
|
label: '年份',
|
||||||
|
picker: 'year',
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: false,
|
||||||
|
format: 'YYYY',
|
||||||
|
valueFormat: 'YYYY'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Select',
|
||||||
|
name: 'stcd',
|
||||||
|
label: '所属电站',
|
||||||
|
options: props.stcdSelectOptions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Select',
|
||||||
|
name: 'runState',
|
||||||
|
label: '工作开展情况',
|
||||||
|
options: runStateList.value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Input',
|
||||||
|
name: 'search',
|
||||||
|
label: '',
|
||||||
|
placeholder: '请输入电站名称,实际放鱼种类'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const initialValues = computed(() => ({
|
||||||
|
years: props.year,
|
||||||
|
stcd: props.stcd,
|
||||||
|
runState: 'all',
|
||||||
|
search: ''
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ==================== 查询/重置 ====================
|
||||||
|
function handleSearch(values: any) {
|
||||||
|
state.years = values.years ? dayjs(values.years).format('YYYY') : '';
|
||||||
|
state.stcd = values.stcd ?? '';
|
||||||
|
state.runState = values.runState ?? 'all';
|
||||||
|
state.search = values.search ?? '';
|
||||||
|
tableRef.value?.getList(buildFilter.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
state.years = props.year;
|
||||||
|
state.stcd = props.stcd;
|
||||||
|
state.runState = 'all';
|
||||||
|
state.search = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 筛选条件 ====================
|
||||||
|
const buildFilter = computed(() => {
|
||||||
|
const { search, runState, years, stcd } = state;
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
currentBaseId && currentBaseId !== 'all'
|
||||||
|
? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: currentBaseId
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
runState != 'all'
|
||||||
|
? {
|
||||||
|
field: 'runState',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: runState
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
years
|
||||||
|
? {
|
||||||
|
field: 'startTime',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dayjs(years).startOf('year').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
years
|
||||||
|
? {
|
||||||
|
field: 'endTime',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dayjs(years).endOf('year').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
stcd
|
||||||
|
? { field: 'stcd', operator: 'eq', dataType: 'string', value: stcd }
|
||||||
|
: null,
|
||||||
|
search
|
||||||
|
? {
|
||||||
|
field: 'ennm',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: search
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
field: 'bldsttCcode',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: [1, 2]
|
||||||
|
}
|
||||||
|
].filter(Boolean)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableSearchParams = computed(() => ({
|
||||||
|
filter: buildFilter.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
const customTransform = (res: any) => ({
|
||||||
|
records: res?.data?.data || res?.data?.items || [],
|
||||||
|
total: res?.data?.total || 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 表格列 ====================
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '电站名称',
|
||||||
|
dataIndex: 'ennm',
|
||||||
|
key: 'ennm',
|
||||||
|
width: 150,
|
||||||
|
ellipsis: true,
|
||||||
|
merge: true,
|
||||||
|
slots: { customRender: 'ennm' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实际放鱼时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
key: 'tm',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实际放鱼种类',
|
||||||
|
dataIndex: 'ftpName',
|
||||||
|
key: 'ftpName',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实际放鱼数量(万尾)',
|
||||||
|
dataIndex: 'fcntjc',
|
||||||
|
key: 'fcntjc',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '放鱼平均规格(cm)',
|
||||||
|
dataIndex: 'fsz',
|
||||||
|
key: 'fsz',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '计划放鱼时段',
|
||||||
|
dataIndex: 'timeSlot',
|
||||||
|
key: 'timeSlot',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true,
|
||||||
|
merge: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '计划放鱼种类',
|
||||||
|
dataIndex: 'planFtpName',
|
||||||
|
key: 'planFtpName',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true,
|
||||||
|
merge: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '计划放鱼数量(万尾)',
|
||||||
|
dataIndex: 'fcntjh',
|
||||||
|
key: 'fcntjh',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true,
|
||||||
|
merge: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '放流地点',
|
||||||
|
dataIndex: 'rplace',
|
||||||
|
key: 'rplace',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所在河流',
|
||||||
|
dataIndex: 'river',
|
||||||
|
key: 'river',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '工作开展情况',
|
||||||
|
dataIndex: 'runStateName',
|
||||||
|
key: 'runStateName',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
merge: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '视频资料',
|
||||||
|
dataIndex: 'fid',
|
||||||
|
key: 'fid',
|
||||||
|
width: 100,
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'right',
|
||||||
|
slots: { customRender: 'fid' }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==================== 电站名称点击 → 二级弹窗 ====================
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
function handleStationClick(record: any) {
|
||||||
|
modelStore.params = {
|
||||||
|
...modelStore.params,
|
||||||
|
sttp: 'ENG',
|
||||||
|
stcd: record.stcd,
|
||||||
|
sttpCode: record.sttpCode || 'ENG'
|
||||||
|
};
|
||||||
|
modelStore.title = record.ennm ? `${record.ennm} ` : '';
|
||||||
|
modelStore.modalVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 导出 ====================
|
||||||
|
function handleExport() {
|
||||||
|
if (exportLoading.value) return;
|
||||||
|
exportLoading.value = true;
|
||||||
|
if (tableRef.value && typeof tableRef.value.exportTable === 'function') {
|
||||||
|
tableRef.value
|
||||||
|
.exportTable({
|
||||||
|
fileName: `电站放流详情_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
exportLoading.value = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.info('暂无导出数据');
|
||||||
|
exportLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.detail-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-link {
|
||||||
|
color: #d9d9d9;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,280 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="水电站介绍">
|
||||||
|
<div class="station-intro">
|
||||||
|
<a-empty v-if="mediaData.length === 0" description="暂无介绍信息" />
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
@mouseenter="isHovering = true"
|
||||||
|
@mouseleave="isHovering = false"
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
@click="openDetail"
|
||||||
|
>
|
||||||
|
<img :src="item.image" :alt="item.title" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pagination-dots-fixed">
|
||||||
|
<span
|
||||||
|
v-for="(dot, index) in mediaData"
|
||||||
|
:key="index"
|
||||||
|
class="dot"
|
||||||
|
:class="{ active: getCurrentRealIndex() === index }"
|
||||||
|
@click="goToSlide(index)"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="description-text">
|
||||||
|
{{ currentDescription }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情弹框 -->
|
||||||
|
<a-modal v-model:open="modalVisible" title="设施详情" width="80%" :footer="null">
|
||||||
|
<div v-if="detailDataSource.length" class="detail-container">
|
||||||
|
<ArtsDetail :dataSource="detailDataSource" :index="0" />
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { inject, watch, type Ref, ref, computed, onMounted, onUnmounted } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue';
|
||||||
|
import { overviewvmsstbprptGetKendoList } from '@/api/home';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ShuiDianZhanJieShao'
|
||||||
|
});
|
||||||
|
|
||||||
|
interface MediaItem {
|
||||||
|
image: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DianZhanStation {
|
||||||
|
wbsCode: string;
|
||||||
|
stcd: string;
|
||||||
|
ennm: string;
|
||||||
|
lgtd: number;
|
||||||
|
lttd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
|
const dianZhanStation = inject<Ref<DianZhanStation | null>>('dianZhanStation');
|
||||||
|
|
||||||
|
// 弹框
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
|
||||||
|
// 媒体数据
|
||||||
|
const mediaData = ref<MediaItem[]>([]);
|
||||||
|
const renderMediaData = ref<MediaItem[]>([]);
|
||||||
|
const currentIndex = ref(1);
|
||||||
|
const isHovering = ref(false);
|
||||||
|
const isTransitioning = ref(false);
|
||||||
|
let timer: any = null;
|
||||||
|
|
||||||
|
const detailDataSource = computed(() => {
|
||||||
|
return mediaData.value.map(item => ({
|
||||||
|
url: item.image || '',
|
||||||
|
description: item.description || '',
|
||||||
|
title: item.title || ''
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentDescription = computed(() => {
|
||||||
|
const realIndex = getCurrentRealIndex();
|
||||||
|
return mediaData.value[realIndex]?.description || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCurrentRealIndex = () => {
|
||||||
|
const len = mediaData.value.length;
|
||||||
|
if (len === 0) return 0;
|
||||||
|
let realIndex = currentIndex.value - 1;
|
||||||
|
if (realIndex < 0) realIndex = len - 1;
|
||||||
|
if (realIndex >= len) realIndex = 0;
|
||||||
|
return realIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initRenderData = () => {
|
||||||
|
const len = mediaData.value.length;
|
||||||
|
if (len === 0) {
|
||||||
|
renderMediaData.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (len === 1) {
|
||||||
|
renderMediaData.value = [...mediaData.value];
|
||||||
|
currentIndex.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renderMediaData.value = [
|
||||||
|
mediaData.value[len - 1],
|
||||||
|
...mediaData.value,
|
||||||
|
mediaData.value[0]
|
||||||
|
];
|
||||||
|
currentIndex.value = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToSlide = (targetIndex: number) => {
|
||||||
|
if (isTransitioning.value || mediaData.value.length <= 1) return;
|
||||||
|
currentIndex.value = targetIndex + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startAutoPlay = () => {
|
||||||
|
if (timer) clearInterval(timer);
|
||||||
|
if (mediaData.value.length <= 1) return;
|
||||||
|
timer = setInterval(() => {
|
||||||
|
if (!isHovering.value && !isTransitioning.value) {
|
||||||
|
currentIndex.value++;
|
||||||
|
}
|
||||||
|
}, 4000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openDetail = () => {
|
||||||
|
if (mediaData.value.length) {
|
||||||
|
modalVisible.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchStationInfo = async (stcd: string) => {
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{ field: 'stcd', operator: 'eq', dataType: 'string', value: stcd },
|
||||||
|
{ field: 'sttpCode', operator: 'eq', dataType: 'string', value: 'ENG' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'siteStepSort', dir: 'asc' }],
|
||||||
|
select: ['inffile', 'introduce', 'ennm', 'logo', 'precis']
|
||||||
|
};
|
||||||
|
const res = await overviewvmsstbprptGetKendoList(params);
|
||||||
|
const dataList = res?.data?.data;
|
||||||
|
if (dataList && Array.isArray(dataList)) {
|
||||||
|
mediaData.value = dataList
|
||||||
|
.filter((item: any) => item.inffile || item.introduce)
|
||||||
|
.map((item: any) => ({
|
||||||
|
image: item.inffile ? `${baseUrl}?${item.inffile}&view=jpg` : '',
|
||||||
|
title: item.ennm || '',
|
||||||
|
description: item.introduce || ''
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
mediaData.value = [];
|
||||||
|
}
|
||||||
|
initRenderData();
|
||||||
|
startAutoPlay();
|
||||||
|
} catch {
|
||||||
|
mediaData.value = [];
|
||||||
|
initRenderData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => dianZhanStation?.value,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal?.stcd) {
|
||||||
|
fetchStationInfo(newVal.stcd);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer) clearInterval(timer);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 228px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.carousel-track {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: transform 0.5s ease-in-out;
|
||||||
|
|
||||||
|
&.no-transition {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
min-width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-top: 12px;
|
||||||
|
min-height: 44px;
|
||||||
|
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,464 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem
|
||||||
|
title="珍稀植物园运行数据"
|
||||||
|
:moreSelect="moreSelect"
|
||||||
|
:select="select"
|
||||||
|
@update-values="handlePanelChange"
|
||||||
|
>
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<a-spin v-if="cardLoading" class="spin-center" />
|
||||||
|
|
||||||
|
<!-- 空数据 -->
|
||||||
|
<a-empty
|
||||||
|
v-else-if="!hasCardData"
|
||||||
|
description="暂无数据"
|
||||||
|
class="empty-center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 左右卡片 -->
|
||||||
|
<div v-else class="card-wrapper" @click="openModal">
|
||||||
|
<div class="card-item card-left">
|
||||||
|
<div class="card-icon">
|
||||||
|
<i class="icon iconfont icon-chengliu"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="card-name"
|
||||||
|
>{{ showData.botanyName || ''
|
||||||
|
}}{{ showData.unitName ? `(${showData.unitName})` : '' }}</span
|
||||||
|
>
|
||||||
|
<h5 class="card-count">{{ showData.tamarixChinensis ?? '-' }}</h5>
|
||||||
|
<img :src="arrowUpSvg" alt="" class="arrow-icon" />
|
||||||
|
<div class="card-rate">{{ showData.tamarixChinensisLive ?? '-' }}</div>
|
||||||
|
<p class="card-label">存活率</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-item card-right">
|
||||||
|
<div class="card-icon"> <i class="icon iconfont icon-zhenxizhiwuyuan"></i></div>
|
||||||
|
|
||||||
|
<span class="card-name">其他</span>
|
||||||
|
<h5 class="card-count">{{ showData.other || 0 }}</h5>
|
||||||
|
<img :src="arrowUpSvg" alt="" class="arrow-icon" />
|
||||||
|
<div class="card-rate">{{ showData.otherLive ?? '-' }}</div>
|
||||||
|
<p class="card-label">存活率</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
title="珍稀植物园运行数据列表"
|
||||||
|
:footer="null"
|
||||||
|
width="80%"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
:centered="true"
|
||||||
|
class="detail-modal"
|
||||||
|
>
|
||||||
|
<div class="search-form">
|
||||||
|
<a-space size="middle">
|
||||||
|
<div>年份:</div>
|
||||||
|
<a-select
|
||||||
|
v-model:value="modalYear"
|
||||||
|
style="width: 100px"
|
||||||
|
:options="select.options"
|
||||||
|
@change="onModalSearch"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" @click="onModalSearch">查询</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:list-url="getBotanicalGardenDetailData"
|
||||||
|
:columns="detailColumns"
|
||||||
|
:scrollY="400"
|
||||||
|
:search-params="modalSearchParams"
|
||||||
|
:transform-data="detailTransform"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch, inject, nextTick } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import {
|
||||||
|
getBotanicalGardenList,
|
||||||
|
getBotanicalGardenStatistic,
|
||||||
|
getBotanicalGardenDetailData
|
||||||
|
} from '@/api/ZXZWYYunXingShuJu';
|
||||||
|
import arrowUpSvg from './arrowUpLg.svg';
|
||||||
|
|
||||||
|
defineOptions({ name: 'ZXZWYYunXingShuJu' });
|
||||||
|
|
||||||
|
// ==================== 注入电站上下文 ====================
|
||||||
|
const dianZhanStation = inject<any>('dianZhanStation', ref(null));
|
||||||
|
const apiId = computed(() => dianZhanStation.value?.stcd || '');
|
||||||
|
|
||||||
|
// ==================== SidePanelItem 选择器配置 ====================
|
||||||
|
const isInitializing = ref(false);
|
||||||
|
|
||||||
|
/** moreSelect: 植物园(标题栏树形选择器) */
|
||||||
|
const moreSelect = ref({
|
||||||
|
show: true,
|
||||||
|
value: undefined as string | undefined,
|
||||||
|
options: [] as any[]
|
||||||
|
});
|
||||||
|
|
||||||
|
/** select: 年份(标题栏普通选择器) */
|
||||||
|
const select = ref({
|
||||||
|
show: true,
|
||||||
|
value: undefined as string | undefined,
|
||||||
|
options: [] as any[],
|
||||||
|
width: '120px',
|
||||||
|
minWidth: '120px'
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 各地植物园的年份列表:{ [stcd]: year[] } */
|
||||||
|
const yearMap = ref<Record<string, string[]>>({});
|
||||||
|
|
||||||
|
// ==================== 卡片数据 ====================
|
||||||
|
const cardLoading = ref(false);
|
||||||
|
const hasCardData = ref(false);
|
||||||
|
const showData = ref({
|
||||||
|
botanyName: '',
|
||||||
|
unitName: '',
|
||||||
|
tamarixChinensis: '-' as string | number,
|
||||||
|
tamarixChinensisLive: '-' as string | number,
|
||||||
|
other: 0 as string | number,
|
||||||
|
otherLive: '-' as string | number
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== Modal ====================
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
const modalYear = ref<string>();
|
||||||
|
const tableRef = ref<any>(null);
|
||||||
|
|
||||||
|
/** 当前选中的植物园 stcd(从 moreSelect.options 中找) */
|
||||||
|
const currentGardenStcd = computed(() => {
|
||||||
|
const val = moreSelect.value.value;
|
||||||
|
const options = moreSelect.value.options || [];
|
||||||
|
const found = (options as any[]).find((o: any) => o.value === val);
|
||||||
|
return found?.stcd || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const modalSearchParams = computed(() => ({
|
||||||
|
group: [],
|
||||||
|
groupResultFlat: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ==================== 详情表格列定义 ====================
|
||||||
|
const detailColumns = [
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '植物园名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
width: '150px',
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{ key: 'tm', title: '年份', dataIndex: 'tm', width: '80px' },
|
||||||
|
{ key: 'tetp', title: '保护种类', dataIndex: 'tetp', width: '120px' },
|
||||||
|
{ key: 'unitName', title: '单位', dataIndex: 'unitName', width: '80px' },
|
||||||
|
{
|
||||||
|
key: 'tecntDisplay',
|
||||||
|
title: '实际种植/种植要求',
|
||||||
|
dataIndex: 'tecntDisplay',
|
||||||
|
width: '140px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'surnumDisplay',
|
||||||
|
title: '存活数量/移栽规模',
|
||||||
|
dataIndex: 'surnumDisplay',
|
||||||
|
width: '140px'
|
||||||
|
},
|
||||||
|
{ key: 'rate', title: '存活率(%)', dataIndex: 'rate', width: '100px' }
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 预处理详情数据:拼接种植/存活字段 */
|
||||||
|
const detailTransform = (res: any) => {
|
||||||
|
const records = (res?.data?.data || []).map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
tecntDisplay: `${item.tecnt ?? '-'}/${item.plantask ?? '-'}`,
|
||||||
|
surnumDisplay: `${item.surnum ?? '-'}/${item.transplant ?? '-'}`
|
||||||
|
}));
|
||||||
|
return { records, total: res?.data?.total || 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== API 请求 ====================
|
||||||
|
/** 获取植物园下拉列表 */
|
||||||
|
const fetchGardenList = async () => {
|
||||||
|
if (!apiId.value) return;
|
||||||
|
isInitializing.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: apiId.value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
dir: 'des',
|
||||||
|
field: 'year'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'des',
|
||||||
|
field: 'stcd'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'des',
|
||||||
|
field: 'name'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
groupResultFlat: 'true'
|
||||||
|
};
|
||||||
|
const res = await getBotanicalGardenList(params);
|
||||||
|
|
||||||
|
const list = res?.data?.data?.[0]?.data || [];
|
||||||
|
if (list.length === 0) return;
|
||||||
|
|
||||||
|
const gardenMap = new Map<string, { stcd: string; stnm: string }>();
|
||||||
|
const yearMapTemp: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
list.forEach((item: any) => {
|
||||||
|
if (!gardenMap.has(item.stcd)) {
|
||||||
|
gardenMap.set(item.stcd, { stcd: item.stcd, stnm: item.name });
|
||||||
|
}
|
||||||
|
if (!yearMapTemp[item.stcd]) {
|
||||||
|
yearMapTemp[item.stcd] = [];
|
||||||
|
}
|
||||||
|
yearMapTemp[item.stcd].push(item.year);
|
||||||
|
});
|
||||||
|
|
||||||
|
yearMap.value = yearMapTemp;
|
||||||
|
|
||||||
|
// moreSelect 使用树形选择器,field-names 中 label 映射为 'title'
|
||||||
|
const gardens = Array.from(gardenMap.values());
|
||||||
|
moreSelect.value.options = gardens.map(g => ({
|
||||||
|
title: g.stnm,
|
||||||
|
value: g.stnm,
|
||||||
|
stcd: g.stcd
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
const firstGarden = gardens[0];
|
||||||
|
moreSelect.value.value = firstGarden?.stnm;
|
||||||
|
updateYearOptions(firstGarden?.stcd);
|
||||||
|
} catch {
|
||||||
|
message.error('获取植物园列表失败');
|
||||||
|
} finally {
|
||||||
|
isInitializing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 根据植物园 stcd 更新年份选项 */
|
||||||
|
const updateYearOptions = (stcd: string) => {
|
||||||
|
const years = yearMap.value[stcd] || [];
|
||||||
|
select.value.options = years.map(y => ({ label: y, value: y }));
|
||||||
|
select.value.value = years[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 获取卡片统计数据 */
|
||||||
|
const fetchStatistic = async () => {
|
||||||
|
const year = select.value.value;
|
||||||
|
const stcd = currentGardenStcd.value;
|
||||||
|
if (!year || !stcd) return;
|
||||||
|
|
||||||
|
cardLoading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: year
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res = await getBotanicalGardenStatistic(params);
|
||||||
|
|
||||||
|
const list = res?.data?.data?.[0]?.data || [];
|
||||||
|
hasCardData.value = list.length > 0;
|
||||||
|
|
||||||
|
if (list.length > 0) {
|
||||||
|
const result = list.reduce((acc: any, cur: any) => {
|
||||||
|
if (cur.name === '其他') {
|
||||||
|
acc.other = cur.tecnt;
|
||||||
|
acc.otherLive = cur.rate;
|
||||||
|
} else {
|
||||||
|
acc.botanyName = cur.name;
|
||||||
|
acc.tamarixChinensis = cur.tecnt;
|
||||||
|
acc.tamarixChinensisLive = cur.rate;
|
||||||
|
acc.unitName = cur.unitName;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
showData.value = {
|
||||||
|
botanyName: result.botanyName || '',
|
||||||
|
unitName: result.unitName || '',
|
||||||
|
tamarixChinensis: result.tamarixChinensis ?? '-',
|
||||||
|
tamarixChinensisLive: result.tamarixChinensisLive ?? '-',
|
||||||
|
other: result.other ?? 0,
|
||||||
|
otherLive: result.otherLive ?? '-'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
hasCardData.value = false;
|
||||||
|
} finally {
|
||||||
|
cardLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 事件处理 ====================
|
||||||
|
/** SidePanelItem 控件变化回调 */
|
||||||
|
const handlePanelChange = async (data: any) => {
|
||||||
|
if (isInitializing.value) return;
|
||||||
|
|
||||||
|
// 植物园变化 → 更新年份选项
|
||||||
|
if (
|
||||||
|
data.moreSelect !== undefined &&
|
||||||
|
data.moreSelect !== moreSelect.value.value
|
||||||
|
) {
|
||||||
|
moreSelect.value.value = data.moreSelect;
|
||||||
|
const stcd = (moreSelect.value.options as any[]).find(
|
||||||
|
(o: any) => o.value === data.moreSelect
|
||||||
|
)?.stcd;
|
||||||
|
updateYearOptions(stcd);
|
||||||
|
return; // select.value.value 被 updateYearOptions 修改后,会再次触发 handlePanelChange → 走下面的分支
|
||||||
|
}
|
||||||
|
|
||||||
|
// 年份变化 → 重新拉数据
|
||||||
|
if (data.select !== undefined && data.select !== select.value.value) {
|
||||||
|
select.value.value = data.select;
|
||||||
|
fetchStatistic();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 打开详情弹窗 */
|
||||||
|
const openModal = () => {
|
||||||
|
modalYear.value = select.value.value;
|
||||||
|
modalVisible.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
onModalSearch();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 弹窗内搜索 */
|
||||||
|
const onModalSearch = () => {
|
||||||
|
if (!modalYear.value || !currentGardenStcd.value) return;
|
||||||
|
tableRef.value?.getList({
|
||||||
|
endTime: [{ field: 'tm', operator: 'eq', value: modalYear.value }],
|
||||||
|
stcd: currentGardenStcd.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 监听 ====================
|
||||||
|
watch(apiId, newId => {
|
||||||
|
if (newId) {
|
||||||
|
fetchGardenList();
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.spin-center,
|
||||||
|
.empty-center {
|
||||||
|
min-height: 197px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 197px;
|
||||||
|
padding: 0px 5px;
|
||||||
|
}
|
||||||
|
.card-icon{
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #2f6b98;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.card-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.card-item:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-left .icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-right .icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-name {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #3E3E3E;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-count {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 32px;
|
||||||
|
color: #2f6b98;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
width: 61px;
|
||||||
|
height: 64px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-rate {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #2f6b98;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 62%;
|
||||||
|
transform: translate(-50%, 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #3E3E3E;
|
||||||
|
text-indent:0 !important;
|
||||||
|
/* margin: 0; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-modal .search-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,292 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
:title="mFilter?.ctitle || '详情'"
|
||||||
|
width="80%"
|
||||||
|
:footer="null"
|
||||||
|
:bodyStyle="{ height: '700px' }"
|
||||||
|
centered
|
||||||
|
destroyOnClose
|
||||||
|
@cancel="handleModalCancel"
|
||||||
|
>
|
||||||
|
<!-- Tab 切换 -->
|
||||||
|
<div class="modal-tabs" v-if="mFilter?.proTable?.length">
|
||||||
|
<div
|
||||||
|
v-for="item in mFilter.proTable"
|
||||||
|
:key="item.key"
|
||||||
|
class="modal-tabs__item"
|
||||||
|
:class="{ 'modal-tabs__item--active': activeTab?.key === item.key }"
|
||||||
|
@click="switchTab(item)"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选表单 -->
|
||||||
|
<BasicSearch
|
||||||
|
v-if="activeTab && searchList.length"
|
||||||
|
:key="activeTab.key"
|
||||||
|
:searchList="searchList"
|
||||||
|
@finish="handleTableSearch"
|
||||||
|
@reset="handleTableReset"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<BasicTable
|
||||||
|
v-if="activeTab"
|
||||||
|
:key="activeTab.key"
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:listUrl="tableListUrl"
|
||||||
|
:searchParams="tableSearchParams"
|
||||||
|
:transformData="customTransform"
|
||||||
|
:defaultPageSize="20"
|
||||||
|
:scrollY="400"
|
||||||
|
>
|
||||||
|
<template #fid="{ record }">
|
||||||
|
<a v-if="record.fid" @click="handleAttachmentPreview(record.fid)">查看</a>
|
||||||
|
<span v-else class="no-data-link">查看</span>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 附件预览弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="attachmentModalVisible"
|
||||||
|
title="附件预览"
|
||||||
|
width="80vw"
|
||||||
|
:footer="null"
|
||||||
|
destroyOnClose
|
||||||
|
:bodyStyle="{ height: '70vh' }"
|
||||||
|
>
|
||||||
|
<AttachmentPreview :fid="currentAttachmentFid" />
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, nextTick } from 'vue';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import AttachmentPreview from '@/components/MapModal/components/AIYXSB/AttachmentPreview.vue';
|
||||||
|
import { getFishDic } from '@/api/dianZhanZhuanTi';
|
||||||
|
|
||||||
|
defineOptions({ name: 'ZenZhiZhanYunXingDetailModal' });
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
selectedStcd: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 不需要默认排序的 tab key
|
||||||
|
const igKeys = ['1-2-1'];
|
||||||
|
|
||||||
|
// ==================== 弹窗状态 ====================
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
const activeTab = ref<any>(null);
|
||||||
|
const mFilter = ref<any>(null);
|
||||||
|
const searchForms = ref<any>({});
|
||||||
|
const tableRef = ref();
|
||||||
|
const fishDic = ref<Array<{ value: string; key: string }>>([]);
|
||||||
|
|
||||||
|
// ==================== 附件预览状态 ====================
|
||||||
|
const attachmentModalVisible = ref(false);
|
||||||
|
const currentAttachmentFid = ref('');
|
||||||
|
|
||||||
|
// ==================== 搜索列表(从 activeTab.filter 构建) ====================
|
||||||
|
const searchList = computed(() => {
|
||||||
|
if (!activeTab.value?.filter) return [];
|
||||||
|
return activeTab.value.filter.map((f: any) => {
|
||||||
|
const item: any = {
|
||||||
|
type: f.type === 'select' ? 'Select' : 'Input',
|
||||||
|
name: f.name,
|
||||||
|
label: f.label,
|
||||||
|
placeholder: f.placeholder || `请输入${f.label}`
|
||||||
|
};
|
||||||
|
if (f.type === 'select' && f.name === 'ftp') {
|
||||||
|
item.options = fishDic.value.map((d: any) => ({
|
||||||
|
label: d.key,
|
||||||
|
value: d.value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// ==================== 表格列(从 proliferationColumns 转换) ====================
|
||||||
|
const buildColumns = (cols: any[]): any[] => {
|
||||||
|
return cols
|
||||||
|
.filter((col: any) => col.visible !== false)
|
||||||
|
.map((col: any) => {
|
||||||
|
const base: any = {
|
||||||
|
title: col.title,
|
||||||
|
dataIndex: col.dataIndex,
|
||||||
|
key: col.key || col.dataIndex,
|
||||||
|
width: col.width || 150,
|
||||||
|
ellipsis: col.ellipsis ?? true
|
||||||
|
};
|
||||||
|
if (col.dataIndex === 'fid' || col.key === 'fid') {
|
||||||
|
base.slots = { customRender: 'fid' };
|
||||||
|
}
|
||||||
|
if (col.children?.length) {
|
||||||
|
base.children = buildColumns(col.children);
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableColumns = computed(() => {
|
||||||
|
if (!activeTab.value?.columns) return [];
|
||||||
|
return buildColumns(activeTab.value.columns);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 表格请求参数 ====================
|
||||||
|
const tableSearchParams = computed(() => {
|
||||||
|
const filters: any[] = [];
|
||||||
|
|
||||||
|
if (props.selectedStcd) {
|
||||||
|
filters.push({
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: props.selectedStcd
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchForms.value) {
|
||||||
|
Object.keys(searchForms.value).forEach(key => {
|
||||||
|
if (searchForms.value[key] !== undefined && searchForms.value[key] !== '') {
|
||||||
|
filters.push({
|
||||||
|
field: key,
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: searchForms.value[key]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: any = {
|
||||||
|
filter: { logic: 'and', filters }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!igKeys.includes(activeTab.value?.key)) {
|
||||||
|
result.sort = [{ field: 'tm', dir: 'desc' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== listUrl ====================
|
||||||
|
const tableListUrl = computed(() => {
|
||||||
|
return (params: any) =>
|
||||||
|
request({
|
||||||
|
url: activeTab.value?.url || '',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const customTransform = (res: any) => ({
|
||||||
|
records: res?.data?.data || res?.data?.items || [],
|
||||||
|
total: res?.data?.total || 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 事件 ====================
|
||||||
|
const switchTab = (item: any) => {
|
||||||
|
searchForms.value = {};
|
||||||
|
activeTab.value = item;
|
||||||
|
nextTick(() => {
|
||||||
|
tableRef.value?.getList(tableSearchParams.value.filter);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTableSearch = (values: any) => {
|
||||||
|
searchForms.value = { ...values };
|
||||||
|
tableRef.value?.getList(tableSearchParams.value.filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTableReset = () => {
|
||||||
|
searchForms.value = {};
|
||||||
|
tableRef.value?.getList(tableSearchParams.value.filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalCancel = () => {
|
||||||
|
modalVisible.value = false;
|
||||||
|
activeTab.value = null;
|
||||||
|
mFilter.value = null;
|
||||||
|
searchForms.value = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAttachmentPreview = (fid: string) => {
|
||||||
|
if (fid) {
|
||||||
|
currentAttachmentFid.value = fid;
|
||||||
|
attachmentModalVisible.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== API ====================
|
||||||
|
const fetchFishDic = async () => {
|
||||||
|
if (fishDic.value.length > 0) return; // 已加载则跳过
|
||||||
|
try {
|
||||||
|
const res = await getFishDic({
|
||||||
|
filter: { logic: 'and', filters: [] },
|
||||||
|
select: ['id', 'name']
|
||||||
|
});
|
||||||
|
// debugger
|
||||||
|
const data = res?.data?.data || [];
|
||||||
|
fishDic.value = data.map((item: any) => ({
|
||||||
|
value: item.id,
|
||||||
|
key: item.name
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取鱼类字典失败', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 暴露方法 ====================
|
||||||
|
const open = (val: any) => {
|
||||||
|
searchForms.value = {};
|
||||||
|
mFilter.value = val;
|
||||||
|
activeTab.value = val.proTable?.[0] || null;
|
||||||
|
modalVisible.value = true;
|
||||||
|
fetchFishDic();
|
||||||
|
nextTick(() => {
|
||||||
|
tableRef.value?.getList(tableSearchParams.value.filter);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-tabs__item {
|
||||||
|
padding: 8px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-tabs__item:hover {
|
||||||
|
color: #2f6b98;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-tabs__item--active {
|
||||||
|
color: #2f6b98;
|
||||||
|
border-bottom-color: #2f6b98;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-link {
|
||||||
|
color: #d9d9d9;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,600 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem
|
||||||
|
title="增殖站运行及评估"
|
||||||
|
:select="stationSelect"
|
||||||
|
@update-values="onPanelChange"
|
||||||
|
>
|
||||||
|
<a-spin v-if="loading" class="spin-center" />
|
||||||
|
<a-empty v-else-if="!hasData" description="暂无数据" class="empty-center" />
|
||||||
|
|
||||||
|
<div v-else class="step-card-wrapper">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in stepList"
|
||||||
|
:key="item.key"
|
||||||
|
class="step-card"
|
||||||
|
:class="{
|
||||||
|
'step-card--active': activeIndex === index,
|
||||||
|
'step-card--last': index === 4
|
||||||
|
}"
|
||||||
|
:style="{ zIndex: activeIndex === index ? 999 : 989 - index * 10 }"
|
||||||
|
@click="index === 4 ? undefined : setActiveIndex(index)"
|
||||||
|
>
|
||||||
|
<!-- 激活状态:展开子步骤 -->
|
||||||
|
<template v-if="activeIndex === index">
|
||||||
|
<div class="step-card__header">{{ item.title }}</div>
|
||||||
|
<div class="step-card__icon">
|
||||||
|
<img :src="fishStation2Svg" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="step-card__arrow">
|
||||||
|
<img :src="arrowDownLine2Svg" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="step-card__list">
|
||||||
|
<div
|
||||||
|
v-for="val in item.textList"
|
||||||
|
:key="val.key"
|
||||||
|
class="step-card__list-item"
|
||||||
|
:class="{
|
||||||
|
'is-disabled': val.disable,
|
||||||
|
'is-active': val.curNewSurFace
|
||||||
|
}"
|
||||||
|
:style="val.disable ? {} : { cursor: 'pointer' }"
|
||||||
|
@click="!val.disable && val.proTable?.length && detailModalRef?.open(val)"
|
||||||
|
>
|
||||||
|
{{ val.ctitle }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="step-card__step">STEP{{ index + 1 }}</p>
|
||||||
|
</template>
|
||||||
|
<!-- 折叠状态 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="step-card__fold-label">{{ item.title }}</div>
|
||||||
|
<p class="step-card__step">STEP{{ index + 1 }}</p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情弹窗(子组件) -->
|
||||||
|
<ZenZhiZhanYunXingDetailModal
|
||||||
|
ref="detailModalRef"
|
||||||
|
:selected-stcd="selectedStcd"
|
||||||
|
/>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch, inject } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import ZenZhiZhanYunXingDetailModal from './ZenZhiZhanYunXingDetailModal.vue';
|
||||||
|
import fishStation2Svg from '@/assets/icons/fishStation2.svg';
|
||||||
|
import arrowDownLine2Svg from '@/assets/icons/arrowDownLine2.svg';
|
||||||
|
import {
|
||||||
|
getHatcheryOperationData
|
||||||
|
} from '@/api/dianZhanZhuanTi';
|
||||||
|
import { msstbprptGetKendoList } from '@/api/zzfl';
|
||||||
|
import {
|
||||||
|
qinYuXinXi,
|
||||||
|
qinYuSiWanJiLu,
|
||||||
|
qinYuPeiYuFangShi,
|
||||||
|
qinYuPeiYuShuiWen,
|
||||||
|
qinYuPeiYuShuiZi,
|
||||||
|
qinYuPeiYuTouWei,
|
||||||
|
taoTaiQinYu,
|
||||||
|
yuLeiRenGo,
|
||||||
|
yuLeiShouJin,
|
||||||
|
yuleifuhua,
|
||||||
|
miaoZhongPeiYu,
|
||||||
|
miaoZhongJiLu,
|
||||||
|
yuBingFangZhi
|
||||||
|
} from '@/components/MapModal/components/NormalOperationData/proliferationColumns';
|
||||||
|
defineOptions({ name: 'ZenZhiZhanYunXingPG' });
|
||||||
|
|
||||||
|
// ==================== 注入电站上下文 ====================
|
||||||
|
const dianZhanStation = inject<any>('dianZhanStation', ref(null));
|
||||||
|
const apiId = computed(() => dianZhanStation.value?.stcd || '');
|
||||||
|
|
||||||
|
// ==================== 5个步骤的初始结构(含 proTable 数据) ====================
|
||||||
|
const INIT_STEP_LIST = [
|
||||||
|
{
|
||||||
|
title: '亲鱼选配与培育',
|
||||||
|
key: '0',
|
||||||
|
textList: [
|
||||||
|
{
|
||||||
|
ctitle: '亲鱼选配',
|
||||||
|
key: '1-1',
|
||||||
|
disable: false,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: [
|
||||||
|
{
|
||||||
|
title: '亲鱼信息',
|
||||||
|
key: '1-1-1',
|
||||||
|
url: '/fb/bsmfr/GetKendoListCust',
|
||||||
|
columns: qinYuXinXi,
|
||||||
|
filter: [
|
||||||
|
{ type: 'select', name: 'ftp', label: '鱼类', options: [] },
|
||||||
|
{ type: 'input', name: 'signnum', label: '标记编号' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '亲鱼死亡记录',
|
||||||
|
key: '1-1-2',
|
||||||
|
url: '/fb/bsdeathr/GetKendoListCust',
|
||||||
|
columns: qinYuSiWanJiLu,
|
||||||
|
filter: [
|
||||||
|
{ type: 'select', name: 'ftp', label: '鱼类', options: [] },
|
||||||
|
{ type: 'input', name: 'fpnum', label: '鱼池编号' },
|
||||||
|
{ type: 'input', name: 'signnum', label: '标记编号' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ctitle: '亲鱼培育',
|
||||||
|
key: '1-2',
|
||||||
|
disable: false,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: [
|
||||||
|
{
|
||||||
|
title: '亲鱼培育方式',
|
||||||
|
key: '1-2-1',
|
||||||
|
url: '/fb/bsctmob/GetKendoListCust',
|
||||||
|
columns: qinYuPeiYuFangShi,
|
||||||
|
filter: [
|
||||||
|
{ type: 'select', name: 'ftp', label: '鱼类', options: [] },
|
||||||
|
{ type: 'input', name: 'fpnum', label: '鱼池编号' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '亲鱼培育水温观测记录',
|
||||||
|
key: '1-2-2',
|
||||||
|
url: '/fb/fbbwtr/GetKendoListCust',
|
||||||
|
columns: qinYuPeiYuShuiWen,
|
||||||
|
filter: [
|
||||||
|
{ type: 'input', name: 'fpnum', label: '鱼池编号' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '亲鱼培育水质监测记录',
|
||||||
|
key: '1-2-3',
|
||||||
|
url: '/fb/fbcwqr/GetKendoListCust',
|
||||||
|
columns: qinYuPeiYuShuiZi,
|
||||||
|
filter: [
|
||||||
|
{ type: 'input', name: 'fpnum', label: '鱼池编号' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '亲鱼培育投喂记录',
|
||||||
|
key: '1-2-4',
|
||||||
|
url: '/fb/bsrfir/GetKendoListCust',
|
||||||
|
columns: qinYuPeiYuTouWei,
|
||||||
|
filter: [
|
||||||
|
{ type: 'input', name: 'fpnum', label: '鱼池编号' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ctitle: '亲鱼补充与淘汰',
|
||||||
|
key: '1-3',
|
||||||
|
disable: false,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: [
|
||||||
|
{
|
||||||
|
title: '淘汰亲鱼信息',
|
||||||
|
key: '1-3-1',
|
||||||
|
url: '/fb/outfishr/GetKendoListCust',
|
||||||
|
columns: taoTaiQinYu,
|
||||||
|
filter: [
|
||||||
|
{ type: 'select', name: 'ftp', label: '鱼类', options: [] },
|
||||||
|
{ type: 'input', name: 'signno', label: '标记编号' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '人工繁殖',
|
||||||
|
key: '1',
|
||||||
|
textList: [
|
||||||
|
{
|
||||||
|
ctitle: '亲本选择',
|
||||||
|
key: '2-1',
|
||||||
|
disable: true,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ctitle: '人工催产',
|
||||||
|
key: '2-2',
|
||||||
|
disable: false,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: [
|
||||||
|
{
|
||||||
|
title: '鱼类人工催产记录',
|
||||||
|
key: '2-2-1',
|
||||||
|
url: '/fb/fishartinlr/GetKendoListCust',
|
||||||
|
columns: yuLeiRenGo,
|
||||||
|
filter: [
|
||||||
|
{ type: 'select', name: 'ftp', label: '鱼类', options: [] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ctitle: '人工授精',
|
||||||
|
key: '2-3',
|
||||||
|
disable: true,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ctitle: '孵化',
|
||||||
|
key: '2-4',
|
||||||
|
disable: false,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: [
|
||||||
|
{
|
||||||
|
title: '鱼类受精卵孵化过程记录',
|
||||||
|
key: '2-4-1',
|
||||||
|
url: '/fb/fishhatchrecr/GetKendoListCust',
|
||||||
|
columns: yuLeiShouJin,
|
||||||
|
filter: [
|
||||||
|
{ type: 'select', name: 'ftp', label: '鱼类', options: [] },
|
||||||
|
{ type: 'input', name: 'fishsrc', label: '亲鱼来源' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '鱼类孵化过程巡查记录',
|
||||||
|
key: '2-4-2',
|
||||||
|
url: '/fb/fishhatchpror/GetKendoListCust',
|
||||||
|
columns: yuleifuhua,
|
||||||
|
filter: [
|
||||||
|
{ type: 'input', name: 'devno', label: '孵化设施编号' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '鱼苗和鱼种的培育',
|
||||||
|
key: '2',
|
||||||
|
textList: [
|
||||||
|
{
|
||||||
|
ctitle: '鱼苗和鱼种的培育',
|
||||||
|
key: '3-1',
|
||||||
|
disable: false,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: [
|
||||||
|
{
|
||||||
|
title: '苗种培育水环境监测记录',
|
||||||
|
key: '3-1-1',
|
||||||
|
url: '/fb/scwemr/GetKendoListCust',
|
||||||
|
columns: miaoZhongPeiYu,
|
||||||
|
filter: [
|
||||||
|
{ type: 'input', name: 'fpnum', label: '鱼池编号' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '苗种培育记录',
|
||||||
|
key: '3-1-2',
|
||||||
|
url: '/fb/fishbreedr/GetKendoListCust',
|
||||||
|
columns: miaoZhongJiLu,
|
||||||
|
filter: [
|
||||||
|
{ type: 'input', name: 'farmno', label: '鱼池编号' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '鱼病防治',
|
||||||
|
key: '3',
|
||||||
|
textList: [
|
||||||
|
{
|
||||||
|
ctitle: '鱼病防治',
|
||||||
|
key: '4-1',
|
||||||
|
disable: false,
|
||||||
|
curNewSurFace: '',
|
||||||
|
proTable: [
|
||||||
|
{
|
||||||
|
title: '鱼病防治记录',
|
||||||
|
key: '4-1-1',
|
||||||
|
url: '/fb/fishdpacr/GetKendoListCust',
|
||||||
|
columns: yuBingFangZhi,
|
||||||
|
filter: [
|
||||||
|
{ type: 'input', name: 'fpnum', label: '鱼池编号' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '放流',
|
||||||
|
key: '4',
|
||||||
|
textList: []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==================== 状态 ====================
|
||||||
|
const loading = ref(false);
|
||||||
|
const hasData = ref(false);
|
||||||
|
const stepList = ref<any[]>([]);
|
||||||
|
const activeIndex = ref(0);
|
||||||
|
const stationOptions = ref<
|
||||||
|
Array<{ value: string; label: string; stcd: string }>
|
||||||
|
>([]);
|
||||||
|
const selectedStation = ref<string>('');
|
||||||
|
const selectedStcd = ref<string>('');
|
||||||
|
|
||||||
|
// ==================== 弹窗子组件 ref ====================
|
||||||
|
const detailModalRef = ref();
|
||||||
|
|
||||||
|
// ==================== SidePanelItem 配置 ====================
|
||||||
|
const stationSelect = computed(() => ({
|
||||||
|
show: true,
|
||||||
|
value: selectedStation.value,
|
||||||
|
options: stationOptions.value,
|
||||||
|
width: '160px'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ==================== API方法 ====================
|
||||||
|
/** 拉取增殖站下拉列表 */
|
||||||
|
const fetchStationList = async (stcd: string) => {
|
||||||
|
if (!stcd) return;
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'FB'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: [
|
||||||
|
'id',
|
||||||
|
'stcd',
|
||||||
|
'rstcd',
|
||||||
|
'ennm',
|
||||||
|
'stnm',
|
||||||
|
'sttpCode',
|
||||||
|
'lgtd',
|
||||||
|
'lttd',
|
||||||
|
'orderIndex'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await msstbprptGetKendoList(params);
|
||||||
|
const data = res?.data?.data || [];
|
||||||
|
if (data.length > 0) {
|
||||||
|
const list = data.map((item: any) => ({
|
||||||
|
label: item.stnm,
|
||||||
|
value: item.stcd,
|
||||||
|
stcd: item.stcd
|
||||||
|
}));
|
||||||
|
stationOptions.value = list.map((item: any) => ({
|
||||||
|
value: item.value,
|
||||||
|
label: item.label,
|
||||||
|
stcd: item.stcd
|
||||||
|
}));
|
||||||
|
selectedStation.value = list[0].value;
|
||||||
|
selectedStcd.value = list[0].stcd;
|
||||||
|
} else {
|
||||||
|
stationOptions.value = [];
|
||||||
|
selectedStation.value = '';
|
||||||
|
selectedStcd.value = '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取增殖站列表失败', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 拉取各步骤运行状态 */
|
||||||
|
const fetchStepStatus = async (stcd: string) => {
|
||||||
|
if (!stcd) return;
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const stepRes = await getHatcheryOperationData(params);
|
||||||
|
|
||||||
|
const stepData = stepRes?.data?.data || [];
|
||||||
|
|
||||||
|
if (stepData.length > 0) {
|
||||||
|
hasData.value = true;
|
||||||
|
const newStepList = JSON.parse(JSON.stringify(INIT_STEP_LIST));
|
||||||
|
|
||||||
|
// 找最新步骤
|
||||||
|
stepData.forEach((item: any) => {
|
||||||
|
const { firstGrade, newest } = item;
|
||||||
|
if (newest) {
|
||||||
|
const idx = newStepList.findIndex((s: any) => s.title === firstGrade);
|
||||||
|
if (idx !== -1) activeIndex.value = idx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 填充各子步骤状态
|
||||||
|
newStepList.forEach((step: any) => {
|
||||||
|
step.textList.forEach((val: any) => {
|
||||||
|
let curNewSurFace = '';
|
||||||
|
stepData.forEach((dItem: any) => {
|
||||||
|
const { key, newSurface, value } = dItem;
|
||||||
|
if (val.ctitle === key) {
|
||||||
|
val.disable = value === '2';
|
||||||
|
curNewSurFace = newSurface;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
val.curNewSurFace = curNewSurFace;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
stepList.value = newStepList;
|
||||||
|
} else {
|
||||||
|
hasData.value = false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取增殖站运行状态失败', e);
|
||||||
|
hasData.value = false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 事件处理 ====================
|
||||||
|
const onPanelChange = (data: any) => {
|
||||||
|
if (data.select) {
|
||||||
|
const option = stationOptions.value.find(
|
||||||
|
(item: any) => item.value === data.select
|
||||||
|
);
|
||||||
|
if (option) {
|
||||||
|
selectedStation.value = option.value;
|
||||||
|
selectedStcd.value = option.stcd;
|
||||||
|
fetchStepStatus(option.stcd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setActiveIndex = (index: number) => {
|
||||||
|
activeIndex.value = index;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 监听 ====================
|
||||||
|
watch(apiId, newId => {
|
||||||
|
if (newId) {
|
||||||
|
fetchStationList(newId);
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
// 拉完下拉列表后自动拉步骤数据
|
||||||
|
watch(selectedStcd, newStcd => {
|
||||||
|
if (newStcd) {
|
||||||
|
fetchStepStatus(newStcd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.spin-center,
|
||||||
|
.empty-center {
|
||||||
|
height: 197px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card-wrapper {
|
||||||
|
display: flex;
|
||||||
|
height: 197px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0px 10px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card {
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #ffffff40;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
height: 197px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 3px 3px 3px 3px #2f6b9840;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card--active {
|
||||||
|
background: #fff;
|
||||||
|
border: 0px solid #00000000;
|
||||||
|
display: block;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 3px 3px 3px 3px #2f6b9840;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card__header {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000000d9;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
width: 126px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card__icon {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 3px 0px;
|
||||||
|
img {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.step-card__arrow {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
img {
|
||||||
|
width: 20px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card__list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card__list-item {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000000d9;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card__list-item.is-disabled {
|
||||||
|
color: #bbb;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card__step {
|
||||||
|
position: absolute;
|
||||||
|
left: -25px;
|
||||||
|
bottom: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card__fold-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000000d9;
|
||||||
|
writing-mode: vertical-lr;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
32
frontend/src/views/dianZhanZhuanTi/components/arrowUpLg.svg
Normal file
32
frontend/src/views/dianZhanZhuanTi/components/arrowUpLg.svg
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="61px" height="64px" viewBox="0 0 61 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>arrowUpLg</title>
|
||||||
|
<defs>
|
||||||
|
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
|
||||||
|
<stop stop-color="#2F6B98" stop-opacity="0.45" offset="0%"></stop>
|
||||||
|
<stop stop-color="#2F6B98" stop-opacity="0.25" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient x1="49.7637128%" y1="0.23628715%" x2="49.7637128%" y2="99.7637128%" id="linearGradient-2">
|
||||||
|
<stop stop-color="#2F6B98" stop-opacity="0.25" offset="0%"></stop>
|
||||||
|
<stop stop-color="#2F6B98" stop-opacity="0.15" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient x1="50%" y1="-2.48949813e-15%" x2="50%" y2="100%" id="linearGradient-3">
|
||||||
|
<stop stop-color="#2F6B98" stop-opacity="0.150950612" offset="0%"></stop>
|
||||||
|
<stop stop-color="#2F6B98" stop-opacity="0" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="5-生态调查" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="5.2-生态调查-陆生生态调查" transform="translate(-1560.000000, -662.000000)">
|
||||||
|
<g id="编组备份-4" transform="translate(1476.000000, 119.000000)">
|
||||||
|
<g id="编组-12" transform="translate(16.000000, 412.000000)">
|
||||||
|
<g id="ArrowUpLg" transform="translate(68.000000, 131.000000)">
|
||||||
|
<path d="M30.5,0.838886685 L59.7879803,34.4317691 L41.8358974,34.4317691 L41.8358974,63.4487179 L19.1641026,63.4487179 L19.1641026,34.4317691 L1.21201968,34.4317691 L30.5,0.838886685 Z" id="路径" stroke="url(#linearGradient-1)" stroke-width="1.1025641"></path>
|
||||||
|
<polygon id="路径备份-4" fill="url(#linearGradient-2)" points="56.9333333 33.1756019 30.5 2.75268817 4.06666667 33.1756019 20.3333333 33.1756019 20.3333333 61.9354839 40.6666667 61.9354839 40.6666667 33.1756019"></polygon>
|
||||||
|
<path d="M40.6666667,61.2473118 L40.6666667,61.9354839 L20.3333333,61.9354839 L20.3333333,61.2473118 L40.6666667,61.2473118 Z M40.6666667,59.8709677 L40.6666667,60.5591398 L20.3333333,60.5591398 L20.3333333,59.8709677 L40.6666667,59.8709677 Z M40.6666667,58.4946237 L40.6666667,59.1827957 L20.3333333,59.1827957 L20.3333333,58.4946237 L40.6666667,58.4946237 Z M40.6666667,57.1182796 L40.6666667,57.8064516 L20.3333333,57.8064516 L20.3333333,57.1182796 L40.6666667,57.1182796 Z M40.6666667,55.7419355 L40.6666667,56.4301075 L20.3333333,56.4301075 L20.3333333,55.7419355 L40.6666667,55.7419355 Z M40.6666667,54.3655914 L40.6666667,55.0537634 L20.3333333,55.0537634 L20.3333333,54.3655914 L40.6666667,54.3655914 Z M40.6666667,52.9892473 L40.6666667,53.6774194 L20.3333333,53.6774194 L20.3333333,52.9892473 L40.6666667,52.9892473 Z M40.6666667,51.6129032 L40.6666667,52.3010753 L20.3333333,52.3010753 L20.3333333,51.6129032 L40.6666667,51.6129032 Z M40.6666667,50.2365591 L40.6666667,50.9247312 L20.3333333,50.9247312 L20.3333333,50.2365591 L40.6666667,50.2365591 Z M40.6666667,48.8602151 L40.6666667,49.5483871 L20.3333333,49.5483871 L20.3333333,48.8602151 L40.6666667,48.8602151 Z M40.6666667,47.483871 L40.6666667,48.172043 L20.3333333,48.172043 L20.3333333,47.483871 L40.6666667,47.483871 Z M40.6666667,46.1075269 L40.6666667,46.7956989 L20.3333333,46.7956989 L20.3333333,46.1075269 L40.6666667,46.1075269 Z M40.6666667,44.7311828 L40.6666667,45.4193548 L20.3333333,45.4193548 L20.3333333,44.7311828 L40.6666667,44.7311828 Z M40.6666667,43.3548387 L40.6666667,44.0430108 L20.3333333,44.0430108 L20.3333333,43.3548387 L40.6666667,43.3548387 Z M40.6666667,41.9784946 L40.6666667,42.6666667 L20.3333333,42.6666667 L20.3333333,41.9784946 L40.6666667,41.9784946 Z M40.6666667,40.6021505 L40.6666667,41.2903226 L20.3333333,41.2903226 L20.3333333,40.6021505 L40.6666667,40.6021505 Z M40.6666667,39.2258065 L40.6666667,39.9139785 L20.3333333,39.9139785 L20.3333333,39.2258065 L40.6666667,39.2258065 Z M40.6666667,37.8494624 L40.6666667,38.5376344 L20.3333333,38.5376344 L20.3333333,37.8494624 L40.6666667,37.8494624 Z M40.6666667,36.4731183 L40.6666667,37.1612903 L20.3333333,37.1612903 L20.3333333,36.4731183 L40.6666667,36.4731183 Z M40.6666667,35.0967742 L40.6666667,35.7849462 L20.3333333,35.7849462 L20.3333333,35.0967742 L40.6666667,35.0967742 Z" id="形状结合" fill="url(#linearGradient-3)"></path>
|
||||||
|
<polyline id="路径-5备份" transform="translate(30.500000, 13.075269) scale(-1, 1) rotate(-90.000000) translate(-30.500000, -13.075269) " points="20.8655914 -3.86917563 40.1344086 13.0752688 20.8655914 30.0197133"></polyline>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
@ -0,0 +1,445 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem
|
||||||
|
title="电站放流统计"
|
||||||
|
:datetimePicker="datePickerConfig"
|
||||||
|
@update-values="onDateChange"
|
||||||
|
>
|
||||||
|
<div class="release-statistics">
|
||||||
|
<a-spin
|
||||||
|
v-show="hasData"
|
||||||
|
:spinning="loading"
|
||||||
|
tip="加载中..."
|
||||||
|
class="chart-container"
|
||||||
|
>
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
</a-spin>
|
||||||
|
<a-empty
|
||||||
|
v-show="!hasData && !loading"
|
||||||
|
description="暂无数据"
|
||||||
|
class="empty-wrapper"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
|
||||||
|
<!-- 弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="showModal"
|
||||||
|
title="电站放流详情"
|
||||||
|
width="80vw"
|
||||||
|
:footer="null"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
>
|
||||||
|
<PowerStationReleasetaStisticsDetail
|
||||||
|
v-if="showModal"
|
||||||
|
:year="modalPageData.year"
|
||||||
|
:stcdSelectOptions="modalPageData.stcdSelectOptions"
|
||||||
|
:stcd="modalPageData.stcd"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
watch,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
nextTick,
|
||||||
|
inject
|
||||||
|
} from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import PowerStationReleasetaStisticsDetail from './PowerStationReleasetaStisticsDetail.vue';
|
||||||
|
import {
|
||||||
|
getPowerStationReleaseYear,
|
||||||
|
getPowerStationReleaseData
|
||||||
|
} from '@/api/dianZhanZhuanTi';
|
||||||
|
|
||||||
|
defineOptions({ name: 'PowerStationReleasetaStistics' });
|
||||||
|
|
||||||
|
// ==================== 注入电站上下文 ====================
|
||||||
|
const dianZhanStation = inject<any>('dianZhanStation', ref(null));
|
||||||
|
const stcd = computed(() => dianZhanStation.value?.stcd || '');
|
||||||
|
|
||||||
|
// ==================== 常量 ====================
|
||||||
|
const currentBaseId = (window as any).__lyConfigs?.baseId ?? '02';
|
||||||
|
const UNIT = '万尾';
|
||||||
|
const COLORS = ['#6ca4f7', '#91cc75'];
|
||||||
|
|
||||||
|
// ==================== 状态 ====================
|
||||||
|
const loading = ref(false);
|
||||||
|
const hasData = ref(false);
|
||||||
|
const searchDate = ref<dayjs.Dayjs | null>(null);
|
||||||
|
const resData = ref<any[]>([]);
|
||||||
|
const chartRef = ref<HTMLDivElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// ==================== 弹窗状态 ====================
|
||||||
|
const showModal = ref(false);
|
||||||
|
const modalPageData = ref({
|
||||||
|
year: '',
|
||||||
|
stcdSelectOptions: [] as { label: string; value: string }[],
|
||||||
|
stcd: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 年份选择器配置 ====================
|
||||||
|
const datePickerConfig = computed(() => ({
|
||||||
|
show: true,
|
||||||
|
picker: 'year' as const,
|
||||||
|
value: searchDate.value ? searchDate.value.format('YYYY') : undefined
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ==================== 数据处理 ====================
|
||||||
|
interface ChartData {
|
||||||
|
stationList: string[];
|
||||||
|
planList: number[];
|
||||||
|
actualList: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartData = computed<ChartData>(() => {
|
||||||
|
if (!resData.value?.length)
|
||||||
|
return { stationList: [], planList: [], actualList: [] };
|
||||||
|
return {
|
||||||
|
stationList: resData.value.map((item: any) => item.ennm),
|
||||||
|
planList: resData.value.map((item: any) =>
|
||||||
|
parseFloat(((item.fcntjh ?? 0) / 10000).toFixed(4))
|
||||||
|
),
|
||||||
|
actualList: resData.value.map((item: any) =>
|
||||||
|
parseFloat(((item.fcntjc ?? 0) / 10000).toFixed(4))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 电站下拉选项(弹窗用) ====================
|
||||||
|
const stcdSelectOptions = computed(() => {
|
||||||
|
if (!resData.value?.length) return [];
|
||||||
|
const options = resData.value.map((item: any) => ({
|
||||||
|
label: item.ennm,
|
||||||
|
value: item.stcd
|
||||||
|
}));
|
||||||
|
return [{ label: '全部', value: '' }, ...options];
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== ECharts ====================
|
||||||
|
const buildOption = (data: ChartData) => {
|
||||||
|
const allValues = [...data.planList, ...data.actualList];
|
||||||
|
const maxVal = Math.max(...allValues, 1);
|
||||||
|
const minVal = Math.min(0, ...allValues);
|
||||||
|
|
||||||
|
// 计算 y 轴刻度(等分成4档)
|
||||||
|
const rawRange = maxVal - minVal;
|
||||||
|
const rawStep = rawRange / 4;
|
||||||
|
const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
||||||
|
const normalizedStep = rawStep / magnitude;
|
||||||
|
let niceStep: number;
|
||||||
|
if (normalizedStep <= 1) niceStep = 1;
|
||||||
|
else if (normalizedStep <= 2) niceStep = 2;
|
||||||
|
else if (normalizedStep <= 5) niceStep = 5;
|
||||||
|
else niceStep = 10;
|
||||||
|
niceStep *= magnitude;
|
||||||
|
|
||||||
|
const yMin = minVal < 0 ? 0 : minVal;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis' as const,
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
let html = `<div style="font-size:14px;margin-bottom:6px;">${params[0].name}</div>`;
|
||||||
|
params.forEach((p: any) => {
|
||||||
|
const val = p.value ?? 0;
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;margin:2px 0;">
|
||||||
|
<span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:${p.color};margin-right:6px;"></span>
|
||||||
|
<span>${p.seriesName}: <strong>${val}</strong> ${UNIT}</span>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 0,
|
||||||
|
left: 'right',
|
||||||
|
orient: 'horizontal' as const,
|
||||||
|
icon: 'roundRect' as const,
|
||||||
|
itemGap: 10,
|
||||||
|
data: [
|
||||||
|
{ name: '计划放流', itemStyle: { color: COLORS[0] } },
|
||||||
|
{ name: '实际放流', itemStyle: { color: COLORS[1] } }
|
||||||
|
],
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
color: COLORS,
|
||||||
|
grid: {
|
||||||
|
top: '15%',
|
||||||
|
left: '8%',
|
||||||
|
right: '8%',
|
||||||
|
bottom: '0%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category' as const,
|
||||||
|
data: data.stationList,
|
||||||
|
axisLabel: {
|
||||||
|
rotate: 45,
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value' as const,
|
||||||
|
name: `数量(${UNIT})`,
|
||||||
|
nameGap: 15,
|
||||||
|
nameTextStyle: { fontSize: 12 },
|
||||||
|
splitNumber: 4,
|
||||||
|
min: yMin,
|
||||||
|
max: maxVal + niceStep,
|
||||||
|
interval: niceStep
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '计划放流',
|
||||||
|
type: 'bar',
|
||||||
|
data: data.planList,
|
||||||
|
barMaxWidth: 15,
|
||||||
|
barGap: '40%',
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'bottom',
|
||||||
|
distance: -25,
|
||||||
|
rotate: 90,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#333',
|
||||||
|
align: 'top',
|
||||||
|
verticalAlign: 'middle'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '实际放流',
|
||||||
|
type: 'bar',
|
||||||
|
data: data.actualList,
|
||||||
|
barMaxWidth: 15,
|
||||||
|
barGap: '40%',
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'bottom',
|
||||||
|
distance: -25,
|
||||||
|
rotate: 90,
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#333',
|
||||||
|
align: 'top',
|
||||||
|
verticalAlign: 'middle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
nextTick(() => {
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
chartInstance.on('click', handleChartClick);
|
||||||
|
if (chartData.value.stationList.length > 0) {
|
||||||
|
chartInstance.setOption(buildOption(chartData.value), true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const destroyChart = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== API 请求 ====================
|
||||||
|
/** 获取数据年份(第一次加载时确定默认年份) */
|
||||||
|
const fetchYear = () => {
|
||||||
|
if (!stcd.value) return;
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: currentBaseId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd.value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getPowerStationReleaseYear(params)
|
||||||
|
.then((res: any) => {
|
||||||
|
// debugger
|
||||||
|
const list = res?.data?.data?.[0] ?? [];
|
||||||
|
// debugger
|
||||||
|
if (list.length > 0) {
|
||||||
|
searchDate.value = dayjs(list[0].plansd);
|
||||||
|
} else {
|
||||||
|
searchDate.value = dayjs();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
searchDate.value = dayjs();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 获取明细数据 */
|
||||||
|
const fetchData = () => {
|
||||||
|
if (!stcd.value || !searchDate.value) return;
|
||||||
|
loading.value = true;
|
||||||
|
const startTime = searchDate.value
|
||||||
|
.startOf('year')
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
const endTime = searchDate.value.endOf('year').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: currentBaseId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'startTime',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startTime
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'endTime',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: endTime
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd.value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getPowerStationReleaseData(params)
|
||||||
|
.then((res: any) => {
|
||||||
|
loading.value = false;
|
||||||
|
const list = res?.data?.data ?? [];
|
||||||
|
resData.value = list;
|
||||||
|
hasData.value = list.length > 0;
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.setOption(buildOption(chartData.value), true);
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
resData.value = [];
|
||||||
|
hasData.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 事件处理 ====================
|
||||||
|
const onDateChange = (payload: any) => {
|
||||||
|
if (payload.datetime) {
|
||||||
|
searchDate.value = dayjs(payload.datetime, 'YYYY');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 图表点击 → 打开弹窗 */
|
||||||
|
const handleChartClick = (params: any) => {
|
||||||
|
const name = params?.name;
|
||||||
|
if (!name) return;
|
||||||
|
const matched = stcdSelectOptions.value.find(item => item.label === name);
|
||||||
|
modalPageData.value = {
|
||||||
|
year: searchDate.value ? searchDate.value.format('YYYY') : '',
|
||||||
|
stcdSelectOptions: stcdSelectOptions.value,
|
||||||
|
stcd: matched?.value ?? ''
|
||||||
|
};
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 监听 ====================
|
||||||
|
watch(stcd, newStcd => {
|
||||||
|
if (newStcd) {
|
||||||
|
fetchYear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(searchDate, newDate => {
|
||||||
|
if (newDate) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
chartData,
|
||||||
|
newData => {
|
||||||
|
if (chartInstance && newData.stationList.length > 0) {
|
||||||
|
chartInstance.setOption(buildOption(newData), true);
|
||||||
|
nextTick(() => chartInstance?.resize());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 生命周期 ====================
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
if (stcd.value) {
|
||||||
|
fetchYear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
destroyChart();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.release-statistics {
|
||||||
|
width: 100%;
|
||||||
|
height: 196px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
:deep(.ant-spin-nested-loading) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
:deep(.ant-spin-container) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,5 +1,381 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="dianzhanzhuanti-page dianzhanzhuanti-hbb">
|
||||||
<h2>电站专题 </h2>
|
<!-- 左上角电站选择器 -->
|
||||||
|
<div class="mapTopRight">
|
||||||
|
<a-tree-select
|
||||||
|
v-model:value="treeSelectValue"
|
||||||
|
:tree-data="treeSelectData"
|
||||||
|
:field-names="{
|
||||||
|
label: 'title',
|
||||||
|
value: 'stcd',
|
||||||
|
children: 'children'
|
||||||
|
}"
|
||||||
|
show-search
|
||||||
|
tree-node-filter-prop="title"
|
||||||
|
style="width: 200px"
|
||||||
|
placeholder="请选择电站"
|
||||||
|
:tree-default-expanded-keys="treeDefaultExpandedKeys"
|
||||||
|
@select="onTreeSelect"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grid 布局容器 -->
|
||||||
|
<div class="layout-grid" v-if="showLayout" :style="gridStyle">
|
||||||
|
<div
|
||||||
|
v-for="item in normalizedItems"
|
||||||
|
:key="item.code"
|
||||||
|
:style="{ gridArea: `${item.position}-${item.areaIdx}` }"
|
||||||
|
class="panel-item"
|
||||||
|
>
|
||||||
|
<component :is="getModuleComponent(item.code)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted, onUnmounted, watch, nextTick, provide } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { getTreeConfiguredps, getPowerSubjectLayout } from '@/api/dianZhanZhuanTi';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
||||||
|
import { getModuleComponent } from './moduleMap';
|
||||||
|
import { LAYOUT_GRID_SKELETONS } from './layoutGridSkeletons';
|
||||||
|
import { setMapLegendPos } from '@/components/gis/gisUtils';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'DianZhanZhuanTi'
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 路由参数 ====================
|
||||||
|
const route = useRoute();
|
||||||
|
const { stcd: pageStcd } = route.query as Record<string, string>;
|
||||||
|
|
||||||
|
// ==================== Store ====================
|
||||||
|
const jidiStore = useJidiSelectEventStore();
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const mapViewStore = useMapViewStore();
|
||||||
|
|
||||||
|
// ==================== 布局显隐(3D 模式下隐藏) ====================
|
||||||
|
const showLayout = ref(true);
|
||||||
|
|
||||||
|
// ==================== 电站上下文(provide 给子模块) ====================
|
||||||
|
interface DianZhanStation {
|
||||||
|
wbsCode: string;
|
||||||
|
stcd: string;
|
||||||
|
ennm: string;
|
||||||
|
lgtd: number;
|
||||||
|
lttd: number;
|
||||||
|
}
|
||||||
|
const dianZhanStation = ref<DianZhanStation | null>(null);
|
||||||
|
provide('dianZhanStation', dianZhanStation);
|
||||||
|
|
||||||
|
// ==================== 状态 ====================
|
||||||
|
const treeSelectData = ref<any[]>([]);
|
||||||
|
const treeSelectValue = ref<string>();
|
||||||
|
const treeDefaultExpandedKeys = ref<string[]>([]);
|
||||||
|
const defaultApiId = ref<string>('');
|
||||||
|
const layoutData = ref<{
|
||||||
|
type: string;
|
||||||
|
data: Array<{ code: string; name: string; position: string; index: number }>;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
// ==================== 计算属性 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 bclData 按 position 各自从 0 重新编号,得到 grid-area 所需的 areaIdx
|
||||||
|
*/
|
||||||
|
const normalizedItems = computed(() => {
|
||||||
|
const items = layoutData.value?.data || [];
|
||||||
|
const counter: Record<string, number> = { left: 0, right: 0, bottom: 0 };
|
||||||
|
return items.map((item: any) => {
|
||||||
|
const areaIdx = counter[item.position]++;
|
||||||
|
return { ...item, areaIdx };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 根据 layoutId 从骨架配置取出 CSS Grid 属性 */
|
||||||
|
const gridStyle = computed(() => {
|
||||||
|
const skeleton = LAYOUT_GRID_SKELETONS[layoutData.value?.type || ''];
|
||||||
|
if (!skeleton) return { display: 'none' };
|
||||||
|
return {
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateAreas: skeleton.gridTemplateAreas,
|
||||||
|
gridTemplateColumns: skeleton.gridTemplateColumns,
|
||||||
|
gridTemplateRows: skeleton.gridTemplateRows,
|
||||||
|
gap: '10px',
|
||||||
|
padding: '10px',
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 方法 ====================
|
||||||
|
|
||||||
|
/** 处理树形数据格式 */
|
||||||
|
function mapFuc(dataArray: any[]): any[] {
|
||||||
|
return (dataArray || []).map((item: any) => {
|
||||||
|
const hasChildren = item?.children?.length > 0;
|
||||||
|
return {
|
||||||
|
key: item?.wbsCode || item?.stcd,
|
||||||
|
value: item?.wbsCode || item?.stcd,
|
||||||
|
stcd: item?.wbsCode || item?.stcd,
|
||||||
|
title: item?.wbsName || item?.ennm,
|
||||||
|
ennm: item?.wbsName || item?.ennm,
|
||||||
|
lgtd: item?.lgtd,
|
||||||
|
lttd: item?.lttd,
|
||||||
|
selectable: hasChildren ? false : true,
|
||||||
|
children: hasChildren ? mapFuc(item.children) : undefined
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加载布局配置 */
|
||||||
|
function getLayout(stcd: string) {
|
||||||
|
getPowerSubjectLayout(stcd)
|
||||||
|
.then((res: any) => {
|
||||||
|
const layoutConfig = res?.data?.[0];
|
||||||
|
if (layoutConfig) {
|
||||||
|
const _data = JSON.parse(layoutConfig.bclData ?? '[]').filter(
|
||||||
|
(el: any) => el.code
|
||||||
|
);
|
||||||
|
layoutData.value = { type: layoutConfig.layoutId, data: _data };
|
||||||
|
} else {
|
||||||
|
layoutData.value = { type: '', data: [] };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
layoutData.value = { type: '', data: [] };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TreeSelect 选择事件 */
|
||||||
|
function onTreeSelect(value: any, item: any) {
|
||||||
|
if (!item?.stcd) return;
|
||||||
|
|
||||||
|
defaultApiId.value = item.stcd;
|
||||||
|
const parentCode = findParentCode(item.stcd);
|
||||||
|
|
||||||
|
dianZhanStation.value = {
|
||||||
|
wbsCode: parentCode,
|
||||||
|
stcd: item.stcd,
|
||||||
|
ennm: item.ennm,
|
||||||
|
lgtd: item.lgtd,
|
||||||
|
lttd: item.lttd
|
||||||
|
};
|
||||||
|
getLayout(item.stcd);
|
||||||
|
|
||||||
|
treeSelectValue.value = item.stcd;
|
||||||
|
treeDefaultExpandedKeys.value = [item.stcd];
|
||||||
|
|
||||||
|
// 地图定位
|
||||||
|
waitForMapReady(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if ((window as any).mapClass) {
|
||||||
|
(window as any).mapClass.flyTopanto([item.lgtd, item.lttd], 14);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在树数据中查找叶子节点对应的父节点(基地级)code */
|
||||||
|
function findParentCode(leafStcd: string): string {
|
||||||
|
for (const parent of treeSelectData.value) {
|
||||||
|
if (parent.children?.some((c: any) => c.stcd === leafStcd)) {
|
||||||
|
return parent.stcd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return leafStcd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 等待地图就绪后执行回调 */
|
||||||
|
function waitForMapReady(cb: () => void) {
|
||||||
|
const mc = (window as any).mapClass;
|
||||||
|
if (mc?.view) {
|
||||||
|
cb();
|
||||||
|
} else {
|
||||||
|
nextTick(() => waitForMapReady(cb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 底图切换 ====================
|
||||||
|
|
||||||
|
function switchToSatellite(retryCount = 0) {
|
||||||
|
const mc = (window as any).mapClass;
|
||||||
|
if (mc?.service?.activeBaseLayerKey === undefined) {
|
||||||
|
setTimeout(() => switchToSatellite(0), 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mc.baseLayerSwitcher('BASEMAP-img', true);
|
||||||
|
mapViewStore.setActiveBaseLayerKey('BASEMAP-img');
|
||||||
|
} catch {
|
||||||
|
if (retryCount < 30) {
|
||||||
|
setTimeout(() => switchToSatellite(retryCount + 1), 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToVector() {
|
||||||
|
try {
|
||||||
|
(window as any).mapClass?.baseLayerSwitcher?.('s_province_boundaries', true);
|
||||||
|
mapViewStore.setActiveBaseLayerKey('s_province_boundaries');
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
//处理返回来的路径
|
||||||
|
const getModuleComponenturl = (url: string) => {
|
||||||
|
return defineAsyncComponent(
|
||||||
|
() => import(url)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
// ==================== 监听外部(jidiSelectorMod)切换基地 ====================
|
||||||
|
watch(
|
||||||
|
() => jidiStore.selectedItem?.wbsCode,
|
||||||
|
(newWbsCode) => {
|
||||||
|
if (!newWbsCode || newWbsCode === dianZhanStation.value?.wbsCode) return;
|
||||||
|
|
||||||
|
const parentNode = treeSelectData.value.find(
|
||||||
|
(el: any) => el.stcd === newWbsCode
|
||||||
|
);
|
||||||
|
const targetNode = parentNode?.children?.[0];
|
||||||
|
const leafStcd = targetNode?.stcd || newWbsCode;
|
||||||
|
|
||||||
|
treeSelectValue.value = leafStcd;
|
||||||
|
treeDefaultExpandedKeys.value = [leafStcd];
|
||||||
|
defaultApiId.value = leafStcd;
|
||||||
|
getLayout(leafStcd);
|
||||||
|
|
||||||
|
dianZhanStation.value = {
|
||||||
|
wbsCode: newWbsCode,
|
||||||
|
stcd: leafStcd,
|
||||||
|
ennm: targetNode?.ennm || '',
|
||||||
|
lgtd: targetNode?.lgtd || 0,
|
||||||
|
lttd: targetNode?.lttd || 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 监听布局变化 → 调整地图控件位置 ====================
|
||||||
|
watch(
|
||||||
|
() => layoutData.value,
|
||||||
|
(val) => {
|
||||||
|
if (val?.type) {
|
||||||
|
const sk = LAYOUT_GRID_SKELETONS[val.type];
|
||||||
|
const rows = sk?.gridTemplateRows?.split(' ') || [];
|
||||||
|
const bottomRowHeight = rows[rows.length - 1];
|
||||||
|
nextTick(() => setMapLegendPos(val.type, val, 460, bottomRowHeight));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 地图控件位置重置 ====================
|
||||||
|
function resetMapControlPositions() {
|
||||||
|
['#qgc-legendtl', '#map-filter-container', '#map-compassControl', '#map-controller', '#map-monitor', '#map-baselayer'].forEach(sel => {
|
||||||
|
const el = document.querySelector(sel) as HTMLElement;
|
||||||
|
if (el) {
|
||||||
|
el.style.left = '';
|
||||||
|
el.style.right = '';
|
||||||
|
el.style.bottom = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 监听 3D/2D 切换 ====================
|
||||||
|
watch(
|
||||||
|
() => uiStore.mapType,
|
||||||
|
(newType) => {
|
||||||
|
showLayout.value = newType !== '3D';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 监听地图切换完成:执行控件复位、飞行、底图恢复 ====================
|
||||||
|
watch(
|
||||||
|
() => uiStore.mapSwitchCompletedTick,
|
||||||
|
() => {
|
||||||
|
if (uiStore.mapType === '3D') {
|
||||||
|
// 2D → 3D 完成:复位控件 + 飞到当前电站
|
||||||
|
resetMapControlPositions();
|
||||||
|
const station = dianZhanStation.value;
|
||||||
|
if (station) {
|
||||||
|
const mc = (window as any).mapClass;
|
||||||
|
mc?.flyTopanto?.([station.lgtd, station.lttd], 14);
|
||||||
|
}
|
||||||
|
} else if (showLayout.value) {
|
||||||
|
// 3D → 2D 完成:重定位控件 + 切回影像底图
|
||||||
|
if (layoutData.value?.type) {
|
||||||
|
const sk = LAYOUT_GRID_SKELETONS[layoutData.value.type];
|
||||||
|
const rows = sk?.gridTemplateRows?.split(' ') || [];
|
||||||
|
nextTick(() => setMapLegendPos(layoutData.value!.type, layoutData.value, 460, rows[rows.length - 1]));
|
||||||
|
}
|
||||||
|
switchToSatellite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 生命周期 ====================
|
||||||
|
onMounted(() => {
|
||||||
|
getTreeConfiguredps()
|
||||||
|
.then((res: any) => {
|
||||||
|
const result = res?.data ?? [];
|
||||||
|
const treeData = mapFuc(result);
|
||||||
|
treeSelectData.value = treeData;
|
||||||
|
const allChildren = treeData.flatMap((el: any) => el.children || []) as any[];
|
||||||
|
const targetStcd = pageStcd || treeData?.[0]?.children?.[0]?.stcd;
|
||||||
|
const targetNode = allChildren.find((el: any) => el.stcd === targetStcd)
|
||||||
|
|| treeData?.[0]?.children?.[0];
|
||||||
|
if (targetNode?.stcd) {
|
||||||
|
treeSelectValue.value = targetNode.stcd;
|
||||||
|
treeDefaultExpandedKeys.value = [targetNode.stcd];
|
||||||
|
onTreeSelect(targetNode.stcd, targetNode);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.error('获取电站树形数据失败', err);
|
||||||
|
message.error('获取电站列表失败');
|
||||||
|
});
|
||||||
|
|
||||||
|
switchToSatellite();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
switchToVector();
|
||||||
|
layoutData.value = null;
|
||||||
|
resetMapControlPositions();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.dianzhanzhuanti-page {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index:999;
|
||||||
|
|
||||||
|
.mapTopRight {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 480px;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-grid {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
.panel-item {
|
||||||
|
pointer-events: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
133
frontend/src/views/dianZhanZhuanTi/layoutGridSkeletons.ts
Normal file
133
frontend/src/views/dianZhanZhuanTi/layoutGridSkeletons.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* CSS Grid 布局骨架配置
|
||||||
|
*
|
||||||
|
* 每种布局只定义网格结构(行、列、区域划分),不指定具体模块。
|
||||||
|
* 具体模块由后端 bclData 动态决定,通过 position + per-position-index 映射到 grid-area 名。
|
||||||
|
*
|
||||||
|
* 区域命名规则:{position}-{areaIdx}
|
||||||
|
* areaIdx = 按 position 各自从 0 递增编号(非全局 index)
|
||||||
|
* 例如 left-0 = 左侧第 1 个模块,right-2 = 右侧第 3 个模块
|
||||||
|
*
|
||||||
|
* 相同区域名出现多次 = 合并单元格(如某侧只有 1 个模块时撑满多行)
|
||||||
|
* "." = 空白区域(透出底图)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface GridSkeleton {
|
||||||
|
/** CSS grid-template-areas 字符串 */
|
||||||
|
gridTemplateAreas: string;
|
||||||
|
/** CSS grid-template-columns 字符串 */
|
||||||
|
gridTemplateColumns: string;
|
||||||
|
/** CSS grid-template-rows 字符串 */
|
||||||
|
gridTemplateRows: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LAYOUT_GRID_SKELETONS: Record<string, GridSkeleton> = {
|
||||||
|
// 左3右3加底
|
||||||
|
layout1: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
'"left-0 . right-0" "left-1 . right-1" "left-2 bottom-0 right-2"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//左4右4加底
|
||||||
|
layout2: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
'"left-0 . right-0" "left-1 . right-1" "left-2 . right-2" "left-3 bottom-0 right-3"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
// 左三右三(无底部)
|
||||||
|
layout3: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
'"left-0 . right-0" "left-1 . right-1" "left-2 . right-2"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//左4右4无底
|
||||||
|
layout4: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
'"left-0 . right-0" "left-1 . right-1" "left-2 . right-2" "left-3 . right-3"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 右二(无左侧、无底部)
|
||||||
|
layout5: {
|
||||||
|
gridTemplateAreas: '". . right-0" ". . right-1"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr'
|
||||||
|
},
|
||||||
|
// 左2右1加底
|
||||||
|
layout6: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
'"left-0 . right-0" "left-1 . right-0" "bottom-0 bottom-0 bottom-0"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//仅左3
|
||||||
|
layout7: {
|
||||||
|
gridTemplateAreas: '"left-0 . ." "left-1 . ." "left-2 . ."',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//左2右3加底
|
||||||
|
layout8: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
'"left-0 . right-0" "left-1 . right-1" "bottom-0 bottom-0 right-2"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//左3加底
|
||||||
|
layout9: {
|
||||||
|
gridTemplateAreas: '"left-0 . ." "left-1 . ." "left-2 bottom-0 bottom-0"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 左二右二 + 底部(底部撑满全宽)
|
||||||
|
layout10: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
'"left-0 . right-0" "left-1 . right-1" "bottom-0 bottom-0 bottom-0"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
// 左3右1无底
|
||||||
|
layout11: {
|
||||||
|
gridTemplateAreas: '"left-0 . right-0" "left-1 . right-0" "left-2 . ."',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//右一无底
|
||||||
|
layout12: {
|
||||||
|
gridTemplateAreas: '". . right-0" ". . right-0" ". . ." ". . ."',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//右1(2/3高)无底
|
||||||
|
layout13: {
|
||||||
|
gridTemplateAreas: '". . right-0" ". . right-0" ". . ." ',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr '
|
||||||
|
},
|
||||||
|
//左一右一无底
|
||||||
|
layout14: {
|
||||||
|
gridTemplateAreas: '". left-0 . right-0" "left-0 . right-0" ". . ."',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//左4右3满底
|
||||||
|
layout15: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
' "left-0 . right-0" "left-1 . right-1" "left-2 . right-2" "left-3 bottom-0 bottom-0"',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
//左(2/3+1/3)右3加底
|
||||||
|
layout16: {
|
||||||
|
gridTemplateAreas:
|
||||||
|
'"left-0 . right-0" "left-0 . right-1" "bottom-0 bottom-0 right-2" ',
|
||||||
|
gridTemplateColumns: '440px 1fr 440px',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr'
|
||||||
|
},
|
||||||
|
// TODO: 后续补充其余布局的骨架配置
|
||||||
|
};
|
||||||
120
frontend/src/views/dianZhanZhuanTi/moduleMap.ts
Normal file
120
frontend/src/views/dianZhanZhuanTi/moduleMap.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电站专题组件映射表
|
||||||
|
* key: 后端 bclData 中的 code 字段值
|
||||||
|
* value: Vue 异步组件(按需加载)
|
||||||
|
*/
|
||||||
|
export const moduleMap: Record<string, any> = {
|
||||||
|
// === 已有 Vue 模块映射 ===
|
||||||
|
|
||||||
|
// 垂向水温变化
|
||||||
|
'dianzhanZhuantiMod/DZChuiXiangShuiWenBianHua': defineAsyncComponent(
|
||||||
|
() => import('@/modules/chuixiangshuiwenChangeMod/index.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 水质达标率
|
||||||
|
'dianzhanZhuantiMod/DZShuiZhiDaBiaoLv': defineAsyncComponent(
|
||||||
|
() => import('@/modules/EnvironmentalQuality/index.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 环保设施运行情况
|
||||||
|
'dianzhanZhuantiMod/HuanBaoSheShiYunxingQK': defineAsyncComponent(
|
||||||
|
() => import('@/modules/huanbaoMod/index.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 增殖站运行评估
|
||||||
|
'dianzhanZhuantiMod/ZenZhiZhanYunXingPG': defineAsyncComponent(
|
||||||
|
() => import('./components/ZenZhiZhanYunXingPG.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 水质监测趋势图 → 沿程水质变化
|
||||||
|
'dianzhanZhuantiMod/DZShuiZhiJianCeQuShiTu': defineAsyncComponent(
|
||||||
|
() => import('@/modules/waterQuality/index.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 监测点水温变化 → 水温监测
|
||||||
|
'dianzhanZhuantiMod/JianCeDianShuiWenBianHua': defineAsyncComponent(
|
||||||
|
() => import('@/modules/qixidishuiwenbianhua/index.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 水生生态监测 → 水生生态调查情况
|
||||||
|
'dianzhanZhuantiMod/SSST_MONITOR': defineAsyncComponent(
|
||||||
|
() => import('@/modules/shuishengshengtaijiance/index.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 陆生生态监测 → 陆生生态监测情况
|
||||||
|
'dianzhanZhuantiMod/LSST_MONITOR': defineAsyncComponent(
|
||||||
|
() => import('@/modules/lushengshengtaijiance/index.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 生态流量实时数据 → 生态流量达标情况
|
||||||
|
'dianzhanZhuantiMod/EcologicalDataMod': defineAsyncComponent(
|
||||||
|
() => import('@/modules/shengtaidabiaoMod/index.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 珍稀植物园运行数据
|
||||||
|
'dianzhanZhuantiMod/ZXZWY_DATA': defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
'@/modules/ZhenXiZhiWuYuanMod/ZhiWuYuanJianSheJiJieRuQingKuangBar/index.vue'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
// === 以下为需要新建的模块(临时占位) ===
|
||||||
|
|
||||||
|
// 水电站介绍(待实现)
|
||||||
|
'dianzhanZhuantiMod/ShuiDianZhanJieShao': defineAsyncComponent(
|
||||||
|
() => import('./components/ShuiDianZhanJieShao.vue')
|
||||||
|
),
|
||||||
|
|
||||||
|
// 社会投资扶贫就业(待实现)
|
||||||
|
'dianzhanZhuantiMod/DZSheHuiTouZiFP': defineAsyncComponent(
|
||||||
|
() => import('./components/DZSheHuiTouZiFP.vue')
|
||||||
|
),
|
||||||
|
// 水电站视频监控
|
||||||
|
'dianzhanZhuantiMod/DZShuiDianZhanSPJK': defineAsyncComponent(
|
||||||
|
() => import('./components/DZShuiDianZhanSPJK.vue')
|
||||||
|
),
|
||||||
|
// 电站放流统计
|
||||||
|
'dianzhanZhuantiMod/powerStationReleasetaStistics': defineAsyncComponent(
|
||||||
|
() => import('./components/powerStationReleasetaStistics.vue')
|
||||||
|
),
|
||||||
|
//生态流量数据分析
|
||||||
|
'ecologicalDataMod': defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
'./components/EcologicalFlow.vue'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
//过鱼总量
|
||||||
|
'liuyu/guoyuzongliang': defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
'@/modules/GYZLLB/index.vue'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
//电站放流统计
|
||||||
|
'liuyu/zengZhiFangLiu/powerStationReleasetaStistics': defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
'./components/powerStationReleasetaStistics.vue'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
//珍稀植物园运行数据
|
||||||
|
'luSSTDiaoCha/ZXZWYYunXingShuJu': defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
'./components/ZXZWYYunXingShuJu.vue'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 code 获取对应组件
|
||||||
|
*/
|
||||||
|
export function getModuleComponent(code: string): any {
|
||||||
|
return moduleMap[code] || null;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -9,8 +9,11 @@
|
|||||||
@values-change="onValuesChange"
|
@values-change="onValuesChange"
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a-tooltip title="新增配置">
|
<a-switch v-model:checked="checked" @change ="switchChange" />
|
||||||
<a-button @click="props.handleAdd" type="primary"> 新增沿程配置 </a-button>
|
<a-tooltip title="新增预警配置">
|
||||||
|
<a-button @click="props.handleAdd" type="primary">
|
||||||
|
新增预警配置</a-button
|
||||||
|
>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
@ -18,46 +21,51 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import BasicSearch from "@/components/BasicSearch/index.vue";
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
|
import {
|
||||||
|
updateShow
|
||||||
|
} from '@/api/system/alertRules';
|
||||||
interface Props {
|
interface Props {
|
||||||
handleAdd: () => void;
|
handleAdd: () => void;
|
||||||
|
riverOptions: any[];
|
||||||
|
riverOptionsLoad: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "reset", values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: "searchFinish", values: any): void;
|
(e: 'searchFinish', values: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
const checked = ref(false);
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
configName: "",
|
ruleName: '',
|
||||||
|
ruleType: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchData = ref<any>({ ...initSearchData });
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
const searchList: any = computed(() => [
|
const searchList: any = computed(() => [
|
||||||
{
|
{
|
||||||
type: "Input",
|
type: 'Select',
|
||||||
name: "name",
|
name: 'ruleType',
|
||||||
label: "沿程配置名称",
|
label: '预警类型',
|
||||||
fieldProps: {
|
width: 120,
|
||||||
allowClear: true,
|
options: props.riverOptions,
|
||||||
},
|
loading: props.riverOptionsLoad
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "Input",
|
type: 'Input',
|
||||||
name: "rvnm",
|
name: 'ruleName',
|
||||||
label: "所在河段",
|
label: '预警名称',
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
allowClear: true,
|
allowClear: true
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onSearchFinish = (values: any) => {
|
const onSearchFinish = (values: any) => {
|
||||||
emit("searchFinish", values);
|
emit('searchFinish', values);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
@ -65,11 +73,13 @@ const onValuesChange = (changedValues: any, allValues: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
emit("reset", initSearchData);
|
emit('reset', initSearchData);
|
||||||
|
};
|
||||||
|
const switchChange = (checked: boolean) => {
|
||||||
|
const res = updateShow({isShow:checked?1:0})
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emit("searchFinish", initSearchData);
|
emit('searchFinish', initSearchData);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\ConfigManagement\index.vue -->
|
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\TiltPhotoManagement\index.vue -->
|
||||||
<template>
|
<template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
预警配置管理
|
<ConfigManagementSearch
|
||||||
<!-- <ConfigManagementSearch
|
ref="tiltPhotoManagementSearch"
|
||||||
ref="configManagementSearch"
|
:river-options="riverOptions"
|
||||||
|
:river-options-load="riverOptionsLoad"
|
||||||
:handle-add="handleAdd"
|
:handle-add="handleAdd"
|
||||||
@reset="handleReset"
|
@reset="handleReset"
|
||||||
@search-finish="onSearchFinish"
|
@search-finish="onSearchFinish"
|
||||||
@ -11,7 +12,7 @@
|
|||||||
<BasicTable
|
<BasicTable
|
||||||
ref="basicTable"
|
ref="basicTable"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:list-url="getAllConfigTree"
|
:list-url="bindGetKendoList"
|
||||||
:search-params="searchParams"
|
:search-params="searchParams"
|
||||||
>
|
>
|
||||||
<template #action="{ column, record }">
|
<template #action="{ column, record }">
|
||||||
@ -35,12 +36,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
<ConfigManagementForm
|
<ConfigManagementForm
|
||||||
ref="configManagementForm"
|
ref="tiltPhotoManagementForm"
|
||||||
v-model:visible="editModalVisible"
|
v-model:visible="editModalVisible"
|
||||||
|
:river-options="riverOptions"
|
||||||
|
:river-options-load="riverOptionsLoad"
|
||||||
:initial-values="currentRecord"
|
:initial-values="currentRecord"
|
||||||
@cancel="editModalCancel"
|
@cancel="editModalCancel"
|
||||||
@ok="handleEditSubmit"
|
@ok="handleEditSubmit"
|
||||||
/> -->
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -51,13 +54,11 @@ import ConfigManagementSearch from './ConfigManagementSearch.vue';
|
|||||||
import ConfigManagementForm from './ConfigManagementForm.vue';
|
import ConfigManagementForm from './ConfigManagementForm.vue';
|
||||||
import { message, Modal } from 'ant-design-vue';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
getAllConfigTree,
|
bindGetKendoList,
|
||||||
deleteBaseWbsb,
|
warnruleAddOrUpdate,
|
||||||
saveBaseWbsb
|
warnruleBindDelete
|
||||||
} from '@/api/system/map/ConfigManagement';
|
} from '@/api/system/alertRules';
|
||||||
|
|
||||||
// 表格实例
|
|
||||||
const basicTable = ref<any>(null);
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -69,32 +70,24 @@ const columns = [
|
|||||||
customRender: ({ text, record, index }) => index + 1
|
customRender: ({ text, record, index }) => index + 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '沿程配置名称',
|
title: '预警名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'ruleName',
|
||||||
key: 'name',
|
key: 'ruleName'
|
||||||
width: 150
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '配置编码',
|
title: '预警类型',
|
||||||
key: 'code',
|
dataIndex: 'ruleTypeName',
|
||||||
dataIndex: 'code',
|
key: 'ruleTypeName'
|
||||||
width: 120
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '所在河段',
|
title: '规则类型',
|
||||||
dataIndex: 'rvnm',
|
dataIndex: 'ruleTypeName',
|
||||||
key: 'rvnm',
|
key: 'ruleTypeName'
|
||||||
ellipsis: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建人',
|
title: '创建人',
|
||||||
key: 'recordUser',
|
key: 'recordUserName',
|
||||||
dataIndex: 'recordUser'
|
dataIndex: 'recordUserName',
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建时间',
|
|
||||||
key: 'recordTime',
|
|
||||||
dataIndex: 'recordTime',
|
|
||||||
width: 150
|
width: 150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -105,9 +98,8 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '备注',
|
title: '备注',
|
||||||
dataIndex: 'remark',
|
key: 'description',
|
||||||
key: 'remark',
|
dataIndex: 'description'
|
||||||
width: 360
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@ -118,13 +110,15 @@ const columns = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 表格实例
|
||||||
|
const basicTable = ref<any>(null);
|
||||||
// 搜索参数
|
// 搜索参数
|
||||||
const searchParams = ref({});
|
const searchParams = ref({});
|
||||||
// 编辑弹窗数据
|
// 编辑弹窗数据
|
||||||
const currentRecord = ref<any | null>(null);
|
const currentRecord = ref<any | null>(null);
|
||||||
const editModalVisible = ref(false);
|
const editModalVisible = ref(false);
|
||||||
|
|
||||||
// 新增处理
|
// 添加处理
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
currentRecord.value = null;
|
currentRecord.value = null;
|
||||||
editModalVisible.value = true;
|
editModalVisible.value = true;
|
||||||
@ -144,11 +138,11 @@ const handleDelete = (record: any) => {
|
|||||||
zIndex: 2002,
|
zIndex: 2002,
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
let res = await deleteBaseWbsb([record.id]);
|
let res: any = await warnruleBindDelete({ id: record.bindId });
|
||||||
message.success('删除成功');
|
if (res.code == 0) message.success('删除成功');
|
||||||
basicTable.value.refresh();
|
basicTable.value.refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,20 +154,26 @@ const onSearchFinish = (values: any) => {
|
|||||||
const params = {
|
const params = {
|
||||||
logic: 'and',
|
logic: 'and',
|
||||||
filters: [
|
filters: [
|
||||||
values.parentId
|
{
|
||||||
|
field: 'ruleCode',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: ['common', 'custom']
|
||||||
|
},
|
||||||
|
values.ruleName
|
||||||
? {
|
? {
|
||||||
field: 'parentId',
|
field: 'ruleName',
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.parentId
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
values.name
|
|
||||||
? {
|
|
||||||
field: 'name',
|
|
||||||
operator: 'contains',
|
operator: 'contains',
|
||||||
dataType: 'string',
|
dataType: 'string',
|
||||||
value: values.name
|
value: values.ruleName
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
values.ruleType
|
||||||
|
? {
|
||||||
|
field: 'ruleType',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.ruleType
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
@ -183,9 +183,11 @@ const onSearchFinish = (values: any) => {
|
|||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
basicTable.value.getList({
|
nextTick(() => {
|
||||||
logic: 'and',
|
basicTable.value.getList({
|
||||||
filters: []
|
logic: 'and',
|
||||||
|
filters: []
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -198,22 +200,25 @@ const editModalCancel = () => {
|
|||||||
// 表单提交
|
// 表单提交
|
||||||
const handleEditSubmit = async (values: any) => {
|
const handleEditSubmit = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
let res = await saveBaseWbsb({
|
let res:any = await warnruleAddOrUpdate(values);
|
||||||
...currentRecord.value,
|
if(res.code == 0) message.success(`保存成功`);
|
||||||
...values
|
|
||||||
});
|
|
||||||
message.success(`保存成功`);
|
|
||||||
|
|
||||||
editModalVisible.value = false;
|
editModalVisible.value = false;
|
||||||
basicTable.value.refresh();
|
basicTable.value.refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(`保存失败`);
|
|
||||||
|
|
||||||
editModalVisible.value = false;
|
editModalVisible.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const riverOptions = ref<any>([
|
||||||
|
{ value: 'RZ_RULE', label: '水位' },
|
||||||
|
{ value: 'QGC_RULE', label: '生态流量' },
|
||||||
|
{ value: 'WT_RULE', label: '水温' },
|
||||||
|
{ value: 'WQ_RULE', label: '水质' }
|
||||||
|
]);
|
||||||
|
const riverOptionsLoad = ref(false);
|
||||||
const initOption = () => {};
|
const initOption = () => {};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initOption();
|
initOption();
|
||||||
@ -221,8 +226,22 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.config-management {
|
.tilt-photo-management {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 900;
|
||||||
|
pointer-events: all;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal
|
<a-modal
|
||||||
:title="isEdit ? '编辑倾斜摄影' : '新增倾斜摄影'"
|
:title="isEdit ? '编辑预警规则' : '新增预警规则'"
|
||||||
v-model:open="modalVisible"
|
v-model:open="modalVisible"
|
||||||
:confirm-loading="localLoading"
|
:confirm-loading="localLoading"
|
||||||
width="1536px"
|
width="1536px"
|
||||||
@ -36,14 +36,14 @@
|
|||||||
:options="riverOptions"
|
:options="riverOptions"
|
||||||
:filter-option="filterOption"
|
:filter-option="filterOption"
|
||||||
:disabled="isEdit"
|
:disabled="isEdit"
|
||||||
@change="fetchWarnLevelTabs"
|
@change="loadStaticRuleData"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8" v-if="isWaterQuality">
|
<a-col :span="8" v-if="isWaterQuality">
|
||||||
<a-form-item label="水质等级" name="warnLevel">
|
<a-form-item label="水质等级" name="lvl">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="formData.warnLevel"
|
v-model:value="formData.lvl"
|
||||||
show-search
|
show-search
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请选择水质等级"
|
placeholder="请选择水质等级"
|
||||||
@ -55,15 +55,33 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-tabs v-if="!warnLevelTabsLoading && warnLevelTabs.length > 0" v-model:activeKey="activeTabKey">
|
<a-tabs
|
||||||
|
v-if="!warnLevelTabsLoading && warnLevelTabs.length > 0"
|
||||||
|
v-model:activeKey="activeTabKey"
|
||||||
|
>
|
||||||
<a-tab-pane v-for="tab in warnLevelTabs" :key="tab.key" :tab="tab.label">
|
<a-tab-pane v-for="tab in warnLevelTabs" :key="tab.key" :tab="tab.label">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col v-for="item in detailData[tab.key] || []" :key="item.ys" :span="8">
|
<a-col
|
||||||
<a-form-item :label="item.ysName + 'm'">
|
v-for="item in detailData[tab.key] || []"
|
||||||
|
:key="item.ys"
|
||||||
|
:span="8"
|
||||||
|
>
|
||||||
|
<div class="mb-1 font-medium">
|
||||||
|
{{ item.ysName }}{{ unitSuffix }}:
|
||||||
|
</div>
|
||||||
|
<a-form-item>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<a-input-number v-model:value="item.minVal" placeholder="最小值" style="width: 100%" />
|
<a-input-number
|
||||||
|
v-model:value="item.minVal"
|
||||||
|
placeholder="最小值"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
<span class="flex items-center px-1">~</span>
|
<span class="flex items-center px-1">~</span>
|
||||||
<a-input-number v-model:value="item.maxVal" placeholder="最大值" style="width: 100%" />
|
<a-input-number
|
||||||
|
v-model:value="item.maxVal"
|
||||||
|
placeholder="最大值"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@ -80,10 +98,8 @@ import { ref, reactive, computed, watch } from 'vue';
|
|||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import type { Rule } from 'ant-design-vue/es/form';
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import {
|
import { getDictItemsByCode } from "@/api/dict";
|
||||||
dictGetRemoteDictValue,
|
import { ruleysList } from '@/api/system/alertRules';
|
||||||
ruleysList
|
|
||||||
} from '@/api/system/alertRules';
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
initialValues?: any | null;
|
initialValues?: any | null;
|
||||||
@ -115,7 +131,7 @@ const defaultFormData = reactive({
|
|||||||
id: undefined,
|
id: undefined,
|
||||||
ruleName: undefined,
|
ruleName: undefined,
|
||||||
ruleType: undefined,
|
ruleType: undefined,
|
||||||
warnLevel: undefined,
|
lvl: undefined
|
||||||
});
|
});
|
||||||
const formData: any = reactive({ ...defaultFormData });
|
const formData: any = reactive({ ...defaultFormData });
|
||||||
|
|
||||||
@ -124,7 +140,7 @@ const rules: Record<string, Rule[]> = {
|
|||||||
{ required: true, message: '请输入倾斜影像名称', trigger: 'blur' }
|
{ required: true, message: '请输入倾斜影像名称', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
ruleType: [{ required: true, message: '请输入所在河段', trigger: 'blur' }],
|
ruleType: [{ required: true, message: '请输入所在河段', trigger: 'blur' }],
|
||||||
warnLevel: [{ required: true, message: '请输入所属电站', trigger: 'blur' }],
|
lvl: [{ required: true, message: '请输入所属电站', trigger: 'blur' }],
|
||||||
url: [{ required: true, message: '请输入倾斜影像URL', trigger: 'blur' }],
|
url: [{ required: true, message: '请输入倾斜影像URL', trigger: 'blur' }],
|
||||||
location: [{ required: true, message: '请输入模型坐标', trigger: 'blur' }]
|
location: [{ required: true, message: '请输入模型坐标', trigger: 'blur' }]
|
||||||
};
|
};
|
||||||
@ -133,21 +149,47 @@ const isEdit = computed(() => !!props.initialValues);
|
|||||||
|
|
||||||
// 是否为水质预警类型
|
// 是否为水质预警类型
|
||||||
const isWaterQuality = computed(() => {
|
const isWaterQuality = computed(() => {
|
||||||
const selectedOption = props.riverOptions?.find(opt => opt.value === formData.ruleType);
|
const selectedOption = props.riverOptions?.find(
|
||||||
|
opt => opt.value === formData.ruleType
|
||||||
|
);
|
||||||
return selectedOption?.label === '水质';
|
return selectedOption?.label === '水质';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 根据 ruleType 返回单位后缀
|
||||||
|
const unitSuffix = computed(() => {
|
||||||
|
const unitMap: Record<string, string> = {
|
||||||
|
RZ_RULE: 'm',
|
||||||
|
QGC_RULE: 'm³/s',
|
||||||
|
WQ_RULE: 'mg/L'
|
||||||
|
};
|
||||||
|
return unitMap[formData.ruleType] || '';
|
||||||
|
});
|
||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
try {
|
try {
|
||||||
await formRef.value.validate();
|
await formRef.value.validate();
|
||||||
|
|
||||||
const submitValues = {
|
const isRz = formData.ruleType === 'RZ_RULE';
|
||||||
...formData,
|
const isWq = formData.ruleType === 'WQ_RULE';
|
||||||
captureDate: formData.captureDate
|
|
||||||
? dayjs(formData.captureDate).format('YYYY-MM-DD')
|
|
||||||
: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const submitValues: any = {
|
||||||
|
ruleName: formData.ruleName,
|
||||||
|
ruleType: formData.ruleType,
|
||||||
|
ruleCode: 'common',
|
||||||
|
detail: [
|
||||||
|
{
|
||||||
|
...(isWq ? { lvl: formData.lvl } : { warnLevel: activeTabKey.value }),
|
||||||
|
ysList: (detailData.value[activeTabKey.value] || []).map((item: any) => ({
|
||||||
|
ys: item.ys,
|
||||||
|
minVal: item.minVal,
|
||||||
|
maxVal: item.maxVal,
|
||||||
|
tbId: item.tbId,
|
||||||
|
...(isRz ? { mark: item.mark } : {})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
],
|
||||||
|
...(formData.id ? { id: formData.id } : {})
|
||||||
|
};
|
||||||
emit('ok', submitValues);
|
emit('ok', submitValues);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Validate Failed:', error);
|
console.error('Validate Failed:', error);
|
||||||
@ -195,11 +237,13 @@ const filterOption = (inputValue: string, option: any) => {
|
|||||||
};
|
};
|
||||||
const ennmOptions = ref([]);
|
const ennmOptions = ref([]);
|
||||||
const handleRiverSelect = async (value: any) => {
|
const handleRiverSelect = async (value: any) => {
|
||||||
let res: any = await dictGetRemoteDictValue({ dictCode: 'wwqtg' });
|
//
|
||||||
|
let res: any = await getDictItemsByCode({ dictCode: 'WWQTG' });
|
||||||
|
// debugger
|
||||||
if (res?.data?.length > 0) {
|
if (res?.data?.length > 0) {
|
||||||
res.data.forEach((item: any) => {
|
res.data.forEach((item: any) => {
|
||||||
item.value = item.dictValue;
|
item.value = item.itemCode;
|
||||||
item.label = item.dictMeaning;
|
item.label = item.dictName;
|
||||||
});
|
});
|
||||||
ennmOptions.value = res.data;
|
ennmOptions.value = res.data;
|
||||||
} else {
|
} else {
|
||||||
@ -207,15 +251,27 @@ const handleRiverSelect = async (value: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 预警类型 → 预警等级字典编码映射
|
// 静态预警等级数据(按 ruleType 值索引)
|
||||||
const ruleTypeToDictCodeMap: Record<string, string> = {
|
const staticTabsData: Record<string, { key: string; label: string }[]> = {
|
||||||
'水位': 'RSVRFSR_WARN_LEVEL',
|
RZ_RULE: [
|
||||||
'生态流量': 'EQMN_WARN_LEVEL',
|
{ key: '1', label: '一级预警' },
|
||||||
'水温': 'WTMN_WARN_LEVEL',
|
{ key: '2', label: '二级预警' },
|
||||||
'水质': 'WQLVL_WARN_LEVEL'
|
{ key: '3', label: '三级预警' }
|
||||||
|
],
|
||||||
|
QGC_RULE: [
|
||||||
|
{ key: '1', label: '一级预警' },
|
||||||
|
{ key: '2', label: '二级预警' }
|
||||||
|
],
|
||||||
|
WT_RULE: [
|
||||||
|
{ key: '1', label: '一级预警' },
|
||||||
|
{ key: '2', label: '二级预警' },
|
||||||
|
{ key: '3', label: '三级预警' }
|
||||||
|
],
|
||||||
|
WQ_RULE: [
|
||||||
|
{ key: '0', label: '通用预警' }
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 预警等级标签页
|
// 预警等级标签页
|
||||||
const activeTabKey = ref<string>('');
|
const activeTabKey = ref<string>('');
|
||||||
const warnLevelTabs = ref<{ key: string; label: string }[]>([]);
|
const warnLevelTabs = ref<{ key: string; label: string }[]>([]);
|
||||||
@ -224,57 +280,103 @@ const warnLevelTabsLoading = ref(false);
|
|||||||
// 要素详情数据
|
// 要素详情数据
|
||||||
const detailData = ref<Record<string, any[]>>({});
|
const detailData = ref<Record<string, any[]>>({});
|
||||||
|
|
||||||
// 获取要素列表并初始化 detailData
|
// 根据 ruleType 加载静态 tabs + 动态 ysList
|
||||||
const fetchYsList = async () => {
|
const loadStaticRuleData = async () => {
|
||||||
const res: any = await ruleysList({
|
const tabs = staticTabsData[formData.ruleType];
|
||||||
filter: {
|
if (tabs) {
|
||||||
logic: 'and',
|
warnLevelTabs.value = tabs;
|
||||||
filters: [
|
activeTabKey.value = tabs.length > 0 ? tabs[0].key : '';
|
||||||
|
warnLevelTabsLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filters: any[] = [
|
||||||
{
|
{
|
||||||
field: 'ruleType',
|
field: 'ruleType',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
dataType: 'string',
|
dataType: 'string',
|
||||||
value: formData.ruleType
|
value: formData.ruleType
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
if (isEdit.value && formData.id) {
|
||||||
|
filters.push({
|
||||||
|
field: 'ruleId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formData.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const res: any = await ruleysList({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const list = res?.data || [];
|
||||||
|
// 编辑回显:根据 API 返回数据设置选中 tab / 水质等级
|
||||||
|
if (isEdit.value && list.length > 0) {
|
||||||
|
if (formData.ruleType === 'WQ_RULE') {
|
||||||
|
if (list[0].lvl != null) formData.lvl = list[0].lvl;
|
||||||
|
} else {
|
||||||
|
if (list[0].warnLevel != null) activeTabKey.value = String(list[0].warnLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const details: Record<string, any[]> = {};
|
||||||
|
// 仅当列表中存在 warnLevel 值时才按 warnLevel 过滤,否则全量展示
|
||||||
|
const hasWarnLevel = list.some((item: any) => item.warnLevel != null);
|
||||||
|
// 兜底:按 tbId 去重,生成空壳行(minVal/maxVal 置空),保证空 tab 行数与其他 tab 一致
|
||||||
|
const fallbackItems = (() => {
|
||||||
|
const seen = new Set();
|
||||||
|
return list
|
||||||
|
.filter((item: any) => {
|
||||||
|
if (seen.has(item.tbId)) return false;
|
||||||
|
seen.add(item.tbId);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((item: any) => ({ ...item, minVal: undefined, maxVal: undefined }));
|
||||||
|
})();
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
const filtered = formData.ruleType === 'WQ_RULE'
|
||||||
|
? list
|
||||||
|
: hasWarnLevel
|
||||||
|
? list.filter((item: any) => String(item.warnLevel) === tab.key)
|
||||||
|
: list;
|
||||||
|
if (formData.ruleType === 'RZ_RULE') {
|
||||||
|
const template = filtered.length > 0 ? filtered : fallbackItems;
|
||||||
|
details[tab.key] = [];
|
||||||
|
// 编辑回显时仅当filtered有数据且带mark才直接使用,fallbackItems可能继承mark会导致空tab不拆分
|
||||||
|
const hasMark = filtered.length > 0 && filtered.some((item: any) => item.mark);
|
||||||
|
if (hasMark) {
|
||||||
|
template.forEach((item: any) => {
|
||||||
|
const ysName = item.ysName || (item.mark === 'low' ? '坝上水位' : '坝下水位');
|
||||||
|
details[tab.key].push({ ys: item.ys, ysName, tbId: item.tbId, minVal: item.minVal, maxVal: item.maxVal, mark: item.mark });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
template.forEach((item: any) => {
|
||||||
|
details[tab.key].push({ ys: item.ys, ysName: '坝上水位', tbId: item.tbId, minVal: item.minVal, maxVal: item.maxVal, mark: 'low' });
|
||||||
|
details[tab.key].push({ ys: item.ys, ysName: '坝下水位', tbId: item.tbId, minVal: item.minVal, maxVal: item.maxVal, mark: 'high' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const source = filtered.length > 0 ? filtered : fallbackItems;
|
||||||
|
details[tab.key] = source.map((item: any) => ({
|
||||||
|
ys: item.ys,
|
||||||
|
ysName: item.ysName,
|
||||||
|
tbId: item.tbId,
|
||||||
|
minVal: item.minVal,
|
||||||
|
maxVal: item.maxVal,
|
||||||
|
...(item.mark ? { mark: item.mark } : {})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
detailData.value = details;
|
||||||
|
} catch {
|
||||||
|
detailData.value = {};
|
||||||
|
} finally {
|
||||||
|
warnLevelTabsLoading.value = false;
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
const list = res?.data?.data || [];
|
warnLevelTabs.value = [];
|
||||||
const details: Record<string, any[]> = {};
|
detailData.value = {};
|
||||||
warnLevelTabs.value.forEach(tab => {
|
|
||||||
details[tab.key] = list.map((item: any) => ({
|
|
||||||
ys: item.ys,
|
|
||||||
ysName: item.ysName,
|
|
||||||
tbId: item.tbId,
|
|
||||||
minVal: null,
|
|
||||||
maxVal: null
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
detailData.value = details;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchWarnLevelTabs = async () => {
|
|
||||||
warnLevelTabsLoading.value = true;
|
|
||||||
try {
|
|
||||||
const selectedOption = props.riverOptions?.find(opt => opt.value === formData.ruleType);
|
|
||||||
const dictCode = selectedOption ? ruleTypeToDictCodeMap[selectedOption.label] : undefined;
|
|
||||||
if (!dictCode) {
|
|
||||||
warnLevelTabs.value = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const res: any = await dictGetRemoteDictValue({ dictCode });
|
|
||||||
if (res?.data?.length > 0) {
|
|
||||||
warnLevelTabs.value = res.data.map((item: any) => ({
|
|
||||||
key: item.dictValue,
|
|
||||||
label: item.dictMeaning
|
|
||||||
}));
|
|
||||||
activeTabKey.value = res.data[0].dictValue;
|
|
||||||
} else {
|
|
||||||
warnLevelTabs.value = [];
|
|
||||||
}
|
|
||||||
await fetchYsList();
|
|
||||||
} finally {
|
|
||||||
warnLevelTabsLoading.value = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -288,7 +390,7 @@ watch(
|
|||||||
if (!props.initialValues && props.riverOptions?.length > 0) {
|
if (!props.initialValues && props.riverOptions?.length > 0) {
|
||||||
formData.ruleType = props.riverOptions[0].value;
|
formData.ruleType = props.riverOptions[0].value;
|
||||||
}
|
}
|
||||||
fetchWarnLevelTabs();
|
loadStaticRuleData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: false }
|
{ immediate: false }
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
@values-change="onValuesChange"
|
@values-change="onValuesChange"
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a-tooltip title="新增倾斜摄影">
|
<a-tooltip title="新增预警">
|
||||||
<a-button @click="props.handleAdd" type="primary"> 新增预警</a-button>
|
<a-button @click="props.handleAdd" type="primary"> 新增预警</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -55,8 +55,10 @@ import LayerManagementForm from './LayerManagementForm.vue';
|
|||||||
import { message, Modal } from 'ant-design-vue';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
warnruleGetKendoList,
|
warnruleGetKendoList,
|
||||||
dictGetRemoteDictValue
|
warnruleAddOrUpdate,
|
||||||
|
warnruleDelete
|
||||||
} from '@/api/system/alertRules';
|
} from '@/api/system/alertRules';
|
||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -69,23 +71,23 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '预警名称',
|
title: '预警名称',
|
||||||
dataIndex: 'stnm',
|
dataIndex: 'ruleName',
|
||||||
key: 'stnm'
|
key: 'ruleName'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '预警类型',
|
title: '预警类型',
|
||||||
dataIndex: 'url',
|
dataIndex: 'ruleTypeName',
|
||||||
key: 'url'
|
key: 'ruleTypeName'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '规则类型',
|
title: '规则类型',
|
||||||
dataIndex: 'hbrvcdName',
|
dataIndex: 'ruleTypeName',
|
||||||
key: 'hbrvcdName'
|
key: 'ruleTypeName'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建人',
|
title: '创建人',
|
||||||
key: 'recordUser',
|
key: 'recordUserName',
|
||||||
dataIndex: 'recordUser',
|
dataIndex: 'recordUserName',
|
||||||
width: 150
|
width: 150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -96,8 +98,8 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '备注',
|
title: '备注',
|
||||||
key: 'recordUser',
|
key: 'description',
|
||||||
dataIndex: 'recordUser'
|
dataIndex: 'description'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@ -132,15 +134,14 @@ const handleEdit = (record: any) => {
|
|||||||
const handleDelete = (record: any) => {
|
const handleDelete = (record: any) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: '确定要删除选中的记录吗?',
|
content: '注意,删除此规则后,绑定他的预警配置将会同步删除,确定要删除选中的记录吗?',
|
||||||
zIndex: 2002,
|
zIndex: 2002,
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
// let res = await deleteTiltPhoto({ id: record.id });
|
let res:any = await warnruleDelete({ id: record.id });
|
||||||
message.success('删除成功');
|
if(res.code == 0) message.success('删除成功');
|
||||||
basicTable.value.refresh();
|
basicTable.value.refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,38 +198,25 @@ const editModalCancel = () => {
|
|||||||
|
|
||||||
// 表单提交
|
// 表单提交
|
||||||
const handleEditSubmit = async (values: any) => {
|
const handleEditSubmit = async (values: any) => {
|
||||||
// debugger
|
|
||||||
try {
|
try {
|
||||||
// let res = await saveTiltPhoto({
|
let res:any = await warnruleAddOrUpdate(values);
|
||||||
// ...currentRecord.value,
|
if(res.code == 0) message.success(`保存成功`);
|
||||||
// ...values
|
|
||||||
// });
|
|
||||||
message.success(`保存成功`);
|
|
||||||
|
|
||||||
editModalVisible.value = false;
|
editModalVisible.value = false;
|
||||||
basicTable.value.refresh();
|
basicTable.value.refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(`保存失败`);
|
|
||||||
|
|
||||||
editModalVisible.value = false;
|
editModalVisible.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const riverOptions = ref<any>([]);
|
const riverOptions = ref<any>([
|
||||||
|
{ value: 'RZ_RULE', label: '水位' },
|
||||||
|
{ value: 'QGC_RULE', label: '生态流量' },
|
||||||
|
{ value: 'WT_RULE', label: '水温' },
|
||||||
|
{ value: 'WQ_RULE', label: '水质' },
|
||||||
|
]);
|
||||||
const riverOptionsLoad = ref(false);
|
const riverOptionsLoad = ref(false);
|
||||||
const initOption = () => {
|
const initOption = () => {};
|
||||||
const params = {};
|
|
||||||
riverOptionsLoad.value = true;
|
|
||||||
|
|
||||||
dictGetRemoteDictValue({ dictCode: 'WARN_RULE_TYPE' }).then((res: any) => {
|
|
||||||
res.data.forEach((item: any) => {
|
|
||||||
item.value = item.dictValue;
|
|
||||||
item.label = item.dictMeaning;
|
|
||||||
});
|
|
||||||
riverOptions.value = res.data;
|
|
||||||
riverOptionsLoad.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initOption();
|
initOption();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, reactive, ref, nextTick } from 'vue';
|
import { onMounted, reactive, ref, nextTick, watch } from 'vue';
|
||||||
import { ElMessage, ElMessageBox, FormRules } from 'element-plus';
|
import { ElMessage, ElMessageBox, FormRules } from 'element-plus';
|
||||||
import Sortable from 'sortablejs';
|
import Sortable from 'sortablejs';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
@ -31,6 +31,14 @@ const treedata: any = ref([]);
|
|||||||
const treeRef = ref();
|
const treeRef = ref();
|
||||||
const treeId = ref('');
|
const treeId = ref('');
|
||||||
const defaultProps = { label: 'dictName' };
|
const defaultProps = { label: 'dictName' };
|
||||||
|
const filterText = ref('');
|
||||||
|
function filterNode(value: string, data: any) {
|
||||||
|
if (!value) return true;
|
||||||
|
return data.dictName?.includes(value);
|
||||||
|
}
|
||||||
|
watch(filterText, val => {
|
||||||
|
treeRef.value?.filter(val);
|
||||||
|
});
|
||||||
// 字典弹框
|
// 字典弹框
|
||||||
const title = ref('');
|
const title = ref('');
|
||||||
const dialogdict = ref(false);
|
const dialogdict = ref(false);
|
||||||
@ -432,6 +440,13 @@ const total = ref();
|
|||||||
/>
|
/>
|
||||||
新增字典</el-button
|
新增字典</el-button
|
||||||
>
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="filterText"
|
||||||
|
placeholder="搜索字典"
|
||||||
|
clearable
|
||||||
|
style="margin-bottom: 10px"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
<el-tree
|
<el-tree
|
||||||
v-loading="treeloading"
|
v-loading="treeloading"
|
||||||
ref="treeRef"
|
ref="treeRef"
|
||||||
@ -444,6 +459,7 @@ const total = ref();
|
|||||||
:allow-drop="allowDrop"
|
:allow-drop="allowDrop"
|
||||||
:data="treedata"
|
:data="treedata"
|
||||||
draggable
|
draggable
|
||||||
|
:filter-node-method="filterNode"
|
||||||
:highlight-current="true"
|
:highlight-current="true"
|
||||||
:props="defaultProps"
|
:props="defaultProps"
|
||||||
@node-click="handleNodeClick"
|
@node-click="handleNodeClick"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user