电站专题-初版
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://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'
|
||||
|
||||
@ -109,7 +109,7 @@ export class MapCesium implements MapInterface {
|
||||
this.containerId = container.id;
|
||||
this.containerElement = 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, {
|
||||
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[]>();
|
||||
|
||||
// 过滤掉 defaultConfig: true 的项(兼容字符串类型)
|
||||
const filteredList = props.userColumnList.filter(item => item.defaultConfig !== true);
|
||||
const filteredList = props.userColumnList.filter(item => item.enable == 1);
|
||||
|
||||
// 按 groupType 分组
|
||||
for (const item of filteredList) {
|
||||
|
||||
@ -19,6 +19,16 @@
|
||||
<!-- ===== 概述 ===== -->
|
||||
<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-form-item label="电站名称" name="ennm">
|
||||
<a-input
|
||||
@ -1121,11 +1131,14 @@ const formRef = ref();
|
||||
const confirmLoading = ref(false);
|
||||
const formData = 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' }],
|
||||
baseId: [{ required: true, message: '请选择基地', trigger: 'change' }],
|
||||
reachcd: [{ required: true, message: '请选择所在河段', trigger: 'change' }]
|
||||
});
|
||||
}));
|
||||
|
||||
// 确认弹框状态
|
||||
const confirmModalVisible = ref(false);
|
||||
|
||||
@ -60,6 +60,7 @@
|
||||
<!-- 编辑电站 Modal -->
|
||||
<EditPowerModal
|
||||
v-model:open="editVisible"
|
||||
:is-add="isAdd"
|
||||
:dvtp-list="dvtpList"
|
||||
:record="editRecord"
|
||||
:top-hynm-list="topHynmList"
|
||||
@ -491,7 +492,7 @@ const fetchColumnConfig = () => {
|
||||
|
||||
// 根据 API 数据动态构建表格列,按 orderIndex 排序,只保留 checked 为 1 的列
|
||||
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);
|
||||
|
||||
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: '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: 'zzflbjfs', title: '标记方式', dataIndex: 'zzflbjfs', visible: true, width: 100, ellipsis: true },
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="export-container">
|
||||
<div class="export-container body_one">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
@ -321,4 +321,9 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.body_one {
|
||||
position: relative;
|
||||
z-index: 900;
|
||||
pointer-events: all;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -548,4 +548,9 @@ onMounted(() => {
|
||||
});
|
||||
</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>
|
||||
|
||||
<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.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_URL1 = 'http://211.99.26.225:12127'
|
||||
# 地图服务地址
|
||||
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'
|
||||
# ?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_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'
|
||||
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';
|
||||
|
||||
// 获取所有倾斜摄影
|
||||
//
|
||||
export function warnruleGetKendoList(data: any) {
|
||||
return request({
|
||||
url: '/api/wmp-sys-server/sys/warn/rule/GetKendoList',
|
||||
url: '/sys/warnRule/GetKendoListCust',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
//预警类型下拉框 //水质等级下拉框
|
||||
export function dictGetRemoteDictValue(data: any) {
|
||||
export function bindGetKendoList(data: any) {
|
||||
return request({
|
||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictValue',
|
||||
method: 'get',
|
||||
params: data
|
||||
url: '/sys/warnRule/bind/GetKendoList',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
//新增修改获取相关输入数据
|
||||
export function ruleysList(data: any) {
|
||||
return request({
|
||||
url: '/api/wmp-sys-server/sys/warn/rule/ysList',
|
||||
url: '/sys/warnRule/ysList',
|
||||
method: 'post',
|
||||
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>
|
||||
<div
|
||||
id="map-baselayer"
|
||||
class="baselayer-switcher"
|
||||
:style="{ right: drawerOpen ? '480px' : '12px' }"
|
||||
v-if="uiStore.mapType == '2D'"
|
||||
@ -62,6 +63,16 @@ const nineSectionsImg: any = {
|
||||
|
||||
const activeKey = ref(layers[0].key);
|
||||
|
||||
// 监听外部通过 store 切换底图(如电站专题页)
|
||||
watch(
|
||||
() => mapViewStore.activeBaseLayerKey,
|
||||
(newKey) => {
|
||||
if (newKey && layers.some(l => l.key === newKey)) {
|
||||
activeKey.value = newKey;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 判断图层管理中的基础底图(customBaseLayer)是否被选中
|
||||
*/
|
||||
|
||||
@ -974,7 +974,8 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: #fff;
|
||||
// box-shadow: 0 1px 2px #00000026;
|
||||
.qgc_title {
|
||||
width: 100%;
|
||||
background-color: #e5edf3;
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
drawDotImg5,
|
||||
offset5
|
||||
} from '@/utils/GisUrlList';
|
||||
import { LAYOUT_GRID_SKELETONS } from '@/views/dianZhanZhuanTi/layoutGridSkeletons';
|
||||
declare global {
|
||||
interface Window {
|
||||
__lyConfigs?: {
|
||||
@ -321,162 +322,82 @@ const getListByPosition = (data: any, position: string) =>
|
||||
/**
|
||||
* 设置地图组件位置
|
||||
* @param layoutType - 布局类型
|
||||
* @param data - 布局数据
|
||||
* @param offset - 偏移量
|
||||
* @param data - 布局数据({ type, data: BclDataItem[] })
|
||||
* @param offset - 面板宽度 + padding + gap,默认 460(440px + 10px + 10px)
|
||||
* @param bottomRowHeight - 底部行高度(如 '200px'),无底部模块时不传
|
||||
*/
|
||||
export const setMapLegendPos = (
|
||||
layoutType: string,
|
||||
data: any,
|
||||
offset = 456
|
||||
offset = 460,
|
||||
bottomRowHeight?: string
|
||||
) => {
|
||||
const menuStateString = localStorage.getItem('menuState'); //处理澜沧江左侧菜单状态
|
||||
const menuStateString = localStorage.getItem('menuState');
|
||||
const menuState =
|
||||
menuStateString !== null ? JSON.parse(menuStateString) : true;
|
||||
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 bottomEle = document.querySelector(
|
||||
'#page-layout-bottom'
|
||||
) as HTMLElement;
|
||||
const legend = document.querySelector('#qgc-legendtl') as HTMLElement; // 图例
|
||||
const filter = document.querySelector('#map-filter-container') 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 legend = document.querySelector('#qgc-legendtl') as HTMLElement;
|
||||
const filter = document.querySelector('#map-filter-container') 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 leftList = getListByPosition(data, 'left');
|
||||
const rightList = getListByPosition(data, 'right');
|
||||
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 (window.__mapMode === '3D') {
|
||||
b = `200px`;
|
||||
} else {
|
||||
b = `${menuState ? 200 : 50}px`;
|
||||
b = window.__mapMode === '3D' ? '200px' : `${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) {
|
||||
legend.style.left =
|
||||
!leftHide && left.includes(layoutType) && leftList?.length > le ? l : b;
|
||||
legend.style.bottom = bottomHide
|
||||
? '0'
|
||||
: bottomList?.length > 0
|
||||
? bottom
|
||||
: '12px';
|
||||
legend.style.left = hasLeft ? `calc(${l} - 10px)` : b;
|
||||
legend.style.bottom = bottomOffset;
|
||||
}
|
||||
if (filter) {
|
||||
if (layoutType === 'layout10') {
|
||||
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;
|
||||
}
|
||||
filter.style.left = hasLeft ? l : b;
|
||||
}
|
||||
if (compassControl) {
|
||||
if (layoutType === 'layout10') {
|
||||
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;
|
||||
}
|
||||
compassControl.style.left = hasLeft ? l : b;
|
||||
}
|
||||
if (controller) {
|
||||
controller.style.right =
|
||||
!rightHide && right.includes(layoutType) && rightList?.length > rle
|
||||
? w
|
||||
: '0';
|
||||
controller.style.bottom = bottomHide ? '0' : bottom;
|
||||
controller.style.right = hasRight ? w : '0';
|
||||
controller.style.bottom = hasBottom ? `calc(${bottomOffset} + 10px)` : '0';
|
||||
}
|
||||
if (monitor) {
|
||||
monitor.style.right =
|
||||
!rightHide && right.includes(layoutType) && rightList?.length > rle
|
||||
? w
|
||||
: '0';
|
||||
// monitor.style.bottom = bottomHide ? '0' : bottom
|
||||
monitor.style.right = hasRight ? w : '0';
|
||||
}
|
||||
if (baselayer) {
|
||||
baselayer.style.right =
|
||||
!rightHide && right.includes(layoutType) && rightList?.length > rle
|
||||
? `calc(${w} + 60px)`
|
||||
: '60px';
|
||||
baselayer.style.bottom = bottomHide ? '0' : bottom;
|
||||
baselayer.style.right = hasRight ? `calc(${w} + 60px)` : '60px';
|
||||
baselayer.style.bottom = hasBottom ? bottomOffset : '0';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -142,7 +142,7 @@ export class MapCesium implements MapInterface {
|
||||
try {
|
||||
this.containerId = container.id;
|
||||
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, {
|
||||
animation: false,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="map-controller" :style="{ right: drawerOpen ? '480px' : '12px' }">
|
||||
<div id="map-controller" class="map-controller" :style="{ right: drawerOpen ? '480px' : '12px' }">
|
||||
<div
|
||||
class="map-controller-group"
|
||||
v-for="(item, index) in controllers"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mapLegendView" v-show="!uiStore.isRoaming">
|
||||
<div id="qgc-legendtl" class="mapLegendView" v-show="!uiStore.isRoaming">
|
||||
<div class="legendTitle">
|
||||
图例
|
||||
<span class="legendBtn" @click="isOpen = !isOpen">
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
</template>
|
||||
|
||||
<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 SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
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(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.wbsCode) {
|
||||
baseid.value = newVal.wbsCode;
|
||||
@ -751,8 +755,9 @@ onMounted(() => {
|
||||
setTimeout(() => {
|
||||
initChart();
|
||||
// 如果已有 selectedItem,触发数据加载
|
||||
if (JidiSelectEventStore.selectedItem?.wbsCode) {
|
||||
baseid.value = JidiSelectEventStore.selectedItem.wbsCode;
|
||||
const currentStation = injectedStation?.value || JidiSelectEventStore.selectedItem;
|
||||
if (currentStation?.wbsCode) {
|
||||
baseid.value = currentStation.wbsCode;
|
||||
getEchartsData();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
@ -29,7 +29,8 @@ import {
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
watch,
|
||||
nextTick
|
||||
nextTick,
|
||||
inject
|
||||
} from 'vue';
|
||||
import * as 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(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
async newVal => {
|
||||
if (!newVal || !newVal.wbsCode) {
|
||||
return;
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref, watch, inject, computed } from 'vue';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
import { environmentalProtectionConstruction } from '@/api/home';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
@ -142,9 +142,12 @@ const handleCardClick = (facility: any) => {
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
// 监听基地变化
|
||||
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||
const injectedStation = inject<any>('dianZhanStation', null);
|
||||
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||
|
||||
watch(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.wbsCode) {
|
||||
baseid.value = newVal.wbsCode;
|
||||
|
||||
@ -62,8 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick,watch } from 'vue';
|
||||
// import { ref, watch } from 'vue';
|
||||
import { ref, onMounted, onUnmounted, nextTick, watch, inject, computed } from 'vue';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.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(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
async newVal => {
|
||||
if (newVal && 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 selectedBaseId = ref('');
|
||||
const currentZoomLevel = ref(4.5);
|
||||
const activeBaseLayerKey = ref('s_province_boundaries');
|
||||
|
||||
// 备注:统一写入当前选中的图层 key,始终保持去重后的结果。
|
||||
const setCheckedLayerKeys = (keys: string[] = []) => {
|
||||
@ -72,12 +73,18 @@ export const useMapViewStore = defineStore('map-view', () => {
|
||||
currentZoomLevel.value = 4.5;
|
||||
};
|
||||
|
||||
const setActiveBaseLayerKey = (key: string) => {
|
||||
activeBaseLayerKey.value = key;
|
||||
};
|
||||
|
||||
return {
|
||||
checkedLayerKeys,
|
||||
legendCheckedState,
|
||||
searchTimeRange,
|
||||
selectedBaseId,
|
||||
currentZoomLevel,
|
||||
activeBaseLayerKey,
|
||||
setActiveBaseLayerKey,
|
||||
setCheckedLayerKeys,
|
||||
getCheckedLayerKeys,
|
||||
setLegendCheckedState,
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<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 type { EChartsOption } from 'echarts';
|
||||
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(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
newVal => {
|
||||
baseid.value = newVal.wbsCode;
|
||||
getselsectData();
|
||||
|
||||
@ -98,7 +98,7 @@
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
qgcetQgcStaticData,
|
||||
@ -301,8 +301,12 @@ const getData = async () => {
|
||||
spinning.value = false;
|
||||
}
|
||||
};
|
||||
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||
const injectedStation = inject<any>('dianZhanStation', null);
|
||||
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||
|
||||
watch(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
newVal => {
|
||||
if (newVal && newVal.wbsCode) {
|
||||
baseid.value = newVal.wbsCode;
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref, watch, inject, computed } from 'vue';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.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(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
async newVal => {
|
||||
if (newVal && newVal.wbsCode) {
|
||||
baseid.value = newVal.wbsCode;
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<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 type { EChartsOption } from 'echarts';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
@ -1170,9 +1170,13 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||
const injectedStation = inject<any>('dianZhanStation', null);
|
||||
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||
|
||||
const wbsCode = ref('');
|
||||
watch(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
newVal => {
|
||||
console.log(newVal);
|
||||
wbsCode.value = newVal.wbsCode;
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<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 type { ECharts } from 'echarts';
|
||||
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(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
() => stationSource.value,
|
||||
newVal => {
|
||||
if (newVal && newVal.wbsCode) {
|
||||
baseid.value = newVal.wbsCode;
|
||||
|
||||
@ -103,7 +103,7 @@ const buildGroupedColumns = () => {
|
||||
const groupMap = new Map<string, ColumnItem[]>();
|
||||
|
||||
// 过滤掉 defaultConfig: true 的项(兼容字符串类型)
|
||||
const filteredList = props.userColumnList.filter(item => item.defaultConfig !== true);
|
||||
const filteredList = props.userColumnList.filter(item => item.enable == 1);
|
||||
|
||||
// 按 groupType 分组
|
||||
for (const item of filteredList) {
|
||||
|
||||
@ -481,7 +481,7 @@ const fetchColumnConfig = () => {
|
||||
|
||||
// 根据 API 数据动态构建表格列,按 orderIndex 排序,只保留 checked 为 1 的列
|
||||
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);
|
||||
|
||||
const cols = sorted.map((item: any) => {
|
||||
|
||||
@ -97,12 +97,12 @@ const previewVideoUrl = ref('');
|
||||
useDraggable(imagePreviewVisible, { boundary: true, resetOnOpen: true });
|
||||
useDraggable(videoPreviewVisible, { boundary: true, resetOnOpen: true });
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
<div>
|
||||
<h2>电站专题 </h2>
|
||||
<div class="dianzhanzhuanti-page dianzhanzhuanti-hbb">
|
||||
<!-- 左上角电站选择器 -->
|
||||
<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>
|
||||
</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"
|
||||
>
|
||||
<template #actions>
|
||||
<a-tooltip title="新增配置">
|
||||
<a-button @click="props.handleAdd" type="primary"> 新增沿程配置 </a-button>
|
||||
<a-switch v-model:checked="checked" @change ="switchChange" />
|
||||
<a-tooltip title="新增预警配置">
|
||||
<a-button @click="props.handleAdd" type="primary">
|
||||
新增预警配置</a-button
|
||||
>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</BasicSearch>
|
||||
@ -18,46 +21,51 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import BasicSearch from "@/components/BasicSearch/index.vue";
|
||||
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||
import {
|
||||
updateShow
|
||||
} from '@/api/system/alertRules';
|
||||
interface Props {
|
||||
handleAdd: () => void;
|
||||
riverOptions: any[];
|
||||
riverOptionsLoad: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "reset", values: any): void;
|
||||
(e: "searchFinish", values: any): void;
|
||||
(e: 'reset', values: any): void;
|
||||
(e: 'searchFinish', values: any): void;
|
||||
}>();
|
||||
|
||||
const checked = ref(false);
|
||||
const initSearchData = {
|
||||
configName: "",
|
||||
ruleName: '',
|
||||
ruleType: ''
|
||||
};
|
||||
|
||||
const searchData = ref<any>({ ...initSearchData });
|
||||
const searchList: any = computed(() => [
|
||||
{
|
||||
type: "Input",
|
||||
name: "name",
|
||||
label: "沿程配置名称",
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
type: 'Select',
|
||||
name: 'ruleType',
|
||||
label: '预警类型',
|
||||
width: 120,
|
||||
options: props.riverOptions,
|
||||
loading: props.riverOptionsLoad
|
||||
},
|
||||
{
|
||||
type: "Input",
|
||||
name: "rvnm",
|
||||
label: "所在河段",
|
||||
type: 'Input',
|
||||
name: 'ruleName',
|
||||
label: '预警名称',
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
allowClear: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
const onSearchFinish = (values: any) => {
|
||||
emit("searchFinish", values);
|
||||
emit('searchFinish', values);
|
||||
};
|
||||
|
||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||
@ -65,11 +73,13 @@ const onValuesChange = (changedValues: any, allValues: any) => {
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
emit("reset", initSearchData);
|
||||
emit('reset', initSearchData);
|
||||
};
|
||||
const switchChange = (checked: boolean) => {
|
||||
const res = updateShow({isShow:checked?1:0})
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
emit("searchFinish", initSearchData);
|
||||
emit('searchFinish', initSearchData);
|
||||
});
|
||||
</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>
|
||||
<div class="content">
|
||||
预警配置管理
|
||||
<!-- <ConfigManagementSearch
|
||||
ref="configManagementSearch"
|
||||
<ConfigManagementSearch
|
||||
ref="tiltPhotoManagementSearch"
|
||||
:river-options="riverOptions"
|
||||
:river-options-load="riverOptionsLoad"
|
||||
:handle-add="handleAdd"
|
||||
@reset="handleReset"
|
||||
@search-finish="onSearchFinish"
|
||||
@ -11,7 +12,7 @@
|
||||
<BasicTable
|
||||
ref="basicTable"
|
||||
:columns="columns"
|
||||
:list-url="getAllConfigTree"
|
||||
:list-url="bindGetKendoList"
|
||||
:search-params="searchParams"
|
||||
>
|
||||
<template #action="{ column, record }">
|
||||
@ -35,12 +36,14 @@
|
||||
</template>
|
||||
</BasicTable>
|
||||
<ConfigManagementForm
|
||||
ref="configManagementForm"
|
||||
ref="tiltPhotoManagementForm"
|
||||
v-model:visible="editModalVisible"
|
||||
:river-options="riverOptions"
|
||||
:river-options-load="riverOptionsLoad"
|
||||
:initial-values="currentRecord"
|
||||
@cancel="editModalCancel"
|
||||
@ok="handleEditSubmit"
|
||||
/> -->
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -51,13 +54,11 @@ import ConfigManagementSearch from './ConfigManagementSearch.vue';
|
||||
import ConfigManagementForm from './ConfigManagementForm.vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
getAllConfigTree,
|
||||
deleteBaseWbsb,
|
||||
saveBaseWbsb
|
||||
} from '@/api/system/map/ConfigManagement';
|
||||
bindGetKendoList,
|
||||
warnruleAddOrUpdate,
|
||||
warnruleBindDelete
|
||||
} from '@/api/system/alertRules';
|
||||
|
||||
// 表格实例
|
||||
const basicTable = ref<any>(null);
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
@ -69,32 +70,24 @@ const columns = [
|
||||
customRender: ({ text, record, index }) => index + 1
|
||||
},
|
||||
{
|
||||
title: '沿程配置名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 150
|
||||
title: '预警名称',
|
||||
dataIndex: 'ruleName',
|
||||
key: 'ruleName'
|
||||
},
|
||||
{
|
||||
title: '配置编码',
|
||||
key: 'code',
|
||||
dataIndex: 'code',
|
||||
width: 120
|
||||
title: '预警类型',
|
||||
dataIndex: 'ruleTypeName',
|
||||
key: 'ruleTypeName'
|
||||
},
|
||||
{
|
||||
title: '所在河段',
|
||||
dataIndex: 'rvnm',
|
||||
key: 'rvnm',
|
||||
ellipsis: true
|
||||
title: '规则类型',
|
||||
dataIndex: 'ruleTypeName',
|
||||
key: 'ruleTypeName'
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
key: 'recordUser',
|
||||
dataIndex: 'recordUser'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'recordTime',
|
||||
dataIndex: 'recordTime',
|
||||
key: 'recordUserName',
|
||||
dataIndex: 'recordUserName',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
@ -105,9 +98,8 @@ const columns = [
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
width: 360
|
||||
key: 'description',
|
||||
dataIndex: 'description'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@ -118,13 +110,15 @@ const columns = [
|
||||
}
|
||||
];
|
||||
|
||||
// 表格实例
|
||||
const basicTable = ref<any>(null);
|
||||
// 搜索参数
|
||||
const searchParams = ref({});
|
||||
// 编辑弹窗数据
|
||||
const currentRecord = ref<any | null>(null);
|
||||
const editModalVisible = ref(false);
|
||||
|
||||
// 新增处理
|
||||
// 添加处理
|
||||
const handleAdd = () => {
|
||||
currentRecord.value = null;
|
||||
editModalVisible.value = true;
|
||||
@ -144,11 +138,11 @@ const handleDelete = (record: any) => {
|
||||
zIndex: 2002,
|
||||
onOk: async () => {
|
||||
try {
|
||||
let res = await deleteBaseWbsb([record.id]);
|
||||
message.success('删除成功');
|
||||
let res: any = await warnruleBindDelete({ id: record.bindId });
|
||||
if (res.code == 0) message.success('删除成功');
|
||||
basicTable.value.refresh();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -160,20 +154,26 @@ const onSearchFinish = (values: any) => {
|
||||
const params = {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
values.parentId
|
||||
? {
|
||||
field: 'parentId',
|
||||
operator: 'eq',
|
||||
{
|
||||
field: 'ruleCode',
|
||||
operator: 'in',
|
||||
dataType: 'string',
|
||||
value: values.parentId
|
||||
}
|
||||
: null,
|
||||
values.name
|
||||
value: ['common', 'custom']
|
||||
},
|
||||
values.ruleName
|
||||
? {
|
||||
field: 'name',
|
||||
field: 'ruleName',
|
||||
operator: 'contains',
|
||||
dataType: 'string',
|
||||
value: values.name
|
||||
value: values.ruleName
|
||||
}
|
||||
: null,
|
||||
values.ruleType
|
||||
? {
|
||||
field: 'ruleType',
|
||||
operator: 'contains',
|
||||
dataType: 'string',
|
||||
value: values.ruleType
|
||||
}
|
||||
: null
|
||||
].filter(Boolean)
|
||||
@ -183,10 +183,12 @@ const onSearchFinish = (values: any) => {
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
nextTick(() => {
|
||||
basicTable.value.getList({
|
||||
logic: 'and',
|
||||
filters: []
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 表单取消
|
||||
@ -198,22 +200,25 @@ const editModalCancel = () => {
|
||||
// 表单提交
|
||||
const handleEditSubmit = async (values: any) => {
|
||||
try {
|
||||
let res = await saveBaseWbsb({
|
||||
...currentRecord.value,
|
||||
...values
|
||||
});
|
||||
message.success(`保存成功`);
|
||||
let res:any = await warnruleAddOrUpdate(values);
|
||||
if(res.code == 0) message.success(`保存成功`);
|
||||
|
||||
editModalVisible.value = false;
|
||||
basicTable.value.refresh();
|
||||
} catch (error) {
|
||||
message.error(`保存失败`);
|
||||
|
||||
|
||||
editModalVisible.value = false;
|
||||
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 = () => {};
|
||||
onMounted(() => {
|
||||
initOption();
|
||||
@ -221,8 +226,22 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.config-management {
|
||||
.tilt-photo-management {
|
||||
height: 100%;
|
||||
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>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:title="isEdit ? '编辑倾斜摄影' : '新增倾斜摄影'"
|
||||
:title="isEdit ? '编辑预警规则' : '新增预警规则'"
|
||||
v-model:open="modalVisible"
|
||||
:confirm-loading="localLoading"
|
||||
width="1536px"
|
||||
@ -36,14 +36,14 @@
|
||||
:options="riverOptions"
|
||||
:filter-option="filterOption"
|
||||
:disabled="isEdit"
|
||||
@change="fetchWarnLevelTabs"
|
||||
@change="loadStaticRuleData"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8" v-if="isWaterQuality">
|
||||
<a-form-item label="水质等级" name="warnLevel">
|
||||
<a-form-item label="水质等级" name="lvl">
|
||||
<a-select
|
||||
v-model:value="formData.warnLevel"
|
||||
v-model:value="formData.lvl"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
placeholder="请选择水质等级"
|
||||
@ -55,15 +55,33 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
</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-row :gutter="16">
|
||||
<a-col v-for="item in detailData[tab.key] || []" :key="item.ys" :span="8">
|
||||
<a-form-item :label="item.ysName + 'm'">
|
||||
<a-col
|
||||
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">
|
||||
<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>
|
||||
<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>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@ -80,10 +98,8 @@ import { ref, reactive, computed, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
dictGetRemoteDictValue,
|
||||
ruleysList
|
||||
} from '@/api/system/alertRules';
|
||||
import { getDictItemsByCode } from "@/api/dict";
|
||||
import { ruleysList } from '@/api/system/alertRules';
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
initialValues?: any | null;
|
||||
@ -115,7 +131,7 @@ const defaultFormData = reactive({
|
||||
id: undefined,
|
||||
ruleName: undefined,
|
||||
ruleType: undefined,
|
||||
warnLevel: undefined,
|
||||
lvl: undefined
|
||||
});
|
||||
const formData: any = reactive({ ...defaultFormData });
|
||||
|
||||
@ -124,7 +140,7 @@ const rules: Record<string, Rule[]> = {
|
||||
{ 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' }],
|
||||
location: [{ required: true, message: '请输入模型坐标', trigger: 'blur' }]
|
||||
};
|
||||
@ -133,21 +149,47 @@ const isEdit = computed(() => !!props.initialValues);
|
||||
|
||||
// 是否为水质预警类型
|
||||
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 === '水质';
|
||||
});
|
||||
|
||||
// 根据 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 () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
|
||||
const submitValues = {
|
||||
...formData,
|
||||
captureDate: formData.captureDate
|
||||
? dayjs(formData.captureDate).format('YYYY-MM-DD')
|
||||
: undefined
|
||||
};
|
||||
const isRz = formData.ruleType === 'RZ_RULE';
|
||||
const isWq = formData.ruleType === 'WQ_RULE';
|
||||
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error('Validate Failed:', error);
|
||||
@ -195,11 +237,13 @@ const filterOption = (inputValue: string, option: any) => {
|
||||
};
|
||||
const ennmOptions = ref([]);
|
||||
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) {
|
||||
res.data.forEach((item: any) => {
|
||||
item.value = item.dictValue;
|
||||
item.label = item.dictMeaning;
|
||||
item.value = item.itemCode;
|
||||
item.label = item.dictName;
|
||||
});
|
||||
ennmOptions.value = res.data;
|
||||
} else {
|
||||
@ -207,15 +251,27 @@ const handleRiverSelect = async (value: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 预警类型 → 预警等级字典编码映射
|
||||
const ruleTypeToDictCodeMap: Record<string, string> = {
|
||||
'水位': 'RSVRFSR_WARN_LEVEL',
|
||||
'生态流量': 'EQMN_WARN_LEVEL',
|
||||
'水温': 'WTMN_WARN_LEVEL',
|
||||
'水质': 'WQLVL_WARN_LEVEL'
|
||||
// 静态预警等级数据(按 ruleType 值索引)
|
||||
const staticTabsData: Record<string, { key: string; label: string }[]> = {
|
||||
RZ_RULE: [
|
||||
{ key: '1', label: '一级预警' },
|
||||
{ key: '2', label: '二级预警' },
|
||||
{ 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 warnLevelTabs = ref<{ key: string; label: string }[]>([]);
|
||||
@ -224,58 +280,104 @@ const warnLevelTabsLoading = ref(false);
|
||||
// 要素详情数据
|
||||
const detailData = ref<Record<string, any[]>>({});
|
||||
|
||||
// 获取要素列表并初始化 detailData
|
||||
const fetchYsList = async () => {
|
||||
const res: any = await ruleysList({
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
// 根据 ruleType 加载静态 tabs + 动态 ysList
|
||||
const loadStaticRuleData = async () => {
|
||||
const tabs = staticTabsData[formData.ruleType];
|
||||
if (tabs) {
|
||||
warnLevelTabs.value = tabs;
|
||||
activeTabKey.value = tabs.length > 0 ? tabs[0].key : '';
|
||||
warnLevelTabsLoading.value = true;
|
||||
|
||||
try {
|
||||
const filters: any[] = [
|
||||
{
|
||||
field: 'ruleType',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
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?.data || [];
|
||||
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[]> = {};
|
||||
warnLevelTabs.value.forEach(tab => {
|
||||
details[tab.key] = list.map((item: 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: null,
|
||||
maxVal: null
|
||||
minVal: item.minVal,
|
||||
maxVal: item.maxVal,
|
||||
...(item.mark ? { mark: item.mark } : {})
|
||||
}));
|
||||
}
|
||||
});
|
||||
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();
|
||||
} catch {
|
||||
detailData.value = {};
|
||||
} finally {
|
||||
warnLevelTabsLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
warnLevelTabs.value = [];
|
||||
detailData.value = {};
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
@ -288,7 +390,7 @@ watch(
|
||||
if (!props.initialValues && props.riverOptions?.length > 0) {
|
||||
formData.ruleType = props.riverOptions[0].value;
|
||||
}
|
||||
fetchWarnLevelTabs();
|
||||
loadStaticRuleData();
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
@values-change="onValuesChange"
|
||||
>
|
||||
<template #actions>
|
||||
<a-tooltip title="新增倾斜摄影">
|
||||
<a-tooltip title="新增预警">
|
||||
<a-button @click="props.handleAdd" type="primary"> 新增预警</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
@ -55,8 +55,10 @@ import LayerManagementForm from './LayerManagementForm.vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
warnruleGetKendoList,
|
||||
dictGetRemoteDictValue
|
||||
warnruleAddOrUpdate,
|
||||
warnruleDelete
|
||||
} from '@/api/system/alertRules';
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
@ -69,23 +71,23 @@ const columns = [
|
||||
},
|
||||
{
|
||||
title: '预警名称',
|
||||
dataIndex: 'stnm',
|
||||
key: 'stnm'
|
||||
dataIndex: 'ruleName',
|
||||
key: 'ruleName'
|
||||
},
|
||||
{
|
||||
title: '预警类型',
|
||||
dataIndex: 'url',
|
||||
key: 'url'
|
||||
dataIndex: 'ruleTypeName',
|
||||
key: 'ruleTypeName'
|
||||
},
|
||||
{
|
||||
title: '规则类型',
|
||||
dataIndex: 'hbrvcdName',
|
||||
key: 'hbrvcdName'
|
||||
dataIndex: 'ruleTypeName',
|
||||
key: 'ruleTypeName'
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
key: 'recordUser',
|
||||
dataIndex: 'recordUser',
|
||||
key: 'recordUserName',
|
||||
dataIndex: 'recordUserName',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
@ -96,8 +98,8 @@ const columns = [
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'recordUser',
|
||||
dataIndex: 'recordUser'
|
||||
key: 'description',
|
||||
dataIndex: 'description'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@ -132,15 +134,14 @@ const handleEdit = (record: any) => {
|
||||
const handleDelete = (record: any) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
content: '注意,删除此规则后,绑定他的预警配置将会同步删除,确定要删除选中的记录吗?',
|
||||
zIndex: 2002,
|
||||
onOk: async () => {
|
||||
try {
|
||||
// let res = await deleteTiltPhoto({ id: record.id });
|
||||
message.success('删除成功');
|
||||
let res:any = await warnruleDelete({ id: record.id });
|
||||
if(res.code == 0) message.success('删除成功');
|
||||
basicTable.value.refresh();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -197,38 +198,25 @@ const editModalCancel = () => {
|
||||
|
||||
// 表单提交
|
||||
const handleEditSubmit = async (values: any) => {
|
||||
// debugger
|
||||
try {
|
||||
// let res = await saveTiltPhoto({
|
||||
// ...currentRecord.value,
|
||||
// ...values
|
||||
// });
|
||||
message.success(`保存成功`);
|
||||
let res:any = await warnruleAddOrUpdate(values);
|
||||
if(res.code == 0) message.success(`保存成功`);
|
||||
|
||||
editModalVisible.value = false;
|
||||
basicTable.value.refresh();
|
||||
} catch (error) {
|
||||
message.error(`保存失败`);
|
||||
|
||||
editModalVisible.value = false;
|
||||
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 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;
|
||||
});
|
||||
};
|
||||
const initOption = () => {};
|
||||
onMounted(() => {
|
||||
initOption();
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<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 Sortable from 'sortablejs';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
@ -31,6 +31,14 @@ const treedata: any = ref([]);
|
||||
const treeRef = ref();
|
||||
const treeId = ref('');
|
||||
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 dialogdict = ref(false);
|
||||
@ -432,6 +440,13 @@ const total = ref();
|
||||
/>
|
||||
新增字典</el-button
|
||||
>
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
placeholder="搜索字典"
|
||||
clearable
|
||||
style="margin-bottom: 10px"
|
||||
size="small"
|
||||
/>
|
||||
<el-tree
|
||||
v-loading="treeloading"
|
||||
ref="treeRef"
|
||||
@ -444,6 +459,7 @@ const total = ref();
|
||||
:allow-drop="allowDrop"
|
||||
:data="treedata"
|
||||
draggable
|
||||
:filter-node-method="filterNode"
|
||||
:highlight-current="true"
|
||||
:props="defaultProps"
|
||||
@node-click="handleNodeClick"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user