添加公共弹框,实时视频,全景影像,监测数据,预警提示,查看报告,批复文件,公共弹框逻辑修改

This commit is contained in:
扈兆增 2026-06-05 16:10:14 +08:00
parent 34a5588899
commit ebf6a57a31
31 changed files with 5953 additions and 3241 deletions

View File

@ -7,12 +7,12 @@ VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
VITE_APP_PORT = 3000 VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/dev-api' VITE_APP_BASE_API = '/dev-api'
# 本地环境 # 本地环境
# VITE_APP_BASE_URL = 'http://localhost:8093' VITE_APP_BASE_URL = 'http://localhost:8093'
# 测试环境 # 测试环境
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093' # VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096' # VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
# 汤伟 # 汤伟
VITE_APP_BASE_URL = 'http://10.84.121.21:8093' # VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
# VITE_APP_BASE_URL = 'http://192.168.1.162:8093' # VITE_APP_BASE_URL = 'http://192.168.1.162:8093'
# 测试环境线上 # 测试环境线上
@ -23,8 +23,9 @@ VITE_APP_TEST_ONLINE_URL = 'https://211.99.26.225:12122'
# 开发环境导入预览地址 # 开发环境导入预览地址
VITE_APP_BASE_API_URL = 'http://10.84.121.21:8093' VITE_APP_BASE_API_URL = 'http://10.84.121.21:8093'
## 开发环境预览 图片视频地址 ## 开发环境 附件服务地址
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125' VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
attachment
# 地图服务地址 # 地图服务地址
VITE_APP_MAP_URL = 'https://211.99.26.225:18085' VITE_APP_MAP_URL = 'https://211.99.26.225:18085'

View File

@ -3,10 +3,17 @@ NODE_ENV='production'
VITE_APP_TITLE = 'qgc-buji-web' VITE_APP_TITLE = 'qgc-buji-web'
VITE_APP_PORT = 3000 VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/prod-api' VITE_APP_BASE_API = '/'
## 生产环境API地址 ## 生产环境API地址
VITE_APP_BASE_URL = 'http://localhost:8093' VITE_APP_BASE_URL = 'http://localhost:8093'
## 生产环境导入预览地址 ## 生产环境导入预览地址
VITE_APP_BASE_API_URL = 'https://211.99.26.225:12130/prod-api' VITE_APP_BASE_API_URL = 'https://211.99.26.225:12130/prod-api'
## 生产环境预览 图片视频地址 ## 生产环境预览 图片视频地址
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125' VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'
## 开发环境 附件服务地址
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'
attachment
# 地图服务地址
VITE_APP_MAP_URL = 'https://211.99.26.225:18085'

View File

@ -37,6 +37,7 @@
"ol": "^10.8.0", "ol": "^10.8.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pdfjs-dist": "^6.0.227",
"pinia": "^2.0.12", "pinia": "^2.0.12",
"screenfull": "^6.0.0", "screenfull": "^6.0.0",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",

View File

