冲突解决

This commit is contained in:
王兴凯 2026-06-05 16:17:08 +08:00
commit f58982afc1
34 changed files with 5914 additions and 3201 deletions

View File

@ -207,7 +207,7 @@ public class EngEqDataServiceImpl implements EngEqDataService {
"eng.INTRODUCE AS introduce, " +
"eng.LOGO AS logo, " +
"eng.INFFILE AS inffile, " +
"eng.INFFILE AS url, " +
"eng.url AS url, " +
"eng.BLDSTT AS bldstt, " +
"CASE eng.BLDSTT_CODE WHEN 0 THEN '0' WHEN 1 THEN '1' WHEN 2 THEN '2' ELSE NULL END AS bldsttCcode, " +
"eng.BLDSTT_CODE AS bldsttCode, " +

View File

@ -410,7 +410,7 @@ public class VdMonitorServiceImpl implements VdMonitorService {
"LEFT JOIN SD_FHBT_B_H fh ON fh.STCD = src.FHSTCD AND NVL(fh.IS_DELETED, 0) = 0 " +
"LEFT JOIN (" + buildStationBaseSql() + ") cst ON cst.STCD = src.CSTCD " +
"LEFT JOIN SD_STTP_B sttp ON sttp.STTP_CODE = src.STTP " +
" AND NVL(sttp.IS_DELETED, 0) = 0 AND NVL(sttp.ENABLED, 1) = 1 ";
" AND NVL(sttp.IS_DELETED, 0) = 0 AND NVL(sttp.ENABLE, 1) = 1 ";
}
private String buildVdDeviceBaseSql() {
@ -420,7 +420,8 @@ public class VdMonitorServiceImpl implements VdMonitorService {
" CAST(NULL AS VARCHAR2(255)) AS LOGO, CAST(NULL AS VARCHAR2(1000)) AS INFFILE, " +
" vd.DTFRQCY, vd.REMARK, vd.VLSR, vd.VLSR_TM, vd.URL AS URL, CAST(NULL AS NUMBER(8,0)) AS ORDER_INDEX, " +
" 'SD_VDINFO_B' AS SOURCE_TABLE " +
"FROM SD_VDINFO_B vd WHERE vd.STTP = 'VD_FP' " +
"FROM SD_VDINFO_B vd WHERE 1=1 " +
// " AND vd.STTP = 'VD_FP' "+
"UNION ALL " +
"SELECT sonar.STCD, sonar.STNM, sonar.MNTP, sonar.STTP, sonar.LGTD, sonar.LTTD, sonar.ELEV, sonar.STLC, " +
" sonar.JCDT, sonar.WDDT, sonar.BLDSTT_CODE, sonar.USFL, sonar.DTIN, sonar.STINDX, sonar.RSTCD, sonar.BASE_ID, " +

View File

@ -334,7 +334,7 @@ public class SdWtMonitorServiceImpl implements SdWtMonitorService {
SUM(CASE WHEN NVL(t.DTIN, 0) = 1 THEN 1 ELSE 0 END) AS accessNum,
SUM(CASE WHEN NVL(t.BLDSTT_CODE, 0) = 2 THEN 1 ELSE 0 END) AS buildNum,
SUM(CASE WHEN NVL(t.BLDSTT_CODE, 0) = 1 THEN 1 ELSE 0 END) AS zjNum
FROM SD_DFLTKW_B_H t
FROM SD_DW_B_H t
LEFT JOIN SD_ENGINFO_B_H eng ON eng.STCD = t.RSTCD
WHERE NVL(t.IS_DELETED, 0) = 0
""" + (StrUtil.isNotBlank(baseId) ? """
@ -704,7 +704,7 @@ public class SdWtMonitorServiceImpl implements SdWtMonitorService {
.append("ELSE '未建/规划' END AS bldsttCcodeName, ")
.append("dw.BLDSTT_CODE AS bldsttCcode, ")
.append("eng.HBRVCD AS hbrvcd ")
.append("FROM SD_DFLTKW_B_H dw ")
.append("FROM SD_DW_B_H dw ")
.append("LEFT JOIN SD_ENGINFO_B_H eng ON eng.STCD = dw.RSTCD AND NVL(eng.IS_DELETED, 0) = 0 ")
.append("LEFT JOIN SD_HYDROBASE hb ON hb.BASEID = eng.BASE_ID AND NVL(hb.IS_DELETED, 0) = 0 ")
.append("LEFT JOIN SD_HBRV_DIC hbrv ON hbrv.HBRVCD = eng.HBRVCD AND hbrv.BASEID = eng.BASE_ID AND NVL(hbrv.IS_DELETED, 0) = 0 ")

View File

@ -7,12 +7,12 @@ VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/dev-api'
# 本地环境
# VITE_APP_BASE_URL = 'http://10.84.121.122: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: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'
# 测试环境线上
@ -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_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

@ -3,10 +3,17 @@ NODE_ENV='production'
VITE_APP_TITLE = 'qgc-buji-web'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/prod-api'
VITE_APP_BASE_API = '/'
## 生产环境API地址
VITE_APP_BASE_URL = 'http://localhost:8093'
## 生产环境导入预览地址
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_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",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0",
"pdfjs-dist": "^6.0.227",
"pinia": "^2.0.12",
"screenfull": "^6.0.0",
"sortablejs": "^1.14.0",

View File

@ -89,6 +89,9 @@ importers:
path-to-regexp:
specifier: ^6.2.0
version: 6.3.0
pdfjs-dist:
specifier: ^6.0.227
version: 6.0.227
pinia:
specifier: ^2.0.12
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':
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':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -2864,6 +2942,10 @@ packages:
resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==}
hasBin: true
pdfjs-dist@6.0.227:
resolution: {integrity: sha512-/P6M4SXw+70waMVLUM7rdRtvo+dEzqE1t6W/zQNvBETo2MaRa5rrvCcAYdfWGiUzadTgM0lJmRApUrW0d9zgKg==}
engines: {node: '>=22.13.0 || >=24'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -4323,6 +4405,54 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@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':
dependencies:
'@nodelib/fs.stat': 2.0.5
@ -6817,6 +6947,10 @@ snapshots:
dependencies:
resolve-protobuf-schema: 2.1.0
pdfjs-dist@6.0.227:
optionalDependencies:
'@napi-rs/canvas': 1.0.0
picocolors@1.1.1: {}
picomatch@2.3.2: {}

View File

@ -6,3 +6,59 @@ export function getStcdDetail(url: string, stcd: string) {
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

@ -67,9 +67,9 @@ export function getSimilarFishDictoryDropdown(params:any) {
// 上传文件
export function uploadFile(data: any) {
return request({
url: import.meta.env.VITE_APP_PREVIEW_URL + '/upload',
url: import.meta.env.VITE_APP_ATTACHMENT_URL + '/upload',
method: 'post',
data,
headers: { 'Content-Type': 'multipart/form-data' },
headers: { 'Content-Type': 'multipart/form-data' }
});
}

View File

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

@ -2,15 +2,30 @@
<a-spin :spinning="loading">
<div class="modal-baseic-container">
<a-row class="w-[100%]" v-if="!loading">
<a-col :span="!item.visible ? 24 : (item.filed == '' ? 12 : 12)" v-for="(item, index) in tabledata"
:key="item.key">
<a-col
: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">
{{ item.name }}
<div v-if="modelStore.isBasicEdit && index == 0">
<a-button v-if="!isEdit" type="primary" size="small" class="mr-2" @click="editBtn">
<a-button
v-if="!isEdit"
type="primary"
size="small"
class="mr-2"
@click="editBtn"
>
编辑信息
</a-button>
<a-button v-else type="primary" size="small" class="mr-2" @click="saveBtn">
<a-button
v-else
type="primary"
size="small"
class="mr-2"
@click="saveBtn"
>
保存信息
</a-button>
<a-button type="primary" size="small" @click="recordBtn">
@ -19,23 +34,46 @@
</div>
</div>
<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>
</div>
<div class="col-right" v-if="item.visible">
<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>
<!-- {{ item.value }} -->
<a-input v-if="item.type == 'input'" v-model:value="item.value" :placeholder="item.name"
size="small"></a-input>
<a-select v-else-if="item.type == 'select'" v-model:value="item.value" :placeholder="item.name"
size="small" class="w-full"></a-select>
<a-input
v-if="item.type == 'input'"
v-model:value="item.value"
:placeholder="item.name"
size="small"
></a-input>
<a-select
v-else-if="item.type == 'select'"
v-model:value="item.value"
:placeholder="item.name"
size="small"
class="w-full"
></a-select>
<!-- <div v-else-if="item.type == 'date'">{{ item.value }}2222</div> -->
<a-date-picker v-else-if="item.type == 'date'" v-model:value="item.value" :format="item.format"
:value-format="item.format" :picker="item.picker" class="w-full"></a-date-picker>
<a-input-number v-else-if="item.type == 'number'" v-model:value="item.value" :placeholder="item.name"
size="small" class="w-full"></a-input-number>
<a-date-picker
v-else-if="item.type == 'date'"
v-model:value="item.value"
:format="item.format"
:value-format="item.format"
:picker="item.picker"
class="w-full"
></a-date-picker>
<a-input-number
v-else-if="item.type == 'number'"
v-model:value="item.value"
:placeholder="item.name"
size="small"
class="w-full"
></a-input-number>
</span>
</a-tooltip>
</div>
@ -45,11 +83,20 @@
<div v-if="isLogo" class="img_box_one">
<div class="img_box">
<div class="img_box_img">
<a-image style="width: 100%;" :src="baseUrl + '/?' + data2.msStbprpT.logo" />
<a-image
style="width: 100%"
:src="baseUrl + '/?' + data2.msStbprpT.logo"
/>
</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
</a-modal>
</div>
@ -57,104 +104,121 @@
</template>
<script lang="ts" setup>
import dayjs from "dayjs";
import { ref, onMounted, nextTick,watch } from "vue";
import { BasicColumns, 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"
import dayjs from 'dayjs';
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 { 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 hasLoaded = ref(false); //
const props = defineProps({
url: {
type: String,
default: "",
default: ''
},
isActive: {
type: Boolean,
default: false
}
});
const visible = ref(false);
const isLogo = ref(false)
const isLogo = ref(false);
const loading = ref(false);
const data2: any = ref({})
const data2: any = ref({});
const tabledata = ref([]);
console.log(BasicColumns);
const columnsConfig = ref([
{
type: "ENG",
columns: BasicColumns,
type: 'ENG',
columns: BasicColumns
},
{
type: "wt_point",
columns: wtPointColumns,
type: 'wt_point',
columns: wtPointColumns
},
{
type: "fh_wq_point",
columns: FhWpPointColumns,
type: 'fh_wq_point',
columns: FhWpPointColumns
},
{
type: "fh_point",
columns: FhPointColumns,
type: 'fh_point',
columns: FhPointColumns
},
{
type: "fh_zq_point",
columns: FhZQPointColumns,
type: 'fh_zq_point',
columns: FhZQPointColumns
}, //
{
type: "fp_point",
columns: FPPointColumns,
type: 'fp_point',
columns: FPPointColumns
},
{
type: "fb_point",
columns: FBPointColumns,
type: 'fb_point',
columns: FBPointColumns
},
{
type: "va_point",
columns: VaPointColumns,
type: 'va_point',
columns: VaPointColumns
},
{
type: "vp_point",
columns: VPPointColumns,
type: 'vp_point',
columns: VPPointColumns
},
{
type: "we_point",
columns: wePointColumns,
type: 'we_point',
columns: wePointColumns
},
{
type: "wa_point",
columns: waPointColumns,
type: 'wa_point',
columns: waPointColumns
},
{
type: "stinfo_ai_video_point",
columns: StinfoAiVidoPointColumns,
type: 'stinfo_ai_video_point',
columns: StinfoAiVidoPointColumns
},
{
type: "eng_alarm_point",
columns: BasicColumns,
type: 'eng_alarm_point',
columns: BasicColumns
},
{
type: "fprd_point",
columns: StinfoAiVidoPointColumns,
type: 'fprd_point',
columns: StinfoAiVidoPointColumns
},
{
type: "dw_point",
columns: DwPointColumns,
type: 'dw_point',
columns: DwPointColumns
},
{
type: "DW_5",
columns: DwFivePointColumns,
type: 'DW_5',
columns: DwFivePointColumns
},
{
type: "DW_6",
columns: DwSixPointColumns,
type: 'DW_6',
columns: DwSixPointColumns
},
{
type: "DW_1",
columns: DwOnePointColumns,
},
type: 'DW_1',
columns: DwOnePointColumns
}
//DW_1
]);
@ -162,51 +226,58 @@ const isEdit = ref(false);
const editBtn = () => {
nextTick(() => {
isEdit.value = true;
tabledata.value.forEach((item) => {
if (item.type == "date") item.value = dayjs(item.value).format(item.format);
tabledata.value.forEach(item => {
if (item.type == 'date')
item.value = dayjs(item.value).format(item.format);
});
});
};
const saveBtn = () => {
isEdit.value = false;
console.log(tabledata.value);
};
const recordBtn = () => {
visible.value = true;
};
const getData = async () => {
if (hasLoaded.value) return;
loading.value = true;
console.log(props.url);
console.log(modelStore.params.stcd);
if (props.url && modelStore.params.stcd) {
let res: any = await getStcdDetail(props.url, modelStore.params.stcd)
console.log(res);
let res: any = await getStcdDetail(props.url, modelStore.params.stcd);
if (res.success) {
let data = res.data
data2.value = data
let data = res.data;
data2.value = data;
let columns = [];
columns = columnsConfig.value.find((item) => item.type == modelStore.params.sttp)
.columns;
columns = columnsConfig.value.find(
item => item.type == modelStore.params.sttp
).columns;
columns.forEach((item) => {
columns.forEach(item => {
if (item.visible) {
if (item.type == "date") {
if (data.msStbprpT[item.filed] && data.msStbprpT[item.filed] !== "-") {
item.value = dayjs(data.msStbprpT[item.filed] || "").format(item.format);
if (item.type == 'date') {
if (
data.msStbprpT[item.filed] &&
data.msStbprpT[item.filed] !== '-'
) {
item.value = dayjs(data.msStbprpT[item.filed] || '').format(
item.format
);
} else {
item.value = null;
}
} else {
if (item.type == "number") {
if (item.type == 'number') {
if (data.msStbprpT[item.filed]) {
if (item.filed == "ttpwr") {
if (item.filed == 'ttpwr') {
item.value =
(Number(data.msStbprpT[item.filed] || 0) / 10).toFixed(item.toFixed) ||
null;
(Number(data.msStbprpT[item.filed] || 0) / 10).toFixed(
item.toFixed
) || null;
} else {
item.value =
Number(data.msStbprpT[item.filed] || 0).toFixed(item.toFixed) || null;
Number(data.msStbprpT[item.filed] || 0).toFixed(
item.toFixed
) || null;
}
} else {
item.value = null;
@ -215,59 +286,54 @@ const getData = async () => {
item.value = data.msStbprpT[item.filed] || null;
}
}
let stcdItem = data.stcdValVoList.find((stcdItem) => {
let str = "";
let stcdItem = data.stcdValVoList.find(stcdItem => {
let str = '';
let arr = [
"baseName",
"addvcdName",
"hbrvcdName",
"bldsttCcodeName",
"dvtpName",
"rgcpName",
'baseName',
'addvcdName',
'hbrvcdName',
'bldsttCcodeName',
'dvtpName',
'rgcpName'
];
if (arr.includes(item.filed)) {
// name
str = item.filed.replace("Name", "");
str = item.filed.replace('Name', '');
} else {
str = item.filed;
}
return stcdItem.attrKey == str;
});
if (stcdItem && stcdItem.attrKey == "rgcp") {
console.log(stcdItem);
if (stcdItem && stcdItem.attrKey == 'rgcp') {
}
if (stcdItem && stcdItem.vlsr) {
item.vlsr = stcdItem.vlsr;
}
// console.log(stcdItem);
}
});
tabledata.value = columns;
loading.value = false;
hasLoaded.value = true;
} else {
loading.value = false;
}
if (data2.value.msStbprpT.logo) {
isLogo.value = true
isLogo.value = true;
} else {
isLogo.value = false
isLogo.value = false;
}
}
};
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) {
getData();
}
onMounted(() => {
getData()
setTimeout(() => {
}, 1000);
});
},
{ immediate: true } //
);
//modelStore.params.stcd
watch(
() => modelStore.params.stcd,
@ -306,20 +372,12 @@ watch(
box-sizing: border-box;
border: 1px solid rgba(0, 127, 204, 0.65);
}
}
.ant-row {
border-left: 1px solid rgba(0, 127, 204, 0.65);
border-top: 1px solid rgba(0, 127, 204, 0.65);
height: 100%;
.ant-col:nth-last-child(-n + 2) {
.col-left,
.col-right {
// border-bottom: none !important;
}
}
}
.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>
<a-modal :open="visible" :title="title" width="80vw" :footer="null" :closable="true" @cancel="handleClose"
:destroyOnClose="true" class="map-modal">
<a-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">
<a-select 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
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>
</div>
<a-tabs :active-key="currentActiveKey" @change="onTabChange">
<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>
<template #rightExtra>
<a-tooltip :title="!isEngConfig ? '' : '该电站无专题配置'">
<a-button type="primary" :disabled="isEngConfig">
<i class="icon iconfont icon-topic mr-[5px]"></i>
电站专题
</a-button></a-tooltip>
</a-button></a-tooltip
>
</template>
</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>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { ref, watch, reactive, computed } from 'vue';
// Tab
import BasicInfo from "./components/BasicInfo.vue";
import WaterTemperature from "./components/WaterTemperature.vue";
import WaterTemperatureContrast from "./components/WaterTemperatureContrast.vue";
import { useModelStore } from "@/store/modules/model";
import { handleTabs } from "./setting.config";
import BasicInfo from './components/BasicInfo.vue'; //
import VideoInfo from './components/videoInfo.vue'; //
import PanoramaInfo from './components/PanoramaInfo.vue'; //
import MonitorInfo from './components/MonitorInfo.vue'; //
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 tabsConfig = ref([]);
@ -57,7 +117,22 @@ interface TabItem {
title: 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
const props = defineProps<{
visible: boolean;
@ -69,17 +144,17 @@ const props = defineProps<{
// Emits
// 'update:activeKey' v-model:active-key
const emit = defineEmits<{
(e: "update:visible", value: boolean): void;
(e: "change", key: string): void;
(e: 'update:visible', value: boolean): void;
(e: 'change', key: string): void;
}>();
// activeKey
const currentActiveKey = ref<string>("");
const currentActiveKey = ref<string>('');
// activeKey
watch(
() => props.activeKey,
(newVal) => {
newVal => {
if (newVal && newVal !== currentActiveKey.value) {
currentActiveKey.value = newVal;
}
@ -88,7 +163,8 @@ watch(
);
watch(
() => modelStore.params,
(newVal) => {
newVal => {
if (!newVal || Object.keys(newVal).length === 0) return;
console.log(newVal);
tabsConfig.value = handleTabs(newVal);
let value = tabsConfig.value.find((item: any) => item.default);
@ -101,12 +177,13 @@ const onTabChange = (key: string) => {
currentActiveKey.value = key;
// activeKey ( v-model)
// ()
emit("change", key);
emit('change', key);
};
//
const handleClose = () => {
emit("update:visible", false);
emit('update:visible', false);
modelStore.params = {};
// ,
modelStore.showStcdSelector = false;
modelStore.stcdOptions = [];
@ -116,7 +193,7 @@ const handleClose = () => {
const modalData = ref(props.data);
watch(
() => props.data,
(newVal) => {
newVal => {
modalData.value = newVal;
}
);

View File

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

View File

@ -19,7 +19,7 @@ declare global {
__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 labelType -
*/
export const appendOffsetPropties = (
item: any,
index: any,
labelType = 2
) => {
export const appendOffsetPropties = (item: any, index: any, labelType = 2) => {
let drawDotImg = null;
let offset = null;
if (labelType == 2) {

View File

@ -144,6 +144,7 @@ export class MapOl implements MapInterface {
this.view.on('change:resolution', () => {});
this.map.on('click', evt => {
this.popupManager.showPopup(undefined, undefined);
this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
modelStore.modalVisible = true;
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,9 +1,20 @@
<template>
<div>
<!-- 搜索表单 - 使用 Ant Design Vue 原生组件 -->
<a-form ref="formRef" :model="formState" layout="inline" @finish="onFinish" style="margin-bottom: 16px">
<a-form
ref="formRef"
:model="formState"
layout="inline"
@finish="onFinish"
style="margin-bottom: 16px"
>
<a-form-item label="达标状态" name="sfdb">
<a-select v-model:value="formState.sfdb" placeholder="请选择" allow-clear style="width: 150px">
<a-select
v-model:value="formState.sfdb"
placeholder="请选择"
allow-clear
style="width: 150px"
>
<a-select-option value="">全部</a-select-option>
<a-select-option :value="'0'">不达标</a-select-option>
<a-select-option :value="'1'">达标</a-select-option>
@ -22,44 +33,58 @@
</a-form>
<!-- 数据表格 -->
<BasicTable ref="tableRef" :columns="columns"
:list-url="props.calculateFlag?.calculateFlag == 1 ? dataGetKendoListCust : NodataGetKendoListCust"
:scrollY="500" :search-params="{ sort: [{ field: 'tm', dir: 'desc' }] }"
:transform-data="customTransform" />
<BasicTable
ref="tableRef"
:columns="columns"
:list-url="
props.calculateFlag?.calculateFlag == 1
? dataGetKendoListCust
: NodataGetKendoListCust
"
:scrollY="500"
:search-params="{ sort: [{ field: 'tm', dir: 'desc' }] }"
:transform-data="customTransform"
/>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted, h, nextTick } from 'vue'
import { omit } from 'lodash-es'
import { Button, Tooltip, Popover } from 'ant-design-vue'
import BasicTable from '@/components/BasicTable/index.vue'
import { ruleGetKendoListCust, noAuthGetKendoListCust, stbprpGetStbprpYsByStcd, dataGetKendoListCust, NodataGetKendoListCust } from '@/api/sz'
import { ref, reactive, computed, watch, onMounted, h, nextTick } from 'vue';
import { omit } from 'lodash';
import { Button, Tooltip, Popover } from 'ant-design-vue';
import BasicTable from '@/components/BasicTable/index.vue';
import {
ruleGetKendoListCust,
noAuthGetKendoListCust,
stbprpGetStbprpYsByStcd,
dataGetKendoListCust,
NodataGetKendoListCust
} from '@/api/sz';
// ==================== Props ====================
interface Props {
tc: {
stcd: string
startTime: string
endTime: string
sfdb: string
}
stcd: string;
startTime: string;
endTime: string;
sfdb: string;
};
calculateFlag?: {
calculateFlag: number
}
calculateFlag: number;
};
}
const props = defineProps<Props>()
const props = defineProps<Props>();
// ==================== ====================
const formRef = ref()
const tableRef = ref()
const col = ref<any[]>([])
const formRef = ref();
const tableRef = ref();
const col = ref<any[]>([]);
// sfdb ref
const formState = reactive({
sfdb: '1'
})
});
// ==================== ====================
const columnList = [
@ -180,16 +205,16 @@ const columnList = [
customRender: ({ record }: any) => {
// TODO:
// return getDictLabel('sd_wq_r.sfdb', record.sfdbName)
return record.sfdbName //
return record.sfdbName; //
}
}
]
];
// ==================== ====================
const columns = computed(() => {
if (!col.value.length) return columnList
return [...columnList, ...col.value]
})
if (!col.value.length) return columnList;
return [...columnList, ...col.value];
});
// ==================== ====================
const tableFilter = computed(() => {
@ -198,10 +223,10 @@ const tableFilter = computed(() => {
sfdb: formState.sfdb === '2' ? '' : formState.sfdb,
startTime: props.tc.startTime,
endTime: props.tc.endTime
}
if (res.sfdb === '') res = omit(res, 'sfdb')
return res
})
};
if (res.sfdb === '') res = omit(res, 'sfdb');
return res;
});
// ==================== ====================
@ -209,101 +234,102 @@ const tableFilter = computed(() => {
const onFinish = () => {
const filters: any[] = [
{
"field": "stcd",
"operator": "eq",
"dataType": "string",
"value": tableFilter.value.stcd
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: tableFilter.value.stcd
},
formState.sfdb ? {
"field": "sfdb",
"operator": "eq",
"dataType": "string",
"value": formState.sfdb
} : null,
{
"field": "tm",
"operator": "gte",
"dataType": "date",
"value": tableFilter.value.startTime
},
{
"field": "tm",
"operator": "lte",
"dataType": "date",
"value": tableFilter.value.endTime
formState.sfdb
? {
field: 'sfdb',
operator: 'eq',
dataType: 'string',
value: formState.sfdb
}
].filter(Boolean)
: null,
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: tableFilter.value.startTime
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: tableFilter.value.endTime
}
].filter(Boolean);
const filter = {
logic: 'and',
filters
}
};
//
tableRef.value?.getList(filter)
}
tableRef.value?.getList(filter);
};
//
const handleReset = () => {
formState.sfdb = '1'
onFinish()
}
formState.sfdb = '1';
onFinish();
};
// TODO: - API
const getCol = async (params: any) => {
try {
const lo = await columYaoshu(params)
col.value = lo || []
const lo = await columYaoshu(params);
col.value = lo || [];
} catch (error) {
console.error('获取列配置失败:', error)
}
console.error('获取列配置失败:', error);
}
};
// TODO: -
const columYaoshu = async (params: any) => {
// API 1:
let params3 = {
"filter": {
"logic": "and",
"filters": [
filter: {
logic: 'and',
filters: [
{
"field": "stcd",
"operator": "eq",
"dataType": "string",
"value": params?.stcd
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: params?.stcd
}
]
}
}
const MaxData = await ruleGetKendoListCust(params3)
};
const MaxData = await ruleGetKendoListCust(params3);
// API 2:
let params2 = {
"filter": {
"logic": "and",
"filters": [
filter: {
logic: 'and',
filters: [
{
"field": "stcd",
"operator": "eq",
"dataType": "string",
"value": params?.stcd
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: params?.stcd
},
{
"field": "tm",
"operator": "lte",
"dataType": "date",
"value": params?.endTime
field: 'tm',
operator: 'lte',
dataType: 'date',
value: params?.endTime
},
{
"field": "tm",
"operator": "gte",
"dataType": "date",
"value": params?.startTime
field: 'tm',
operator: 'gte',
dataType: 'date',
value: params?.startTime
}
]
}
}
const noAuthRes = await noAuthGetKendoListCust(params2)
const noAuthResData = noAuthRes?.data?.data || []
};
const noAuthRes = await noAuthGetKendoListCust(params2);
const noAuthResData = noAuthRes?.data?.data || [];
// API 3:
let params1 = {
@ -311,38 +337,38 @@ const columYaoshu = async (params: any) => {
tbCode: 'WQ_R',
endTime: params.endTime,
startTime: params.startTime
}
const res = await stbprpGetStbprpYsByStcd(params1)
};
const res = await stbprpGetStbprpYsByStcd(params1);
if (res?.data?.data) {
const processedColumns = res.data.data
.filter((item: any) => item.enable === '1')
.map((item: any) => {
const _t = item.ysShowName
const reg = /[(][^()]+[)]/
const _t = item.ysShowName;
const reg = /[(][^()]+[)]/;
// TODO:
const _u = getUnitConfigByCode('WQ_R', item.ys)?.unit
const _u = getUnitConfigByCode('WQ_R', item.ys)?.unit;
if (_u) {
item.ysShowName = reg.test(_t)
? _t.replace(reg, `(${_u})`)
: `${_t}(${_u})`
: `${_t}(${_u})`;
}
let showValue = ''
let showValue = '';
if (noAuthResData.length) {
const { max, min } = noAuthResData[0]
const ys = item.ys
const maxValue = max.filter((e: any) => e[ys])
const minValue = min.filter((e: any) => e[ys])
const { max, min } = noAuthResData[0];
const ys = item.ys;
const maxValue = max.filter((e: any) => e[ys]);
const minValue = min.filter((e: any) => e[ys]);
if (maxValue.length && minValue.length) {
showValue = `(${minValue[0]?.[ys]}-${maxValue[0]?.[ys]})`
showValue = `(${minValue[0]?.[ys]}-${maxValue[0]?.[ys]})`;
} else {
if (minValue.length) {
showValue = `>${minValue[0]?.[ys]}`
showValue = `>${minValue[0]?.[ys]}`;
}
if (maxValue.length) {
showValue = `<${maxValue[0]?.[ys]}`
showValue = `<${maxValue[0]?.[ys]}`;
}
}
}
@ -351,14 +377,19 @@ const columYaoshu = async (params: any) => {
dataIndexRaw: '',
key: item.ys.toLowerCase(),
// Vue3 title VNode
title: () => h('div', [
h('p', {
title: () =>
h('div', [
h(
'p',
{
style: {
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'keep-all'
}
}, item.ysShowName),
},
item.ysShowName
),
h('p', showValue)
]),
dataIndex: item.ys.toLowerCase(),
@ -374,34 +405,63 @@ const columYaoshu = async (params: any) => {
// Vue3 使 customRender render
customRender: ({ record }: any) => {
// TODO: MaxData
const datalist = MaxData?.data?.data
const dynamicDataIndex = item.ys
const dynamicDataName = item.ysShowName
const matchedItem = datalist.find((m: any) => m.ys === dynamicDataIndex)
const datalist = MaxData?.data?.data;
const dynamicDataIndex = item.ys;
const dynamicDataName = item.ysShowName;
const matchedItem = datalist.find(
(m: any) => m.ys === dynamicDataIndex
);
//
if (
matchedItem &&
(matchedItem.isAsc == 1 ? matchedItem?.maxVal : matchedItem.minVal) &&
(matchedItem.isAsc == 1
? matchedItem?.maxVal
: matchedItem.minVal) &&
record[dynamicDataIndex?.toLowerCase()] &&
((matchedItem.isAsc == 1 && record[dynamicDataIndex?.toLowerCase()] > matchedItem?.maxVal) ||
(matchedItem.isAsc != 1 && record[dynamicDataIndex?.toLowerCase()] < matchedItem.minVal))
((matchedItem.isAsc == 1 &&
record[dynamicDataIndex?.toLowerCase()] >
matchedItem?.maxVal) ||
(matchedItem.isAsc != 1 &&
record[dynamicDataIndex?.toLowerCase()] < matchedItem.minVal))
) {
// Vue3 使 h() VNode
return h(Popover, {
content: h('div', `预警标准: ${matchedItem.isAsc === 1 ? `>${matchedItem.maxVal}` : `<${matchedItem.minVal}`}`),
return h(
Popover,
{
content: h(
'div',
`预警标准: ${
matchedItem.isAsc === 1
? `>${matchedItem.maxVal}`
: `<${matchedItem.minVal}`
}`
),
trigger: 'hover'
}, () =>
h('span', {
style: { color: 'red', cursor: 'pointer' },
},
() =>
h(
'span',
{
style: { color: 'red', cursor: 'pointer' }
// TODO:
}, transUnitRender(record[dynamicDataIndex?.toLowerCase()], 'WQ_R', dynamicDataIndex))
},
transUnitRender(
record[dynamicDataIndex?.toLowerCase()],
'WQ_R',
dynamicDataIndex
)
)
);
}
//
// TODO:
return transUnitRender(record[dynamicDataIndex?.toLowerCase()], 'WQ_R', dynamicDataIndex)
return transUnitRender(
record[dynamicDataIndex?.toLowerCase()],
'WQ_R',
dynamicDataIndex
);
},
fromType: '',
dataType: '',
@ -416,17 +476,13 @@ const columYaoshu = async (params: any) => {
elementCode: '',
disabledDate: true,
multiLink: false
};
});
console.log(processedColumns);
onFinish();
return processedColumns || [];
}
})
console.log(processedColumns)
onFinish()
return processedColumns || []
}
}
};
// TODO: -
// ==================== ====================
@ -440,22 +496,22 @@ const columYaoshu = async (params: any) => {
const getUnitConfigByCode = (tbCode: string, ys: string) => {
// TODO:
const unitConfig: Record<string, Record<string, { unit: string }>> = {
'WQ_R': {
'PH': { unit: '' },
'DOX': { unit: 'mg/L' },
'CODMN': { unit: 'mg/L' },
'CODCR': { unit: 'mg/L' },
'NH3N': { unit: 'mg/L' },
'TP': { unit: 'mg/L' },
'TN': { unit: 'mg/L' },
'WTMP': { unit: '℃' },
'COND': { unit: 'μS/cm' },
'CLARITY': { unit: 'm' }
}
WQ_R: {
PH: { unit: '' },
DOX: { unit: 'mg/L' },
CODMN: { unit: 'mg/L' },
CODCR: { unit: 'mg/L' },
NH3N: { unit: 'mg/L' },
TP: { unit: 'mg/L' },
TN: { unit: 'mg/L' },
WTMP: { unit: '℃' },
COND: { unit: 'μS/cm' },
CLARITY: { unit: 'm' }
}
};
return unitConfig[tbCode]?.[ys] || { unit: '' }
}
return unitConfig[tbCode]?.[ys] || { unit: '' };
};
/**
* 带单位的值渲染根据要素类型设置不同小数位数不带单位
@ -465,27 +521,27 @@ const getUnitConfigByCode = (tbCode: string, ys: string) => {
* @returns 格式化后的字符串不带单位
*/
const transUnitRender = (value: any, tbCode: string, ys: string) => {
if (value === null || value === undefined || value === '') return '-'
if (value === null || value === undefined || value === '') return '-';
const numValue = Number(value)
if (isNaN(numValue)) return String(value)
const numValue = Number(value);
if (isNaN(numValue)) return String(value);
//
let formattedValue: string
let formattedValue: string;
if (ys === 'WTMP' || ys === 'PH') {
// PH
formattedValue = numValue.toFixed(1)
formattedValue = numValue.toFixed(1);
} else if (ys === 'COND') {
//
formattedValue = Math.round(numValue).toString()
formattedValue = Math.round(numValue).toString();
} else {
//
formattedValue = numValue.toFixed(2)
formattedValue = numValue.toFixed(2);
}
return formattedValue
}
return formattedValue;
};
/**
* 纯单位转换不带渲染
@ -495,9 +551,9 @@ const transUnitRender = (value: any, tbCode: string, ys: string) => {
* @returns 转换后的数值
*/
const transUnit = (value: any, tbCode: string, ys: string) => {
if (value === null || value === undefined) return null
return Number(value)
}
if (value === null || value === undefined) return null;
return Number(value);
};
// ==================== ====================
@ -506,33 +562,31 @@ watch(
() => [props.tc.stcd, props.tc.startTime, props.tc.endTime, formState.sfdb],
([stcd, startTime, endTime]) => {
if (!stcd) {
return
return;
}
// debugger
tableRef.value.tableData = []
getCol({ stcd, startTime, endTime })
tableRef.value.tableData = [];
getCol({ stcd, startTime, endTime });
},
{ immediate: false } // onMounted
)
);
//
onMounted(() => {
nextTick(() => {
})
nextTick(() => {});
getCol({
stcd: props.tc.stcd,
startTime: props.tc.startTime,
endTime: props.tc.endTime
})
})
});
});
//
const customTransform = (res: any) => {
return {
records: res?.data?.data || [],
total: res?.data?.total || 0
}
}
};
};
</script>
<style scoped>

View File

@ -1,13 +1,24 @@
<!-- SidePanelItem.vue -->
<template>
<SidePanelItem title="设施类型介绍">
<div class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
<div
class="container"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
<!-- 跑马灯轨道容器 -->
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }"
:style="{ transform: `translateX(-${currentIndex * 100}%)` }">
<div
class="carousel-track"
:class="{ 'no-transition': isTransitioning }"
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
>
<!-- 遍历所有媒体项包含克隆项 -->
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item"
@click="handleItemClick(originalMediaData)">
<div
v-for="(item, index) in renderMediaData"
:key="index"
class="carousel-item"
@click="handleItemClick(originalMediaData)"
>
<!-- 图片 -->
<img v-if="item.url" :src="item.url" alt="" />
<!-- 说明文字随媒体项移动 -->
@ -17,8 +28,13 @@
<!-- 面板指示器固定在底部右侧 -->
<div class="pagination-dots-fixed">
<span v-for="(dot, index) in originalMediaData" :key="index" class="dot"
:class="{ active: getCurrentRealIndex() === index }" @click="goToSlide(index)"></span>
<span
v-for="(dot, index) in originalMediaData"
:key="index"
class="dot"
:class="{ active: getCurrentRealIndex() === index }"
@click="goToSlide(index)"
></span>
</div>
</div>
@ -26,23 +42,26 @@
<div class="description-text" @click="handleItemClick(originalMediaData)">
{{ currentDescription }}
</div>
</SidePanelItem>
<!-- 设施详情弹框 -->
<a-modal v-model:open="modalVisible" :title="'设施类型介绍'" width="1536px" :footer="null">
<a-modal
v-model:open="modalVisible"
:title="'设施类型介绍'"
width="1536px"
:footer="null"
>
<!-- 你在这里编写弹框内容 -->
<div v-if="currentItem">
<ArtsDetail :dataSource='currentItem' :index="getCurrentRealIndex()" />
<ArtsDetail :dataSource="currentItem" :index="getCurrentRealIndex()" />
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue';
import { sttpbGetKendoList } from "@/api/sw";
import { sttpbGetKendoList } from '@/api/sw';
// 便
defineOptions({
name: 'diwenshuijianhuansheshileixingzuchengjijieruqingkuang'
@ -57,9 +76,7 @@ interface MediaItem {
}
// 3
const originalMediaData = ref<MediaItem[]>([
]);
const originalMediaData = ref<MediaItem[]>([]);
//
// [, ..., ]
@ -150,33 +167,33 @@ const currentDescription = computed(() => {
const getListData = async () => {
try {
const params = {
"filter": {
"logic": "and",
"filters": [
filter: {
logic: 'and',
filters: [
{
"field": "fullPath",
"operator": "contains",
"dataType": "string",
"value": "DW,DW"
field: 'fullPath',
operator: 'contains',
dataType: 'string',
value: 'DW,DW'
},
{
"field": "logo",
"operator": "isnotnull",
"dataType": "string"
field: 'logo',
operator: 'isnotnull',
dataType: 'string'
},
{
"field": "introduce",
"operator": "isnotnull",
"dataType": "string"
field: 'introduce',
operator: 'isnotnull',
dataType: 'string'
}
]
}
}
};
let res = await sttpbGetKendoList(params);
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) => ({
text: item.sttpName,
description: item.introduce,
@ -184,10 +201,8 @@ const getListData = async () => {
}));
console.log(originalMediaData.value);
} catch (error) {
originalMediaData.value = [];
} finally {
}
};
@ -296,7 +311,7 @@ const goToSlide = (targetIndex: number) => {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #D8D8D8;
background-color: #d8d8d8;
cursor: pointer;
transition: background-color 0.3s ease;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff