Merge branch 'wxk-main'

This commit is contained in:
王兴凯 2026-08-20 09:05:32 +08:00
commit 617bf8edaa
39 changed files with 4190 additions and 1826 deletions

View File

@ -114,6 +114,24 @@ export function getFlowDataMonthList(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) {
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) {
return request({

View File

@ -54,7 +54,7 @@ interface Props {
data?: any[];
enableRowSelection?: boolean;
enableRowHighlight?: boolean; //
rowKey?: string;
rowKey?: string | ((record: any) => string | number);
searchParams?: Record<string, any>;
defaultPageSize?: number;
isPage?: boolean;
@ -663,7 +663,20 @@ const enhancedColumns = computed(() => {
title: String(text)
},
{
default: () => String(text)
default: () =>
h(
'span',
{
style: {
display: 'inline-block',
maxWidth: '100%',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
},
String(text)
)
}
)
);

View File

@ -210,7 +210,7 @@
<!-- 图片上传 -->
<a-row :gutter="16" class="mt-4">
<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
v-model:file-list="picFileList"
:multiple="false"
@ -230,7 +230,7 @@
<!-- 视频上传 -->
<a-row :gutter="16" class="mt-4">
<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
v-model:file-list="vdpFileList"
:multiple="false"

View File

@ -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>

View File

@ -57,8 +57,10 @@
</a-form-item-rest>
</template>
<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 danger :disabled="!selectedCount" @click="batchDelete"
>批量删除</a-button
>
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
</template>
</BasicSearch>
@ -77,8 +79,8 @@ const emit = defineEmits<{
(e: 'export-btn'): void;
(e: 'reset', values: any): void;
(e: 'search-finish', values: any): void;
(e: 'add'): void;
(e: 'seeEdit'): void;
(e: 'batch-delete'): void;
}>();
const basicSearchRef = ref<any>();
@ -86,13 +88,13 @@ const btnLoading = ref<boolean>(false);
const props = defineProps<{
exportLoading?: boolean;
mway: string;
selectedCount?: number;
}>();
const initSearchData = {
rvcd: 'all',
rstcd: null,
stcd: '0086601020VP000006',
mway: props.mway
mway: '1'
};
const searchData = ref<any>({ ...initSearchData });
@ -140,6 +142,22 @@ const searchList: any = computed(() => [
value: item.stcd
}))
},
{
type: 'Radio',
name: 'mway',
label: '数据类型',
width: 140,
options: [
{
label: '人工',
value: '1'
},
{
label: '自动',
value: '2'
}
]
},
{
type: 'custom',
name: 'jcdt',
@ -157,15 +175,13 @@ const onSearchFinish = (values: any) => {
const exportBtn = () => {
emit('export-btn');
};
const handleAdd = () => {
emit('add');
};
const seeEdit = () => {
emit('seeEdit');
};
const initCrossSectionList = (params, setDefault = false) => {
const batchDelete = () => {
emit('batch-delete');
};
const initCrossSectionList = params => {
const filters = [];
if (params) {
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;
getFishReleaseMonitorSectionList({
filter: {
@ -202,15 +211,6 @@ const initCrossSectionList = (params, setDefault = false) => {
}).then(res => {
if (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;
});
@ -261,8 +261,9 @@ onMounted(() => {
min: `${now.subtract(5, 'year').format('YYYY-MM-DD')} 00:00:00`,
max: `${now.format('YYYY-MM-DD')} 23:59:59`
};
//
initCrossSectionList(undefined, true);
initCrossSectionList();
// 使 handleSearchFinish jcdt
handleSearchFinish({ ...initSearchData });
});
</script>

View File

@ -1,22 +1,697 @@
<template>
<div class="w-full h-full flex flex-col pl-[20px] body_one">
<a-tabs v-model:activeKey="activeTab" class="fish-tabs">
<a-tab-pane key="manual" tab="人工站点">
<FishDataManual />
</a-tab-pane>
<a-tab-pane key="auto" tab="自动站点">
<FishDataAuto />
</a-tab-pane>
</a-tabs>
<div class="w-full h-full flex flex-col pt-[20px] pl-[20px] body_one">
<!-- 搜索组件 -->
<FishDataSearch
@export-btn="exportBtn"
@search-finish="onSearchFinish"
@reset="onReset"
@seeEdit="handleSeeEdit"
@batch-delete="handleBatchDelete"
: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>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import FishDataManual from './FishDataManual.vue';
import FishDataAuto from './FishDataAuto.vue';
import { ref, computed, onMounted, nextTick, h } from 'vue';
import dayjs from 'dayjs';
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>
<style scoped lang="scss">
@ -24,21 +699,4 @@ const activeTab = ref('manual');
position: relative;
z-index: 900;
pointer-events: all;
}
.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>
}</style>

View File

@ -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>

View File

@ -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>

View File

@ -59,6 +59,7 @@
</a-form-item-rest>
</template>
<template #actions>
<a-button type="primary" @click="seeEdit">查看修改</a-button>
<a-button :loading="exportLoading" @click="exportBtn">导出</a-button>
</template>
</BasicSearch>
@ -97,6 +98,7 @@ const emit = defineEmits<{
(e: 'export-btn'): void;
(e: 'reset', values: any): void;
(e: 'search-finish', values: any): void;
(e: 'seeEdit'): void;
}>();
const basicSearchRef = ref<any>();
@ -157,6 +159,9 @@ const onSearchFinish = (values: any) => {
const exportBtn = () => {
emit('export-btn');
};
const seeEdit = () => {
emit('seeEdit');
};
const onValuesChange = (changedValues: any, allValues: any) => {
handleValuesChange(changedValues, allValues);

View File

@ -5,6 +5,7 @@
@export-btn="exportBtn"
@search-finish="onSearchFinish"
@reset="onReset"
@seeEdit="handleSeeEdit"
:exportLoading="exportLoading"
ref="searchRef"
/>
@ -19,13 +20,36 @@
sort: sort
}"
>
<template #action="{ record }">
<a-button
type="link"
class="text-[#2f6b98]"
size="small"
@click="handleDetail(record)"
>详情</a-button
>
</template>
</BasicTable>
<!-- 详情弹窗 -->
<FishReleaseDetailModal
v-model:open="detailVisible"
:record="detailRecord"
/>
<!-- 操作日志弹框 -->
<OperationLogModal
v-model:open="operationLogVisible"
:table-name="operationTableName"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick, h } from 'vue';
import dayjs from 'dayjs';
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 { getFishReleaseList } from '@/api/DataQueryMenuModule';
import { calcTableScrollY } from '@/utils/index';
@ -54,6 +78,11 @@ const tableScrollY = ref<string | number>(0);
const currentSearchParams = ref<any>({});
const exportLoading = ref(false);
//
const operationLogVisible = ref(false);
// TODO:
const operationTableName = 'SD_RPIMNFISH_R';
const tableScrollX = computed(() =>
Math.max(
columns.reduce((sum: number, col: any) => sum + (col.width || 180), 0),
@ -122,6 +151,13 @@ const columns: any[] = [
dataIndex: 'fcnt',
width: 100,
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);
};
//
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(() => {
nextTick(() => {
tableScrollY.value = calcTableScrollY();

View File

@ -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>

View File

@ -63,6 +63,14 @@
</a-form-item-rest>
</template>
<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>
</template>
</BasicSearch>
@ -78,13 +86,22 @@ 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;
}>();
const props = withDefaults(
defineProps<{
exportLoading?: boolean;
selectedCount?: number;
showBatchDelete?: boolean;
}>(),
{
showBatchDelete: true
}
);
const initSearchData = {
rvcd: 'all',
@ -185,6 +202,12 @@ const onSearchFinish = (values: any) => {
const exportBtn = () => {
emit('export-btn');
};
const seeEdit = () => {
emit('seeEdit');
};
const batchDelete = () => {
emit('batch-delete');
};
const onValuesChange = (changedValues: any, allValues: any) => {
handleValuesChange(changedValues, allValues);

View File

@ -5,6 +5,10 @@
@export-btn="exportBtn"
@search-finish="onSearchFinish"
@reset="onReset"
@seeEdit="handleSeeEdit"
@batch-delete="handleBatchDelete"
:show-batch-delete="currentSearchParams.timeScale === 'tm'"
:selected-count="selectedRowKeys.length"
:exportLoading="exportLoading"
ref="searchRef"
/>
@ -12,6 +16,7 @@
<!-- 表格组件 -->
<BasicTable
ref="tableRef"
:enable-row-selection="currentSearchParams.timeScale === 'tm'"
:scrollX="tableScrollX"
:scrollY="tableScrollY"
:columns="displayColumns"
@ -19,20 +24,65 @@
:searchParams="{
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>
<!-- 编辑生态流量数据 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>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick, h } from 'vue';
import dayjs from 'dayjs';
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 {
getFlowDataList,
getFlowDataDayList,
getFlowDataMonthList
getFlowDataMonthList,
deleteFlowDataInfo
} from '@/api/DataQueryMenuModule';
import { calcTableScrollY } from '@/utils/index';
import { buildTimeFilters } from '@/utils/buildTimeFilters';
@ -62,6 +112,23 @@ const searchRef = ref();
const tableScrollY = ref<string | number>(0);
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
const currentListUrl = computed(() => {
const timeScale = currentSearchParams.value.timeScale;
@ -359,18 +426,37 @@ const ruleColumnsMap: any = {
const displayColumns = computed(() => {
const ruleType = currentSearchParams.value.ruleType ?? '';
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) => {
// debugger
currentSearchParams.value = values;
//
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
await nextTick();
initTable(values);
};
const onReset = async (values: any) => {
currentSearchParams.value = values;
//
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
await nextTick();
initTable(values);
};
@ -456,6 +542,71 @@ const initTable = (values: any) => {
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(() => {
nextTick(() => {
tableScrollY.value = calcTableScrollY();

View File

@ -61,6 +61,14 @@
</a-form-item-rest>
</template>
<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>
</template>
</BasicSearch>
@ -77,14 +85,23 @@ 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;
}>();
const props = withDefaults(
defineProps<{
exportLoading?: boolean;
selectedCount?: number;
showBatchDelete?: boolean;
}>(),
{
showBatchDelete: true
}
);
const initSearchData = {
rvcd: 'all',
@ -184,6 +201,12 @@ const onSearchFinish = (values: any) => {
const exportBtn = () => {
emit('export-btn');
};
const seeEdit = () => {
emit('seeEdit');
};
const batchDelete = () => {
emit('batch-delete');
};
const initCrossSectionList = params => {
const filters = [
{

View File

@ -5,12 +5,18 @@
@export-btn="exportBtn"
@search-finish="onSearchFinish"
@reset="onReset"
@seeEdit="handleSeeEdit"
@batch-delete="handleBatchDelete"
:show-batch-delete="currentSearchParams.timeScale === 'tm'"
:selected-count="selectedRowKeys.length"
:exportLoading="exportLoading"
ref="searchRef"
/>
<!-- 表格组件 -->
<BasicTable
ref="tableRef"
:enable-row-selection="currentSearchParams.timeScale === 'tm'"
:row-key="getRowKey"
:scrollX="tableScrollX"
:scrollY="tableScrollY"
:columns="Columns"
@ -18,6 +24,7 @@
:searchParams="{
sort: sort
}"
@selection-change="handleSelectionChange"
>
<template #action="{ record }">
<template v-if="currentSearchParams.timeScale === 'tm'">
@ -53,6 +60,12 @@
label="数据"
@success="handleEditSuccess"
/>
<!-- 操作日志弹框 -->
<OperationLogModal
v-model:open="operationLogVisible"
:table-name="operationTableName"
/>
</div>
</template>
<script setup lang="ts">
@ -61,7 +74,9 @@ import dayjs from 'dayjs';
import FlowStationSearch from './FlowStationSearch.vue';
import EditFlowStationModal from './EditFlowStationModal.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 { message } from 'ant-design-vue';
import {
getFlowStationList,
getFlowStationDayList,
@ -108,6 +123,17 @@ const exportLoading = ref(false);
const editVisible = ref(false);
const editRecord = ref<any>(null);
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
const currentListUrl = computed(() => {
@ -230,6 +256,10 @@ const Columns = computed(() => {
const onSearchFinish = async (values: any) => {
currentSearchParams.value = values;
//
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
// prop getList
await nextTick();
initTable(values);
@ -237,6 +267,10 @@ const onSearchFinish = async (values: any) => {
const onReset = async (values: any) => {
currentSearchParams.value = values;
//
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
await nextTick();
initTable(values);
};
@ -262,25 +296,60 @@ const handleEdit = (record: any) => {
editVisible.value = true;
};
// record record.records
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({
dataList: [
{
id: record.stcd,
tm: record.tm
}
],
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, () => {
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 = () => {
initTable(currentSearchParams.value);
};

View File

@ -61,6 +61,14 @@
</a-form-item-rest>
</template>
<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>
</template>
</BasicSearch>
@ -77,14 +85,23 @@ 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;
}>();
const props = withDefaults(
defineProps<{
exportLoading?: boolean;
selectedCount?: number;
showBatchDelete?: boolean;
}>(),
{
showBatchDelete: true
}
);
const initSearchData = {
rvcd: 'all',
@ -184,6 +201,12 @@ const onSearchFinish = (values: any) => {
const exportBtn = () => {
emit('export-btn');
};
const seeEdit = () => {
emit('seeEdit');
};
const batchDelete = () => {
emit('batch-delete');
};
const initCrossSectionList = params => {
const filters = [
{

View File

@ -5,12 +5,18 @@
@export-btn="exportBtn"
@search-finish="onSearchFinish"
@reset="onReset"
@seeEdit="handleSeeEdit"
@batch-delete="handleBatchDelete"
:show-batch-delete="currentSearchParams.timeScale === 'tm'"
:selected-count="selectedRowKeys.length"
:exportLoading="exportLoading"
ref="searchRef"
/>
<!-- 表格组件 -->
<BasicTable
ref="tableRef"
:enable-row-selection="currentSearchParams.timeScale === 'tm'"
:row-key="getRowKey"
:scrollX="tableScrollX"
:scrollY="tableScrollY"
:columns="Columns"
@ -18,6 +24,7 @@
:searchParams="{
sort: sort
}"
@selection-change="handleSelectionChange"
>
<template #action="{ record }">
<template v-if="currentSearchParams.timeScale === 'tm'">
@ -53,6 +60,12 @@
label="数据"
@success="handleEditSuccess"
/>
<!-- 操作日志弹框 -->
<OperationLogModal
v-model:open="operationLogVisible"
:table-name="operationTableName"
/>
</div>
</template>
<script setup lang="ts">
@ -61,7 +74,9 @@ import dayjs from 'dayjs';
import SurfaceTempSearch from './SurfaceTempSearch.vue';
import EditSurfaceTempModal from './EditSurfaceTempModal.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 { message } from 'ant-design-vue';
import {
getSurfaceTempList,
getSurfaceTempDayList,
@ -106,6 +121,17 @@ const exportLoading = ref(false);
const editVisible = ref(false);
const editRecord = ref<any>(null);
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
const currentListUrl = computed(() => {
@ -194,6 +220,10 @@ const Columns = computed(() => {
const onSearchFinish = async (values: any) => {
currentSearchParams.value = values;
//
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
// prop getList
await nextTick();
initTable(values);
@ -201,6 +231,10 @@ const onSearchFinish = async (values: any) => {
const onReset = async (values: any) => {
currentSearchParams.value = values;
//
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
await nextTick();
initTable(values);
};
@ -226,25 +260,60 @@ const handleEdit = (record: any) => {
editVisible.value = true;
};
// record record.records
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({
dataList: [
{
id: record.stcd,
dt: record.tm
}
],
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, () => {
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 = () => {
initTable(currentSearchParams.value);
};

View File

@ -63,6 +63,10 @@
</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>
@ -79,6 +83,8 @@ 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>();
@ -86,6 +92,7 @@ const btnLoading = ref<boolean>(false);
const props = defineProps<{
exportLoading?: boolean;
selectedCount?: number;
}>();
const initSearchData = {
@ -186,6 +193,12 @@ const onSearchFinish = (values: any) => {
const exportBtn = () => {
emit('export-btn');
};
const seeEdit = () => {
emit('seeEdit');
};
const batchDelete = () => {
emit('batch-delete');
};
const initCrossSectionList = params => {
const filters = [
{

View File

@ -5,6 +5,9 @@
@export-btn="exportBtn"
@search-finish="onSearchFinish"
@reset="onReset"
@seeEdit="handleSeeEdit"
@batch-delete="handleBatchDelete"
:selected-count="currentSearchParams.timeScale === 'tm' ? selectedRowsForChart.length : 0"
:exportLoading="exportLoading"
ref="searchRef"
/>
@ -96,6 +99,12 @@
label="数据"
@success="handleEditSuccess"
/>
<!-- 操作日志弹框 -->
<OperationLogModal
v-model:open="operationLogVisible"
:table-name="operationTableName"
/>
</div>
</template>
@ -113,7 +122,9 @@ import * as echarts from 'echarts';
import VerticalTempSearch from './VerticalTempSearch.vue';
import EditVerticalTempModal from './EditVerticalTempModal.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 { message } from 'ant-design-vue';
import { getVerticalList, deleteVerticalInfo } from '@/api/DataQueryMenuModule';
import { calcTableScrollY } from '@/utils/index';
@ -132,6 +143,8 @@ const tableData = ref<any[]>([]);
const editVisible = ref(false);
const editRecord = ref<any>(null);
const deleteModalRef = ref();
const operationLogVisible = ref(false);
const operationTableName = 'SD_WTVT_R';
//
const chartRef = ref<HTMLElement>();
@ -439,25 +452,59 @@ const handleEdit = (record: any) => {
editVisible.value = true;
};
// record record.records
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({
dataList: [
{
id: record.stcd ?? record.id,
tm: record.tm ?? record.dt
}
],
dataList,
dataType: 'TIME',
source: reason
});
};
//
const refreshAfterDelete = () => {
selectedRowsForChart.value = [];
tableRef.value?.clearSelection();
initTable(currentSearchParams.value);
};
const handleDelete = (record: any) => {
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 = () => {
initTable(currentSearchParams.value);
};

View File

@ -63,6 +63,14 @@
</a-form-item-rest>
</template>
<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>
</template>
</BasicSearch>
@ -79,13 +87,22 @@ 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;
}>();
const props = withDefaults(
defineProps<{
exportLoading?: boolean;
selectedCount?: number;
showBatchDelete?: boolean;
}>(),
{
showBatchDelete: true
}
);
const initSearchData = {
rvcd: 'all',
@ -201,6 +218,12 @@ const onSearchFinish = (values: any) => {
const exportBtn = () => {
emit('export-btn');
};
const seeEdit = () => {
emit('seeEdit');
};
const batchDelete = () => {
emit('batch-delete');
};
const initCrossSectionList = params => {
const filters = [
{

View File

@ -5,6 +5,10 @@
@export-btn="exportBtn"
@search-finish="onSearchFinish"
@reset="onReset"
@seeEdit="handleSeeEdit"
@batch-delete="handleBatchDelete"
:show-batch-delete="currentSearchParams.timeScale === 'tm'"
:selected-count="selectedRowKeys.length"
:exportLoading="exportLoading"
ref="searchRef"
/>
@ -12,6 +16,8 @@
<!-- 表格组件 -->
<BasicTable
ref="tableRef"
:enable-row-selection="currentSearchParams.timeScale === 'tm'"
row-key="stcd"
:scrollX="tableScrollX"
:scrollY="tableScrollY"
:columns="Columns"
@ -19,6 +25,7 @@
:searchParams="{
sort: sort
}"
@selection-change="handleSelectionChange"
>
<template #action="{ record }">
<template v-if="currentSearchParams.timeScale === 'tm'">
@ -55,6 +62,12 @@
label="数据"
@success="handleEditSuccess"
/>
<!-- 操作日志弹框 -->
<OperationLogModal
v-model:open="operationLogVisible"
:table-name="operationTableName"
/>
</div>
</template>
<script setup lang="ts">
@ -63,7 +76,8 @@ import dayjs from 'dayjs';
import WaterDataSearch from './WaterDataSearch.vue';
import EditWaterDataModal from './EditWaterDataModal.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 {
getWaterDataList,
@ -111,6 +125,16 @@ const exportLoading = ref(false);
const editVisible = ref(false);
const editRecord = ref<any>(null);
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
const currentListUrl = computed(() => {
@ -340,12 +364,20 @@ const Columns = computed(() => {
const onSearchFinish = async (values: any) => {
currentSearchParams.value = values;
//
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
await nextTick();
initTable(values);
};
const onReset = async (values: any) => {
currentSearchParams.value = values;
//
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
await nextTick();
initTable(values);
};
@ -416,32 +448,65 @@ const handleEdit = (record: any) => {
editVisible.value = true;
};
//
// record record.records
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({
dataList: [
{
id: record.stcd,
tm: record.tm
}
],
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, () => {
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 = () => {
initTable(currentSearchParams.value);
};
//
const handleSeeEdit = () => {
operationLogVisible.value = true;
};
onMounted(() => {
nextTick(() => {
tableScrollY.value = calcTableScrollY();

View File

@ -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>

View File

@ -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>

View File

@ -1,25 +1,28 @@
<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
mway="2"
:exportLoading="exportLoading"
<FishSurveyDataSearch
@export-btn="exportBtn"
@search-finish="onSearchFinish"
@reset="onReset"
@seeEdit="handleSeeEdit"
@batch-delete="handleBatchDelete"
:selected-count="selectedRowKeys.length"
:exportLoading="exportLoading"
ref="searchRef"
/>
<!-- 表格组件 -->
<BasicTable
ref="tableRef"
:enable-row-selection="true"
:scrollX="tableScrollX"
:scrollY="tableScrollY"
:columns="tableColumns"
:list-url="getFishReleaseMonitorList"
:columns="columnsAuto"
:list-url="getFpssrlAiRPageList"
:searchParams="{
sort: sort
}"
@selection-change="handleSelectionChange"
>
<template #action="{ record }">
<a-button
@ -64,7 +67,7 @@
</a-modal>
<!-- 编辑 Modal -->
<EditFishDataAutoModal
<EditFishSurveyDataModal
v-model:open="editVisible"
:record="editRecord"
@success="handleEditSuccess"
@ -73,27 +76,31 @@
<!-- 删除确认 Modal -->
<DeleteConfirmModal
ref="deleteModalRef"
:delete-fn="deleteFpssrlFn"
:label="'过鱼自动数据'"
title="删除过鱼自动数据"
:delete-fn="deleteFn"
:label="deleteLabel"
:title="'删除' + deleteLabel"
@success="handleEditSuccess"
/>
<OperationLogModal v-model:open="operationLogVisible" :table-name="'SD_FPSS_A'" />
<!-- 操作日志弹框 -->
<OperationLogModal
v-model:open="operationLogVisible"
:table-name="operationTableName"
/>
</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 { Button, message } from 'ant-design-vue';
import FishSurveyDataSearch from './fishSurveyDataSearch.vue';
import BasicTable from '@/components/BasicTable/index.vue';
import { getFishReleaseMonitorList, deleteFpssrlRInfo } from '@/api/DataQueryMenuModule';
import { calcTableScrollY } from '@/utils/index';
import { useDraggable } from '@/utils/drag';
import EditFishDataAutoModal from './EditFishDataAutoModal.vue';
import EditFishSurveyDataModal from './EditFishSurveyDataModal.vue';
import DeleteConfirmModal from '@/views/conventionalHydropower/BasicData/DeleteConfirmModal.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>([
{
@ -123,11 +130,21 @@ 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 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);
@ -149,7 +166,7 @@ const openVideoPreview = (url: string) => {
const tableScrollX = computed(() =>
Math.max(
tableColumns.value.reduce(
columnsAuto.reduce(
(sum: number, col: any) => sum + (col.width || 180),
0
),
@ -158,7 +175,7 @@ const tableScrollX = computed(() =>
);
//
const tableColumns = ref<any[]>([
const columnsAuto: any[] = [
{
key: 'ennm',
title: '电站名称',
@ -177,7 +194,7 @@ const tableColumns = ref<any[]>([
},
{
key: 'stnm',
title: '过鱼设施名称',
title: '设施名称',
dataIndex: 'stnm',
sort: true,
width: 200
@ -339,7 +356,7 @@ const tableColumns = ref<any[]>([
fixed: 'right',
width: 120
}
]);
];
const onSearchFinish = async (values: any) => {
currentSearchParams.value = values;
@ -362,7 +379,7 @@ const exportBtn = () => {
tableRef.value
.exportTable({
fileName: `过鱼监测数据_自动_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
fileName: `过鱼监测数据_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
})
.finally(() => {
exportLoading.value = false;
@ -389,12 +406,12 @@ const initTable = (values: any) => {
value: values.rstcd
}
: null,
values.mway
values.sttpCode
? {
field: 'mway',
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: values.mway
value: values.sttpCode
}
: null
].filter(Boolean); //
@ -441,10 +458,6 @@ const initTable = (values: any) => {
tableRef.value.getList(params);
};
const handleSeeEdit = () => {
operationLogVisible.value = true;
};
//
const handleEdit = (record: any) => {
editRecord.value = { ...record };
@ -457,19 +470,49 @@ const handleEditSuccess = () => {
};
//
const deleteFpssrlFn = async (record: any, reason: string) => {
return deleteFpssrlRInfo({
ids: [record.id],
const deleteFn = async (record: any, reason: string) => {
const ids = record.ids || [record.id];
return deleteFpssrlAiRInfo({
ids,
source: reason
});
};
//
const refreshAfterDelete = () => {
selectedRowKeys.value = [];
selectedRows.value = [];
tableRef.value?.clearSelection();
initTable(currentSearchParams.value);
};
const handleDelete = (record: any) => {
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(() => {
nextTick(() => {
tableScrollY.value = calcTableScrollY();
@ -477,4 +520,9 @@ onMounted(() => {
});
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.body_one {
position: relative;
z-index: 900;
pointer-events: all;
}</style>

View File

@ -47,6 +47,12 @@ const dialog = ref(false);
const treeRef = ref();
const fishTreeRef = ref();
const infoForm = ref();
//
const treeSelectProps = {
value: 'id',
label: 'orgname',
children: 'childList'
};
//
const treeloading = ref(true);
function getTree() {
@ -99,7 +105,8 @@ function addClick() {
nickname: '',
email: '',
phone: '',
roleinfo: []
roleinfo: [],
orgid: ''
};
getrole();
}
@ -178,7 +185,8 @@ const info = ref({
nickname: '',
email: '',
phone: '',
roleinfo: [] as any[]
roleinfo: [] as any[],
orgid: ''
});
//-
function editdepartment(row: any) {
@ -188,6 +196,8 @@ function editdepartment(row: any) {
});
info.value = JSON.parse(JSON.stringify(row));
info.value.roleinfo = selectID;
//
info.value.orgid = row.orgid;
rolesdata.value = row.roles;
title.value = '修改用户';
dialogVisible.value = true;
@ -207,7 +217,8 @@ function confirmClick(formEl: any) {
nickname: info.value.nickname,
email: info.value.email,
phone: info.value.phone,
orgid: orgId.value
//
orgid: info.value.orgid
};
const roleids = String(info.value.roleinfo);
updataUser(params, roleids).then(() => {
@ -787,6 +798,7 @@ function getrole() {
rolename: ''
};
listGroupedByTenant(params).then(res => {
// debugger
rolesdata.value = res;
});
}
@ -1250,14 +1262,14 @@ function handleClearSelection() {
placeholder="请输入登录账号"
></el-input>
</el-form-item>
<el-form-item label="所属角色">
<el-select
v-model="info.roleinfo"
placeholder=" "
style="width: 100%"
multiple
filterable
multiple
>
<el-option-group
v-for="group in rolesdata"
@ -1273,6 +1285,17 @@ function handleClearSelection() {
</el-option-group>
</el-select>
</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>
<span
class="dialog-footer"

View File

@ -47,6 +47,12 @@ const dialog = ref(false);
const treeRef = ref();
const fishTreeRef = ref();
const infoForm = ref();
//
const treeSelectProps = {
value: 'id',
label: 'orgname',
children: 'childList'
};
//
const treeloading = ref(true);
function getTree() {
@ -99,7 +105,8 @@ function addClick() {
nickname: '',
email: '',
phone: '',
roleinfo: []
roleinfo: [],
orgid: ''
};
getrole();
}
@ -178,7 +185,8 @@ const info = ref({
nickname: '',
email: '',
phone: '',
roleinfo: [] as any[]
roleinfo: [] as any[],
orgid: ''
});
//-
function editdepartment(row: any) {
@ -188,6 +196,8 @@ function editdepartment(row: any) {
});
info.value = JSON.parse(JSON.stringify(row));
info.value.roleinfo = selectID;
//
info.value.orgid = row.orgid;
rolesdata.value = row.roles;
title.value = '修改用户';
dialogVisible.value = true;
@ -207,7 +217,8 @@ function confirmClick(formEl: any) {
nickname: info.value.nickname,
email: info.value.email,
phone: info.value.phone,
orgid: orgId.value
//
orgid: info.value.orgid
};
const roleids = String(info.value.roleinfo);
updataUser(params, roleids).then(() => {
@ -787,6 +798,7 @@ function getrole() {
rolename: ''
};
listGroupedByTenant(params).then(res => {
// debugger
rolesdata.value = res;
});
}
@ -1250,13 +1262,14 @@ function handleClearSelection() {
placeholder="请输入登录账号"
></el-input>
</el-form-item>
<el-form-item label="所属角色">
<el-form-item label="所属角色">
<el-select
v-model="info.roleinfo"
placeholder=" "
style="width: 100%"
multiple
filterable
multiple
>
<el-option-group
v-for="group in rolesdata"
@ -1272,6 +1285,17 @@ function handleClearSelection() {
</el-option-group>
</el-select>
</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>
<span
class="dialog-footer"

View File

@ -4,34 +4,11 @@
<a-row class="w-[100%]" v-if="!loading">
<a-col
:span="!item.visible ? 24 : item.filed == '' ? 12 : 12"
v-for="(item, index) in tabledata"
v-for="item in tabledata"
:key="item.key"
>
<div class="col-title" v-if="!item.visible">
{{ 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 class="col-left" v-if="item.visible && item.filed != ''">
<a-tooltip :title="item.name.length > 10 ? item.name : ''"
@ -41,50 +18,14 @@
<div class="col-right" v-if="item.visible">
<a-tooltip
:title="
!isEdit
? item.vlsr || String(item.value).length > 10
? item.vlsr
? '数据来源:' + item.vlsr
: item.value
: ''
item.vlsr || String(item.value).length > 10
? item.vlsr
? '数据来源:' + item.vlsr
: item.value
: ''
"
>
<span v-if="!isEdit && item.filed != ''">{{
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>
<span>{{ item.value || '-' }}</span>
</a-tooltip>
</div>
</a-col>
@ -100,22 +41,13 @@
</div>
</div>
</div>
<a-modal
v-model:open="visible"
title="修改记录"
:footer="null"
width="900px"
:zIndex="2000"
>
123
</a-modal>
</div>
</a-spin>
</template>
<script lang="ts" setup>
import dayjs from 'dayjs';
import { ref, nextTick, watch, computed } from 'vue';
import { ref, watch } from 'vue';
import {
BasicColumns,
wtPointColumns,
@ -152,7 +84,6 @@ import { getStcdDetail } from '@/api/mapModal';
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
const modelStore = useModelStore();
const hasLoaded = ref(false); //
import { useDraggable } from '@/utils/drag';
const props = defineProps({
url: {
type: String,
@ -163,12 +94,10 @@ const props = defineProps({
default: false
}
});
const visible = ref(false);
const isLogo = ref(false);
const loading = ref(false);
const data2: any = ref({});
const tabledata = ref([]);
useDraggable(visible, { boundary: true, resetOnOpen: true });
const columnsConfig = ref([
{
type: 'ENG',
@ -337,22 +266,6 @@ const columnsConfig = ref([
//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 () => {
if (hasLoaded.value) return;
loading.value = true;

View File

@ -598,7 +598,16 @@ const setQGCTabList = async (_tabs: any, stcd: string) => {
) {
tabsConfig.value = newTabs;
} 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) {
console.error('QGC Tab 过滤失败:', error);

View File

@ -221,7 +221,6 @@ const handleItemClick = (item: MediaItem) => {
modelStore.params.sttp = 'WVA';
modelStore.params.stcd = item.stcd;
modelStore.title = `${item.stnm} 野生动物监测`;
modelStore.isBasicEdit = true;
};
// ==================== ====================

View File

@ -550,7 +550,6 @@ const initChart = () => {
modelStore.params.sttp = 'WTRV';
modelStore.params.enfc = '1';
modelStore.title = stnm;
// modelStore.isBasicEdit = true;
modelStore.params.stcd = stcd;
modelStore.filter.rangeTm = [startTime, endTime];

View File

@ -276,7 +276,7 @@ const fetchFacilityStats = async () => {
"field": "bldsttCcode",
"operator": "eq",
"dataType": "string",
"value": "2"
"value": formState.bldstt
} : null,
formState.stnm ? {
"field": "stnm",
@ -334,6 +334,7 @@ const fetchBuildStateList = async () => {
value: item.itemCode
}))
buildStateList.value.unshift({ label: '全部', value: '' })
// debugger
// 使
// buildStateList.value = [
// { label: '', value: '' },
@ -399,7 +400,7 @@ const handleSearch = () => {
"field": "bldsttCcode",
"operator": "eq",
"dataType": "string",
"value": "2"
"value": formState.bldstt
} : null,
formState.stnm ? {
"field": "stnm",

View File

@ -347,7 +347,6 @@ const handleViewDetail = (record: any, type: any) => {
modelStore.modalVisible = true;
modelStore.params.sttp = 'VD_FBFM';
modelStore.title = '视频监控站详情信息';
modelStore.isBasicEdit = true;
modelStore.params.stcd = record.stcd;
}
}

View File

@ -519,7 +519,6 @@ const handleViewDetail = (record: any) => {
modelStore.modalVisible = true;
modelStore.params.sttp = "WT";
modelStore.title = record.stnm ;
// modelStore.isBasicEdit = true;
if (tabIndex.value == "27") {
modelStore.params.enfc = "1";
}

View File

@ -27,6 +27,7 @@ import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { qgcGetKendoListCust } from '@/api/sz';
import { getMonitorDataWaterQualityDetail } from '@/api/mapModal/index';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { useModelStore } from '@/store/modules/model';
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 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)}`;
return COLOR_PALETTE[index % COLOR_PALETTE.length];
};
// showControl.lineColorkey
const indicatorColorMap = ref<Record<string, string>>({});
// ==================== ====================
const generateIndicatorConfig = (data: StationData[]) => {
if (!data || data.length === 0) {
@ -216,54 +191,15 @@ const generateIndicatorConfig = (data: StationData[]) => {
.filter(ind => validKeysSet.has(ind.key))
.sort((a, b) => a.sort - b.sort);
//
// 退
indicatorConfig.value = validIndicators.map((ind, index) => ({
key: ind.key,
name: ind.name,
unit: ind.description || '',
color: generateRandomColor(index),
color: indicatorColorMap.value[ind.key] || generateRandomColor(index),
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;
tm: string;
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 }>;
[key: string]: any; //
// 访
[key: string]: any;
}
const mockData = ref<StationData[]>([]);
@ -637,17 +538,11 @@ const handleLegendSelectChanged = (params: any) => {
const clickedName = name;
const isNowSelected = selected[clickedName];
console.log(
`图例点击: ${clickedName}, 当前状态: ${isNowSelected ? '显示' : '隐藏'}`
);
console.log('当前可见队列:', visibleSeriesQueue.value);
if (isNowSelected) {
//
if (visibleSeriesQueue.value.length >= 2) {
// 2
const removed = visibleSeriesQueue.value.shift();
console.log(`已达上限,移除最早系列: ${removed}`);
visibleSeriesQueue.value.shift();
}
//
if (!visibleSeriesQueue.value.includes(clickedName)) {
@ -658,135 +553,23 @@ const handleLegendSelectChanged = (params: any) => {
const index = visibleSeriesQueue.value.indexOf(clickedName);
if (index > -1) {
visibleSeriesQueue.value.splice(index, 1);
console.log(`从队列中移除: ${clickedName}`);
}
}
console.log('更新后可见队列:', visibleSeriesQueue.value);
// 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++;
}
}
// 2Y
if (!isShow || displayIndex >= 2) {
return { ...item, show: false };
}
return { ...item, show: true };
});
currentOption.yAxis = newYAxis;
// Y
yAxisShowDynamic(finalSelected, currentOption);
//
// y /formatter/min-max
// getOption() y
try {
chartInstance.setOption(currentOption, true);
console.log('图表配置已更新');
const option = getChartOption();
chartInstance.setOption(option, true);
} catch (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;
}
// Y11
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) => {
if (!params || !params.dataIndex) return;
// dataIndex 0 null/undefined
if (!params || params.dataIndex == null) return;
//
const dataIndex = params.dataIndex;
@ -796,7 +579,6 @@ const handleDataPointClick = (params: any) => {
modelStore.modalVisible = true;
modelStore.params.sttp = 'WQFB';
modelStore.title = stationData.stnm;
// modelStore.isBasicEdit = true;
modelStore.params.stcd = stationData.stcd;
modelStore.filter.rangeTm = [
dayjs(datetimePicker.value.value)
@ -821,8 +603,6 @@ const getecharts = async () => {
return;
}
console.log('开始获取图表数据select.value:', select.value.value);
// loading
loading.value = true;
hasData.value = false;
@ -882,8 +662,6 @@ const getecharts = async () => {
}
}
console.log('原始数据条数:', apiData.length);
// sttp === 'WQ'sort
const filteredData = apiData
.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 {
stnm: stnm,
stcd: item.stcd || '',
type: item.rstcd ? '1' : '0', // rstcd
tm: item.tm || '',
SORT: index + 1,
ph: item.ph != null ? Number(item.ph) : null,
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,
...indicatorValues,
limits: limits //
};
});
@ -992,12 +743,42 @@ const getecharts = async () => {
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);
//
if (indicatorConfig.value.length === 0) {
console.log('没有有效指标,显示空状态');
hasData.value = false;
chartInstance?.clear();
loading.value = false;
@ -1101,7 +882,6 @@ const initChart = async () => {
// sort
visibleSeriesQueue.value = [indicatorConfig.value[0].name];
console.log('初始化可见队列:', visibleSeriesQueue.value);
// ECharts
chartInstance = echarts.init(chartRef.value);
@ -1178,7 +958,6 @@ const wbsCode = ref('');
watch(
() => stationSource.value,
newVal => {
console.log(newVal);
wbsCode.value = newVal.wbsCode;
select.value.value = newVal.wbsCode;
getecharts();
@ -1199,22 +978,7 @@ watch(
{ deep: true, immediate: true }
);
// tooltip formatter 使 visibleSeriesQueue
watch(
() => visibleSeriesQueue.value.length,
(newLength, oldLength) => {
//
if (
newLength !== oldLength &&
chartInstance &&
indicatorConfig.value.length > 0
) {
// option formatter visibleSeriesQueue
const newOption = getChartOption();
chartInstance.setOption(newOption, true);
}
}
);
// handleLegendSelectChanged
</script>
<style lang="scss" scoped>

View File

@ -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>

View File

@ -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>

View File

@ -80,13 +80,12 @@ const btnLoading = ref<boolean>(false);
const props = defineProps<{
exportLoading?: boolean;
mway: string;
}>();
const initSearchData = {
rvcd: 'all',
rstcd: null,
stcd: '0086601020VP000006',
mway: props.mway
mway: '1'
};
const searchData = ref<any>({ ...initSearchData });
@ -134,6 +133,22 @@ const searchList: any = computed(() => [
value: item.stcd
}))
},
{
type: 'Radio',
name: 'mway',
label: '数据类型',
width: 140,
options: [
{
label: '人工',
value: '1'
},
{
label: '自动',
value: '2'
}
]
},
{
type: 'custom',
name: 'jcdt',

View File

@ -1,42 +1,551 @@
<template>
<div class="w-full h-full">
<a-tabs
v-model:activeKey="activeTab"
class="fish-tabs"
<div class="w-full h-full flex flex-col">
<!-- 搜索组件 -->
<FishDataSearch
@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="人工站点">
<FishDataManual />
</a-tab-pane>
<a-tab-pane key="auto" tab="自动站点">
<FishDataAuto />
</a-tab-pane>
</a-tabs>
</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 } from 'vue';
import FishDataManual from './FishDataManual.vue';
import FishDataAuto from './FishDataAuto.vue';
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 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>
<style scoped lang="scss">
.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>
<style scoped lang="scss"></style>

View File

@ -47,6 +47,12 @@ const dialog = ref(false);
const treeRef = ref();
const fishTreeRef = ref();
const infoForm = ref();
//
const treeSelectProps = {
value: 'id',
label: 'orgname',
children: 'childList'
};
//
const treeloading = ref(true);
function getTree() {
@ -99,7 +105,8 @@ function addClick() {
nickname: '',
email: '',
phone: '',
roleinfo: []
roleinfo: [],
orgid: ''
};
getrole();
}
@ -178,7 +185,8 @@ const info = ref({
nickname: '',
email: '',
phone: '',
roleinfo: [] as any[]
roleinfo: [] as any[],
orgid: ''
});
//-
function editdepartment(row: any) {
@ -188,6 +196,8 @@ function editdepartment(row: any) {
});
info.value = JSON.parse(JSON.stringify(row));
info.value.roleinfo = selectID;
//
info.value.orgid = row.orgid;
rolesdata.value = row.roles;
title.value = '修改用户';
dialogVisible.value = true;
@ -207,7 +217,8 @@ function confirmClick(formEl: any) {
nickname: info.value.nickname,
email: info.value.email,
phone: info.value.phone,
orgid: orgId.value
//
orgid: info.value.orgid
};
const roleids = String(info.value.roleinfo);
updataUser(params, roleids).then(() => {
@ -1274,6 +1285,17 @@ function handleClearSelection() {
</el-option-group>
</el-select>
</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>
<span
class="dialog-footer"