@ -89,6 +89,9 @@ importers:
path-to-regexp: path-to-regexp:
specifier: ^6.2.0 specifier: ^6.2.0
version: 6.3.0 version: 6.3.0
pdfjs-dist:
specifier: ^6.0.227
version: 6.0.227
pinia: pinia:
specifier: ^2.0.12 specifier: ^2.0.12
version: 2.3.1(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3)) version: 2.3.1(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3))
@ -595,6 +598,81 @@ packages:
'@jridgewell/trace-mapping@0.3.9': '@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@napi-rs/canvas-android-arm64@1.0.0':
resolution: {integrity: sha512-3hNKJObUK7JsCF9aJlVCs1J0/KE/gGfZNeK8MO1ge6bB3aicr5walGme9t9No1f/oyk9GgvdAT/rjSdsx3gbIw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@napi-rs/canvas-darwin-arm64@1.0.0':
resolution: {integrity: sha512-ZIja19/BiGz2puhki+WUYSRriwFeFJ8Mi9eK3hZdSS85w4Y60cuEAJVhMCfKwswQkKkUtrnzdKMBuO7TupvexA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@napi-rs/canvas-darwin-x64@1.0.0':
resolution: {integrity: sha512-hImggWc82jqZVpEsFR9S7PE9OQYjq/H/D7vwCGB6X1jRH+UVBP1+1niJTPBOat1B154T6GKK7/kcFtoWgjgFzQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@napi-rs/canvas-linux-arm-gnueabihf@1.0.0':
resolution: {integrity: sha512-hlJRy6d+kWLKVOG/+1rEvNQVURZ0DxxRPJsLmEWwhwiXZUJc0BF5o9esALHSEP4CoJK4wChRtj3hnyBgVx2oWA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@napi-rs/canvas-linux-arm64-gnu@1.0.0':
resolution: {integrity: sha512-5Hru4T3RXkosRQafcjelv7AUzw9mXqmGYsxnzeDDOWveFCJyEPMSJltvGCM+jfH98seOCbfwm9KyFg6Jm5FhAA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-arm64-musl@1.0.0':
resolution: {integrity: sha512-LTUl9jS8WsLSUGaxQZKQkxfluOJRpgvBuxxdM4pYcjib+di8AU4OzQc6+L6SzGMLcKc9H0RAjojRatBhTMqYdg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@napi-rs/canvas-linux-riscv64-gnu@1.0.0':
resolution: {integrity: sha512-Iz931SAZf+WVDzpjk52Q3ffW3zw0YflFwEZMgs036Wfu1kX/LrwT9wGjsuSqyduqefUkl91/vTdAjn8hQu5ezA==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-gnu@1.0.0':
resolution: {integrity: sha512-pFEQ5eFK4JusgN1K6KkO9DKP/Hi1WMJOkF8Ch03/khTc4bFbCKkCCsJG4YcOMOW9bI4XbT2/eMAWxhO0xaWgPA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-musl@1.0.0':
resolution: {integrity: sha512-jnvr8NrLHiZ3NCiOKWqDbkI4Ah+QDrqtZ+sddPZBltEb1mQ2coSvCSJYfict+oAwcm0c970oTmVySpjKP/lnaA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@napi-rs/canvas-win32-arm64-msvc@1.0.0':
resolution: {integrity: sha512-y2j9/Gfd5joqiqxdP/L1smqjQ+uAx3C4N0EC7bDHrnZEEH8ToM/OC5p3uHvtj4Lq591aHj+ArL01UDLNwT5HgQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@napi-rs/canvas-win32-x64-msvc@1.0.0':
resolution: {integrity: sha512-qwdhh9N6Gge/hC4pL9S1tQp0iKwhSl/dYjg7+RGp9k26iRGRi5MqqUyKGOXIWli0zOcuy5Y2wIH/jk2ry6i/jA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@napi-rs/canvas@1.0.0':
resolution: {integrity: sha512-Jqxcy1XOIqj+lH9sl1GT+il6GR3uQv13vI2mrwubP3uT8Olak2ClDrK2RnxlQKjwv8BRr4b3ug0YR7c6hBX8wg==}
engines: {node: '>= 10'}
'@nodelib/fs.scandir@2.1.5': '@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -2864,6 +2942,10 @@ packages:
resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==} resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==}
hasBin: true hasBin: true
pdfjs-dist@6.0.227:
resolution: {integrity: sha512-/P6M4SXw+70waMVLUM7rdRtvo+dEzqE1t6W/zQNvBETo2MaRa5rrvCcAYdfWGiUzadTgM0lJmRApUrW0d9zgKg==}
engines: {node: '>=22.13.0 || >=24'}
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -4323,6 +4405,54 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
'@napi-rs/canvas-android-arm64@1.0.0':
optional: true
'@napi-rs/canvas-darwin-arm64@1.0.0':
optional: true
'@napi-rs/canvas-darwin-x64@1.0.0':
optional: true
'@napi-rs/canvas-linux-arm-gnueabihf@1.0.0':
optional: true
'@napi-rs/canvas-linux-arm64-gnu@1.0.0':
optional: true
'@napi-rs/canvas-linux-arm64-musl@1.0.0':
optional: true
'@napi-rs/canvas-linux-riscv64-gnu@1.0.0':
optional: true
'@napi-rs/canvas-linux-x64-gnu@1.0.0':
optional: true
'@napi-rs/canvas-linux-x64-musl@1.0.0':
optional: true
'@napi-rs/canvas-win32-arm64-msvc@1.0.0':
optional: true
'@napi-rs/canvas-win32-x64-msvc@1.0.0':
optional: true
'@napi-rs/canvas@1.0.0':
optionalDependencies:
'@napi-rs/canvas-android-arm64': 1.0.0
'@napi-rs/canvas-darwin-arm64': 1.0.0
'@napi-rs/canvas-darwin-x64': 1.0.0
'@napi-rs/canvas-linux-arm-gnueabihf': 1.0.0
'@napi-rs/canvas-linux-arm64-gnu': 1.0.0
'@napi-rs/canvas-linux-arm64-musl': 1.0.0
'@napi-rs/canvas-linux-riscv64-gnu': 1.0.0
'@napi-rs/canvas-linux-x64-gnu': 1.0.0
'@napi-rs/canvas-linux-x64-musl': 1.0.0
'@napi-rs/canvas-win32-arm64-msvc': 1.0.0
'@napi-rs/canvas-win32-x64-msvc': 1.0.0
optional: true
'@nodelib/fs.scandir@2.1.5': '@nodelib/fs.scandir@2.1.5':
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@ -6817,6 +6947,10 @@ snapshots:
dependencies: dependencies:
resolve-protobuf-schema: 2.1.0 resolve-protobuf-schema: 2.1.0
pdfjs-dist@6.0.227:
optionalDependencies:
'@napi-rs/canvas': 1.0.0
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@2.3.2: {} picomatch@2.3.2: {}

View File

@ -5,4 +5,60 @@ export function getStcdDetail(url: string, stcd: string) {
method: 'get', method: 'get',
params: { stcd } params: { stcd }
}); });
} }
// 公共接口 - post
export function queryPostUrlList(url: string, data: any) {
return request({
url: url,
method: 'post',
data
});
}
// 公共接口 - 获取预览地址
export function getIdUrl(params: any) {
return request({
url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/get',
method: 'get',
params
});
}
// 全景影像
export function getPanoramaList(data: any) {
return request({
url: '/eq/msstbprpt/GetKendoList',
method: 'post',
data
});
}
// 实时视频
export function getVideoList(data: any) {
return request({
url: '/vd/vmsstbprpt/GetKendoList',
method: 'post',
data
});
}
// 实时视频单个查询
export function getVideoDetail(data: any) {
return request({
url: '/vd/GetKendoListCust',
method: 'post',
data
});
}
// 实时视频 回放
export function getVideoPlayDetail(data: any) {
return request({
url: '/vd/runData/GetKendoListCust',
method: 'post',
data
});
}
// 监测数据 - 电站
export function getMonitorData(data: any) {
return request({
url: '/eq/data/GetKendoListCust',
method: 'post',
data
});
}

View File

@ -1,7 +1,7 @@
import request from '@/utils/request'; import request from '@/utils/request';
// 基地下拉列表 // 基地下拉列表
export function getBaseDropdown(data:any) { export function getBaseDropdown(data: any) {
return request({ return request({
url: '/env/hydrobase/dropdown', url: '/env/hydrobase/dropdown',
method: 'get', method: 'get',
@ -9,7 +9,7 @@ export function getBaseDropdown(data:any) {
}); });
} }
// 流域下拉列表 // 流域下拉列表
export function getSelectForDropdown(data:any) { export function getSelectForDropdown(data: any) {
return request({ return request({
url: '/env/hbrv/selectForDropdown', url: '/env/hbrv/selectForDropdown',
method: 'get', method: 'get',
@ -17,7 +17,7 @@ export function getSelectForDropdown(data:any) {
}); });
} }
//电站下拉列表 //电站下拉列表
export function getEngInfoDropdown(data:any) { export function getEngInfoDropdown(data: any) {
return request({ return request({
url: '/env/engInfo/dropdown', url: '/env/engInfo/dropdown',
method: 'post', method: 'post',
@ -25,7 +25,7 @@ export function getEngInfoDropdown(data:any) {
}); });
} }
// 重新验证并更新行数据 // 重新验证并更新行数据
export function revalidateAndUpdateRow(data:any) { export function revalidateAndUpdateRow(data: any) {
return request({ return request({
url: '/data/fishDraft/revalidateAndUpdateRow', url: '/data/fishDraft/revalidateAndUpdateRow',
method: 'post', method: 'post',
@ -33,7 +33,7 @@ export function revalidateAndUpdateRow(data:any) {
}); });
} }
// 删除 行数据 // 删除 行数据
export function deleteRowById(data:any) { export function deleteRowById(data: any) {
return request({ return request({
url: '/data/fishDraft/deleteRowById', url: '/data/fishDraft/deleteRowById',
method: 'post', method: 'post',
@ -42,7 +42,7 @@ export function deleteRowById(data:any) {
} }
//过鱼设施下拉列表 //过鱼设施下拉列表
export function getFpssDropdown(params:any) { export function getFpssDropdown(params: any) {
return request({ return request({
url: '/env/fpss/dropdown', url: '/env/fpss/dropdown',
method: 'get', method: 'get',
@ -57,7 +57,7 @@ export function getFishDictoryDropdown() {
}); });
} }
// 类似鱼类名称下拉列表 // 类似鱼类名称下拉列表
export function getSimilarFishDictoryDropdown(params:any) { export function getSimilarFishDictoryDropdown(params: any) {
return request({ return request({
url: '/env/fishDictory/similar', url: '/env/fishDictory/similar',
method: 'get', method: 'get',
@ -65,11 +65,11 @@ export function getSimilarFishDictoryDropdown(params:any) {
}); });
} }
// 上传文件 // 上传文件
export function uploadFile(data:any) { export function uploadFile(data: any) {
return request({ return request({
url: import.meta.env.VITE_APP_PREVIEW_URL + '/upload', url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/upload',
method: 'post', method: 'post',
data, data,
headers: { 'Content-Type': 'multipart/form-data' }, headers: { 'Content-Type': 'multipart/form-data' }
}); });
} }

View File

@ -1,8 +1,16 @@
<template> <template>
<div class="table-container" ref="tableContainerRef"> <div class="table-container" ref="tableContainerRef">
<a-table size="small" :loading="loading" :row-selection="enableRowSelection ? rowSelection : undefined" <a-table
:data-source="tableData" :columns="enhancedColumns" :pagination="paginationConfig" :scroll="scrollConfig" size="small"
:row-key="rowKey" @change="handleTableChange"> :loading="loading"
:row-selection="enableRowSelection ? rowSelection : undefined"
:data-source="tableData"
:columns="enhancedColumns"
:pagination="paginationConfig"
:scroll="scrollConfig"
:row-key="rowKey"
@change="handleTableChange"
>
<!-- 透传插槽支持自定义列内容 --> <!-- 透传插槽支持自定义列内容 -->
<template v-for="slot in Object.keys($slots)" #[slot]="scope" :key="slot"> <template v-for="slot in Object.keys($slots)" #[slot]="scope" :key="slot">
<slot :name="slot" v-bind="scope"></slot> <slot :name="slot" v-bind="scope"></slot>
@ -12,15 +20,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick, h } from "vue"; import { ref, computed, onMounted, watch, nextTick, h } from 'vue';
import { Tooltip } from "ant-design-vue"; import { Tooltip } from 'ant-design-vue';
import { calcTableScrollY } from "@/utils/index"; import { calcTableScrollY } from '@/utils/index';
// --- Types --- // --- Types ---
interface Props { interface Props {
columns: any[]; columns: any[];
scrollY?: string | number; // 使 scrollY?: string | number; // 使
listUrl: (params: any) => Promise<any>; scrollX?: string | number; //
listUrl?: (params: any) => Promise<any>;
data?: any[]; data?: any[];
enableRowSelection?: boolean; enableRowSelection?: boolean;
rowKey?: string; rowKey?: string;
@ -37,7 +46,7 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
enableRowSelection: false, enableRowSelection: false,
rowKey: "id", rowKey: 'id',
isPage: true, isPage: true,
data: () => [], data: () => [],
searchParams: () => ({}), searchParams: () => ({}),
@ -45,16 +54,19 @@ const props = withDefaults(defineProps<Props>(), {
getCheckboxProps: undefined, getCheckboxProps: undefined,
transformData: undefined, transformData: undefined,
scrollY: undefined, scrollY: undefined,
emptyPlaceholder: "-", emptyPlaceholder: '-',
processEmptyValues: true, // processEmptyValues: true, //
enableEllipsis: true, // enableEllipsis: true, //
enableSort: false, // enableSort: false //
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(e: "data-loaded", params: any, data: any): void; (e: 'data-loaded', params: any, data: any): void;
(e: "selection-change", selectedRowKeys: string[], selectedRows: any[]): void; (e: 'selection-change', selectedRowKeys: string[], selectedRows: any[]): void;
(e: "sort-change", sortInfo: { field: string; order: 'ascend' | 'descend' | null }): void; (
e: 'sort-change',
sortInfo: { field: string; order: 'ascend' | 'descend' | null }
): void;
}>(); }>();
// --- State --- // --- State ---
@ -72,19 +84,23 @@ const tableScrollY = ref<number>(0); // ✅ 新增:存储容器高度
// --- Computed Scroll Config --- // --- Computed Scroll Config ---
const scrollConfig = computed(() => { const scrollConfig = computed(() => {
const config: any = { const config: any = {
x: "100%", x: '100%',
y: 0, y: 0
}; };
// // 1. scrollY使 // 1. scrollX使
if (props.scrollX !== undefined && props.scrollX !== null) {
config.x = props.scrollX;
}
// 2. scrollY使
if (props.scrollY !== undefined && props.scrollY !== null) { if (props.scrollY !== undefined && props.scrollY !== null) {
config.y = props.scrollY; config.y = props.scrollY;
} }
// 2. 使 // 3. 使
else if (tableScrollY.value > 0) { else if (tableScrollY.value > 0) {
config.y = tableScrollY.value; config.y = tableScrollY.value;
} }
console.log(config);
return config; return config;
}); });
@ -95,11 +111,11 @@ const rowSelection = computed(() => ({
onChange: (keys: string[], rows: any[]) => { onChange: (keys: string[], rows: any[]) => {
selectedRowKeys.value = keys; selectedRowKeys.value = keys;
selectedRows.value = rows; selectedRows.value = rows;
emit("selection-change", keys, rows); emit('selection-change', keys, rows);
}, },
getCheckboxProps: props.getCheckboxProps getCheckboxProps: props.getCheckboxProps
? props.getCheckboxProps ? props.getCheckboxProps
: (record: any) => ({}), : (record: any) => ({})
})); }));
// --- Pagination Config --- // --- Pagination Config ---
@ -110,7 +126,7 @@ const paginationConfig = computed(() => ({
showSizeChanger: true, showSizeChanger: true,
showQuickJumper: true, showQuickJumper: true,
showTotal: (total: number) => `${total}`, showTotal: (total: number) => `${total}`,
pageSizeOptions: ["20", "50", "100"], pageSizeOptions: ['20', '50', '100']
})); }));
// --- Methods --- // --- Methods ---
@ -128,7 +144,7 @@ const getList = async (filter?: Record<string, any>) => {
...props.searchParams, ...props.searchParams,
skip: page.value, skip: page.value,
take: size.value, take: size.value,
filter: lastFilter.value, filter: lastFilter.value
}; };
if (!props.isPage) { if (!props.isPage) {
delete params.take; delete params.take;
@ -151,9 +167,9 @@ const getList = async (filter?: Record<string, any>) => {
tableData.value = processData(finalRecords); tableData.value = processData(finalRecords);
total.value = finalTotal; total.value = finalTotal;
emit("data-loaded", params, { records: finalRecords, total: finalTotal }); emit('data-loaded', params, { records: finalRecords, total: finalTotal });
} catch (error) { } catch (error) {
console.error("Fetch table data error:", error); console.error('Fetch table data error:', error);
tableData.value = []; tableData.value = [];
total.value = 0; total.value = 0;
} finally { } finally {
@ -166,14 +182,13 @@ const handleTableChange = (pag: any, filters: any, sorter: any) => {
size.value = pag.pageSize; size.value = pag.pageSize;
if (props.enableSort) { if (props.enableSort) {
if (sorter && sorter.field) { if (sorter && sorter.field) {
emit("sort-change", { field: sorter.field, order: sorter.order }); emit('sort-change', { field: sorter.field, order: sorter.order });
} else { } else {
emit("sort-change", { field: '', order: null }); emit('sort-change', { field: '', order: null });
} }
}else { } else {
getList(); getList();
} }
}; };
const reset = () => { const reset = () => {
@ -201,8 +216,8 @@ defineExpose({
clearSelection, clearSelection,
getSelected: () => ({ getSelected: () => ({
keys: selectedRowKeys.value, keys: selectedRowKeys.value,
rows: selectedRows.value, rows: selectedRows.value
}), })
}); });
const observer = new ResizeObserver(() => { const observer = new ResizeObserver(() => {
@ -218,7 +233,7 @@ const processData = (records: any[]) => {
for (const key in processedRecord) { for (const key in processedRecord) {
if (Object.prototype.hasOwnProperty.call(processedRecord, key)) { if (Object.prototype.hasOwnProperty.call(processedRecord, key)) {
const val = processedRecord[key]; const val = processedRecord[key];
if (val === null || val === undefined || val === "") { if (val === null || val === undefined || val === '') {
processedRecord[key] = props.emptyPlaceholder; processedRecord[key] = props.emptyPlaceholder;
} }
} }
@ -241,15 +256,21 @@ const enhancedColumns = computed(() => {
ellipsis: true, // Ant Design Vue ellipsis ellipsis: true, // Ant Design Vue ellipsis
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
const content = String(text ?? props.emptyPlaceholder); const content = String(text ?? props.emptyPlaceholder);
return h( return h(Tooltip, { title: content }, () =>
Tooltip, h(
{ title: content }, 'div',
() => h('div', { {
class: 'table-cell-ellipsis', class: 'table-cell-ellipsis',
style: { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' } style: {
}, content) overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis'
}
},
content
)
); );
}, }
}; };
}); });
}); });
@ -263,8 +284,24 @@ onMounted(() => {
}); });
if (props.data && props.data.length > 0) { if (props.data && props.data.length > 0) {
tableData.value = processData(props.data); tableData.value = processData(props.data);
total.value = props.data.length;
} }
}); });
// data prop
watch(
() => props.data,
newData => {
if (newData && newData.length > 0) {
tableData.value = processData(newData);
total.value = newData.length;
} else {
tableData.value = [];
total.value = 0;
}
},
{ deep: true }
);
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

View File

@ -0,0 +1,264 @@
<template>
<a-spin :spinning="loading">
<div class="attachment-container">
<!-- 左侧文件列表 -->
<div v-loading="loading" class="file-list" v-if="fileList.length > 0">
<div
v-for="file in fileList"
:key="file.id"
:class="['file-item', { active: selectedFile?.id === file.id }]"
@click="selectFile(file)"
>
<span class="file-name" :title="file.name">{{ file.name }}</span>
</div>
</div>
<!-- 右侧PDF预览 -->
<div class="pdf-preview">
<div v-if="showDownload && selectedFile" class="download-bar">
<button class="download-btn" @click="downloadFile(selectedFile)">
<span class="download-icon"></span>
<span>下载文件</span>
</button>
</div>
<pdf-view :file-id="selectedFile?.id || ''" />
<a-empty
v-show="!loading && fileList.length === 0"
class="empty-state"
:description="
props.tabKey == 'attachment' ? '暂无数据' : '暂无批复文件'
"
/>
</div>
</div>
</a-spin>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive, watch, computed } from 'vue';
import PdfView from '@/components/pdf/pdfView.vue';
import { queryPostUrlList, getIdUrl } from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
const modelStore = useModelStore();
const hasLoaded = ref(false); //
interface FileItem {
id: string | number;
name: string;
url: string;
}
const props = defineProps({
isActive: {
type: Boolean,
default: false
},
showDownload: {
type: Boolean,
default: false
},
tabKey: {
type: String,
default: 'attachment'
},
activeKey: {
type: String,
default: ''
},
url: {
type: String,
default: ''
}
});
const fileList = reactive<FileItem[]>([]);
const selectedFile = ref<FileItem | null>(null);
const loading = ref(false);
const selectFile = (file: FileItem) => {
selectedFile.value = file;
};
const downloadFile = (file: FileItem) => {
window.open(file.url, '_blank');
};
//
const loadReportData = () => {
if (hasLoaded.value) return;
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
}
]
}
};
loading.value = true;
queryPostUrlList(props.url, params).then(res => {
const ids = res.data?.data?.map((item: any) => item) || [];
if (ids.length === 0) {
hasLoaded.value = true;
loading.value = false;
return;
}
getIdUrl({
id: ids.join(',')
}).then(res1 => {
const data = res1?.data || {};
const fileArray = Object.keys(data).map(key => ({
id: key,
name: data[key].name,
url: data[key].fullpath
}));
loading.value = false;
hasLoaded.value = true;
updateFileList(fileArray);
});
});
};
//
const loadApprovalData = () => {
if (hasLoaded.value) return;
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
}
]
}
};
loading.value = true;
queryPostUrlList(props.url, params).then(res => {
const data = res.data?.data || [];
const fileArray = data.map((item: any) => ({
id: item.flpath.split('?')[1] || '',
name: item.approvalFileNo || item.approvalFileTitle,
url: item.flpath || ''
}));
hasLoaded.value = true;
updateFileList(fileArray);
});
};
const updateFileList = (files: FileItem[]) => {
fileList.length = 0;
files.forEach(file => {
fileList.push(file);
});
if (files.length > 0) {
selectedFile.value = files[0];
}
loading.value = false;
};
const initReportList = () => {
const type = props.tabKey;
if (type === 'approval') {
loadApprovalData();
} else if (type === 'attachment') {
loadReportData();
}
};
watch(
() => props.isActive,
active => {
if (active) {
initReportList();
}
},
{ immediate: true } //
);
</script>
<style scoped lang="scss">
.attachment-container {
display: flex;
width: 100%;
height: 600px;
border: 1px solid #e4e7ed;
border-radius: 3px;
overflow: hidden;
}
/* 左侧文件列表 */
.file-list {
width: 200px;
height: 100%;
overflow-y: auto;
background-color: #f8f9fa;
border-right: 1px solid #e4e7ed;
}
.file-item {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
cursor: pointer;
transition: background-color 0.2s;
border-bottom: 1px solid #e4e7ed;
&:hover {
background-color: #e8f4fd;
}
&.active {
background-color: #005293;
color: #fff;
}
}
.file-name {
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 右侧PDF预览 */
.pdf-preview {
flex: 1;
height: 100%;
position: relative;
}
.empty-state {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
color: #909399;
}
.empty-icon {
font-size: 48px;
}
.empty-text {
font-size: 14px;
}
.download-bar {
position: absolute;
right: 60px;
top: 4px;
z-index: 1000;
}
</style>

View File

@ -1,16 +1,31 @@
<template> <template>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<div class="modal-baseic-container" > <div class="modal-baseic-container">
<a-row class="w-[100%]" v-if="!loading"> <a-row class="w-[100%]" v-if="!loading">
<a-col :span="!item.visible ? 24 : (item.filed == '' ? 12 : 12)" v-for="(item, index) in tabledata" <a-col
:key="item.key"> :span="!item.visible ? 24 : item.filed == '' ? 12 : 12"
v-for="(item, index) in tabledata"
:key="item.key"
>
<div class="col-title" v-if="!item.visible"> <div class="col-title" v-if="!item.visible">
{{ item.name }} {{ item.name }}
<div v-if="modelStore.isBasicEdit && index == 0"> <div v-if="modelStore.isBasicEdit && index == 0">
<a-button v-if="!isEdit" type="primary" size="small" class="mr-2" @click="editBtn"> <a-button
v-if="!isEdit"
type="primary"
size="small"
class="mr-2"
@click="editBtn"
>
编辑信息 编辑信息
</a-button> </a-button>
<a-button v-else type="primary" size="small" class="mr-2" @click="saveBtn"> <a-button
v-else
type="primary"
size="small"
class="mr-2"
@click="saveBtn"
>
保存信息 保存信息
</a-button> </a-button>
<a-button type="primary" size="small" @click="recordBtn"> <a-button type="primary" size="small" @click="recordBtn">
@ -19,23 +34,46 @@
</div> </div>
</div> </div>
<div class="col-left" v-if="item.visible && item.filed != ''"> <div class="col-left" v-if="item.visible && item.filed != ''">
<a-tooltip :title="item.name.length > 10 ? item.name : ''">{{ item.name }} <a-tooltip :title="item.name.length > 10 ? item.name : ''"
>{{ item.name }}
</a-tooltip> </a-tooltip>
</div> </div>
<div class="col-right" v-if="item.visible"> <div class="col-right" v-if="item.visible">
<a-tooltip :title="!isEdit ? item.vlsr : ''"> <a-tooltip :title="!isEdit ? item.vlsr : ''">
<span v-if="!isEdit && item.filed != ''">{{ item.value || "-" }}</span> <span v-if="!isEdit && item.filed != ''">{{
item.value || '-'
}}</span>
<span v-else> <span v-else>
<!-- {{ item.value }} --> <!-- {{ item.value }} -->
<a-input v-if="item.type == 'input'" v-model:value="item.value" :placeholder="item.name" <a-input
size="small"></a-input> v-if="item.type == 'input'"
<a-select v-else-if="item.type == 'select'" v-model:value="item.value" :placeholder="item.name" v-model:value="item.value"
size="small" class="w-full"></a-select> :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> --> <!-- <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" <a-date-picker
:value-format="item.format" :picker="item.picker" class="w-full"></a-date-picker> v-else-if="item.type == 'date'"
<a-input-number v-else-if="item.type == 'number'" v-model:value="item.value" :placeholder="item.name" v-model:value="item.value"
size="small" class="w-full"></a-input-number> :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>
</a-tooltip> </a-tooltip>
</div> </div>
@ -43,13 +81,22 @@
</a-row> </a-row>
<div v-if="isLogo" class="img_box_one"> <div v-if="isLogo" class="img_box_one">
<div class="img_box"> <div class="img_box">
<div class="img_box_img"> <div class="img_box_img">
<a-image style="width: 100%;" :src="baseUrl + '/?' + data2.msStbprpT.logo" /> <a-image
</div> style="width: 100%"
:src="baseUrl + '/?' + data2.msStbprpT.logo"
/>
</div>
</div> </div>
</div> </div>
<a-modal v-model:open="visible" title="修改记录" :footer="null" width="900px" :zIndex="2000"> <a-modal
v-model:open="visible"
title="修改记录"
:footer="null"
width="900px"
:zIndex="2000"
>
123 123
</a-modal> </a-modal>
</div> </div>
@ -57,104 +104,121 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import dayjs from "dayjs"; import dayjs from 'dayjs';
import { ref, onMounted, nextTick,watch } from "vue"; import { ref, onMounted, nextTick, watch, computed } from 'vue';
import { BasicColumns, wtPointColumns, FhWpPointColumns, FhPointColumns, FhZQPointColumns, FPPointColumns, FBPointColumns, VaPointColumns, VPPointColumns, wePointColumns, waPointColumns, DwPointColumns, DwFivePointColumns, DwSixPointColumns, DwOnePointColumns, StinfoAiVidoPointColumns } from "../column.config"; import {
import { useModelStore } from "@/store/modules/model"; BasicColumns,
import { getStcdDetail } from "@/api/mapModal" wtPointColumns,
FhWpPointColumns,
FhPointColumns,
FhZQPointColumns,
FPPointColumns,
FBPointColumns,
VaPointColumns,
VPPointColumns,
wePointColumns,
waPointColumns,
DwPointColumns,
DwFivePointColumns,
DwSixPointColumns,
DwOnePointColumns,
StinfoAiVidoPointColumns
} from '../column.config';
import { useModelStore } from '@/store/modules/model';
import { getStcdDetail } from '@/api/mapModal';
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL; const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
const modelStore = useModelStore(); const modelStore = useModelStore();
const hasLoaded = ref(false); //
const props = defineProps({ const props = defineProps({
url: { url: {
type: String, type: String,
default: "", default: ''
}, },
isActive: {
type: Boolean,
default: false
}
}); });
const visible = ref(false); const visible = ref(false);
const isLogo = ref(false) const isLogo = ref(false);
const loading = ref(false); const loading = ref(false);
const data2: any = ref({}) const data2: any = ref({});
const tabledata = ref([]); const tabledata = ref([]);
console.log(BasicColumns);
const columnsConfig = ref([ const columnsConfig = ref([
{ {
type: "ENG", type: 'ENG',
columns: BasicColumns, columns: BasicColumns
}, },
{ {
type: "wt_point", type: 'wt_point',
columns: wtPointColumns, columns: wtPointColumns
}, },
{ {
type: "fh_wq_point", type: 'fh_wq_point',
columns: FhWpPointColumns, columns: FhWpPointColumns
}, },
{ {
type: "fh_point", type: 'fh_point',
columns: FhPointColumns, columns: FhPointColumns
}, },
{ {
type: "fh_zq_point", type: 'fh_zq_point',
columns: FhZQPointColumns, columns: FhZQPointColumns
},// }, //
{ {
type: "fp_point", type: 'fp_point',
columns: FPPointColumns, columns: FPPointColumns
}, },
{ {
type: "fb_point", type: 'fb_point',
columns: FBPointColumns, columns: FBPointColumns
}, },
{ {
type: "va_point", type: 'va_point',
columns: VaPointColumns, columns: VaPointColumns
}, },
{ {
type: "vp_point", type: 'vp_point',
columns: VPPointColumns, columns: VPPointColumns
}, },
{ {
type: "we_point", type: 'we_point',
columns: wePointColumns, columns: wePointColumns
}, },
{ {
type: "wa_point", type: 'wa_point',
columns: waPointColumns, columns: waPointColumns
}, },
{ {
type: "stinfo_ai_video_point", type: 'stinfo_ai_video_point',
columns: StinfoAiVidoPointColumns, columns: StinfoAiVidoPointColumns
}, },
{ {
type: "eng_alarm_point", type: 'eng_alarm_point',
columns: BasicColumns, columns: BasicColumns
}, },
{ {
type: "fprd_point", type: 'fprd_point',
columns: StinfoAiVidoPointColumns, columns: StinfoAiVidoPointColumns
}, },
{ {
type: "dw_point", type: 'dw_point',
columns: DwPointColumns, columns: DwPointColumns
}, },
{ {
type: "DW_5", type: 'DW_5',
columns: DwFivePointColumns, columns: DwFivePointColumns
}, },
{ {
type: "DW_6", type: 'DW_6',
columns: DwSixPointColumns, columns: DwSixPointColumns
}, },
{ {
type: "DW_1", type: 'DW_1',
columns: DwOnePointColumns, columns: DwOnePointColumns
}, }
//DW_1 //DW_1
]); ]);
@ -162,51 +226,58 @@ const isEdit = ref(false);
const editBtn = () => { const editBtn = () => {
nextTick(() => { nextTick(() => {
isEdit.value = true; isEdit.value = true;
tabledata.value.forEach((item) => { tabledata.value.forEach(item => {
if (item.type == "date") item.value = dayjs(item.value).format(item.format); if (item.type == 'date')
item.value = dayjs(item.value).format(item.format);
}); });
}); });
}; };
const saveBtn = () => { const saveBtn = () => {
isEdit.value = false; isEdit.value = false;
console.log(tabledata.value);
}; };
const recordBtn = () => { const recordBtn = () => {
visible.value = true; visible.value = true;
}; };
const getData = async () => { const getData = async () => {
if (hasLoaded.value) return;
loading.value = true; loading.value = true;
console.log(props.url);
console.log(modelStore.params.stcd);
if (props.url && modelStore.params.stcd) { if (props.url && modelStore.params.stcd) {
let res: any = await getStcdDetail(props.url, modelStore.params.stcd) let res: any = await getStcdDetail(props.url, modelStore.params.stcd);
console.log(res);
if (res.success) { if (res.success) {
let data = res.data let data = res.data;
data2.value = data data2.value = data;
let columns = [];
columns = columnsConfig.value.find((item) => item.type == modelStore.params.sttp)
.columns;
columns.forEach((item) => { let columns = [];
columns = columnsConfig.value.find(
item => item.type == modelStore.params.sttp
).columns;
columns.forEach(item => {
if (item.visible) { if (item.visible) {
if (item.type == "date") { if (item.type == 'date') {
if (data.msStbprpT[item.filed] && data.msStbprpT[item.filed] !== "-") { if (
item.value = dayjs(data.msStbprpT[item.filed] || "").format(item.format); data.msStbprpT[item.filed] &&
data.msStbprpT[item.filed] !== '-'
) {
item.value = dayjs(data.msStbprpT[item.filed] || '').format(
item.format
);
} else { } else {
item.value = null; item.value = null;
} }
} else { } else {
if (item.type == "number") { if (item.type == 'number') {
if (data.msStbprpT[item.filed]) { if (data.msStbprpT[item.filed]) {
if (item.filed == "ttpwr") { if (item.filed == 'ttpwr') {
item.value = item.value =
(Number(data.msStbprpT[item.filed] || 0) / 10).toFixed(item.toFixed) || (Number(data.msStbprpT[item.filed] || 0) / 10).toFixed(
null; item.toFixed
) || null;
} else { } else {
item.value = item.value =
Number(data.msStbprpT[item.filed] || 0).toFixed(item.toFixed) || null; Number(data.msStbprpT[item.filed] || 0).toFixed(
item.toFixed
) || null;
} }
} else { } else {
item.value = null; item.value = null;
@ -215,59 +286,54 @@ const getData = async () => {
item.value = data.msStbprpT[item.filed] || null; item.value = data.msStbprpT[item.filed] || null;
} }
} }
let stcdItem = data.stcdValVoList.find((stcdItem) => { let stcdItem = data.stcdValVoList.find(stcdItem => {
let str = ""; let str = '';
let arr = [ let arr = [
"baseName", 'baseName',
"addvcdName", 'addvcdName',
"hbrvcdName", 'hbrvcdName',
"bldsttCcodeName", 'bldsttCcodeName',
"dvtpName", 'dvtpName',
"rgcpName", 'rgcpName'
]; ];
if (arr.includes(item.filed)) { if (arr.includes(item.filed)) {
// name // name
str = item.filed.replace("Name", ""); str = item.filed.replace('Name', '');
} else { } else {
str = item.filed; str = item.filed;
} }
return stcdItem.attrKey == str; return stcdItem.attrKey == str;
}); });
if (stcdItem && stcdItem.attrKey == "rgcp") { if (stcdItem && stcdItem.attrKey == 'rgcp') {
console.log(stcdItem);
} }
if (stcdItem && stcdItem.vlsr) { if (stcdItem && stcdItem.vlsr) {
item.vlsr = stcdItem.vlsr; item.vlsr = stcdItem.vlsr;
} }
// console.log(stcdItem);
} }
}); });
tabledata.value = columns; tabledata.value = columns;
loading.value = false; loading.value = false;
hasLoaded.value = true;
} else { } else {
loading.value = false; loading.value = false;
} }
if (data2.value.msStbprpT.logo) { if (data2.value.msStbprpT.logo) {
isLogo.value = true isLogo.value = true;
} else { } else {
isLogo.value = false isLogo.value = false;
} }
} }
};
} watch(
() => props.isActive,
onMounted(() => { active => {
getData() if (active && !hasLoaded.value) {
getData();
}
},
{ immediate: true } //
);
setTimeout(() => {
}, 1000);
});
//modelStore.params.stcd //modelStore.params.stcd
watch( watch(
() => modelStore.params.stcd, () => modelStore.params.stcd,
@ -288,7 +354,7 @@ watch(
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
.img_box_one{ .img_box_one {
width: 40%; width: 40%;
// height: 80%; // height: 80%;
} }
@ -306,20 +372,12 @@ watch(
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(0, 127, 204, 0.65); border: 1px solid rgba(0, 127, 204, 0.65);
} }
} }
.ant-row { .ant-row {
border-left: 1px solid rgba(0, 127, 204, 0.65); border-left: 1px solid rgba(0, 127, 204, 0.65);
border-top: 1px solid rgba(0, 127, 204, 0.65); border-top: 1px solid rgba(0, 127, 204, 0.65);
height: 100%; height: 100%;
.ant-col:nth-last-child(-n + 2) {
.col-left,
.col-right {
// border-bottom: none !important;
}
}
} }
.ant-col { .ant-col {

View File

@ -0,0 +1,201 @@
<template>
<div class="early-warning-alert">
<a-tabs v-model:activeKey="activeTabKey" @change="handleTabChange">
<a-tab-pane v-for="tab in tabsItems" :key="tab.key" :tab="tab.name">
<div class="table-wrapper">
<BasicTable
ref="tableRef"
:columns="currentColumns"
:list-url="fetchTableData"
:search-params="searchParams"
:enable-sort="true"
/>
</div>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
import BasicTable from '@/components/BasicTable/index.vue';
import { queryPostUrlList } from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
const props = defineProps<{
tabsItems: Array<{
name: string;
key: string;
type: string;
hiddenChart: boolean;
tableUrl: string;
}>;
isActive: boolean;
}>();
const modelStore = useModelStore();
const activeTabKey = ref(props.tabsItems?.[0]?.key || '');
const hasLoaded = ref(false);
const tableRef = ref();
//
const searchParams = computed(() => ({
...modelStore.params
}));
// tab
const columnsMap: Record<string, any[]> = {
DesignParameterChangePrompt: [
{
title: '重大变动类型',
dataIndex: 'majorChangeType',
key: 'majorChangeType'
},
{ title: '依据阶段', dataIndex: 'basisStage', key: 'basisStage' },
{
title: '有重大变动的阶段',
dataIndex: 'changedStage',
key: 'changedStage'
},
{
title: '依据阶段数据',
dataIndex: 'basisStageData',
key: 'basisStageData'
},
{
title: '重大变动阶段数据',
dataIndex: 'changedStageData',
key: 'changedStageData'
},
{ title: '预警描述', dataIndex: 'warningDesc', key: 'warningDesc' }
],
ImplementEarlyWarning: [
{ title: '设施名称', dataIndex: 'stnm', key: 'stnm' },
{ title: '行政区', dataIndex: 'addvnm', key: 'addvnm' },
{ title: '流域', dataIndex: 'rvnm', key: 'rvnm' },
{
title: '可研(核准)',
dataIndex: 'feasibilityApproval',
key: 'feasibilityApproval'
},
{
title: '预可是否设计',
dataIndex: 'preFeasibilityDesign',
key: 'preFeasibilityDesign'
},
{
title: '环评是否有要求',
dataIndex: 'eiarqst',
key: 'eiarqst'
},
{ title: '专项设计', dataIndex: 'specialDesign', key: 'specialDesign' },
{ title: '建设', dataIndex: 'isbuild', key: 'isbuild' },
{ title: '证明材料', dataIndex: 'proofMaterial', key: 'proofMaterial' }
],
ConstructionEarlyWarning: [
{ title: '设施名称', dataIndex: 'facilityName', key: 'facilityName' },
{ title: '行政区', dataIndex: 'district', key: 'district' },
{ title: '流域', dataIndex: 'basin', key: 'basin' },
{
title: '可研(核准)',
dataIndex: 'feasibilityApproval',
key: 'feasibilityApproval'
},
{
title: '预可是否设计',
dataIndex: 'preFeasibilityDesign',
key: 'preFeasibilityDesign'
},
{
title: '环评是否有要求',
dataIndex: 'eiaRequirement',
key: 'eiaRequirement'
},
{ title: '专项设计', dataIndex: 'specialDesign', key: 'specialDesign' },
{ title: '建设', dataIndex: 'construction', key: 'construction' },
{ title: '证明材料', dataIndex: 'proofMaterial', key: 'proofMaterial' }
],
RunEarlyWarning: [
{ title: '设施名称', dataIndex: 'stnm', key: 'stnm' },
{ title: '所属行政区域', dataIndex: 'addvnm', key: 'addvnm' },
{ title: '运行状态', dataIndex: 'status', key: 'status' }
],
ReleaseEarlyWarning: [
{ title: '时间', dataIndex: 'releaseTime', key: 'releaseTime' },
{ title: '鱼类', dataIndex: 'fishType', key: 'fishType' },
{ title: '放鱼规格(cm)', dataIndex: 'fishSpec', key: 'fishSpec' },
{ title: '放鱼数量(尾)', dataIndex: 'fishCount', key: 'fishCount' },
{ title: '放流地点', dataIndex: 'releaseLocation', key: 'releaseLocation' },
{ title: '所在河流', dataIndex: 'river', key: 'river' },
{ title: '鱼龄(年)', dataIndex: 'fishAge', key: 'fishAge' },
{ title: '标记方式', dataIndex: 'markMethod', key: 'markMethod' },
{ title: '标记种类', dataIndex: 'markType', key: 'markType' },
{ title: '标记数量(尾)', dataIndex: 'markCount', key: 'markCount' },
{ title: '运行状态', dataIndex: 'runStatus', key: 'runStatus' }
]
};
// tab
const currentColumns = computed(() => {
return columnsMap[activeTabKey.value] || [];
});
// tab
const currentTableUrl = computed(() => {
const tab = props.tabsItems.find(item => item.key === activeTabKey.value);
return tab?.tableUrl || '';
});
//
const fetchTableData = async (params: any) => {
if (!currentTableUrl.value) {
return { data: { records: [], total: 0 } };
}
return await queryPostUrlList(currentTableUrl.value, params);
};
// tab
const handleTabChange = (key: string) => {
activeTabKey.value = key;
// tab
if (tableRef.value) {
tableRef.value.refresh();
}
};
// isActive
watch(
() => props.isActive,
newVal => {
if (newVal && !hasLoaded.value) {
hasLoaded.value = true;
// tab
setTimeout(() => {
if (tableRef.value) {
// tableRef.value.getList();
}
}, 100);
}
},
{ immediate: true }
);
// tabsItems activeTabKey
watch(
() => props.tabsItems,
newTabs => {
if (newTabs && newTabs.length > 0 && !activeTabKey.value) {
activeTabKey.value = newTabs[0].key;
}
},
{ immediate: true }
);
</script>
<style lang="scss" scoped>
.early-warning-alert {
.table-wrapper {
min-height: 300px;
}
}
</style>

View File

@ -0,0 +1,609 @@
<template>
<div class="monitor-info">
<!-- Tabs 区域 -->
<a-tabs v-model:activeKey="activeTabKey">
<a-tab-pane v-for="tab in tabsList" :key="tab.key" :tab="tab.name" />
<template #rightExtra>
<div class="search-bar">
<a-range-picker
v-model:value="dateRange"
:format="dateFormat"
:show-time="showTime ? showTimeConfig : false"
:allowClear="false"
:disabled-date="disabledDate"
>
</a-range-picker>
<a-button type="primary" class="search-btn" @click="handleSearch">
查询
</a-button>
</div>
</template>
</a-tabs>
<!-- 内容区域 -->
<div v-if="activeTabKey === 'dzxq.tabs.jcsj'" class="tab-content">
<a-spin :spinning="isLoading" tip="加载中...">
<!-- 左侧图表 + 右侧表格 -->
<div class="content-body">
<div class="chart-wrapper" ref="chartRef"></div>
<div class="table-wrapper">
<BasicTable
ref="tableRef"
:scrollY="480"
:scrollX="980"
:columns="currentColumns"
:data="tableData"
:isPage="true"
/>
</div>
</div>
</a-spin>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import dayjs, { Dayjs } from 'dayjs';
import { getMonitorData } from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
import BasicTable from '@/components/BasicTable/index.vue';
const modelStore = useModelStore();
const props = defineProps({
isActive: {
type: Boolean,
default: false
},
code: {
type: String,
default: ''
}
});
const hasLoaded = ref(false);
const isLoading = ref(false);
const activeTabKey = ref('dzxq.tabs.jcsj');
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const tableRef = ref<any>(null);
const tableData = ref<any[]>([]);
// ==================== Tab ====================
const tabsConfig: Record<string, any[]> = {
'dzxq.tabs.jcsj': [{ name: '电站运行过程线', key: 'dzxq.tabs.jcsj' }]
};
const tabsList = computed(() => {
return tabsConfig[props.code] || tabsConfig['dzxq.tabs.jcsj'];
});
// ==================== ====================
const timeConfig: Record<string, { showTime: boolean; format: string }> = {
'dzxq.tabs.jcsj': { showTime: true, format: 'YYYY-MM-DD HH:mm' }
};
const currentTimeConfig = computed(() => {
return timeConfig[props.code] || timeConfig['dzxq.tabs.jcsj'];
});
const showTime = computed(() => currentTimeConfig.value.showTime);
const dateFormat = computed(() => currentTimeConfig.value.format);
//
const showTimeConfig = {
format: 'HH:mm',
hourStep: 1,
minuteStep: 5,
secondStep: 60
};
//
const initDateRange = (): [Dayjs, Dayjs] => {
const endDate = dayjs();
const startDate = dayjs().subtract(1, 'month');
return [startDate, endDate];
};
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
//
const disabledDate = (current: Dayjs) => {
return current && current.isAfter(dayjs(), 'day');
};
// ==================== ====================
const tableColumnsConfig: Record<string, any[]> = {
'dzxq.tabs.jcsj': [
{
title: '时间',
dataIndex: 'tm',
width: 180,
fixed: 'left',
customRender: ({ text }: any) =>
text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
},
{
title: '坝上水位(m)',
dataIndex: 'rz',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
},
{
title: '坝下水位(m)',
dataIndex: 'dz',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
},
{
title: '入库流量(m³/s)',
dataIndex: 'qi',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Math.round(Number(text)) : '-'
},
{
title: '出库流量(m³/s)',
dataIndex: 'qo',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text) : '-'
},
{
title: '生态流量(m³/s)',
dataIndex: 'qec',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
},
{
title: '生态流量限值(m³/s)',
dataIndex: 'qecLimit',
width: 150,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
}
]
};
const currentColumns = computed(() => {
return tableColumnsConfig[props.code] || tableColumnsConfig['dzxq.tabs.jcsj'];
});
// ==================== ====================
const chartLegendConfig: Record<string, string[]> = {
'dzxq.tabs.jcsj': [
'坝上水位',
'坝下水位',
'入库流量',
'出库流量',
'生态流量',
'生态流量限值'
]
};
const chartLegendData = computed(() => {
return chartLegendConfig[props.code] || chartLegendConfig['dzxq.tabs.jcsj'];
});
// ==================== ====================
const fetchData = async () => {
if (!dateRange.value) return;
isLoading.value = true;
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
},
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
}
]
},
sort: [
{
field: 'tm',
dir: 'asc'
}
]
};
try {
const res = await getMonitorData(params);
const rawData = res?.data?.data || res?.data?.records || [];
// 11
const reversedData = [...rawData].reverse();
tableData.value = reversedData;
updateChart(rawData); //
} catch (error) {
console.error('获取数据失败:', error);
tableData.value = [];
} finally {
isLoading.value = false;
}
};
//
const handleSearch = () => {
fetchData();
};
// ==================== ====================
const updateChart = (data: any[]) => {
if (!chartInstance) return;
if (!data || data.length === 0) {
chartInstance.clear();
return;
}
//
const sorted = [...data].sort(
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
);
const xAxisData = sorted.map(item => dayjs(item.tm).format('HH:mm'));
const rzData = sorted.map(item => item.rz);
const dzData = sorted.map(item => item.dz);
const qiData = sorted.map(item => item.qi);
const qoData = sorted.map(item => item.qo);
const qecData = sorted.map(item => item.qec);
const qecLimitData = sorted.map(item => item.qecLimit);
// Y
const waterValues = [...rzData, ...dzData].filter(
v => v !== null && v !== undefined
);
const waterMax = waterValues.length > 0 ? Math.max(...waterValues) : 10;
const waterYMax = Math.ceil(waterMax * 1.1);
const waterYMin = Math.floor(Math.min(...waterValues) * 0.95);
// Y
const flowValues = [...qiData, ...qoData, ...qecData, ...qecLimitData].filter(
v => v !== null && v !== undefined
);
const flowMax = flowValues.length > 0 ? Math.max(...flowValues) : 10;
const flowYMax = Math.ceil(flowMax * 1.1);
const option = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(50, 50, 50, 0.9)',
textStyle: { color: '#fff', fontSize: 12 },
axisPointer: { type: 'cross' },
formatter: (params: any) => {
if (!params || params.length === 0) return '';
const dataIndex = params[0].dataIndex;
const fullTime = sorted[dataIndex]?.tm
? dayjs(sorted[dataIndex].tm).format('YYYY-MM-DD HH:mm:ss')
: '';
let html = `<div style="font-size:14px;margin-bottom:8px;">${fullTime}</div>`;
params.forEach((param: any) => {
const value = param.value != null ? param.value : '-';
html += `
<div style="display:flex;align-items:center;margin:4px 0;">
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
<span style="font-size:14px;">${param.seriesName}: ${value}</span>
</div>
`;
});
return html;
}
},
legend: {
type: 'scroll',
top: 10,
data: chartLegendData.value,
textStyle: { fontSize: 12 },
selected: {
坝上水位: false,
坝下水位: false,
入库流量: true,
出库流量: true,
生态流量: false,
生态流量限值: false
}
},
grid: {
left: 60,
right: 60,
top: 80,
bottom: 60
},
xAxis: {
type: 'category',
data: xAxisData,
axisLine: { lineStyle: { color: '#000000' } },
axisTick: { show: false },
axisLabel: {
fontSize: 12
},
splitLine: {
show: true,
lineStyle: { color: '#bfbfbf', type: 'solid' }
}
},
yAxis: [
// Y
{
name: '水位',
type: 'value',
position: 'left',
axisLine: { lineStyle: { color: '#000000' } },
alignTicks: true,
scale: true,
splitNumber: 9,
boundaryGap: ['10%', 0]
},
// Y
{
name: '流量',
type: 'value',
position: 'right',
axisLine: { lineStyle: { color: '#000000' } },
alignTicks: true,
scale: true,
splitNumber: 9,
boundaryGap: ['10%', 0]
}
],
series: [
// 使 Y
{
name: '坝上水位',
type: 'line',
yAxisIndex: 0,
data: rzData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#56C2E3', width: 2 },
itemStyle: { color: '#56C2E3' },
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
}
},
{
name: '坝下水位',
type: 'line',
yAxisIndex: 0,
data: dzData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#7399C6', width: 2 },
itemStyle: { color: '#7399C6' },
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
}
},
// 使 Y
{
name: '入库流量',
type: 'line',
yAxisIndex: 1,
data: qiData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#4B79AB', width: 2 },
itemStyle: { color: '#4B79AB' },
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
}
},
{
name: '出库流量',
type: 'line',
yAxisIndex: 1,
data: qoData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#78C300', width: 2 },
itemStyle: { color: '#78C300' },
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
}
},
{
name: '生态流量',
type: 'line',
yAxisIndex: 1,
data: qecData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#00A050', width: 2 },
itemStyle: { color: '#00A050' },
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
}
},
{
name: '生态流量限值',
type: 'line',
yAxisIndex: 1,
data: qecLimitData,
smooth: true,
symbol: 'circle',
symbolSize: 4,
lineStyle: { color: '#F7A737', width: 2, type: 'dashed' },
itemStyle: { color: '#F7A737' },
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
}
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0],
throttle: 50,
start: 0,
end: 100
}
],
toolbox: {
show: true,
feature: {
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
},
right: 20,
top: 10
}
};
chartInstance.setOption(option, true);
};
const initChart = () => {
if (!chartRef.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
};
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
const destroyChart = () => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
};
// ==================== ====================
const initData = () => {
if (hasLoaded.value) return;
fetchData();
setTimeout(() => {
initChart();
}, 200);
hasLoaded.value = true;
};
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) {
initData();
}
},
{ immediate: true }
);
watch(
() => modelStore.params.stcd,
(newStcd, oldStcd) => {
if (newStcd && newStcd !== oldStcd) {
console.log('【MonitorInfo】stcd 变化,重新加载数据:', newStcd);
fetchData();
}
}
);
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
destroyChart();
window.removeEventListener('resize', handleResize);
});
</script>
<style lang="scss" scoped>
.monitor-info {
width: 100%;
min-height: 600px;
:deep(.ant-tabs-nav) {
margin-bottom: 4px;
}
.search-bar {
width: 100%;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
.search-btn {
margin-left: 8px;
}
}
.tab-content {
width: 100%;
.content-body {
width: 100%;
display: flex;
height: 600px;
.chart-wrapper {
flex: 1;
height: 100%;
box-sizing: border-box;
padding-top: 10px;
}
.table-wrapper {
flex: 1;
min-width: 500px;
height: 100%;
box-sizing: border-box;
padding: 10px;
overflow-x: auto;
border-left: 1px solid #dcdfe6;
margin-left: 10px;
}
}
}
}
</style>

