数据填报bug修改,添加预览视频图片公共组件,删除无用预览改为公共组件

This commit is contained in:
扈兆增 2026-05-09 19:30:03 +08:00
parent 0d5c79bd2e
commit f289059628
20 changed files with 2790 additions and 2806 deletions

View File

@ -15,6 +15,6 @@ VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
# 开发环境导入预览地址
VITE_APP_BASE_API_URL = 'http://172.16.21.14:8096'
VITE_APP_BASE_API_URL = 'http://10.84.121.21:8093'
## 开发环境预览 图片视频地址
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'

View File

@ -32,5 +32,5 @@ module.exports = {
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'lf'
endOfLine: 'lf',
};

BIN
frontend/dist.rar Normal file

Binary file not shown.

View File

@ -1,7 +1,7 @@
import request from '@/utils/request';
// 分页查询过鱼数据
export function getFishDraftPage(data:any) {
export function getFishDraftPage(data: any) {
return request({
url: '/data/fishDraft/page',
method: 'post',
@ -9,7 +9,7 @@ export function getFishDraftPage(data:any) {
});
}
//新增过鱼数据
export function addFishDraft(queryParams:any) {
export function addFishDraft(queryParams: any) {
return request({
url: '/data/fishDraft/saveDraft',
method: 'post',
@ -17,7 +17,7 @@ export function addFishDraft(queryParams:any) {
});
}
//修改过鱼数据
export function editFishDraft(queryParams:any) {
export function editFishDraft(queryParams: any) {
return request({
url: '/data/fishDraft/updateDraft',
method: 'post',
@ -25,7 +25,7 @@ export function editFishDraft(queryParams:any) {
});
}
//删除 过鱼数据
export function delFishDraft(data:any) {
export function delFishDraft(data: any) {
return request({
url: '/data/fishDraft/batchRemoveDraft',
method: 'post',
@ -33,22 +33,22 @@ export function delFishDraft(data:any) {
});
}
//提交过鱼数据
export function submitFishDraft(data:any) {
export function submitFishDraft(data: any) {
return request({
url: '/data/fishDraft/submitDrafts',
method: 'post',
data
});
}
//提交全部过鱼数据
//提交全部过鱼数据
export function batchApproveAll() {
return request({
url: '/data/fishDraft/submitDraftsAll',
method: 'post',
method: 'post'
});
}
//审批过鱼数据
export function successFishDraft(data:any) {
export function successFishDraft(data: any) {
return request({
url: '/data/fishDraft/batchApprove',
method: 'post',
@ -56,7 +56,7 @@ export function successFishDraft(data:any) {
});
}
//驳回过鱼数据
export function rejectFishDraft(data:any) {
export function rejectFishDraft(data: any) {
return request({
url: '/data/fishDraft/reject',
method: 'post',
@ -64,7 +64,7 @@ export function rejectFishDraft(data:any) {
});
}
// 导入zip
export function importFishZip(data:FormData) {
export function importFishZip(data: FormData) {
return request({
url: '/data/fishDraft/importZip',
method: 'post',
@ -73,7 +73,7 @@ export function importFishZip(data:FormData) {
});
}
// 取消导入任务
export function cancelImportTask(data:any) {
export function cancelImportTask(data: any) {
return request({
url: '/data/fishDraft/cancelImport',
method: 'post',
@ -95,7 +95,7 @@ export function getLastImportResult() {
method: 'get'
});
}
export function deleteFile(params:any) {
export function deleteFile(params: any) {
return request({
url: '/data/fishDraft/deleteFile',
method: 'get',
@ -103,10 +103,18 @@ export function deleteFile(params:any) {
});
}
// 批量保存草稿
export function batchSaveDraft(data:any) {
export function batchSaveDraft(data: any) {
return request({
url: '/data/fishDraft/batchSaveDraft',
method: 'post',
data
});
}
}
// 批量删除 审批已驳回的
export function batchRemoveDraft(data: any) {
return request({
url: '/data/approvalMain/batchDelete',
method: 'post',
data
});
}

View File

@ -34,8 +34,6 @@ interface Props {
rowKey?: string;
// /
searchParams?: Record<string, any>;
// false
isOneLoad?: boolean;
//
defaultPageSize?: number;
getCheckboxProps?: (record: any) => any;
@ -46,7 +44,6 @@ const props = withDefaults(defineProps<Props>(), {
enableRowSelection: false,
rowKey: "id",
data: () => ([]),
isOneLoad: true,
searchParams: () => ({}),
defaultPageSize: 20,
getCheckboxProps: undefined,
@ -143,7 +140,6 @@ const getList = async (filter?: Record<string, any>) => {
totalCount = res?.data?.total || res?.total || 0;
}
tableData.value = records;
total.value = totalCount;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,10 +10,10 @@
class="map-modal"
>
<a-tabs :active-key="currentActiveKey" @change="onTabChange">
<a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.title">
<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" />
<BasicInfo v-if="currentActiveKey === 'basicInfo'" :url="tab.url" />
<!-- 地图组件 -->
<!-- <MapView v-else-if="currentActiveKey === 'mapView'" :data="modalData" /> -->
<!-- 周边配套组件 -->
@ -23,6 +23,14 @@
/> -->
</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>
</template>
</a-tabs>
</a-modal>
</template>
@ -31,6 +39,13 @@
import { ref, watch } from "vue";
// Tab
import BasicInfo from "./components/BasicInfo.vue";
import { useModelStore } from "@/store/modules/model";
import { handleTabs } from "./setting.config";
const modelStore = useModelStore();
const tabsConfig = ref([]);
//
const isEngConfig = ref(true);
// import MapView from './components/MapView.vue';
// import SurroundingInfo from './components/SurroundingInfo.vue';
@ -46,7 +61,6 @@ const props = defineProps<{
visible: boolean;
title?: string;
activeKey?: string; // tab
tabsConfig?: TabItem[]; // Tab Tab
data?: any; //
}>();
@ -71,7 +85,13 @@ watch(
},
{ immediate: true }
);
watch(
() => modelStore.params,
(newVal) => {
tabsConfig.value = handleTabs(newVal);
},
{ deep: true, immediate: true }
);
// tab
const onTabChange = (key: string) => {
currentActiveKey.value = key;

View File

@ -12,95 +12,98 @@
// import { Utility } from '@zebras/qgc-share/utils/Utility'
// // 水电站 √
// const ENGTabs: Array<any> = [
// {
// name: '基础信息',
// key: 'basicInfo',
// type: 'basic',
// url: '/bbi/siteBipc/getSiteBasicInfo'
// },
// {
// name: '阶段属性',
// key: 'basicFilter',
// type: 'basicFilter',
// url: '/eng/engBasisInfo/getEngBaseInfo'
// },
// {
// name: '实时视频',
// key: 'videoInfo',
// type: 'video',
// url: '/video/dataStcdFrame/getVideoMonitorList'
// },
// {
// name: '全景影像',
// key: 'panoramaInfo',
// type: 'panorama'
// },
// {
// name: '监测数据',
// key: 'monitorInfo',
// type: 'tabsWithTwo',
// code: 'dzxq.tabs.jcsj'
// },
// {
// name: '预警提示',
// key: 'tableTabs',
// type: 'tableTabs',
// code: 'dzxq-yjts',
// tabs: [
// {
// name: '设计参数变更提示',
// key: 'DesignParameterChangePrompt',
// type: 'table',
// hiddenChart: true,
// tableUrl: '/dec-lygk-base-server/base/engWarning/GetKendoList'
// },
// {
// name: '施工期环保措施落实预警',
// key: 'ImplementEarlyWarning',
// type: 'table',
// hiddenChart: true,
// tableUrl: '/dec-lygk-base-server/base/engWarning/sgqhbss/GetKendoListCust'
// },
// {
// name: '环保设施建设预警',
// key: 'ConstructionEarlyWarning',
// type: 'table',
// hiddenChart: true,
// tableUrl: '/dec-lygk-base-server/base/engWarning/hbssjs/GetKendoListCust'
// },
// {
// name: '环保设施运行预警',
// key: 'RunEarlyWarning',
// type: 'table',
// hiddenChart: true,
// tableUrl: '/dec-lygk-base-server/base/engWarning/hbssyx/GetKendoListCust'
// },
// {
// name: '鱼类放流预警',
// key: 'ReleaseEarlyWarning',
// type: 'table',
// hiddenChart: true,
// tableUrl: '/dec-lygk-base-server/base/engWarning/ylfl/GetKendoListCust'
// }
// ]
// },
// Session.getAppCode() === 'hbb' ? {
// name: '查看报告',
// key: 'attachment',
// type: 'attachment'
// } : null,
// {
// name: '批复文件',
// key: 'approval',
// type: 'approval'
// },
// // {
// // name: "特征曲线",
// // key: "characteristicCurve",
// // type: "characteristicCurve"
// // }
// ].filter(Boolean)
const ENGTabs: Array<any> = [
{
name: '基础信息',
key: 'basicInfo',
type: 'basic',
url: '/bbi/siteBipc/getSiteBasicInfo'
},
{
name: '阶段属性',
key: 'basicFilter',
type: 'basicFilter',
url: '/eng/engBasisInfo/getEngBaseInfo'
},
{
name: '实时视频',
key: 'videoInfo',
type: 'video',
url: '/video/dataStcdFrame/getVideoMonitorList'
},
{
name: '全景影像',
key: 'panoramaInfo',
type: 'panorama'
},
{
name: '监测数据',
key: 'monitorInfo',
type: 'tabsWithTwo',
code: 'dzxq.tabs.jcsj'
},
{
name: '预警提示',
key: 'tableTabs',
type: 'tableTabs',
code: 'dzxq-yjts',
tabs: [
{
name: '设计参数变更提示',
key: 'DesignParameterChangePrompt',
type: 'table',
hiddenChart: true,
tableUrl: '/dec-lygk-base-server/base/engWarning/GetKendoList'
},
{
name: '施工期环保措施落实预警',
key: 'ImplementEarlyWarning',
type: 'table',
hiddenChart: true,
tableUrl:
'/dec-lygk-base-server/base/engWarning/sgqhbss/GetKendoListCust'
},
{
name: '环保设施建设预警',
key: 'ConstructionEarlyWarning',
type: 'table',
hiddenChart: true,
tableUrl:
'/dec-lygk-base-server/base/engWarning/hbssjs/GetKendoListCust'
},
{
name: '环保设施运行预警',
key: 'RunEarlyWarning',
type: 'table',
hiddenChart: true,
tableUrl:
'/dec-lygk-base-server/base/engWarning/hbssyx/GetKendoListCust'
},
{
name: '鱼类放流预警',
key: 'ReleaseEarlyWarning',
type: 'table',
hiddenChart: true,
tableUrl: '/dec-lygk-base-server/base/engWarning/ylfl/GetKendoListCust'
}
]
},
{
name: '查看报告',
key: 'attachment',
type: 'attachment'
},
{
name: '批复文件',
key: 'approval',
type: 'approval'
}
// {
// name: "特征曲线",
// key: "characteristicCurve",
// type: "characteristicCurve"
// }
].filter(Boolean);
// // 水电站生态流量 √
// const ENGEQTabs: Array<any> = [
// {
@ -1076,15 +1079,15 @@
// // }
// // ]
// //其他配置
// const CommonTabs: any = [
// {
// name: '基础信息',
// key: 'basicInfo',
// type: 'basic',
// url: '/bbi/siteBipc/getSiteBasicInfo'
// }
// ]
// 其他配置
const CommonTabs: any = [
{
name: '基础信息',
key: 'basicInfo',
type: 'basic',
url: '/bbi/siteBipc/getSiteBasicInfo'
}
];
// // 气象站
// const WeatherTabs: any = [
@ -1139,157 +1142,158 @@
// ]
// //mway : 1-是人工 2-是自动 dtinType: 0-自建 1-国家 2-人工
// const handleTabs = (modaldata: any) => {
// console.log('modaldata', modaldata)
// if (!modaldata?.sttp) return
// let sttp = modaldata?.sttp ? modaldata?.sttp.toUpperCase() : ''
// switch (sttp) {
// case 'ENG':
// if (modaldata?.eqtp == 'QEC') {
// const { page } = Utility.parseQueryString()
// if (page == 'shengTaiLiuLiangManZuQingKuangJiangJu') {
// return ENGEQTabsJuangJu
// } else {
// return ENGEQTabs
// }
// } else {
// return Session.getAppCode() === 'hbb' ? ENGTabs.filter((e) => e.name !== '阶段属性') : ENGTabs
// }
// case 'ENG_ALARM':
// return DZGJ
// case 'EQ':
// return EQTabs
// case 'DW':
// case 'DW_1':
// case 'DW_2':
// case 'DW_3':
// case 'DW_4':
// case 'DW_5':
// return DWTabs
// case 'FP': //todo后续再删除多余代码
// case 'FP_1': //todo后续再删除多余代码
// // case "FP_2": //todo后续再删除多余代码
// case 'FP_3': //todo后续再删除多余代码
// case 'FP_4': //todo后续再删除多余代码
// // case "FP_5": //todo后续再删除多余代码
// if (Session.getAppCode() === 'hbb') {
// // mway为2的时侯显示在线监测数据
// if (modaldata.bldsttCcode == '1' || modaldata.bldsttCcode == '0' || modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return [
// ...FPTabs1 //建设情况
// ]
// } else {
// if (modaldata.mway != 2) {
// return FPTabs.filter((item) => item.name !== '在线监测数据')
// } else {
// return [
// ...FPTabs //有监测数据
// ]
// }
// }
// } else {
// if (modaldata.bldsttCcode == '1' || modaldata.bldsttCcode == '0' || modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return [
// ...FPTabs1
// // {
// // name: "系统运行记录",
// // key: "FishSystemRunState", //Normal1
// // type: "FishSystemRunState", //Normal
// // tabs: []
// // }
// ]
// } else {
// return [
// ...FPTabs
// // {
// // name: "系统运行记录",
// // key: "FishSystemRunState", //Normal1
// // type: "FishSystemRunState", //Normal
// // tabs: []
// // }
// ]
// }
// }
// case 'FP_2': //todo后续再删除多余代码
// case 'FP_5': //todo后续再删除多余代码
// if (modaldata.bldsttCcode == '1' || modaldata.bldsttCcode == '0' || modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return FPTabs1
// } else {
// return FPTabs
// }
// case 'ZQ':
// return ZQTabs
// case 'FB':
// if (modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return FBTabs1
// } else {
// return FBTabs
// }
// case 'FH':
// return FHTabs
// case 'VD_FB':
// case 'VD_FP':
// case 'VD_SG':
// case 'VD_VP':
// case 'VD_EQ':
// case 'VD_DW':
// case 'VD_FH':
// case 'VD_OTWE':
// case 'VD_OTTE':
// case 'VD_FBP':
// case 'VD_FC':
// case 'VD_WQ':
// case 'VD_TE':
// case 'VD_WE':
// case 'VD_EQS':
// case 'VD_WT':
// case 'VD_FBFM':
// case 'VD_FBI':
// case 'VD_PR':
// case 'VD_FPB':
// case 'VD_GZFC':
// case 'VD_FPC':
// case 'VD_VA':
// case 'VD':
// return videoTabs
// case 'VD_SN':
// case 'VD_WVD':
// return noLiveVideoTabs
// case 'VP':
// return VPTabs
// case 'VA':
// return VATabs
// case 'WQFB':
// return WQFBTabs
// case 'WQ':
// const _tabs = [...WQTabs]
// if (modaldata?.dtinType == 2 || modaldata?.dtinType == 1) _tabs.splice(2, 1)//国家站 人工站把视频tab去除
// return _tabs
// case 'WQDTA':
// return WQDTATabs
// case 'LL':
// return FLOWTabs
// case 'WT':
// case 'WTRV':
// if (modaldata.enfc == '1') {
// return WTTabs1
// } else {
// return WTTabs
// }
// case 'WE':
// return WETabs
// case 'MM':
// return WeatherTabs
// case 'WARN':
// return WaterQualityMonitoring
// case 'AI':
// return AIPrediction
// case 'AI_Basic':
// return AIbasic
// default:
// return CommonTabs
// }
// }
const handleTabs = (modaldata: any) => {
console.log('modaldata', modaldata);
if (!modaldata?.sttp) return;
let sttp = modaldata?.sttp ? modaldata?.sttp.toUpperCase() : '';
switch (sttp) {
case 'ENG':
return ENGTabs;
// if (modaldata?.eqtp == 'QEC') {
// const { page } = Utility.parseQueryString()
// if (page == 'shengTaiLiuLiangManZuQingKuangJiangJu') {
// return ENGEQTabsJuangJu
// } else {
// return ENGEQTabs
// }
// } else {
// return Session.getAppCode() === 'hbb' ? ENGTabs.filter((e) => e.name !== '阶段属性') : ENGTabs
// }
// case 'ENG_ALARM':
// return DZGJ
// case 'EQ':
// return EQTabs
// case 'DW':
// case 'DW_1':
// case 'DW_2':
// case 'DW_3':
// case 'DW_4':
// case 'DW_5':
// return DWTabs
// case 'FP': //todo后续再删除多余代码
// case 'FP_1': //todo后续再删除多余代码
// // case "FP_2": //todo后续再删除多余代码
// case 'FP_3': //todo后续再删除多余代码
// case 'FP_4': //todo后续再删除多余代码
// // case "FP_5": //todo后续再删除多余代码
// if (Session.getAppCode() === 'hbb') {
// // mway为2的时侯显示在线监测数据
// if (modaldata.bldsttCcode == '1' || modaldata.bldsttCcode == '0' || modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return [
// ...FPTabs1 //建设情况
// ]
// } else {
// if (modaldata.mway != 2) {
// return FPTabs.filter((item) => item.name !== '在线监测数据')
// } else {
// return [
// ...FPTabs //有监测数据
// ]
// }
// }
// } else {
// if (modaldata.bldsttCcode == '1' || modaldata.bldsttCcode == '0' || modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return [
// ...FPTabs1
// // {
// // name: "系统运行记录",
// // key: "FishSystemRunState", //Normal1
// // type: "FishSystemRunState", //Normal
// // tabs: []
// // }
// ]
// } else {
// return [
// ...FPTabs
// // {
// // name: "系统运行记录",
// // key: "FishSystemRunState", //Normal1
// // type: "FishSystemRunState", //Normal
// // tabs: []
// // }
// ]
// }
// }
// case 'FP_2': //todo后续再删除多余代码
// case 'FP_5': //todo后续再删除多余代码
// if (modaldata.bldsttCcode == '1' || modaldata.bldsttCcode == '0' || modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return FPTabs1
// } else {
// return FPTabs
// }
// case 'ZQ':
// return ZQTabs
// case 'FB':
// if (modaldata.bldstt == '1' || modaldata.bldstt == '0') {
// return FBTabs1
// } else {
// return FBTabs
// }
// case 'FH':
// return FHTabs
// case 'VD_FB':
// case 'VD_FP':
// case 'VD_SG':
// case 'VD_VP':
// case 'VD_EQ':
// case 'VD_DW':
// case 'VD_FH':
// case 'VD_OTWE':
// case 'VD_OTTE':
// case 'VD_FBP':
// case 'VD_FC':
// case 'VD_WQ':
// case 'VD_TE':
// case 'VD_WE':
// case 'VD_EQS':
// case 'VD_WT':
// case 'VD_FBFM':
// case 'VD_FBI':
// case 'VD_PR':
// case 'VD_FPB':
// case 'VD_GZFC':
// case 'VD_FPC':
// case 'VD_VA':
// case 'VD':
// return videoTabs
// case 'VD_SN':
// case 'VD_WVD':
// return noLiveVideoTabs
// case 'VP':
// return VPTabs
// case 'VA':
// return VATabs
// case 'WQFB':
// return WQFBTabs
// case 'WQ':
// const _tabs = [...WQTabs]
// if (modaldata?.dtinType == 2 || modaldata?.dtinType == 1) _tabs.splice(2, 1)//国家站 人工站把视频tab去除
// return _tabs
// case 'WQDTA':
// return WQDTATabs
// case 'LL':
// return FLOWTabs
// case 'WT':
// case 'WTRV':
// if (modaldata.enfc == '1') {
// return WTTabs1
// } else {
// return WTTabs
// }
// case 'WE':
// return WETabs
// case 'MM':
// return WeatherTabs
// case 'WARN':
// return WaterQualityMonitoring
// case 'AI':
// return AIPrediction
// case 'AI_Basic':
// return AIbasic
default:
return CommonTabs;
}
};
// const modalTabSetting = {
// footer: false,
@ -1309,4 +1313,21 @@
// className: 'map-tabs'
// }
// export { ENGTabs, DWTabs, WQTabs, FLOWTabs, EQTabs, FBTabs, WETabs, FPTabs, VPTabs, FHTabs, WTTabs, VATabs, WTTabs1, handleTabs, modalTabSetting, modalTabSettingLy }
export {
ENGTabs,
// DWTabs,
// WQTabs,
// FLOWTabs,
// EQTabs,
// FBTabs,
// WETabs,
// FPTabs,
// VPTabs,
// FHTabs,
// WTTabs,
// VATabs,
// WTTabs1,
handleTabs
// modalTabSetting,
// modalTabSettingLy
};

View File

@ -0,0 +1,315 @@
<template>
<!-- 媒体预览 Modal -->
<a-modal
:open="visible"
:title="videoPreviewTitle"
:footer="null"
width="900px"
@cancel="closeMediaPreview"
:zIndex="2000"
>
<div class="flex h-[60vh] gap-4">
<!-- 左侧混合列表 (图片+视频) -->
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
<div
v-for="(item, index) in previewList"
:key="index"
class="mb-2 cursor-pointer list-item"
:class="{ select: index == currentMediaIndex }"
@click="currentMediaIndex = index"
>
<span class="file-name">{{ item.name }}</span>
<!-- 删除按钮 -->
<div
class="list-item-delete"
v-if="isDel"
@click.stop="handleDeleteMedia(item, index)"
>
<CloseCircleOutlined />
</div>
</div>
</div>
<!-- 右侧动态预览区域 -->
<div
class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4"
>
<a-spin v-if="imageLoading" tip="图片加载中..." size="large" />
<!-- 图片预览 -->
<div
v-if="
currentMediaItem &&
currentMediaItem.url != '' &&
currentMediaItem.type === 'image'
"
>
<img
v-show="!imageLoading && !imageLoadError"
:src="currentMediaItem.url"
alt="preview"
class="max-w-full max-h-[50vh] object-contain"
@load="onImageLoad"
@error="onImageError"
/>
<!-- 3. 加载失败提示 -->
<div
v-if="!imageLoading && imageLoadError"
class="text-gray-400 flex flex-col items-center"
>
<ExclamationCircleOutlined style="font-size: 24px; margin-bottom: 8px" />
<span>图片加载失败</span>
</div>
</div>
<div
v-else-if="
currentMediaItem &&
currentMediaItem.url == '' &&
currentMediaItem.type === 'image'
"
class="text-gray-400"
>
暂无图片预览
</div>
<!-- 视频预览 -->
<video
v-else-if="
currentMediaItem &&
currentMediaItem.url != '' &&
currentMediaItem.type === 'video'
"
:src="currentMediaItem.url"
controls
autoplay
class="max-w-full max-h-[50vh]"
>
您的浏览器不支持视频播放
</video>
<div
v-else-if="
currentMediaItem &&
currentMediaItem.url == '' &&
currentMediaItem.type === 'video'
"
class="text-gray-400"
>
暂无视频预览
</div>
<!-- 底部切换控制 -->
<div class="absolute bottom-4 flex gap-4 z-10">
<a-button
shape="circle"
:icon="h(LeftOutlined)"
@click="prevMedia"
:disabled="currentMediaIndex === 0"
/>
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
</span>
<a-button
shape="circle"
:icon="h(RightOutlined)"
@click="nextMedia"
:disabled="currentMediaIndex === previewList.length - 1"
/>
</div>
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref, nextTick, computed, watch, h } from "vue";
import { message, Modal } from "ant-design-vue"; // 使 ant-design-vue
import {
LeftOutlined,
RightOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined,
} from "@ant-design/icons-vue";
import { deleteFile } from "@/api/guoYuSheShiShuJuTianBao";
//
const imageLoading = ref(false);
const imageLoadError = ref(false);
interface MediaItem {
id: string;
type: "image" | "video";
name: string;
url: string;
}
const props = defineProps<{
visible: boolean;
videoPreviewTitle: string;
previewList: MediaItem[];
previewListIndex: number;
taskId?: string;
isDel?: boolean;
}>();
const emit = defineEmits(["update:visible", "deleteMedia"]);
const currentMediaIndex = ref(props.previewListIndex);
//
const currentMediaItem = computed(() => props.previewList[currentMediaIndex.value]);
const closeMediaPreview = () => {
emit("update:visible", false);
nextTick(() => {
currentMediaIndex.value = 0;
});
};
//
const prevMedia = () => {
if (currentMediaIndex.value > 0) {
currentMediaIndex.value--;
}
};
//
const nextMedia = () => {
if (currentMediaIndex.value < props.previewList.length - 1) {
currentMediaIndex.value++;
}
};
//
const handleDeleteMedia = (item: any, index: number) => {
Modal.confirm({
title: "确认删除",
content: "确定要从预览列表中移除该项吗?",
zIndex: 2002,
onOk: async () => {
return new Promise(async (resolve, reject) => {
const params = {
id: item.id,
taskId: props.taskId,
type:
props.videoPreviewTitle == "图片预览"
? 1
: props.videoPreviewTitle == "视频预览"
? 2
: 0,
filename: item.name,
};
try {
let res: any = await deleteFile(params);
if (res && res?.code == 0) {
message.success("删除成功");
emit("deleteMedia", item, props.videoPreviewTitle, index);
if (props.previewList.length === 0) {
closeMediaPreview();
} else {
//
if (index <= currentMediaIndex.value) {
currentMediaIndex.value = Math.max(0, currentMediaIndex.value - 1);
}
}
resolve(true);
} else {
message.error("删除失败");
reject();
}
} catch (e) {
message.error("删除失败");
reject();
}
}).catch(() => console.log("Oops errors!"));
},
});
};
//
const onImageLoad = () => {
imageLoading.value = false;
imageLoadError.value = false;
};
//
const onImageError = () => {
imageLoading.value = false;
imageLoadError.value = true;
};
watch(
() => props.previewListIndex,
(newIndex) => {
currentMediaIndex.value = newIndex;
}
);
watch(
() => props.visible,
(newVisible) => {
if (newVisible) {
if (props.videoPreviewTitle == "图片预览") {
imageLoading.value = true;
imageLoadError.value = false;
}
}
}
);
watch(currentMediaIndex, (newIndex) => {
const item = props.previewList[newIndex];
if (item && item.type === "image") {
imageLoading.value = true;
imageLoadError.value = false;
} else {
imageLoading.value = false;
imageLoadError.value = false;
}
});
</script>
<style scoped lang="scss">
.media-list-container {
background-color: rgb(234, 241, 251);
padding: 10px;
.list-item {
width: 100%;
height: 36px;
line-height: 36px;
padding: 0 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background-color: rgba(255, 255, 255, 0.5);
}
.file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
&.select {
color: #ffffff;
background-color: rgb(37, 93, 138);
}
.list-item-delete {
color: red;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
margin-left: 8px;
&:hover {
transform: scale(1.1);
}
}
&:hover .list-item-delete {
opacity: 1;
}
}
}
</style>

View File

@ -2,8 +2,12 @@
import { computed } from "vue";
import { useTagsViewStore } from "@/store/modules/tagsView";
import { useRoute } from "vue-router";
import MapModal from "@/components/MapModal/index.vue";
import { useModelStore } from "@/store/modules/model";
// import GisView from "@/components/gis/GisView.vue";
const modelStore = useModelStore();
const tagsViewStore = useTagsViewStore();
const router = useRoute();
@ -22,6 +26,13 @@ const routeKey = computed(() => router.path + Math.random());
</transition>
</router-view>
</div>
<MapModal
v-model:visible="modelStore.modalVisible"
v-model:active-key="modelStore.currentTabKey"
:title="modelStore.title"
:tabs-config="modelStore.tabList"
/>
</section>
</template>

View File

@ -3,12 +3,23 @@ import { ref } from 'vue';
export const useModelStore = defineStore('model', () => {
// state
const info = ref<{
type: string;
const modalVisible = ref(false);
const currentTabKey = ref("basicInfo");
const tabList = ref([]);
const title = ref('');
const isBasicEdit = ref(false);
const params = ref<{
sttp: string;
}>({
type: 'zh'
sttp: 'eng'
});
return {
info,
params,
modalVisible,
currentTabKey,
tabList,
title,
isBasicEdit,
};
});

View File

@ -4,10 +4,9 @@ import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
import RightDrawer from "@/components/RightDrawer/index.vue";
import jidiInfoMod from "@/modules/jidiInfoMod/index.vue";
import shuidianhuangjingjieruMod from "@/modules/shuidianhuangjingjieruMod/index.vue";
import MapModal from "@/components/MapModal/index.vue";
import { useModelStore } from "@/store/modules/model";
const modelInfo = useModelStore();
const modelStore = useModelStore();
// import { getQgcStaticData } from "@/api/ecoFlow";
onMounted(() => {
// const params = {
@ -26,29 +25,23 @@ onMounted(() => {
// });
});
const modalVisible = ref(false);
const currentTabKey = ref("basicInfo");
const projectData = ref({ id: 1, name: "测试项目" });
const tabList = [
{ key: "basicInfo", title: "基础信息", url: "" },
{ key: "mapView", title: "地图视图", url: "" },
{ key: "surrounding", title: "周边配套", url: "" },
];
const showMapModal = () => {
modalVisible.value = true;
currentTabKey.value = "basicInfo";
modelInfo.info.type = "eng";
modelStore.modalVisible = true;
modelStore.params.sttp = "ENG";
modelStore.title = "三峡 详情信息";
modelStore.currentTabKey = "basicInfo";
modelStore.isBasicEdit = true;
};
const showMapModal1 = () => {
modalVisible.value = true;
currentTabKey.value = "basicInfo";
modelInfo.info.type = "zh";
};
const onTabChange = (key: string) => {
console.log("Tab 切换为:", key);
modelStore.modalVisible = true;
modelStore.params.sttp = "ENG";
modelStore.title = "三峡222 详情信息";
modelStore.currentTabKey = "basicInfo";
modelStore.isBasicEdit = false;
// modelStore.modalVisible = true;
// modelStore.params.sttp = "zh";
// modelStore.title = '222 ';
// modelStore.currentTabKey = "mapView";
};
</script>
@ -60,16 +53,8 @@ const onTabChange = (key: string) => {
<div class="rightContent">
<RightDrawer>
<!-- <a-button @click="showMapModal">打开电站地图弹窗</a-button>
<a-button @click="showMapModal1">打开地图弹窗1</a-button>
<a-button @click="showMapModal1">打开地图弹窗1</a-button> -->
<MapModal
v-model:visible="modalVisible"
v-model:active-key="currentTabKey"
title="三峡 详情信息"
:tabs-config="tabList"
:data="projectData"
@change="onTabChange"
/> -->
<jidiInfoMod />
<shuidianhuangjingjieruMod />
</RightDrawer>

View File

@ -1,90 +1,67 @@
<template>
<div class="guoYuSheShiShuJuHistory-page">
<GuoYuSheShiShuJuHistorySearch ref="searchRef" class="search-container" :guoyuStatus="guoyuStatus"
:direction="direction" @reset="handleReset" @search-finish="handleSearchFinish" />
<GuoYuSheShiShuJuHistorySearch
ref="searchRef"
class="search-container"
:guoyuStatus="guoyuStatus"
:direction="direction"
@reset="handleReset"
@search-finish="handleSearchFinish"
/>
<div class="table-container" ref="tableContainerRef">
<BasicTable ref="tableRef" :columns="columns" :scroll-y="tableScrollY" :list-url="getFishDraftPage"
:search-params="{}" :transform-data="customTransform">
<BasicTable
ref="tableRef"
:columns="columns"
:scroll-y="tableScrollY"
:list-url="getFishDraftPage"
:search-params="{}"
:transform-data="customTransform"
>
<template #bodyCell="{ column, record }">
<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>
</BasicTable>
</div>
<!-- 预览媒体组件 -->
<PreviewMedia
ref="previewMediaRef"
v-model:visible="mediaPreviewVisible"
:previewList="previewList"
:previewListIndex="previewListIndex"
:videoPreviewTitle="videoPreviewTitle"
/>
<a-modal v-model:open="mediaPreviewVisible" :title="videoPreviewTitle" :footer="null" width="900px"
@cancel="closeMediaPreview" :zIndex="2000">
<div class="flex h-[60vh] gap-4">
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
<div v-for="(item, index) in previewList" :key="index" class="mb-2 cursor-pointer list-item"
:class="{ select: index === currentMediaIndex }" @click="currentMediaIndex = index">
<span class="file-name">{{ item.name }}</span>
</div>
</div>
<div class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4">
<a-image v-if="
currentMediaItem &&
currentMediaItem.url != '' &&
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
" :src="currentMediaItem.url" class="max-w-full max-h-full object-contain" />
<div v-else-if="
currentMediaItem &&
currentMediaItem.url == '' &&
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
" class="text-gray-400">
暂无图片预览
</div>
<video v-else-if="
currentMediaItem &&
currentMediaItem.url != '' &&
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
" :src="currentMediaItem.url" controls autoplay class="max-w-full max-h-[50vh]">
您的浏览器不支持视频播放
</video>
<div v-else-if="
currentMediaItem &&
currentMediaItem.url == '' &&
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
" class="text-gray-400">
暂无视频预览
</div>
<div class="absolute bottom-4 flex gap-4 z-10">
<a-button shape="circle" :icon="h(LeftOutlined)" @click="prevMedia" :disabled="currentMediaIndex === 0" />
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
</span>
<a-button shape="circle" :icon="h(RightOutlined)" @click="nextMedia"
:disabled="currentMediaIndex === previewList.length - 1" />
</div>
</div>
</div>
</a-modal>
<EditModal ref="editModalRef" v-model:visible="editModalVisible" :is-view="true" :direction="direction"
:initial-values="currentRecord" @cancel="editModalCancel" @preview-click="handlePreviewClick" />
<EditModal
ref="editModalRef"
v-model:visible="editModalVisible"
:is-view="true"
:direction="direction"
:initial-values="currentRecord"
@cancel="editModalCancel"
@preview-click="handlePreviewClick"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, h, nextTick } from "vue";
import { message } from "ant-design-vue";
import { LeftOutlined, RightOutlined } from "@ant-design/icons-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 baseUrlPreview = import.meta.env.VITE_APP_BASE_URL;
interface FormData {
[key: string]: any;
@ -100,8 +77,6 @@ interface ColumnConfig {
}
const tableRef = ref<any>(null);
const searchRef = ref<any>(null);
const editModalRef = ref<any>(null);
const direction = ref<any>([]);
const guoyuStatus = ref<any>([]);
@ -174,17 +149,13 @@ const videoPreviewTitle = ref("视频预览");
interface MediaItem {
id: string;
type: "image" | "video" | "formVideo" | "formImage";
type: "image" | "video";
name: string;
url: string;
}
const previewList = ref<MediaItem[]>([]);
const currentMediaIndex = ref(0);
const tablePreviewRecord = ref<any>({});
const currentMediaItem = computed(() => previewList.value[currentMediaIndex.value]);
const previewListIndex = ref(0);
const columns = computed(() => {
return [
...baseColumnsConfig.map((col) => {
@ -231,59 +202,23 @@ const editModalCancel = () => {
const handlePreviewClick = (record: any, type: string, index: number) => {
const mixedList: MediaItem[] = [];
if (type === "image") {
tablePreviewRecord.value = record;
videoPreviewTitle.value = "图片预览";
const nameList = record.picpthList;
nameList.forEach((item: any) => {
mixedList.push({
id: record.id,
type: "image",
name: item.name,
url:
baseUrlPreview +
"/data/fishDraft/previewFile?filename=" +
item.name +
"&taskId=" +
"" +
"&type=1",
});
});
} else if (type === "video") {
tablePreviewRecord.value = record;
videoPreviewTitle.value = "视频预览";
const nameList = record.vdpthList;
nameList.forEach((item: any) => {
mixedList.push({
id: record.id,
type: "video",
name: item.name,
url:
baseUrlPreview +
"/data/fishDraft/previewFile?filename=" +
item.name +
"&taskId=" +
"" +
"&type=2",
});
});
} else if (type === "formImage") {
videoPreviewTitle.value = "图片预览";
const nameList = JSON.parse(JSON.stringify(record)).picpthList;
nameList.forEach((item: any) => {
mixedList.push({
id: record.id,
type: "formImage",
type: "image",
name: item.name,
url: item.url,
});
});
} else if (type === "formVideo") {
} else if (type === "video") {
videoPreviewTitle.value = "视频预览";
const nameList = JSON.parse(JSON.stringify(record)).vdpthList;
nameList.forEach((item: any) => {
mixedList.push({
id: record.id,
type: "formVideo",
type: "video",
name: item.name,
url: item.url,
});
@ -291,32 +226,12 @@ const handlePreviewClick = (record: any, type: string, index: number) => {
}
mediaPreviewVisible.value = true;
currentMediaIndex.value = index;
previewListIndex.value = index;
nextTick(() => {
previewList.value = mixedList;
});
};
const prevMedia = () => {
if (currentMediaIndex.value > 0) {
currentMediaIndex.value--;
}
};
const nextMedia = () => {
if (currentMediaIndex.value < previewList.value.length - 1) {
currentMediaIndex.value++;
}
};
const closeMediaPreview = () => {
mediaPreviewVisible.value = false;
nextTick(() => {
previewList.value = [];
currentMediaIndex.value = 0;
});
};
const customTransform = (res: any) => {
const rawRecords = res?.data?.records || [];
const modifiedRecords = rawRecords.map((item: any) => {
@ -349,13 +264,13 @@ const handleSearchFinish = (values: any) => {
field: "strdt",
operator: "gte",
dataType: "date",
value: values.strdt[0] + ' 00:00:00',
value: values.strdt[0] + " 00:00:00",
},
{
field: "strdt",
operator: "lte",
dataType: "date",
value: values.strdt[1] + ' 23:59:59',
value: values.strdt[1] + " 23:59:59",
},
values.direction && {
field: "direction",
@ -367,7 +282,7 @@ const handleSearchFinish = (values: any) => {
field: "status",
operator: "eq",
dataType: "string",
value: 'APPROVED',
value: "APPROVED",
},
values.stcd && {
field: "stcd",
@ -456,39 +371,4 @@ onMounted(() => {
overflow: hidden;
position: relative;
}
.media-list-container {
background-color: rgb(234, 241, 251);
padding: 10px;
.list-item {
width: 100%;
height: 36px;
line-height: 36px;
padding: 0 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background-color: rgba(255, 255, 255, 0.5);
}
.file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
&.select {
color: #ffffff;
background-color: rgb(37, 93, 138);
}
}
}
</style>

View File

@ -18,7 +18,7 @@
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="流域" name="hbrvcd">
<a-form-item label="流域" name="hbrvcd">
<a-select
v-model:value="formData.hbrvcd"
:loading="hbrvcdLoading"
@ -413,7 +413,7 @@ 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): void;
(e: "preview-click", record: any, type: string, index: number, action: string): void;
}>();
//
@ -654,13 +654,12 @@ const initForm = () => {
//
const handleVideoPreview = () => {
emit("preview-click", { vdpthList: videoFileList.value }, "formVideo", 0);
emit("preview-click", { vdpthList: videoFileList.value }, "video", 0, "preview");
};
//
const handleImagePreview = async (file: any) => {
emit("preview-click", { picpthList: imageFileList.value }, "formImage", 0);
return Promise.reject();
emit("preview-click", { picpthList: imageFileList.value }, "image", 0, "preview");
};
//
const handleOk = async () => {
@ -809,7 +808,7 @@ watch(
if (newVisible) {
//
// getBaseDropdownSelect();//
getHbrvcdDropdownSelect();//
getHbrvcdDropdownSelect(); //
getEngInfoDropdownSelect(formData.hbrvcd);
getFpssDropdownSelect(formData.rstcd, formData.hbrvcd);
initForm();
@ -819,7 +818,7 @@ watch(
);
//
defineExpose({
localLoading
localLoading,
});
</script>

View File

@ -9,7 +9,7 @@
row-key="id"
>
<template #bodyCell="{ column, record, index }">
<!--
<!--
<template v-if="column.key === 'index' || column.dataIndex === 'index'">
{{ index + 1 }}
</template> -->
@ -144,14 +144,14 @@
</template>
<!-- 过鱼时间 -->
<template v-else-if="column.dataIndex === 'strdt'">
<template v-else-if="column.dataIndex === 'strdtStr'">
<a-date-picker
v-model:value="editingData.strdt"
v-model:value="editingData.strdtStr"
show-time
style="width: 100%"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
@change="(val) => delWarning(val, 'strdt')"
@change="(val) => delWarning(val, 'strdtStr')"
/>
</template>
@ -251,7 +251,7 @@
<div
class="text"
:class="{ text_warning: record.picpthsWarnings.includes(item.name) }"
@click="emit('preview-click', record, 'image', index)"
@click="emit('preview-click', record, 'image', index, 'delete')"
>
{{ item.name }}
</div>
@ -263,7 +263,7 @@
<div
class="text"
:class="{ text_warning: record.vdpthsWarnings.includes(item.name) }"
@click="emit('preview-click', record, 'video', index)"
@click="emit('preview-click', record, 'video', index, 'delete')"
>
{{ item.name }}
</div>
@ -307,7 +307,15 @@ const rowStates = reactive<Record<number, any>>({});
const editingData = ref<any>(null);
const modalColumns = ref([
{ dataIndex: "rowIndex", key: "rowIndex", title: "序号", width: 60, dataIndexKey: "rowIndex",align: "center",fixed: "left" },
{
dataIndex: "rowIndex",
key: "rowIndex",
title: "序号",
width: 60,
dataIndexKey: "rowIndex",
align: "center",
fixed: "left",
},
{
dataIndex: "hbrvnm",
key: "hbrvnm",
@ -330,9 +338,9 @@ const modalColumns = ref([
width: 150,
},
{
dataIndex: "strdt",
key: "strdt",
dataIndexKey: "strdt",
dataIndex: "strdtStr",
key: "strdtStr",
dataIndexKey: "strdtStr",
title: "过鱼时间",
width: 190,
},
@ -577,6 +585,10 @@ const startEdit = (index: number) => {
editingData.value.stcd = null;
delWarning(null, "stcd");
}
if (editingData.value.warnings.includes("strdt")) {
editingData.value.strdt = null;
delWarning(null, "strdt");
}
// 3. ( editingData )
if (editingData.value.hbrvcd == "" || editingData.value.hbrvcd == undefined) {
@ -652,6 +664,7 @@ const saveEdit = async (index: number) => {
// 2.
const newData = [...props.fileTableData];
newData[index] = { ...editingData.value };
newData[index].strdt = newData[index].strdtStr;
emit("update:fileLoading", true);
try {
const res: any = await revalidateAndUpdateRow({
@ -687,7 +700,7 @@ const handlePreviewDelete = async (index: number) => {
});
if (res && res?.code == 0) {
message.success("删除成功");
props.getFileList();
editingRowIndex.value = null;
editingData.value = null;

View File

@ -185,142 +185,28 @@
@ok="handleEditSubmit"
@preview-click="handlePreviewClick"
/>
<!-- 媒体预览 Modal -->
<a-modal
v-model:open="mediaPreviewVisible"
:title="videoPreviewTitle"
:footer="null"
width="900px"
@cancel="closeMediaPreview"
:zIndex="2000"
>
<div class="flex h-[60vh] gap-4">
<!-- 左侧混合列表 (图片+视频) -->
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
<div
v-for="(item, index) in previewList"
:key="index"
class="mb-2 cursor-pointer list-item"
:class="{ select: index === currentMediaIndex }"
@click="currentMediaIndex = index"
>
<span class="file-name">{{ item.name }}</span>
<!-- 删除按钮 -->
<div
class="list-item-delete"
v-if="item.type != 'formVideo' && item.type != 'formImage'"
@click.stop="handleDeleteMedia(item, index)"
>
<CloseCircleOutlined />
</div>
</div>
</div>
<!-- 右侧动态预览区域 -->
<div
class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4"
>
<a-spin v-if="imageLoading" tip="图片加载中..." size="large" />
<!-- 图片预览 -->
<div
v-if="
currentMediaItem &&
currentMediaItem.url != '' &&
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
"
>
<img
v-show="!imageLoading && !imageLoadError"
:src="currentMediaItem.url"
alt="preview"
class="max-w-full max-h-[50vh] object-contain"
@load="onImageLoad"
@error="onImageError"
/>
<!-- 3. 加载失败提示 -->
<div
v-if="!imageLoading && imageLoadError"
class="text-gray-400 flex flex-col items-center"
>
<ExclamationCircleOutlined style="font-size: 24px; margin-bottom: 8px" />
<span>图片加载失败</span>
</div>
</div>
<div
v-else-if="
currentMediaItem &&
currentMediaItem.url == '' &&
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
"
class="text-gray-400"
>
暂无图片预览
</div>
<!-- 视频预览 -->
<video
v-else-if="
currentMediaItem &&
currentMediaItem.url != '' &&
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
"
:src="currentMediaItem.url"
controls
autoplay
class="max-w-full max-h-[50vh]"
>
您的浏览器不支持视频播放
</video>
<div
v-else-if="
currentMediaItem &&
currentMediaItem.url == '' &&
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
"
class="text-gray-400"
>
暂无视频预览
</div>
<!-- 底部切换控制 -->
<div class="absolute bottom-4 flex gap-4 z-10">
<a-button
shape="circle"
:icon="h(LeftOutlined)"
@click="prevMedia"
:disabled="currentMediaIndex === 0"
/>
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
</span>
<a-button
shape="circle"
:icon="h(RightOutlined)"
@click="nextMedia"
:disabled="currentMediaIndex === previewList.length - 1"
/>
</div>
</div>
</div>
</a-modal>
<!-- 预览媒体组件 -->
<PreviewMedia
ref="previewMediaRef"
v-model:visible="mediaPreviewVisible"
:previewList="previewList"
:previewListIndex="previewListIndex"
:videoPreviewTitle="videoPreviewTitle"
:taskId="taskId"
:isDel="isPreviewMediaDelete"
@deleteMedia="handleDeleteMedia"
/>
</div>
</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 {
LeftOutlined,
RightOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined,
} from "@ant-design/icons-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,
@ -335,11 +221,11 @@ import {
checkImportStatus,
batchSaveDraft,
getLastImportResult,
deleteFile,
batchApproveAll,
} from "@/api/guoYuSheShiShuJuTianBao";
import { Tag } from "ant-design-vue"; // Tag
import { getDictItemsByCode } from "@/api/dict";
import { fr } from "element-plus/es/locale/index.mjs";
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
const baseUrlApi = import.meta.env.VITE_APP_BASE_API_URL;
@ -358,9 +244,6 @@ interface ColumnConfig {
}
const fileInputRef = ref<any>(null);
const tableRef = ref<any>(null);
//
const imageLoading = ref(false);
const imageLoadError = ref(false);
//
const direction = ref<any>([]);
const guoyuStatus = ref<any>([]);
@ -435,20 +318,18 @@ const isView = ref(false);
const currentRecord = ref<FormData | null>(null);
const mediaPreviewVisible = ref(false);
const isPreviewMediaDelete = ref(false);
const videoPreviewTitle = ref("视频预览");
interface MediaItem {
id: string;
type: "image" | "video" | "formVideo" | "formImage";
type: "image" | "video";
name: string;
url: string;
}
const previewList = ref<MediaItem[]>([]);
const currentMediaIndex = ref(0);
const previewListIndex = ref(0);
const tablePreviewRecord = ref<any>({});
//
const currentMediaItem = computed(() => previewList.value[currentMediaIndex.value]);
//
const fileTableData = ref<any[]>([]);
const batchData = ref<any[]>([]);
@ -810,6 +691,9 @@ const importBtn = async () => {
if (visible.value) importBtn();
}, 5000);
}
} else if (currentTask.status == "FAILED") {
message.error(currentTask.statusText);
fileLoading.value = false;
} else if (currentTask.status == "VALIDATED") {
nextTick(async () => {
modalTableRef.value.editingRowIndex = null;
@ -839,13 +723,6 @@ const fileChange = (file: File) => {
const formData = new FormData();
formData.append("file", file);
await importFishZip(formData);
try {
setTimeout(() => {
if (visible.value) importBtn();
}, 5000);
} catch (error) {
message.error("导入失败");
}
});
};
//
@ -994,167 +871,95 @@ const handleSearchFinish = (values: any) => {
tableRef.value?.getList(filter);
};
// ()
const handlePreviewClick = (record: any, type: string, index: number) => {
const handlePreviewClick = (record: any, type: string, index: number, action: string) => {
const mixedList: MediaItem[] = [];
if (type === "image") {
imageLoading.value = true;
imageLoadError.value = false;
if (action === "delete") {
isPreviewMediaDelete.value = true;
tablePreviewRecord.value = record;
} else if (action === "preview") {
isPreviewMediaDelete.value = false;
}
if (type === "image") {
videoPreviewTitle.value = "图片预览";
const nameList = record.picpthList;
nameList.forEach((item: any) => {
let url = "";
if (action === "preview") {
url = item.url;
} else if (action === "delete") {
url =
baseUrlApi +
"/data/fishDraft/previewFile?filename=" +
item.name +
"&taskId=" +
taskId.value +
"&type=1";
}
mixedList.push({
id: record.id,
type: "image",
name: item.name,
url:
url: url,
});
});
} else if (type === "video") {
videoPreviewTitle.value = "视频预览";
const nameList = record.vdpthList;
nameList.forEach((item: any) => {
let url = "";
if (action === "preview") {
url = item.url;
} else if (action === "delete") {
url =
baseUrlApi +
"/data/fishDraft/previewFile?filename=" +
item.name +
"&taskId=" +
taskId.value +
"&type=1",
});
});
} else if (type === "video") {
tablePreviewRecord.value = record;
videoPreviewTitle.value = "视频预览";
const nameList = record.vdpthList;
nameList.forEach((item: any) => {
"&type=2";
}
mixedList.push({
id: record.id,
type: "video",
name: item.name, //
url:
baseUrlApi +
"/data/fishDraft/previewFile?filename=" +
item.name +
"&taskId=" +
taskId.value +
"&type=2",
});
});
} else if (type === "formImage") {
imageLoading.value = true;
imageLoadError.value = false;
videoPreviewTitle.value = "图片预览";
const nameList = JSON.parse(JSON.stringify(record)).picpthList;
nameList.forEach((item: any) => {
mixedList.push({
id: record.id,
type: "formImage",
name: item.name, //
url: item.url,
});
});
} else if (type === "formVideo") {
videoPreviewTitle.value = "视频预览";
const nameList = JSON.parse(JSON.stringify(record)).vdpthList;
nameList.forEach((item: any) => {
mixedList.push({
id: record.id,
type: "formVideo",
name: item.name, //
url: item.url,
url: url,
});
});
}
mediaPreviewVisible.value = true;
currentMediaIndex.value = index;
previewListIndex.value = index;
nextTick(() => {
console.log(mixedList);
previewList.value = mixedList;
});
};
//
const prevMedia = () => {
if (currentMediaIndex.value > 0) {
currentMediaIndex.value--;
const handleDeleteMedia = (item: any, type: string, index: number) => {
previewList.value.splice(index, 1);
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(
(warningName: any) => warningName !== item.name
);
}
} else {
if (previewList.value.length == 0) {
tablePreviewRecord.value.vdpthList = [];
tablePreviewRecord.value.vdpthsWarnings = [];
} else {
tablePreviewRecord.value.vdpthList.splice(index, 1);
tablePreviewRecord.value.vdpthsWarnings = tablePreviewRecord.value.vdpthsWarnings.filter(
(warningName: any) => warningName !== item.name
);
}
}
};
//
const nextMedia = () => {
if (currentMediaIndex.value < previewList.value.length - 1) {
currentMediaIndex.value++;
}
};
//
const handleDeleteMedia = (item: any, index: number) => {
Modal.confirm({
title: "确认删除",
content: "确定要从预览列表中移除该项吗?",
zIndex: 2002,
onOk: async () => {
return new Promise(async (resolve, reject) => {
const params = {
id: item.id,
taskId: taskId.value,
type:
videoPreviewTitle.value == "图片预览"
? 1
: videoPreviewTitle.value == "视频预览"
? 2
: 0,
filename: item.name,
};
try {
let res: any = await deleteFile(params);
if (res && res?.code == 0) {
message.success("删除成功");
previewList.value.splice(index, 1);
if (videoPreviewTitle.value == "图片预览") {
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(
(warningName: any) => warningName !== item.name
);
}
} else {
if (previewList.value.length == 0) {
tablePreviewRecord.value.vdpthList = [];
tablePreviewRecord.value.vdpthsWarnings = [];
} else {
tablePreviewRecord.value.vdpthList.splice(index, 1);
tablePreviewRecord.value.vdpthsWarnings = tablePreviewRecord.value.vdpthsWarnings.filter(
(warningName: any) => warningName !== item.name
);
}
}
if (previewList.value.length === 0) {
mediaPreviewVisible.value = false;
} else {
//
if (index <= currentMediaIndex.value) {
currentMediaIndex.value = Math.max(0, currentMediaIndex.value - 1);
}
}
resolve(true);
} else {
message.error("删除失败");
reject();
}
} catch (e) {
message.error("删除失败");
reject();
}
}).catch(() => console.log("Oops errors!"));
},
});
};
const closeMediaPreview = () => {
mediaPreviewVisible.value = false;
nextTick(() => {
previewList.value = [];
currentMediaIndex.value = 0;
});
};
const tableContainerRef = ref<HTMLElement | null>(null);
const tableScrollY = ref<number | undefined>(undefined);
//
@ -1174,28 +979,6 @@ const observer = new ResizeObserver(() => {
calcTableScrollY();
});
//
const onImageLoad = () => {
imageLoading.value = false;
imageLoadError.value = false;
};
//
const onImageError = () => {
imageLoading.value = false;
imageLoadError.value = true;
};
watch(currentMediaIndex, (newIndex) => {
const item = previewList.value[newIndex];
if (item && (item.type === "image" || item.type === "formImage")) {
imageLoading.value = true;
imageLoadError.value = false;
} else {
imageLoading.value = false;
imageLoadError.value = false;
}
});
// --- ---
onMounted(() => {
nextTick(() => {
@ -1248,57 +1031,6 @@ onMounted(() => {
/* 为绝对定位元素提供参考(如果需要) */
}
.media-list-container {
background-color: rgb(234, 241, 251);
padding: 10px;
.list-item {
width: 100%;
height: 36px;
line-height: 36px;
padding: 0 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background-color: rgba(255, 255, 255, 0.5);
}
.file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
&.select {
color: #ffffff;
background-color: rgb(37, 93, 138);
}
.list-item-delete {
color: red;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
margin-left: 8px;
&:hover {
transform: scale(1.1);
}
}
&:hover .list-item-delete {
opacity: 1;
}
}
}
.buttonStyle {
width: 100%;
display: flex;

View File

@ -1,7 +1,14 @@
<template>
<div class="approval-detail-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" />
</template>
@ -62,10 +69,10 @@ const searchList: any = computed(() => [
type: "RangePicker",
name: "strdt",
label: "过鱼时间",
picker: "month",
picker: "date",
fieldProps: {
format: "YYYY-MM",
valueFormat: "YYYY-MM",
format: "YYYY-MM-DD",
valueFormat: "YYYY-MM-DD",
allowClear: false,
},
presets: DateSetting.RangeButton.month,

File diff suppressed because it is too large Load Diff

View File

@ -1,116 +1,121 @@
<template>
<div class="guoYuSheShiShuJuTianBao-search">
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'zhu'"
@finish="onSearchFinish" @values-change="onValuesChange" @reset="handleReset">
<template #actions>
<a-tooltip title="批量审批">
<a-button :disabled="selectedCount === 0" @click="$emit('batch-approve')">
<template #icon>
<CheckSquareOutlined />
</template>
批量审批
</a-button>
</a-tooltip>
<a-tooltip title="批量驳回">
<a-button :disabled="selectedCount === 0" @click="$emit('batch-reject')">
<template #icon>
<CloseCircleOutlined />
</template>
批量驳回
</a-button>
</a-tooltip>
<div class="guoYuSheShiShuJuTianBao-search">
<BasicSearch
ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
:zhujianfujian="'zhu'"
@finish="onSearchFinish"
@values-change="onValuesChange"
@reset="handleReset"
>
<template #actions>
<a-tooltip title="批量审批">
<a-button :disabled="selectedCount === 0" @click="$emit('batch-approve')">
<template #icon>
<CheckSquareOutlined />
</template>
</BasicSearch>
</div>
批量审批
</a-button>
</a-tooltip>
<a-tooltip title="批量驳回">
<a-button :disabled="selectedCount === 0" @click="$emit('batch-reject')">
<template #icon>
<CloseCircleOutlined />
</template>
批量驳回
</a-button>
</a-tooltip>
<a-tooltip title="批量删除">
<a-button :disabled="selectedCount === 0" @click="$emit('batch-delete')">
批量删除
</a-button>
</a-tooltip>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import BasicSearch from "@/components/BasicSearch/index.vue"; //
import { getDictItemsByCode } from '@/api/dict';
import {
CheckSquareOutlined,
CloseCircleOutlined,
} from "@ant-design/icons-vue";
import { getDictItemsByCode } from "@/api/dict";
import { CheckSquareOutlined, CloseCircleOutlined } from "@ant-design/icons-vue";
// --- Props & Emits ---
interface Props {
selectedCount?: number;
selectedCount?: number;
}
const props = withDefaults(defineProps<Props>(), {
selectedCount: 0
selectedCount: 0,
});
const emit = defineEmits<{
(e: "reset", values: any): void;
(e: "searchFinish", values: any): void;
(e: "batch-approve"): void;
(e: "batch-reject"): void;
(e: "reset", values: any): void;
(e: "searchFinish", values: any): void;
(e: "batch-approve"): void;
(e: "batch-reject"): void;
(e: "batch-delete"): void;
}>();
// initSearchData
const initSearchData = {
hbrvcd:'all',
stcd:'',
status: '',
hbrvcd: "all",
stcd: "",
status: "",
};
const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [
{
type: "waterStation",
name: "hbrvcd",
label: "流域",
fieldProps: {
allowClear: true,
},
options: [],
{
type: "waterStation",
name: "hbrvcd",
label: "流域",
fieldProps: {
allowClear: true,
},
options: [],
},
{
type: "Select",
name: "status",
label: "审批状态",
fieldProps: {
allowClear: true,
},
options: statusData.value,
{
type: "Select",
name: "status",
label: "审批状态",
fieldProps: {
allowClear: true,
},
options: statusData.value,
},
]);
// --- Methods ---
// 2.
const onSearchFinish = (values: any) => {
console.log(values);
console.log(values);
emit("searchFinish", values);
emit("searchFinish", values);
};
const handleReset = () => {
emit("reset", initSearchData);
emit("reset", initSearchData);
};
const onValuesChange = (changedValues: any, allValues: any) => {
// searchData便使
searchData.value = { ...searchData.value, ...allValues };
// searchData便使
searchData.value = { ...searchData.value, ...allValues };
};
// --- Lifecycle ---
onMounted(() => {
emit("searchFinish", initSearchData);
getstatusData()
emit("searchFinish", initSearchData);
getstatusData();
});
const statusData = ref(false)
const statusData = ref(false);
const getstatusData = () => {
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => {
statusData.value = res.data;
});
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => {
statusData.value = res.data;
});
};
</script>
<style lang="scss"></style>
<style lang="scss"></style>