Merge branch 'wxk-main'
This commit is contained in:
commit
617bf8edaa
@ -114,6 +114,24 @@ export function getFlowDataMonthList(data) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生态流量运行数据 - 删除
|
||||||
|
export function deleteFlowDataInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/eq/data/removeKendoByIds',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生态流量运行数据 - 修改
|
||||||
|
export function updateEqRsData(data) {
|
||||||
|
return request({
|
||||||
|
url: '/eq/data/updateEqRsData',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
// 水质监测数据 - 断面
|
// 水质监测数据 - 断面
|
||||||
export function getWaterDataSectionList(data) {
|
export function getWaterDataSectionList(data) {
|
||||||
return request({
|
return request({
|
||||||
@ -331,6 +349,36 @@ export function deleteFpssrlRInfo(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 过鱼设施AI自动数据 - 分页查询
|
||||||
|
export function getFpssrlAiRPageList(data) {
|
||||||
|
return request({
|
||||||
|
url: '/fp/query/qgc/GetKendoListCustAI',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过鱼设施AI自动数据 - 编辑
|
||||||
|
export function updateFpssrlAiRInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/base/fpssrlAiR/update',
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过鱼设施AI自动数据 - 删除
|
||||||
|
export function deleteFpssrlAiRInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/base/fpssrlAiR/delete',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 鱼类 放鱼
|
// 鱼类 放鱼
|
||||||
export function getFishReleaseList(data) {
|
export function getFishReleaseList(data) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@ -54,7 +54,7 @@ interface Props {
|
|||||||
data?: any[];
|
data?: any[];
|
||||||
enableRowSelection?: boolean;
|
enableRowSelection?: boolean;
|
||||||
enableRowHighlight?: boolean; // 是否启用行点击高亮
|
enableRowHighlight?: boolean; // 是否启用行点击高亮
|
||||||
rowKey?: string;
|
rowKey?: string | ((record: any) => string | number);
|
||||||
searchParams?: Record<string, any>;
|
searchParams?: Record<string, any>;
|
||||||
defaultPageSize?: number;
|
defaultPageSize?: number;
|
||||||
isPage?: boolean;
|
isPage?: boolean;
|
||||||
@ -663,7 +663,20 @@ const enhancedColumns = computed(() => {
|
|||||||
title: String(text)
|
title: String(text)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: () => String(text)
|
default: () =>
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
display: 'inline-block',
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
String(text)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -210,7 +210,7 @@
|
|||||||
<!-- 图片上传 -->
|
<!-- 图片上传 -->
|
||||||
<a-row :gutter="16" class="mt-4">
|
<a-row :gutter="16" class="mt-4">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-form-item label="图片" name="firstimgurl" :label-col="{ span: 3 }" :wrapper-col="{ span: 21 }">
|
<a-form-item label="图片" name="firstimgurl" :label-col="{ span: 4 }" :wrapper-col="{ span: 21 }">
|
||||||
<a-upload
|
<a-upload
|
||||||
v-model:file-list="picFileList"
|
v-model:file-list="picFileList"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
@ -230,7 +230,7 @@
|
|||||||
<!-- 视频上传 -->
|
<!-- 视频上传 -->
|
||||||
<a-row :gutter="16" class="mt-4">
|
<a-row :gutter="16" class="mt-4">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-form-item label="视频" name="videourl" :label-col="{ span: 3 }" :wrapper-col="{ span: 21 }">
|
<a-form-item label="视频" name="videourl" :label-col="{ span: 4 }" :wrapper-col="{ span: 21 }">
|
||||||
<a-upload
|
<a-upload
|
||||||
v-model:file-list="vdpFileList"
|
v-model:file-list="vdpFileList"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
|
|||||||
@ -1,456 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="w-full h-full flex flex-col">
|
|
||||||
<!-- 搜索组件 -->
|
|
||||||
<FishDataSearch
|
|
||||||
mway="1"
|
|
||||||
:exportLoading="exportLoading"
|
|
||||||
@export-btn="exportBtn"
|
|
||||||
@search-finish="onSearchFinish"
|
|
||||||
@reset="onReset"
|
|
||||||
@add="handleAdd"
|
|
||||||
@seeEdit="handleSeeEdit"
|
|
||||||
ref="searchRef"
|
|
||||||
/>
|
|
||||||
<!-- 表格组件 -->
|
|
||||||
<BasicTable
|
|
||||||
ref="tableRef"
|
|
||||||
:scrollX="tableScrollX"
|
|
||||||
:scrollY="tableScrollY"
|
|
||||||
:columns="tableColumns"
|
|
||||||
:list-url="getFishReleaseMonitorList"
|
|
||||||
:searchParams="{
|
|
||||||
sort: sort
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #action="{ record }">
|
|
||||||
<a-button
|
|
||||||
type="link"
|
|
||||||
class="text-[#2f6b98]"
|
|
||||||
size="small"
|
|
||||||
@click="handleEdit(record)"
|
|
||||||
>编辑</a-button
|
|
||||||
>
|
|
||||||
<a-button type="link" danger size="small" @click="handleDelete(record)"
|
|
||||||
>删除</a-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</BasicTable>
|
|
||||||
|
|
||||||
<!-- 图片预览弹框 -->
|
|
||||||
<a-modal
|
|
||||||
v-model:open="imagePreviewVisible"
|
|
||||||
title="图片预览"
|
|
||||||
:footer="null"
|
|
||||||
width="800px"
|
|
||||||
>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<img :src="previewImageUrl" style="width: 100%; height: 600px" />
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
|
|
||||||
<!-- 视频预览弹框 -->
|
|
||||||
<a-modal
|
|
||||||
v-model:open="videoPreviewVisible"
|
|
||||||
title="视频预览"
|
|
||||||
:footer="null"
|
|
||||||
width="800px"
|
|
||||||
>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<video
|
|
||||||
:src="previewVideoUrl"
|
|
||||||
controls
|
|
||||||
style="width: 100%; height: 600px"
|
|
||||||
></video>
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
|
|
||||||
<!-- 编辑/新增 Modal -->
|
|
||||||
<EditFishDataManualModal
|
|
||||||
v-model:open="editVisible"
|
|
||||||
:record="editRecord"
|
|
||||||
:is-add="isAdd"
|
|
||||||
@success="handleEditSuccess"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 删除确认 Modal -->
|
|
||||||
<DeleteConfirmModal
|
|
||||||
ref="deleteModalRef"
|
|
||||||
:delete-fn="deleteFpssFn"
|
|
||||||
:label="'过鱼人工数据'"
|
|
||||||
title="删除过鱼人工数据"
|
|
||||||
@success="handleEditSuccess"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<OperationLogModal v-model:open="operationLogVisible" :table-name="'SD_FPSS_R'" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import FishDataSearch from './FishDataSearch.vue';
|
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
|
||||||
import { getFishReleaseMonitorList, deleteFpssRInfo } from '@/api/DataQueryMenuModule';
|
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
|
||||||
import { useDraggable } from '@/utils/drag';
|
|
||||||
import EditFishDataManualModal from './EditFishDataManualModal.vue';
|
|
||||||
import DeleteConfirmModal from '@/views/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
|
||||||
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
|
||||||
|
|
||||||
const sort = ref<any>([
|
|
||||||
{
|
|
||||||
field: 'baseStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'rvcdStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'rstcdStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'ttpwr',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
dir: 'desc'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
const tableRef = ref();
|
|
||||||
const searchRef = ref();
|
|
||||||
const tableScrollY = ref<string | number>(0);
|
|
||||||
const currentSearchParams = ref<any>({});
|
|
||||||
const exportLoading = ref(false);
|
|
||||||
|
|
||||||
// 编辑/新增相关
|
|
||||||
const editVisible = ref(false);
|
|
||||||
const editRecord = ref<any>(null);
|
|
||||||
const isAdd = ref(false);
|
|
||||||
const deleteModalRef = ref();
|
|
||||||
const operationLogVisible = ref(false);
|
|
||||||
|
|
||||||
// 图片预览
|
|
||||||
const imagePreviewVisible = ref(false);
|
|
||||||
const previewImageUrl = ref('');
|
|
||||||
// 视频预览
|
|
||||||
const videoPreviewVisible = ref(false);
|
|
||||||
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;
|
|
||||||
imagePreviewVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const openVideoPreview = (url: string) => {
|
|
||||||
previewVideoUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL + '/?' + url;
|
|
||||||
videoPreviewVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableScrollX = computed(() =>
|
|
||||||
Math.max(
|
|
||||||
tableColumns.value.reduce(
|
|
||||||
(sum: number, col: any) => sum + (col.width || 180),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
600
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 人工列
|
|
||||||
const tableColumns = ref<any[]>([
|
|
||||||
{
|
|
||||||
key: 'ennm',
|
|
||||||
title: '电站名称',
|
|
||||||
dataIndex: 'stnm',
|
|
||||||
visible: true,
|
|
||||||
width: 200,
|
|
||||||
sort: true,
|
|
||||||
merge: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'baseName',
|
|
||||||
title: '所属基地',
|
|
||||||
dataIndex: 'baseName',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'stnm',
|
|
||||||
title: '过鱼设施名称',
|
|
||||||
dataIndex: 'stnm',
|
|
||||||
sort: true,
|
|
||||||
width: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'strdt',
|
|
||||||
title: '开始时间',
|
|
||||||
dataIndex: 'strdt',
|
|
||||||
sort: true,
|
|
||||||
width: 100,
|
|
||||||
customRender: ({ text }) => dayjs(text).format('YYYY-MM-DD') || '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'enddt',
|
|
||||||
title: '结束时间',
|
|
||||||
dataIndex: 'enddt',
|
|
||||||
sort: true,
|
|
||||||
width: 100,
|
|
||||||
customRender: ({ text }) => dayjs(text).format('YYYY-MM-DD') || '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'tm',
|
|
||||||
title: '填报时间',
|
|
||||||
dataIndex: 'tm',
|
|
||||||
sort: true,
|
|
||||||
width: 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ftp',
|
|
||||||
title: '鱼种类',
|
|
||||||
dataIndex: 'ftp',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'fcnt',
|
|
||||||
title: '过鱼数量(尾)',
|
|
||||||
dataIndex: 'fcnt',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'directionName',
|
|
||||||
title: '鱼游向',
|
|
||||||
dataIndex: 'directionName',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'fsz',
|
|
||||||
title: '鱼类规格',
|
|
||||||
dataIndex: 'fsz',
|
|
||||||
sort: true,
|
|
||||||
width: 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'firstimgurl',
|
|
||||||
title: '图片',
|
|
||||||
dataIndex: 'firstimgurl',
|
|
||||||
align: 'center',
|
|
||||||
width: 110,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const hasValidUrl =
|
|
||||||
text &&
|
|
||||||
text !== '-' &&
|
|
||||||
text.trim() !== '' &&
|
|
||||||
text !== 'null' &&
|
|
||||||
text !== 'undefined';
|
|
||||||
return h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
disabled: !hasValidUrl,
|
|
||||||
style: {
|
|
||||||
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
|
||||||
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
if (hasValidUrl) {
|
|
||||||
openImagePreview(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => '查看图片'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'videourl',
|
|
||||||
title: '视频',
|
|
||||||
dataIndex: 'videourl',
|
|
||||||
align: 'center',
|
|
||||||
width: 110,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const hasValidUrl =
|
|
||||||
text &&
|
|
||||||
text !== '-' &&
|
|
||||||
text.trim() !== '' &&
|
|
||||||
text !== 'null' &&
|
|
||||||
text !== 'undefined';
|
|
||||||
return h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
disabled: !hasValidUrl,
|
|
||||||
style: {
|
|
||||||
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
|
||||||
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
if (hasValidUrl) {
|
|
||||||
openVideoPreview(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => '查看视频'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'action',
|
|
||||||
title: '操作',
|
|
||||||
dataIndex: 'action',
|
|
||||||
fixed: 'right',
|
|
||||||
width: 120
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const onSearchFinish = async (values: any) => {
|
|
||||||
currentSearchParams.value = values;
|
|
||||||
// 等待 prop 更新后再调用 getList
|
|
||||||
await nextTick();
|
|
||||||
initTable(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onReset = async (values: any) => {
|
|
||||||
currentSearchParams.value = values;
|
|
||||||
await nextTick();
|
|
||||||
initTable(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportBtn = () => {
|
|
||||||
if (exportLoading.value) return;
|
|
||||||
|
|
||||||
exportLoading.value = true;
|
|
||||||
searchRef.value.btnLoading = true;
|
|
||||||
|
|
||||||
tableRef.value
|
|
||||||
.exportTable({
|
|
||||||
fileName: `过鱼监测数据_人工_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
exportLoading.value = false;
|
|
||||||
searchRef.value.btnLoading = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const initTable = (values: any) => {
|
|
||||||
console.log(values);
|
|
||||||
const filters = [
|
|
||||||
values.rvcd && values.rvcd !== 'all'
|
|
||||||
? {
|
|
||||||
field: 'rvcd',
|
|
||||||
operator: 'contains',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.rvcd
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
values.rstcd
|
|
||||||
? {
|
|
||||||
field: 'rstcd',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.rstcd
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
values.mway
|
|
||||||
? {
|
|
||||||
field: 'mway',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.mway
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
].filter(Boolean); // 移除空项
|
|
||||||
if (values.stcd) {
|
|
||||||
filters.push({
|
|
||||||
logic: 'or',
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
field: 'stcd',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.stcd
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'stcode',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.stcd
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (values.jcdt) {
|
|
||||||
filters.push(
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
operator: 'gte',
|
|
||||||
dataType: 'date',
|
|
||||||
value: dayjs(values.jcdt.min).format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
operator: 'lte',
|
|
||||||
dataType: 'date',
|
|
||||||
value: dayjs(values.jcdt.max).format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
logic: 'and',
|
|
||||||
filters
|
|
||||||
};
|
|
||||||
tableRef.value.getList(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 新增
|
|
||||||
const handleAdd = () => {
|
|
||||||
isAdd.value = true;
|
|
||||||
editRecord.value = null;
|
|
||||||
editVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSeeEdit = () => {
|
|
||||||
operationLogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编辑
|
|
||||||
const handleEdit = (record: any) => {
|
|
||||||
isAdd.value = false;
|
|
||||||
editRecord.value = { ...record };
|
|
||||||
editVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编辑成功回调
|
|
||||||
const handleEditSuccess = () => {
|
|
||||||
initTable(currentSearchParams.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除
|
|
||||||
const deleteFpssFn = async (record: any, reason: string) => {
|
|
||||||
return deleteFpssRInfo({
|
|
||||||
ids: [record.id],
|
|
||||||
source: reason
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = (record: any) => {
|
|
||||||
deleteModalRef.value?.open(record, () => {
|
|
||||||
initTable(currentSearchParams.value);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
tableScrollY.value = calcTableScrollY();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@ -57,8 +57,10 @@
|
|||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a-button v-if="props.mway === '1'" type="primary" @click="handleAdd">新增</a-button>
|
|
||||||
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
||||||
|
<a-button danger :disabled="!selectedCount" @click="batchDelete"
|
||||||
|
>批量删除</a-button
|
||||||
|
>
|
||||||
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
||||||
</template>
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
@ -77,8 +79,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'export-btn'): void;
|
(e: 'export-btn'): void;
|
||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
(e: 'add'): void;
|
|
||||||
(e: 'seeEdit'): void;
|
(e: 'seeEdit'): void;
|
||||||
|
(e: 'batch-delete'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
@ -86,13 +88,13 @@ const btnLoading = ref<boolean>(false);
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
exportLoading?: boolean;
|
exportLoading?: boolean;
|
||||||
mway: string;
|
selectedCount?: number;
|
||||||
}>();
|
}>();
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
rstcd: null,
|
rstcd: null,
|
||||||
stcd: '0086601020VP000006',
|
stcd: '0086601020VP000006',
|
||||||
mway: props.mway
|
mway: '1'
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchData = ref<any>({ ...initSearchData });
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
@ -140,6 +142,22 @@ const searchList: any = computed(() => [
|
|||||||
value: item.stcd
|
value: item.stcd
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'Radio',
|
||||||
|
name: 'mway',
|
||||||
|
label: '数据类型',
|
||||||
|
width: 140,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '人工',
|
||||||
|
value: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '自动',
|
||||||
|
value: '2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
name: 'jcdt',
|
name: 'jcdt',
|
||||||
@ -157,15 +175,13 @@ const onSearchFinish = (values: any) => {
|
|||||||
const exportBtn = () => {
|
const exportBtn = () => {
|
||||||
emit('export-btn');
|
emit('export-btn');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdd = () => {
|
|
||||||
emit('add');
|
|
||||||
};
|
|
||||||
|
|
||||||
const seeEdit = () => {
|
const seeEdit = () => {
|
||||||
emit('seeEdit');
|
emit('seeEdit');
|
||||||
};
|
};
|
||||||
const initCrossSectionList = (params, setDefault = false) => {
|
const batchDelete = () => {
|
||||||
|
emit('batch-delete');
|
||||||
|
};
|
||||||
|
const initCrossSectionList = params => {
|
||||||
const filters = [];
|
const filters = [];
|
||||||
if (params) {
|
if (params) {
|
||||||
if (params.rvcd != '' && params.rvcd != 'all') {
|
if (params.rvcd != '' && params.rvcd != 'all') {
|
||||||
@ -185,13 +201,6 @@ const initCrossSectionList = (params, setDefault = false) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 根据人工/自动站过滤过鱼设施
|
|
||||||
filters.push({
|
|
||||||
field: 'mway',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: props.mway
|
|
||||||
});
|
|
||||||
crossSectionListLoading.value = true;
|
crossSectionListLoading.value = true;
|
||||||
getFishReleaseMonitorSectionList({
|
getFishReleaseMonitorSectionList({
|
||||||
filter: {
|
filter: {
|
||||||
@ -202,15 +211,6 @@ const initCrossSectionList = (params, setDefault = false) => {
|
|||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res.data?.data) {
|
if (res.data?.data) {
|
||||||
crossSectionList.value = res.data?.data;
|
crossSectionList.value = res.data?.data;
|
||||||
// 首次加载时默认选中第一条过鱼设施
|
|
||||||
if (setDefault && crossSectionList.value.length > 0) {
|
|
||||||
const firstStcd = crossSectionList.value[0].stcd;
|
|
||||||
initSearchData.stcd = firstStcd;
|
|
||||||
if (basicSearchRef.value?.formData) {
|
|
||||||
basicSearchRef.value.formData.stcd = firstStcd;
|
|
||||||
}
|
|
||||||
handleSearchFinish({ ...initSearchData, stcd: firstStcd });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
crossSectionListLoading.value = false;
|
crossSectionListLoading.value = false;
|
||||||
});
|
});
|
||||||
@ -261,8 +261,9 @@ onMounted(() => {
|
|||||||
min: `${now.subtract(5, 'year').format('YYYY-MM-DD')} 00:00:00`,
|
min: `${now.subtract(5, 'year').format('YYYY-MM-DD')} 00:00:00`,
|
||||||
max: `${now.format('YYYY-MM-DD')} 23:59:59`
|
max: `${now.format('YYYY-MM-DD')} 23:59:59`
|
||||||
};
|
};
|
||||||
// 首次加载:请求设施列表后默认选中第一条并触发搜索
|
initCrossSectionList();
|
||||||
initCrossSectionList(undefined, true);
|
// 使用 handleSearchFinish 确保包含 jcdt 等完整参数
|
||||||
|
handleSearchFinish({ ...initSearchData });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,697 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full flex flex-col pl-[20px] body_one">
|
<div class="w-full h-full flex flex-col pt-[20px] pl-[20px] body_one">
|
||||||
<a-tabs v-model:activeKey="activeTab" class="fish-tabs">
|
<!-- 搜索组件 -->
|
||||||
<a-tab-pane key="manual" tab="人工站点">
|
<FishDataSearch
|
||||||
<FishDataManual />
|
@export-btn="exportBtn"
|
||||||
</a-tab-pane>
|
@search-finish="onSearchFinish"
|
||||||
<a-tab-pane key="auto" tab="自动站点">
|
@reset="onReset"
|
||||||
<FishDataAuto />
|
@seeEdit="handleSeeEdit"
|
||||||
</a-tab-pane>
|
@batch-delete="handleBatchDelete"
|
||||||
</a-tabs>
|
:selected-count="selectedRowKeys.length"
|
||||||
|
:exportLoading="exportLoading"
|
||||||
|
ref="searchRef"
|
||||||
|
/>
|
||||||
|
<!-- 表格组件 -->
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:enable-row-selection="true"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:scrollY="tableScrollY"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:list-url="getFishReleaseMonitorList"
|
||||||
|
:searchParams="{
|
||||||
|
sort: sort
|
||||||
|
}"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="text-[#2f6b98]"
|
||||||
|
size="small"
|
||||||
|
@click="handleEdit(record)"
|
||||||
|
>编辑</a-button
|
||||||
|
>
|
||||||
|
<a-button type="link" danger size="small" @click="handleDelete(record)"
|
||||||
|
>删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
|
||||||
|
<!-- 图片预览弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="imagePreviewVisible"
|
||||||
|
title="图片预览"
|
||||||
|
:footer="null"
|
||||||
|
width="800px"
|
||||||
|
>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<img :src="previewImageUrl" style="width: 100%; height: 600px" />
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 视频预览弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="videoPreviewVisible"
|
||||||
|
title="视频预览"
|
||||||
|
:footer="null"
|
||||||
|
width="800px"
|
||||||
|
>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<video
|
||||||
|
:src="previewVideoUrl"
|
||||||
|
controls
|
||||||
|
style="width: 100%; height: 600px"
|
||||||
|
></video>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 编辑 Modal -->
|
||||||
|
<EditFishDataManualModal
|
||||||
|
v-if="isManual"
|
||||||
|
v-model:open="editVisible"
|
||||||
|
:record="editRecord"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
<EditFishDataAutoModal
|
||||||
|
v-else
|
||||||
|
v-model:open="editVisible"
|
||||||
|
:record="editRecord"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 删除确认 Modal -->
|
||||||
|
<DeleteConfirmModal
|
||||||
|
ref="deleteModalRef"
|
||||||
|
:delete-fn="deleteFn"
|
||||||
|
:label="deleteLabel"
|
||||||
|
:title="'删除' + deleteLabel"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 操作日志弹框 -->
|
||||||
|
<OperationLogModal
|
||||||
|
v-model:open="operationLogVisible"
|
||||||
|
:table-name="operationTableName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
||||||
import FishDataManual from './FishDataManual.vue';
|
import dayjs from 'dayjs';
|
||||||
import FishDataAuto from './FishDataAuto.vue';
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
import FishDataSearch from './FishDataSearch.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import EditFishDataManualModal from './EditFishDataManualModal.vue';
|
||||||
|
import EditFishDataAutoModal from './EditFishDataAutoModal.vue';
|
||||||
|
import DeleteConfirmModal from '@/views/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
||||||
|
import { getFishReleaseMonitorList, deleteFpssRInfo, deleteFpssrlRInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
const activeTab = ref('manual');
|
const sort = ref<any>([
|
||||||
|
{
|
||||||
|
field: 'baseStepSort',
|
||||||
|
dir: 'asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rvcdStepSort',
|
||||||
|
dir: 'asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rstcdStepSort',
|
||||||
|
dir: 'asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ttpwr',
|
||||||
|
dir: 'asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
dir: 'desc'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const tableRef = ref();
|
||||||
|
const searchRef = ref();
|
||||||
|
const tableScrollY = ref<string | number>(0);
|
||||||
|
const currentSearchParams = ref<any>({});
|
||||||
|
const exportLoading = ref(false);
|
||||||
|
|
||||||
|
// 编辑/删除相关
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const editRecord = ref<any>(null);
|
||||||
|
const deleteModalRef = ref();
|
||||||
|
const operationLogVisible = ref(false);
|
||||||
|
const isManual = computed(() => currentSearchParams.value.mway !== '2');
|
||||||
|
const deleteLabel = computed(() =>
|
||||||
|
isManual.value ? '过鱼人工数据' : '过鱼自动数据'
|
||||||
|
);
|
||||||
|
const operationTableName = computed(() =>
|
||||||
|
isManual.value ? 'SD_FPSS_R' : 'SD_FPSS_A'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 多选相关
|
||||||
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
|
const selectedRows = ref<any[]>([]);
|
||||||
|
const handleSelectionChange = (keys: string[], rows: any[]) => {
|
||||||
|
selectedRowKeys.value = keys;
|
||||||
|
selectedRows.value = rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图片预览
|
||||||
|
const imagePreviewVisible = ref(false);
|
||||||
|
const previewImageUrl = ref('');
|
||||||
|
// 视频预览
|
||||||
|
const videoPreviewVisible = ref(false);
|
||||||
|
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;
|
||||||
|
imagePreviewVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openVideoPreview = (url: string) => {
|
||||||
|
previewVideoUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL + '/?' + url;
|
||||||
|
videoPreviewVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableScrollX = computed(() =>
|
||||||
|
Math.max(
|
||||||
|
tableColumns.value.reduce(
|
||||||
|
(sum: number, col: any) => sum + (col.width || 180),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
600
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 人工列
|
||||||
|
const columnsManual: any[] = [
|
||||||
|
{
|
||||||
|
key: 'ennm',
|
||||||
|
title: '电站名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
visible: true,
|
||||||
|
width: 200,
|
||||||
|
sort: true,
|
||||||
|
merge: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'baseName',
|
||||||
|
title: '所属基地',
|
||||||
|
dataIndex: 'baseName',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '过鱼设施名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
sort: true,
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'strdt',
|
||||||
|
title: '开始时间',
|
||||||
|
dataIndex: 'strdt',
|
||||||
|
sort: true,
|
||||||
|
width: 100,
|
||||||
|
customRender: ({ text }) => dayjs(text).format('YYYY-MM-DD') || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enddt',
|
||||||
|
title: '结束时间',
|
||||||
|
dataIndex: 'enddt',
|
||||||
|
sort: true,
|
||||||
|
width: 100,
|
||||||
|
customRender: ({ text }) => dayjs(text).format('YYYY-MM-DD') || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tm',
|
||||||
|
title: '填报时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
sort: true,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ftp',
|
||||||
|
title: '鱼种类',
|
||||||
|
dataIndex: 'ftp',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fcnt',
|
||||||
|
title: '过鱼数量(尾)',
|
||||||
|
dataIndex: 'fcnt',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'directionName',
|
||||||
|
title: '鱼游向',
|
||||||
|
dataIndex: 'directionName',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fsz',
|
||||||
|
title: '鱼类规格',
|
||||||
|
dataIndex: 'fsz',
|
||||||
|
sort: true,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'firstimgurl',
|
||||||
|
title: '图片',
|
||||||
|
dataIndex: 'firstimgurl',
|
||||||
|
align: 'center',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const hasValidUrl =
|
||||||
|
text &&
|
||||||
|
text !== '-' &&
|
||||||
|
text.trim() !== '' &&
|
||||||
|
text !== 'null' &&
|
||||||
|
text !== 'undefined';
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
disabled: !hasValidUrl,
|
||||||
|
style: {
|
||||||
|
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
||||||
|
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
if (hasValidUrl) {
|
||||||
|
openImagePreview(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => '查看图片'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'videourl',
|
||||||
|
title: '视频',
|
||||||
|
dataIndex: 'videourl',
|
||||||
|
align: 'center',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const hasValidUrl =
|
||||||
|
text &&
|
||||||
|
text !== '-' &&
|
||||||
|
text.trim() !== '' &&
|
||||||
|
text !== 'null' &&
|
||||||
|
text !== 'undefined';
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
disabled: !hasValidUrl,
|
||||||
|
style: {
|
||||||
|
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
||||||
|
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
if (hasValidUrl) {
|
||||||
|
openVideoPreview(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => '查看视频'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 自动列
|
||||||
|
const columnsAuto: any[] = [
|
||||||
|
{
|
||||||
|
key: 'ennm',
|
||||||
|
title: '电站名称',
|
||||||
|
dataIndex: 'ennm',
|
||||||
|
visible: true,
|
||||||
|
width: 200,
|
||||||
|
sort: true,
|
||||||
|
merge: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'baseName',
|
||||||
|
title: '所属基地',
|
||||||
|
dataIndex: 'baseName',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '过鱼设施名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
sort: true,
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tm',
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
sort: true,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ftp',
|
||||||
|
title: '鱼种类',
|
||||||
|
dataIndex: 'ftp',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'length',
|
||||||
|
title: '鱼长度(cm)',
|
||||||
|
dataIndex: 'length',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'width',
|
||||||
|
title: '鱼宽度(cm)',
|
||||||
|
dataIndex: 'width',
|
||||||
|
sort: true,
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const num = parseFloat(text);
|
||||||
|
return isNaN(num) ? '-' : num.toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fishspeed',
|
||||||
|
title: '鱼速度(m/s)',
|
||||||
|
dataIndex: 'fishspeed',
|
||||||
|
sort: true,
|
||||||
|
width: 80,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const num = parseFloat(text);
|
||||||
|
return isNaN(num) ? '-' : num.toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'directionName',
|
||||||
|
title: '鱼游向',
|
||||||
|
dataIndex: 'directionName',
|
||||||
|
sort: true,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'temperature',
|
||||||
|
title: '水温(℃)',
|
||||||
|
dataIndex: 'temperature',
|
||||||
|
sort: true,
|
||||||
|
width: 160,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const num = parseFloat(text);
|
||||||
|
return isNaN(num) ? '-' : num.toFixed(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'speed',
|
||||||
|
title: '流速(m/s)',
|
||||||
|
dataIndex: 'speed',
|
||||||
|
sort: true,
|
||||||
|
width: 160,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const num = parseFloat(text);
|
||||||
|
return isNaN(num) ? '-' : num.toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fcnt',
|
||||||
|
title: '过鱼数量(尾)',
|
||||||
|
dataIndex: 'fcnt',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'channelno',
|
||||||
|
title: '过鱼通道',
|
||||||
|
dataIndex: 'channelno',
|
||||||
|
sort: true,
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'firstimgurl',
|
||||||
|
title: '轮廓图',
|
||||||
|
dataIndex: 'firstimgurl',
|
||||||
|
align: 'center',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const hasValidUrl =
|
||||||
|
text &&
|
||||||
|
text !== '-' &&
|
||||||
|
text.trim() !== '' &&
|
||||||
|
text !== 'null' &&
|
||||||
|
text !== 'undefined';
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
disabled: !hasValidUrl,
|
||||||
|
style: {
|
||||||
|
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
||||||
|
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
if (hasValidUrl) {
|
||||||
|
openImagePreview(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => '查看图片'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'videourl',
|
||||||
|
title: '短视频',
|
||||||
|
dataIndex: 'videourl',
|
||||||
|
align: 'center',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const hasValidUrl =
|
||||||
|
text &&
|
||||||
|
text !== '-' &&
|
||||||
|
text.trim() !== '' &&
|
||||||
|
text !== 'null' &&
|
||||||
|
text !== 'undefined';
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
disabled: !hasValidUrl,
|
||||||
|
style: {
|
||||||
|
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
||||||
|
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
if (hasValidUrl) {
|
||||||
|
openVideoPreview(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => '查看视频'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 根据数据类型动态切换列
|
||||||
|
const tableColumns = computed(() => {
|
||||||
|
const mway = currentSearchParams.value.mway;
|
||||||
|
return mway === '2' ? columnsAuto : columnsManual;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSearchFinish = async (values: any) => {
|
||||||
|
currentSearchParams.value = values;
|
||||||
|
// 等待 prop 更新后再调用 getList
|
||||||
|
await nextTick();
|
||||||
|
initTable(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReset = async (values: any) => {
|
||||||
|
currentSearchParams.value = values;
|
||||||
|
await nextTick();
|
||||||
|
initTable(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportBtn = () => {
|
||||||
|
if (exportLoading.value) return;
|
||||||
|
|
||||||
|
exportLoading.value = true;
|
||||||
|
searchRef.value.btnLoading = true;
|
||||||
|
|
||||||
|
tableRef.value
|
||||||
|
.exportTable({
|
||||||
|
fileName: `过鱼监测数据_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
exportLoading.value = false;
|
||||||
|
searchRef.value.btnLoading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initTable = (values: any) => {
|
||||||
|
console.log(values);
|
||||||
|
const filters = [
|
||||||
|
values.rvcd && values.rvcd !== 'all'
|
||||||
|
? {
|
||||||
|
field: 'rvcd',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.rvcd
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
values.rstcd
|
||||||
|
? {
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.rstcd
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
values.mway
|
||||||
|
? {
|
||||||
|
field: 'mway',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.mway
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
].filter(Boolean); // 移除空项
|
||||||
|
if (values.stcd) {
|
||||||
|
filters.push({
|
||||||
|
logic: 'or',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (values.jcdt) {
|
||||||
|
filters.push(
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dayjs(values.jcdt.min).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dayjs(values.jcdt.max).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
};
|
||||||
|
tableRef.value.getList(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (record: any) => {
|
||||||
|
editRecord.value = { ...record };
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑成功回调
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const deleteFn = async (record: any, reason: string) => {
|
||||||
|
const ids = record.ids || [record.id];
|
||||||
|
if (isManual.value) {
|
||||||
|
return deleteFpssRInfo({
|
||||||
|
ids,
|
||||||
|
source: reason
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deleteFpssrlRInfo({
|
||||||
|
ids,
|
||||||
|
source: reason
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除后刷新:清空选中(让批量删除按钮回到禁用状态)并重新加载列表
|
||||||
|
const refreshAfterDelete = () => {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
deleteModalRef.value?.open(record, () => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (selectedRowKeys.value.length === 0) {
|
||||||
|
message.info('请先勾选要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteModalRef.value?.open(
|
||||||
|
{
|
||||||
|
ids: [...selectedRowKeys.value],
|
||||||
|
stnm: `选中的 ${selectedRowKeys.value.length} 条数据`
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSeeEdit = () => {
|
||||||
|
operationLogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
tableScrollY.value = calcTableScrollY();
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@ -24,21 +699,4 @@ const activeTab = ref('manual');
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 900;
|
z-index: 900;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}</style>
|
||||||
|
|
||||||
.fish-tabs {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
:deep(.ant-tabs-content-holder) {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-tabs-content),
|
|
||||||
:deep(.ant-tabs-tabpane) {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="编辑放鱼数据"
|
||||||
|
width="600px"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<a-empty description="编辑功能开发中,接口待接入" />
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
record?: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:open', 'success']);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: val => emit('update:open', val)
|
||||||
|
});
|
||||||
|
useDraggable(visible, { boundary: true, resetOnOpen: true });
|
||||||
|
</script>
|
||||||
@ -0,0 +1,420 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="放鱼详情"
|
||||||
|
width="90vw"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<!-- 查询区:复用页面搜索项 -->
|
||||||
|
<BasicSearch
|
||||||
|
ref="basicSearchRef"
|
||||||
|
:searchList="searchList"
|
||||||
|
:initial-values="initSearchData"
|
||||||
|
:zhujianfujian="'fu'"
|
||||||
|
@reset="handleResetWrapper"
|
||||||
|
@finish="onSearchFinish"
|
||||||
|
@values-change="onValuesChange"
|
||||||
|
>
|
||||||
|
<template #jcdt>
|
||||||
|
<a-date-picker
|
||||||
|
class="w-[160px]"
|
||||||
|
v-model:value="jcdt.min"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
picker="year"
|
||||||
|
:showTime="timeShowTime"
|
||||||
|
placeholder="起始时间"
|
||||||
|
:showToday="false"
|
||||||
|
>
|
||||||
|
<template #renderExtraFooter>
|
||||||
|
<div class="flex items-center flex-wrap px-2">
|
||||||
|
<span
|
||||||
|
v-for="item in timeDateList"
|
||||||
|
:key="item"
|
||||||
|
@click="handleRangeClick(item)"
|
||||||
|
>
|
||||||
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-date-picker>
|
||||||
|
至
|
||||||
|
<a-form-item-rest>
|
||||||
|
<a-date-picker
|
||||||
|
class="w-[160px]"
|
||||||
|
v-model:value="jcdt.max"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
picker="year"
|
||||||
|
:showTime="timeShowTime"
|
||||||
|
:disabledDate="disabledEndDate"
|
||||||
|
placeholder="结束时间"
|
||||||
|
:showToday="false"
|
||||||
|
>
|
||||||
|
<template #renderExtraFooter>
|
||||||
|
<div class="flex items-center flex-wrap px-2">
|
||||||
|
<span
|
||||||
|
v-for="item in timeDateList"
|
||||||
|
:key="item"
|
||||||
|
@click="handleRangeClick(item)"
|
||||||
|
>
|
||||||
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-date-picker>
|
||||||
|
</a-form-item-rest>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<a-button
|
||||||
|
danger
|
||||||
|
:disabled="!selectedRowKeys.length"
|
||||||
|
@click="handleBatchDelete"
|
||||||
|
>批量删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</BasicSearch>
|
||||||
|
|
||||||
|
<!-- 表格(无放流总量列) -->
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:enable-row-selection="true"
|
||||||
|
:row-key="getRowKey"
|
||||||
|
:columns="columns"
|
||||||
|
:list-url="listUrl"
|
||||||
|
:scrollY="500"
|
||||||
|
:searchParams="{ sort }"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="text-[#2f6b98]"
|
||||||
|
size="small"
|
||||||
|
@click="handleEdit(record)"
|
||||||
|
>修改</a-button
|
||||||
|
>
|
||||||
|
<a-button type="link" danger size="small" @click="handleDelete(record)"
|
||||||
|
>删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
|
||||||
|
<!-- 编辑弹窗(占位) -->
|
||||||
|
<EditFishReleaseModal
|
||||||
|
v-model:open="editVisible"
|
||||||
|
:record="editRecord"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 删除确认 Modal -->
|
||||||
|
<DeleteConfirmModal
|
||||||
|
ref="deleteModalRef"
|
||||||
|
:delete-fn="handleDeleteFn"
|
||||||
|
:label="deleteLabel"
|
||||||
|
:title="'删除' + deleteLabel"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, nextTick } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import EditFishReleaseModal from './EditFishReleaseModal.vue';
|
||||||
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
|
import { useTimeScale } from '@/store/composables/useTimeScale';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
record?: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:open', 'success']);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: val => emit('update:open', val)
|
||||||
|
});
|
||||||
|
useDraggable(visible, { boundary: true, resetOnOpen: true });
|
||||||
|
|
||||||
|
const tableRef = ref();
|
||||||
|
const basicSearchRef = ref();
|
||||||
|
|
||||||
|
const initSearchData = {
|
||||||
|
rvcd: 'all',
|
||||||
|
rstcd: null,
|
||||||
|
stcd: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeDateList = ref([
|
||||||
|
{
|
||||||
|
label: '一年',
|
||||||
|
value: [dayjs().startOf('year'), dayjs().endOf('year')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '两年',
|
||||||
|
value: [dayjs().subtract(1, 'year').startOf('year'), dayjs().endOf('year')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '三年',
|
||||||
|
value: [dayjs().subtract(2, 'year').startOf('year'), dayjs().endOf('year')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '五年',
|
||||||
|
value: [
|
||||||
|
dayjs().subtract(4, 'year').startOf('year'),
|
||||||
|
dayjs().startOf('year')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
jcdt,
|
||||||
|
currentTimeScale,
|
||||||
|
timeShowTime,
|
||||||
|
handleRangeClick,
|
||||||
|
disabledEndDate,
|
||||||
|
handleSearchFinish,
|
||||||
|
handleValuesChange,
|
||||||
|
init
|
||||||
|
} = useTimeScale({
|
||||||
|
defaultTimeScale: initSearchData.timeScale,
|
||||||
|
onSearchFinish: values => initTable(values),
|
||||||
|
onReset: () => initTable(initSearchData)
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchList: any = computed(() => [
|
||||||
|
{
|
||||||
|
type: 'waterStation',
|
||||||
|
name: 'rvcd',
|
||||||
|
label: '流域',
|
||||||
|
placeholder: '请输入流域名称',
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true
|
||||||
|
},
|
||||||
|
options: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
name: 'jcdt',
|
||||||
|
label: '时间',
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onSearchFinish = (values: any) => {
|
||||||
|
handleSearchFinish(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
|
handleValuesChange(changedValues, allValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetWrapper = () => {
|
||||||
|
currentTimeScale.value = initSearchData.timeScale;
|
||||||
|
const now = dayjs();
|
||||||
|
jcdt.value = {
|
||||||
|
min: `${now
|
||||||
|
.subtract(1, 'year')
|
||||||
|
.startOf('year')
|
||||||
|
.format('YYYY-MM-DD')} 00:00:00`,
|
||||||
|
max: `${now.endOf('year').format('YYYY-MM-DD')} 23:59:59`
|
||||||
|
};
|
||||||
|
handleSearchFinish({ ...initSearchData });
|
||||||
|
};
|
||||||
|
|
||||||
|
const sort = ref<any>([
|
||||||
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
|
{ field: 'siteStepSort', dir: 'asc' },
|
||||||
|
{ field: 'flyr', dir: 'asc' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// TODO: 弹窗查询接口待接入,暂时返回空数据
|
||||||
|
const listUrl = async () => {
|
||||||
|
console.warn('放鱼详情查询接口待接入');
|
||||||
|
return { data: { records: [], total: 0 } };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格列(无放流总量)
|
||||||
|
const columns: any[] = [
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '电站名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
visible: true,
|
||||||
|
width: 200,
|
||||||
|
sort: true,
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'baseName',
|
||||||
|
title: '所属基地',
|
||||||
|
dataIndex: 'baseName',
|
||||||
|
sort: true,
|
||||||
|
width: 160,
|
||||||
|
mergeDeps: ['stnm']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'flyr',
|
||||||
|
title: '放鱼年份',
|
||||||
|
dataIndex: 'flyr',
|
||||||
|
sort: true,
|
||||||
|
width: 160,
|
||||||
|
mergeDeps: ['stnm']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'release',
|
||||||
|
title: '放流来源',
|
||||||
|
dataIndex: 'release',
|
||||||
|
mergeDeps: ['stnm'],
|
||||||
|
width: 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ftpName',
|
||||||
|
title: '放鱼种类',
|
||||||
|
dataIndex: 'ftpName',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fcnt',
|
||||||
|
title: '数量(万尾)',
|
||||||
|
dataIndex: 'fcnt',
|
||||||
|
width: 100,
|
||||||
|
customRender: ({ record }: any) =>
|
||||||
|
(record.fcnt / 10000).toFixed(4) || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 多选相关(唯一标识 stcd+ftpName)
|
||||||
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
|
const selectedRows = ref<any[]>([]);
|
||||||
|
const getRowKey = (record: any) => `${record.stcd}_${record.ftpName}`;
|
||||||
|
const handleSelectionChange = (keys: string[], rows: any[]) => {
|
||||||
|
selectedRowKeys.value = keys;
|
||||||
|
selectedRows.value = rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const editRecord = ref<any>(null);
|
||||||
|
const handleEdit = (record: any) => {
|
||||||
|
editRecord.value = { ...record };
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentSearchParams = ref<any>({});
|
||||||
|
|
||||||
|
const initTable = (values: any) => {
|
||||||
|
currentSearchParams.value = values;
|
||||||
|
const filters = [
|
||||||
|
values.rvcd && values.rvcd !== 'all'
|
||||||
|
? {
|
||||||
|
field: 'rvcd',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.rvcd
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
values.rstcd
|
||||||
|
? {
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.rstcd
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
].filter(Boolean);
|
||||||
|
if (values.jcdt) {
|
||||||
|
filters.push(
|
||||||
|
{
|
||||||
|
field: 'flyr',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dayjs(values.jcdt.min).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'flyr',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dayjs(values.jcdt.max).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tableRef.value?.getList({
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const deleteModalRef = ref();
|
||||||
|
const deleteLabel = '放鱼数据';
|
||||||
|
|
||||||
|
// 删除接口暂未提供,待接入后替换
|
||||||
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
message.error('删除接口暂未提供');
|
||||||
|
throw new Error('删除接口暂未提供');
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshAfterDelete = () => {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
deleteModalRef.value?.open(record, () => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (selectedRowKeys.value.length === 0) {
|
||||||
|
message.info('请先勾选要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteModalRef.value?.open(
|
||||||
|
{
|
||||||
|
ids: [...selectedRowKeys.value],
|
||||||
|
stnm: `选中的 ${selectedRowKeys.value.length} 条数据`
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init();
|
||||||
|
const now = dayjs();
|
||||||
|
jcdt.value = {
|
||||||
|
min: `${now
|
||||||
|
.subtract(1, 'year')
|
||||||
|
.startOf('year')
|
||||||
|
.format('YYYY-MM-DD')} 00:00:00`,
|
||||||
|
max: `${now.endOf('year').format('YYYY-MM-DD')} 23:59:59`
|
||||||
|
};
|
||||||
|
handleSearchFinish({ ...initSearchData });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -59,6 +59,7 @@
|
|||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
||||||
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
||||||
</template>
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
@ -97,6 +98,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'export-btn'): void;
|
(e: 'export-btn'): void;
|
||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
|
(e: 'seeEdit'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
@ -157,6 +159,9 @@ const onSearchFinish = (values: any) => {
|
|||||||
const exportBtn = () => {
|
const exportBtn = () => {
|
||||||
emit('export-btn');
|
emit('export-btn');
|
||||||
};
|
};
|
||||||
|
const seeEdit = () => {
|
||||||
|
emit('seeEdit');
|
||||||
|
};
|
||||||
|
|
||||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
handleValuesChange(changedValues, allValues);
|
handleValuesChange(changedValues, allValues);
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
@export-btn="exportBtn"
|
@export-btn="exportBtn"
|
||||||
@search-finish="onSearchFinish"
|
@search-finish="onSearchFinish"
|
||||||
@reset="onReset"
|
@reset="onReset"
|
||||||
|
@seeEdit="handleSeeEdit"
|
||||||
:exportLoading="exportLoading"
|
:exportLoading="exportLoading"
|
||||||
ref="searchRef"
|
ref="searchRef"
|
||||||
/>
|
/>
|
||||||
@ -19,13 +20,36 @@
|
|||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="text-[#2f6b98]"
|
||||||
|
size="small"
|
||||||
|
@click="handleDetail(record)"
|
||||||
|
>详情</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
|
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<FishReleaseDetailModal
|
||||||
|
v-model:open="detailVisible"
|
||||||
|
:record="detailRecord"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 操作日志弹框 -->
|
||||||
|
<OperationLogModal
|
||||||
|
v-model:open="operationLogVisible"
|
||||||
|
:table-name="operationTableName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import FishReleaseSearch from './FishReleaseSearch.vue';
|
import FishReleaseSearch from './FishReleaseSearch.vue';
|
||||||
|
import FishReleaseDetailModal from './FishReleaseDetailModal.vue';
|
||||||
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import { getFishReleaseList } from '@/api/DataQueryMenuModule';
|
import { getFishReleaseList } from '@/api/DataQueryMenuModule';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
@ -54,6 +78,11 @@ const tableScrollY = ref<string | number>(0);
|
|||||||
const currentSearchParams = ref<any>({});
|
const currentSearchParams = ref<any>({});
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
|
|
||||||
|
// 查看修改(操作日志)
|
||||||
|
const operationLogVisible = ref(false);
|
||||||
|
// TODO: 操作日志表名待确认
|
||||||
|
const operationTableName = 'SD_RPIMNFISH_R';
|
||||||
|
|
||||||
const tableScrollX = computed(() =>
|
const tableScrollX = computed(() =>
|
||||||
Math.max(
|
Math.max(
|
||||||
columns.reduce((sum: number, col: any) => sum + (col.width || 180), 0),
|
columns.reduce((sum: number, col: any) => sum + (col.width || 180), 0),
|
||||||
@ -122,6 +151,13 @@ const columns: any[] = [
|
|||||||
dataIndex: 'fcnt',
|
dataIndex: 'fcnt',
|
||||||
width: 100,
|
width: 100,
|
||||||
customRender: ({ record }: any) => (record.fcnt / 10000).toFixed(4) || '-'
|
customRender: ({ record }: any) => (record.fcnt / 10000).toFixed(4) || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 80
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -198,6 +234,19 @@ const initTable = (values: any) => {
|
|||||||
tableRef.value.getList(params);
|
tableRef.value.getList(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 查看修改(操作日志)
|
||||||
|
const handleSeeEdit = () => {
|
||||||
|
operationLogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 详情弹窗
|
||||||
|
const detailVisible = ref(false);
|
||||||
|
const detailRecord = ref<any>(null);
|
||||||
|
const handleDetail = (record: any) => {
|
||||||
|
detailRecord.value = { ...record };
|
||||||
|
detailVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
tableScrollY.value = calcTableScrollY();
|
tableScrollY.value = calcTableScrollY();
|
||||||
|
|||||||
@ -0,0 +1,400 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="编辑生态流量数据"
|
||||||
|
width="70vw"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:label-col="{ span: 8 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
:rules="formRules"
|
||||||
|
class="max-h-[70vh] overflow-y-auto pr-4"
|
||||||
|
>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="电站名称" name="stnm">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.stnm"
|
||||||
|
placeholder="请输入电站名称"
|
||||||
|
disabled
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="基地" name="baseName">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.baseName"
|
||||||
|
placeholder="请输入基地"
|
||||||
|
disabled
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="时间" name="tm">
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="formData.tm"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="请选择时间"
|
||||||
|
style="width: 100%"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="生态流量是否达标" name="sfdb">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.sfdb"
|
||||||
|
placeholder="请选择是否达标"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in sfdbOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
:value="opt.value"
|
||||||
|
>{{ opt.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="水利部要求是否达标" name="mwrSfdb">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.mwrSfdb"
|
||||||
|
placeholder="请选择是否达标"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in sfdbOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
:value="opt.value"
|
||||||
|
>{{ opt.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="多年平均流量*10%是否达标" name="avqSfdb">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.avqSfdb"
|
||||||
|
placeholder="请选择是否达标"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in sfdbOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
:value="opt.value"
|
||||||
|
>{{ opt.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="入库流量(m³/s)" name="qi">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.qi"
|
||||||
|
placeholder="请输入入库流量"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="生态流量值(m³/s)" name="qec">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.qec"
|
||||||
|
placeholder="请输入生态流量值"
|
||||||
|
:precision="3"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="生态环境部限值要求(m³/s)" name="qecLimit">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.qecLimit"
|
||||||
|
placeholder="请输入生态环境部限值要求"
|
||||||
|
:precision="5"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="水利部限值要求(m³/s)" name="mwrLimit">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.mwrLimit"
|
||||||
|
placeholder="请输入水利部限值要求"
|
||||||
|
:precision="5"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="多年平均流量(m³/s)" name="avqLimit">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.avqLimit"
|
||||||
|
placeholder="请输入多年平均流量"
|
||||||
|
:precision="5"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
v-model:open="confirmModalVisible"
|
||||||
|
:diff-list="diffList"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@confirm="handleConfirmSubmit"
|
||||||
|
@cancel="handleConfirmCancel"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import ConfirmModal from '@/components/ConfirmModal/index.vue';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
import { updateEqRsData } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
record?: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:open', 'success']);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: val => emit('update:open', val)
|
||||||
|
});
|
||||||
|
useDraggable(visible, { boundary: true, resetOnOpen: true });
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
const formData = ref<any>({});
|
||||||
|
const originalRecord = ref<any>({});
|
||||||
|
const originalLabels = ref<Record<string, string>>({});
|
||||||
|
const confirmModalVisible = ref(false);
|
||||||
|
|
||||||
|
const formRules = ref<any>({
|
||||||
|
tm: [{ required: true, message: '请选择时间', trigger: 'change' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 字段名 → 中文名映射(与页面表格展示字段对应)
|
||||||
|
const fieldLabelMap: Record<string, string> = {
|
||||||
|
stnm: '电站名称',
|
||||||
|
baseName: '基地',
|
||||||
|
tm: '时间',
|
||||||
|
qi: '入库流量(m³/s)',
|
||||||
|
qec: '生态流量值(m³/s)',
|
||||||
|
sfdb: '生态流量是否达标',
|
||||||
|
qecLimit: '生态环境部限值要求(m³/s)',
|
||||||
|
mwrLimit: '水利部限值要求(m³/s)',
|
||||||
|
mwrSfdb: '水利部要求是否达标',
|
||||||
|
avqLimit: '多年平均流量(m³/s)',
|
||||||
|
avqSfdb: '多年平均流量*10%是否达标'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 达标状态选项
|
||||||
|
const sfdbOptions = [
|
||||||
|
{ label: '不达标', value: 0 },
|
||||||
|
{ label: '达标', value: 1 },
|
||||||
|
{ label: '无生态流量数据', value: 2 },
|
||||||
|
{ label: '无生态流量限值要求', value: 3 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// select 选项映射
|
||||||
|
const selectOptionsMap: Record<string, () => any[]> = {
|
||||||
|
sfdb: () => sfdbOptions,
|
||||||
|
mwrSfdb: () => sfdbOptions,
|
||||||
|
avqSfdb: () => sfdbOptions
|
||||||
|
};
|
||||||
|
|
||||||
|
// 按修改顺序记录变更
|
||||||
|
const changeOrder = ref<
|
||||||
|
{ field: string; label: string; oldValue: string; newValue: string }[]
|
||||||
|
>([]);
|
||||||
|
const diffList = changeOrder;
|
||||||
|
|
||||||
|
const resolveSelectLabel = (field: string, value: any): string => {
|
||||||
|
if (value === null || value === undefined || value === '') return '空';
|
||||||
|
const getOptions = selectOptionsMap[field];
|
||||||
|
if (getOptions) {
|
||||||
|
const opt = getOptions().find(o => String(o.value) === String(value));
|
||||||
|
if (opt) return opt.label;
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 formData 变化
|
||||||
|
watch(
|
||||||
|
() => formData.value,
|
||||||
|
newData => {
|
||||||
|
const original = originalRecord.value;
|
||||||
|
for (const key in newData) {
|
||||||
|
const oldVal = original[key];
|
||||||
|
const newVal = newData[key];
|
||||||
|
if (oldVal !== newVal) {
|
||||||
|
const existingIdx = changeOrder.value.findIndex(c => c.field === key);
|
||||||
|
const oldNorm =
|
||||||
|
oldVal === null || oldVal === undefined || oldVal === '-'
|
||||||
|
? null
|
||||||
|
: oldVal;
|
||||||
|
const newNorm = newVal === null || newVal === undefined ? null : newVal;
|
||||||
|
if (oldNorm === newNorm) continue;
|
||||||
|
if (
|
||||||
|
oldNorm !== null &&
|
||||||
|
newNorm !== null &&
|
||||||
|
!isNaN(Number(oldNorm)) &&
|
||||||
|
!isNaN(Number(newNorm)) &&
|
||||||
|
Number(oldNorm) === Number(newNorm)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const isSelect = !!selectOptionsMap[key];
|
||||||
|
const oldDisplay = isSelect
|
||||||
|
? originalLabels.value[key] ?? resolveSelectLabel(key, oldNorm)
|
||||||
|
: oldNorm === null
|
||||||
|
? '空'
|
||||||
|
: String(oldNorm);
|
||||||
|
const newDisplay = isSelect
|
||||||
|
? resolveSelectLabel(key, newNorm)
|
||||||
|
: newNorm === null
|
||||||
|
? '空'
|
||||||
|
: String(newNorm);
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
field: key,
|
||||||
|
label: fieldLabelMap[key] || key,
|
||||||
|
oldValue: oldDisplay,
|
||||||
|
newValue: newDisplay
|
||||||
|
};
|
||||||
|
if (existingIdx >= 0) {
|
||||||
|
changeOrder.value[existingIdx] = entry;
|
||||||
|
} else {
|
||||||
|
changeOrder.value.push(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changeOrder.value = changeOrder.value.filter(c => c.field !== key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听 record 变化
|
||||||
|
watch(
|
||||||
|
() => props.record,
|
||||||
|
newRecord => {
|
||||||
|
if (newRecord) {
|
||||||
|
const converted = { ...newRecord };
|
||||||
|
// 将 "-" 还原为 null,避免控件显示异常
|
||||||
|
for (const key in converted) {
|
||||||
|
if (converted[key] === '-') {
|
||||||
|
converted[key] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 数字字段字符串转数字,保证 select/input-number 回显匹配
|
||||||
|
[
|
||||||
|
'sfdb',
|
||||||
|
'mwrSfdb',
|
||||||
|
'avqSfdb',
|
||||||
|
'qi',
|
||||||
|
'qec',
|
||||||
|
'qecLimit',
|
||||||
|
'mwrLimit',
|
||||||
|
'avqLimit'
|
||||||
|
].forEach(key => {
|
||||||
|
if (
|
||||||
|
converted[key] !== null &&
|
||||||
|
converted[key] !== undefined &&
|
||||||
|
converted[key] !== ''
|
||||||
|
) {
|
||||||
|
converted[key] = Number(converted[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
originalRecord.value = { ...converted };
|
||||||
|
formData.value = { ...converted };
|
||||||
|
changeOrder.value = [];
|
||||||
|
// 提前解析 select 字段的原始显示名
|
||||||
|
const labels: Record<string, string> = {};
|
||||||
|
for (const key in converted) {
|
||||||
|
if (selectOptionsMap[key]) {
|
||||||
|
labels[key] = resolveSelectLabel(key, converted[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
originalLabels.value = labels;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validateFields();
|
||||||
|
if (changeOrder.value.length === 0) {
|
||||||
|
message.info('未检测到任何修改');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
confirmModalVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间字段选择时仅到年月日,提交时统一补上时分秒
|
||||||
|
const formatDateTime = (val: any): any => {
|
||||||
|
if (val === null || val === undefined || val === '') return val;
|
||||||
|
const str = String(val);
|
||||||
|
return str.length === 10 ? `${str} 00:00:00` : str;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSubmit = async (source: string) => {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
// 与 EditWaterDataModal 保持一致:updateData 传完整表单,source 传修改依据
|
||||||
|
const updateData: Record<string, any> = { ...formData.value };
|
||||||
|
if (updateData.tm) {
|
||||||
|
updateData.tm = formatDateTime(updateData.tm);
|
||||||
|
}
|
||||||
|
const res = await updateEqRsData({
|
||||||
|
updateData,
|
||||||
|
source: source.trim()
|
||||||
|
});
|
||||||
|
if (res?.code == 0 || res?.success) {
|
||||||
|
message.success('编辑成功');
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
visible.value = false;
|
||||||
|
emit('success');
|
||||||
|
} else {
|
||||||
|
message.error(res?.msg || '编辑失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('提交失败,请重试');
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmCancel = () => {
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -63,6 +63,14 @@
|
|||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="showBatchDelete"
|
||||||
|
danger
|
||||||
|
:disabled="!selectedCount"
|
||||||
|
@click="batchDelete"
|
||||||
|
>批量删除</a-button
|
||||||
|
>
|
||||||
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
||||||
</template>
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
@ -78,13 +86,22 @@ const emit = defineEmits<{
|
|||||||
(e: 'export-btn'): void;
|
(e: 'export-btn'): void;
|
||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
|
(e: 'seeEdit'): void;
|
||||||
|
(e: 'batch-delete'): void;
|
||||||
}>();
|
}>();
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
exportLoading?: boolean;
|
exportLoading?: boolean;
|
||||||
}>();
|
selectedCount?: number;
|
||||||
|
showBatchDelete?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showBatchDelete: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
@ -185,6 +202,12 @@ const onSearchFinish = (values: any) => {
|
|||||||
const exportBtn = () => {
|
const exportBtn = () => {
|
||||||
emit('export-btn');
|
emit('export-btn');
|
||||||
};
|
};
|
||||||
|
const seeEdit = () => {
|
||||||
|
emit('seeEdit');
|
||||||
|
};
|
||||||
|
const batchDelete = () => {
|
||||||
|
emit('batch-delete');
|
||||||
|
};
|
||||||
|
|
||||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
handleValuesChange(changedValues, allValues);
|
handleValuesChange(changedValues, allValues);
|
||||||
|
|||||||
@ -5,6 +5,10 @@
|
|||||||
@export-btn="exportBtn"
|
@export-btn="exportBtn"
|
||||||
@search-finish="onSearchFinish"
|
@search-finish="onSearchFinish"
|
||||||
@reset="onReset"
|
@reset="onReset"
|
||||||
|
@seeEdit="handleSeeEdit"
|
||||||
|
@batch-delete="handleBatchDelete"
|
||||||
|
:show-batch-delete="currentSearchParams.timeScale === 'tm'"
|
||||||
|
:selected-count="selectedRowKeys.length"
|
||||||
:exportLoading="exportLoading"
|
:exportLoading="exportLoading"
|
||||||
ref="searchRef"
|
ref="searchRef"
|
||||||
/>
|
/>
|
||||||
@ -12,6 +16,7 @@
|
|||||||
<!-- 表格组件 -->
|
<!-- 表格组件 -->
|
||||||
<BasicTable
|
<BasicTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
|
:enable-row-selection="currentSearchParams.timeScale === 'tm'"
|
||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:scrollY="tableScrollY"
|
:scrollY="tableScrollY"
|
||||||
:columns="displayColumns"
|
:columns="displayColumns"
|
||||||
@ -19,20 +24,65 @@
|
|||||||
:searchParams="{
|
:searchParams="{
|
||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="text-[#2f6b98]"
|
||||||
|
size="small"
|
||||||
|
@click="handleEdit(record)"
|
||||||
|
>编辑</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
@click="handleDelete(record)"
|
||||||
|
>删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
|
|
||||||
|
<!-- 编辑生态流量数据 Modal -->
|
||||||
|
<EditFlowDataModal
|
||||||
|
v-model:open="editVisible"
|
||||||
|
:record="editRecord"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 删除确认 Modal -->
|
||||||
|
<DeleteConfirmModal
|
||||||
|
ref="deleteModalRef"
|
||||||
|
:delete-fn="handleDeleteFn"
|
||||||
|
:label="deleteLabel"
|
||||||
|
:title="'删除' + deleteLabel"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 操作日志弹框 -->
|
||||||
|
<OperationLogModal
|
||||||
|
v-model:open="operationLogVisible"
|
||||||
|
:table-name="operationTableName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import FlowDataSearch from './FlowDataSearch.vue';
|
import FlowDataSearch from './FlowDataSearch.vue';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import EditFlowDataModal from './EditFlowDataModal.vue';
|
||||||
|
import DeleteConfirmModal from '@/views/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
||||||
|
import { Tooltip, message } from 'ant-design-vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import {
|
import {
|
||||||
getFlowDataList,
|
getFlowDataList,
|
||||||
getFlowDataDayList,
|
getFlowDataDayList,
|
||||||
getFlowDataMonthList
|
getFlowDataMonthList,
|
||||||
|
deleteFlowDataInfo
|
||||||
} from '@/api/DataQueryMenuModule';
|
} from '@/api/DataQueryMenuModule';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
||||||
@ -62,6 +112,23 @@ const searchRef = ref();
|
|||||||
const tableScrollY = ref<string | number>(0);
|
const tableScrollY = ref<string | number>(0);
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
|
|
||||||
|
// 编辑/删除相关
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const editRecord = ref<any>(null);
|
||||||
|
const deleteModalRef = ref();
|
||||||
|
const operationLogVisible = ref(false);
|
||||||
|
const deleteLabel = '生态流量数据';
|
||||||
|
// TODO: 操作日志表名待确认
|
||||||
|
const operationTableName = 'SD_QEC_R';
|
||||||
|
|
||||||
|
// 多选相关
|
||||||
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
|
const selectedRows = ref<any[]>([]);
|
||||||
|
const handleSelectionChange = (keys: string[], rows: any[]) => {
|
||||||
|
selectedRowKeys.value = keys;
|
||||||
|
selectedRows.value = rows;
|
||||||
|
};
|
||||||
|
|
||||||
// 根据 timeScale 动态切换 API
|
// 根据 timeScale 动态切换 API
|
||||||
const currentListUrl = computed(() => {
|
const currentListUrl = computed(() => {
|
||||||
const timeScale = currentSearchParams.value.timeScale;
|
const timeScale = currentSearchParams.value.timeScale;
|
||||||
@ -359,18 +426,37 @@ const ruleColumnsMap: any = {
|
|||||||
const displayColumns = computed(() => {
|
const displayColumns = computed(() => {
|
||||||
const ruleType = currentSearchParams.value.ruleType ?? '';
|
const ruleType = currentSearchParams.value.ruleType ?? '';
|
||||||
const ruleColumns = ruleColumnsMap[ruleType] || ruleColumnsMap[''];
|
const ruleColumns = ruleColumnsMap[ruleType] || ruleColumnsMap[''];
|
||||||
return [...baseColumns, ...ruleColumns];
|
const columns: any[] = [...baseColumns, ...ruleColumns];
|
||||||
|
// 仅 timeScale === 'tm' 时显示操作列
|
||||||
|
if (currentSearchParams.value.timeScale === 'tm') {
|
||||||
|
columns.push({
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSearchFinish = async (values: any) => {
|
const onSearchFinish = async (values: any) => {
|
||||||
// debugger
|
// debugger
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
|
// 切换查询条件时清空选中
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initTable(values);
|
initTable(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReset = async (values: any) => {
|
const onReset = async (values: any) => {
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
|
// 切换查询条件时清空选中
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initTable(values);
|
initTable(values);
|
||||||
};
|
};
|
||||||
@ -456,6 +542,71 @@ const initTable = (values: any) => {
|
|||||||
tableRef.value.getList(params);
|
tableRef.value.getList(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (record: any) => {
|
||||||
|
editRecord.value = { ...record };
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑成功回调
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除(兼容单条:record 本身;批量:record.records 数组),仅小时(tm)数据支持删除
|
||||||
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
const list = Array.isArray(record.records) ? record.records : [record];
|
||||||
|
const dataList = list.map((r: any) => ({
|
||||||
|
id: r.stcd,
|
||||||
|
tm: r.tm
|
||||||
|
}));
|
||||||
|
return deleteFlowDataInfo({
|
||||||
|
dataList,
|
||||||
|
dataType: 'TIME',
|
||||||
|
source: reason
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除后刷新:清空选中(让批量删除按钮回到禁用状态)并重新加载列表
|
||||||
|
const refreshAfterDelete = () => {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
deleteModalRef.value?.open(record, () => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (currentSearchParams.value.timeScale !== 'tm') {
|
||||||
|
message.info('仅小时数据支持删除');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedRowKeys.value.length === 0) {
|
||||||
|
message.info('请先勾选要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteModalRef.value?.open(
|
||||||
|
{
|
||||||
|
records: [...selectedRows.value],
|
||||||
|
stnm: `选中的 ${selectedRowKeys.value.length} 条数据`
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看修改(操作日志)
|
||||||
|
const handleSeeEdit = () => {
|
||||||
|
operationLogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
tableScrollY.value = calcTableScrollY();
|
tableScrollY.value = calcTableScrollY();
|
||||||
|
|||||||
@ -61,6 +61,14 @@
|
|||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="showBatchDelete"
|
||||||
|
danger
|
||||||
|
:disabled="!selectedCount"
|
||||||
|
@click="batchDelete"
|
||||||
|
>批量删除</a-button
|
||||||
|
>
|
||||||
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
||||||
</template>
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
@ -77,14 +85,23 @@ const emit = defineEmits<{
|
|||||||
(e: 'export-btn'): void;
|
(e: 'export-btn'): void;
|
||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
|
(e: 'seeEdit'): void;
|
||||||
|
(e: 'batch-delete'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
exportLoading?: boolean;
|
exportLoading?: boolean;
|
||||||
}>();
|
selectedCount?: number;
|
||||||
|
showBatchDelete?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showBatchDelete: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
@ -184,6 +201,12 @@ const onSearchFinish = (values: any) => {
|
|||||||
const exportBtn = () => {
|
const exportBtn = () => {
|
||||||
emit('export-btn');
|
emit('export-btn');
|
||||||
};
|
};
|
||||||
|
const seeEdit = () => {
|
||||||
|
emit('seeEdit');
|
||||||
|
};
|
||||||
|
const batchDelete = () => {
|
||||||
|
emit('batch-delete');
|
||||||
|
};
|
||||||
const initCrossSectionList = params => {
|
const initCrossSectionList = params => {
|
||||||
const filters = [
|
const filters = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,12 +5,18 @@
|
|||||||
@export-btn="exportBtn"
|
@export-btn="exportBtn"
|
||||||
@search-finish="onSearchFinish"
|
@search-finish="onSearchFinish"
|
||||||
@reset="onReset"
|
@reset="onReset"
|
||||||
|
@seeEdit="handleSeeEdit"
|
||||||
|
@batch-delete="handleBatchDelete"
|
||||||
|
:show-batch-delete="currentSearchParams.timeScale === 'tm'"
|
||||||
|
:selected-count="selectedRowKeys.length"
|
||||||
:exportLoading="exportLoading"
|
:exportLoading="exportLoading"
|
||||||
ref="searchRef"
|
ref="searchRef"
|
||||||
/>
|
/>
|
||||||
<!-- 表格组件 -->
|
<!-- 表格组件 -->
|
||||||
<BasicTable
|
<BasicTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
|
:enable-row-selection="currentSearchParams.timeScale === 'tm'"
|
||||||
|
:row-key="getRowKey"
|
||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:scrollY="tableScrollY"
|
:scrollY="tableScrollY"
|
||||||
:columns="Columns"
|
:columns="Columns"
|
||||||
@ -18,6 +24,7 @@
|
|||||||
:searchParams="{
|
:searchParams="{
|
||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<template v-if="currentSearchParams.timeScale === 'tm'">
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
||||||
@ -53,6 +60,12 @@
|
|||||||
label="数据"
|
label="数据"
|
||||||
@success="handleEditSuccess"
|
@success="handleEditSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 操作日志弹框 -->
|
||||||
|
<OperationLogModal
|
||||||
|
v-model:open="operationLogVisible"
|
||||||
|
:table-name="operationTableName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -61,7 +74,9 @@ import dayjs from 'dayjs';
|
|||||||
import FlowStationSearch from './FlowStationSearch.vue';
|
import FlowStationSearch from './FlowStationSearch.vue';
|
||||||
import EditFlowStationModal from './EditFlowStationModal.vue';
|
import EditFlowStationModal from './EditFlowStationModal.vue';
|
||||||
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
getFlowStationList,
|
getFlowStationList,
|
||||||
getFlowStationDayList,
|
getFlowStationDayList,
|
||||||
@ -108,6 +123,17 @@ const exportLoading = ref(false);
|
|||||||
const editVisible = ref(false);
|
const editVisible = ref(false);
|
||||||
const editRecord = ref<any>(null);
|
const editRecord = ref<any>(null);
|
||||||
const deleteModalRef = ref();
|
const deleteModalRef = ref();
|
||||||
|
const operationLogVisible = ref(false);
|
||||||
|
const operationTableName = 'SD_RIVER_R';
|
||||||
|
|
||||||
|
// 多选相关(唯一标识 stcd+tm,日/月模式数据用 dt)
|
||||||
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
|
const selectedRows = ref<any[]>([]);
|
||||||
|
const getRowKey = (record: any) => `${record.stcd}_${record.tm ?? record.dt}`;
|
||||||
|
const handleSelectionChange = (keys: string[], rows: any[]) => {
|
||||||
|
selectedRowKeys.value = keys;
|
||||||
|
selectedRows.value = rows;
|
||||||
|
};
|
||||||
|
|
||||||
// 根据 timeScale 动态切换 API
|
// 根据 timeScale 动态切换 API
|
||||||
const currentListUrl = computed(() => {
|
const currentListUrl = computed(() => {
|
||||||
@ -230,6 +256,10 @@ const Columns = computed(() => {
|
|||||||
|
|
||||||
const onSearchFinish = async (values: any) => {
|
const onSearchFinish = async (values: any) => {
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
|
// 切换查询条件时清空选中
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
// 等待 prop 更新后再调用 getList
|
// 等待 prop 更新后再调用 getList
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initTable(values);
|
initTable(values);
|
||||||
@ -237,6 +267,10 @@ const onSearchFinish = async (values: any) => {
|
|||||||
|
|
||||||
const onReset = async (values: any) => {
|
const onReset = async (values: any) => {
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
|
// 切换查询条件时清空选中
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initTable(values);
|
initTable(values);
|
||||||
};
|
};
|
||||||
@ -262,25 +296,60 @@ const handleEdit = (record: any) => {
|
|||||||
editVisible.value = true;
|
editVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除(兼容单条:record 本身;批量:record.records 数组)
|
||||||
const handleDeleteFn = async (record: any, reason: string) => {
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
const list = Array.isArray(record.records) ? record.records : [record];
|
||||||
|
const dataList = list.map((r: any) => ({
|
||||||
|
id: r.stcd,
|
||||||
|
tm: r.tm
|
||||||
|
}));
|
||||||
return deleteFlowStationInfo({
|
return deleteFlowStationInfo({
|
||||||
dataList: [
|
dataList,
|
||||||
{
|
|
||||||
id: record.stcd,
|
|
||||||
tm: record.tm
|
|
||||||
}
|
|
||||||
],
|
|
||||||
dataType: 'TIME',
|
dataType: 'TIME',
|
||||||
source: reason
|
source: reason
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除后刷新:清空选中(让批量删除按钮回到禁用状态)并重新加载列表
|
||||||
|
const refreshAfterDelete = () => {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = (record: any) => {
|
const handleDelete = (record: any) => {
|
||||||
deleteModalRef.value?.open(record, () => {
|
deleteModalRef.value?.open(record, () => {
|
||||||
initTable(currentSearchParams.value);
|
refreshAfterDelete();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 批量删除(仅小时 tm 模式,与单条删除一致)
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (currentSearchParams.value.timeScale !== 'tm') {
|
||||||
|
message.info('仅小时数据支持删除');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedRowKeys.value.length === 0) {
|
||||||
|
message.info('请先勾选要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteModalRef.value?.open(
|
||||||
|
{
|
||||||
|
records: [...selectedRows.value],
|
||||||
|
stnm: `选中的 ${selectedRowKeys.value.length} 条数据`
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看修改(操作日志)
|
||||||
|
const handleSeeEdit = () => {
|
||||||
|
operationLogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
const handleEditSuccess = () => {
|
const handleEditSuccess = () => {
|
||||||
initTable(currentSearchParams.value);
|
initTable(currentSearchParams.value);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -61,6 +61,14 @@
|
|||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="showBatchDelete"
|
||||||
|
danger
|
||||||
|
:disabled="!selectedCount"
|
||||||
|
@click="batchDelete"
|
||||||
|
>批量删除</a-button
|
||||||
|
>
|
||||||
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
||||||
</template>
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
@ -77,14 +85,23 @@ const emit = defineEmits<{
|
|||||||
(e: 'export-btn'): void;
|
(e: 'export-btn'): void;
|
||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
|
(e: 'seeEdit'): void;
|
||||||
|
(e: 'batch-delete'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
exportLoading?: boolean;
|
exportLoading?: boolean;
|
||||||
}>();
|
selectedCount?: number;
|
||||||
|
showBatchDelete?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showBatchDelete: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
@ -184,6 +201,12 @@ const onSearchFinish = (values: any) => {
|
|||||||
const exportBtn = () => {
|
const exportBtn = () => {
|
||||||
emit('export-btn');
|
emit('export-btn');
|
||||||
};
|
};
|
||||||
|
const seeEdit = () => {
|
||||||
|
emit('seeEdit');
|
||||||
|
};
|
||||||
|
const batchDelete = () => {
|
||||||
|
emit('batch-delete');
|
||||||
|
};
|
||||||
const initCrossSectionList = params => {
|
const initCrossSectionList = params => {
|
||||||
const filters = [
|
const filters = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,12 +5,18 @@
|
|||||||
@export-btn="exportBtn"
|
@export-btn="exportBtn"
|
||||||
@search-finish="onSearchFinish"
|
@search-finish="onSearchFinish"
|
||||||
@reset="onReset"
|
@reset="onReset"
|
||||||
|
@seeEdit="handleSeeEdit"
|
||||||
|
@batch-delete="handleBatchDelete"
|
||||||
|
:show-batch-delete="currentSearchParams.timeScale === 'tm'"
|
||||||
|
:selected-count="selectedRowKeys.length"
|
||||||
:exportLoading="exportLoading"
|
:exportLoading="exportLoading"
|
||||||
ref="searchRef"
|
ref="searchRef"
|
||||||
/>
|
/>
|
||||||
<!-- 表格组件 -->
|
<!-- 表格组件 -->
|
||||||
<BasicTable
|
<BasicTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
|
:enable-row-selection="currentSearchParams.timeScale === 'tm'"
|
||||||
|
:row-key="getRowKey"
|
||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:scrollY="tableScrollY"
|
:scrollY="tableScrollY"
|
||||||
:columns="Columns"
|
:columns="Columns"
|
||||||
@ -18,6 +24,7 @@
|
|||||||
:searchParams="{
|
:searchParams="{
|
||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<template v-if="currentSearchParams.timeScale === 'tm'">
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
||||||
@ -53,6 +60,12 @@
|
|||||||
label="数据"
|
label="数据"
|
||||||
@success="handleEditSuccess"
|
@success="handleEditSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 操作日志弹框 -->
|
||||||
|
<OperationLogModal
|
||||||
|
v-model:open="operationLogVisible"
|
||||||
|
:table-name="operationTableName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -61,7 +74,9 @@ import dayjs from 'dayjs';
|
|||||||
import SurfaceTempSearch from './SurfaceTempSearch.vue';
|
import SurfaceTempSearch from './SurfaceTempSearch.vue';
|
||||||
import EditSurfaceTempModal from './EditSurfaceTempModal.vue';
|
import EditSurfaceTempModal from './EditSurfaceTempModal.vue';
|
||||||
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
getSurfaceTempList,
|
getSurfaceTempList,
|
||||||
getSurfaceTempDayList,
|
getSurfaceTempDayList,
|
||||||
@ -106,6 +121,17 @@ const exportLoading = ref(false);
|
|||||||
const editVisible = ref(false);
|
const editVisible = ref(false);
|
||||||
const editRecord = ref<any>(null);
|
const editRecord = ref<any>(null);
|
||||||
const deleteModalRef = ref();
|
const deleteModalRef = ref();
|
||||||
|
const operationLogVisible = ref(false);
|
||||||
|
const operationTableName = 'SD_WTRV_R';
|
||||||
|
|
||||||
|
// 多选相关(唯一标识 stcd+tm,日/月模式数据用 dt)
|
||||||
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
|
const selectedRows = ref<any[]>([]);
|
||||||
|
const getRowKey = (record: any) => `${record.stcd}_${record.tm ?? record.dt}`;
|
||||||
|
const handleSelectionChange = (keys: string[], rows: any[]) => {
|
||||||
|
selectedRowKeys.value = keys;
|
||||||
|
selectedRows.value = rows;
|
||||||
|
};
|
||||||
|
|
||||||
// 根据 timeScale 动态切换 API
|
// 根据 timeScale 动态切换 API
|
||||||
const currentListUrl = computed(() => {
|
const currentListUrl = computed(() => {
|
||||||
@ -194,6 +220,10 @@ const Columns = computed(() => {
|
|||||||
|
|
||||||
const onSearchFinish = async (values: any) => {
|
const onSearchFinish = async (values: any) => {
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
|
// 切换查询条件时清空选中
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
// 等待 prop 更新后再调用 getList
|
// 等待 prop 更新后再调用 getList
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initTable(values);
|
initTable(values);
|
||||||
@ -201,6 +231,10 @@ const onSearchFinish = async (values: any) => {
|
|||||||
|
|
||||||
const onReset = async (values: any) => {
|
const onReset = async (values: any) => {
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
|
// 切换查询条件时清空选中
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initTable(values);
|
initTable(values);
|
||||||
};
|
};
|
||||||
@ -226,25 +260,60 @@ const handleEdit = (record: any) => {
|
|||||||
editVisible.value = true;
|
editVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除(兼容单条:record 本身;批量:record.records 数组)
|
||||||
const handleDeleteFn = async (record: any, reason: string) => {
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
const list = Array.isArray(record.records) ? record.records : [record];
|
||||||
|
const dataList = list.map((r: any) => ({
|
||||||
|
id: r.stcd,
|
||||||
|
dt: r.tm
|
||||||
|
}));
|
||||||
return deleteSurfaceTempInfo({
|
return deleteSurfaceTempInfo({
|
||||||
dataList: [
|
dataList,
|
||||||
{
|
|
||||||
id: record.stcd,
|
|
||||||
dt: record.tm
|
|
||||||
}
|
|
||||||
],
|
|
||||||
dataType: 'TIME',
|
dataType: 'TIME',
|
||||||
source: reason
|
source: reason
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除后刷新:清空选中(让批量删除按钮回到禁用状态)并重新加载列表
|
||||||
|
const refreshAfterDelete = () => {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = (record: any) => {
|
const handleDelete = (record: any) => {
|
||||||
deleteModalRef.value?.open(record, () => {
|
deleteModalRef.value?.open(record, () => {
|
||||||
initTable(currentSearchParams.value);
|
refreshAfterDelete();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 批量删除(仅小时 tm 模式,与单条删除一致)
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (currentSearchParams.value.timeScale !== 'tm') {
|
||||||
|
message.info('仅小时数据支持删除');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedRowKeys.value.length === 0) {
|
||||||
|
message.info('请先勾选要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteModalRef.value?.open(
|
||||||
|
{
|
||||||
|
records: [...selectedRows.value],
|
||||||
|
stnm: `选中的 ${selectedRowKeys.value.length} 条数据`
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看修改(操作日志)
|
||||||
|
const handleSeeEdit = () => {
|
||||||
|
operationLogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
const handleEditSuccess = () => {
|
const handleEditSuccess = () => {
|
||||||
initTable(currentSearchParams.value);
|
initTable(currentSearchParams.value);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -63,6 +63,10 @@
|
|||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
||||||
|
<a-button danger :disabled="!selectedCount" @click="batchDelete"
|
||||||
|
>批量删除</a-button
|
||||||
|
>
|
||||||
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
||||||
</template>
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
@ -79,6 +83,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'export-btn'): void;
|
(e: 'export-btn'): void;
|
||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
|
(e: 'seeEdit'): void;
|
||||||
|
(e: 'batch-delete'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
@ -86,6 +92,7 @@ const btnLoading = ref<boolean>(false);
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
exportLoading?: boolean;
|
exportLoading?: boolean;
|
||||||
|
selectedCount?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
@ -186,6 +193,12 @@ const onSearchFinish = (values: any) => {
|
|||||||
const exportBtn = () => {
|
const exportBtn = () => {
|
||||||
emit('export-btn');
|
emit('export-btn');
|
||||||
};
|
};
|
||||||
|
const seeEdit = () => {
|
||||||
|
emit('seeEdit');
|
||||||
|
};
|
||||||
|
const batchDelete = () => {
|
||||||
|
emit('batch-delete');
|
||||||
|
};
|
||||||
const initCrossSectionList = params => {
|
const initCrossSectionList = params => {
|
||||||
const filters = [
|
const filters = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
@export-btn="exportBtn"
|
@export-btn="exportBtn"
|
||||||
@search-finish="onSearchFinish"
|
@search-finish="onSearchFinish"
|
||||||
@reset="onReset"
|
@reset="onReset"
|
||||||
|
@seeEdit="handleSeeEdit"
|
||||||
|
@batch-delete="handleBatchDelete"
|
||||||
|
:selected-count="currentSearchParams.timeScale === 'tm' ? selectedRowsForChart.length : 0"
|
||||||
:exportLoading="exportLoading"
|
:exportLoading="exportLoading"
|
||||||
ref="searchRef"
|
ref="searchRef"
|
||||||
/>
|
/>
|
||||||
@ -96,6 +99,12 @@
|
|||||||
label="数据"
|
label="数据"
|
||||||
@success="handleEditSuccess"
|
@success="handleEditSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 操作日志弹框 -->
|
||||||
|
<OperationLogModal
|
||||||
|
v-model:open="operationLogVisible"
|
||||||
|
:table-name="operationTableName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -113,7 +122,9 @@ import * as echarts from 'echarts';
|
|||||||
import VerticalTempSearch from './VerticalTempSearch.vue';
|
import VerticalTempSearch from './VerticalTempSearch.vue';
|
||||||
import EditVerticalTempModal from './EditVerticalTempModal.vue';
|
import EditVerticalTempModal from './EditVerticalTempModal.vue';
|
||||||
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
import { getVerticalList, deleteVerticalInfo } from '@/api/DataQueryMenuModule';
|
import { getVerticalList, deleteVerticalInfo } from '@/api/DataQueryMenuModule';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
|
|
||||||
@ -132,6 +143,8 @@ const tableData = ref<any[]>([]);
|
|||||||
const editVisible = ref(false);
|
const editVisible = ref(false);
|
||||||
const editRecord = ref<any>(null);
|
const editRecord = ref<any>(null);
|
||||||
const deleteModalRef = ref();
|
const deleteModalRef = ref();
|
||||||
|
const operationLogVisible = ref(false);
|
||||||
|
const operationTableName = 'SD_WTVT_R';
|
||||||
|
|
||||||
// 图表相关
|
// 图表相关
|
||||||
const chartRef = ref<HTMLElement>();
|
const chartRef = ref<HTMLElement>();
|
||||||
@ -439,25 +452,59 @@ const handleEdit = (record: any) => {
|
|||||||
editVisible.value = true;
|
editVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除(兼容单条:record 本身;批量:record.records 数组)
|
||||||
const handleDeleteFn = async (record: any, reason: string) => {
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
const list = Array.isArray(record.records) ? record.records : [record];
|
||||||
|
const dataList = list.map((r: any) => ({
|
||||||
|
id: r.stcd ?? r.id,
|
||||||
|
tm: r.tm ?? r.dt
|
||||||
|
}));
|
||||||
return deleteVerticalInfo({
|
return deleteVerticalInfo({
|
||||||
dataList: [
|
dataList,
|
||||||
{
|
|
||||||
id: record.stcd ?? record.id,
|
|
||||||
tm: record.tm ?? record.dt
|
|
||||||
}
|
|
||||||
],
|
|
||||||
dataType: 'TIME',
|
dataType: 'TIME',
|
||||||
source: reason
|
source: reason
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除后刷新:清空选中(让批量删除按钮回到禁用状态)并重新加载列表
|
||||||
|
const refreshAfterDelete = () => {
|
||||||
|
selectedRowsForChart.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = (record: any) => {
|
const handleDelete = (record: any) => {
|
||||||
deleteModalRef.value?.open(record, () => {
|
deleteModalRef.value?.open(record, () => {
|
||||||
initTable(currentSearchParams.value);
|
refreshAfterDelete();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 批量删除(仅小时 tm 模式,与单条删除一致)
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (currentSearchParams.value.timeScale !== 'tm') {
|
||||||
|
message.info('仅小时数据支持删除');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedRowsForChart.value.length === 0) {
|
||||||
|
message.info('请先勾选要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteModalRef.value?.open(
|
||||||
|
{
|
||||||
|
records: [...selectedRowsForChart.value],
|
||||||
|
stnm: `选中的 ${selectedRowsForChart.value.length} 条数据`
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看修改(操作日志)
|
||||||
|
const handleSeeEdit = () => {
|
||||||
|
operationLogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
const handleEditSuccess = () => {
|
const handleEditSuccess = () => {
|
||||||
initTable(currentSearchParams.value);
|
initTable(currentSearchParams.value);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -63,6 +63,14 @@
|
|||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="showBatchDelete"
|
||||||
|
danger
|
||||||
|
:disabled="!selectedCount"
|
||||||
|
@click="batchDelete"
|
||||||
|
>批量删除</a-button
|
||||||
|
>
|
||||||
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
||||||
</template>
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
@ -79,13 +87,22 @@ const emit = defineEmits<{
|
|||||||
(e: 'export-btn'): void;
|
(e: 'export-btn'): void;
|
||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
|
(e: 'seeEdit'): void;
|
||||||
|
(e: 'batch-delete'): void;
|
||||||
}>();
|
}>();
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
exportLoading?: boolean;
|
exportLoading?: boolean;
|
||||||
}>();
|
selectedCount?: number;
|
||||||
|
showBatchDelete?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showBatchDelete: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
@ -201,6 +218,12 @@ const onSearchFinish = (values: any) => {
|
|||||||
const exportBtn = () => {
|
const exportBtn = () => {
|
||||||
emit('export-btn');
|
emit('export-btn');
|
||||||
};
|
};
|
||||||
|
const seeEdit = () => {
|
||||||
|
emit('seeEdit');
|
||||||
|
};
|
||||||
|
const batchDelete = () => {
|
||||||
|
emit('batch-delete');
|
||||||
|
};
|
||||||
const initCrossSectionList = params => {
|
const initCrossSectionList = params => {
|
||||||
const filters = [
|
const filters = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,6 +5,10 @@
|
|||||||
@export-btn="exportBtn"
|
@export-btn="exportBtn"
|
||||||
@search-finish="onSearchFinish"
|
@search-finish="onSearchFinish"
|
||||||
@reset="onReset"
|
@reset="onReset"
|
||||||
|
@seeEdit="handleSeeEdit"
|
||||||
|
@batch-delete="handleBatchDelete"
|
||||||
|
:show-batch-delete="currentSearchParams.timeScale === 'tm'"
|
||||||
|
:selected-count="selectedRowKeys.length"
|
||||||
:exportLoading="exportLoading"
|
:exportLoading="exportLoading"
|
||||||
ref="searchRef"
|
ref="searchRef"
|
||||||
/>
|
/>
|
||||||
@ -12,6 +16,8 @@
|
|||||||
<!-- 表格组件 -->
|
<!-- 表格组件 -->
|
||||||
<BasicTable
|
<BasicTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
|
:enable-row-selection="currentSearchParams.timeScale === 'tm'"
|
||||||
|
row-key="stcd"
|
||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:scrollY="tableScrollY"
|
:scrollY="tableScrollY"
|
||||||
:columns="Columns"
|
:columns="Columns"
|
||||||
@ -19,6 +25,7 @@
|
|||||||
:searchParams="{
|
:searchParams="{
|
||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<template v-if="currentSearchParams.timeScale === 'tm'">
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
||||||
@ -55,6 +62,12 @@
|
|||||||
label="数据"
|
label="数据"
|
||||||
@success="handleEditSuccess"
|
@success="handleEditSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 操作日志弹框 -->
|
||||||
|
<OperationLogModal
|
||||||
|
v-model:open="operationLogVisible"
|
||||||
|
:table-name="operationTableName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -63,7 +76,8 @@ import dayjs from 'dayjs';
|
|||||||
import WaterDataSearch from './WaterDataSearch.vue';
|
import WaterDataSearch from './WaterDataSearch.vue';
|
||||||
import EditWaterDataModal from './EditWaterDataModal.vue';
|
import EditWaterDataModal from './EditWaterDataModal.vue';
|
||||||
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
||||||
|
import { Tooltip, message } from 'ant-design-vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import {
|
import {
|
||||||
getWaterDataList,
|
getWaterDataList,
|
||||||
@ -111,6 +125,16 @@ const exportLoading = ref(false);
|
|||||||
const editVisible = ref(false);
|
const editVisible = ref(false);
|
||||||
const editRecord = ref<any>(null);
|
const editRecord = ref<any>(null);
|
||||||
const deleteModalRef = ref();
|
const deleteModalRef = ref();
|
||||||
|
const operationLogVisible = ref(false);
|
||||||
|
const operationTableName = 'SD_WQ_R';
|
||||||
|
|
||||||
|
// 多选相关
|
||||||
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
|
const selectedRows = ref<any[]>([]);
|
||||||
|
const handleSelectionChange = (keys: string[], rows: any[]) => {
|
||||||
|
selectedRowKeys.value = keys;
|
||||||
|
selectedRows.value = rows;
|
||||||
|
};
|
||||||
|
|
||||||
// 根据 timeScale 动态切换 API
|
// 根据 timeScale 动态切换 API
|
||||||
const currentListUrl = computed(() => {
|
const currentListUrl = computed(() => {
|
||||||
@ -340,12 +364,20 @@ const Columns = computed(() => {
|
|||||||
|
|
||||||
const onSearchFinish = async (values: any) => {
|
const onSearchFinish = async (values: any) => {
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
|
// 切换查询条件时清空选中
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initTable(values);
|
initTable(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReset = async (values: any) => {
|
const onReset = async (values: any) => {
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
|
// 切换查询条件时清空选中
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initTable(values);
|
initTable(values);
|
||||||
};
|
};
|
||||||
@ -416,32 +448,65 @@ const handleEdit = (record: any) => {
|
|||||||
editVisible.value = true;
|
editVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除
|
// 删除(兼容单条:record 本身;批量:record.records 数组)
|
||||||
const handleDeleteFn = async (record: any, reason: string) => {
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
console.log(record, reason);
|
const list = Array.isArray(record.records) ? record.records : [record];
|
||||||
|
const dataList = list.map((r: any) => ({
|
||||||
|
id: r.stcd,
|
||||||
|
tm: r.tm
|
||||||
|
}));
|
||||||
return deleteWaterDataInfo({
|
return deleteWaterDataInfo({
|
||||||
dataList: [
|
dataList,
|
||||||
{
|
|
||||||
id: record.stcd,
|
|
||||||
tm: record.tm
|
|
||||||
}
|
|
||||||
],
|
|
||||||
dataType: 'TIME',
|
dataType: 'TIME',
|
||||||
source: reason
|
source: reason
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除后刷新:清空选中(让批量删除按钮回到禁用状态)并重新加载列表
|
||||||
|
const refreshAfterDelete = () => {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = (record: any) => {
|
const handleDelete = (record: any) => {
|
||||||
deleteModalRef.value?.open(record, () => {
|
deleteModalRef.value?.open(record, () => {
|
||||||
initTable(currentSearchParams.value);
|
refreshAfterDelete();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (currentSearchParams.value.timeScale !== 'tm') {
|
||||||
|
message.info('仅小时数据支持删除');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedRowKeys.value.length === 0) {
|
||||||
|
message.info('请先勾选要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteModalRef.value?.open(
|
||||||
|
{
|
||||||
|
records: [...selectedRows.value],
|
||||||
|
stnm: `选中的 ${selectedRowKeys.value.length} 条数据`
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 编辑/删除成功回调
|
// 编辑/删除成功回调
|
||||||
const handleEditSuccess = () => {
|
const handleEditSuccess = () => {
|
||||||
initTable(currentSearchParams.value);
|
initTable(currentSearchParams.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 查看修改(操作日志)
|
||||||
|
const handleSeeEdit = () => {
|
||||||
|
operationLogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
tableScrollY.value = calcTableScrollY();
|
tableScrollY.value = calcTableScrollY();
|
||||||
|
|||||||
@ -0,0 +1,841 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="编辑过鱼自动数据"
|
||||||
|
width="70vw"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:label-col="{ span: 8 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
:rules="formRules"
|
||||||
|
class="max-h-[70vh] overflow-y-auto pr-4"
|
||||||
|
>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="过鱼设施" name="stcd">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.stcd"
|
||||||
|
placeholder="请选择过鱼设施"
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
:loading="facilityLoading"
|
||||||
|
:filter-option="filterOption"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in facilityOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
>{{ item.label }}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="时间" name="tm">
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="formData.tm"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="请选择时间"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="鱼种类" name="ftp">
|
||||||
|
<fishSearch v-model="formData.ftp" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="过鱼数量" name="fcnt">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.fcnt"
|
||||||
|
placeholder="请输入过鱼数量"
|
||||||
|
:min="0"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="过鱼通道" name="channelno">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.channelno"
|
||||||
|
placeholder="请输入过鱼通道"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="鱼游向" name="direction">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.direction"
|
||||||
|
placeholder="请选择鱼游向"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
|
<a-select-option :value="0">上行</a-select-option>
|
||||||
|
<a-select-option :value="1">下行</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="鱼尺寸" name="fsz">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.fsz"
|
||||||
|
placeholder="请选择鱼尺寸"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
|
<a-select-option value="大">大</a-select-option>
|
||||||
|
<a-select-option value="中">中</a-select-option>
|
||||||
|
<a-select-option value="小">小</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="鱼长度(cm)" name="length">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.length"
|
||||||
|
placeholder="请输入鱼长度"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="鱼宽度(cm)" name="width">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.width"
|
||||||
|
placeholder="请输入鱼宽度"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="鱼速度(m/s)" name="fishspeed">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.fishspeed"
|
||||||
|
placeholder="请输入鱼速度"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="鱼位置" name="fishposition">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.fishposition"
|
||||||
|
placeholder="请输入鱼位置"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="水温(℃)" name="temperature">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.temperature"
|
||||||
|
placeholder="请输入水温"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="水位(m)" name="waterlevel">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.waterlevel"
|
||||||
|
placeholder="请输入水位"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="流速(m/s)" name="speed">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.speed"
|
||||||
|
placeholder="请输入流速"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="流量(m³/s)" name="q">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.q"
|
||||||
|
placeholder="请输入流量"
|
||||||
|
:precision="3"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="溶氧(mg/L)" name="dox">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.dox"
|
||||||
|
placeholder="请输入溶氧"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="浊度(NTU)" name="tu">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.tu"
|
||||||
|
placeholder="请输入浊度"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="备注" name="remark" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="formData.remark"
|
||||||
|
placeholder="请输入备注"
|
||||||
|
:maxlength="255"
|
||||||
|
show-count
|
||||||
|
:rows="2"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<!-- 图片上传 -->
|
||||||
|
<a-row :gutter="16" class="mt-4">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="图片" name="firstimgurl" :label-col="{ span: 4 }" :wrapper-col="{ span: 21 }">
|
||||||
|
<a-upload
|
||||||
|
v-model:file-list="picFileList"
|
||||||
|
:multiple="false"
|
||||||
|
:max-count="1"
|
||||||
|
list-type="picture-card"
|
||||||
|
:before-upload="handleBeforePicUpload"
|
||||||
|
@remove="handleRemove"
|
||||||
|
>
|
||||||
|
<div v-if="picFileList.length < 1">
|
||||||
|
<plus-outlined />
|
||||||
|
<div class="mt-2">上传图片</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<!-- 视频上传 -->
|
||||||
|
<a-row :gutter="16" class="mt-4">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="视频" name="videourl" :label-col="{ span: 4 }" :wrapper-col="{ span: 21 }">
|
||||||
|
<a-upload
|
||||||
|
v-model:file-list="vdpFileList"
|
||||||
|
:multiple="false"
|
||||||
|
:max-count="1"
|
||||||
|
:before-upload="handleBeforeVdpUpload"
|
||||||
|
@remove="handleRemove"
|
||||||
|
>
|
||||||
|
<a-button v-if="vdpFileList.length < 1">
|
||||||
|
<upload-outlined /> 上传视频
|
||||||
|
</a-button>
|
||||||
|
</a-upload>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
v-model:open="confirmModalVisible"
|
||||||
|
:diff-list="diffList"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@confirm="handleConfirmSubmit"
|
||||||
|
@cancel="handleConfirmCancel"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { message, Upload } from 'ant-design-vue';
|
||||||
|
import { PlusOutlined, UploadOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { updateFpssrlAiRInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
import { getFishReleaseMonitorSectionList } from '@/api/DataQueryMenuModule';
|
||||||
|
import ConfirmModal from '@/components/ConfirmModal/index.vue';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
import fishSearch from '@/components/fishSearch/index.vue';
|
||||||
|
import { useShuJuTianBaoStore } from '@/store/modules/shuJuTianBao';
|
||||||
|
|
||||||
|
const shuJuTianBaoStore = useShuJuTianBaoStore();
|
||||||
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
record?: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:open', 'success']);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: val => emit('update:open', val)
|
||||||
|
});
|
||||||
|
useDraggable(visible, { boundary: true, resetOnOpen: true });
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
const formData = ref<any>({});
|
||||||
|
const originalRecord = ref<any>({});
|
||||||
|
const originalLabels = ref<Record<string, string>>({});
|
||||||
|
const confirmModalVisible = ref(false);
|
||||||
|
|
||||||
|
// 过鱼设施下拉
|
||||||
|
const facilityLoading = ref(false);
|
||||||
|
const facilityOptions = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 图片上传相关
|
||||||
|
const picFileList = ref<any[]>([]);
|
||||||
|
// 视频上传相关
|
||||||
|
const vdpFileList = ref<any[]>([]);
|
||||||
|
|
||||||
|
const formRules = ref<any>({
|
||||||
|
stcd: [{ required: true, message: '请选择过鱼设施', trigger: 'change' }],
|
||||||
|
tm: [{ required: true, message: '请选择时间', trigger: 'change' }],
|
||||||
|
ftp: [{ required: true, message: '请选择鱼种类', trigger: 'blur' }],
|
||||||
|
fcnt: [{ required: true, message: '请输入过鱼数量', trigger: 'blur' }],
|
||||||
|
channelno: [{ required: true, message: '请输入过鱼通道', trigger: 'blur' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 字段名 → 中文名映射
|
||||||
|
const fieldLabelMap: Record<string, string> = {
|
||||||
|
stcd: '过鱼设施',
|
||||||
|
tm: '时间',
|
||||||
|
ftp: '鱼种类',
|
||||||
|
fcnt: '过鱼数量',
|
||||||
|
fsz: '鱼尺寸',
|
||||||
|
length: '鱼长度(cm)',
|
||||||
|
width: '鱼宽度(cm)',
|
||||||
|
fishspeed: '鱼速度(m/s)',
|
||||||
|
direction: '鱼游向',
|
||||||
|
fishposition: '鱼位置',
|
||||||
|
firstimgurl: '图片',
|
||||||
|
videourl: '视频',
|
||||||
|
temperature: '水温(℃)',
|
||||||
|
waterlevel: '水位(m)',
|
||||||
|
speed: '流速(m/s)',
|
||||||
|
q: '流量(m³/s)',
|
||||||
|
dox: '溶氧(mg/L)',
|
||||||
|
tu: '浊度(NTU)',
|
||||||
|
channelno: '过鱼通道',
|
||||||
|
remark: '备注'
|
||||||
|
};
|
||||||
|
|
||||||
|
// select 选项映射
|
||||||
|
const selectOptionsMap: Record<string, () => any[]> = {
|
||||||
|
stcd: () => facilityOptions.value,
|
||||||
|
ftp: () =>
|
||||||
|
shuJuTianBaoStore.getFishOption().map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id
|
||||||
|
})),
|
||||||
|
direction: () => [
|
||||||
|
{ label: '上行', value: 0 },
|
||||||
|
{ label: '下行', value: 1 }
|
||||||
|
],
|
||||||
|
fsz: () => [
|
||||||
|
{ label: '大', value: '大' },
|
||||||
|
{ label: '中', value: '中' },
|
||||||
|
{ label: '小', value: '小' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 按修改顺序记录变更
|
||||||
|
const changeOrder = ref<
|
||||||
|
{ field: string; label: string; oldValue: string; newValue: string }[]
|
||||||
|
>([]);
|
||||||
|
const diffList = changeOrder;
|
||||||
|
|
||||||
|
const filterOption = (inputValue: string, option: any) => {
|
||||||
|
const label = option.label || '';
|
||||||
|
const keyword = inputValue || '';
|
||||||
|
return label.includes(keyword);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBeforePicUpload = (file: any) => {
|
||||||
|
const isImage =
|
||||||
|
file.type === 'image/jpeg' ||
|
||||||
|
file.type === 'image/png' ||
|
||||||
|
file.type === 'image/gif' ||
|
||||||
|
file.type === 'image/webp';
|
||||||
|
if (!isImage) {
|
||||||
|
message.error('只能上传图片文件(JPG/PNG/GIF/WEBP)');
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||||
|
if (!isLt10M) {
|
||||||
|
message.error('图片大小不能超过 10MB');
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
picFileList.value = [...picFileList.value, file];
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBeforeVdpUpload = (file: any) => {
|
||||||
|
const isVideo =
|
||||||
|
file.type === 'video/mp4' ||
|
||||||
|
file.type === 'video/quicktime' ||
|
||||||
|
file.type === 'video/x-msvideo' ||
|
||||||
|
file.type === 'video/webm' ||
|
||||||
|
file.type === 'video/x-matroska';
|
||||||
|
if (!isVideo) {
|
||||||
|
message.error('只能上传视频文件(MP4/MOV/AVI/WEBM/MKV)');
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
const isLt100M = file.size / 1024 / 1024 < 100;
|
||||||
|
if (!isLt100M) {
|
||||||
|
message.error('视频大小不能超过 100MB');
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
vdpFileList.value = [...vdpFileList.value, file];
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析图片/视频 id 列表,过滤 null/-/undefined 等占位字符串
|
||||||
|
const normalizeIds = (val: any): string[] => {
|
||||||
|
if (val === null || val === undefined || val === '') return [];
|
||||||
|
return String(val)
|
||||||
|
.split(',')
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(s => s && s !== '-' && s !== 'null' && s !== 'undefined');
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveSelectLabel = (field: string, value: any): string => {
|
||||||
|
if (value === null || value === undefined || value === '') return '空';
|
||||||
|
const getOptions = selectOptionsMap[field];
|
||||||
|
if (getOptions) {
|
||||||
|
const opt = getOptions().find(
|
||||||
|
(o: any) => String(o.value) === String(value)
|
||||||
|
);
|
||||||
|
if (opt) return opt.label;
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析原始值的显示名:优先用预解析快照,不依赖实时选项
|
||||||
|
const resolveOriginalLabel = (field: string): string => {
|
||||||
|
return resolveSelectLabel(field, originalRecord.value[field]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 formData 变化
|
||||||
|
watch(
|
||||||
|
() => formData.value,
|
||||||
|
newData => {
|
||||||
|
const original = originalRecord.value;
|
||||||
|
for (const key in newData) {
|
||||||
|
const oldVal = original[key];
|
||||||
|
const newVal = newData[key];
|
||||||
|
if (oldVal !== newVal) {
|
||||||
|
const existingIdx = changeOrder.value.findIndex(c => c.field === key);
|
||||||
|
const oldNorm =
|
||||||
|
oldVal === null || oldVal === undefined || oldVal === '-'
|
||||||
|
? null
|
||||||
|
: oldVal;
|
||||||
|
const newNorm = newVal === null || newVal === undefined ? null : newVal;
|
||||||
|
if (oldNorm === newNorm) continue;
|
||||||
|
if (
|
||||||
|
oldNorm !== null &&
|
||||||
|
newNorm !== null &&
|
||||||
|
!isNaN(Number(oldNorm)) &&
|
||||||
|
!isNaN(Number(newNorm)) &&
|
||||||
|
Number(oldNorm) === Number(newNorm)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const isSelect = !!selectOptionsMap[key];
|
||||||
|
const oldDisplay = isSelect
|
||||||
|
? (originalLabels.value[key] ?? resolveSelectLabel(key, oldNorm))
|
||||||
|
: oldNorm === null
|
||||||
|
? '空'
|
||||||
|
: String(oldNorm);
|
||||||
|
const newDisplay = isSelect
|
||||||
|
? resolveSelectLabel(key, newNorm)
|
||||||
|
: newNorm === null
|
||||||
|
? '空'
|
||||||
|
: String(newNorm);
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
field: key,
|
||||||
|
label: fieldLabelMap[key] || key,
|
||||||
|
oldValue: oldDisplay,
|
||||||
|
newValue: newDisplay
|
||||||
|
};
|
||||||
|
if (existingIdx >= 0) {
|
||||||
|
changeOrder.value[existingIdx] = entry;
|
||||||
|
} else {
|
||||||
|
changeOrder.value.push(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changeOrder.value = changeOrder.value.filter(c => c.field !== key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听图片列表变化,生成图片增删 diff
|
||||||
|
watch(
|
||||||
|
() => picFileList.value,
|
||||||
|
newList => {
|
||||||
|
const originalIds = normalizeIds(originalRecord.value.firstimgurl);
|
||||||
|
const currentExistingIds = newList
|
||||||
|
.filter((f: any) => f.isExisting)
|
||||||
|
.map((f: any) => f.uid);
|
||||||
|
const currentNewCount = newList.filter((f: any) => f.originFileObj).length;
|
||||||
|
|
||||||
|
const deletedCount = originalIds.filter(
|
||||||
|
(id: string) => !currentExistingIds.includes(id)
|
||||||
|
).length;
|
||||||
|
const addedCount = currentNewCount;
|
||||||
|
|
||||||
|
if (deletedCount > 0 || addedCount > 0) {
|
||||||
|
const existingIdx = changeOrder.value.findIndex(
|
||||||
|
c => c.field === 'firstimgurl'
|
||||||
|
);
|
||||||
|
const oldDisplay = `${originalIds.length}张图片`;
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (deletedCount > 0) parts.push(`删除${deletedCount}张`);
|
||||||
|
if (addedCount > 0) parts.push(`新增${addedCount}张`);
|
||||||
|
const entry = {
|
||||||
|
field: 'firstimgurl',
|
||||||
|
label: '图片',
|
||||||
|
oldValue: oldDisplay,
|
||||||
|
newValue: parts.join(',')
|
||||||
|
};
|
||||||
|
if (existingIdx >= 0) {
|
||||||
|
changeOrder.value[existingIdx] = entry;
|
||||||
|
} else {
|
||||||
|
changeOrder.value.push(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changeOrder.value = changeOrder.value.filter(
|
||||||
|
c => c.field !== 'firstimgurl'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听视频列表变化,生成视频增删 diff
|
||||||
|
watch(
|
||||||
|
() => vdpFileList.value,
|
||||||
|
newList => {
|
||||||
|
const originalIds = normalizeIds(originalRecord.value.videourl);
|
||||||
|
const currentExistingIds = newList
|
||||||
|
.filter((f: any) => f.isExisting)
|
||||||
|
.map((f: any) => f.uid);
|
||||||
|
const currentNewCount = newList.filter((f: any) => f.originFileObj).length;
|
||||||
|
|
||||||
|
const deletedCount = originalIds.filter(
|
||||||
|
(id: string) => !currentExistingIds.includes(id)
|
||||||
|
).length;
|
||||||
|
const addedCount = currentNewCount;
|
||||||
|
|
||||||
|
if (deletedCount > 0 || addedCount > 0) {
|
||||||
|
const existingIdx = changeOrder.value.findIndex(
|
||||||
|
c => c.field === 'videourl'
|
||||||
|
);
|
||||||
|
const oldDisplay = `${originalIds.length}个视频`;
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (deletedCount > 0) parts.push(`删除${deletedCount}个`);
|
||||||
|
if (addedCount > 0) parts.push(`新增${addedCount}个`);
|
||||||
|
const entry = {
|
||||||
|
field: 'videourl',
|
||||||
|
label: '视频',
|
||||||
|
oldValue: oldDisplay,
|
||||||
|
newValue: parts.join(',')
|
||||||
|
};
|
||||||
|
if (existingIdx >= 0) {
|
||||||
|
changeOrder.value[existingIdx] = entry;
|
||||||
|
} else {
|
||||||
|
changeOrder.value.push(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changeOrder.value = changeOrder.value.filter(
|
||||||
|
c => c.field !== 'videourl'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadFacilities = async () => {
|
||||||
|
facilityLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getFishReleaseMonitorSectionList({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'mway',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: '2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: ['stcd', 'stnm', 'mway']
|
||||||
|
});
|
||||||
|
facilityOptions.value = (res.data?.data || []).map((item: any) => ({
|
||||||
|
label: item.stnm,
|
||||||
|
value: item.stcd
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取过鱼设施列表失败:', error);
|
||||||
|
} finally {
|
||||||
|
facilityLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 record 变化
|
||||||
|
watch(
|
||||||
|
() => props.record,
|
||||||
|
newRecord => {
|
||||||
|
if (newRecord) {
|
||||||
|
const converted = { ...newRecord };
|
||||||
|
// 将 "-" 还原为 null,避免控件显示异常
|
||||||
|
for (const key in converted) {
|
||||||
|
if (converted[key] === '-') {
|
||||||
|
converted[key] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 数字字段字符串转数字,保证 select/input-number 回显匹配
|
||||||
|
[
|
||||||
|
'direction',
|
||||||
|
'fcnt',
|
||||||
|
'length',
|
||||||
|
'width',
|
||||||
|
'fishposition',
|
||||||
|
'temperature',
|
||||||
|
'waterlevel',
|
||||||
|
'speed',
|
||||||
|
'q',
|
||||||
|
'dox',
|
||||||
|
'tu'
|
||||||
|
].forEach(key => {
|
||||||
|
if (
|
||||||
|
converted[key] !== null &&
|
||||||
|
converted[key] !== undefined &&
|
||||||
|
converted[key] !== ''
|
||||||
|
) {
|
||||||
|
converted[key] = Number(converted[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 鱼种类:列表返回名称,fishSearch 需用 fishId(id) 回显
|
||||||
|
const ftpName = converted.ftp;
|
||||||
|
if (converted.fishId) {
|
||||||
|
converted.ftp = converted.fishId;
|
||||||
|
}
|
||||||
|
originalRecord.value = { ...converted };
|
||||||
|
formData.value = { ...converted };
|
||||||
|
changeOrder.value = [];
|
||||||
|
// 提前解析 select 字段的原始显示名,防止联动刷新选项后丢失 label
|
||||||
|
const labels: Record<string, string> = {};
|
||||||
|
for (const key in converted) {
|
||||||
|
if (selectOptionsMap[key]) {
|
||||||
|
labels[key] = resolveOriginalLabel(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ftp 优先使用返回的名称,避免选项未加载时显示 id
|
||||||
|
if (ftpName && converted.fishId) {
|
||||||
|
labels.ftp = ftpName;
|
||||||
|
}
|
||||||
|
originalLabels.value = labels;
|
||||||
|
// 处理已有图片
|
||||||
|
const picIds = normalizeIds(converted.firstimgurl);
|
||||||
|
if (picIds.length > 0) {
|
||||||
|
picFileList.value = picIds.map((id: string) => ({
|
||||||
|
uid: id,
|
||||||
|
name: id,
|
||||||
|
status: 'done',
|
||||||
|
url: `${baseUrl}/?${id}&view=jpg`,
|
||||||
|
isExisting: true
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
picFileList.value = [];
|
||||||
|
}
|
||||||
|
// 处理已有视频
|
||||||
|
const vdpIds = normalizeIds(converted.videourl);
|
||||||
|
if (vdpIds.length > 0) {
|
||||||
|
vdpFileList.value = vdpIds.map((id: string) => ({
|
||||||
|
uid: id,
|
||||||
|
name: id,
|
||||||
|
status: 'done',
|
||||||
|
url: `${baseUrl}/?${id}`,
|
||||||
|
isExisting: true
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
vdpFileList.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 选项加载完成后重新解析原始显示名
|
||||||
|
watch(facilityOptions, () => {
|
||||||
|
const original = originalRecord.value;
|
||||||
|
if (!original || Object.keys(original).length === 0) return;
|
||||||
|
const labels: Record<string, string> = {};
|
||||||
|
for (const key in original) {
|
||||||
|
if (selectOptionsMap[key]) {
|
||||||
|
labels[key] = resolveOriginalLabel(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 保留已解析的 ftp 名称,避免选项未加载时显示 id
|
||||||
|
if (originalLabels.value.ftp) {
|
||||||
|
labels.ftp = originalLabels.value.ftp;
|
||||||
|
}
|
||||||
|
originalLabels.value = labels;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
val => {
|
||||||
|
if (val) {
|
||||||
|
loadFacilities();
|
||||||
|
// 编辑时统一初始化图片/视频(无值则清空,效果同新增)
|
||||||
|
const picIds = normalizeIds(props.record?.firstimgurl);
|
||||||
|
picFileList.value = picIds.length
|
||||||
|
? picIds.map((id: string) => ({
|
||||||
|
uid: id,
|
||||||
|
name: id,
|
||||||
|
status: 'done',
|
||||||
|
url: `${baseUrl}/?${id}&view=jpg`,
|
||||||
|
isExisting: true
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
const vdpIds = normalizeIds(props.record?.videourl);
|
||||||
|
vdpFileList.value = vdpIds.length
|
||||||
|
? vdpIds.map((id: string) => ({
|
||||||
|
uid: id,
|
||||||
|
name: id,
|
||||||
|
status: 'done',
|
||||||
|
url: `${baseUrl}/?${id}`,
|
||||||
|
isExisting: true
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validateFields();
|
||||||
|
if (changeOrder.value.length === 0) {
|
||||||
|
message.info('未检测到任何修改');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
confirmModalVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFormData = (engInfo: Record<string, any>, source: string) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
const data = {
|
||||||
|
engInfo,
|
||||||
|
source: source.trim() || '编辑'
|
||||||
|
};
|
||||||
|
formData.append('data', JSON.stringify(data));
|
||||||
|
// 添加新增的图片文件
|
||||||
|
picFileList.value.forEach((file: any) => {
|
||||||
|
if (file.originFileObj) {
|
||||||
|
formData.append('picFiles', file.originFileObj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 添加新增的视频文件
|
||||||
|
vdpFileList.value.forEach((file: any) => {
|
||||||
|
if (file.originFileObj) {
|
||||||
|
formData.append('vdFiles', file.originFileObj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间字段选择时仅到年月日,提交时统一补上时分秒
|
||||||
|
const formatDateTime = (val: any): any => {
|
||||||
|
if (val === null || val === undefined || val === '') return val;
|
||||||
|
const str = String(val);
|
||||||
|
return str.length === 10 ? `${str} 00:00:00` : str;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSubmit = async (source: string) => {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
// 编辑时只传修改过的字段
|
||||||
|
const engInfo: Record<string, any> = {};
|
||||||
|
engInfo.id = formData.value.id;
|
||||||
|
changeOrder.value.forEach(item => {
|
||||||
|
// select 清空后值为 undefined/null,统一转为空字符串,避免序列化时字段被省略
|
||||||
|
let val = formData.value[item.field];
|
||||||
|
if (val === null || val === undefined) {
|
||||||
|
val = '';
|
||||||
|
}
|
||||||
|
engInfo[item.field] = val;
|
||||||
|
});
|
||||||
|
// 保留剩余的已有图片 id
|
||||||
|
const existingPicIds = picFileList.value
|
||||||
|
.filter((f: any) => f.isExisting)
|
||||||
|
.map((f: any) => f.uid);
|
||||||
|
if (existingPicIds.length > 0) {
|
||||||
|
engInfo.firstimgurl = existingPicIds.join(',');
|
||||||
|
} else {
|
||||||
|
delete engInfo.firstimgurl;
|
||||||
|
}
|
||||||
|
// 保留剩余的已有视频 id
|
||||||
|
const existingVdpIds = vdpFileList.value
|
||||||
|
.filter((f: any) => f.isExisting)
|
||||||
|
.map((f: any) => f.uid);
|
||||||
|
if (existingVdpIds.length > 0) {
|
||||||
|
engInfo.videourl = existingVdpIds.join(',');
|
||||||
|
} else {
|
||||||
|
delete engInfo.videourl;
|
||||||
|
}
|
||||||
|
// 时间字段统一补时分秒
|
||||||
|
if (engInfo.tm) {
|
||||||
|
engInfo.tm = formatDateTime(engInfo.tm);
|
||||||
|
}
|
||||||
|
const fd = buildFormData(engInfo, source);
|
||||||
|
const res = await updateFpssrlAiRInfo(fd);
|
||||||
|
if (res?.code == 0 || res?.success) {
|
||||||
|
message.success('编辑成功');
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
visible.value = false;
|
||||||
|
emit('success');
|
||||||
|
} else {
|
||||||
|
message.error(res?.msg || '编辑失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('提交失败,请重试');
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmCancel = () => {
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -0,0 +1,300 @@
|
|||||||
|
<template>
|
||||||
|
<div class="approval-search">
|
||||||
|
<BasicSearch
|
||||||
|
ref="basicSearchRef"
|
||||||
|
:searchList="searchList"
|
||||||
|
:initial-values="initSearchData"
|
||||||
|
:zhujianfujian="'fu'"
|
||||||
|
@reset="handleResetWrapper"
|
||||||
|
@finish="onSearchFinish"
|
||||||
|
@values-change="onValuesChange"
|
||||||
|
><template #jcdt>
|
||||||
|
<a-date-picker
|
||||||
|
class="w-[120px]"
|
||||||
|
v-model:value="jcdt.min"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
placeholder="起始时间"
|
||||||
|
:allow-clear="false"
|
||||||
|
:showToday="false"
|
||||||
|
>
|
||||||
|
<template #renderExtraFooter>
|
||||||
|
<div class="flex items-center flex-wrap px-2">
|
||||||
|
<span
|
||||||
|
v-for="item in DateSetting.RangeButton.days1"
|
||||||
|
:key="item"
|
||||||
|
@click="handleRangeClick(item)"
|
||||||
|
>
|
||||||
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-date-picker>
|
||||||
|
至
|
||||||
|
<a-form-item-rest>
|
||||||
|
<a-date-picker
|
||||||
|
class="w-[120px]"
|
||||||
|
:allow-clear="false"
|
||||||
|
v-model:value="jcdt.max"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:disabledDate="disabledEndDate"
|
||||||
|
placeholder="结束时间"
|
||||||
|
:showToday="false"
|
||||||
|
>
|
||||||
|
<template #renderExtraFooter>
|
||||||
|
<div class="flex items-center flex-wrap px-2">
|
||||||
|
<span
|
||||||
|
v-for="item in DateSetting.RangeButton.days1"
|
||||||
|
:key="item"
|
||||||
|
@click="handleRangeClick(item)"
|
||||||
|
>
|
||||||
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-date-picker>
|
||||||
|
</a-form-item-rest>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<a-button type="primary" @click="seeEdit">查看修改</a-button>
|
||||||
|
<a-button danger :disabled="!selectedCount" @click="batchDelete"
|
||||||
|
>批量删除</a-button
|
||||||
|
>
|
||||||
|
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicSearch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
import { useTimeScale } from '@/store/composables/useTimeScale';
|
||||||
|
import { getFishReleaseMonitorSectionList } from '@/api/DataQueryMenuModule';
|
||||||
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'export-btn'): void;
|
||||||
|
(e: 'reset', values: any): void;
|
||||||
|
(e: 'search-finish', values: any): void;
|
||||||
|
(e: 'seeEdit'): void;
|
||||||
|
(e: 'batch-delete'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const basicSearchRef = ref<any>();
|
||||||
|
const btnLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
exportLoading?: boolean;
|
||||||
|
selectedCount?: number;
|
||||||
|
}>();
|
||||||
|
const initSearchData = {
|
||||||
|
rvcd: 'all',
|
||||||
|
rstcd: null,
|
||||||
|
stcd: null,
|
||||||
|
mway: '2',
|
||||||
|
sttpCode: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
|
|
||||||
|
const {
|
||||||
|
jcdt,
|
||||||
|
currentTimeScale,
|
||||||
|
handleRangeClick,
|
||||||
|
disabledEndDate,
|
||||||
|
handleSearchFinish,
|
||||||
|
handleValuesChange,
|
||||||
|
init,
|
||||||
|
setDefaultTimeRange
|
||||||
|
} = useTimeScale({
|
||||||
|
defaultTimeScale: initSearchData.timeScale,
|
||||||
|
onSearchFinish: values => emit('search-finish', values),
|
||||||
|
onReset: () => emit('reset', initSearchData)
|
||||||
|
});
|
||||||
|
const crossSectionListLoading = ref<any>(false);
|
||||||
|
const crossSectionList = ref<any>([]);
|
||||||
|
const facilityTypeOptions = ref<any[]>([]);
|
||||||
|
|
||||||
|
const searchList: any = computed(() => [
|
||||||
|
{
|
||||||
|
type: 'waterStation',
|
||||||
|
name: 'rvcd',
|
||||||
|
label: '流域',
|
||||||
|
placeholder: '请输入流域名称',
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true
|
||||||
|
},
|
||||||
|
options: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Select',
|
||||||
|
name: 'sttpCode',
|
||||||
|
label: '设施类型',
|
||||||
|
width: 140,
|
||||||
|
placeholder: '请选择设施类型',
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true
|
||||||
|
},
|
||||||
|
options: facilityTypeOptions.value.map(item => ({
|
||||||
|
label: item.dictName,
|
||||||
|
value: item.itemCode
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Select',
|
||||||
|
name: 'stcd',
|
||||||
|
label: '设施名称',
|
||||||
|
width: 200,
|
||||||
|
placeholder: '请选择过鱼设施名称',
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true
|
||||||
|
},
|
||||||
|
loading: crossSectionListLoading.value,
|
||||||
|
options: crossSectionList.value.map(item => ({
|
||||||
|
label: item.stnm,
|
||||||
|
value: item.stcd
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
name: 'jcdt',
|
||||||
|
label: '时间',
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onSearchFinish = (values: any) => {
|
||||||
|
handleSearchFinish(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportBtn = () => {
|
||||||
|
emit('export-btn');
|
||||||
|
};
|
||||||
|
const seeEdit = () => {
|
||||||
|
emit('seeEdit');
|
||||||
|
};
|
||||||
|
const batchDelete = () => {
|
||||||
|
emit('batch-delete');
|
||||||
|
};
|
||||||
|
const initCrossSectionList = params => {
|
||||||
|
const filters = [];
|
||||||
|
if (params) {
|
||||||
|
if (params.rvcd != '' && params.rvcd != 'all') {
|
||||||
|
filters.push({
|
||||||
|
field: 'rvcd',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: params.rvcd
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (params.rstcd != '' && params.rstcd != null) {
|
||||||
|
filters.push({
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: params.rstcd
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (params.sttpCode != '' && params.sttpCode != null) {
|
||||||
|
filters.push({
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: params.sttpCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crossSectionListLoading.value = true;
|
||||||
|
return getFishReleaseMonitorSectionList({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: filters
|
||||||
|
},
|
||||||
|
select: ['stcd', 'stnm', 'wtDeviceType', 'dtinType', 'mway']
|
||||||
|
}).then(res => {
|
||||||
|
if (res.data?.data) {
|
||||||
|
crossSectionList.value = res.data?.data;
|
||||||
|
}
|
||||||
|
// 设施名称固定选中第一条数据
|
||||||
|
if (crossSectionList.value.length > 0) {
|
||||||
|
if (basicSearchRef.value?.formData) {
|
||||||
|
basicSearchRef.value.formData.stcd = crossSectionList.value[0].stcd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crossSectionListLoading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
|
// 当流域、断面或设施类型变化时,清空过鱼设施选中并重新请求列表
|
||||||
|
if (
|
||||||
|
'rvcd' in changedValues ||
|
||||||
|
'rstcd' in changedValues ||
|
||||||
|
'sttpCode' in changedValues
|
||||||
|
) {
|
||||||
|
allValues.stcd = null;
|
||||||
|
if (basicSearchRef.value?.formData) {
|
||||||
|
basicSearchRef.value.formData.stcd = null;
|
||||||
|
}
|
||||||
|
initCrossSectionList(allValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleValuesChange(changedValues, allValues);
|
||||||
|
searchData.value = { ...searchData.value, ...allValues };
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetWrapper = async () => {
|
||||||
|
// 手动重置内部状态,避免触发两次请求
|
||||||
|
currentTimeScale.value = initSearchData.timeScale;
|
||||||
|
const now = dayjs();
|
||||||
|
jcdt.value = {
|
||||||
|
min: `${now
|
||||||
|
.subtract(5, 'year')
|
||||||
|
.startOf('year')
|
||||||
|
.format('YYYY-MM-DD')} 00:00:00`,
|
||||||
|
max: `${now.endOf('year').format('YYYY-MM-DD')} 23:59:59`
|
||||||
|
};
|
||||||
|
setDefaultTimeRange(initSearchData.timeScale);
|
||||||
|
// 重置后重新加载设施列表并选中第一条
|
||||||
|
await initCrossSectionList();
|
||||||
|
// 触发搜索,使用当前状态而非 initSearchData
|
||||||
|
handleSearchFinish({
|
||||||
|
...initSearchData,
|
||||||
|
...(basicSearchRef.value?.formData || {})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
btnLoading,
|
||||||
|
searchData
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
init();
|
||||||
|
// 设置默认年份:前一年到当前年
|
||||||
|
const now = dayjs();
|
||||||
|
jcdt.value = {
|
||||||
|
min: `${now.subtract(5, 'year').format('YYYY-MM-DD')} 00:00:00`,
|
||||||
|
max: `${now.format('YYYY-MM-DD')} 23:59:59`
|
||||||
|
};
|
||||||
|
// 设施类型字典
|
||||||
|
getDictItemsByCode({ dictCode: 'facilityType' }).then(res => {
|
||||||
|
facilityTypeOptions.value = res.data || [];
|
||||||
|
});
|
||||||
|
// 先加载设施列表(默认选中第一条),再发起初始查询
|
||||||
|
await initCrossSectionList();
|
||||||
|
// 使用 handleSearchFinish 确保包含 jcdt 等完整参数
|
||||||
|
handleSearchFinish({
|
||||||
|
...initSearchData,
|
||||||
|
...(basicSearchRef.value?.formData || {})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss"></style>
|
||||||
@ -1,25 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full flex flex-col">
|
<div class="w-full h-full flex flex-col pt-[20px] pl-[20px] body_one">
|
||||||
<!-- 搜索组件 -->
|
<!-- 搜索组件 -->
|
||||||
<FishDataSearch
|
<FishSurveyDataSearch
|
||||||
mway="2"
|
|
||||||
:exportLoading="exportLoading"
|
|
||||||
@export-btn="exportBtn"
|
@export-btn="exportBtn"
|
||||||
@search-finish="onSearchFinish"
|
@search-finish="onSearchFinish"
|
||||||
@reset="onReset"
|
@reset="onReset"
|
||||||
@seeEdit="handleSeeEdit"
|
@seeEdit="handleSeeEdit"
|
||||||
|
@batch-delete="handleBatchDelete"
|
||||||
|
:selected-count="selectedRowKeys.length"
|
||||||
|
:exportLoading="exportLoading"
|
||||||
ref="searchRef"
|
ref="searchRef"
|
||||||
/>
|
/>
|
||||||
<!-- 表格组件 -->
|
<!-- 表格组件 -->
|
||||||
<BasicTable
|
<BasicTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
|
:enable-row-selection="true"
|
||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:scrollY="tableScrollY"
|
:scrollY="tableScrollY"
|
||||||
:columns="tableColumns"
|
:columns="columnsAuto"
|
||||||
:list-url="getFishReleaseMonitorList"
|
:list-url="getFpssrlAiRPageList"
|
||||||
:searchParams="{
|
:searchParams="{
|
||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<a-button
|
<a-button
|
||||||
@ -64,7 +67,7 @@
|
|||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- 编辑 Modal -->
|
<!-- 编辑 Modal -->
|
||||||
<EditFishDataAutoModal
|
<EditFishSurveyDataModal
|
||||||
v-model:open="editVisible"
|
v-model:open="editVisible"
|
||||||
:record="editRecord"
|
:record="editRecord"
|
||||||
@success="handleEditSuccess"
|
@success="handleEditSuccess"
|
||||||
@ -73,27 +76,31 @@
|
|||||||
<!-- 删除确认 Modal -->
|
<!-- 删除确认 Modal -->
|
||||||
<DeleteConfirmModal
|
<DeleteConfirmModal
|
||||||
ref="deleteModalRef"
|
ref="deleteModalRef"
|
||||||
:delete-fn="deleteFpssrlFn"
|
:delete-fn="deleteFn"
|
||||||
:label="'过鱼自动数据'"
|
:label="deleteLabel"
|
||||||
title="删除过鱼自动数据"
|
:title="'删除' + deleteLabel"
|
||||||
@success="handleEditSuccess"
|
@success="handleEditSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<OperationLogModal v-model:open="operationLogVisible" :table-name="'SD_FPSS_A'" />
|
<!-- 操作日志弹框 -->
|
||||||
|
<OperationLogModal
|
||||||
|
v-model:open="operationLogVisible"
|
||||||
|
:table-name="operationTableName"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Button } from 'ant-design-vue';
|
import { Button, message } from 'ant-design-vue';
|
||||||
import FishDataSearch from './FishDataSearch.vue';
|
import FishSurveyDataSearch from './fishSurveyDataSearch.vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import { getFishReleaseMonitorList, deleteFpssrlRInfo } from '@/api/DataQueryMenuModule';
|
import EditFishSurveyDataModal from './EditFishSurveyDataModal.vue';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
|
||||||
import { useDraggable } from '@/utils/drag';
|
|
||||||
import EditFishDataAutoModal from './EditFishDataAutoModal.vue';
|
|
||||||
import DeleteConfirmModal from '@/views/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
import DeleteConfirmModal from '@/views/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
||||||
|
import { getFpssrlAiRPageList, deleteFpssrlAiRInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
const sort = ref<any>([
|
const sort = ref<any>([
|
||||||
{
|
{
|
||||||
@ -123,11 +130,21 @@ const tableScrollY = ref<string | number>(0);
|
|||||||
const currentSearchParams = ref<any>({});
|
const currentSearchParams = ref<any>({});
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
|
|
||||||
// 编辑/删除/日志相关
|
// 编辑/删除相关
|
||||||
const editVisible = ref(false);
|
const editVisible = ref(false);
|
||||||
const editRecord = ref<any>(null);
|
const editRecord = ref<any>(null);
|
||||||
const deleteModalRef = ref();
|
const deleteModalRef = ref();
|
||||||
const operationLogVisible = ref(false);
|
const operationLogVisible = ref(false);
|
||||||
|
const deleteLabel = '过鱼自动数据';
|
||||||
|
const operationTableName = 'SD_FPSSRL_AI_R';
|
||||||
|
|
||||||
|
// 多选相关
|
||||||
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
|
const selectedRows = ref<any[]>([]);
|
||||||
|
const handleSelectionChange = (keys: string[], rows: any[]) => {
|
||||||
|
selectedRowKeys.value = keys;
|
||||||
|
selectedRows.value = rows;
|
||||||
|
};
|
||||||
|
|
||||||
// 图片预览
|
// 图片预览
|
||||||
const imagePreviewVisible = ref(false);
|
const imagePreviewVisible = ref(false);
|
||||||
@ -149,7 +166,7 @@ const openVideoPreview = (url: string) => {
|
|||||||
|
|
||||||
const tableScrollX = computed(() =>
|
const tableScrollX = computed(() =>
|
||||||
Math.max(
|
Math.max(
|
||||||
tableColumns.value.reduce(
|
columnsAuto.reduce(
|
||||||
(sum: number, col: any) => sum + (col.width || 180),
|
(sum: number, col: any) => sum + (col.width || 180),
|
||||||
0
|
0
|
||||||
),
|
),
|
||||||
@ -158,7 +175,7 @@ const tableScrollX = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 自动列
|
// 自动列
|
||||||
const tableColumns = ref<any[]>([
|
const columnsAuto: any[] = [
|
||||||
{
|
{
|
||||||
key: 'ennm',
|
key: 'ennm',
|
||||||
title: '电站名称',
|
title: '电站名称',
|
||||||
@ -177,7 +194,7 @@ const tableColumns = ref<any[]>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'stnm',
|
key: 'stnm',
|
||||||
title: '过鱼设施名称',
|
title: '设施名称',
|
||||||
dataIndex: 'stnm',
|
dataIndex: 'stnm',
|
||||||
sort: true,
|
sort: true,
|
||||||
width: 200
|
width: 200
|
||||||
@ -339,7 +356,7 @@ const tableColumns = ref<any[]>([
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 120
|
width: 120
|
||||||
}
|
}
|
||||||
]);
|
];
|
||||||
|
|
||||||
const onSearchFinish = async (values: any) => {
|
const onSearchFinish = async (values: any) => {
|
||||||
currentSearchParams.value = values;
|
currentSearchParams.value = values;
|
||||||
@ -362,7 +379,7 @@ const exportBtn = () => {
|
|||||||
|
|
||||||
tableRef.value
|
tableRef.value
|
||||||
.exportTable({
|
.exportTable({
|
||||||
fileName: `过鱼监测数据_自动_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
fileName: `过鱼监测数据_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
exportLoading.value = false;
|
exportLoading.value = false;
|
||||||
@ -389,12 +406,12 @@ const initTable = (values: any) => {
|
|||||||
value: values.rstcd
|
value: values.rstcd
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
values.mway
|
values.sttpCode
|
||||||
? {
|
? {
|
||||||
field: 'mway',
|
field: 'sttpCode',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
dataType: 'string',
|
dataType: 'string',
|
||||||
value: values.mway
|
value: values.sttpCode
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
].filter(Boolean); // 移除空项
|
].filter(Boolean); // 移除空项
|
||||||
@ -441,10 +458,6 @@ const initTable = (values: any) => {
|
|||||||
tableRef.value.getList(params);
|
tableRef.value.getList(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSeeEdit = () => {
|
|
||||||
operationLogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编辑
|
// 编辑
|
||||||
const handleEdit = (record: any) => {
|
const handleEdit = (record: any) => {
|
||||||
editRecord.value = { ...record };
|
editRecord.value = { ...record };
|
||||||
@ -457,19 +470,49 @@ const handleEditSuccess = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
const deleteFpssrlFn = async (record: any, reason: string) => {
|
const deleteFn = async (record: any, reason: string) => {
|
||||||
return deleteFpssrlRInfo({
|
const ids = record.ids || [record.id];
|
||||||
ids: [record.id],
|
return deleteFpssrlAiRInfo({
|
||||||
|
ids,
|
||||||
source: reason
|
source: reason
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除后刷新:清空选中(让批量删除按钮回到禁用状态)并重新加载列表
|
||||||
|
const refreshAfterDelete = () => {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = (record: any) => {
|
const handleDelete = (record: any) => {
|
||||||
deleteModalRef.value?.open(record, () => {
|
deleteModalRef.value?.open(record, () => {
|
||||||
initTable(currentSearchParams.value);
|
refreshAfterDelete();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (selectedRowKeys.value.length === 0) {
|
||||||
|
message.info('请先勾选要删除的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteModalRef.value?.open(
|
||||||
|
{
|
||||||
|
ids: [...selectedRowKeys.value],
|
||||||
|
stnm: `选中的 ${selectedRowKeys.value.length} 条数据`
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
refreshAfterDelete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSeeEdit = () => {
|
||||||
|
operationLogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
tableScrollY.value = calcTableScrollY();
|
tableScrollY.value = calcTableScrollY();
|
||||||
@ -477,4 +520,9 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
.body_one {
|
||||||
|
position: relative;
|
||||||
|
z-index: 900;
|
||||||
|
pointer-events: all;
|
||||||
|
}</style>
|
||||||
@ -47,6 +47,12 @@ const dialog = ref(false);
|
|||||||
const treeRef = ref();
|
const treeRef = ref();
|
||||||
const fishTreeRef = ref();
|
const fishTreeRef = ref();
|
||||||
const infoForm = ref();
|
const infoForm = ref();
|
||||||
|
//所属组织树型下拉框字段映射
|
||||||
|
const treeSelectProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'orgname',
|
||||||
|
children: 'childList'
|
||||||
|
};
|
||||||
//获取企业树形信息
|
//获取企业树形信息
|
||||||
const treeloading = ref(true);
|
const treeloading = ref(true);
|
||||||
function getTree() {
|
function getTree() {
|
||||||
@ -99,7 +105,8 @@ function addClick() {
|
|||||||
nickname: '',
|
nickname: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
roleinfo: []
|
roleinfo: [],
|
||||||
|
orgid: ''
|
||||||
};
|
};
|
||||||
getrole();
|
getrole();
|
||||||
}
|
}
|
||||||
@ -178,7 +185,8 @@ const info = ref({
|
|||||||
nickname: '',
|
nickname: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
roleinfo: [] as any[]
|
roleinfo: [] as any[],
|
||||||
|
orgid: ''
|
||||||
});
|
});
|
||||||
//修改-用户
|
//修改-用户
|
||||||
function editdepartment(row: any) {
|
function editdepartment(row: any) {
|
||||||
@ -188,6 +196,8 @@ function editdepartment(row: any) {
|
|||||||
});
|
});
|
||||||
info.value = JSON.parse(JSON.stringify(row));
|
info.value = JSON.parse(JSON.stringify(row));
|
||||||
info.value.roleinfo = selectID;
|
info.value.roleinfo = selectID;
|
||||||
|
// 回显所属组织
|
||||||
|
info.value.orgid = row.orgid;
|
||||||
rolesdata.value = row.roles;
|
rolesdata.value = row.roles;
|
||||||
title.value = '修改用户';
|
title.value = '修改用户';
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
@ -207,7 +217,8 @@ function confirmClick(formEl: any) {
|
|||||||
nickname: info.value.nickname,
|
nickname: info.value.nickname,
|
||||||
email: info.value.email,
|
email: info.value.email,
|
||||||
phone: info.value.phone,
|
phone: info.value.phone,
|
||||||
orgid: orgId.value
|
// 修改时根据所属组织树型下拉框传递
|
||||||
|
orgid: info.value.orgid
|
||||||
};
|
};
|
||||||
const roleids = String(info.value.roleinfo);
|
const roleids = String(info.value.roleinfo);
|
||||||
updataUser(params, roleids).then(() => {
|
updataUser(params, roleids).then(() => {
|
||||||
@ -787,6 +798,7 @@ function getrole() {
|
|||||||
rolename: ''
|
rolename: ''
|
||||||
};
|
};
|
||||||
listGroupedByTenant(params).then(res => {
|
listGroupedByTenant(params).then(res => {
|
||||||
|
// debugger
|
||||||
rolesdata.value = res;
|
rolesdata.value = res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1256,8 +1268,8 @@ function handleClearSelection() {
|
|||||||
v-model="info.roleinfo"
|
v-model="info.roleinfo"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
multiple
|
|
||||||
filterable
|
filterable
|
||||||
|
multiple
|
||||||
>
|
>
|
||||||
<el-option-group
|
<el-option-group
|
||||||
v-for="group in rolesdata"
|
v-for="group in rolesdata"
|
||||||
@ -1273,6 +1285,17 @@ function handleClearSelection() {
|
|||||||
</el-option-group>
|
</el-option-group>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="所属组织" v-if="info.id">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="info.orgid"
|
||||||
|
:data="treedata"
|
||||||
|
:props="treeSelectProps"
|
||||||
|
:render-after-expand="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择所属组织"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<span
|
<span
|
||||||
class="dialog-footer"
|
class="dialog-footer"
|
||||||
|
|||||||
@ -47,6 +47,12 @@ const dialog = ref(false);
|
|||||||
const treeRef = ref();
|
const treeRef = ref();
|
||||||
const fishTreeRef = ref();
|
const fishTreeRef = ref();
|
||||||
const infoForm = ref();
|
const infoForm = ref();
|
||||||
|
//所属组织树型下拉框字段映射
|
||||||
|
const treeSelectProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'orgname',
|
||||||
|
children: 'childList'
|
||||||
|
};
|
||||||
//获取企业树形信息
|
//获取企业树形信息
|
||||||
const treeloading = ref(true);
|
const treeloading = ref(true);
|
||||||
function getTree() {
|
function getTree() {
|
||||||
@ -99,7 +105,8 @@ function addClick() {
|
|||||||
nickname: '',
|
nickname: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
roleinfo: []
|
roleinfo: [],
|
||||||
|
orgid: ''
|
||||||
};
|
};
|
||||||
getrole();
|
getrole();
|
||||||
}
|
}
|
||||||
@ -178,7 +185,8 @@ const info = ref({
|
|||||||
nickname: '',
|
nickname: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
roleinfo: [] as any[]
|
roleinfo: [] as any[],
|
||||||
|
orgid: ''
|
||||||
});
|
});
|
||||||
//修改-用户
|
//修改-用户
|
||||||
function editdepartment(row: any) {
|
function editdepartment(row: any) {
|
||||||
@ -188,6 +196,8 @@ function editdepartment(row: any) {
|
|||||||
});
|
});
|
||||||
info.value = JSON.parse(JSON.stringify(row));
|
info.value = JSON.parse(JSON.stringify(row));
|
||||||
info.value.roleinfo = selectID;
|
info.value.roleinfo = selectID;
|
||||||
|
// 回显所属组织
|
||||||
|
info.value.orgid = row.orgid;
|
||||||
rolesdata.value = row.roles;
|
rolesdata.value = row.roles;
|
||||||
title.value = '修改用户';
|
title.value = '修改用户';
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
@ -207,7 +217,8 @@ function confirmClick(formEl: any) {
|
|||||||
nickname: info.value.nickname,
|
nickname: info.value.nickname,
|
||||||
email: info.value.email,
|
email: info.value.email,
|
||||||
phone: info.value.phone,
|
phone: info.value.phone,
|
||||||
orgid: orgId.value
|
// 修改时根据所属组织树型下拉框传递
|
||||||
|
orgid: info.value.orgid
|
||||||
};
|
};
|
||||||
const roleids = String(info.value.roleinfo);
|
const roleids = String(info.value.roleinfo);
|
||||||
updataUser(params, roleids).then(() => {
|
updataUser(params, roleids).then(() => {
|
||||||
@ -787,6 +798,7 @@ function getrole() {
|
|||||||
rolename: ''
|
rolename: ''
|
||||||
};
|
};
|
||||||
listGroupedByTenant(params).then(res => {
|
listGroupedByTenant(params).then(res => {
|
||||||
|
// debugger
|
||||||
rolesdata.value = res;
|
rolesdata.value = res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1250,13 +1262,14 @@ function handleClearSelection() {
|
|||||||
placeholder="请输入登录账号"
|
placeholder="请输入登录账号"
|
||||||
></el-input>
|
></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="所属角色">
|
<el-form-item label="所属角色">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="info.roleinfo"
|
v-model="info.roleinfo"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
multiple
|
|
||||||
filterable
|
filterable
|
||||||
|
multiple
|
||||||
>
|
>
|
||||||
<el-option-group
|
<el-option-group
|
||||||
v-for="group in rolesdata"
|
v-for="group in rolesdata"
|
||||||
@ -1272,6 +1285,17 @@ function handleClearSelection() {
|
|||||||
</el-option-group>
|
</el-option-group>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="所属组织" v-if="info.id">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="info.orgid"
|
||||||
|
:data="treedata"
|
||||||
|
:props="treeSelectProps"
|
||||||
|
:render-after-expand="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择所属组织"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<span
|
<span
|
||||||
class="dialog-footer"
|
class="dialog-footer"
|
||||||
|
|||||||
@ -4,34 +4,11 @@
|
|||||||
<a-row class="w-[100%]" v-if="!loading">
|
<a-row class="w-[100%]" v-if="!loading">
|
||||||
<a-col
|
<a-col
|
||||||
:span="!item.visible ? 24 : item.filed == '' ? 12 : 12"
|
:span="!item.visible ? 24 : item.filed == '' ? 12 : 12"
|
||||||
v-for="(item, index) in tabledata"
|
v-for="item in tabledata"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
>
|
>
|
||||||
<div class="col-title" v-if="!item.visible">
|
<div class="col-title" v-if="!item.visible">
|
||||||
{{ item.name }}:
|
{{ item.name }}:
|
||||||
<div v-if="modelStore.isBasicEdit && index == 0">
|
|
||||||
<a-button
|
|
||||||
v-if="!isEdit"
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
class="mr-2"
|
|
||||||
@click="editBtn"
|
|
||||||
>
|
|
||||||
编辑信息
|
|
||||||
</a-button>
|
|
||||||
<a-button
|
|
||||||
v-else
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
class="mr-2"
|
|
||||||
@click="saveBtn"
|
|
||||||
>
|
|
||||||
保存信息
|
|
||||||
</a-button>
|
|
||||||
<a-button type="primary" size="small" @click="recordBtn">
|
|
||||||
修改记录
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-left" v-if="item.visible && item.filed != ''">
|
<div class="col-left" v-if="item.visible && item.filed != ''">
|
||||||
<a-tooltip :title="item.name.length > 10 ? item.name : ''"
|
<a-tooltip :title="item.name.length > 10 ? item.name : ''"
|
||||||
@ -41,50 +18,14 @@
|
|||||||
<div class="col-right" v-if="item.visible">
|
<div class="col-right" v-if="item.visible">
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
:title="
|
:title="
|
||||||
!isEdit
|
item.vlsr || String(item.value).length > 10
|
||||||
? item.vlsr || String(item.value).length > 10
|
|
||||||
? item.vlsr
|
? item.vlsr
|
||||||
? '数据来源:' + item.vlsr
|
? '数据来源:' + item.vlsr
|
||||||
: item.value
|
: item.value
|
||||||
: ''
|
: ''
|
||||||
: ''
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span v-if="!isEdit && item.filed != ''">{{
|
<span>{{ item.value || '-' }}</span>
|
||||||
item.value || '-'
|
|
||||||
}}</span>
|
|
||||||
<span v-else>
|
|
||||||
<!-- {{ item.value }} -->
|
|
||||||
<a-input
|
|
||||||
v-if="item.type == 'input'"
|
|
||||||
v-model:value="item.value"
|
|
||||||
:placeholder="item.name"
|
|
||||||
size="small"
|
|
||||||
></a-input>
|
|
||||||
<a-select
|
|
||||||
v-else-if="item.type == 'select'"
|
|
||||||
v-model:value="item.value"
|
|
||||||
:placeholder="item.name"
|
|
||||||
size="small"
|
|
||||||
class="w-full"
|
|
||||||
></a-select>
|
|
||||||
<!-- <div v-else-if="item.type == 'date'">{{ item.value }}2222</div> -->
|
|
||||||
<a-date-picker
|
|
||||||
v-else-if="item.type == 'date'"
|
|
||||||
v-model:value="item.value"
|
|
||||||
:format="item.format"
|
|
||||||
:value-format="item.format"
|
|
||||||
:picker="item.picker"
|
|
||||||
class="w-full"
|
|
||||||
></a-date-picker>
|
|
||||||
<a-input-number
|
|
||||||
v-else-if="item.type == 'number'"
|
|
||||||
v-model:value="item.value"
|
|
||||||
:placeholder="item.name"
|
|
||||||
size="small"
|
|
||||||
class="w-full"
|
|
||||||
></a-input-number>
|
|
||||||
</span>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
@ -100,22 +41,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-modal
|
|
||||||
v-model:open="visible"
|
|
||||||
title="修改记录"
|
|
||||||
:footer="null"
|
|
||||||
width="900px"
|
|
||||||
:zIndex="2000"
|
|
||||||
>
|
|
||||||
123
|
|
||||||
</a-modal>
|
|
||||||
</div>
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { ref, nextTick, watch, computed } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import {
|
import {
|
||||||
BasicColumns,
|
BasicColumns,
|
||||||
wtPointColumns,
|
wtPointColumns,
|
||||||
@ -152,7 +84,6 @@ import { getStcdDetail } from '@/api/mapModal';
|
|||||||
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
const hasLoaded = ref(false); // 标记是否已经请求过
|
const hasLoaded = ref(false); // 标记是否已经请求过
|
||||||
import { useDraggable } from '@/utils/drag';
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -163,12 +94,10 @@ const props = defineProps({
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const visible = ref(false);
|
|
||||||
const isLogo = ref(false);
|
const isLogo = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const data2: any = ref({});
|
const data2: any = ref({});
|
||||||
const tabledata = ref([]);
|
const tabledata = ref([]);
|
||||||
useDraggable(visible, { boundary: true, resetOnOpen: true });
|
|
||||||
const columnsConfig = ref([
|
const columnsConfig = ref([
|
||||||
{
|
{
|
||||||
type: 'ENG',
|
type: 'ENG',
|
||||||
@ -337,22 +266,6 @@ const columnsConfig = ref([
|
|||||||
|
|
||||||
//DW_1
|
//DW_1
|
||||||
]);
|
]);
|
||||||
const isEdit = ref(false);
|
|
||||||
const editBtn = () => {
|
|
||||||
nextTick(() => {
|
|
||||||
isEdit.value = true;
|
|
||||||
tabledata.value.forEach(item => {
|
|
||||||
if (item.type == 'date')
|
|
||||||
item.value = dayjs(item.value).format(item.format);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const saveBtn = () => {
|
|
||||||
isEdit.value = false;
|
|
||||||
};
|
|
||||||
const recordBtn = () => {
|
|
||||||
visible.value = true;
|
|
||||||
};
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
if (hasLoaded.value) return;
|
if (hasLoaded.value) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|||||||
@ -598,7 +598,16 @@ const setQGCTabList = async (_tabs: any, stcd: string) => {
|
|||||||
) {
|
) {
|
||||||
tabsConfig.value = newTabs;
|
tabsConfig.value = newTabs;
|
||||||
} else {
|
} else {
|
||||||
tabsConfig.value = newTabs.filter((item: any) => item.type != 'video');
|
let arr = ['DW', 'DW_1', 'DW_2', 'DW_3', 'DW_4', 'DW_5', 'DW_6'];
|
||||||
|
if (arr.includes(modelStore.params.sttp)) {
|
||||||
|
tabsConfig.value = newTabs.filter(
|
||||||
|
(item: any) => item.type == 'basic'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tabsConfig.value = newTabs.filter(
|
||||||
|
(item: any) => item.type != 'video'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('QGC Tab 过滤失败:', error);
|
console.error('QGC Tab 过滤失败:', error);
|
||||||
|
|||||||
@ -221,7 +221,6 @@ const handleItemClick = (item: MediaItem) => {
|
|||||||
modelStore.params.sttp = 'WVA';
|
modelStore.params.sttp = 'WVA';
|
||||||
modelStore.params.stcd = item.stcd;
|
modelStore.params.stcd = item.stcd;
|
||||||
modelStore.title = `${item.stnm} 野生动物监测`;
|
modelStore.title = `${item.stnm} 野生动物监测`;
|
||||||
modelStore.isBasicEdit = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 获取站点列表 ====================
|
// ==================== 获取站点列表 ====================
|
||||||
|
|||||||
@ -550,7 +550,6 @@ const initChart = () => {
|
|||||||
modelStore.params.sttp = 'WTRV';
|
modelStore.params.sttp = 'WTRV';
|
||||||
modelStore.params.enfc = '1';
|
modelStore.params.enfc = '1';
|
||||||
modelStore.title = stnm;
|
modelStore.title = stnm;
|
||||||
// modelStore.isBasicEdit = true;
|
|
||||||
modelStore.params.stcd = stcd;
|
modelStore.params.stcd = stcd;
|
||||||
modelStore.filter.rangeTm = [startTime, endTime];
|
modelStore.filter.rangeTm = [startTime, endTime];
|
||||||
|
|
||||||
|
|||||||
@ -276,7 +276,7 @@ const fetchFacilityStats = async () => {
|
|||||||
"field": "bldsttCcode",
|
"field": "bldsttCcode",
|
||||||
"operator": "eq",
|
"operator": "eq",
|
||||||
"dataType": "string",
|
"dataType": "string",
|
||||||
"value": "2"
|
"value": formState.bldstt
|
||||||
} : null,
|
} : null,
|
||||||
formState.stnm ? {
|
formState.stnm ? {
|
||||||
"field": "stnm",
|
"field": "stnm",
|
||||||
@ -334,6 +334,7 @@ const fetchBuildStateList = async () => {
|
|||||||
value: item.itemCode
|
value: item.itemCode
|
||||||
}))
|
}))
|
||||||
buildStateList.value.unshift({ label: '全部', value: '' })
|
buildStateList.value.unshift({ label: '全部', value: '' })
|
||||||
|
// debugger
|
||||||
// 临时模拟数据(实际使用时删除)
|
// 临时模拟数据(实际使用时删除)
|
||||||
// buildStateList.value = [
|
// buildStateList.value = [
|
||||||
// { label: '全部', value: '' },
|
// { label: '全部', value: '' },
|
||||||
@ -399,7 +400,7 @@ const handleSearch = () => {
|
|||||||
"field": "bldsttCcode",
|
"field": "bldsttCcode",
|
||||||
"operator": "eq",
|
"operator": "eq",
|
||||||
"dataType": "string",
|
"dataType": "string",
|
||||||
"value": "2"
|
"value": formState.bldstt
|
||||||
} : null,
|
} : null,
|
||||||
formState.stnm ? {
|
formState.stnm ? {
|
||||||
"field": "stnm",
|
"field": "stnm",
|
||||||
|
|||||||
@ -347,7 +347,6 @@ const handleViewDetail = (record: any, type: any) => {
|
|||||||
modelStore.modalVisible = true;
|
modelStore.modalVisible = true;
|
||||||
modelStore.params.sttp = 'VD_FBFM';
|
modelStore.params.sttp = 'VD_FBFM';
|
||||||
modelStore.title = '视频监控站详情信息';
|
modelStore.title = '视频监控站详情信息';
|
||||||
modelStore.isBasicEdit = true;
|
|
||||||
modelStore.params.stcd = record.stcd;
|
modelStore.params.stcd = record.stcd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -519,7 +519,6 @@ const handleViewDetail = (record: any) => {
|
|||||||
modelStore.modalVisible = true;
|
modelStore.modalVisible = true;
|
||||||
modelStore.params.sttp = "WT";
|
modelStore.params.sttp = "WT";
|
||||||
modelStore.title = record.stnm ;
|
modelStore.title = record.stnm ;
|
||||||
// modelStore.isBasicEdit = true;
|
|
||||||
if (tabIndex.value == "27") {
|
if (tabIndex.value == "27") {
|
||||||
modelStore.params.enfc = "1";
|
modelStore.params.enfc = "1";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import * as echarts from 'echarts';
|
|||||||
import type { EChartsOption } from 'echarts';
|
import type { EChartsOption } from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import { qgcGetKendoListCust } from '@/api/sz';
|
import { qgcGetKendoListCust } from '@/api/sz';
|
||||||
|
import { getMonitorDataWaterQualityDetail } from '@/api/mapModal/index';
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import {
|
import {
|
||||||
@ -101,49 +102,23 @@ const indicatorConfig = ref<
|
|||||||
}>
|
}>
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
// ==================== HSL 随机颜色生成 ====================
|
// ==================== 指标颜色 ====================
|
||||||
|
// 参考 MonitorInfo.vue 的固定色板,按指标顺序循环取色,颜色稳定可预期
|
||||||
|
const COLOR_PALETTE = [
|
||||||
|
'#56C2E3',
|
||||||
|
'#7399C6',
|
||||||
|
'#4B79AB',
|
||||||
|
'#78C300',
|
||||||
|
'#00A050',
|
||||||
|
'#F7A737'
|
||||||
|
];
|
||||||
const generateRandomColor = (index: number): string => {
|
const generateRandomColor = (index: number): string => {
|
||||||
// 使用黄金角度确保颜色均匀分布
|
return COLOR_PALETTE[index % COLOR_PALETTE.length];
|
||||||
const goldenAngle = 137.508;
|
|
||||||
const hue = (index * goldenAngle) % 360;
|
|
||||||
|
|
||||||
// 饱和度控制在 45%-65% 之间
|
|
||||||
const saturation = 45 + Math.random() * 20;
|
|
||||||
|
|
||||||
// 亮度控制在 45%-60% 之间,避免太亮或太暗
|
|
||||||
const lightness = 45 + Math.random() * 15;
|
|
||||||
|
|
||||||
// HSL 转 RGB
|
|
||||||
const c = ((1 - Math.abs((2 * lightness) / 100 - 1)) * saturation) / 100;
|
|
||||||
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
|
|
||||||
const m = lightness / 100 - c / 2;
|
|
||||||
|
|
||||||
let r = 0,
|
|
||||||
g = 0,
|
|
||||||
b = 0;
|
|
||||||
|
|
||||||
if (hue < 60) {
|
|
||||||
[r, g, b] = [c, x, 0];
|
|
||||||
} else if (hue < 120) {
|
|
||||||
[r, g, b] = [x, c, 0];
|
|
||||||
} else if (hue < 180) {
|
|
||||||
[r, g, b] = [0, c, x];
|
|
||||||
} else if (hue < 240) {
|
|
||||||
[r, g, b] = [0, x, c];
|
|
||||||
} else if (hue < 300) {
|
|
||||||
[r, g, b] = [x, 0, c];
|
|
||||||
} else {
|
|
||||||
[r, g, b] = [c, 0, x];
|
|
||||||
}
|
|
||||||
|
|
||||||
const toHex = (value: number) => {
|
|
||||||
const hex = Math.round((value + m) * 255).toString(16);
|
|
||||||
return hex.length === 1 ? '0' + hex : hex;
|
|
||||||
};
|
|
||||||
|
|
||||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 指标颜色映射(来自水质参数接口 showControl.lineColor,key 为指标小写编码)
|
||||||
|
const indicatorColorMap = ref<Record<string, string>>({});
|
||||||
|
|
||||||
// ==================== 动态指标筛选和配置生成 ====================
|
// ==================== 动态指标筛选和配置生成 ====================
|
||||||
const generateIndicatorConfig = (data: StationData[]) => {
|
const generateIndicatorConfig = (data: StationData[]) => {
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
@ -216,54 +191,15 @@ const generateIndicatorConfig = (data: StationData[]) => {
|
|||||||
.filter(ind => validKeysSet.has(ind.key))
|
.filter(ind => validKeysSet.has(ind.key))
|
||||||
.sort((a, b) => a.sort - b.sort);
|
.sort((a, b) => a.sort - b.sort);
|
||||||
|
|
||||||
// 生成最终配置
|
// 生成最终配置:颜色优先取接口配置,缺失时回退到色板
|
||||||
indicatorConfig.value = validIndicators.map((ind, index) => ({
|
indicatorConfig.value = validIndicators.map((ind, index) => ({
|
||||||
key: ind.key,
|
key: ind.key,
|
||||||
name: ind.name,
|
name: ind.name,
|
||||||
unit: ind.description || '',
|
unit: ind.description || '',
|
||||||
color: generateRandomColor(index),
|
color: indicatorColorMap.value[ind.key] || generateRandomColor(index),
|
||||||
sort: ind.sort
|
sort: ind.sort
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('动态生成指标配置:', indicatorConfig.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ==================== 模拟数据 ====================
|
|
||||||
const generateMockData = () => {
|
|
||||||
const stations = [
|
|
||||||
{ stnm: '两河口', stcd: 'ST001', type: '0' },
|
|
||||||
{ stnm: '锦屏一级', stcd: 'ST002', type: '1' },
|
|
||||||
{ stnm: '桐子林', stcd: 'ST003', type: '0' },
|
|
||||||
{ stnm: '溪洛渡', stcd: 'ST004', type: '0' },
|
|
||||||
{ stnm: '向家坝', stcd: 'ST005', type: '1' },
|
|
||||||
{ stnm: '三峡', stcd: 'ST006', type: '0' },
|
|
||||||
{ stnm: '葛洲坝', stcd: 'ST007', type: '0' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const tm = '2024-01-15 08:00:00';
|
|
||||||
|
|
||||||
// 模拟限值数据
|
|
||||||
const mockLimits = {
|
|
||||||
ph: { min: 6, max: 9 },
|
|
||||||
dox: { min: 6 },
|
|
||||||
codmn: { max: 4 },
|
|
||||||
nh3n: { max: 0.5 },
|
|
||||||
tp: { max: 0.1 },
|
|
||||||
tn: { max: 1.0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
return stations.map((station, index) => ({
|
|
||||||
...station,
|
|
||||||
tm,
|
|
||||||
SORT: index + 1,
|
|
||||||
ph: 7.2 + Math.random() * 0.8,
|
|
||||||
dox: 6.5 + Math.random() * 2.5,
|
|
||||||
codmn: 1.5 + Math.random() * 2.0,
|
|
||||||
nh3n: 0.15 + Math.random() * 0.35,
|
|
||||||
tp: 0.05 + Math.random() * 0.15,
|
|
||||||
tn: 0.8 + Math.random() * 1.2,
|
|
||||||
limits: mockLimits // 新增:存储该站点的所有限值
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义数据类型接口
|
// 定义数据类型接口
|
||||||
@ -273,44 +209,9 @@ interface StationData {
|
|||||||
type: string;
|
type: string;
|
||||||
tm: string;
|
tm: string;
|
||||||
SORT: number;
|
SORT: number;
|
||||||
// 所有水质指标字段(动态)
|
|
||||||
ph?: number | null;
|
|
||||||
dox?: number | null;
|
|
||||||
codmn?: number | null;
|
|
||||||
codcr?: number | null;
|
|
||||||
bod5?: number | null;
|
|
||||||
nh3n?: number | null;
|
|
||||||
tp?: number | null;
|
|
||||||
tn?: number | null;
|
|
||||||
cu?: number | null;
|
|
||||||
zn?: number | null;
|
|
||||||
f?: number | null;
|
|
||||||
se?: number | null;
|
|
||||||
ars?: number | null;
|
|
||||||
hg?: number | null;
|
|
||||||
cd?: number | null;
|
|
||||||
cr6?: number | null;
|
|
||||||
pb?: number | null;
|
|
||||||
cn?: number | null;
|
|
||||||
vlph?: number | null;
|
|
||||||
oil?: number | null;
|
|
||||||
las?: number | null;
|
|
||||||
s2?: number | null;
|
|
||||||
fcg?: number | null;
|
|
||||||
cl?: number | null;
|
|
||||||
so4?: number | null;
|
|
||||||
no3?: number | null;
|
|
||||||
thrd?: number | null;
|
|
||||||
cond?: number | null;
|
|
||||||
fe?: number | null;
|
|
||||||
mn?: number | null;
|
|
||||||
al?: number | null;
|
|
||||||
chla?: number | null;
|
|
||||||
clarity?: number | null;
|
|
||||||
tu?: number | null;
|
|
||||||
wtmp?: number | null;
|
|
||||||
limits: Record<string, { min?: number; max?: number }>;
|
limits: Record<string, { min?: number; max?: number }>;
|
||||||
[key: string]: any; // 允许任意水质指标字段
|
// 水质指标字段为动态生成,通过索引签名访问
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockData = ref<StationData[]>([]);
|
const mockData = ref<StationData[]>([]);
|
||||||
@ -637,17 +538,11 @@ const handleLegendSelectChanged = (params: any) => {
|
|||||||
const clickedName = name;
|
const clickedName = name;
|
||||||
const isNowSelected = selected[clickedName];
|
const isNowSelected = selected[clickedName];
|
||||||
|
|
||||||
console.log(
|
|
||||||
`图例点击: ${clickedName}, 当前状态: ${isNowSelected ? '显示' : '隐藏'}`
|
|
||||||
);
|
|
||||||
console.log('当前可见队列:', visibleSeriesQueue.value);
|
|
||||||
|
|
||||||
if (isNowSelected) {
|
if (isNowSelected) {
|
||||||
// 用户想要显示这个系列
|
// 用户想要显示这个系列
|
||||||
if (visibleSeriesQueue.value.length >= 2) {
|
if (visibleSeriesQueue.value.length >= 2) {
|
||||||
// 已满2个,移除最早的(队列头部)
|
// 已满2个,移除最早的(队列头部)
|
||||||
const removed = visibleSeriesQueue.value.shift();
|
visibleSeriesQueue.value.shift();
|
||||||
console.log(`已达上限,移除最早系列: ${removed}`);
|
|
||||||
}
|
}
|
||||||
// 添加到队列尾部
|
// 添加到队列尾部
|
||||||
if (!visibleSeriesQueue.value.includes(clickedName)) {
|
if (!visibleSeriesQueue.value.includes(clickedName)) {
|
||||||
@ -658,135 +553,23 @@ const handleLegendSelectChanged = (params: any) => {
|
|||||||
const index = visibleSeriesQueue.value.indexOf(clickedName);
|
const index = visibleSeriesQueue.value.indexOf(clickedName);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
visibleSeriesQueue.value.splice(index, 1);
|
visibleSeriesQueue.value.splice(index, 1);
|
||||||
console.log(`从队列中移除: ${clickedName}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('更新后可见队列:', visibleSeriesQueue.value);
|
// 基于最新队列重新生成完整配置(所有 y 轴都带颜色/formatter/min-max,
|
||||||
|
// 避免从 getOption() 快照激活隐藏 y 轴时丢失样式导致颜色消失、宽度变化)
|
||||||
// 根据队列构建最终的 selected 状态
|
|
||||||
const finalSelected: Record<string, boolean> = {};
|
|
||||||
indicatorConfig.value.forEach(config => {
|
|
||||||
finalSelected[config.name] = visibleSeriesQueue.value.includes(config.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 深拷贝当前配置
|
|
||||||
const currentOption = JSON.parse(JSON.stringify(chartInstance.getOption()));
|
|
||||||
|
|
||||||
// 同步图例选中状态
|
|
||||||
currentOption.legend[0].selected = finalSelected;
|
|
||||||
|
|
||||||
// 根据图例状态更新 Y 轴显隐
|
|
||||||
const newYAxis = currentOption.yAxis.map((item: any) => {
|
|
||||||
let isShow = false;
|
|
||||||
let displayIndex = 0;
|
|
||||||
|
|
||||||
// 计算这是第几个显示的Y轴
|
|
||||||
for (const key in finalSelected) {
|
|
||||||
if (finalSelected[key]) {
|
|
||||||
const config = indicatorConfig.value.find(c => c.name === key);
|
|
||||||
if (config && config.name === item.name) {
|
|
||||||
isShow = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
displayIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最多只显示2个Y轴
|
|
||||||
if (!isShow || displayIndex >= 2) {
|
|
||||||
return { ...item, show: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...item, show: true };
|
|
||||||
});
|
|
||||||
|
|
||||||
currentOption.yAxis = newYAxis;
|
|
||||||
|
|
||||||
// 动态调整 Y 轴布局
|
|
||||||
yAxisShowDynamic(finalSelected, currentOption);
|
|
||||||
|
|
||||||
// 完全替换模式更新
|
|
||||||
try {
|
try {
|
||||||
chartInstance.setOption(currentOption, true);
|
const option = getChartOption();
|
||||||
console.log('图表配置已更新');
|
chartInstance.setOption(option, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('图表更新失败:', error);
|
console.error('图表更新失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== Y轴动态布局调整 ====================
|
|
||||||
const yAxisShowDynamic = (selected: Record<string, boolean>, options: any) => {
|
|
||||||
const allShow = options.yAxis?.filter((item: any) => item.show);
|
|
||||||
const showCount = allShow?.length || 0;
|
|
||||||
|
|
||||||
// 没有显示的Y轴
|
|
||||||
if (showCount === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只有一个Y轴:统一置于左侧
|
|
||||||
if (showCount === 1) {
|
|
||||||
delete options.grid.right;
|
|
||||||
options.grid.left = '80px';
|
|
||||||
|
|
||||||
options.yAxis = options.yAxis.map((item: any) => {
|
|
||||||
if (item.show) {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
position: 'left',
|
|
||||||
offset: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 两个Y轴:左侧1个,右侧1个
|
|
||||||
if (showCount >= 2) {
|
|
||||||
let leftAssigned = false;
|
|
||||||
let rightAssigned = false;
|
|
||||||
|
|
||||||
options.yAxis = options.yAxis.map((item: any) => {
|
|
||||||
if (!item.show) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第一个可见的Y轴放在左侧
|
|
||||||
if (!leftAssigned) {
|
|
||||||
leftAssigned = true;
|
|
||||||
delete options.grid.right;
|
|
||||||
options.grid.left = '80px';
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
position: 'left',
|
|
||||||
offset: 0
|
|
||||||
};
|
|
||||||
} else if (!rightAssigned) {
|
|
||||||
// 第二个Y轴放在右侧
|
|
||||||
rightAssigned = true;
|
|
||||||
options.grid.right = '60px';
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
position: 'right',
|
|
||||||
offset: 0
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// 第三个及以后的Y轴隐藏(理论上不应该到这里)
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
show: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ==================== 数据点点击事件处理 ====================
|
// ==================== 数据点点击事件处理 ====================
|
||||||
const handleDataPointClick = (params: any) => {
|
const handleDataPointClick = (params: any) => {
|
||||||
if (!params || !params.dataIndex) return;
|
// dataIndex 为 0 时不能直接取反判断,必须判断 null/undefined
|
||||||
|
if (!params || params.dataIndex == null) return;
|
||||||
|
|
||||||
// 获取点击的数据点对应的站点信息
|
// 获取点击的数据点对应的站点信息
|
||||||
const dataIndex = params.dataIndex;
|
const dataIndex = params.dataIndex;
|
||||||
@ -796,7 +579,6 @@ const handleDataPointClick = (params: any) => {
|
|||||||
modelStore.modalVisible = true;
|
modelStore.modalVisible = true;
|
||||||
modelStore.params.sttp = 'WQFB';
|
modelStore.params.sttp = 'WQFB';
|
||||||
modelStore.title = stationData.stnm;
|
modelStore.title = stationData.stnm;
|
||||||
// modelStore.isBasicEdit = true;
|
|
||||||
modelStore.params.stcd = stationData.stcd;
|
modelStore.params.stcd = stationData.stcd;
|
||||||
modelStore.filter.rangeTm = [
|
modelStore.filter.rangeTm = [
|
||||||
dayjs(datetimePicker.value.value)
|
dayjs(datetimePicker.value.value)
|
||||||
@ -821,8 +603,6 @@ const getecharts = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('开始获取图表数据,select.value:', select.value.value);
|
|
||||||
|
|
||||||
// 设置 loading 状态
|
// 设置 loading 状态
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
hasData.value = false;
|
hasData.value = false;
|
||||||
@ -882,8 +662,6 @@ const getecharts = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('原始数据条数:', apiData.length);
|
|
||||||
|
|
||||||
// 过滤出有水质数据的站点(sttp === 'WQ')并按sort排序
|
// 过滤出有水质数据的站点(sttp === 'WQ')并按sort排序
|
||||||
const filteredData = apiData
|
const filteredData = apiData
|
||||||
.filter((item: any) => item.sttp === 'WQ')
|
.filter((item: any) => item.sttp === 'WQ')
|
||||||
@ -931,47 +709,20 @@ const getecharts = async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 动态收集指标字段(基于 indicators 配置,避免硬编码 30+ 个字段映射)
|
||||||
|
const indicatorValues: Record<string, number | null> = {};
|
||||||
|
indicators.forEach(ind => {
|
||||||
|
const val = item[ind.key];
|
||||||
|
indicatorValues[ind.key] = val != null ? Number(val) : null;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stnm: stnm,
|
stnm: stnm,
|
||||||
stcd: item.stcd || '',
|
stcd: item.stcd || '',
|
||||||
type: item.rstcd ? '1' : '0', // 有rstcd表示是电站出库站
|
type: item.rstcd ? '1' : '0', // 有rstcd表示是电站出库站
|
||||||
tm: item.tm || '',
|
tm: item.tm || '',
|
||||||
SORT: index + 1,
|
SORT: index + 1,
|
||||||
ph: item.ph != null ? Number(item.ph) : null,
|
...indicatorValues,
|
||||||
dox: item.dox != null ? Number(item.dox) : null,
|
|
||||||
codmn: item.codmn != null ? Number(item.codmn) : null,
|
|
||||||
codcr: item.codcr != null ? Number(item.codcr) : null,
|
|
||||||
bod5: item.bod5 != null ? Number(item.bod5) : null,
|
|
||||||
nh3n: item.nh3n != null ? Number(item.nh3n) : null,
|
|
||||||
tp: item.tp != null ? Number(item.tp) : null,
|
|
||||||
tn: item.tn != null ? Number(item.tn) : null,
|
|
||||||
cu: item.cu != null ? Number(item.cu) : null,
|
|
||||||
zn: item.zn != null ? Number(item.zn) : null,
|
|
||||||
f: item.f != null ? Number(item.f) : null,
|
|
||||||
se: item.se != null ? Number(item.se) : null,
|
|
||||||
ars: item.ars != null ? Number(item.ars) : null,
|
|
||||||
hg: item.hg != null ? Number(item.hg) : null,
|
|
||||||
cd: item.cd != null ? Number(item.cd) : null,
|
|
||||||
cr6: item.cr6 != null ? Number(item.cr6) : null,
|
|
||||||
pb: item.pb != null ? Number(item.pb) : null,
|
|
||||||
cn: item.cn != null ? Number(item.cn) : null,
|
|
||||||
vlph: item.vlph != null ? Number(item.vlph) : null,
|
|
||||||
oil: item.oil != null ? Number(item.oil) : null,
|
|
||||||
las: item.las != null ? Number(item.las) : null,
|
|
||||||
s2: item.s2 != null ? Number(item.s2) : null,
|
|
||||||
fcg: item.fcg != null ? Number(item.fcg) : null,
|
|
||||||
cl: item.cl != null ? Number(item.cl) : null,
|
|
||||||
so4: item.so4 != null ? Number(item.so4) : null,
|
|
||||||
no3: item.no3 != null ? Number(item.no3) : null,
|
|
||||||
thrd: item.thrd != null ? Number(item.thrd) : null,
|
|
||||||
cond: item.cond != null ? Number(item.cond) : null,
|
|
||||||
fe: item.fe != null ? Number(item.fe) : null,
|
|
||||||
mn: item.mn != null ? Number(item.mn) : null,
|
|
||||||
al: item.al != null ? Number(item.al) : null,
|
|
||||||
chla: item.chla != null ? Number(item.chla) : null,
|
|
||||||
clarity: item.clarity != null ? Number(item.clarity) : null,
|
|
||||||
tu: item.tu != null ? Number(item.tu) : null,
|
|
||||||
wtmp: item.wtmp != null ? Number(item.wtmp) : null,
|
|
||||||
limits: limits // 新增:存储该站点的所有限值
|
limits: limits // 新增:存储该站点的所有限值
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -992,12 +743,42 @@ const getecharts = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取水质参数配置(showControl.lineColor),用于指标颜色
|
||||||
|
// 参考 WaterQuality.vue:颜色来自接口返回的每个参数配置
|
||||||
|
try {
|
||||||
|
const configStcd = modelStore.params.stcd || chartData[0]?.stcd;
|
||||||
|
if (configStcd) {
|
||||||
|
const detailRes = await getMonitorDataWaterQualityDetail({
|
||||||
|
stcd: configStcd,
|
||||||
|
tbCode: 'WQ_R',
|
||||||
|
startTime: getStartTime(),
|
||||||
|
endTime: getEndTime()
|
||||||
|
});
|
||||||
|
const detailData = detailRes?.data?.data || detailRes?.data || [];
|
||||||
|
const map: Record<string, string> = {};
|
||||||
|
detailData.forEach((param: any) => {
|
||||||
|
if (!param.ys) return;
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(param.showControl || '{}');
|
||||||
|
if (config.lineColor) {
|
||||||
|
map[param.ys.toLowerCase()] = config.lineColor;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 解析失败忽略该参数
|
||||||
|
}
|
||||||
|
});
|
||||||
|
indicatorColorMap.value = map;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 配置获取失败不影响主流程,颜色走色板回退
|
||||||
|
console.error('获取水质参数配置失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
// 动态生成指标配置
|
// 动态生成指标配置
|
||||||
generateIndicatorConfig(chartData);
|
generateIndicatorConfig(chartData);
|
||||||
|
|
||||||
// 如果没有有效指标,显示空状态
|
// 如果没有有效指标,显示空状态
|
||||||
if (indicatorConfig.value.length === 0) {
|
if (indicatorConfig.value.length === 0) {
|
||||||
console.log('没有有效指标,显示空状态');
|
|
||||||
hasData.value = false;
|
hasData.value = false;
|
||||||
chartInstance?.clear();
|
chartInstance?.clear();
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -1101,7 +882,6 @@ const initChart = async () => {
|
|||||||
|
|
||||||
// 初始化可见队列:默认只显示第一个系列(按sort排序后的第一个)
|
// 初始化可见队列:默认只显示第一个系列(按sort排序后的第一个)
|
||||||
visibleSeriesQueue.value = [indicatorConfig.value[0].name];
|
visibleSeriesQueue.value = [indicatorConfig.value[0].name];
|
||||||
console.log('初始化可见队列:', visibleSeriesQueue.value);
|
|
||||||
|
|
||||||
// 初始化 ECharts 实例
|
// 初始化 ECharts 实例
|
||||||
chartInstance = echarts.init(chartRef.value);
|
chartInstance = echarts.init(chartRef.value);
|
||||||
@ -1178,7 +958,6 @@ const wbsCode = ref('');
|
|||||||
watch(
|
watch(
|
||||||
() => stationSource.value,
|
() => stationSource.value,
|
||||||
newVal => {
|
newVal => {
|
||||||
console.log(newVal);
|
|
||||||
wbsCode.value = newVal.wbsCode;
|
wbsCode.value = newVal.wbsCode;
|
||||||
select.value.value = newVal.wbsCode;
|
select.value.value = newVal.wbsCode;
|
||||||
getecharts();
|
getecharts();
|
||||||
@ -1199,22 +978,7 @@ watch(
|
|||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// 监听可见系列数量变化,重新设置图表配置以确保 tooltip formatter 使用最新的 visibleSeriesQueue 值
|
// 图例切换时 handleLegendSelectChanged 已基于最新队列重建完整配置,此处无需再监听
|
||||||
watch(
|
|
||||||
() => visibleSeriesQueue.value.length,
|
|
||||||
(newLength, oldLength) => {
|
|
||||||
// 只有当长度真正变化时才重新设置
|
|
||||||
if (
|
|
||||||
newLength !== oldLength &&
|
|
||||||
chartInstance &&
|
|
||||||
indicatorConfig.value.length > 0
|
|
||||||
) {
|
|
||||||
// 重新生成 option(这会创建新的 formatter 闭包,捕获最新的 visibleSeriesQueue)
|
|
||||||
const newOption = getChartOption();
|
|
||||||
chartInstance.setOption(newOption, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -1,404 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="w-full h-full flex flex-col">
|
|
||||||
<!-- 搜索组件 -->
|
|
||||||
<FishDataSearch
|
|
||||||
mway="2"
|
|
||||||
:exportLoading="exportLoading"
|
|
||||||
@export-btn="exportBtn"
|
|
||||||
@search-finish="onSearchFinish"
|
|
||||||
@reset="onReset"
|
|
||||||
ref="searchRef"
|
|
||||||
/>
|
|
||||||
<!-- 表格组件 -->
|
|
||||||
<BasicTable
|
|
||||||
ref="tableRef"
|
|
||||||
:scrollX="tableScrollX"
|
|
||||||
:scrollY="tableScrollY"
|
|
||||||
:columns="tableColumns"
|
|
||||||
:list-url="getFishReleaseMonitorList"
|
|
||||||
:searchParams="{
|
|
||||||
sort: sort
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</BasicTable>
|
|
||||||
|
|
||||||
<!-- 图片预览弹框 -->
|
|
||||||
<a-modal
|
|
||||||
v-model:open="imagePreviewVisible"
|
|
||||||
title="图片预览"
|
|
||||||
:footer="null"
|
|
||||||
width="800px"
|
|
||||||
>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<img :src="previewImageUrl" style="width: 100%; height: 600px" />
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
|
|
||||||
<!-- 视频预览弹框 -->
|
|
||||||
<a-modal
|
|
||||||
v-model:open="videoPreviewVisible"
|
|
||||||
title="视频预览"
|
|
||||||
:footer="null"
|
|
||||||
width="800px"
|
|
||||||
>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<video
|
|
||||||
:src="previewVideoUrl"
|
|
||||||
controls
|
|
||||||
style="width: 100%; height: 600px"
|
|
||||||
></video>
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import FishDataSearch from './FishDataSearch.vue';
|
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
|
||||||
import { getFishReleaseMonitorList } from '@/api/DataQueryMenuModule';
|
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
|
||||||
import { useDraggable } from '@/utils/drag';
|
|
||||||
|
|
||||||
const sort = ref<any>([
|
|
||||||
{
|
|
||||||
field: 'baseStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'rvcdStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'rstcdStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'ttpwr',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
dir: 'desc'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
const tableRef = ref();
|
|
||||||
const searchRef = ref();
|
|
||||||
const tableScrollY = ref<string | number>(0);
|
|
||||||
const currentSearchParams = ref<any>({});
|
|
||||||
const exportLoading = ref(false);
|
|
||||||
|
|
||||||
// 图片预览
|
|
||||||
const imagePreviewVisible = ref(false);
|
|
||||||
const previewImageUrl = ref('');
|
|
||||||
// 视频预览
|
|
||||||
const videoPreviewVisible = ref(false);
|
|
||||||
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_URL1 + '/?' + url;
|
|
||||||
imagePreviewVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const openVideoPreview = (url: string) => {
|
|
||||||
previewVideoUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL1 + '/?' + url;
|
|
||||||
videoPreviewVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableScrollX = computed(() =>
|
|
||||||
Math.max(
|
|
||||||
tableColumns.value.reduce(
|
|
||||||
(sum: number, col: any) => sum + (col.width || 180),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
600
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 自动列
|
|
||||||
const tableColumns = ref<any[]>([
|
|
||||||
{
|
|
||||||
key: 'stnm',
|
|
||||||
title: '电站名称',
|
|
||||||
dataIndex: 'stnm',
|
|
||||||
visible: true,
|
|
||||||
width: 200,
|
|
||||||
sort: true,
|
|
||||||
merge: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'baseName',
|
|
||||||
title: '所属基地',
|
|
||||||
dataIndex: 'baseName',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'stnm',
|
|
||||||
title: '过鱼设施名称',
|
|
||||||
dataIndex: 'stnm',
|
|
||||||
sort: true,
|
|
||||||
width: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'tm',
|
|
||||||
title: '时间',
|
|
||||||
dataIndex: 'tm',
|
|
||||||
sort: true,
|
|
||||||
width: 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ftp',
|
|
||||||
title: '鱼种类',
|
|
||||||
dataIndex: 'ftp',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'length',
|
|
||||||
title: '鱼长度(cm)',
|
|
||||||
dataIndex: 'length',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'width',
|
|
||||||
title: '鱼宽度(cm)',
|
|
||||||
dataIndex: 'width',
|
|
||||||
sort: true,
|
|
||||||
width: 120,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const num = parseFloat(text);
|
|
||||||
return isNaN(num) ? '-' : num.toFixed(2);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'fishspeed',
|
|
||||||
title: '鱼速度(m/s)',
|
|
||||||
dataIndex: 'fishspeed',
|
|
||||||
sort: true,
|
|
||||||
width: 80,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const num = parseFloat(text);
|
|
||||||
return isNaN(num) ? '-' : num.toFixed(2);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'directionName',
|
|
||||||
title: '鱼游向',
|
|
||||||
dataIndex: 'directionName',
|
|
||||||
sort: true,
|
|
||||||
width: 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'temperature',
|
|
||||||
title: '水温(℃)',
|
|
||||||
dataIndex: 'temperature',
|
|
||||||
sort: true,
|
|
||||||
width: 160,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const num = parseFloat(text);
|
|
||||||
return isNaN(num) ? '-' : num.toFixed(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'speed',
|
|
||||||
title: '流速(m/s)',
|
|
||||||
dataIndex: 'speed',
|
|
||||||
sort: true,
|
|
||||||
width: 160,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const num = parseFloat(text);
|
|
||||||
return isNaN(num) ? '-' : num.toFixed(2);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'fcnt',
|
|
||||||
title: '过鱼数量(尾)',
|
|
||||||
dataIndex: 'fcnt',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'channelno',
|
|
||||||
title: '过鱼通道',
|
|
||||||
dataIndex: 'channelno',
|
|
||||||
sort: true,
|
|
||||||
width: 140
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'firstimgurl',
|
|
||||||
title: '轮廓图',
|
|
||||||
dataIndex: 'firstimgurl',
|
|
||||||
align: 'center',
|
|
||||||
width: 110,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const hasValidUrl =
|
|
||||||
text &&
|
|
||||||
text !== '-' &&
|
|
||||||
text.trim() !== '' &&
|
|
||||||
text !== 'null' &&
|
|
||||||
text !== 'undefined';
|
|
||||||
return h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
disabled: !hasValidUrl,
|
|
||||||
style: {
|
|
||||||
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
|
||||||
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
if (hasValidUrl) {
|
|
||||||
openImagePreview(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => '查看图片'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'videourl',
|
|
||||||
title: '短视频',
|
|
||||||
dataIndex: 'videourl',
|
|
||||||
align: 'center',
|
|
||||||
width: 110,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const hasValidUrl =
|
|
||||||
text &&
|
|
||||||
text !== '-' &&
|
|
||||||
text.trim() !== '' &&
|
|
||||||
text !== 'null' &&
|
|
||||||
text !== 'undefined';
|
|
||||||
return h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
disabled: !hasValidUrl,
|
|
||||||
style: {
|
|
||||||
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
|
||||||
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
if (hasValidUrl) {
|
|
||||||
openVideoPreview(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => '查看视频'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const onSearchFinish = async (values: any) => {
|
|
||||||
currentSearchParams.value = values;
|
|
||||||
// 等待 prop 更新后再调用 getList
|
|
||||||
await nextTick();
|
|
||||||
initTable(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onReset = async (values: any) => {
|
|
||||||
currentSearchParams.value = values;
|
|
||||||
await nextTick();
|
|
||||||
initTable(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportBtn = () => {
|
|
||||||
if (exportLoading.value) return;
|
|
||||||
|
|
||||||
exportLoading.value = true;
|
|
||||||
searchRef.value.btnLoading = true;
|
|
||||||
|
|
||||||
tableRef.value
|
|
||||||
.exportTable({
|
|
||||||
fileName: `过鱼监测数据_自动_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
exportLoading.value = false;
|
|
||||||
searchRef.value.btnLoading = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const initTable = (values: any) => {
|
|
||||||
console.log(values);
|
|
||||||
const filters = [
|
|
||||||
values.rvcd && values.rvcd !== 'all'
|
|
||||||
? {
|
|
||||||
field: 'rvcd',
|
|
||||||
operator: 'contains',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.rvcd
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
values.rstcd
|
|
||||||
? {
|
|
||||||
field: 'rstcd',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.rstcd
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
values.mway
|
|
||||||
? {
|
|
||||||
field: 'mway',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.mway
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
].filter(Boolean); // 移除空项
|
|
||||||
if (values.stcd) {
|
|
||||||
filters.push({
|
|
||||||
logic: 'or',
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
field: 'stcd',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.stcd
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'stcode',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.stcd
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (values.jcdt) {
|
|
||||||
filters.push(
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
operator: 'gte',
|
|
||||||
dataType: 'date',
|
|
||||||
value: dayjs(values.jcdt.min).format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
operator: 'lte',
|
|
||||||
dataType: 'date',
|
|
||||||
value: dayjs(values.jcdt.max).format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
logic: 'and',
|
|
||||||
filters
|
|
||||||
};
|
|
||||||
tableRef.value.getList(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
tableScrollY.value = calcTableScrollY();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@ -1,369 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="w-full h-full flex flex-col">
|
|
||||||
<!-- 搜索组件 -->
|
|
||||||
<FishDataSearch
|
|
||||||
mway="1"
|
|
||||||
:exportLoading="exportLoading"
|
|
||||||
@export-btn="exportBtn"
|
|
||||||
@search-finish="onSearchFinish"
|
|
||||||
@reset="onReset"
|
|
||||||
ref="searchRef"
|
|
||||||
/>
|
|
||||||
<!-- 表格组件 -->
|
|
||||||
<BasicTable
|
|
||||||
ref="tableRef"
|
|
||||||
:scrollX="tableScrollX"
|
|
||||||
:scrollY="tableScrollY"
|
|
||||||
:columns="tableColumns"
|
|
||||||
:list-url="getFishReleaseMonitorList"
|
|
||||||
:searchParams="{
|
|
||||||
sort: sort
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</BasicTable>
|
|
||||||
|
|
||||||
<!-- 图片预览弹框 -->
|
|
||||||
<a-modal
|
|
||||||
v-model:open="imagePreviewVisible"
|
|
||||||
title="图片预览"
|
|
||||||
:footer="null"
|
|
||||||
width="800px"
|
|
||||||
>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<img :src="previewImageUrl" style="width: 100%; height: 600px" />
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
|
|
||||||
<!-- 视频预览弹框 -->
|
|
||||||
<a-modal
|
|
||||||
v-model:open="videoPreviewVisible"
|
|
||||||
title="视频预览"
|
|
||||||
:footer="null"
|
|
||||||
width="800px"
|
|
||||||
>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<video
|
|
||||||
:src="previewVideoUrl"
|
|
||||||
controls
|
|
||||||
style="width: 100%; height: 600px"
|
|
||||||
></video>
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import FishDataSearch from './FishDataSearch.vue';
|
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
|
||||||
import { getFishReleaseMonitorList } from '@/api/DataQueryMenuModule';
|
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
|
||||||
import { useDraggable } from '@/utils/drag';
|
|
||||||
|
|
||||||
const sort = ref<any>([
|
|
||||||
{
|
|
||||||
field: 'baseStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'rvcdStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'rstcdStepSort',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'ttpwr',
|
|
||||||
dir: 'asc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
dir: 'desc'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
const tableRef = ref();
|
|
||||||
const searchRef = ref();
|
|
||||||
const tableScrollY = ref<string | number>(0);
|
|
||||||
const currentSearchParams = ref<any>({});
|
|
||||||
const exportLoading = ref(false);
|
|
||||||
|
|
||||||
// 图片预览
|
|
||||||
const imagePreviewVisible = ref(false);
|
|
||||||
const previewImageUrl = ref('');
|
|
||||||
// 视频预览
|
|
||||||
const videoPreviewVisible = ref(false);
|
|
||||||
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_URL1 + '/?' + url;
|
|
||||||
imagePreviewVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const openVideoPreview = (url: string) => {
|
|
||||||
previewVideoUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL1 + '/?' + url;
|
|
||||||
videoPreviewVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableScrollX = computed(() =>
|
|
||||||
Math.max(
|
|
||||||
tableColumns.value.reduce(
|
|
||||||
(sum: number, col: any) => sum + (col.width || 180),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
600
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 人工列
|
|
||||||
const tableColumns = ref<any[]>([
|
|
||||||
{
|
|
||||||
key: 'stnm',
|
|
||||||
title: '电站名称',
|
|
||||||
dataIndex: 'stnm',
|
|
||||||
visible: true,
|
|
||||||
width: 200,
|
|
||||||
sort: true,
|
|
||||||
merge: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'baseName',
|
|
||||||
title: '所属基地',
|
|
||||||
dataIndex: 'baseName',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'stnm',
|
|
||||||
title: '过鱼设施名称',
|
|
||||||
dataIndex: 'stnm',
|
|
||||||
sort: true,
|
|
||||||
width: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'strdt',
|
|
||||||
title: '开始时间',
|
|
||||||
dataIndex: 'strdt',
|
|
||||||
sort: true,
|
|
||||||
width: 100,
|
|
||||||
customRender: ({ text }) => dayjs(text).format('YYYY-MM-DD') || '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'enddt',
|
|
||||||
title: '结束时间',
|
|
||||||
dataIndex: 'enddt',
|
|
||||||
sort: true,
|
|
||||||
width: 100,
|
|
||||||
customRender: ({ text }) => dayjs(text).format('YYYY-MM-DD') || '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'tm',
|
|
||||||
title: '填报时间',
|
|
||||||
dataIndex: 'tm',
|
|
||||||
sort: true,
|
|
||||||
width: 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ftp',
|
|
||||||
title: '鱼种类',
|
|
||||||
dataIndex: 'ftp',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'fcnt',
|
|
||||||
title: '过鱼数量(尾)',
|
|
||||||
dataIndex: 'fcnt',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'directionName',
|
|
||||||
title: '鱼游向',
|
|
||||||
dataIndex: 'directionName',
|
|
||||||
sort: true,
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'fsz',
|
|
||||||
title: '鱼类规格',
|
|
||||||
dataIndex: 'fsz',
|
|
||||||
sort: true,
|
|
||||||
width: 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'firstimgurl',
|
|
||||||
title: '图片',
|
|
||||||
dataIndex: 'firstimgurl',
|
|
||||||
align: 'center',
|
|
||||||
width: 110,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const hasValidUrl =
|
|
||||||
text &&
|
|
||||||
text !== '-' &&
|
|
||||||
text.trim() !== '' &&
|
|
||||||
text !== 'null' &&
|
|
||||||
text !== 'undefined';
|
|
||||||
return h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
disabled: !hasValidUrl,
|
|
||||||
style: {
|
|
||||||
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
|
||||||
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
if (hasValidUrl) {
|
|
||||||
openImagePreview(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => '查看图片'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'videourl',
|
|
||||||
title: '视频',
|
|
||||||
dataIndex: 'videourl',
|
|
||||||
align: 'center',
|
|
||||||
width: 110,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
const hasValidUrl =
|
|
||||||
text &&
|
|
||||||
text !== '-' &&
|
|
||||||
text.trim() !== '' &&
|
|
||||||
text !== 'null' &&
|
|
||||||
text !== 'undefined';
|
|
||||||
return h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
disabled: !hasValidUrl,
|
|
||||||
style: {
|
|
||||||
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
|
||||||
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
if (hasValidUrl) {
|
|
||||||
openVideoPreview(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => '查看视频'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const onSearchFinish = async (values: any) => {
|
|
||||||
currentSearchParams.value = values;
|
|
||||||
// 等待 prop 更新后再调用 getList
|
|
||||||
await nextTick();
|
|
||||||
initTable(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onReset = async (values: any) => {
|
|
||||||
currentSearchParams.value = values;
|
|
||||||
await nextTick();
|
|
||||||
initTable(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportBtn = () => {
|
|
||||||
if (exportLoading.value) return;
|
|
||||||
|
|
||||||
exportLoading.value = true;
|
|
||||||
searchRef.value.btnLoading = true;
|
|
||||||
|
|
||||||
tableRef.value
|
|
||||||
.exportTable({
|
|
||||||
fileName: `过鱼监测数据_人工_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
exportLoading.value = false;
|
|
||||||
searchRef.value.btnLoading = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const initTable = (values: any) => {
|
|
||||||
console.log(values);
|
|
||||||
const filters = [
|
|
||||||
values.rvcd && values.rvcd !== 'all'
|
|
||||||
? {
|
|
||||||
field: 'rvcd',
|
|
||||||
operator: 'contains',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.rvcd
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
values.rstcd
|
|
||||||
? {
|
|
||||||
field: 'rstcd',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.rstcd
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
values.mway
|
|
||||||
? {
|
|
||||||
field: 'mway',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.mway
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
].filter(Boolean); // 移除空项
|
|
||||||
if (values.stcd) {
|
|
||||||
filters.push({
|
|
||||||
logic: 'or',
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
field: 'stcd',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.stcd
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'stcode',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: values.stcd
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (values.jcdt) {
|
|
||||||
filters.push(
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
operator: 'gte',
|
|
||||||
dataType: 'date',
|
|
||||||
value: dayjs(values.jcdt.min).format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
operator: 'lte',
|
|
||||||
dataType: 'date',
|
|
||||||
value: dayjs(values.jcdt.max).format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
logic: 'and',
|
|
||||||
filters
|
|
||||||
};
|
|
||||||
tableRef.value.getList(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
tableScrollY.value = calcTableScrollY();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@ -80,13 +80,12 @@ const btnLoading = ref<boolean>(false);
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
exportLoading?: boolean;
|
exportLoading?: boolean;
|
||||||
mway: string;
|
|
||||||
}>();
|
}>();
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
rstcd: null,
|
rstcd: null,
|
||||||
stcd: '0086601020VP000006',
|
stcd: '0086601020VP000006',
|
||||||
mway: props.mway
|
mway: '1'
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchData = ref<any>({ ...initSearchData });
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
@ -134,6 +133,22 @@ const searchList: any = computed(() => [
|
|||||||
value: item.stcd
|
value: item.stcd
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'Radio',
|
||||||
|
name: 'mway',
|
||||||
|
label: '数据类型',
|
||||||
|
width: 140,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '人工',
|
||||||
|
value: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '自动',
|
||||||
|
value: '2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
name: 'jcdt',
|
name: 'jcdt',
|
||||||
|
|||||||
@ -1,42 +1,551 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full flex flex-col">
|
||||||
<a-tabs
|
<!-- 搜索组件 -->
|
||||||
v-model:activeKey="activeTab"
|
<FishDataSearch
|
||||||
class="fish-tabs"
|
@export-btn="exportBtn"
|
||||||
|
@search-finish="onSearchFinish"
|
||||||
|
@reset="onReset"
|
||||||
|
:exportLoading="exportLoading"
|
||||||
|
ref="searchRef"
|
||||||
|
/>
|
||||||
|
<!-- 表格组件 -->
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:scrollY="tableScrollY"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:list-url="getFishReleaseMonitorList"
|
||||||
|
:searchParams="{
|
||||||
|
sort: sort
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<a-tab-pane key="manual" tab="人工站点">
|
</BasicTable>
|
||||||
<FishDataManual />
|
|
||||||
</a-tab-pane>
|
<!-- 图片预览弹框 -->
|
||||||
<a-tab-pane key="auto" tab="自动站点">
|
<a-modal
|
||||||
<FishDataAuto />
|
v-model:open="imagePreviewVisible"
|
||||||
</a-tab-pane>
|
title="图片预览"
|
||||||
</a-tabs>
|
:footer="null"
|
||||||
|
width="800px"
|
||||||
|
>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<img :src="previewImageUrl" style="width: 100%; height: 600px" />
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 视频预览弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="videoPreviewVisible"
|
||||||
|
title="视频预览"
|
||||||
|
:footer="null"
|
||||||
|
width="800px"
|
||||||
|
>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<video
|
||||||
|
:src="previewVideoUrl"
|
||||||
|
controls
|
||||||
|
style="width: 100%; height: 600px"
|
||||||
|
></video>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
||||||
import FishDataManual from './FishDataManual.vue';
|
import dayjs from 'dayjs';
|
||||||
import FishDataAuto from './FishDataAuto.vue';
|
import { Button } from 'ant-design-vue';
|
||||||
|
import FishDataSearch from './FishDataSearch.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { getFishReleaseMonitorList } from '@/api/DataQueryMenuModule';
|
||||||
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
const activeTab = ref('manual');
|
const sort = ref<any>([
|
||||||
|
{
|
||||||
|
field: 'baseStepSort',
|
||||||
|
dir: 'asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rvcdStepSort',
|
||||||
|
dir: 'asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rstcdStepSort',
|
||||||
|
dir: 'asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ttpwr',
|
||||||
|
dir: 'asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
dir: 'desc'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const tableRef = ref();
|
||||||
|
const searchRef = ref();
|
||||||
|
const tableScrollY = ref<string | number>(0);
|
||||||
|
const currentSearchParams = ref<any>({});
|
||||||
|
const exportLoading = ref(false);
|
||||||
|
|
||||||
|
// 图片预览
|
||||||
|
const imagePreviewVisible = ref(false);
|
||||||
|
const previewImageUrl = ref('');
|
||||||
|
// 视频预览
|
||||||
|
const videoPreviewVisible = ref(false);
|
||||||
|
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_URL1 + '/?' + url;
|
||||||
|
imagePreviewVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openVideoPreview = (url: string) => {
|
||||||
|
previewVideoUrl.value = import.meta.env.VITE_APP_ATTACHMENT_URL1 + '/?' + url;
|
||||||
|
videoPreviewVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableScrollX = computed(() =>
|
||||||
|
Math.max(
|
||||||
|
tableColumns.value.reduce(
|
||||||
|
(sum: number, col: any) => sum + (col.width || 180),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
600
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 人工列
|
||||||
|
const columnsManual: any[] = [
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '电站名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
visible: true,
|
||||||
|
width: 200,
|
||||||
|
sort: true,
|
||||||
|
merge: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'baseName',
|
||||||
|
title: '所属基地',
|
||||||
|
dataIndex: 'baseName',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '过鱼设施名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
sort: true,
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'strdt',
|
||||||
|
title: '开始时间',
|
||||||
|
dataIndex: 'strdt',
|
||||||
|
sort: true,
|
||||||
|
width: 100,
|
||||||
|
customRender: ({ text }) => dayjs(text).format('YYYY-MM-DD') || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enddt',
|
||||||
|
title: '结束时间',
|
||||||
|
dataIndex: 'enddt',
|
||||||
|
sort: true,
|
||||||
|
width: 100,
|
||||||
|
customRender: ({ text }) => dayjs(text).format('YYYY-MM-DD') || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tm',
|
||||||
|
title: '填报时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
sort: true,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ftp',
|
||||||
|
title: '鱼种类',
|
||||||
|
dataIndex: 'ftp',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fcnt',
|
||||||
|
title: '过鱼数量(尾)',
|
||||||
|
dataIndex: 'fcnt',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'directionName',
|
||||||
|
title: '鱼游向',
|
||||||
|
dataIndex: 'directionName',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fsz',
|
||||||
|
title: '鱼类规格',
|
||||||
|
dataIndex: 'fsz',
|
||||||
|
sort: true,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'firstimgurl',
|
||||||
|
title: '图片',
|
||||||
|
dataIndex: 'firstimgurl',
|
||||||
|
align: 'center',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const hasValidUrl =
|
||||||
|
text &&
|
||||||
|
text !== '-' &&
|
||||||
|
text.trim() !== '' &&
|
||||||
|
text !== 'null' &&
|
||||||
|
text !== 'undefined';
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
disabled: !hasValidUrl,
|
||||||
|
style: {
|
||||||
|
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
||||||
|
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
if (hasValidUrl) {
|
||||||
|
openImagePreview(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => '查看图片'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'videourl',
|
||||||
|
title: '视频',
|
||||||
|
dataIndex: 'videourl',
|
||||||
|
align: 'center',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const hasValidUrl =
|
||||||
|
text &&
|
||||||
|
text !== '-' &&
|
||||||
|
text.trim() !== '' &&
|
||||||
|
text !== 'null' &&
|
||||||
|
text !== 'undefined';
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
disabled: !hasValidUrl,
|
||||||
|
style: {
|
||||||
|
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
||||||
|
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
if (hasValidUrl) {
|
||||||
|
openVideoPreview(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => '查看视频'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 自动列
|
||||||
|
const columnsAuto: any[] = [
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '电站名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
visible: true,
|
||||||
|
width: 200,
|
||||||
|
sort: true,
|
||||||
|
merge: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'baseName',
|
||||||
|
title: '所属基地',
|
||||||
|
dataIndex: 'baseName',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '过鱼设施名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
sort: true,
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tm',
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
sort: true,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ftp',
|
||||||
|
title: '鱼种类',
|
||||||
|
dataIndex: 'ftp',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'length',
|
||||||
|
title: '鱼长度(cm)',
|
||||||
|
dataIndex: 'length',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'width',
|
||||||
|
title: '鱼宽度(cm)',
|
||||||
|
dataIndex: 'width',
|
||||||
|
sort: true,
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const num = parseFloat(text);
|
||||||
|
return isNaN(num) ? '-' : num.toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fishspeed',
|
||||||
|
title: '鱼速度(m/s)',
|
||||||
|
dataIndex: 'fishspeed',
|
||||||
|
sort: true,
|
||||||
|
width: 80,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const num = parseFloat(text);
|
||||||
|
return isNaN(num) ? '-' : num.toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'directionName',
|
||||||
|
title: '鱼游向',
|
||||||
|
dataIndex: 'directionName',
|
||||||
|
sort: true,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'temperature',
|
||||||
|
title: '水温(℃)',
|
||||||
|
dataIndex: 'temperature',
|
||||||
|
sort: true,
|
||||||
|
width: 160,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const num = parseFloat(text);
|
||||||
|
return isNaN(num) ? '-' : num.toFixed(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'speed',
|
||||||
|
title: '流速(m/s)',
|
||||||
|
dataIndex: 'speed',
|
||||||
|
sort: true,
|
||||||
|
width: 160,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const num = parseFloat(text);
|
||||||
|
return isNaN(num) ? '-' : num.toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fcnt',
|
||||||
|
title: '过鱼数量(尾)',
|
||||||
|
dataIndex: 'fcnt',
|
||||||
|
sort: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'channelno',
|
||||||
|
title: '过鱼通道',
|
||||||
|
dataIndex: 'channelno',
|
||||||
|
sort: true,
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'firstimgurl',
|
||||||
|
title: '轮廓图',
|
||||||
|
dataIndex: 'firstimgurl',
|
||||||
|
align: 'center',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const hasValidUrl =
|
||||||
|
text &&
|
||||||
|
text !== '-' &&
|
||||||
|
text.trim() !== '' &&
|
||||||
|
text !== 'null' &&
|
||||||
|
text !== 'undefined';
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
disabled: !hasValidUrl,
|
||||||
|
style: {
|
||||||
|
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
||||||
|
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
if (hasValidUrl) {
|
||||||
|
openImagePreview(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => '查看图片'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'videourl',
|
||||||
|
title: '短视频',
|
||||||
|
dataIndex: 'videourl',
|
||||||
|
align: 'center',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const hasValidUrl =
|
||||||
|
text &&
|
||||||
|
text !== '-' &&
|
||||||
|
text.trim() !== '' &&
|
||||||
|
text !== 'null' &&
|
||||||
|
text !== 'undefined';
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
disabled: !hasValidUrl,
|
||||||
|
style: {
|
||||||
|
color: !hasValidUrl ? '#ccc' : '#1890ff',
|
||||||
|
cursor: !hasValidUrl ? 'not-allowed' : 'pointer'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
if (hasValidUrl) {
|
||||||
|
openVideoPreview(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => '查看视频'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 根据数据类型动态切换列
|
||||||
|
const tableColumns = computed(() => {
|
||||||
|
const mway = currentSearchParams.value.mway;
|
||||||
|
return mway === '2' ? columnsAuto : columnsManual;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSearchFinish = async (values: any) => {
|
||||||
|
currentSearchParams.value = values;
|
||||||
|
// 等待 prop 更新后再调用 getList
|
||||||
|
await nextTick();
|
||||||
|
initTable(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReset = async (values: any) => {
|
||||||
|
currentSearchParams.value = values;
|
||||||
|
await nextTick();
|
||||||
|
initTable(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportBtn = () => {
|
||||||
|
if (exportLoading.value) return;
|
||||||
|
|
||||||
|
exportLoading.value = true;
|
||||||
|
searchRef.value.btnLoading = true;
|
||||||
|
|
||||||
|
tableRef.value
|
||||||
|
.exportTable({
|
||||||
|
fileName: `过鱼监测数据_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
exportLoading.value = false;
|
||||||
|
searchRef.value.btnLoading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initTable = (values: any) => {
|
||||||
|
console.log(values);
|
||||||
|
const filters = [
|
||||||
|
values.rvcd && values.rvcd !== 'all'
|
||||||
|
? {
|
||||||
|
field: 'rvcd',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.rvcd
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
values.rstcd
|
||||||
|
? {
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.rstcd
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
values.mway
|
||||||
|
? {
|
||||||
|
field: 'mway',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.mway
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
].filter(Boolean); // 移除空项
|
||||||
|
if (values.stcd) {
|
||||||
|
filters.push({
|
||||||
|
logic: 'or',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: values.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (values.jcdt) {
|
||||||
|
filters.push(
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dayjs(values.jcdt.min).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dayjs(values.jcdt.max).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
};
|
||||||
|
tableRef.value.getList(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
tableScrollY.value = calcTableScrollY();
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss"></style>
|
||||||
.fish-tabs {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
// margin: 0 !important;
|
|
||||||
|
|
||||||
:deep(.ant-tabs-content-holder) {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-tabs-content),
|
|
||||||
:deep(.ant-tabs-tabpane) {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -47,6 +47,12 @@ const dialog = ref(false);
|
|||||||
const treeRef = ref();
|
const treeRef = ref();
|
||||||
const fishTreeRef = ref();
|
const fishTreeRef = ref();
|
||||||
const infoForm = ref();
|
const infoForm = ref();
|
||||||
|
//所属组织树型下拉框字段映射
|
||||||
|
const treeSelectProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'orgname',
|
||||||
|
children: 'childList'
|
||||||
|
};
|
||||||
//获取企业树形信息
|
//获取企业树形信息
|
||||||
const treeloading = ref(true);
|
const treeloading = ref(true);
|
||||||
function getTree() {
|
function getTree() {
|
||||||
@ -99,7 +105,8 @@ function addClick() {
|
|||||||
nickname: '',
|
nickname: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
roleinfo: []
|
roleinfo: [],
|
||||||
|
orgid: ''
|
||||||
};
|
};
|
||||||
getrole();
|
getrole();
|
||||||
}
|
}
|
||||||
@ -178,7 +185,8 @@ const info = ref({
|
|||||||
nickname: '',
|
nickname: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
roleinfo: [] as any[]
|
roleinfo: [] as any[],
|
||||||
|
orgid: ''
|
||||||
});
|
});
|
||||||
//修改-用户
|
//修改-用户
|
||||||
function editdepartment(row: any) {
|
function editdepartment(row: any) {
|
||||||
@ -188,6 +196,8 @@ function editdepartment(row: any) {
|
|||||||
});
|
});
|
||||||
info.value = JSON.parse(JSON.stringify(row));
|
info.value = JSON.parse(JSON.stringify(row));
|
||||||
info.value.roleinfo = selectID;
|
info.value.roleinfo = selectID;
|
||||||
|
// 回显所属组织
|
||||||
|
info.value.orgid = row.orgid;
|
||||||
rolesdata.value = row.roles;
|
rolesdata.value = row.roles;
|
||||||
title.value = '修改用户';
|
title.value = '修改用户';
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
@ -207,7 +217,8 @@ function confirmClick(formEl: any) {
|
|||||||
nickname: info.value.nickname,
|
nickname: info.value.nickname,
|
||||||
email: info.value.email,
|
email: info.value.email,
|
||||||
phone: info.value.phone,
|
phone: info.value.phone,
|
||||||
orgid: orgId.value
|
// 修改时根据所属组织树型下拉框传递
|
||||||
|
orgid: info.value.orgid
|
||||||
};
|
};
|
||||||
const roleids = String(info.value.roleinfo);
|
const roleids = String(info.value.roleinfo);
|
||||||
updataUser(params, roleids).then(() => {
|
updataUser(params, roleids).then(() => {
|
||||||
@ -1274,6 +1285,17 @@ function handleClearSelection() {
|
|||||||
</el-option-group>
|
</el-option-group>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="所属组织" v-if="info.id">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="info.orgid"
|
||||||
|
:data="treedata"
|
||||||
|
:props="treeSelectProps"
|
||||||
|
:render-after-expand="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择所属组织"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<span
|
<span
|
||||||
class="dialog-footer"
|
class="dialog-footer"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user