View File

@ -0,0 +1,140 @@
<template>
<div class="panorama-info-view-container">
<a-spin :spinning="loading">
<iframe
v-show="iframeUrl != ''"
:src="iframeUrl"
class="panorama-info-iframe"
frameborder="0"
></iframe>
<a-select
v-show="iframeUrl != ''"
class="panorama-info-select"
v-model:value="selectedOption"
:options="options"
:fieldNames="{
label: 'name',
value: 'id'
}"
placeholder="请选择文件"
@change="handleSelectChange"
/>
<a-empty
v-show="!loading && iframeUrl == ''"
class="empty-state"
description="暂无数据"
/>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { getPanoramaList, getIdUrl } from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
const modelStore = useModelStore();
const hasLoaded = ref(false); //
const loading = ref(true); //
const props = defineProps({
url: {
type: String,
default: ''
},
isActive: {
type: Boolean,
default: false
}
});
const selectedOption = ref('');
const options = ref([]);
const iframeUrl = ref('');
const handleSelectChange = value => {
options.value.forEach(item => {
if (item.id === value) {
iframeUrl.value = item.newUrl;
}
});
};
const initPanoramaInfoList = () => {
if (hasLoaded.value) return;
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
}
]
},
select: ['url', 'stnm']
};
loading.value = true;
getPanoramaList(params).then(res => {
if (res.data.data[0].url) {
getIdUrl({
id: res.data.data[0].url
}).then(res1 => {
hasLoaded.value = true; //
loading.value = false;
const data = res1?.data || {};
const fileArray = Object.keys(data).map(key => ({
id: key,
name: data[key].name,
oldUrl: data[key].fullpath,
newUrl:
import.meta.env.VITE_APP_ATTACHMENT_URL +
'/Pano/Show/' +
data[key].fullpath.split('?')[1]
}));
options.value = fileArray;
iframeUrl.value = options.value[0].newUrl;
selectedOption.value = options.value[0].id;
});
} else {
hasLoaded.value = true; //
loading.value = false;
}
});
};
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) {
initPanoramaInfoList();
}
},
{ immediate: true } //
);
</script>
<style scoped lang="scss">
.panorama-info-view-container {
width: 100%;
height: 100%;
position: relative;
background-color: #f5f5f5;
}
.panorama-info-select {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
}
.panorama-info-iframe {
width: 100%;
height: 600px;
}
.empty-state {
height: 600px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
</style>

View File

@ -1,12 +0,0 @@
<template>
<div>
监测数据
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
</script>
<style lang="scss" scoped></style>

View File

@ -1,682 +0,0 @@
<template>
<div class="body_one">
<div class="search_one">
<div class="search_left">
<div>鱼类选择</div>
<a-select v-model:value="selectValue" mode="multiple" style="width: 260px;" placeholder=" "
:options="selectOption" @change="handleChange"></a-select>
</div>
<div>
<a-range-picker v-model:value="rangePicker" format="YYYY-MM-DD" :disabled-date="disabledDate"
:allow-clear="false" @change="handleDateChange">
<template #renderExtraFooter>
<div class="quick-date-options">
<a-button size="small" @click="setQuickRange(7)">最近七天</a-button>
<a-button size="small" @click="setQuickRange(30)">最近一个月</a-button>
<a-button size="small" @click="setQuickRange(90)">最近三个月</a-button>
<a-button size="small" @click="setQuickRange(180)">最近六个月</a-button>
</div>
</template>
</a-range-picker>
</div>
</div>
<div class="body_body">
<div class="echarts" ref="chartRef"></div>
<div class="table">
<BasicTable ref="tableRef" :scrollY="460" :columns="columns" :list-url="fishGetKendoListCust"
:search-params="{}" :transform-data="customTransform">
<!-- 同期对比列的自定义渲染 -->
<template #contrast="{ record }">
<span v-if="record.wt && record.beforeWt"
:style="{ color: (record.wt - record.beforeWt) > 0 ? 'rgb(255, 85, 0)' : 'rgb(135, 208, 104)' }">
{{ (record.wt - record.beforeWt) > 0 ? '+' : '' }}{{ (record.wt -
record.beforeWt).toFixed(2) }}
</span>
<span v-else>-</span>
</template>
</BasicTable>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted, computed } from "vue";
import * as echarts from 'echarts';
import dayjs, { Dayjs } from 'dayjs';
import { useModelStore } from "@/store/modules/model";
import { infoGetKendoListCust, fishGetKendoListCust } from "@/api/sw";
import BasicTable from "@/components/BasicTable/index.vue";
const modelStore = useModelStore();
const selectOption = ref([])
const selectValue = ref([])
const tableRef = ref<any>(null);
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const chartData = ref<any[]>([]);
const columns = [
{
title: '时间',
dataIndex: 'dt',
width: 180,
customRender: ({ text }: any) => text ? dayjs(text).format('YYYY-MM-DD') : '-'
},
{
title: '水温°C',
dataIndex: 'wt',
customRender: ({ text }: any) => text !== undefined && text !== null ? text : '-'
},
{
title: '上一年水温',
dataIndex: 'beforeWt',
customRender: ({ text }: any) => text !== undefined && text !== null ? text : '-'
},
{
title: '同期对比',
dataIndex: 'contrast',
width: 120,
slots: { customRender: 'contrast' }
},
{
title: '适宜产卵鱼',
dataIndex: 'spawnFish',
width: 200,
customRender: ({ record }: any) => {
if (record.fishList && Array.isArray(record.fishList) && record.fishList.length > 0) {
return record.fishList.map(fish => fish.name).join('、');
}
return '-';
}
},
];
// 7
const initDateRange = () => {
const endDate = dayjs();
const startDate = dayjs().subtract(7, 'day');
return [startDate, endDate] as [Dayjs, Dayjs];
};
const rangePicker = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
//
const disabledDate = (current: Dayjs) => {
//
return current && current.isAfter(dayjs(), 'day');
};
//
const setQuickRange = (days: number) => {
const endDate = dayjs();
const startDate = dayjs().subtract(days, 'day');
rangePicker.value = [startDate, endDate];
//
handleDateChange(rangePicker.value, [
startDate.format('YYYY-MM-DD'),
endDate.format('YYYY-MM-DD')
]);
};
//
const handleDateChange = (dates: any, dateStrings: string[]) => {
console.log('日期改变:', dates, dateStrings);
getEchartsData()
gerTableData()
// TODO:
};
//
const getSelectOption = async () => {
//
// modelStore.params.date
let params = {
"filter": {
"logic": "and",
"filters": [
{
"field": "stcd",
"operator": "eq",
"dataType": "string",
"value": modelStore.params.stcd
}
]
}
}
let res = await infoGetKendoListCust(params)
let data = res?.data?.data || res?.data
console.log(res)
selectOption.value = data.map(item => {
return {
label: item.name,
value: item.id,
pretemp: item.pretemp,
}
})
// ID2221
selectValue.value = data.length >= 2 ? data.slice(0, 2).map(item => item.id) : (data.length > 0 ? [data[0].id] : [])
}
//echarts
const getEchartsData = async () => {
const params = {
"filter": {
"logic": "and",
"filters": [
{
"field": "stcd",
"operator": "eq",
"dataType": "string",
"value": modelStore.params.stcd
},
{
"field": "startTime",
"operator": "gte",
"dataType": "date",
"value": rangePicker.value[0].format('YYYY-MM-DD') + " 00:00:00"
},
{
"field": "endTime",
"operator": "lte",
"dataType": "date",
"value": rangePicker.value[1].format('YYYY-MM-DD') + " 23:59:59"
}
]
},
"sort": [
{
"field": "dt",
"dir": "desc"
}
]
}
let res = await fishGetKendoListCust(params)
console.log('ECharts原始数据:', res)
//
const rawData = res?.data?.data || res?.data?.records || []
chartData.value = rawData
// DOM
setTimeout(() => {
updateChart()
}, 100)
}
//table
const gerTableData = async () => {
const filter = {
logic: 'and',
"filters": [
{
"field": "stcd",
"operator": "eq",
"dataType": "string",
"value": modelStore.params.stcd
},
{
"field": "startTime",
"operator": "gte",
"dataType": "date",
"value": rangePicker.value[0].format('YYYY-MM-DD') + " 00:00:00"
},
{
"field": "endTime",
"operator": "lte",
"dataType": "date",
"value": rangePicker.value[1].format('YYYY-MM-DD') + " 23:59:59"
}
]
};
tableRef.value?.getList(filter);
}
const handleChange = (value: any) => {
console.log(value)
getEchartsData()
// gerTableData()
}
const customTransform = (res: any) => {
console.log('表格数据:', res);
return {
records: res?.data?.data || [],
total: res?.data?.total || 0
};
};
//
const processChartData = (rawData: any[]) => {
if (!rawData || !Array.isArray(rawData)) return [];
// wt beforeWt null
const filtered = rawData.filter(item =>
item.wt !== null && item.wt !== undefined
);
// && item.beforeWt !== null && item.beforeWt !== undefined
// dt
return filtered.sort((a, b) =>
new Date(a.dt).getTime() - new Date(b.dt).getTime()
);
};
// Y
const calculateYAxisMax = (data: any[]) => {
if (!data || data.length === 0) return 10;
const allValues = [
...data.map(item => item.wt),
...data.map(item => item.beforeWt)
].filter(v => v !== null && v !== undefined);
const max = Math.max(...allValues);
return Math.ceil(max);
};
// HSLHEX
const generateRandomColor = () => {
const hue = Math.random() * 360; // 0-360°
const saturation = 40 + Math.random() * 30; // 40%-70%
const lightness = 45 + Math.random() * 20; // 45%-65%
// HSLRGB
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 = c; g = x; b = 0; }
else if (hue < 120) { r = x; g = c; b = 0; }
else if (hue < 180) { r = 0; g = c; b = x; }
else if (hue < 240) { r = 0; g = x; b = c; }
else if (hue < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
// RGBHEX
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)}`;
};
//
const calculateTempRanges = () => {
if (!selectValue.value || selectValue.value.length === 0) {
return [];
}
// selectOption
const selectedFish = selectOption.value.filter(fish =>
selectValue.value.includes(fish.value)
);
if (selectedFish.length === 0) {
return [];
}
//
return selectedFish
.map(fish => {
if (!fish.pretemp || !Array.isArray(fish.pretemp) || fish.pretemp.length !== 2) {
return null;
}
const min = parseFloat(fish.pretemp[0]);
const max = parseFloat(fish.pretemp[1]);
if (isNaN(min) || isNaN(max)) {
return null;
}
return {
name: fish.label,
min,
max,
color: generateRandomColor()
};
})
.filter(item => item !== null);
};
// ECharts
const getChartOption = computed(() => {
const data = processChartData(chartData.value);
if (data.length === 0) {
return {};
}
// const yAxisMax = calculateYAxisMax(data);
const tempRanges = calculateTempRanges();
const dataMax = calculateYAxisMax(data);
const tempMax = tempRanges.length > 0
? Math.max(...tempRanges.map(r => r.max))
: 0;
const yAxisMax = Math.max(dataMax, tempMax);
//
const xAxisData = data.map(item => dayjs(item.dt).format('YYYY-MM-DD'));
const wtData = data.map(item => item.wt);
const beforeWtData = data.map(item => item.beforeWt);
// markArea
const markAreaConfig = tempRanges.length > 0 ? {
silent: true,
data: tempRanges.map(range => [
{
yAxis: range.min,
name: `${range.name}: ${range.min}-${range.max}°C`,
itemStyle: { color: `${range.color}26` }
},
{
yAxis: range.max
}
])
} : undefined;
return {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(50, 50, 50, 0.9)',
textStyle: {
color: '#fff',
fontSize: 14
},
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
formatter: (params: any) => {
if (!params || params.length === 0) return '';
const date = params[0].axisValue;
let html = `<div style="font-weight:bold;margin-bottom:8px;">${date}</div>`;
params.forEach((param: any) => {
const dataIndex = param.dataIndex;
const record = data[dataIndex];
const value = param.value != null ? param.value : '-';
html += `
<div style="display:flex;align-items:center;margin:4px 0;">
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
<span>${param.seriesName}: ${value}°C</span>
</div>
`;
});
return html;
}
},
legend: {
top: 10,
left: 'center',
data: ['现在水温', '上一年同期水温'],
textStyle: {
fontSize: 13
}
},
grid: {
left: 60,
right: 40,
top: 60,
bottom: 50
},
xAxis: {
type: 'category',
data: xAxisData,
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: false
},
axisLabel: {
interval: (() => {
const dataLen = xAxisData.length;
if (dataLen <= 8) return 0; //
//
// 2 + 6 = 8
const maxLabels = 8;
const minInterval = Math.ceil((dataLen - 1) / (maxLabels - 1));
// 使使
const calculatedInterval = Math.floor((dataLen - 1) / 7);
return Math.max(calculatedInterval, minInterval);
})(),
fontSize: 12,
rotate: 0,
margin: 10
},
splitLine: {
show: true,
lineStyle: {
color: '#bfbfbf',
type: 'solid'
}
}
},
yAxis: {
type: 'value',
name: '水温(°C)',
min: 0,
max: yAxisMax,
scale: false,
axisLine: {
lineStyle: {
color: '#000000',
width: 1
}
},
axisTick: {
show: true,
length: 4
},
axisLabel: {
fontSize: 12,
formatter: '{value}'
},
splitLine: {
show: true,
lineStyle: {
color: '#bfbfbf',
type: 'solid'
}
}
},
series: [
{
name: '现在水温',
type: 'line',
data: wtData,
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#009dff', width: 2 },
itemStyle: { color: '#009dff' },
connectNulls: false,
// markArea
},
{
name: '上一年同期水温',
type: 'line',
data: beforeWtData,
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#fee588', width: 2 },
itemStyle: { color: '#fee588' },
connectNulls: false
},
// markArea
{
name: '温度区间背景', // legend
type: 'line',
data: new Array(xAxisData.length).fill(0), //
showSymbol: false,
symbol: 'none',
lineStyle: { opacity: 0 },
itemStyle: { opacity: 0 },
legendHoverLink: false, //
silent: true, //
markArea: markAreaConfig //
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0],
throttle: 50,
start: 0,
end: 100
}
],
toolbox: {
show: true,
feature: {
saveAsImage: {
title: '导出图片',
type: 'png',
pixelRatio: 2
}
},
right: 20,
top: 10
}
};
});
//
const initChart = () => {
if (!chartRef.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
updateChart();
// resize
window.addEventListener('resize', handleResize);
};
//
const updateChart = () => {
if (!chartInstance) return;
const option = getChartOption.value;
if (Object.keys(option).length > 0) {
chartInstance.setOption(option, true);
} else {
//
chartInstance.clear();
}
};
// resize
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
//
const destroyChart = () => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
};
onMounted(() => {
getSelectOption()
getEchartsData()
gerTableData()
//
setTimeout(() => {
initChart()
}, 200)
});
onUnmounted(() => {
destroyChart()
});
watch(
() => modelStore.params.stcd,
(newStcd, oldStcd) => {
//
if (newStcd && newStcd !== oldStcd) {
console.log('【BasicInfo】stcd 变化,重新加载数据:', newStcd);
getSelectOption()
getEchartsData()
gerTableData()
}
}
);
</script>
<style lang="scss" scoped>
.body_one {
width: 100%;
min-height: 600px;
.search_one {
width: 100%;
height: 32px;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
.search_left {
display: flex;
align-items: center;
}
}
.body_body {
width: 100%;
display: flex;
height: 540px;
.echarts {
flex: 1;
height: 100%;
box-sizing: border-box;
padding-top: 10px;
}
.table {
flex: 1;
height: 100%;
box-sizing: border-box;
padding: 10px;
}
}
}
.quick-date-options {
display: flex;
gap: 8px;
padding: 8px 0;
.ant-btn {
border-color: #1890ff !important;
color: #1890ff !important;
background-color: #e6f7ff !important;
font-size: 12px;
&:hover {
border-color: #40a9ff !important;
color: #40a9ff !important;
background-color: #bae7ff !important;
}
}
}
</style>

View File

@ -0,0 +1,779 @@
<template>
<div class="body_one">
<div class="search_one">
<div class="search_left">
<div>鱼类选择</div>
<a-select
v-model:value="selectValue"
mode="multiple"
style="width: 260px"
placeholder=" "
:options="selectOption"
@change="handleChange"
></a-select>
</div>
<div>
<a-range-picker
v-model:value="rangePicker"
format="YYYY-MM-DD"
:disabled-date="disabledDate"
:allow-clear="false"
@change="handleDateChange"
>
<template #renderExtraFooter>
<div class="quick-date-options">
<a-button size="small" @click="setQuickRange(7)"
>最近七天</a-button
>
<a-button size="small" @click="setQuickRange(30)"
>最近一个月</a-button
>
<a-button size="small" @click="setQuickRange(90)"
>最近三个月</a-button
>
<a-button size="small" @click="setQuickRange(180)"
>最近六个月</a-button
>
</div>
</template>
</a-range-picker>
</div>
</div>
<div class="body_body">
<div class="echarts" ref="chartRef"></div>
<div class="table">
<BasicTable
ref="tableRef"
:scrollY="460"
:columns="columns"
:list-url="fishGetKendoListCust"
:search-params="{}"
:transform-data="customTransform"
>
<!-- 同期对比列的自定义渲染 -->
<template #contrast="{ record }">
<span
v-if="record.wt && record.beforeWt"
:style="{
color:
record.wt - record.beforeWt > 0
? 'rgb(255, 85, 0)'
: 'rgb(135, 208, 104)'
}"
>
{{ record.wt - record.beforeWt > 0 ? '+' : ''
}}{{ (record.wt - record.beforeWt).toFixed(2) }}
</span>
<span v-else>-</span>
</template>
</BasicTable>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted, computed } from 'vue';
import * as echarts from 'echarts';
import dayjs, { Dayjs } from 'dayjs';
import { useModelStore } from '@/store/modules/model';
import { infoGetKendoListCust, fishGetKendoListCust } from '@/api/sw';
import BasicTable from '@/components/BasicTable/index.vue';
const modelStore = useModelStore();
const selectOption = ref([]);
const selectValue = ref([]);
const tableRef = ref<any>(null);
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const chartData = ref<any[]>([]);
const hasLoaded = ref(false); //
const props = defineProps({
isActive: {
type: Boolean,
default: false
}
});
const columns = [
{
title: '时间',
dataIndex: 'dt',
width: 180,
customRender: ({ text }: any) =>
text ? dayjs(text).format('YYYY-MM-DD') : '-'
},
{
title: '水温°C',
dataIndex: 'wt',
customRender: ({ text }: any) =>
text !== undefined && text !== null ? text : '-'
},
{
title: '上一年水温',
dataIndex: 'beforeWt',
customRender: ({ text }: any) =>
text !== undefined && text !== null ? text : '-'
},
{
title: '同期对比',
dataIndex: 'contrast',
width: 120,
slots: { customRender: 'contrast' }
},
{
title: '适宜产卵鱼',
dataIndex: 'spawnFish',
width: 200,
customRender: ({ record }: any) => {
if (
record.fishList &&
Array.isArray(record.fishList) &&
record.fishList.length > 0
) {
return record.fishList.map(fish => fish.name).join('、');
}
return '-';
}
}
];
// 7
const initDateRange = () => {
const endDate = dayjs();
const startDate = dayjs().subtract(7, 'day');
return [startDate, endDate] as [Dayjs, Dayjs];
};
const rangePicker = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
//
const disabledDate = (current: Dayjs) => {
//
return current && current.isAfter(dayjs(), 'day');
};
//
const setQuickRange = (days: number) => {
const endDate = dayjs();
const startDate = dayjs().subtract(days, 'day');
rangePicker.value = [startDate, endDate];
//
handleDateChange(rangePicker.value, [
startDate.format('YYYY-MM-DD'),
endDate.format('YYYY-MM-DD')
]);
};
//
const handleDateChange = (dates: any, dateStrings: string[]) => {
console.log('日期改变:', dates, dateStrings);
getEchartsData();
gerTableData();
// TODO:
};
//
const getSelectOption = async () => {
//
// modelStore.params.date
let params = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
}
]
}
};
let res = await infoGetKendoListCust(params);
let data = res?.data?.data || res?.data;
console.log(res);
selectOption.value = data.map(item => {
return {
label: item.name,
value: item.id,
pretemp: item.pretemp
};
});
// ID2221
selectValue.value =
data.length >= 2
? data.slice(0, 2).map(item => item.id)
: data.length > 0
? [data[0].id]
: [];
};
//echarts
const getEchartsData = async () => {
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
},
{
field: 'startTime',
operator: 'gte',
dataType: 'date',
value: rangePicker.value[0].format('YYYY-MM-DD') + ' 00:00:00'
},
{
field: 'endTime',
operator: 'lte',
dataType: 'date',
value: rangePicker.value[1].format('YYYY-MM-DD') + ' 23:59:59'
}
]
},
sort: [
{
field: 'dt',
dir: 'desc'
}
]
};
let res = await fishGetKendoListCust(params);
console.log('ECharts原始数据:', res);
//
const rawData = res?.data?.data || res?.data?.records || [];
chartData.value = rawData;
// DOM
setTimeout(() => {
updateChart();
}, 100);
};
//table
const gerTableData = async () => {
const filter = {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
},
{
field: 'startTime',
operator: 'gte',
dataType: 'date',
value: rangePicker.value[0].format('YYYY-MM-DD') + ' 00:00:00'
},
{
field: 'endTime',
operator: 'lte',
dataType: 'date',
value: rangePicker.value[1].format('YYYY-MM-DD') + ' 23:59:59'
}
]
};
tableRef.value?.getList(filter);
};
const handleChange = (value: any) => {
console.log(value);
getEchartsData();
// gerTableData()
};
const customTransform = (res: any) => {
console.log('表格数据:', res);
return {
records: res?.data?.data || [],
total: res?.data?.total || 0
};
};
//
const processChartData = (rawData: any[]) => {
if (!rawData || !Array.isArray(rawData)) return [];
// wt beforeWt null
const filtered = rawData.filter(
item => item.wt !== null && item.wt !== undefined
);
// && item.beforeWt !== null && item.beforeWt !== undefined
// dt
return filtered.sort(
(a, b) => new Date(a.dt).getTime() - new Date(b.dt).getTime()
);
};
// Y
const calculateYAxisMax = (data: any[]) => {
if (!data || data.length === 0) return 10;
const allValues = [
...data.map(item => item.wt),
...data.map(item => item.beforeWt)
].filter(v => v !== null && v !== undefined);
const max = Math.max(...allValues);
return Math.ceil(max);
};
// HSLHEX
const generateRandomColor = () => {
const hue = Math.random() * 360; // 0-360°
const saturation = 40 + Math.random() * 30; // 40%-70%
const lightness = 45 + Math.random() * 20; // 45%-65%
// HSLRGB
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 = c;
g = x;
b = 0;
} else if (hue < 120) {
r = x;
g = c;
b = 0;
} else if (hue < 180) {
r = 0;
g = c;
b = x;
} else if (hue < 240) {
r = 0;
g = x;
b = c;
} else if (hue < 300) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
// RGBHEX
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)}`;
};
//
const calculateTempRanges = () => {
if (!selectValue.value || selectValue.value.length === 0) {
return [];
}
// selectOption
const selectedFish = selectOption.value.filter(fish =>
selectValue.value.includes(fish.value)
);
if (selectedFish.length === 0) {
return [];
}
//
return selectedFish
.map(fish => {
if (
!fish.pretemp ||
!Array.isArray(fish.pretemp) ||
fish.pretemp.length !== 2
) {
return null;
}
const min = parseFloat(fish.pretemp[0]);
const max = parseFloat(fish.pretemp[1]);
if (isNaN(min) || isNaN(max)) {
return null;
}
return {
name: fish.label,
min,
max,
color: generateRandomColor()
};
})
.filter(item => item !== null);
};
// ECharts
const getChartOption = computed(() => {
const data = processChartData(chartData.value);
if (data.length === 0) {
return {};
}
// const yAxisMax = calculateYAxisMax(data);
const tempRanges = calculateTempRanges();
const dataMax = calculateYAxisMax(data);
const tempMax =
tempRanges.length > 0 ? Math.max(...tempRanges.map(r => r.max)) : 0;
const yAxisMax = Math.max(dataMax, tempMax);
//
const xAxisData = data.map(item => dayjs(item.dt).format('YYYY-MM-DD'));
const wtData = data.map(item => item.wt);
const beforeWtData = data.map(item => item.beforeWt);
// markArea
const markAreaConfig =
tempRanges.length > 0
? {
silent: true,
data: tempRanges.map(range => [
{
yAxis: range.min,
name: `${range.name}: ${range.min}-${range.max}°C`,
itemStyle: { color: `${range.color}26` }
},
{
yAxis: range.max
}
])
}
: undefined;
return {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(50, 50, 50, 0.9)',
textStyle: {
color: '#fff',
fontSize: 14
},
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
formatter: (params: any) => {
if (!params || params.length === 0) return '';
const date = params[0].axisValue;
let html = `<div style="font-weight:bold;margin-bottom:8px;">${date}</div>`;
params.forEach((param: any) => {
const dataIndex = param.dataIndex;
const record = data[dataIndex];
const value = param.value != null ? param.value : '-';
html += `
<div style="display:flex;align-items:center;margin:4px 0;">
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
<span>${param.seriesName}: ${value}°C</span>
</div>
`;
});
return html;
}
},
legend: {
top: 10,
left: 'center',
data: ['现在水温', '上一年同期水温'],
textStyle: {
fontSize: 13
}
},
grid: {
left: 60,
right: 40,
top: 60,
bottom: 50
},
xAxis: {
type: 'category',
data: xAxisData,
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: false
},
axisLabel: {
interval: (() => {
const dataLen = xAxisData.length;
if (dataLen <= 8) return 0; //
//
// 2 + 6 = 8
const maxLabels = 8;
const minInterval = Math.ceil((dataLen - 1) / (maxLabels - 1));
// 使使
const calculatedInterval = Math.floor((dataLen - 1) / 7);
return Math.max(calculatedInterval, minInterval);
})(),
fontSize: 12,
rotate: 0,
margin: 10
},
splitLine: {
show: true,
lineStyle: {
color: '#bfbfbf',
type: 'solid'
}
}
},
yAxis: {
type: 'value',
name: '水温(°C)',
min: 0,
max: yAxisMax,
scale: false,
axisLine: {
lineStyle: {
color: '#000000',
width: 1
}
},
axisTick: {
show: true,
length: 4
},
axisLabel: {
fontSize: 12,
formatter: '{value}'
},
splitLine: {
show: true,
lineStyle: {
color: '#bfbfbf',
type: 'solid'
}
}
},
series: [
{
name: '现在水温',
type: 'line',
data: wtData,
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#009dff', width: 2 },
itemStyle: { color: '#009dff' },
connectNulls: false
// markArea
},
{
name: '上一年同期水温',
type: 'line',
data: beforeWtData,
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#fee588', width: 2 },
itemStyle: { color: '#fee588' },
connectNulls: false
},
// markArea
{
name: '温度区间背景', // legend
type: 'line',
data: new Array(xAxisData.length).fill(0), //
showSymbol: false,
symbol: 'none',
lineStyle: { opacity: 0 },
itemStyle: { opacity: 0 },
legendHoverLink: false, //
silent: true, //
markArea: markAreaConfig //
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0],
throttle: 50,
start: 0,
end: 100
}
],
toolbox: {
show: true,
feature: {
saveAsImage: {
title: '导出图片',
type: 'png',
pixelRatio: 2
}
},
right: 20,
top: 10
}
};
});
//
const initChart = () => {
if (!chartRef.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
updateChart();
// resize
window.addEventListener('resize', handleResize);
};
//
const updateChart = () => {
if (!chartInstance) return;
const option = getChartOption.value;
if (Object.keys(option).length > 0) {
chartInstance.setOption(option, true);
} else {
//
chartInstance.clear();
}
};
// resize
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
//
const destroyChart = () => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
};
const initData = () => {
if (hasLoaded.value) return;
getSelectOption();
getEchartsData();
gerTableData();
//
setTimeout(() => {
initChart();
}, 200);
hasLoaded.value = true; //
};
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) {
initData();
}
},
{ immediate: true } //
);
// onMounted(() => {
// getSelectOption()
// getEchartsData()
// gerTableData()
// //
// setTimeout(() => {
// initChart()
// }, 200)
// });
onUnmounted(() => {
destroyChart();
});
watch(
() => modelStore.params.stcd,
(newStcd, oldStcd) => {
//
if (newStcd && newStcd !== oldStcd) {
console.log('【BasicInfo】stcd 变化,重新加载数据:', newStcd);
getSelectOption();
getEchartsData();
gerTableData();
}
}
);
</script>
<style lang="scss" scoped>
.body_one {
width: 100%;
min-height: 600px;
.search_one {
width: 100%;
height: 32px;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
.search_left {
display: flex;
align-items: center;
}
}
.body_body {
width: 100%;
display: flex;
height: 540px;
.echarts {
flex: 1;
height: 100%;
box-sizing: border-box;
padding-top: 10px;
}
.table {
flex: 1;
height: 100%;
box-sizing: border-box;
padding: 10px;
}
}
}
.quick-date-options {
display: flex;
gap: 8px;
padding: 8px 0;
.ant-btn {
border-color: #1890ff !important;
color: #1890ff !important;
background-color: #e6f7ff !important;
font-size: 12px;
&:hover {
border-color: #40a9ff !important;
color: #40a9ff !important;
background-color: #bae7ff !important;
}
}
}
</style>

View File

@ -0,0 +1,674 @@
<template>
<div class="video-info-container">
<a-row class="video-info-row" type="flex" style="height: 100%" :gutter="16">
<!-- 左侧列表 15% -->
<a-col flex="15%" class="left-panel">
<div class="panel-header"><a-divider type="vertical" />视频列表</div>
<div class="list-container">
<a-spin :spinning="loading">
<div
v-for="item in videoList"
:key="item.stcd"
class="list-item"
:class="{ active: currentVideo?.stcd === item.stcd }"
@click="handleSelectVideo(item)"
:title="item.stnm"
>
{{ item.stnm }}
</div>
<a-empty
v-if="!loading && videoList.length === 0"
description="暂无数据"
/>
</a-spin>
</div>
</a-col>
<!-- 中间视频区域 55% -->
<a-col flex="55%" class="center-panel">
<!-- 视频宫格 -->
<div class="video-grid" :class="`layout-${viewLayout}`">
<div
v-for="index in parseInt(viewLayout)"
:key="index"
class="video-item"
>
<LiveVideoBox
:video-data="videoSlots[index - 1]"
:monitor-type="'live'"
:show-title="viewLayout !== '1'"
height="100%"
@close="handleCloseVideo(index - 1)"
/>
</div>
</div>
</a-col>
<!-- 右侧录像列表 30% -->
<a-col flex="30%" class="right-panel">
<div class="panel-header">
<a-divider type="vertical" />
回放列表
<!-- 右上角宫格切换 -->
<div class="layout-switcher">
<a-radio-group v-model:value="viewLayout" button-style="solid">
<a-radio-button value="1">
<i class="iconfont icon-view1"></i>
</a-radio-button>
<a-radio-button value="4">
<i class="iconfont icon-view4"></i>
</a-radio-button>
<a-radio-button value="9">
<i class="iconfont icon-view9"></i>
</a-radio-button>
</a-radio-group>
</div>
</div>
<!-- 时间选择 -->
<div class="date-picker">
<a-range-picker
v-model:value="dateRange"
format="YYYY-MM-DD"
style="width: 100%"
:allowClear="false"
:presets="DateSetting.RangeButton.days"
/>
</div>
<!-- 录像列表 -->
<div class="record-list">
<a-spin :spinning="recordLoading">
<div v-if="paginatedRecords.length === 0" class="empty-record">
<a-empty />
</div>
<div v-else class="record-grid">
<div
class="record-item"
v-for="item in paginatedRecords"
:key="item.id"
@click="handlePlayRecord(item)"
>
<div class="record-image-wrapper">
<a-image :src="item.imgPath" :preview="false" alt="" />
<div class="play-btn">
<i class="icon iconfont icon-play"></i>
</div>
</div>
<div class="record-info">
<div class="record-name">{{ item.name }}</div>
<div class="record-time">
{{ dayjs(item.time).format('YYYY-MM-DD HH') }}
</div>
</div>
</div>
</div>
</a-spin>
</div>
<!-- 分页 -->
<div class="pagination-wrapper">
<a-pagination
v-model:current="currentPage"
:total="recordTotal"
:pageSize="pageSize"
size="small"
show-less-items
/>
</div>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
import {
getVideoList,
getVideoDetail,
getVideoPlayDetail
} from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
import { DateSetting } from '@/utils/enumeration';
import dayjs from 'dayjs';
import type { Dayjs } from 'dayjs';
import LiveVideoBox from '@/views/shiPinJianKong/components/LiveVideoBox.vue';
const modelStore = useModelStore();
const hasLoaded = ref(false);
const loading = ref(false);
const recordLoading = ref(false);
const props = defineProps({
isActive: {
type: Boolean,
default: false
}
});
//
const videoList = ref<any[]>([]);
//
const videoSlots = ref<any[]>(Array(9).fill(null));
//
const currentVideo = ref<any>(null);
//
const viewLayout = ref<'1' | '4' | '9'>('1');
//
const dateRange = ref<[Dayjs, Dayjs]>([dayjs().subtract(7, 'day'), dayjs()]);
//
const recordList = ref<any[]>([]);
const recordTotal = ref(0);
//
const currentPage = ref(1);
const pageSize = 4; // 422
// stcd
const selectedStcd = ref<string>('');
//
const paginatedRecords = computed(() => {
return recordList.value;
});
//
const loadRecordList = async () => {
if (!selectedStcd.value) {
recordList.value = [];
recordTotal.value = 0;
return;
}
recordLoading.value = true;
try {
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'in',
value: [selectedStcd.value]
},
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: dateRange.value[0].format('YYYY-MM-DD 00:00:00')
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: dateRange.value[1].format('YYYY-MM-DD 23:59:59')
}
]
},
sort: [{ field: 'tm', dir: 'desc' }],
skip: (currentPage.value - 1) * pageSize,
take: pageSize
};
const res: any = await getVideoPlayDetail(params);
if (res?.data?.data) {
recordList.value = res.data.data;
recordTotal.value = res.data.total || 0;
} else {
recordList.value = [];
recordTotal.value = 0;
}
} catch (error) {
recordList.value = [];
recordTotal.value = 0;
} finally {
recordLoading.value = false;
}
};
//
const initData = async () => {
if (hasLoaded.value) return;
loading.value = true;
try {
const params = {
filter: {
logic: 'and',
filters: [
{ field: 'sttpCode', operator: 'startswith', value: 'VD_' },
{
logic: 'or',
filters: [
{
field: 'rstcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
},
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
},
{
field: 'fhstcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
},
{
field: 'stCode',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
}
]
}
]
},
select: ['stcd', 'stnm', 'sttpCode', 'sttpName', 'playWay', 'url'],
sort: [{ field: 'stnm', dir: 'asc' }]
};
const res: any = await getVideoList(params);
if (res?.data?.data) {
videoList.value = res.data.data;
//
if (res.data.data.length > 0) {
const firstItem = res.data.data[0];
currentVideo.value = firstItem;
selectedStcd.value = firstItem.stcd;
//
videoSlots.value[0] = {
flnm: firstItem.stnm,
stnm: firstItem.stnm,
url: firstItem.url,
key: firstItem.stcd,
...firstItem
};
loadRecordList();
}
}
hasLoaded.value = true;
} catch (error) {
console.error('获取视频列表失败:', error);
} finally {
loading.value = false;
}
};
//
const handleSelectVideo = (item: any) => {
currentVideo.value = item;
selectedStcd.value = item.stcd;
currentPage.value = 1; //
// viewLayout 1
if (viewLayout.value === '1') {
const videoData = {
flnm: item.stnm,
stnm: item.stnm,
url: item.url,
key: item.stcd,
...item
};
videoSlots.value[0] = videoData;
//
for (let i = 1; i < videoSlots.value.length; i++) {
videoSlots.value[i] = null;
}
}
//
loadRecordList();
};
//
const handleCloseVideo = (index: number) => {
videoSlots.value[index] = null;
};
//
const handlePlayRecord = (record: any) => {
const videoData = {
flnm: record.name,
stnm: record.name,
url: record.url,
key: record.id,
...record
};
//
const emptyIndex = videoSlots.value.findIndex(
(v, i) => i < parseInt(viewLayout.value) && !v
);
if (emptyIndex !== -1) {
videoSlots.value[emptyIndex] = videoData;
} else {
//
videoSlots.value[0] = videoData;
}
};
//
watch(viewLayout, newLayout => {
const maxCount = parseInt(newLayout);
while (videoSlots.value.length < maxCount) {
videoSlots.value.push(null);
}
// 1
if (newLayout === '1') {
const firstVideo = videoSlots.value.find(
v => v !== null && v !== undefined
);
if (firstVideo) {
//
videoSlots.value[0] = firstVideo;
for (let i = 1; i < videoSlots.value.length; i++) {
videoSlots.value[i] = null;
}
} else if (currentVideo.value) {
//
videoSlots.value[0] = {
flnm: currentVideo.value.stnm,
stnm: currentVideo.value.stnm,
url: currentVideo.value.url,
key: currentVideo.value.stcd,
...currentVideo.value
};
}
}
});
//
watch(dateRange, () => {
currentPage.value = 1;
loadRecordList();
});
//
watch(currentPage, () => {
loadRecordList();
});
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) {
initData();
}
},
{ immediate: true }
);
</script>
<style lang="scss" scoped>
.video-info-container {
width: 100%;
height: 630px;
box-sizing: border-box;
background-color: #fff;
.video-info-row {
height: 100%;
}
//
.left-panel {
height: 100%;
border: 1px solid #f0f0f0;
border-radius: 4px;
display: flex;
flex-direction: column;
min-width: 0;
.panel-header {
height: 40px;
padding: 0 12px;
display: flex;
align-items: center;
font-size: 14px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
border-bottom: 1px solid #f0f0f0;
:deep(.ant-divider) {
margin-right: 6px;
height: 1.2em !important;
border-left: 3px solid rgb(47, 107, 152) !important;
}
}
.list-container {
flex: 1;
overflow-y: auto;
padding: 8px 0;
.list-item {
padding: 8px 12px;
cursor: pointer;
border-radius: 2px;
transition: all 0.2s;
font-size: 13px;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
background-color: #f5f5f5;
}
&.active {
background-color: #005293;
color: #fff;
}
}
}
}
//
.center-panel {
height: 100%;
border-radius: 4px;
display: flex;
flex-direction: column;
position: relative;
.video-grid {
flex: 1;
display: grid;
gap: 8px;
&.layout-1 {
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
&.layout-4 {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
}
&.layout-9 {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
}
.video-item {
overflow: hidden;
min-height: 0;
}
}
}
//
.right-panel {
height: 100%;
border: 1px solid #f0f0f0;
border-radius: 4px;
display: flex;
flex-direction: column;
.panel-header {
position: relative;
height: 40px;
padding: 0 12px;
display: flex;
align-items: center;
font-size: 14px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
border-bottom: 1px solid #f0f0f0;
:deep(.ant-divider) {
margin-right: 6px;
height: 1.2em !important;
border-left: 3px solid rgb(47, 107, 152) !important;
}
.layout-switcher {
position: absolute;
top: 8px;
right: 8px;
z-index: 10;
:deep(.ant-radio-group) {
display: flex;
gap: 4px;
.ant-radio-button-wrapper {
width: 28px;
height: 28px;
padding: 0 !important;
border: none !important;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 14px;
}
&:before {
display: none;
}
}
}
}
}
.date-picker {
padding: 12px;
border-bottom: 1px solid #f0f0f0;
}
.record-list {
flex: 1;
overflow-y: auto;
padding: 12px;
.empty-record {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 200px;
}
.record-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
.record-item {
cursor: pointer;
transition: all 0.2s;
&:hover {
.play-btn {
opacity: 1;
}
}
.record-image-wrapper {
position: relative;
width: 100%;
height: 80px;
border: 1px solid #d9d9d9;
border-bottom: none;
border-radius: 4px 4px 0 0;
overflow: hidden;
background-color: #f5f5f5;
:deep(.ant-image) {
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 30px;
height: 30px;
background-color: rgba(12, 51, 88, 0.8);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.7;
transition: opacity 0.2s;
i {
color: #fff;
font-size: 14px;
}
}
}
.record-info {
height: 50px;
padding: 6px;
text-align: center;
border: 1px solid #d9d9d9;
border-top: none;
border-radius: 0 0 4px 4px;
.record-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 4px;
}
.record-time {
font-size: 11px;
color: #666;
}
}
}
}
}
.pagination-wrapper {
padding: 12px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: center;
}
}
}
</style>

View File

@ -1,48 +1,108 @@
<template> <template>
<a-modal :open="visible" :title="title" width="80vw" :footer="null" :closable="true" @cancel="handleClose" <a-modal
:destroyOnClose="true" class="map-modal"> :open="visible"
:title="title"
width="80vw"
:footer="null"
:closable="true"
@cancel="handleClose"
:destroyOnClose="true"
class="map-modal"
>
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper"> <div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
<a-select v-model:value="modelStore.params.stcd" placeholder="请选择测站" style="width: 240px; margin-bottom: 12px;" <a-select
:options="modelStore.stcdOptions" show-search :filter-option="filterOption" @change="handleStcdChange"> v-model:value="modelStore.params.stcd"
placeholder="请选择测站"
style="width: 240px; margin-bottom: 12px"
:options="modelStore.stcdOptions"
show-search
:filter-option="filterOption"
@change="handleStcdChange"
>
</a-select> </a-select>
</div> </div>
<a-tabs :active-key="currentActiveKey" @change="onTabChange"> <a-tabs :active-key="currentActiveKey" @change="onTabChange">
<a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.name"> <a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.name">
<div class="content">
<!-- 基本信息组件 -->
<BasicInfo v-if="currentActiveKey === 'basicInfo'" :url="tab.url" />
<!-- 水温-监测数据 -->
<WaterTemperature v-if="currentActiveKey === 'WaterTemperature'" />
<!-- 水温-鱼类繁殖适宜性分析 -->
<WaterTemperatureContrast v-if="currentActiveKey === 'WaterTemperatureRep'" />
<!-- 地图组件 -->
<!-- <MapView v-else-if="currentActiveKey === 'mapView'" :data="modalData" /> -->
<!-- 周边配套组件 -->
<!-- <SurroundingInfo
v-else-if="currentActiveKey === 'surrounding'"
:data="modalData"
/> -->
</div>
</a-tab-pane> </a-tab-pane>
<template #rightExtra> <template #rightExtra>
<a-tooltip :title="!isEngConfig ? '' : '该电站无专题配置'"> <a-tooltip :title="!isEngConfig ? '' : '该电站无专题配置'">
<a-button type="primary" :disabled="isEngConfig"> <a-button type="primary" :disabled="isEngConfig">
<i class="icon iconfont icon-topic mr-[5px]"></i> <i class="icon iconfont icon-topic mr-[5px]"></i>
电站专题 电站专题
</a-button></a-tooltip> </a-button></a-tooltip
>
</template> </template>
</a-tabs> </a-tabs>
<!-- 内容区域 - 使用动态组件 + keep-alive 实现按需加载和缓存 -->
<keep-alive>
<div class="content">
<!-- 基本信息 -->
<BasicInfo
v-show="currentActiveKey === 'basicInfo'"
:url="getTabUrl('basicInfo')"
:is-active="currentActiveKey === 'basicInfo'"
/>
<!-- 实时视频 -->
<VideoInfo
v-show="currentActiveKey === 'videoInfo'"
:is-active="currentActiveKey === 'videoInfo'"
/>
<!-- 全景影像 -->
<PanoramaInfo
v-show="currentActiveKey === 'panoramaInfo'"
:url="getTabUrl('panoramaInfo')"
:is-active="currentActiveKey === 'panoramaInfo'"
/>
<!-- 监测数据 -->
<MonitorInfo
v-show="currentActiveKey === 'monitorInfo'"
:is-active="currentActiveKey === 'monitorInfo'"
:code="getTabCode('monitorInfo')"
/>
<!-- 预警提示 -->
<EarlyWarningAlert
v-show="currentActiveKey === 'tableTabs'"
:tabs-items="getTabChildren('tableTabs')"
:is-active="currentActiveKey === 'tableTabs'"
/>
<!-- 鱼类繁殖适宜性分析 -->
<WaterTemperatureRep
v-show="currentActiveKey === 'WaterTemperatureRep'"
:is-active="currentActiveKey === 'WaterTemperatureRep'"
/>
<!-- 查看报告 -->
<Attachment
v-show="currentActiveKey === 'attachment'"
:is-active="currentActiveKey === 'attachment'"
tabKey="attachment"
:show-download="false"
:url="getTabUrl('attachment')"
/>
<!-- 批复文件 -->
<Attachment
v-show="currentActiveKey === 'approval'"
:is-active="currentActiveKey === 'approval'"
tabKey="approval"
:show-download="true"
:url="getTabUrl('approval')"
/>
</div>
</keep-alive>
</a-modal> </a-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { ref, watch, reactive, computed } from 'vue';
// Tab // Tab
import BasicInfo from "./components/BasicInfo.vue"; import BasicInfo from './components/BasicInfo.vue'; //
import WaterTemperature from "./components/WaterTemperature.vue"; import VideoInfo from './components/videoInfo.vue'; //
import WaterTemperatureContrast from "./components/WaterTemperatureContrast.vue"; import PanoramaInfo from './components/PanoramaInfo.vue'; //
import { useModelStore } from "@/store/modules/model"; import MonitorInfo from './components/MonitorInfo.vue'; //
import { handleTabs } from "./setting.config"; import EarlyWarningAlert from './components/EarlyWarningAlert.vue'; //
import Attachment from './components/Attachment.vue'; // /
import WaterTemperatureRep from './components/WaterTemperatureRep.vue'; //
import { useModelStore } from '@/store/modules/model';
import { handleTabs } from './setting.config';
const modelStore = useModelStore(); const modelStore = useModelStore();
const tabsConfig = ref([]); const tabsConfig = ref([]);
@ -57,7 +117,22 @@ interface TabItem {
title: string; title: string;
url: string; url: string;
} }
// Tab URL
const getTabUrl = (key: string) => {
const tab = tabsConfig.value.find((item: any) => item.key === key);
return tab?.url || '';
};
// Tab Tab
const getTabChildren = (key: string) => {
console.log(tabsConfig.value);
const tab = tabsConfig.value.find((item: any) => item.key === key);
return tab?.tabs || [];
};
// Tab code
const getTabCode = (key: string) => {
const tab = tabsConfig.value.find((item: any) => item.key === key);
return tab?.code || '';
};
// Props // Props
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
@ -69,17 +144,17 @@ const props = defineProps<{
// Emits // Emits
// 'update:activeKey' v-model:active-key // 'update:activeKey' v-model:active-key
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:visible", value: boolean): void; (e: 'update:visible', value: boolean): void;
(e: "change", key: string): void; (e: 'change', key: string): void;
}>(); }>();
// activeKey // activeKey
const currentActiveKey = ref<string>(""); const currentActiveKey = ref<string>('');
// activeKey // activeKey
watch( watch(
() => props.activeKey, () => props.activeKey,
(newVal) => { newVal => {
if (newVal && newVal !== currentActiveKey.value) { if (newVal && newVal !== currentActiveKey.value) {
currentActiveKey.value = newVal; currentActiveKey.value = newVal;
} }
@ -88,7 +163,8 @@ watch(
); );
watch( watch(
() => modelStore.params, () => modelStore.params,
(newVal) => { newVal => {
if (!newVal || Object.keys(newVal).length === 0) return;
console.log(newVal); console.log(newVal);
tabsConfig.value = handleTabs(newVal); tabsConfig.value = handleTabs(newVal);
let value = tabsConfig.value.find((item: any) => item.default); let value = tabsConfig.value.find((item: any) => item.default);
@ -101,12 +177,13 @@ const onTabChange = (key: string) => {
currentActiveKey.value = key; currentActiveKey.value = key;
// activeKey ( v-model) // activeKey ( v-model)
// () // ()
emit("change", key); emit('change', key);
}; };
// //
const handleClose = () => { const handleClose = () => {
emit("update:visible", false); emit('update:visible', false);
modelStore.params = {};
// , // ,
modelStore.showStcdSelector = false; modelStore.showStcdSelector = false;
modelStore.stcdOptions = []; modelStore.stcdOptions = [];
@ -116,7 +193,7 @@ const handleClose = () => {
const modalData = ref(props.data); const modalData = ref(props.data);
watch( watch(
() => props.data, () => props.data,
(newVal) => { newVal => {
modalData.value = newVal; modalData.value = newVal;
} }
); );

View File

@ -20,12 +20,12 @@ const ENGTabs: Array<any> = [
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo', url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo',
default: true // 默认显示 default: true // 默认显示
}, },
{ // {
name: '阶段属性', // name: '阶段属性',
key: 'basicFilter', // key: 'basicFilter',
type: 'basicFilter', // type: 'basicFilter',
url: '/eng/engBasisInfo/getEngBaseInfo' // url: '/eng/engBasisInfo/getEngBaseInfo'
}, // },
{ {
name: '实时视频', name: '实时视频',
key: 'videoInfo', key: 'videoInfo',
@ -92,12 +92,14 @@ const ENGTabs: Array<any> = [
{ {
name: '查看报告', name: '查看报告',
key: 'attachment', key: 'attachment',
type: 'attachment' type: 'attachment',
url: '/eq/fid/GetKendoListCust'
}, },
{ {
name: '批复文件', name: '批复文件',
key: 'approval', key: 'approval',
type: 'approval' type: 'approval',
url: '/eng/base/eiaapproval/GetKendoList'
} }
// { // {
// name: "特征曲线", // name: "特征曲线",
@ -296,7 +298,7 @@ const FPTabs: Array<any> =
// type: 'AIYXSB', // type: 'AIYXSB',
// } // }
// ] // ]
// : // :
[ [
{ {
name: '基础信息', name: '基础信息',
@ -333,7 +335,7 @@ const FPTabs: Array<any> =
name: '运行情况', name: '运行情况',
key: 'FishFacilityRunState', //Normal1 key: 'FishFacilityRunState', //Normal1
type: 'FishFacilityRunState', //Normal type: 'FishFacilityRunState', //Normal
code: 'gyss.tabs.yxqk', code: 'gyss.tabs.yxqk'
// tabs: [ // tabs: [
// { // {
// name: '', // name: '',
@ -349,9 +351,9 @@ const FPTabs: Array<any> =
name: '综合分析', name: '综合分析',
key: 'FishZHFX', key: 'FishZHFX',
type: 'FishZHFX', type: 'FishZHFX',
code: "gyss.tabs.zhfx" code: 'gyss.tabs.zhfx'
}, }
] ];
// // 过鱼设施在建状态 // // 过鱼设施在建状态
// const FPTabs1: Array<any> = [ // const FPTabs1: Array<any> = [
@ -405,7 +407,7 @@ const FBTabs: Array<any> = [
name: '过程图', name: '过程图',
key: 'ProcessDiagram', key: 'ProcessDiagram',
type: 'ProcessDiagram', type: 'ProcessDiagram',
code: "ylzzz.tabs.gct" code: 'ylzzz.tabs.gct'
}, },
{ {
name: '增殖放流情况', name: '增殖放流情况',
@ -457,7 +459,7 @@ const FBTabs: Array<any> = [
// key: "panoramaInfo", // key: "panoramaInfo",
// type: "panorama" // type: "panorama"
// }, // },
] ];
// const FBTabs1: Array<any> = [ // const FBTabs1: Array<any> = [
// { // {
@ -590,7 +592,7 @@ const FHTabs: Array<any> = [
{ {
name: '流量监测', name: '流量监测',
key: 'FlowMeasure', key: 'FlowMeasure',
type: 'FlowMeasure', type: 'FlowMeasure'
// tabs: [ // tabs: [
// { // {
// url: '/wmp-eng-server/eng/river/GetKendoListCust', // url: '/wmp-eng-server/eng/river/GetKendoListCust',
@ -622,7 +624,7 @@ const FHTabs: Array<any> = [
// key: "panoramaInfo", // key: "panoramaInfo",
// type: "panorama" // type: "panorama"
// } // }
] ];
// const ZQTabs: Array<any> = [ // const ZQTabs: Array<any> = [
// { // {
// name: '基础信息', // name: '基础信息',
@ -675,7 +677,7 @@ const WQFBTabs: Array<any> = [
type: 'video', type: 'video',
url: '/video/dataStcdFrame/getVideoMonitorList' url: '/video/dataStcdFrame/getVideoMonitorList'
} }
] ];
// const WQTabs: Array<any> = [ // const WQTabs: Array<any> = [
// ...WQFBTabs, // ...WQFBTabs,
// { // {
@ -723,7 +725,7 @@ const FLOWTabs: Array<any> = [
key: 'FlowMeasure', key: 'FlowMeasure',
type: 'FlowMeasure' type: 'FlowMeasure'
} }
] ];
// //水温监测站 √ // //水温监测站 √
const WTTabs: Array<any> = [ const WTTabs: Array<any> = [
{ {
@ -748,9 +750,9 @@ const WTTabs: Array<any> = [
name: '鱼类繁殖适宜性分析', name: '鱼类繁殖适宜性分析',
key: 'WaterTemperatureRep', key: 'WaterTemperatureRep',
type: 'WaterTemperatureRep', type: 'WaterTemperatureRep',
code: 'swjc.tabs.ylfzsyxfx', code: 'swjc.tabs.ylfzsyxfx'
} }
] ];
// //垂向水温 √ // //垂向水温 √
// const WTTabs1: Array<any> = [ // const WTTabs1: Array<any> = [
// { // {
@ -774,7 +776,7 @@ const DWTabs: Array<any> = [
type: 'basic', type: 'basic',
default: true, // 默认显示 default: true, // 默认显示
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo' url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo'
}, }
// { // {
// name: '实时视频', // name: '实时视频',
// key: 'videoInfo', // key: 'videoInfo',
@ -791,7 +793,7 @@ const DWTabs: Array<any> = [
// key: 'AIYXSB', // key: 'AIYXSB',
// type: 'AIYXSB', // type: 'AIYXSB',
// } // }
] ];
// //低温水减缓设施这是旧的 // //低温水减缓设施这是旧的
// const DWTabs2: Array<any> = [ // const DWTabs2: Array<any> = [
// { // {
@ -834,7 +836,7 @@ const WETabs: Array<any> = [
key: 'basicInfo', key: 'basicInfo',
type: 'basic', type: 'basic',
url: '/bbi/siteBipc/getSiteBasicInfo', url: '/bbi/siteBipc/getSiteBasicInfo',
default: true, // 默认显示 default: true // 默认显示
}, },
{ {
name: '监测数据', name: '监测数据',
@ -845,7 +847,7 @@ const WETabs: Array<any> = [
name: '调查鱼类', name: '调查鱼类',
type: 'table', type: 'table',
key: 'weFish', key: 'weFish',
// tableUrl: getUrl('/wmp-env-server/env/we/fisht/GetKendoListCust'), // tableUrl: getUrl('/wmp-env-server/env/we/fisht/GetKendoListCust'),
hiddenChart: true, hiddenChart: true,
// filter: NormalWEFilter, // filter: NormalWEFilter,
filterProps: { filterProps: {
@ -887,7 +889,7 @@ const WETabs: Array<any> = [
} }
] ]
} }
] ];
//鱼类调查装置 //鱼类调查装置
const DEVICETABS: Array<any> = [ const DEVICETABS: Array<any> = [
{ {
@ -895,9 +897,9 @@ const DEVICETABS: Array<any> = [
key: 'basicInfo', key: 'basicInfo',
type: 'basic', type: 'basic',
url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo', url: '/api/dec-lygk-base-server/base/msstbprpt/getStcdInfo',
default: true, // 默认显示 default: true // 默认显示
}, }
] ];
// //电站告警 // //电站告警
const DZGJ: Array<any> = [ const DZGJ: Array<any> = [
{ {
@ -905,7 +907,7 @@ const DZGJ: Array<any> = [
key: 'basicInfo', key: 'basicInfo',
type: 'basic', type: 'basic',
url: '/bbi/siteBipc/getSiteBasicInfo', url: '/bbi/siteBipc/getSiteBasicInfo',
default: true, // 默认显示 default: true // 默认显示
}, },
{ {
name: '监测数据', name: '监测数据',
@ -958,7 +960,7 @@ const DZGJ: Array<any> = [
} }
] ]
} }
] ];
//珍稀植物园 //珍稀植物园
const VPTabs: Array<any> = [ const VPTabs: Array<any> = [
@ -967,7 +969,7 @@ const VPTabs: Array<any> = [
key: 'basicInfo', key: 'basicInfo',
type: 'basic', type: 'basic',
url: '/bbi/siteBipc/getSiteBasicInfo', url: '/bbi/siteBipc/getSiteBasicInfo',
default: true, // 默认显示 default: true // 默认显示
}, },
// { // {
// name: '实时视频', // name: '实时视频',
@ -1011,7 +1013,7 @@ const VPTabs: Array<any> = [
// type: "FishSystemRunState", //Normal // type: "FishSystemRunState", //Normal
// tabs: [] // tabs: []
// } // }
] ];
// //视频站 // //视频站
const videoTabs: Array<any> = [ const videoTabs: Array<any> = [
{ {
@ -1019,7 +1021,7 @@ const videoTabs: Array<any> = [
key: 'basicInfo', key: 'basicInfo',
type: 'basic', type: 'basic',
url: '/bbi/siteBipc/getSiteBasicInfo', url: '/bbi/siteBipc/getSiteBasicInfo',
default: true, // 默认显示 default: true // 默认显示
}, },
{ {
name: '实时视频', name: '实时视频',
@ -1027,7 +1029,7 @@ const videoTabs: Array<any> = [
type: 'video', type: 'video',
url: '/video/dataStcdFrame/getVideoMonitorList' url: '/video/dataStcdFrame/getVideoMonitorList'
} }
] ];
// //视频站 // //视频站
// const noLiveVideoTabs: Array<any> = [ // const noLiveVideoTabs: Array<any> = [
// { // {
@ -1050,7 +1052,7 @@ const VATabs: Array<any> = [
key: 'basicInfo', key: 'basicInfo',
type: 'basic', type: 'basic',
url: '/bbi/siteBipc/getSiteBasicInfo', url: '/bbi/siteBipc/getSiteBasicInfo',
default: true, // 默认显示 default: true // 默认显示
}, },
// { // {
// name: '实时视频', // name: '实时视频',
@ -1076,7 +1078,7 @@ const VATabs: Array<any> = [
} }
] ]
} }
] ];
// // //河道水情站 // // //河道水情站
// // const hdsqz: Array<any> = [ // // const hdsqz: Array<any> = [
@ -1177,20 +1179,19 @@ const WATabs: Array<any> = [
key: 'basicInfo', key: 'basicInfo',
type: 'basic', type: 'basic',
url: '/bbi/siteBipc/getSiteBasicInfo', url: '/bbi/siteBipc/getSiteBasicInfo',
default: true, // 默认显示 default: true // 默认显示
}, },
{ {
name: '现场录像', name: '现场录像',
key: 'liveFootage', key: 'liveFootage',
type: 'liveFootage', type: 'liveFootage'
}, },
{ {
name: '现场图片', name: '现场图片',
key: 'onSitePhoto', key: 'onSitePhoto',
type: 'onSitePhoto', type: 'onSitePhoto'
}, }
];
]
// //mway : 1-是人工 2-是自动 dtinType: 0-自建 1-国家 2-人工 // //mway : 1-是人工 2-是自动 dtinType: 0-自建 1-国家 2-人工
const handleTabs = (modaldata: any) => { const handleTabs = (modaldata: any) => {
if (!modaldata?.sttp) return; if (!modaldata?.sttp) return;
@ -1206,7 +1207,7 @@ const handleTabs = (modaldata: any) => {
case 'FH_POINT': case 'FH_POINT':
if (modaldata.show) { if (modaldata.show) {
return FHTabs; return FHTabs;
}else{ } else {
let FHTabsTwo = FHTabs.slice(0, 1); let FHTabsTwo = FHTabs.slice(0, 1);
return FHTabsTwo; return FHTabsTwo;
} }

View File

@ -19,7 +19,7 @@ declare global {
__mapMode?: string; // 建议同时声明代码中用到的其他全局变量 __mapMode?: string; // 建议同时声明代码中用到的其他全局变量
} }
} }
console.log(import.meta.env.VITE_APP_PREVIEW_URL); console.log(import.meta.env.VITE_APP_ATTACHMENT_URL);
/** /**
* *
@ -60,11 +60,7 @@ export const legendData2Obj = (data: any[]) => {
* @param index - item的索引 * @param index - item的索引
* @param labelType - * @param labelType -
*/ */
export const appendOffsetPropties = ( export const appendOffsetPropties = (item: any, index: any, labelType = 2) => {
item: any,
index: any,
labelType = 2
) => {
let drawDotImg = null; let drawDotImg = null;
let offset = null; let offset = null;
if (labelType == 2) { if (labelType == 2) {

View File

@ -144,6 +144,7 @@ export class MapOl implements MapInterface {
this.view.on('change:resolution', () => {}); this.view.on('change:resolution', () => {});
this.map.on('click', evt => { this.map.on('click', evt => {
this.popupManager.showPopup(undefined, undefined);
this.popupManager.handleMapClick(evt.pixel, detectedFeature => { this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params = detectedFeature.values_; modelStore.params = detectedFeature.values_;

View File

@ -0,0 +1,51 @@
<template>
<div class="pdf-view-container">
<iframe
ref="iframeRef"
:src="iframeUrl"
class="pdf-iframe"
frameborder="0"
:title="fileId"
></iframe>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps({
fileId: {
type: String,
default: ''
}
});
const iframeUrl = computed(() => {
if (!props.fileId) return '';
return (
import.meta.env.VITE_APP_ATTACHMENT_URL +
`/ShowDocument?fileKey=${props.fileId}&backgroundColor=transparent`
);
});
</script>
<style scoped lang="scss">
.pdf-view-container {
width: 100%;
height: 100%;
position: relative;
background-color: #f5f5f5;
}
.pdf-iframe {
width: 100%;
height: 100%;
}
.empty-state {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #ccc;
font-size: 14px;
}
</style>

View File

@ -1,65 +1,82 @@
<!-- SidePanelItem.vue --> <!-- SidePanelItem.vue -->
<template> <template>
<SidePanelItem title="设施类型介绍"> <SidePanelItem title="设施类型介绍">
<div class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"> <div
<!-- 跑马灯轨道容器 --> class="container"
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }" @mouseenter="handleMouseEnter"
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"> @mouseleave="handleMouseLeave"
<!-- 遍历所有媒体项包含克隆项 --> >
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item" <!-- 跑马灯轨道容器 -->
@click="handleItemClick(originalMediaData)"> <div
<!-- 图片 --> class="carousel-track"
<img v-if="item.url" :src="item.url" alt="" /> :class="{ 'no-transition': isTransitioning }"
<!-- 说明文字随媒体项移动 --> :style="{ transform: `translateX(-${currentIndex * 100}%)` }"
<!-- <div class="text">{{ item.text }}</div> --> >
</div> <!-- 遍历所有媒体项包含克隆项 -->
</div> <div
v-for="(item, index) in renderMediaData"
<!-- 面板指示器固定在底部右侧 --> :key="index"
<div class="pagination-dots-fixed"> class="carousel-item"
<span v-for="(dot, index) in originalMediaData" :key="index" class="dot" @click="handleItemClick(originalMediaData)"
:class="{ active: getCurrentRealIndex() === index }" @click="goToSlide(index)"></span> >
</div> <!-- 图片 -->
<img v-if="item.url" :src="item.url" alt="" />
<!-- 说明文字随媒体项移动 -->
<!-- <div class="text">{{ item.text }}</div> -->
</div> </div>
</div>
<!-- 独立的文字说明区域随跑马灯切换而变化 --> <!-- 面板指示器固定在底部右侧 -->
<div class="description-text" @click="handleItemClick(originalMediaData)"> <div class="pagination-dots-fixed">
{{ currentDescription }} <span
</div> v-for="(dot, index) in originalMediaData"
:key="index"
</SidePanelItem> class="dot"
<!-- 设施详情弹框 --> :class="{ active: getCurrentRealIndex() === index }"
<a-modal v-model:open="modalVisible" :title="'设施类型介绍'" width="1536px" :footer="null"> @click="goToSlide(index)"
<!-- 你在这里编写弹框内容 --> ></span>
<div v-if="currentItem"> </div>
<ArtsDetail :dataSource='currentItem' :index="getCurrentRealIndex()" /> </div>
</div>
</a-modal>
<!-- 独立的文字说明区域随跑马灯切换而变化 -->
<div class="description-text" @click="handleItemClick(originalMediaData)">
{{ currentDescription }}
</div>
</SidePanelItem>
<!-- 设施详情弹框 -->
<a-modal
v-model:open="modalVisible"
:title="'设施类型介绍'"
width="1536px"
:footer="null"
>
<!-- 你在这里编写弹框内容 -->
<div v-if="currentItem">
<ArtsDetail :dataSource="currentItem" :index="getCurrentRealIndex()" />
</div>
</a-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'; import { ref, onMounted, onUnmounted, computed } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue'; import SidePanelItem from '@/components/SidePanelItem/index.vue';
import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue'; import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue';
import { sttpbGetKendoList } from "@/api/sw"; import { sttpbGetKendoList } from '@/api/sw';
// 便 // 便
defineOptions({ defineOptions({
name: 'diwenshuijianhuansheshileixingzuchengjijieruqingkuang' name: 'diwenshuijianhuansheshileixingzuchengjijieruqingkuang'
}); });
// //
interface MediaItem { interface MediaItem {
type: 'image' | 'video'; type: 'image' | 'video';
url: string; url: string;
text: string; text: string;
description: string; description: string;
} }
// 3 // 3
const originalMediaData = ref<MediaItem[]>([ const originalMediaData = ref<MediaItem[]>([]);
]);
// //
// [, ..., ] // [, ..., ]
@ -81,252 +98,250 @@ const modalVisible = ref(false);
const currentItem = ref<any>(null); const currentItem = ref<any>(null);
// //
const initRenderData = () => { const initRenderData = () => {
const length = originalMediaData.value.length; const length = originalMediaData.value.length;
if (length === 0) return; if (length === 0) return;
renderMediaData.value = [ renderMediaData.value = [
originalMediaData.value[length - 1], // originalMediaData.value[length - 1], //
...originalMediaData.value, // ...originalMediaData.value, //
originalMediaData.value[0] // originalMediaData.value[0] //
]; ];
}; };
// //
const startAutoPlay = () => { const startAutoPlay = () => {
if (timer) clearInterval(timer); if (timer) clearInterval(timer);
timer = setInterval(() => { timer = setInterval(() => {
if (!isHovering.value && !isTransitioning.value) { if (!isHovering.value && !isTransitioning.value) {
nextSlide(); nextSlide();
} }
}, 4000); }, 4000);
}; };
// //
const nextSlide = () => { const nextSlide = () => {
currentIndex.value++; currentIndex.value++;
// //
setTimeout(() => { setTimeout(() => {
checkSeamlessJump(); checkSeamlessJump();
}, 500); // transition }, 500); // transition
}; };
// //
const checkSeamlessJump = () => { const checkSeamlessJump = () => {
const realLength = originalMediaData.value.length; const realLength = originalMediaData.value.length;
// = realLength + 1 // = realLength + 1
if (currentIndex.value >= realLength + 1) { if (currentIndex.value >= realLength + 1) {
// 1. // 1.
isTransitioning.value = true; isTransitioning.value = true;
// 2. 1 // 2. 1
currentIndex.value = 1; currentIndex.value = 1;
// 3. DOM // 3. DOM
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
isTransitioning.value = false; isTransitioning.value = false;
}); });
}); });
} }
}; };
// //
const handleMouseEnter = () => { const handleMouseEnter = () => {
isHovering.value = true; isHovering.value = true;
}; };
// //
const handleMouseLeave = () => { const handleMouseLeave = () => {
isHovering.value = false; isHovering.value = false;
}; };
// //
const currentDescription = computed(() => { const currentDescription = computed(() => {
const realIndex = getCurrentRealIndex(); const realIndex = getCurrentRealIndex();
return originalMediaData.value[realIndex]?.description || ''; return originalMediaData.value[realIndex]?.description || '';
}); });
const getListData = async () => { const getListData = async () => {
try { try {
const params = { const params = {
"filter": { filter: {
"logic": "and", logic: 'and',
"filters": [ filters: [
{ {
"field": "fullPath", field: 'fullPath',
"operator": "contains", operator: 'contains',
"dataType": "string", dataType: 'string',
"value": "DW,DW" value: 'DW,DW'
}, },
{ {
"field": "logo", field: 'logo',
"operator": "isnotnull", operator: 'isnotnull',
"dataType": "string" dataType: 'string'
}, },
{ {
"field": "introduce", field: 'introduce',
"operator": "isnotnull", operator: 'isnotnull',
"dataType": "string" dataType: 'string'
} }
] ]
} }
} };
let res = await sttpbGetKendoList(params); let res = await sttpbGetKendoList(params);
let data = res?.data?.data || res?.data || []; let data = res?.data?.data || res?.data || [];
// //
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL; const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
originalMediaData.value = data.map((item: any) => ({ originalMediaData.value = data.map((item: any) => ({
text: item.sttpName, text: item.sttpName,
description: item.introduce, description: item.introduce,
url: item.logo ? `${baseUrl}/?${item.logo}` : '' url: item.logo ? `${baseUrl}/?${item.logo}` : ''
})); }));
console.log(originalMediaData.value); console.log(originalMediaData.value);
} catch (error) { } catch (error) {
originalMediaData.value = [];
originalMediaData.value = []; } finally {
} finally { }
}
}; };
// //
const handleItemClick = (item: any) => { const handleItemClick = (item: any) => {
console.log('轮播图项目被点击:', item,getCurrentRealIndex()); console.log('轮播图项目被点击:', item, getCurrentRealIndex());
currentItem.value = item; currentItem.value = item;
modalVisible.value = true; modalVisible.value = true;
}; };
// //
onMounted(async () => { onMounted(async () => {
// //
await getListData(); await getListData();
// //
initRenderData(); initRenderData();
// //
startAutoPlay(); startAutoPlay();
}); });
// //
onUnmounted(() => { onUnmounted(() => {
if (timer) clearInterval(timer); if (timer) clearInterval(timer);
}); });
// //
const getCurrentRealIndex = () => { const getCurrentRealIndex = () => {
const realLength = originalMediaData.value.length; const realLength = originalMediaData.value.length;
let realIndex = currentIndex.value - 1; // let realIndex = currentIndex.value - 1; //
// //
if (realIndex < 0) realIndex = realLength - 1; if (realIndex < 0) realIndex = realLength - 1;
if (realIndex >= realLength) realIndex = 0; if (realIndex >= realLength) realIndex = 0;
return realIndex; return realIndex;
}; };
// //
const goToSlide = (targetIndex: number) => { const goToSlide = (targetIndex: number) => {
if (isTransitioning.value) return; if (isTransitioning.value) return;
// //
currentIndex.value = targetIndex + 1; currentIndex.value = targetIndex + 1;
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
width: 100%; width: 100%;
height: 228px; height: 228px;
// border: 1px solid #7fd6ff; // border: 1px solid #7fd6ff;
// border-radius: 5px; // border-radius: 5px;
position: relative; position: relative;
overflow: hidden; // overflow: hidden; //
// //
.carousel-track { .carousel-track {
display: flex; // display: flex; //
width: 100%;
height: 100%;
transition: transform 0.5s ease-in-out; // 0.5
//
&.no-transition {
transition: none;
}
//
.carousel-item {
min-width: 100%; //
height: 100%;
position: relative;
flex-shrink: 0; //
cursor: pointer;
img,
video {
width: 100%; width: 100%;
height: 100%; height: 100%;
transition: transform 0.5s ease-in-out; // 0.5 object-fit: cover; //
}
// .text {
&.no-transition {
transition: none;
}
//
.carousel-item {
min-width: 100%; //
height: 100%;
position: relative;
flex-shrink: 0; //
cursor: pointer;
img,
video {
width: 100%;
height: 100%;
object-fit: cover; //
}
.text {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 22px;
line-height: 22px;
background: rgba(0, 0, 0, 0.1);
color: #fff;
padding-left: 10px;
}
}
}
//
.pagination-dots-fixed {
position: absolute; position: absolute;
bottom: 10px; bottom: 0;
right: 10px; left: 0;
display: flex; width: 100%;
gap: 6px; height: 22px;
z-index: 10; // line-height: 22px;
background: rgba(0, 0, 0, 0.1);
.dot { color: #fff;
width: 5px; padding-left: 10px;
height: 5px; }
border-radius: 50%;
background-color: #D8D8D8;
cursor: pointer;
transition: background-color 0.3s ease;
&.active {
background-color: #005293;
}
&:hover {
opacity: 0.8;
}
}
} }
}
//
.pagination-dots-fixed {
position: absolute;
bottom: 10px;
right: 10px;
display: flex;
gap: 6px;
z-index: 10; //
.dot {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #d8d8d8;
cursor: pointer;
transition: background-color 0.3s ease;
&.active {
background-color: #005293;
}
&:hover {
opacity: 0.8;
}
}
}
} }
// //
.description-text { .description-text {
// padding: 8px 12px; // padding: 8px 12px;
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
// min-height: 40px; // min-height: 40px;
transition: all 0.3s ease; transition: all 0.3s ease;
margin-bottom: 20px; margin-bottom: 20px;
cursor: pointer; cursor: pointer;
// //
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
word-break: break-word; word-break: break-word;
} }
</style> </style>

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="live-video-box" :style="{ height }"> <div
class="live-video-box"
:class="{ 'no-title': !showTitle }"
:style="{ height }"
>
<!-- 有视频时显示播放器 --> <!-- 有视频时显示播放器 -->
<div v-if="videoUrl" class="video-container"> <div v-if="videoUrl" class="video-container">
<!-- 视频标题和关闭按钮 --> <!-- 视频标题和关闭按钮 -->
<div class="live-video-title"> <div v-if="showTitle" class="live-video-title">
{{ videoData?.flnm }} {{ videoData?.flnm }}
<div class="title-actions"> <div class="title-actions">
<CloseOutlined @click="handleClose" /> <CloseOutlined @click="handleClose" />
@ -21,11 +25,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, computed } from "vue"; import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
import { CloseOutlined } from "@ant-design/icons-vue"; import { CloseOutlined } from '@ant-design/icons-vue';
import videojs from "video.js"; import videojs from 'video.js';
import "video.js/dist/video-js.css"; import 'video.js/dist/video-js.css';
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL; const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
// HLS video.js 8.x HLS // HLS video.js 8.x HLS
@ -37,11 +41,17 @@ interface VideoData {
[key: string]: any; [key: string]: any;
} }
const props = defineProps<{ const props = withDefaults(
videoData?: VideoData | null; defineProps<{
height?: string; videoData?: VideoData | null;
monitorType?: "live" | "record"; // live-, record- height?: string;
}>(); monitorType?: 'live' | 'record'; // live-, record-
showTitle?: boolean; //
}>(),
{
showTitle: true
}
);
const emit = defineEmits<{ const emit = defineEmits<{
close: [nodeKey?: string]; close: [nodeKey?: string];
@ -53,7 +63,7 @@ let player: any = null;
// URL // URL
const videoUrl = computed(() => { const videoUrl = computed(() => {
if (!props.videoData) return ""; if (!props.videoData) return '';
// 使url // 使url
if (props.videoData.url) return props.videoData.url; if (props.videoData.url) return props.videoData.url;
@ -67,7 +77,7 @@ const videoUrl = computed(() => {
return props.videoData.urls[0]; return props.videoData.urls[0];
} }
return ""; return '';
}); });
// video.js // video.js
@ -81,49 +91,49 @@ const initPlayer = () => {
} }
try { try {
// video // video
const videoElement = document.createElement("video"); const videoElement = document.createElement('video');
videoElement.className = "video-js vjs-default-skin vjs-big-play-centered"; videoElement.className = 'video-js vjs-default-skin vjs-big-play-centered';
videoElement.setAttribute("id", playerId); videoElement.setAttribute('id', playerId);
videoElement.setAttribute("playsinline", "playsinline"); videoElement.setAttribute('playsinline', 'playsinline');
videoElement.setAttribute("webkit-playsinline", "webkit-playsinline"); videoElement.setAttribute('webkit-playsinline', 'webkit-playsinline');
// video // video
const container = document.getElementById(playerId); const container = document.getElementById(playerId);
if (container) { if (container) {
container.innerHTML = ""; container.innerHTML = '';
container.appendChild(videoElement); container.appendChild(videoElement);
} }
// monitorType // monitorType
// live: m3u8UI // live: m3u8UI
// record: // record:
const isLiveStream = props.monitorType === "live"; const isLiveStream = props.monitorType === 'live';
// URL MIME Type // URL MIME Type
const getVideoType = (url: string): string => { const getVideoType = (url: string): string => {
const lowerUrl = url.toLowerCase(); const lowerUrl = url.toLowerCase();
// URL // URL
if (lowerUrl.includes(".m3u8")) { if (lowerUrl.includes('.m3u8')) {
return "application/x-mpegURL"; // HLS return 'application/x-mpegURL'; // HLS
} else if (lowerUrl.includes(".mp4")) { } else if (lowerUrl.includes('.mp4')) {
return "video/mp4"; return 'video/mp4';
} else if (lowerUrl.includes(".webm")) { } else if (lowerUrl.includes('.webm')) {
return "video/webm"; return 'video/webm';
} else if (lowerUrl.includes(".ogg") || lowerUrl.includes(".ogv")) { } else if (lowerUrl.includes('.ogg') || lowerUrl.includes('.ogv')) {
return "video/ogg"; return 'video/ogg';
} else if (lowerUrl.includes(".avi")) { } else if (lowerUrl.includes('.avi')) {
return "video/x-msvideo"; return 'video/x-msvideo';
} else if (lowerUrl.includes(".mov")) { } else if (lowerUrl.includes('.mov')) {
return "video/quicktime"; return 'video/quicktime';
} else if (lowerUrl.includes(".wmv")) { } else if (lowerUrl.includes('.wmv')) {
return "video/x-ms-wmv"; return 'video/x-ms-wmv';
} else if (lowerUrl.includes(".flv")) { } else if (lowerUrl.includes('.flv')) {
return "video/x-flv"; return 'video/x-flv';
} }
// URL // URL
return isLiveStream ? "application/x-mpegURL" : "video/mp4"; return isLiveStream ? 'application/x-mpegURL' : 'video/mp4';
}; };
const videoType = getVideoType(videoUrl.value); const videoType = getVideoType(videoUrl.value);
@ -134,56 +144,56 @@ const initPlayer = () => {
muted: true, muted: true,
controls: true, controls: true,
fluid: false, fluid: false,
width: "100%", width: '100%',
height: "100%", height: '100%',
preload: "auto", preload: 'auto',
playbackRates: [0.5, 1, 1.5, 2], playbackRates: [0.5, 1, 1.5, 2],
liveui: isLiveStream, // UI liveui: isLiveStream, // UI
// //
inactivityTimeout: 0, // inactivityTimeout: 0, //
controlBar: { controlBar: {
children: [ children: [
"playToggle", 'playToggle',
"volumePanel", 'volumePanel',
"currentTimeDisplay", 'currentTimeDisplay',
"timeDivider", 'timeDivider',
"durationDisplay", 'durationDisplay',
"progressControl", 'progressControl',
"liveDisplay", 'liveDisplay',
"remainingTimeDisplay", 'remainingTimeDisplay',
"customControlSpacer", 'customControlSpacer',
"playbackRateMenuButton", 'playbackRateMenuButton',
"fullscreenToggle", 'fullscreenToggle'
], ]
}, },
html5: { html5: {
hls: { hls: {
enableLowInitialPlaylist: true, enableLowInitialPlaylist: true,
smoothQualityChange: true, smoothQualityChange: true
}, }
}, },
sources: [ sources: [
{ {
src: videoUrl.value, src: videoUrl.value,
type: videoType, // type: videoType //
}, }
], ]
}); });
// //
if (!isLiveStream) { if (!isLiveStream) {
player.ready(() => { player.ready(() => {
const liveDisplay = player.controlBar.getChild("liveDisplay"); const liveDisplay = player.controlBar.getChild('liveDisplay');
if (liveDisplay) { if (liveDisplay) {
liveDisplay.el().style.display = "none"; liveDisplay.el().style.display = 'none';
} }
}); });
} }
// //
player.on("error", () => { player.on('error', () => {
const error = player.error(); const error = player.error();
console.error("播放器错误:", error); console.error('播放器错误:', error);
// //
// //
@ -197,7 +207,7 @@ const initPlayer = () => {
try { try {
player.src({ player.src({
src: videoUrl.value, src: videoUrl.value,
type: videoType, // type: videoType //
}); });
player.load(); player.load();
player.play(); player.play();
@ -214,16 +224,16 @@ const initPlayer = () => {
}); });
// //
player.on("loadeddata", () => {}); player.on('loadeddata', () => {});
// //
player.on("play", () => {}); player.on('play', () => {});
// //
player.on("pause", () => {}); player.on('pause', () => {});
// //
player.on("waiting", () => {}); player.on('waiting', () => {});
} catch (error) { } catch (error) {
// //
} }
@ -232,7 +242,7 @@ const initPlayer = () => {
// //
const handleClose = () => { const handleClose = () => {
// key // key
emit("close", props.videoData?.key); emit('close', props.videoData?.key);
}; };
// URL // URL
@ -323,6 +333,12 @@ onUnmounted(() => {
} }
} }
&.no-title {
.video-container {
height: 100%;
}
}
.video-empty { .video-empty {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -15,7 +15,7 @@
:list-url="statistics" :list-url="statistics"
:transform-data="customTransform" :transform-data="customTransform"
> >
<template #headerCell="{ column }"> <template #bodyCell="{ column }">
<!-- 判断列的 key如果是 selection就显示自定义内容 --> <!-- 判断列的 key如果是 selection就显示自定义内容 -->
<span v-if="column.key === 'selection'"> <span v-if="column.key === 'selection'">
<Checkbox <Checkbox
@ -32,12 +32,12 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import dayjs from "dayjs"; import dayjs from 'dayjs';
import { ref, onMounted, h, computed } from "vue"; import { ref, onMounted, h, computed } from 'vue';
import { Checkbox, Tooltip, message, Modal } from "ant-design-vue"; import { Checkbox, Tooltip, message, Modal } from 'ant-design-vue';
import BasicTable from "@/components/BasicTable/index.vue"; import BasicTable from '@/components/BasicTable/index.vue';
import GuoYuDaoShuTongJiSearch from "./guoYuDaoShuTongJiSearch.vue"; import GuoYuDaoShuTongJiSearch from './guoYuDaoShuTongJiSearch.vue';
import { statistics, batchUrgeContent } from "@/api/guoYuSheShiShuJuTianBao"; import { statistics, batchUrgeContent } from '@/api/guoYuSheShiShuJuTianBao';
const searchRef = ref(); const searchRef = ref();
const selectedRowKeys = ref<string[]>([]); const selectedRowKeys = ref<string[]>([]);
const detailTableRef = ref(); const detailTableRef = ref();
@ -46,12 +46,13 @@ const batchData = ref([]);
const tableDataList = ref<any[]>([]); // 便 const tableDataList = ref<any[]>([]); // 便
// 1. Key ( id, stcd ) // 1. Key ( id, stcd )
const getUniqueKey = (item: any) => item.id || item.stcd || JSON.stringify(item); const getUniqueKey = (item: any) =>
item.id || item.stcd || JSON.stringify(item);
// 2. Key () // 2. Key ()
const getGroupKeys = (currentRecord: any) => { const getGroupKeys = (currentRecord: any) => {
const keys: string[] = []; const keys: string[] = [];
tableDataList.value.forEach((item) => { tableDataList.value.forEach(item => {
if ( if (
item.basinNames === currentRecord.basinNames && item.basinNames === currentRecord.basinNames &&
item.stationNames === currentRecord.stationNames item.stationNames === currentRecord.stationNames
@ -69,9 +70,9 @@ const handleGroupSelect = (checked: boolean, groupKeys: string[]) => {
const newSelectedKeys = new Set(selectedRowKeys.value); const newSelectedKeys = new Set(selectedRowKeys.value);
if (checked) { if (checked) {
groupKeys.forEach((key) => newSelectedKeys.add(key)); groupKeys.forEach(key => newSelectedKeys.add(key));
} else { } else {
groupKeys.forEach((key) => newSelectedKeys.delete(key)); groupKeys.forEach(key => newSelectedKeys.delete(key));
} }
selectedRowKeys.value = Array.from(newSelectedKeys); selectedRowKeys.value = Array.from(newSelectedKeys);
@ -85,8 +86,8 @@ const headerCheckboxState = computed(() => {
} }
// 1. hasData != 1 Key // 1. hasData != 1 Key
const validItems = tableDataList.value.filter((item) => item.hasData != 1); const validItems = tableDataList.value.filter(item => item.hasData != 1);
const validKeys = validItems.map((item) => getUniqueKey(item)); const validKeys = validItems.map(item => getUniqueKey(item));
// //
if (validKeys.length === 0) { if (validKeys.length === 0) {
@ -94,8 +95,9 @@ const headerCheckboxState = computed(() => {
} }
// 2. // 2.
const selectedCount = validKeys.filter((key) => selectedRowKeys.value.includes(key)) const selectedCount = validKeys.filter(key =>
.length; selectedRowKeys.value.includes(key)
).length;
// 3. // 3.
const isAllSelected = selectedCount === validKeys.length; const isAllSelected = selectedCount === validKeys.length;
@ -103,12 +105,12 @@ const headerCheckboxState = computed(() => {
return { return {
checked: isAllSelected, checked: isAllSelected,
indeterminate: isIndeterminate, indeterminate: isIndeterminate
}; };
}); });
const updateBatchData = () => { const updateBatchData = () => {
// tableDataList key selectedRowKeys // tableDataList key selectedRowKeys
batchData.value = tableDataList.value.filter((item) => batchData.value = tableDataList.value.filter(item =>
selectedRowKeys.value.includes(getUniqueKey(item)) selectedRowKeys.value.includes(getUniqueKey(item))
); );
}; };
@ -117,8 +119,8 @@ const handleHeaderSelectAll = (e: any) => {
const checked = e.target.checked; const checked = e.target.checked;
if (checked) { if (checked) {
// //
const validItems = tableDataList.value.filter((item) => item.hasData != 1); const validItems = tableDataList.value.filter(item => item.hasData != 1);
const allKeys = validItems.map((item) => getUniqueKey(item)); const allKeys = validItems.map(item => getUniqueKey(item));
selectedRowKeys.value = allKeys; selectedRowKeys.value = allKeys;
} else { } else {
// //
@ -129,14 +131,14 @@ const handleHeaderSelectAll = (e: any) => {
let columns = ref([ let columns = ref([
{ {
title: "选择", title: '选择',
key: "selection", key: 'selection',
width: 60, width: 60,
align: "center", align: 'center',
// //
customCell: (record) => { customCell: record => {
return { return {
rowSpan: record._selectionRowSpan || 0, rowSpan: record._selectionRowSpan || 0
}; };
}, },
// 使 h Checkbox // 使 h Checkbox
@ -148,8 +150,8 @@ let columns = ref([
// //
const isAllSelected = const isAllSelected =
groupKeys.length > 0 && groupKeys.length > 0 &&
groupKeys.every((key) => selectedRowKeys.value.includes(key)); groupKeys.every(key => selectedRowKeys.value.includes(key));
const isPartiallySelected = groupKeys.some((key) => const isPartiallySelected = groupKeys.some(key =>
selectedRowKeys.value.includes(key) selectedRowKeys.value.includes(key)
); );
@ -157,33 +159,33 @@ let columns = ref([
checked: isAllSelected, checked: isAllSelected,
disabled: record.hasData == 1, disabled: record.hasData == 1,
indeterminate: isPartiallySelected && !isAllSelected, indeterminate: isPartiallySelected && !isAllSelected,
onChange: (e: any) => handleGroupSelect(e.target.checked, groupKeys), onChange: (e: any) => handleGroupSelect(e.target.checked, groupKeys)
}); });
} }
return null; // return null; //
}, }
}, },
{ {
dataIndex: "basinNames", dataIndex: 'basinNames',
key: "basinNames", key: 'basinNames',
title: "流域", title: '流域',
width: 160, width: 160,
customCell: (record, index) => { customCell: (record, index) => {
return { return {
rowSpan: record._basinRowSpan || 0, rowSpan: record._basinRowSpan || 0
}; };
}, },
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
if (!text) return "-"; if (!text) return '-';
const str = String(text); const str = String(text);
// //
const items = str.split(/[,]/); const items = str.split(/[,]/);
// 66 // 66
let displayText = ""; let displayText = '';
if (items.length > 6) { if (items.length > 6) {
displayText = items.slice(0, 6).join("") + " ..."; displayText = items.slice(0, 6).join('') + ' ...';
} else { } else {
displayText = str; displayText = str;
} }
@ -193,34 +195,34 @@ let columns = ref([
Tooltip, Tooltip,
{ {
title: str, // Tooltip title: str, // Tooltip
placement: "topLeft", // placement: 'topLeft' //
}, },
() => h("span", displayText) () => h('span', displayText)
); );
}, }
}, },
{ {
dataIndex: "stationNames", dataIndex: 'stationNames',
key: "stationNames", key: 'stationNames',
title: "电站名称", title: '电站名称',
width: 160, width: 160,
customCell: (record, index) => { customCell: (record, index) => {
return { return {
rowSpan: record._stationRowSpan || 0, rowSpan: record._stationRowSpan || 0
}; };
}, },
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
if (!text) return "-"; if (!text) return '-';
const str = String(text); const str = String(text);
// //
const items = str.split(/[,]/); const items = str.split(/[,]/);
// 66 // 66
let displayText = ""; let displayText = '';
if (items.length > 6) { if (items.length > 6) {
displayText = items.slice(0, 6).join("") + " ..."; displayText = items.slice(0, 6).join('') + ' ...';
} else { } else {
displayText = str; displayText = str;
} }
@ -230,33 +232,38 @@ let columns = ref([
Tooltip, Tooltip,
{ {
title: str, // Tooltip title: str, // Tooltip
placement: "topLeft", // placement: 'topLeft' //
}, },
() => h("span", displayText) () => h('span', displayText)
); );
}, }
}, },
{ dataIndex: "reportMonth", key: "reportMonth", title: "月份", width: 80 }, { dataIndex: 'reportMonth', key: 'reportMonth', title: '月份', width: 80 },
{ {
dataIndex: "minStrdt", dataIndex: 'minStrdt',
key: "minStrdt", key: 'minStrdt',
title: "过鱼开始时间", title: '过鱼开始时间',
width: 140, width: 140,
customRender: ({ text }) => { customRender: ({ text }) => {
return text ? dayjs(text).format("YYYY-MM-DD") : ""; return text ? dayjs(text).format('YYYY-MM-DD') : '';
}, }
}, },
{ {
dataIndex: "maxEnddt", dataIndex: 'maxEnddt',
key: "maxEnddt", key: 'maxEnddt',
title: "过鱼结束时间", title: '过鱼结束时间',
width: 140, width: 140,
customRender: ({ text }) => { customRender: ({ text }) => {
return text ? dayjs(text).format("YYYY-MM-DD") : ""; return text ? dayjs(text).format('YYYY-MM-DD') : '';
}, }
}, },
{ dataIndex: "totalFcnt", key: "totalFcnt", title: "本月过鱼总数", width: 140 }, {
{ dataIndex: "contact", key: "contact", title: "联系人/电话", width: 250 }, dataIndex: 'totalFcnt',
key: 'totalFcnt',
title: '本月过鱼总数',
width: 140
},
{ dataIndex: 'contact', key: 'contact', title: '联系人/电话', width: 250 }
]); ]);
const customTransform = (res: any) => { const customTransform = (res: any) => {
@ -279,7 +286,7 @@ const customTransform = (res: any) => {
...item, ...item,
_basinRowSpan: 1, _basinRowSpan: 1,
_stationRowSpan: 1, _stationRowSpan: 1,
_selectionRowSpan: 1, // _selectionRowSpan: 1 //
})); }));
// 3. // 3.
@ -321,7 +328,7 @@ const customTransform = (res: any) => {
// 4. // 4.
return { return {
records: modifiedRecords, records: modifiedRecords,
total: res.data.total || 0, total: res.data.total || 0
}; };
}; };
@ -329,34 +336,34 @@ const customTransform = (res: any) => {
const handleDetailSearchFinish = (values: any) => { const handleDetailSearchFinish = (values: any) => {
const filters = [ const filters = [
values.strdt && { values.strdt && {
field: "maxEnddt", field: 'maxEnddt',
operator: "gte", operator: 'gte',
dataType: "date", dataType: 'date',
value: values.strdt[0] + " 00:00:00", value: values.strdt[0] + ' 00:00:00'
}, },
values.strdt && { values.strdt && {
field: "minStrdt", field: 'minStrdt',
operator: "lte", operator: 'lte',
dataType: "date", dataType: 'date',
value: values.strdt[1] + " 23:59:59", value: values.strdt[1] + ' 23:59:59'
}, },
values.hbrvcd !== "all" && { values.hbrvcd !== 'all' && {
field: "basinCode", field: 'basinCode',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.hbrvcd, value: values.hbrvcd
}, },
values.rstcd && { values.rstcd && {
field: "stationCode", field: 'stationCode',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.rstcd, value: values.rstcd
}, }
].filter(Boolean); ].filter(Boolean);
const filter = { const filter = {
logic: "and", logic: 'and',
filters: filters, filters: filters
}; };
detailTableRef.value?.getList(filter); detailTableRef.value?.getList(filter);
}; };
@ -367,17 +374,17 @@ const handleDetailReset = (values: any) => {
}; };
const urgeBtn = async () => { const urgeBtn = async () => {
Modal.confirm({ Modal.confirm({
title: "是否催促选择的用户?", title: '是否催促选择的用户?',
content: h("div", {}, [ content: h('div', {}, [
"将对选中的 ", '将对选中的 ',
h( h(
"span", 'span',
{ style: { color: "#ff4d4f", fontWeight: "bold" } }, { style: { color: '#ff4d4f', fontWeight: 'bold' } },
batchData.value.length + " 位用户" batchData.value.length + ' 位用户'
), ),
"发送【", '发送【',
h("span", { style: { color: "#ff4d4f" } }, "过鱼数据填报催促通知"), h('span', { style: { color: '#ff4d4f' } }, '过鱼数据填报催促通知'),
"】", '】'
]), ]),
onOk: async () => { onOk: async () => {
let ids = []; let ids = [];
@ -389,15 +396,15 @@ const urgeBtn = async () => {
let res: any = await batchUrgeContent({ userIds: ids }); let res: any = await batchUrgeContent({ userIds: ids });
if (res && res?.code == 0) { if (res && res?.code == 0) {
message.success("催促成功"); message.success('催促成功');
detailTableRef.value.loading = false; detailTableRef.value.loading = false;
searchRef.value.btnLoading = false; searchRef.value.btnLoading = false;
} else { } else {
message.error("催促失败"); message.error('催促失败');
detailTableRef.value.loading = false; detailTableRef.value.loading = false;
searchRef.value.btnLoading = false; searchRef.value.btnLoading = false;
} }
}, }
}); });
}; };
// //

View File

@ -17,9 +17,13 @@
:transform-data="customTransform" :transform-data="customTransform"
> >
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'action' || column.dataIndex === 'action'"> <template
v-if="column.key === 'action' || column.dataIndex === 'action'"
>
<div class="flex"> <div class="flex">
<a-button type="link" size="small" @click="handleView(record)">查看</a-button> <a-button type="link" size="small" @click="handleView(record)"
>查看</a-button
>
</div> </div>
</template> </template>
</template> </template>
@ -46,16 +50,16 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, h, nextTick } from "vue"; import { ref, computed, onMounted, h, nextTick } from 'vue';
import BasicTable from "@/components/BasicTable/index.vue"; import BasicTable from '@/components/BasicTable/index.vue';
import PreviewMedia from "@/components/previewMedia/index.vue"; import PreviewMedia from '@/components/previewMedia/index.vue';
import GuoYuSheShiShuJuHistorySearch from "./guoYuSheShiShuJuHistorySearch.vue"; import GuoYuSheShiShuJuHistorySearch from './guoYuSheShiShuJuHistorySearch.vue';
import EditModal from "../guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoForm.vue"; import EditModal from '../guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoForm.vue';
import { getFishDraftPage } from "@/api/guoYuSheShiShuJuTianBao"; import { getFishDraftPage } from '@/api/guoYuSheShiShuJuTianBao';
import { Tag } from "ant-design-vue"; import { Tag } from 'ant-design-vue';
import { getDictItemsByCode } from "@/api/dict"; import { getDictItemsByCode } from '@/api/dict';
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL; const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
interface FormData { interface FormData {
[key: string]: any; [key: string]: any;
@ -77,73 +81,86 @@ const guoyuStatus = ref<any>([]);
const baseColumnsConfig: ColumnConfig[] = [ const baseColumnsConfig: ColumnConfig[] = [
{ {
dataIndex: "hbrvnm", dataIndex: 'hbrvnm',
key: "hbrvnm", key: 'hbrvnm',
title: "流域", title: '流域',
width: 120, width: 120,
fixed: "left", fixed: 'left'
}, },
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150, fixed: "left" },
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
{ {
dataIndex: "isfs", dataIndex: 'ennm',
key: "isfs", key: 'ennm',
title: "是否鱼苗", title: '电站名称',
width: 120,
fixed: 'left'
},
{
dataIndex: 'stnm',
key: 'stnm',
title: '过鱼设施名称',
width: 150,
fixed: 'left'
},
{ dataIndex: 'strdt', key: 'strdt', title: '过鱼时间', width: 150 },
{ dataIndex: 'ftpName', key: 'ftpName', title: '鱼种类', width: 120 },
{
dataIndex: 'isfs',
key: 'isfs',
title: '是否鱼苗',
width: 74, width: 74,
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
const isYes = text === 1 || text === "1"; const isYes = text === 1 || text === '1';
return h( return h(
Tag, Tag,
{ {
color: isYes ? "success" : "error", color: isYes ? 'success' : 'error',
style: { margin: 0 }, style: { margin: 0 }
}, },
() => (isYes ? "是" : "否") () => (isYes ? '是' : '否')
); );
}, }
}, },
{ {
dataIndex: "direction", dataIndex: 'direction',
key: "direction", key: 'direction',
title: "游向", title: '游向',
width: 80, width: 80,
customRender: ({ text }: any) => customRender: ({ text }: any) =>
direction.value.find((item: any) => item.itemCode === text)?.dictName || "-", direction.value.find((item: any) => item.itemCode === text)?.dictName ||
'-'
}, },
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 }, { dataIndex: 'fcnt', key: 'fcnt', title: '过鱼数量(尾)', width: 120 },
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 110 }, { dataIndex: 'fsz', key: 'fsz', title: '体长(cm)', width: 110 },
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 110 }, { dataIndex: 'fwet', key: 'fwet', title: '平均体重(g)', width: 110 },
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 }, { dataIndex: 'wt', key: 'wt', title: '水温(℃)', width: 80 },
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 }, { dataIndex: 'tm', key: 'tm', title: '填报时间', width: 150 },
{ {
dataIndex: "status", dataIndex: 'status',
key: "status", key: 'status',
title: "状态", title: '状态',
width: 74, width: 74,
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
let data = guoyuStatus.value.find((item: any) => item.itemCode === text); let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
return h( return h(
Tag, Tag,
{ {
color: data?.custom1 || "error", color: data?.custom1 || 'error'
}, },
() => data?.dictName || "-" () => data?.dictName || '-'
); );
}, }
}, }
]; ];
const editModalVisible = ref(false); const editModalVisible = ref(false);
const currentRecord = ref<FormData | null>(null); const currentRecord = ref<FormData | null>(null);
const mediaPreviewVisible = ref(false); const mediaPreviewVisible = ref(false);
const videoPreviewTitle = ref("视频预览"); const videoPreviewTitle = ref('视频预览');
interface MediaItem { interface MediaItem {
id: string; id: string;
type: "image" | "video"; type: 'image' | 'video';
name: string; name: string;
url: string; url: string;
} }
@ -152,35 +169,35 @@ const previewList = ref<MediaItem[]>([]);
const previewListIndex = ref(0); const previewListIndex = ref(0);
const columns = computed(() => { const columns = computed(() => {
return [ return [
...baseColumnsConfig.map((col) => { ...baseColumnsConfig.map(col => {
if (col.dataIndex === "level5") { if (col.dataIndex === 'level5') {
return { return {
...col, ...col,
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
if (!text) return "-"; if (!text) return '-';
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`; return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
}, }
}; };
} }
if (col.dataIndex === "level6") { if (col.dataIndex === 'level6') {
return { return {
...col, ...col,
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
if (!text) return "-"; if (!text) return '-';
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`; return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
}, }
}; };
} }
return { ...col, visible: true }; return { ...col, visible: true };
}), }),
{ {
title: "操作", title: '操作',
key: "action", key: 'action',
dataIndex: "action", dataIndex: 'action',
fixed: "right", fixed: 'right',
width: 100, width: 100,
align: "center", align: 'center'
}, }
]; ];
}); });
@ -195,26 +212,26 @@ const editModalCancel = () => {
const handlePreviewClick = (record: any, type: string, index: number) => { const handlePreviewClick = (record: any, type: string, index: number) => {
const mixedList: MediaItem[] = []; const mixedList: MediaItem[] = [];
if (type === "image") { if (type === 'image') {
videoPreviewTitle.value = "图片预览"; videoPreviewTitle.value = '图片预览';
const nameList = JSON.parse(JSON.stringify(record)).picpthList; const nameList = JSON.parse(JSON.stringify(record)).picpthList;
nameList.forEach((item: any) => { nameList.forEach((item: any) => {
mixedList.push({ mixedList.push({
id: record.id, id: record.id,
type: "image", type: 'image',
name: item.name, name: item.name,
url: item.url, url: item.url
}); });
}); });
} else if (type === "video") { } else if (type === 'video') {
videoPreviewTitle.value = "视频预览"; videoPreviewTitle.value = '视频预览';
const nameList = JSON.parse(JSON.stringify(record)).vdpthList; const nameList = JSON.parse(JSON.stringify(record)).vdpthList;
nameList.forEach((item: any) => { nameList.forEach((item: any) => {
mixedList.push({ mixedList.push({
id: record.id, id: record.id,
type: "video", type: 'video',
name: item.name, name: item.name,
url: item.url, url: item.url
}); });
}); });
} }
@ -232,88 +249,89 @@ const customTransform = (res: any) => {
return { return {
...item, ...item,
picpthList: item.picpth picpthList: item.picpth
? item.picpth.split(",").map((item: string) => baseUrl + "/?" + item) || [] ? item.picpth.split(',').map((item: string) => baseUrl + '/?' + item) ||
: [], []
: []
}; };
}); });
return { return {
records: modifiedRecords, records: modifiedRecords,
total: res?.data?.total || 0, total: res?.data?.total || 0
}; };
}; };
const handleReset = (values) => { const handleReset = values => {
handleSearchFinish(values); handleSearchFinish(values);
}; };
const handleSearchFinish = (values: any) => { const handleSearchFinish = (values: any) => {
const filters = [ const filters = [
values.ftp && { values.ftp && {
field: "ftp", field: 'ftp',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.ftp, value: values.ftp
}, },
{ {
field: "strdt", field: 'strdt',
operator: "gte", operator: 'gte',
dataType: "date", dataType: 'date',
value: values.strdt[0] + " 00:00:00", value: values.strdt[0] + ' 00:00:00'
}, },
{ {
field: "strdt", field: 'strdt',
operator: "lte", operator: 'lte',
dataType: "date", dataType: 'date',
value: values.strdt[1] + " 23:59:59", value: values.strdt[1] + ' 23:59:59'
}, },
values.direction && { values.direction && {
field: "direction", field: 'direction',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.direction, value: values.direction
}, },
{ {
field: "status", field: 'status',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: "APPROVED", value: 'APPROVED'
}, },
values.stcd && { values.stcd && {
field: "stcd", field: 'stcd',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.stcd, value: values.stcd
}, },
values.rstcd && { values.rstcd && {
field: "rstcd", field: 'rstcd',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.rstcd, value: values.rstcd
},
values.hbrvcd !== "all" && {
field: "hbrvcd",
operator: "eq",
dataType: "string",
value: values.hbrvcd,
}, },
values.hbrvcd !== 'all' && {
field: 'hbrvcd',
operator: 'eq',
dataType: 'string',
value: values.hbrvcd
}
].filter(Boolean); ].filter(Boolean);
const filter = { const filter = {
logic: "and", logic: 'and',
filters: filters, filters: filters
}; };
tableRef.value?.getList(filter); tableRef.value?.getList(filter);
}; };
onMounted(() => { onMounted(() => {
getDictItemsByCode({ dictCode: "direction" }).then((res) => { getDictItemsByCode({ dictCode: 'direction' }).then(res => {
direction.value = res.data; direction.value = res.data;
}); });
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => { getDictItemsByCode({ dictCode: 'approvalStatus' }).then(res => {
guoyuStatus.value.length = 0; guoyuStatus.value.length = 0;
res.data.forEach((item: any) => { res.data.forEach((item: any) => {
if (item.itemCode == "APPROVED") { if (item.itemCode == 'APPROVED') {
guoyuStatus.value.push(item); guoyuStatus.value.push(item);
} }
}); });

View File

@ -240,7 +240,11 @@
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="12" class="imgupload" :class="{ imgupload_hidden: isView }"> <a-col
:span="12"
class="imgupload"
:class="{ imgupload_hidden: isView }"
>
<a-form-item label="图片" name="picpth"> <a-form-item label="图片" name="picpth">
<a-upload <a-upload
v-model:file-list="imageFileList" v-model:file-list="imageFileList"
@ -281,7 +285,10 @@
上传视频 (MP4) 上传视频 (MP4)
</a-button> </a-button>
</a-upload> </a-upload>
<a-button v-else-if="videoFileList.length > 0" @click="handleVideoPreview"> <a-button
v-else-if="videoFileList.length > 0"
@click="handleVideoPreview"
>
点击预览视频 点击预览视频
</a-button> </a-button>
</a-form-item> </a-form-item>
@ -292,19 +299,19 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch, computed } from "vue"; import { ref, reactive, watch, computed } from 'vue';
import dayjs from "dayjs"; import dayjs from 'dayjs';
import { Upload, message } from "ant-design-vue"; import { Upload, message } from 'ant-design-vue';
import type { UploadProps } from "ant-design-vue"; import type { UploadProps } from 'ant-design-vue';
import type { Rule } from "ant-design-vue/es/form"; import type { Rule } from 'ant-design-vue/es/form';
import { UploadOutlined, PlusOutlined } from "@ant-design/icons-vue"; import { UploadOutlined, PlusOutlined } from '@ant-design/icons-vue';
import fishSearch from "@/components/fishSearch/index.vue"; import fishSearch from '@/components/fishSearch/index.vue';
import { import {
getSelectForDropdown, getSelectForDropdown,
getEngInfoDropdown, getEngInfoDropdown,
getFpssDropdown, getFpssDropdown,
uploadFile, uploadFile
} from "@/api/select"; } from '@/api/select';
// Props // Props
interface Props { interface Props {
@ -333,12 +340,12 @@ const videoFileList = ref<any[]>([]);
const disabledDate = (current: any) => { const disabledDate = (current: any) => {
// current moment/dayjs // current moment/dayjs
// true // true
return current && current > dayjs().endOf("day"); return current && current > dayjs().endOf('day');
}; };
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
visible: false, visible: false,
initialValues: null, initialValues: null,
loading: false, loading: false
}); });
// //
// const getBaseDropdownSelect = async () => { // const getBaseDropdownSelect = async () => {
@ -363,11 +370,11 @@ const getHbrvcdDropdownSelect = async () => {
const res = await getSelectForDropdown({}); const res = await getSelectForDropdown({});
let list = res.data || []; let list = res.data || [];
if (list && list.length > 0) { if (list && list.length > 0) {
list = list.filter((item: any) => item.hbrvcd !== "all"); list = list.filter((item: any) => item.hbrvcd !== 'all');
} }
hbrvcdOption.value = list; hbrvcdOption.value = list;
} catch (error) { } catch (error) {
console.error("获取流域列表失败:", error); console.error('获取流域列表失败:', error);
} finally { } finally {
hbrvcdLoading.value = false; hbrvcdLoading.value = false;
} }
@ -390,7 +397,7 @@ const getEngInfoDropdownSelect = async (hbrvcd: string) => {
const res = await getEngInfoDropdown({ hbrvcd }); const res = await getEngInfoDropdown({ hbrvcd });
engOption.value = res.data; engOption.value = res.data;
} catch (error) { } catch (error) {
console.error("获取电站列表失败", error); console.error('获取电站列表失败', error);
} finally { } finally {
engLoading.value = false; engLoading.value = false;
} }
@ -405,28 +412,34 @@ const getFpssDropdownSelect = async (rstcd: string, hbrvcd: string) => {
const res = await getFpssDropdown({ rstcd, hbrvcd }); const res = await getFpssDropdown({ rstcd, hbrvcd });
fpssOption.value = res.data; fpssOption.value = res.data;
} catch (error) { } catch (error) {
console.error("获取流量列表失败", error); console.error('获取流量列表失败', error);
} finally { } finally {
fpssLoading.value = false; fpssLoading.value = false;
} }
}; };
const modalVisible = computed({ const modalVisible = computed({
get: () => props.visible, get: () => props.visible,
set: (val) => emit("update:visible", val), set: val => emit('update:visible', val)
}); });
// Emits // Emits
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:visible", value: boolean): void; (e: 'update:visible', value: boolean): void;
(e: "cancel"): void; (e: 'cancel'): void;
(e: "ok", values: any): void; (e: 'ok', values: any): void;
(e: "preview-click", record: any, type: string, index: number, action: string): void; (
e: 'preview-click',
record: any,
type: string,
index: number,
action: string
): void;
}>(); }>();
// //
const formRef = ref(); const formRef = ref();
// 1. // 1.
const bodyLengthError = ref<string>(""); const bodyLengthError = ref<string>('');
const weightError = ref<string>(""); const weightError = ref<string>('');
// //
const defaultFormData = reactive({ const defaultFormData = reactive({
@ -447,7 +460,7 @@ const defaultFormData = reactive({
bodyLengthMin: undefined, bodyLengthMin: undefined,
bodyLengthMax: undefined, bodyLengthMax: undefined,
weightMin: undefined, weightMin: undefined,
weightMax: undefined, weightMax: undefined
}); });
const formData: any = reactive({ ...defaultFormData }); const formData: any = reactive({ ...defaultFormData });
const filterOption = (inputValue: string, option: any) => { const filterOption = (inputValue: string, option: any) => {
@ -456,13 +469,13 @@ const filterOption = (inputValue: string, option: any) => {
}; };
// //
const rules: Record<string, Rule[]> = { const rules: Record<string, Rule[]> = {
hbrvcd: [{ required: true, message: "请选择流域", trigger: "change" }], hbrvcd: [{ required: true, message: '请选择流域', trigger: 'change' }],
rstcd: [{ required: true, message: "请选择电站", trigger: "change" }], rstcd: [{ required: true, message: '请选择电站', trigger: 'change' }],
stcd: [{ required: true, message: "请选择过鱼设施", trigger: "change" }], stcd: [{ required: true, message: '请选择过鱼设施', trigger: 'change' }],
strdt: [{ required: true, message: "请选择过鱼时间", trigger: "change" }], strdt: [{ required: true, message: '请选择过鱼时间', trigger: 'change' }],
ftp: [{ required: true, message: "请选择鱼种类", trigger: "change" }], ftp: [{ required: true, message: '请选择鱼种类', trigger: 'change' }],
direction: [{ required: true, message: "请选择游向", trigger: "change" }], direction: [{ required: true, message: '请选择游向', trigger: 'change' }],
fcnt: [{ required: true, message: "请输入过鱼数量", trigger: "change" }], fcnt: [{ required: true, message: '请输入过鱼数量', trigger: 'change' }]
}; };
// //
@ -473,15 +486,15 @@ const validateBodyLength = () => {
const max = formData.bodyLengthMax; const max = formData.bodyLengthMax;
// //
bodyLengthError.value = ""; bodyLengthError.value = '';
// ( undefined, null, ) // ( undefined, null, )
const hasMin = min !== undefined && min !== null && min !== ""; const hasMin = min !== undefined && min !== null && min !== '';
const hasMax = max !== undefined && max !== null && max !== ""; const hasMax = max !== undefined && max !== null && max !== '';
// 1. // 1.
if ((hasMin && !hasMax) || (!hasMin && hasMax)) { if ((hasMin && !hasMax) || (!hasMin && hasMax)) {
bodyLengthError.value = "最小体长和最大体长均需填写"; bodyLengthError.value = '最小体长和最大体长均需填写';
return false; return false;
} }
@ -489,7 +502,7 @@ const validateBodyLength = () => {
if (hasMin && hasMax) { if (hasMin && hasMax) {
// min > max // min > max
if (Number(min) > Number(max)) { if (Number(min) > Number(max)) {
bodyLengthError.value = "最小体长不能大于最大体长"; bodyLengthError.value = '最小体长不能大于最大体长';
return false; return false;
} }
} }
@ -502,15 +515,15 @@ const validateWeight = () => {
const max = formData.weightMax; const max = formData.weightMax;
// //
weightError.value = ""; weightError.value = '';
// //
const hasMin = min !== undefined && min !== null && min !== ""; const hasMin = min !== undefined && min !== null && min !== '';
const hasMax = max !== undefined && max !== null && max !== ""; const hasMax = max !== undefined && max !== null && max !== '';
// 1. // 1.
if ((hasMin && !hasMax) || (!hasMin && hasMax)) { if ((hasMin && !hasMax) || (!hasMin && hasMax)) {
weightError.value = "最小体重和最大体重均需填写"; weightError.value = '最小体重和最大体重均需填写';
return false; return false;
} }
@ -518,7 +531,7 @@ const validateWeight = () => {
if (hasMin && hasMax) { if (hasMin && hasMax) {
// min > max // min > max
if (Number(min) > Number(max)) { if (Number(min) > Number(max)) {
weightError.value = "最小体重不能大于最大体重"; weightError.value = '最小体重不能大于最大体重';
return false; return false;
} }
} }
@ -526,20 +539,20 @@ const validateWeight = () => {
return true; return true;
}; };
// //
const beforeImageUpload: UploadProps["beforeUpload"] = (file) => { const beforeImageUpload: UploadProps['beforeUpload'] = file => {
const isJpgOrPng = const isJpgOrPng =
file.type === "image/jpeg" || file.type === 'image/jpeg' ||
file.type === "image/png" || file.type === 'image/png' ||
file.name.toLowerCase().endsWith(".jpg") || file.name.toLowerCase().endsWith('.jpg') ||
file.name.toLowerCase().endsWith(".jpeg") || file.name.toLowerCase().endsWith('.jpeg') ||
file.name.toLowerCase().endsWith(".png"); file.name.toLowerCase().endsWith('.png');
if (!isJpgOrPng) { if (!isJpgOrPng) {
message.error("只能上传 JPG/PNG/JPEG 格式的图片!"); message.error('只能上传 JPG/PNG/JPEG 格式的图片!');
return Upload.LIST_IGNORE; return Upload.LIST_IGNORE;
} }
const isLt2M = file.size / 1024 / 1024 < 10; // 10M const isLt2M = file.size / 1024 / 1024 < 10; // 10M
if (!isLt2M) { if (!isLt2M) {
message.error("图片大小不能超过 10MB!"); message.error('图片大小不能超过 10MB!');
return Upload.LIST_IGNORE; return Upload.LIST_IGNORE;
} }
// false // false
@ -547,15 +560,16 @@ const beforeImageUpload: UploadProps["beforeUpload"] = (file) => {
}; };
// //
const beforeVideoUpload: UploadProps["beforeUpload"] = (file) => { const beforeVideoUpload: UploadProps['beforeUpload'] = file => {
const isMp4 = file.type === "video/mp4" || file.name.toLowerCase().endsWith(".mp4"); const isMp4 =
file.type === 'video/mp4' || file.name.toLowerCase().endsWith('.mp4');
if (!isMp4) { if (!isMp4) {
message.error("只能上传 MP4 格式的视频!"); message.error('只能上传 MP4 格式的视频!');
return Upload.LIST_IGNORE; return Upload.LIST_IGNORE;
} }
const isLt50M = file.size / 1024 / 1024 < 20; // 20M const isLt50M = file.size / 1024 / 1024 < 20; // 20M
if (!isLt50M) { if (!isLt50M) {
message.error("视频大小不能超过 20MB!"); message.error('视频大小不能超过 20MB!');
return Upload.LIST_IGNORE; return Upload.LIST_IGNORE;
} }
// false // false
@ -587,7 +601,7 @@ const initForm = () => {
// --- --- // --- ---
const values = props.initialValues; const values = props.initialValues;
if (values.fwet) { if (values.fwet) {
const weights = values.fwet.split("~"); const weights = values.fwet.split('~');
if (weights.length === 2) { if (weights.length === 2) {
formData.weightMin = weights[0]; formData.weightMin = weights[0];
formData.weightMax = weights[1]; formData.weightMax = weights[1];
@ -601,7 +615,7 @@ const initForm = () => {
} }
if (values.fsz) { if (values.fsz) {
const sizes = values.fsz.split("~"); const sizes = values.fsz.split('~');
if (sizes.length === 2) { if (sizes.length === 2) {
formData.bodyLengthMin = sizes[0]; formData.bodyLengthMin = sizes[0];
formData.bodyLengthMax = sizes[1]; formData.bodyLengthMax = sizes[1];
@ -616,35 +630,37 @@ const initForm = () => {
if (values.picpth) { if (values.picpth) {
const paths = Array.isArray(values.picpth) const paths = Array.isArray(values.picpth)
? values.picpth ? values.picpth
: values.picpth.split(","); : values.picpth.split(',');
imageFileList.value = paths.map((path: string, index: number) => ({ imageFileList.value = paths.map((path: string, index: number) => ({
uid: `-${index}`, uid: `-${index}`,
name: path.split("/").pop() || `image-${index}`, name: path.split('/').pop() || `image-${index}`,
status: "done", status: 'done',
path: path, path: path,
url: import.meta.env.VITE_APP_PREVIEW_URL + "/?" + path, // 访URL url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/?' + path // 访URL
})); }));
} }
// //
if (values.vdpth) { if (values.vdpth) {
const paths = Array.isArray(values.vdpth) ? values.vdpth : values.vdpth.split(","); const paths = Array.isArray(values.vdpth)
? values.vdpth
: values.vdpth.split(',');
videoFileList.value = paths.map((path: string, index: number) => ({ videoFileList.value = paths.map((path: string, index: number) => ({
uid: `-${index}`, uid: `-${index}`,
name: path.split("/").pop() || `image-${index}`, name: path.split('/').pop() || `image-${index}`,
status: "done", status: 'done',
path: path, path: path,
url: import.meta.env.VITE_APP_PREVIEW_URL + "/?" + path, // 访URL url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/?' + path // 访URL
})); }));
} }
// //
Object.keys(formData).forEach((key) => { Object.keys(formData).forEach(key => {
// //
if ( if (
key === "weightMin" || key === 'weightMin' ||
key === "weightMax" || key === 'weightMax' ||
key === "bodyLengthMin" || key === 'bodyLengthMin' ||
key === "bodyLengthMax" key === 'bodyLengthMax'
) { ) {
return; return;
} }
@ -660,12 +676,24 @@ const initForm = () => {
// //
const handleVideoPreview = () => { const handleVideoPreview = () => {
emit("preview-click", { vdpthList: videoFileList.value }, "video", 0, "preview"); emit(
'preview-click',
{ vdpthList: videoFileList.value },
'video',
0,
'preview'
);
}; };
// //
const handleImagePreview = async (file: any) => { const handleImagePreview = async (file: any) => {
emit("preview-click", { picpthList: imageFileList.value }, "image", 0, "preview"); emit(
'preview-click',
{ picpthList: imageFileList.value },
'image',
0,
'preview'
);
}; };
// //
const handleOk = async () => { const handleOk = async () => {
@ -674,7 +702,7 @@ const handleOk = async () => {
const isBodyLenValid = validateBodyLength(); const isBodyLenValid = validateBodyLength();
const isWeightValid = validateWeight(); const isWeightValid = validateWeight();
if (!isBodyLenValid || !isWeightValid) { if (!isBodyLenValid || !isWeightValid) {
message.error("请检查体长或体重填写是否正确"); message.error('请检查体长或体重填写是否正确');
return; return;
} }
// //
@ -683,16 +711,16 @@ const handleOk = async () => {
let uploadedPicPaths: any[] = []; let uploadedPicPaths: any[] = [];
// url // url
const newImageFiles = imageFileList.value.filter( const newImageFiles = imageFileList.value.filter(
(file) => !file.url && file.originFileObj file => !file.url && file.originFileObj
); );
localLoading.value = true; localLoading.value = true;
if (newImageFiles.length > 0) { if (newImageFiles.length > 0) {
// 使 Promise.all // 使 Promise.all
// uploadFile { data: { url: '...' } } // uploadFile { data: { url: '...' } }
const uploadPromises = newImageFiles.map((file) => { const uploadPromises = newImageFiles.map(file => {
const formDataUpload = new FormData(); const formDataUpload = new FormData();
formDataUpload.append("file", file.originFileObj); formDataUpload.append('file', file.originFileObj);
return uploadFile(formDataUpload); // return uploadFile(formDataUpload); //
}); });
@ -701,30 +729,30 @@ const handleOk = async () => {
path: item.message, path: item.message,
uid: `-${index}`, uid: `-${index}`,
name: item.fileMD5, name: item.fileMD5,
status: "done", status: 'done',
url: item.fullFilePath, // 访URL url: item.fullFilePath // 访URL
})); }));
} }
// //
const existingPics = imageFileList.value const existingPics = imageFileList.value
.filter((file) => file.path) .filter(file => file.path)
.map((file) => file.path); .map(file => file.path);
const uploadedPicPaths1 = uploadedPicPaths const uploadedPicPaths1 = uploadedPicPaths
.filter((file) => file.path) .filter(file => file.path)
.map((file) => file.path); .map(file => file.path);
const finalPicPaths = [...existingPics, ...uploadedPicPaths1]; const finalPicPaths = [...existingPics, ...uploadedPicPaths1];
// 2. // 2.
let uploadedVideoPath = []; let uploadedVideoPath = [];
const newVideoFiles = videoFileList.value.filter( const newVideoFiles = videoFileList.value.filter(
(file) => !file.url && file.originFileObj file => !file.url && file.originFileObj
); );
if (newVideoFiles.length > 0) { if (newVideoFiles.length > 0) {
const uploadPromises = newVideoFiles.map((file) => { const uploadPromises = newVideoFiles.map(file => {
const formDataUpload = new FormData(); const formDataUpload = new FormData();
formDataUpload.append("file", file.originFileObj); formDataUpload.append('file', file.originFileObj);
return uploadFile(formDataUpload); // return uploadFile(formDataUpload); //
}); });
const results = await Promise.all(uploadPromises); const results = await Promise.all(uploadPromises);
@ -732,31 +760,34 @@ const handleOk = async () => {
path: item.message, path: item.message,
uid: `-${index}`, uid: `-${index}`,
name: item.fileMD5, name: item.fileMD5,
status: "done", status: 'done',
url: item.fullFilePath, // 访URL url: item.fullFilePath // 访URL
})); }));
} }
const existingVideos = videoFileList.value const existingVideos = videoFileList.value
.filter((file) => file.path) .filter(file => file.path)
.map((file) => file.path); .map(file => file.path);
const uploadedVideoPaths1 = uploadedVideoPath const uploadedVideoPaths1 = uploadedVideoPath
.filter((file) => file.path) .filter(file => file.path)
.map((file) => file.path); .map(file => file.path);
const finalVideoPaths = [...existingVideos, ...uploadedVideoPaths1]; const finalVideoPaths = [...existingVideos, ...uploadedVideoPaths1];
let fwet = ""; let fwet = '';
if ( if (
formData.weightMin == formData.weightMax && formData.weightMin == formData.weightMax &&
formData.weightMin != undefined && formData.weightMin != undefined &&
formData.weightMax != undefined formData.weightMax != undefined
) { ) {
fwet = formData.weightMin; fwet = formData.weightMin;
} else if (formData.weightMin == undefined && formData.weightMax == undefined) { } else if (
fwet = ""; formData.weightMin == undefined &&
formData.weightMax == undefined
) {
fwet = '';
} else { } else {
fwet = formData.weightMin + "~" + formData.weightMax; fwet = formData.weightMin + '~' + formData.weightMax;
} }
let fsz = ""; let fsz = '';
if ( if (
formData.bodyLengthMin == formData.bodyLengthMax && formData.bodyLengthMin == formData.bodyLengthMax &&
formData.bodyLengthMin != undefined && formData.bodyLengthMin != undefined &&
@ -767,26 +798,26 @@ const handleOk = async () => {
formData.bodyLengthMin == undefined && formData.bodyLengthMin == undefined &&
formData.bodyLengthMax == undefined formData.bodyLengthMax == undefined
) { ) {
fsz = ""; fsz = '';
} else { } else {
fsz = formData.bodyLengthMin + "~" + formData.bodyLengthMax; fsz = formData.bodyLengthMin + '~' + formData.bodyLengthMax;
} }
// //
const submitValues = { const submitValues = {
...formData, ...formData,
fwet: fwet, fwet: fwet,
fsz: fsz, fsz: fsz,
strdt: formData.strdt + " 00:00:00", strdt: formData.strdt + ' 00:00:00',
picpth: finalPicPaths.join(","), // picpth: finalPicPaths.join(','), //
vdpth: finalVideoPaths.join(","), vdpth: finalVideoPaths.join(',')
}; };
if (!formData.id) submitValues.tm = dayjs().format("YYYY-MM-DD HH:mm:ss"); if (!formData.id) submitValues.tm = dayjs().format('YYYY-MM-DD HH:mm:ss');
// console.log(submitValues); // console.log(submitValues);
// return; // return;
emit("ok", submitValues); emit('ok', submitValues);
} catch (error) { } catch (error) {
console.error("Validate Failed:", error); console.error('Validate Failed:', error);
message.error("请检查表单填写是否正确"); message.error('请检查表单填写是否正确');
} }
}; };
// //
@ -796,8 +827,8 @@ const resetForm = () => {
} }
Object.assign(formData, defaultFormData); Object.assign(formData, defaultFormData);
// //
bodyLengthError.value = ""; bodyLengthError.value = '';
weightError.value = ""; weightError.value = '';
// //
imageFileList.value = []; imageFileList.value = [];
videoFileList.value = []; videoFileList.value = [];
@ -805,14 +836,14 @@ const resetForm = () => {
// //
const handleCancel = () => { const handleCancel = () => {
emit("update:visible", false); emit('update:visible', false);
emit("cancel"); emit('cancel');
resetForm(); resetForm();
}; };
watch( watch(
() => props.visible, () => props.visible,
(newVisible) => { newVisible => {
if (newVisible) { if (newVisible) {
// //
// getBaseDropdownSelect();// // getBaseDropdownSelect();//
@ -826,7 +857,7 @@ watch(
); );
// //
defineExpose({ defineExpose({
localLoading, localLoading
}); });
</script> </script>

View File

@ -1,18 +1,37 @@
<template> <template>
<div class="guoYuSheShiShuJuTianBao-search"> <div class="guoYuSheShiShuJuTianBao-search">
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'fu'" <BasicSearch
@reset="handleReset" @finish="onSearchFinish" @values-change="onValuesChange"> ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
:zhujianfujian="'fu'"
@reset="handleReset"
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #ftp="{ onChange }"> <template #ftp="{ onChange }">
<fishSearch v-model="localTypeDate" width="200px" @update:modelValue="onChange" /> <fishSearch
v-model="localTypeDate"
width="200px"
@update:modelValue="onChange"
/>
</template> </template>
<template #actions> <template #actions>
<a-tooltip title="手动新增"> <a-tooltip title="手动新增">
<a-button v-hasPerm="['sjtb:import-add']" @click="props.handleAdd" type="primary"> <a-button
v-hasPerm="['sjtb:import-add']"
@click="props.handleAdd"
type="primary"
>
手动新增 手动新增
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip title="数据批量导入zip"> <a-tooltip title="数据批量导入zip">
<a-button v-hasPerm="['sjtb:import-add']" @click="props.importBtn" type="primary"> <a-button
v-hasPerm="['sjtb:import-add']"
@click="props.importBtn"
type="primary"
>
数据批量导入zip 数据批量导入zip
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -69,7 +88,10 @@
</a-tooltip> </a-tooltip>
<a-tooltip title="操作手册下载"> <a-tooltip title="操作手册下载">
<a-button> <a-button>
<a href="/file/过鱼设施数据填报操作手册.docx" download="过鱼设施数据填报操作手册.docx"> <a
href="/file/过鱼设施数据填报操作手册.docx"
download="过鱼设施数据填报操作手册.docx"
>
操作手册下载 操作手册下载
</a> </a>
</a-button> </a-button>
@ -80,18 +102,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, watch } from "vue"; import { ref, computed, onMounted, watch } from 'vue';
import { import {
SaveOutlined, SaveOutlined,
CheckSquareOutlined, CheckSquareOutlined,
QuestionOutlined, QuestionOutlined
} from "@ant-design/icons-vue"; } from '@ant-design/icons-vue';
import dayjs from "dayjs"; import dayjs from 'dayjs';
import BasicSearch from "@/components/BasicSearch/index.vue"; // import BasicSearch from '@/components/BasicSearch/index.vue'; //
import { DateSetting } from "@/utils/enumeration"; import { DateSetting } from '@/utils/enumeration';
import fishSearch from "@/components/fishSearch/index.vue"; import fishSearch from '@/components/fishSearch/index.vue';
import { useShuJuTianBaoStore } from "@/store/modules/shuJuTianBao"; import { useShuJuTianBaoStore } from '@/store/modules/shuJuTianBao';
import { downloadFileByUrl } from "@/utils/index"; import { downloadFileByUrl } from '@/utils/index';
interface Props { interface Props {
direction: any[]; direction: any[];
@ -109,14 +131,14 @@ const shuJuTianBaoStore = useShuJuTianBaoStore();
const props = defineProps<Props>(); const props = defineProps<Props>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: "reset", values: any): void; (e: 'reset', values: any): void;
(e: "searchFinish", values: any): void; (e: 'searchFinish', values: any): void;
}>(); }>();
const localTypeDate = ref<string>(null); const localTypeDate = ref<string>(null);
const basicSearchRef = ref<any>(); const basicSearchRef = ref<any>();
const initSearchData = { const initSearchData = {
hbrvcd: "all", hbrvcd: 'all',
// baseId: "all", // baseId: "all",
stcd: null, stcd: null,
rstcd: null, rstcd: null,
@ -124,88 +146,88 @@ const initSearchData = {
status: 'DRAFT', status: 'DRAFT',
direction: null, direction: null,
strdt: [ strdt: [
dayjs().subtract(1, 'month').startOf('month').format("YYYY-MM-DD"), dayjs().subtract(1, 'month').startOf('month').format('YYYY-MM-DD'),
dayjs().format("YYYY-MM-DD"), dayjs().format('YYYY-MM-DD')
], ]
}; };
const searchData = ref<any>({ ...initSearchData }); const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [ const searchList: any = computed(() => [
{ {
type: "waterStation", type: 'waterStation',
name: "hbrvcd", name: 'hbrvcd',
label: "流域", label: '流域',
fieldProps: { fieldProps: {
allowClear: true, allowClear: true
}, },
options: [], options: []
}, },
{ {
type: "Select", type: 'Select',
name: "stcd", name: 'stcd',
label: "过鱼设施", label: '过鱼设施',
values: { name: "stnm", value: "stcd" }, values: { name: 'stnm', value: 'stcd' },
fieldProps: { fieldProps: {
allowClear: true, allowClear: true
}, },
options: shuJuTianBaoStore.fpssOption, options: shuJuTianBaoStore.fpssOption
}, },
{ {
type: "Select", type: 'Select',
name: "direction", name: 'direction',
label: "游向", label: '游向',
width: 120, width: 120,
options: props.direction, options: props.direction,
fieldProps: { fieldProps: {
allowClear: true, allowClear: true
}, }
}, },
{ {
type: "custom", type: 'custom',
name: "ftp", name: 'ftp',
label: "鱼种类", label: '鱼种类',
fieldProps: { fieldProps: {
allowClear: true, allowClear: true
}, }
}, },
{ {
width: 120, width: 120,
type: "Select", type: 'Select',
name: "status", name: 'status',
label: "数据状态", label: '数据状态',
fieldProps: { fieldProps: {
allowClear: true, allowClear: true
}, },
options: props.guoyuStatus, options: props.guoyuStatus
}, },
{ {
span: 12, span: 12,
type: "RangePicker", type: 'RangePicker',
name: "strdt", name: 'strdt',
label: "过鱼时间", label: '过鱼时间',
picker: "date", picker: 'date',
fieldProps: { fieldProps: {
format: "YYYY-MM-DD", format: 'YYYY-MM-DD',
valueFormat: "YYYY-MM-DD", valueFormat: 'YYYY-MM-DD',
allowClear: false, allowClear: false
}, },
presets: DateSetting.RangeButton.days, presets: DateSetting.RangeButton.days
}, }
]); ]);
// --- Methods --- // --- Methods ---
// 2. // 2.
const onSearchFinish = (values: any) => { const onSearchFinish = (values: any) => {
emit("searchFinish", values); emit('searchFinish', values);
}; };
const onValuesChange = (changedValues: any, allValues: any) => { const onValuesChange = (changedValues: any, allValues: any) => {
searchData.value = { ...searchData.value, ...allValues }; searchData.value = { ...searchData.value, ...allValues };
// searchData便使 // searchData便使
if ( if (
Object.keys(changedValues)[0] == "rstcd" || Object.keys(changedValues)[0] == 'rstcd' ||
Object.keys(changedValues)[0] == "baseId" || Object.keys(changedValues)[0] == 'baseId' ||
Object.keys(changedValues)[0] == "hbrvcd" Object.keys(changedValues)[0] == 'hbrvcd'
) { ) {
const formInstance = basicSearchRef.value?.formData; const formInstance = basicSearchRef.value?.formData;
formInstance.stcd = null; formInstance.stcd = null;
@ -214,24 +236,28 @@ const onValuesChange = (changedValues: any, allValues: any) => {
// //
const downloadTemplate = () => { const downloadTemplate = () => {
downloadFileByUrl(import.meta.env.VITE_APP_PREVIEW_URL + "/?20260429090252740092641634938251", "过鱼设施数据填报模板"); downloadFileByUrl(
import.meta.env.VITE_APP_ATTACHMENT_URL +
'/?20260429090252740092641634938251',
'过鱼设施数据填报模板'
);
}; };
const handleReset = () => { const handleReset = () => {
localTypeDate.value = null; localTypeDate.value = null;
emit("reset", initSearchData); emit('reset', initSearchData);
}; };
watch( watch(
() => initSearchData.ftp, () => initSearchData.ftp,
(newVal) => { newVal => {
localTypeDate.value = newVal || ""; localTypeDate.value = newVal || '';
}, },
{ immediate: true } { immediate: true }
); );
onMounted(() => { onMounted(() => {
emit("searchFinish", initSearchData); emit('searchFinish', initSearchData);
shuJuTianBaoStore.getFpssOption("", ""); shuJuTianBaoStore.getFpssOption('', '');
}); });
</script> </script>
<style lang="scss"></style> <style lang="scss"></style>

View File

@ -28,7 +28,9 @@
> >
<!-- 使用 bodyCell 插槽自定义单元格渲染 --> <!-- 使用 bodyCell 插槽自定义单元格渲染 -->
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'action' || column.dataIndex === 'action'"> <template
v-if="column.key === 'action' || column.dataIndex === 'action'"
>
<div class="flex"> <div class="flex">
<a-button <a-button
v-hasPerm="['sjtb:import-add']" v-hasPerm="['sjtb:import-add']"
@ -68,9 +70,12 @@
size="small" size="small"
@click="handleEdit(record, 'view')" @click="handleEdit(record, 'view')"
v-if=" v-if="
(checkPerm(['sjtb:edit-review']) && record.status === 'DRAFT') || (checkPerm(['sjtb:edit-review']) &&
(checkPerm(['sjtb:import-add']) && record.status === 'PENDING') || record.status === 'DRAFT') ||
(checkPerm(['sjtb:edit-review']) && record.status === 'REJECTED') || (checkPerm(['sjtb:import-add']) &&
record.status === 'PENDING') ||
(checkPerm(['sjtb:edit-review']) &&
record.status === 'REJECTED') ||
record.status === 'APPROVED' record.status === 'APPROVED'
" "
>查看</a-button >查看</a-button
@ -111,7 +116,11 @@
</div> </div>
<div class="button"> <div class="button">
<a-tooltip title="全部提交"> <a-tooltip title="全部提交">
<a-button v-hasPerm="['sjtb:import-add']" @click="allSubmitBtn" size="large"> <a-button
v-hasPerm="['sjtb:import-add']"
@click="allSubmitBtn"
size="large"
>
全部提交 全部提交
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -156,7 +165,7 @@
:direction="direction" :direction="direction"
@preview-click="handlePreviewClick" @preview-click="handlePreviewClick"
v-model:file-loading="fileLoading" v-model:file-loading="fileLoading"
@update:file-table-data="(val) => (fileTableData = val)" @update:file-table-data="val => (fileTableData = val)"
/> />
<template #footer> <template #footer>
<a-button key="back" @click="handleCustomCancel">取消导入</a-button> <a-button key="back" @click="handleCustomCancel">取消导入</a-button>
@ -197,14 +206,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, h, nextTick, watch } from "vue"; import { ref, computed, onMounted, h, nextTick, watch } from 'vue';
import { message, Modal } from "ant-design-vue"; // 使 ant-design-vue import { message, Modal } from 'ant-design-vue'; // 使 ant-design-vue
import BasicTable from "@/components/BasicTable/index.vue"; import BasicTable from '@/components/BasicTable/index.vue';
import GuoYuSheShiShuJuTianBaoSearch from "./guoYuSheShiShuJuTianBaoSearch.vue"; import GuoYuSheShiShuJuTianBaoSearch from './guoYuSheShiShuJuTianBaoSearch.vue';
import GuoYuSheShiShuJuTianBaoTable from "./guoYuSheShiShuJuTianBaoTable.vue"; import GuoYuSheShiShuJuTianBaoTable from './guoYuSheShiShuJuTianBaoTable.vue';
import EditModal from "./guoYuSheShiShuJuTianBaoForm.vue"; import EditModal from './guoYuSheShiShuJuTianBaoForm.vue';
import PreviewMedia from "@/components/previewMedia/index.vue"; import PreviewMedia from '@/components/previewMedia/index.vue';
import { checkPerm } from "@/directive/permission"; import { checkPerm } from '@/directive/permission';
import { import {
getFishDraftPage, getFishDraftPage,
addFishDraft, addFishDraft,
@ -218,13 +227,13 @@ import {
checkImportStatus, checkImportStatus,
batchSaveDraft, batchSaveDraft,
getLastImportResult, getLastImportResult,
batchApproveAll, batchApproveAll
} from "@/api/guoYuSheShiShuJuTianBao"; } from '@/api/guoYuSheShiShuJuTianBao';
import { Tag } from "ant-design-vue"; // Tag import { Tag } from 'ant-design-vue'; // Tag
import { getDictItemsByCode } from "@/api/dict"; import { getDictItemsByCode } from '@/api/dict';
import { calcTableScrollY } from "@/utils/index"; import { calcTableScrollY } from '@/utils/index';
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL; const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
const baseUrlApi = import.meta.env.VITE_APP_BASE_API_URL; const baseUrlApi = import.meta.env.VITE_APP_BASE_API_URL;
// --- --- // --- ---
interface FormData { interface FormData {
@ -247,62 +256,75 @@ const guoyuStatus = ref<any>([]);
// --- --- // --- ---
const baseColumnsConfig: ColumnConfig[] = [ const baseColumnsConfig: ColumnConfig[] = [
{ {
dataIndex: "hbrvnm", dataIndex: 'hbrvnm',
key: "hbrvnm", key: 'hbrvnm',
title: "流域", title: '流域',
width: 120, width: 120,
fixed: "left", fixed: 'left'
}, },
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150, fixed: "left" },
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
{ {
dataIndex: "isfs", dataIndex: 'ennm',
key: "isfs", key: 'ennm',
title: "是否鱼苗", title: '电站名称',
width: 120,
fixed: 'left'
},
{
dataIndex: 'stnm',
key: 'stnm',
title: '过鱼设施名称',
width: 150,
fixed: 'left'
},
{ dataIndex: 'strdt', key: 'strdt', title: '过鱼时间', width: 150 },
{ dataIndex: 'ftpName', key: 'ftpName', title: '鱼种类', width: 120 },
{
dataIndex: 'isfs',
key: 'isfs',
title: '是否鱼苗',
width: 74, width: 74,
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
const isYes = text === 1 || text === "1"; const isYes = text === 1 || text === '1';
return h( return h(
Tag, Tag,
{ {
color: isYes ? "success" : "error", // Antdv Tag color: isYes ? 'success' : 'error', // Antdv Tag
style: { margin: 0 }, // margin使 style: { margin: 0 } // margin使
}, },
() => (isYes ? "是" : "否") () => (isYes ? '是' : '否')
); );
}, }
}, },
{ {
dataIndex: "direction", dataIndex: 'direction',
key: "direction", key: 'direction',
title: "游向", title: '游向',
width: 80, width: 80,
customRender: ({ text }: any) => customRender: ({ text }: any) =>
direction.value.find((item: any) => item.itemCode === text)?.dictName || "-", direction.value.find((item: any) => item.itemCode === text)?.dictName ||
'-'
}, },
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 }, { dataIndex: 'fcnt', key: 'fcnt', title: '过鱼数量(尾)', width: 120 },
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 110 }, { dataIndex: 'fsz', key: 'fsz', title: '体长(cm)', width: 110 },
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 110 }, { dataIndex: 'fwet', key: 'fwet', title: '平均体重(g)', width: 110 },
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 }, { dataIndex: 'wt', key: 'wt', title: '水温(℃)', width: 80 },
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 }, { dataIndex: 'tm', key: 'tm', title: '填报时间', width: 150 },
{ {
dataIndex: "status", dataIndex: 'status',
key: "status", key: 'status',
title: "状态", title: '状态',
width: 74, width: 74,
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
let data = guoyuStatus.value.find((item: any) => item.itemCode === text); let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
return h( return h(
Tag, Tag,
{ {
color: data?.custom1 || "error", // Antdv Tag color: data?.custom1 || 'error' // Antdv Tag
}, },
() => data?.dictName || "-" () => data?.dictName || '-'
); );
}, }
}, }
]; ];
// --- --- // --- ---
@ -316,10 +338,10 @@ const currentRecord = ref<FormData | null>(null);
const mediaPreviewVisible = ref(false); const mediaPreviewVisible = ref(false);
const isPreviewMediaDelete = ref(false); const isPreviewMediaDelete = ref(false);
const videoPreviewTitle = ref("视频预览"); const videoPreviewTitle = ref('视频预览');
interface MediaItem { interface MediaItem {
id: string; id: string;
type: "image" | "video"; type: 'image' | 'video';
name: string; name: string;
url: string; url: string;
} }
@ -333,41 +355,41 @@ const batchData = ref<any[]>([]);
const modalTableRef = ref<any>(null); const modalTableRef = ref<any>(null);
const fileLoading = ref(false); const fileLoading = ref(false);
const taskId = ref<string>(""); const taskId = ref<string>('');
// Columns // Columns
const columns = computed(() => { const columns = computed(() => {
return [ return [
...baseColumnsConfig.map((col) => { ...baseColumnsConfig.map(col => {
if (col.dataIndex === "level5") { if (col.dataIndex === 'level5') {
return { return {
...col, ...col,
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
if (!text) return "-"; if (!text) return '-';
// Icon // Icon
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`; return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
}, }
}; };
} }
if (col.dataIndex === "level6") { if (col.dataIndex === 'level6') {
return { return {
...col, ...col,
customRender: ({ text }: any) => { customRender: ({ text }: any) => {
if (!text) return "-"; if (!text) return '-';
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`; return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
}, }
}; };
} }
return { ...col, visible: true }; return { ...col, visible: true };
}), }),
{ {
title: "操作", title: '操作',
key: "action", key: 'action',
dataIndex: "action", dataIndex: 'action',
fixed: "right", fixed: 'right',
width: 200, width: 200,
align: "center", align: 'center'
}, }
]; ];
}); });
@ -380,7 +402,7 @@ const handleAdd = () => {
}; };
// //
const handleEdit = (record: any, type: string) => { const handleEdit = (record: any, type: string) => {
if (type == "view") { if (type == 'view') {
isView.value = true; isView.value = true;
} else { } else {
isView.value = false; isView.value = false;
@ -392,14 +414,14 @@ const handleEdit = (record: any, type: string) => {
// / // /
const handleDelete = (ids: any[]) => { const handleDelete = (ids: any[]) => {
Modal.confirm({ Modal.confirm({
title: "是否确认 删除 选中数据吗?", title: '是否确认 删除 选中数据吗?',
onOk: async () => { onOk: async () => {
let res: any = await delFishDraft(ids); let res: any = await delFishDraft(ids);
if (res && res?.code == 0) { if (res && res?.code == 0) {
message.success("删除成功"); message.success('删除成功');
tableRef.value?.refresh(); tableRef.value?.refresh();
} }
}, }
}); });
}; };
// - // -
@ -409,14 +431,14 @@ const batchDelBtn = () => {
/// ///
const handleSubmit = (ids: any[]) => { const handleSubmit = (ids: any[]) => {
Modal.confirm({ Modal.confirm({
title: "是否 提交 选中数据吗?", title: '是否 提交 选中数据吗?',
onOk: async () => { onOk: async () => {
let res: any = await submitFishDraft(ids); let res: any = await submitFishDraft(ids);
if (res && res?.code == 0) { if (res && res?.code == 0) {
message.success("提交成功"); message.success('提交成功');
tableRef.value?.refresh(); tableRef.value?.refresh();
} }
}, }
}); });
}; };
// - // -
@ -426,58 +448,61 @@ const submitBtn = async () => {
// //
const allSubmitBtn = async () => { const allSubmitBtn = async () => {
if (tableRef.value.tableData.length === 0) { if (tableRef.value.tableData.length === 0) {
message.warning("请先添加数据!"); message.warning('请先添加数据!');
return; return;
} }
Modal.confirm({ Modal.confirm({
title: "是否 提交 全部数据吗?", title: '是否 提交 全部数据吗?',
onOk: async () => { onOk: async () => {
let res: any = await batchApproveAll(); let res: any = await batchApproveAll();
if (res && res?.code == 0) { if (res && res?.code == 0) {
message.success("提交成功"); message.success('提交成功');
tableRef.value?.refresh(); tableRef.value?.refresh();
} }
}, }
}); });
}; };
// / // /
const handleSuccess = (ids: any[]) => { const handleSuccess = (ids: any[]) => {
let rejectReason = ""; let rejectReason = '';
Modal.confirm({ Modal.confirm({
title: "是否确认 审批通过 选中数据?", title: '是否确认 审批通过 选中数据?',
// 使 h // 使 h
content: h("div", { style: "margin-top: 10px;" }, [ content: h('div', { style: 'margin-top: 10px;' }, [
h("div", { style: "margin-bottom: 8px;" }, [ h('div', { style: 'margin-bottom: 8px;' }, [
h("span", { style: "color: red;" }, "* "), h('span', { style: 'color: red;' }, '* '),
h("span", "审批原因:"), h('span', '审批原因:')
]), ]),
h("textarea", { h('textarea', {
class: "ant-input", // 使 antdv 使 Antdv class: 'ant-input', // 使 antdv 使 Antdv
placeholder: "请输入审批原因", placeholder: '请输入审批原因',
rows: 4, rows: 4,
style: { style: {
width: "100%", width: '100%',
resize: "none", resize: 'none',
border: "1px solid #d9d9d9", border: '1px solid #d9d9d9',
padding: "8px", padding: '8px',
borderRadius: "4px", borderRadius: '4px'
}, },
// //
onInput: (e: Event) => { onInput: (e: Event) => {
rejectReason = (e.target as HTMLTextAreaElement).value; rejectReason = (e.target as HTMLTextAreaElement).value;
}, }
}), })
]), ]),
okText: "确认审批", okText: '确认审批',
cancelText: "取消", cancelText: '取消',
onOk: async () => { onOk: async () => {
let res: any = await successFishDraft({ ids: ids, approveComment: rejectReason }); let res: any = await successFishDraft({
ids: ids,
approveComment: rejectReason
});
if (res && res?.code == 0) { if (res && res?.code == 0) {
message.success("审批成功"); message.success('审批成功');
tableRef.value?.refresh(); tableRef.value?.refresh();
} }
}, }
}); });
}; };
// - // -
@ -487,55 +512,59 @@ const successBtn = async () => {
// //
const handleReject = (id: any) => { const handleReject = (id: any) => {
let rejectReason = ""; let rejectReason = '';
Modal.confirm({ Modal.confirm({
title: "是否确认 驳回 选中数据?", title: '是否确认 驳回 选中数据?',
// 使 h // 使 h
content: h("div", { style: "margin-top: 10px;" }, [ content: h('div', { style: 'margin-top: 10px;' }, [
h("div", { style: "margin-bottom: 8px;" }, [ h('div', { style: 'margin-bottom: 8px;' }, [
h("span", { style: "color: red;" }, "* "), h('span', { style: 'color: red;' }, '* '),
h("span", "驳回原因:"), h('span', '驳回原因:')
]), ]),
h("textarea", { h('textarea', {
class: "ant-input", // 使 antdv 使 Antdv class: 'ant-input', // 使 antdv 使 Antdv
placeholder: "请输入驳回原因", placeholder: '请输入驳回原因',
rows: 4, rows: 4,
style: { style: {
width: "100%", width: '100%',
resize: "none", resize: 'none',
border: "1px solid #d9d9d9", border: '1px solid #d9d9d9',
padding: "8px", padding: '8px',
borderRadius: "4px", borderRadius: '4px'
}, },
// //
onInput: (e: Event) => { onInput: (e: Event) => {
rejectReason = (e.target as HTMLTextAreaElement).value; rejectReason = (e.target as HTMLTextAreaElement).value;
}, }
}), })
]), ]),
okText: "确认驳回", okText: '确认驳回',
cancelText: "取消", cancelText: '取消',
onOk: async () => { onOk: async () => {
// //
if (!rejectReason || rejectReason.trim() === "") { if (!rejectReason || rejectReason.trim() === '') {
message.warning("请输入驳回原因"); message.warning('请输入驳回原因');
return Promise.reject(); // Modal return Promise.reject(); // Modal
} }
let res: any = await rejectFishDraft({ id: id, rejectReason: rejectReason }); let res: any = await rejectFishDraft({
id: id,
rejectReason: rejectReason
});
if (res && res?.code == 0) { if (res && res?.code == 0) {
message.success("驳回成功"); message.success('驳回成功');
tableRef.value?.refresh(); tableRef.value?.refresh();
} }
}, }
}); });
}; };
// //
const getCheckboxProps = (record: any) => { const getCheckboxProps = (record: any) => {
return { return {
disabled: [!checkPerm(["sjtb:edit-review"]) && "PENDING", "APPROVED"].includes( disabled: [
record.status !checkPerm(['sjtb:edit-review']) && 'PENDING',
), 'APPROVED'
].includes(record.status)
}; };
}; };
// //
@ -551,10 +580,10 @@ const handleEditSubmit = async (values: FormData) => {
if (currentRecord.value) { if (currentRecord.value) {
// //
let res: any = await editFishDraft({ let res: any = await editFishDraft({
...values, ...values
}); });
if (res && res?.code == 0) { if (res && res?.code == 0) {
message.success("编辑成功"); message.success('编辑成功');
editModalVisible.value = false; editModalVisible.value = false;
tableRef.value?.refresh(); tableRef.value?.refresh();
} }
@ -562,10 +591,10 @@ const handleEditSubmit = async (values: FormData) => {
} else { } else {
// //
let res: any = await addFishDraft({ let res: any = await addFishDraft({
...values, ...values
}); });
if (res && res?.code == 0) { if (res && res?.code == 0) {
message.success("新增成功"); message.success('新增成功');
editModalVisible.value = false; editModalVisible.value = false;
tableRef.value?.refresh(); tableRef.value?.refresh();
} }
@ -575,7 +604,7 @@ const handleEditSubmit = async (values: FormData) => {
// //
const handleModalOk = () => { const handleModalOk = () => {
if (modalTableRef.value.editingData != undefined) { if (modalTableRef.value.editingData != undefined) {
message.warning("请点击保存后提交数据!"); message.warning('请点击保存后提交数据!');
return; return;
} }
for (let i = 0; i < fileTableData.value.length; i++) { for (let i = 0; i < fileTableData.value.length; i++) {
@ -584,95 +613,95 @@ const handleModalOk = () => {
// message.warning(""); // message.warning("");
// return; // return;
// } // }
if (fileTableData.value[i].warnings.includes("hbrvcd")) { if (fileTableData.value[i].warnings.includes('hbrvcd')) {
message.warning("请检查流域,流域填写有误!"); message.warning('请检查流域,流域填写有误!');
return; return;
} }
if (fileTableData.value[i].warnings.includes("rstcd")) { if (fileTableData.value[i].warnings.includes('rstcd')) {
message.warning("请检查电站,电站填写有误!"); message.warning('请检查电站,电站填写有误!');
return; return;
} }
if (fileTableData.value[i].warnings.includes("stcd")) { if (fileTableData.value[i].warnings.includes('stcd')) {
message.warning("请检查过鱼设施,过鱼设施填写有误!"); message.warning('请检查过鱼设施,过鱼设施填写有误!');
return; return;
} }
if (fileTableData.value[i].warnings.includes("strdt")) { if (fileTableData.value[i].warnings.includes('strdt')) {
message.warning("请检查过鱼时间,过鱼时间填写有误!"); message.warning('请检查过鱼时间,过鱼时间填写有误!');
return; return;
} }
if (fileTableData.value[i].warnings.includes("ftp")) { if (fileTableData.value[i].warnings.includes('ftp')) {
message.warning("请检查鱼种类,鱼种类填写有误!"); message.warning('请检查鱼种类,鱼种类填写有误!');
return; return;
} }
if (fileTableData.value[i].warnings.includes("direction")) { if (fileTableData.value[i].warnings.includes('direction')) {
message.warning("请检查游向,游向填写有误!"); message.warning('请检查游向,游向填写有误!');
return; return;
} }
if (fileTableData.value[i].warnings.includes("isfs")) { if (fileTableData.value[i].warnings.includes('isfs')) {
message.warning("请检查是否鱼苗,是否鱼苗填写有误!"); message.warning('请检查是否鱼苗,是否鱼苗填写有误!');
return; return;
} }
if (fileTableData.value[i].warnings.includes("fcnt")) { if (fileTableData.value[i].warnings.includes('fcnt')) {
message.warning("请检查过鱼数量,过鱼数量填写有误!"); message.warning('请检查过鱼数量,过鱼数量填写有误!');
return; return;
} }
} }
if (fileTableData.value[i].picpthsWarnings?.length > 0) { if (fileTableData.value[i].picpthsWarnings?.length > 0) {
message.warning("请检查图片,图片路径有误!"); message.warning('请检查图片,图片路径有误!');
return; return;
} }
if (fileTableData.value[i].vdpthsWarnings?.length > 0) { if (fileTableData.value[i].vdpthsWarnings?.length > 0) {
message.warning("请检查视频,视频路径有误!"); message.warning('请检查视频,视频路径有误!');
return; return;
} }
} }
Modal.confirm({ Modal.confirm({
title: "是否提交导入数据?", title: '是否提交导入数据?',
onOk: async () => { onOk: async () => {
try { try {
let res: any = await batchSaveDraft({ let res: any = await batchSaveDraft({
taskId: taskId.value, taskId: taskId.value
}); });
if (res && res?.code == 0) { if (res && res?.code == 0) {
fileTableData.value = []; fileTableData.value = [];
taskId.value = ""; taskId.value = '';
tableRef.value?.refresh(); tableRef.value?.refresh();
visible.value = false; visible.value = false;
message.success("导入成功"); message.success('导入成功');
} else { } else {
message.error("导入失败,请检查数据是否正确"); message.error('导入失败,请检查数据是否正确');
} }
} catch (error) { } catch (error) {
message.error("导入数据失败"); message.error('导入数据失败');
} }
}, }
}); });
}; };
// //
const handleCustomCancel = () => { const handleCustomCancel = () => {
Modal.confirm({ Modal.confirm({
title: "是否取消导入数据?", title: '是否取消导入数据?',
content: "未提交的数据将丢失", content: '未提交的数据将丢失',
okText: "确定", okText: '确定',
onOk: async () => { onOk: async () => {
// //
let res: any = await cancelImportTask({ taskId: taskId.value }); let res: any = await cancelImportTask({ taskId: taskId.value });
if (res && res?.code == 0) { if (res && res?.code == 0) {
fileTableData.value = []; fileTableData.value = [];
taskId.value = ""; taskId.value = '';
message.success("取消成功"); message.success('取消成功');
tableRef.value?.refresh(); tableRef.value?.refresh();
visible.value = false; visible.value = false;
} else { } else {
message.error("取消失败"); message.error('取消失败');
} }
}, }
}); });
}; };
const importBtn = async () => { const importBtn = async () => {
let res: any = await checkImportStatus(); let res: any = await checkImportStatus();
taskId.value = ""; taskId.value = '';
if (res?.code == 0) { if (res?.code == 0) {
const { hasImportingTask, currentTask } = res?.data || {}; const { hasImportingTask, currentTask } = res?.data || {};
if (currentTask) { if (currentTask) {
@ -681,17 +710,17 @@ const importBtn = async () => {
if (hasImportingTask) { if (hasImportingTask) {
visible.value = true; visible.value = true;
fileLoading.value = true; fileLoading.value = true;
if (currentTask.status == "UPLOADED") { if (currentTask.status == 'UPLOADED') {
if (visible.value) { if (visible.value) {
// message.warning(currentTask.statusText) // message.warning(currentTask.statusText)
setTimeout(() => { setTimeout(() => {
if (visible.value) importBtn(); if (visible.value) importBtn();
}, 5000); }, 5000);
} }
} else if (currentTask.status == "FAILED") { } else if (currentTask.status == 'FAILED') {
message.error(currentTask.statusText); message.error(currentTask.statusText);
fileLoading.value = false; fileLoading.value = false;
} else if (currentTask.status == "VALIDATED") { } else if (currentTask.status == 'VALIDATED') {
nextTick(async () => { nextTick(async () => {
modalTableRef.value.editingRowIndex = null; modalTableRef.value.editingRowIndex = null;
getFileList(); getFileList();
@ -701,7 +730,7 @@ const importBtn = async () => {
fileInputRef.value?.click(); fileInputRef.value?.click();
} }
} else { } else {
message.error("导入查询失败"); message.error('导入查询失败');
} }
}; };
const getFileList = async () => { const getFileList = async () => {
@ -715,7 +744,7 @@ const fileChange = (file: File) => {
fileLoading.value = true; fileLoading.value = true;
fileTableData.value = []; fileTableData.value = [];
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append('file', file);
let res: any = await importFishZip(formData); let res: any = await importFishZip(formData);
if (res && res?.code == 0) { if (res && res?.code == 0) {
if (visible.value) importBtn(); if (visible.value) importBtn();
@ -734,19 +763,19 @@ const handleFileSelect = (e: Event) => {
// (300MB) // (300MB)
const maxSize = 300 * 1024 * 1024; const maxSize = 300 * 1024 * 1024;
if (file.size > maxSize) { if (file.size > maxSize) {
message.error("文件大小不能超过300MB"); message.error('文件大小不能超过300MB');
resetFileInput(); resetFileInput();
return; return;
} }
// //
const isZip = const isZip =
file.name.toLowerCase().endsWith(".zip") || file.name.toLowerCase().endsWith('.zip') ||
file.type === "application/zip" || file.type === 'application/zip' ||
file.type === "application/x-zip-compressed"; file.type === 'application/x-zip-compressed';
if (!isZip) { if (!isZip) {
message.error("请选择.zip格式的压缩包"); message.error('请选择.zip格式的压缩包');
resetFileInput(); resetFileInput();
return; return;
} }
@ -756,7 +785,7 @@ const handleFileSelect = (e: Event) => {
const resetFileInput = () => { const resetFileInput = () => {
if (fileInputRef.value) { if (fileInputRef.value) {
fileInputRef.value.value = ""; fileInputRef.value.value = '';
} }
}; };
const fileTableaAnalysis = (res: any) => { const fileTableaAnalysis = (res: any) => {
@ -771,7 +800,7 @@ const fileTableaAnalysis = (res: any) => {
return keyA - keyB; return keyA - keyB;
}); });
list.forEach((item) => { list.forEach(item => {
data.push({ data.push({
...item.data, ...item.data,
rowIndex: item.rowIndex, rowIndex: item.rowIndex,
@ -779,13 +808,13 @@ const fileTableaAnalysis = (res: any) => {
vdpthsWarnings: item.vdpthsWarnings, vdpthsWarnings: item.vdpthsWarnings,
picpthList: item.picpthList, picpthList: item.picpthList,
picpthsWarnings: item.picpthsWarnings, picpthsWarnings: item.picpthsWarnings,
warnings: item.warnings, warnings: item.warnings
}); });
}); });
fileTableData.value = data || []; fileTableData.value = data || [];
fileLoading.value = false; fileLoading.value = false;
}; };
const handleReset = (values) => { const handleReset = values => {
handleSearchFinish(values); handleSearchFinish(values);
}; };
// //
@ -795,67 +824,68 @@ const customTransform = (res: any) => {
return { return {
...item, ...item,
picpthList: item.picpth picpthList: item.picpth
? item.picpth.split(",").map((item: string) => baseUrl + "/?" + item) || [] ? item.picpth.split(',').map((item: string) => baseUrl + '/?' + item) ||
: [], []
: []
}; };
}); });
return { return {
records: modifiedRecords, records: modifiedRecords,
total: res?.data?.total || 0, total: res?.data?.total || 0
}; };
}; };
// - // -
const handleSearchFinish = (values: any) => { const handleSearchFinish = (values: any) => {
const filters = [ const filters = [
values.ftp && { values.ftp && {
field: "ftp", field: 'ftp',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.ftp, value: values.ftp
}, },
{ {
field: "strdt", field: 'strdt',
operator: "gte", operator: 'gte',
dataType: "date", dataType: 'date',
value: values.strdt[0] + " 00:00:00", value: values.strdt[0] + ' 00:00:00'
}, },
{ {
field: "strdt", field: 'strdt',
operator: "lte", operator: 'lte',
dataType: "date", dataType: 'date',
value: values.strdt[1] + " 23:59:59", value: values.strdt[1] + ' 23:59:59'
}, },
values.direction && { values.direction && {
field: "direction", field: 'direction',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.direction, value: values.direction
}, },
// status: 'DRAFT', // status: 'DRAFT',
values.status && { values.status && {
field: "status", field: 'status',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.status, value: values.status
}, },
values.stcd && { values.stcd && {
field: "stcd", field: 'stcd',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.stcd, value: values.stcd
}, },
values.rstcd && { values.rstcd && {
field: "rstcd", field: 'rstcd',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: values.rstcd, value: values.rstcd
},
values.hbrvcd !== "all" && {
field: "hbrvcd",
operator: "eq",
dataType: "string",
value: values.hbrvcd,
}, },
values.hbrvcd !== 'all' && {
field: 'hbrvcd',
operator: 'eq',
dataType: 'string',
value: values.hbrvcd
}
// values.baseId !== "all" && { // values.baseId !== "all" && {
// field: "baseId", // field: "baseId",
// operator: "eq", // operator: "eq",
@ -865,65 +895,70 @@ const handleSearchFinish = (values: any) => {
].filter(Boolean); ].filter(Boolean);
const filter = { const filter = {
logic: "and", logic: 'and',
filters: filters, filters: filters
}; };
tableRef.value?.getList(filter); tableRef.value?.getList(filter);
}; };
// () // ()
const handlePreviewClick = (record: any, type: string, index: number, action: string) => { const handlePreviewClick = (
record: any,
type: string,
index: number,
action: string
) => {
const mixedList: MediaItem[] = []; const mixedList: MediaItem[] = [];
if (action === "delete") { if (action === 'delete') {
isPreviewMediaDelete.value = true; isPreviewMediaDelete.value = true;
tablePreviewRecord.value = record; tablePreviewRecord.value = record;
} else if (action === "preview") { } else if (action === 'preview') {
isPreviewMediaDelete.value = false; isPreviewMediaDelete.value = false;
} }
if (type === "image") { if (type === 'image') {
videoPreviewTitle.value = "图片预览"; videoPreviewTitle.value = '图片预览';
const nameList = record.picpthList; const nameList = record.picpthList;
nameList.forEach((item: any) => { nameList.forEach((item: any) => {
let url = ""; let url = '';
if (action === "preview") { if (action === 'preview') {
url = item.url; url = item.url;
} else if (action === "delete") { } else if (action === 'delete') {
url = url =
baseUrlApi + baseUrlApi +
"/data/fishDraft/previewFile?filename=" + '/data/fishDraft/previewFile?filename=' +
item.name + item.name +
"&taskId=" + '&taskId=' +
taskId.value + taskId.value +
"&type=1"; '&type=1';
} }
mixedList.push({ mixedList.push({
id: record.id, id: record.id,
type: "image", type: 'image',
name: item.name, name: item.name,
url: url, url: url
}); });
}); });
} else if (type === "video") { } else if (type === 'video') {
videoPreviewTitle.value = "视频预览"; videoPreviewTitle.value = '视频预览';
const nameList = record.vdpthList; const nameList = record.vdpthList;
nameList.forEach((item: any) => { nameList.forEach((item: any) => {
let url = ""; let url = '';
if (action === "preview") { if (action === 'preview') {
url = item.url; url = item.url;
} else if (action === "delete") { } else if (action === 'delete') {
url = url =
baseUrlApi + baseUrlApi +
"/data/fishDraft/previewFile?filename=" + '/data/fishDraft/previewFile?filename=' +
item.name + item.name +
"&taskId=" + '&taskId=' +
taskId.value + taskId.value +
"&type=2"; '&type=2';
} }
mixedList.push({ mixedList.push({
id: record.id, id: record.id,
type: "video", type: 'video',
name: item.name, // name: item.name, //
url: url, url: url
}); });
}); });
} }
@ -938,15 +973,16 @@ const handlePreviewClick = (record: any, type: string, index: number, action: st
const handleDeleteMedia = (item: any, type: string, index: number) => { const handleDeleteMedia = (item: any, type: string, index: number) => {
previewList.value.splice(index, 1); previewList.value.splice(index, 1);
if (type == "图片预览") { if (type == '图片预览') {
if (previewList.value.length == 0) { if (previewList.value.length == 0) {
tablePreviewRecord.value.picpthList = []; tablePreviewRecord.value.picpthList = [];
tablePreviewRecord.value.picpthsWarnings = []; tablePreviewRecord.value.picpthsWarnings = [];
} else { } else {
tablePreviewRecord.value.picpthList.splice(index, 1); tablePreviewRecord.value.picpthList.splice(index, 1);
tablePreviewRecord.value.picpthsWarnings = tablePreviewRecord.value.picpthsWarnings.filter( tablePreviewRecord.value.picpthsWarnings =
(warningName: any) => warningName !== item.name tablePreviewRecord.value.picpthsWarnings.filter(
); (warningName: any) => warningName !== item.name
);
} }
} else { } else {
if (previewList.value.length == 0) { if (previewList.value.length == 0) {
@ -954,9 +990,10 @@ const handleDeleteMedia = (item: any, type: string, index: number) => {
tablePreviewRecord.value.vdpthsWarnings = []; tablePreviewRecord.value.vdpthsWarnings = [];
} else { } else {
tablePreviewRecord.value.vdpthList.splice(index, 1); tablePreviewRecord.value.vdpthList.splice(index, 1);
tablePreviewRecord.value.vdpthsWarnings = tablePreviewRecord.value.vdpthsWarnings.filter( tablePreviewRecord.value.vdpthsWarnings =
(warningName: any) => warningName !== item.name tablePreviewRecord.value.vdpthsWarnings.filter(
); (warningName: any) => warningName !== item.name
);
} }
} }
}; };
@ -976,13 +1013,13 @@ onMounted(() => {
observer.observe(tableContainerRef.value); observer.observe(tableContainerRef.value);
} }
}); });
getDictItemsByCode({ dictCode: "direction" }).then((res) => { getDictItemsByCode({ dictCode: 'direction' }).then(res => {
direction.value = res.data; direction.value = res.data;
}); });
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => { getDictItemsByCode({ dictCode: 'approvalStatus' }).then(res => {
guoyuStatus.value.length = 0; guoyuStatus.value.length = 0;
res.data.forEach((item: any) => { res.data.forEach((item: any) => {
if (item.itemCode != "APPROVED") { if (item.itemCode != 'APPROVED') {
guoyuStatus.value.push(item); guoyuStatus.value.push(item);
} }
}); });

File diff suppressed because it is too large Load Diff