数据填报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' VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'

View File

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

BIN
frontend/dist.rar Normal file

Binary file not shown.

View File

@ -44,7 +44,7 @@ export function submitFishDraft(data:any) {
export function batchApproveAll() { export function batchApproveAll() {
return request({ return request({
url: '/data/fishDraft/submitDraftsAll', url: '/data/fishDraft/submitDraftsAll',
method: 'post', method: 'post'
}); });
} }
//审批过鱼数据 //审批过鱼数据
@ -110,3 +110,11 @@ export function batchSaveDraft(data:any) {
data 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; rowKey?: string;
// / // /
searchParams?: Record<string, any>; searchParams?: Record<string, any>;
// false
isOneLoad?: boolean;
// //
defaultPageSize?: number; defaultPageSize?: number;
getCheckboxProps?: (record: any) => any; getCheckboxProps?: (record: any) => any;
@ -46,7 +44,6 @@ const props = withDefaults(defineProps<Props>(), {
enableRowSelection: false, enableRowSelection: false,
rowKey: "id", rowKey: "id",
data: () => ([]), data: () => ([]),
isOneLoad: true,
searchParams: () => ({}), searchParams: () => ({}),
defaultPageSize: 20, defaultPageSize: 20,
getCheckboxProps: undefined, getCheckboxProps: undefined,
@ -143,7 +140,6 @@ const getList = async (filter?: Record<string, any>) => {
totalCount = res?.data?.total || res?.total || 0; totalCount = res?.data?.total || res?.total || 0;
} }
tableData.value = records; tableData.value = records;
total.value = totalCount; 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,7 +10,7 @@
class="map-modal" class="map-modal"
> >
<a-tabs :active-key="currentActiveKey" @change="onTabChange"> <a-tabs :active-key="currentActiveKey" @change="onTabChange">
<a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.title"> <a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.name">
<div class="content"> <div class="content">
<!-- 基本信息组件 --> <!-- 基本信息组件 -->
<BasicInfo v-if="currentActiveKey === 'basicInfo'" :url="tab.url" /> <BasicInfo v-if="currentActiveKey === 'basicInfo'" :url="tab.url" />
@ -23,6 +23,14 @@
/> --> /> -->
</div> </div>
</a-tab-pane> </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-tabs>
</a-modal> </a-modal>
</template> </template>
@ -31,6 +39,13 @@
import { ref, watch } from "vue"; import { ref, watch } from "vue";
// Tab // Tab
import BasicInfo from "./components/BasicInfo.vue"; 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 MapView from './components/MapView.vue';
// import SurroundingInfo from './components/SurroundingInfo.vue'; // import SurroundingInfo from './components/SurroundingInfo.vue';
@ -46,7 +61,6 @@ const props = defineProps<{
visible: boolean; visible: boolean;
title?: string; title?: string;
activeKey?: string; // tab activeKey?: string; // tab
tabsConfig?: TabItem[]; // Tab Tab
data?: any; // data?: any; //
}>(); }>();
@ -71,7 +85,13 @@ watch(
}, },
{ immediate: true } { immediate: true }
); );
watch(
() => modelStore.params,
(newVal) => {
tabsConfig.value = handleTabs(newVal);
},
{ deep: true, immediate: true }
);
// tab // tab
const onTabChange = (key: string) => { const onTabChange = (key: string) => {
currentActiveKey.value = key; currentActiveKey.value = key;

View File

@ -12,95 +12,98 @@
// import { Utility } from '@zebras/qgc-share/utils/Utility' // import { Utility } from '@zebras/qgc-share/utils/Utility'
// // 水电站 √ // // 水电站 √
// const ENGTabs: Array<any> = [ 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: '基础信息', // name: "特征曲线",
// key: 'basicInfo', // key: "characteristicCurve",
// type: 'basic', // type: "characteristicCurve"
// 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'
// } // }
// ] ].filter(Boolean);
// },
// Session.getAppCode() === 'hbb' ? {
// name: '查看报告',
// key: 'attachment',
// type: 'attachment'
// } : null,
// {
// name: '批复文件',
// key: 'approval',
// type: 'approval'
// },
// // {
// // name: "特征曲线",
// // key: "characteristicCurve",
// // type: "characteristicCurve"
// // }
// ].filter(Boolean)
// // 水电站生态流量 √ // // 水电站生态流量 √
// const ENGEQTabs: Array<any> = [ // const ENGEQTabs: Array<any> = [
// { // {
@ -1076,15 +1079,15 @@
// // } // // }
// // ] // // ]
// //其他配置 // 其他配置
// const CommonTabs: any = [ const CommonTabs: any = [
// { {
// name: '基础信息', name: '基础信息',
// key: 'basicInfo', key: 'basicInfo',
// type: 'basic', type: 'basic',
// url: '/bbi/siteBipc/getSiteBasicInfo' url: '/bbi/siteBipc/getSiteBasicInfo'
// } }
// ] ];
// // 气象站 // // 气象站
// const WeatherTabs: any = [ // const WeatherTabs: any = [
@ -1139,12 +1142,13 @@
// ] // ]
// //mway : 1-是人工 2-是自动 dtinType: 0-自建 1-国家 2-人工 // //mway : 1-是人工 2-是自动 dtinType: 0-自建 1-国家 2-人工
// const handleTabs = (modaldata: any) => { const handleTabs = (modaldata: any) => {
// console.log('modaldata', modaldata) console.log('modaldata', modaldata);
// if (!modaldata?.sttp) return if (!modaldata?.sttp) return;
// let sttp = modaldata?.sttp ? modaldata?.sttp.toUpperCase() : '' let sttp = modaldata?.sttp ? modaldata?.sttp.toUpperCase() : '';
// switch (sttp) { switch (sttp) {
// case 'ENG': case 'ENG':
return ENGTabs;
// if (modaldata?.eqtp == 'QEC') { // if (modaldata?.eqtp == 'QEC') {
// const { page } = Utility.parseQueryString() // const { page } = Utility.parseQueryString()
// if (page == 'shengTaiLiuLiangManZuQingKuangJiangJu') { // if (page == 'shengTaiLiuLiangManZuQingKuangJiangJu') {
@ -1286,10 +1290,10 @@
// return AIPrediction // return AIPrediction
// case 'AI_Basic': // case 'AI_Basic':
// return AIbasic // return AIbasic
// default: default:
// return CommonTabs return CommonTabs;
// } }
// } };
// const modalTabSetting = { // const modalTabSetting = {
// footer: false, // footer: false,
@ -1309,4 +1313,21 @@
// className: 'map-tabs' // 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 { computed } from "vue";
import { useTagsViewStore } from "@/store/modules/tagsView"; import { useTagsViewStore } from "@/store/modules/tagsView";
import { useRoute } from "vue-router"; 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"; // import GisView from "@/components/gis/GisView.vue";
const modelStore = useModelStore();
const tagsViewStore = useTagsViewStore(); const tagsViewStore = useTagsViewStore();
const router = useRoute(); const router = useRoute();
@ -22,6 +26,13 @@ const routeKey = computed(() => router.path + Math.random());
</transition> </transition>
</router-view> </router-view>
</div> </div>
<MapModal
v-model:visible="modelStore.modalVisible"
v-model:active-key="modelStore.currentTabKey"
:title="modelStore.title"
:tabs-config="modelStore.tabList"
/>
</section> </section>
</template> </template>

View File

@ -3,12 +3,23 @@ import { ref } from 'vue';
export const useModelStore = defineStore('model', () => { export const useModelStore = defineStore('model', () => {
// state // state
const info = ref<{ const modalVisible = ref(false);
type: string; const currentTabKey = ref("basicInfo");
const tabList = ref([]);
const title = ref('');
const isBasicEdit = ref(false);
const params = ref<{
sttp: string;
}>({ }>({
type: 'zh' sttp: 'eng'
}); });
return { 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 RightDrawer from "@/components/RightDrawer/index.vue";
import jidiInfoMod from "@/modules/jidiInfoMod/index.vue"; import jidiInfoMod from "@/modules/jidiInfoMod/index.vue";
import shuidianhuangjingjieruMod from "@/modules/shuidianhuangjingjieruMod/index.vue"; import shuidianhuangjingjieruMod from "@/modules/shuidianhuangjingjieruMod/index.vue";
import MapModal from "@/components/MapModal/index.vue";
import { useModelStore } from "@/store/modules/model"; import { useModelStore } from "@/store/modules/model";
const modelInfo = useModelStore(); const modelStore = useModelStore();
// import { getQgcStaticData } from "@/api/ecoFlow"; // import { getQgcStaticData } from "@/api/ecoFlow";
onMounted(() => { onMounted(() => {
// const params = { // 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 = () => { const showMapModal = () => {
modalVisible.value = true; modelStore.modalVisible = true;
currentTabKey.value = "basicInfo"; modelStore.params.sttp = "ENG";
modelInfo.info.type = "eng"; modelStore.title = "三峡 详情信息";
modelStore.currentTabKey = "basicInfo";
modelStore.isBasicEdit = true;
}; };
const showMapModal1 = () => { const showMapModal1 = () => {
modalVisible.value = true; modelStore.modalVisible = true;
currentTabKey.value = "basicInfo"; modelStore.params.sttp = "ENG";
modelInfo.info.type = "zh"; modelStore.title = "三峡222 详情信息";
}; modelStore.currentTabKey = "basicInfo";
modelStore.isBasicEdit = false;
const onTabChange = (key: string) => { // modelStore.modalVisible = true;
console.log("Tab 切换为:", key); // modelStore.params.sttp = "zh";
// modelStore.title = '222 ';
// modelStore.currentTabKey = "mapView";
}; };
</script> </script>
@ -60,16 +53,8 @@ const onTabChange = (key: string) => {
<div class="rightContent"> <div class="rightContent">
<RightDrawer> <RightDrawer>
<!-- <a-button @click="showMapModal">打开电站地图弹窗</a-button> <!-- <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 /> <jidiInfoMod />
<shuidianhuangjingjieruMod /> <shuidianhuangjingjieruMod />
</RightDrawer> </RightDrawer>

View File

@ -1,90 +1,67 @@
<template> <template>
<div class="guoYuSheShiShuJuHistory-page"> <div class="guoYuSheShiShuJuHistory-page">
<GuoYuSheShiShuJuHistorySearch ref="searchRef" class="search-container" :guoyuStatus="guoyuStatus" <GuoYuSheShiShuJuHistorySearch
:direction="direction" @reset="handleReset" @search-finish="handleSearchFinish" /> ref="searchRef"
class="search-container"
:guoyuStatus="guoyuStatus"
:direction="direction"
@reset="handleReset"
@search-finish="handleSearchFinish"
/>
<div class="table-container" ref="tableContainerRef"> <div class="table-container" ref="tableContainerRef">
<BasicTable ref="tableRef" :columns="columns" :scroll-y="tableScrollY" :list-url="getFishDraftPage" <BasicTable
:search-params="{}" :transform-data="customTransform"> ref="tableRef"
:columns="columns"
:scroll-y="tableScrollY"
:list-url="getFishDraftPage"
:search-params="{}"
:transform-data="customTransform"
>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'action' || column.dataIndex === 'action'"> <template v-if="column.key === 'action' || column.dataIndex === 'action'">
<div class="flex"> <div class="flex">
<a-button type="link" size="small" @click="handleView(record)">查看</a-button> <a-button type="link" size="small" @click="handleView(record)"
>查看</a-button
>
</div> </div>
</template> </template>
</template> </template>
</BasicTable> </BasicTable>
</div> </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" <EditModal
@cancel="closeMediaPreview" :zIndex="2000"> ref="editModalRef"
<div class="flex h-[60vh] gap-4"> v-model:visible="editModalVisible"
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container"> :is-view="true"
<div v-for="(item, index) in previewList" :key="index" class="mb-2 cursor-pointer list-item" :direction="direction"
:class="{ select: index === currentMediaIndex }" @click="currentMediaIndex = index"> :initial-values="currentRecord"
<span class="file-name">{{ item.name }}</span> @cancel="editModalCancel"
</div> @preview-click="handlePreviewClick"
</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" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, h, nextTick } from "vue"; import { ref, computed, onMounted, h, nextTick } from "vue";
import { message } from "ant-design-vue";
import { LeftOutlined, RightOutlined } from "@ant-design/icons-vue";
import BasicTable from "@/components/BasicTable/index.vue"; import BasicTable from "@/components/BasicTable/index.vue";
import PreviewMedia from "@/components/previewMedia/index.vue";
import GuoYuSheShiShuJuHistorySearch from "./guoYuSheShiShuJuHistorySearch.vue"; import GuoYuSheShiShuJuHistorySearch from "./guoYuSheShiShuJuHistorySearch.vue";
import EditModal from "../guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoForm.vue"; import EditModal from "../guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoForm.vue";
import { getFishDraftPage } from "@/api/guoYuSheShiShuJuTianBao"; import { getFishDraftPage } from "@/api/guoYuSheShiShuJuTianBao";
import { Tag } from "ant-design-vue"; import { Tag } from "ant-design-vue";
import { getDictItemsByCode } from "@/api/dict"; import { getDictItemsByCode } from "@/api/dict";
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL; const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
const baseUrlPreview = import.meta.env.VITE_APP_BASE_URL;
interface FormData { interface FormData {
[key: string]: any; [key: string]: any;
@ -100,8 +77,6 @@ interface ColumnConfig {
} }
const tableRef = ref<any>(null); const tableRef = ref<any>(null);
const searchRef = ref<any>(null);
const editModalRef = ref<any>(null);
const direction = ref<any>([]); const direction = ref<any>([]);
const guoyuStatus = ref<any>([]); const guoyuStatus = ref<any>([]);
@ -174,17 +149,13 @@ const videoPreviewTitle = ref("视频预览");
interface MediaItem { interface MediaItem {
id: string; id: string;
type: "image" | "video" | "formVideo" | "formImage"; type: "image" | "video";
name: string; name: string;
url: string; url: string;
} }
const previewList = ref<MediaItem[]>([]); const previewList = ref<MediaItem[]>([]);
const currentMediaIndex = ref(0); const previewListIndex = ref(0);
const tablePreviewRecord = ref<any>({});
const currentMediaItem = computed(() => previewList.value[currentMediaIndex.value]);
const columns = computed(() => { const columns = computed(() => {
return [ return [
...baseColumnsConfig.map((col) => { ...baseColumnsConfig.map((col) => {
@ -231,59 +202,23 @@ const editModalCancel = () => {
const handlePreviewClick = (record: any, type: string, index: number) => { const handlePreviewClick = (record: any, type: string, index: number) => {
const mixedList: MediaItem[] = []; const mixedList: MediaItem[] = [];
if (type === "image") { if (type === "image") {
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 = "图片预览"; videoPreviewTitle.value = "图片预览";
const nameList = JSON.parse(JSON.stringify(record)).picpthList; const nameList = JSON.parse(JSON.stringify(record)).picpthList;
nameList.forEach((item: any) => { nameList.forEach((item: any) => {
mixedList.push({ mixedList.push({
id: record.id, id: record.id,
type: "formImage", type: "image",
name: item.name, name: item.name,
url: item.url, url: item.url,
}); });
}); });
} else if (type === "formVideo") { } else if (type === "video") {
videoPreviewTitle.value = "视频预览"; videoPreviewTitle.value = "视频预览";
const nameList = JSON.parse(JSON.stringify(record)).vdpthList; const nameList = JSON.parse(JSON.stringify(record)).vdpthList;
nameList.forEach((item: any) => { nameList.forEach((item: any) => {
mixedList.push({ mixedList.push({
id: record.id, id: record.id,
type: "formVideo", type: "video",
name: item.name, name: item.name,
url: item.url, url: item.url,
}); });
@ -291,32 +226,12 @@ const handlePreviewClick = (record: any, type: string, index: number) => {
} }
mediaPreviewVisible.value = true; mediaPreviewVisible.value = true;
currentMediaIndex.value = index; previewListIndex.value = index;
nextTick(() => { nextTick(() => {
previewList.value = mixedList; 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 customTransform = (res: any) => {
const rawRecords = res?.data?.records || []; const rawRecords = res?.data?.records || [];
const modifiedRecords = rawRecords.map((item: any) => { const modifiedRecords = rawRecords.map((item: any) => {
@ -349,13 +264,13 @@ const handleSearchFinish = (values: any) => {
field: "strdt", field: "strdt",
operator: "gte", operator: "gte",
dataType: "date", dataType: "date",
value: values.strdt[0] + ' 00:00:00', value: values.strdt[0] + " 00:00:00",
}, },
{ {
field: "strdt", field: "strdt",
operator: "lte", operator: "lte",
dataType: "date", dataType: "date",
value: values.strdt[1] + ' 23:59:59', value: values.strdt[1] + " 23:59:59",
}, },
values.direction && { values.direction && {
field: "direction", field: "direction",
@ -367,7 +282,7 @@ const handleSearchFinish = (values: any) => {
field: "status", field: "status",
operator: "eq", operator: "eq",
dataType: "string", dataType: "string",
value: 'APPROVED', value: "APPROVED",
}, },
values.stcd && { values.stcd && {
field: "stcd", field: "stcd",
@ -456,39 +371,4 @@ onMounted(() => {
overflow: hidden; overflow: hidden;
position: relative; 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> </style>

View File

@ -413,7 +413,7 @@ const emit = defineEmits<{
(e: "update:visible", value: boolean): void; (e: "update:visible", value: boolean): void;
(e: "cancel"): void; (e: "cancel"): void;
(e: "ok", values: any): void; (e: "ok", values: any): void;
(e: "preview-click", record: any, type: string, index: number): void; (e: "preview-click", record: any, type: string, index: number, action: string): void;
}>(); }>();
// //
@ -654,13 +654,12 @@ const initForm = () => {
// //
const handleVideoPreview = () => { const handleVideoPreview = () => {
emit("preview-click", { vdpthList: videoFileList.value }, "formVideo", 0); emit("preview-click", { vdpthList: videoFileList.value }, "video", 0, "preview");
}; };
// //
const handleImagePreview = async (file: any) => { const handleImagePreview = async (file: any) => {
emit("preview-click", { picpthList: imageFileList.value }, "formImage", 0); emit("preview-click", { picpthList: imageFileList.value }, "image", 0, "preview");
return Promise.reject();
}; };
// //
const handleOk = async () => { const handleOk = async () => {
@ -819,7 +818,7 @@ watch(
); );
// //
defineExpose({ defineExpose({
localLoading localLoading,
}); });
</script> </script>

View File

@ -144,14 +144,14 @@
</template> </template>
<!-- 过鱼时间 --> <!-- 过鱼时间 -->
<template v-else-if="column.dataIndex === 'strdt'"> <template v-else-if="column.dataIndex === 'strdtStr'">
<a-date-picker <a-date-picker
v-model:value="editingData.strdt" v-model:value="editingData.strdtStr"
show-time show-time
style="width: 100%" style="width: 100%"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
value-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> </template>
@ -251,7 +251,7 @@
<div <div
class="text" class="text"
:class="{ text_warning: record.picpthsWarnings.includes(item.name) }" :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 }} {{ item.name }}
</div> </div>
@ -263,7 +263,7 @@
<div <div
class="text" class="text"
:class="{ text_warning: record.vdpthsWarnings.includes(item.name) }" :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 }} {{ item.name }}
</div> </div>
@ -307,7 +307,15 @@ const rowStates = reactive<Record<number, any>>({});
const editingData = ref<any>(null); const editingData = ref<any>(null);
const modalColumns = ref([ 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", dataIndex: "hbrvnm",
key: "hbrvnm", key: "hbrvnm",
@ -330,9 +338,9 @@ const modalColumns = ref([
width: 150, width: 150,
}, },
{ {
dataIndex: "strdt", dataIndex: "strdtStr",
key: "strdt", key: "strdtStr",
dataIndexKey: "strdt", dataIndexKey: "strdtStr",
title: "过鱼时间", title: "过鱼时间",
width: 190, width: 190,
}, },
@ -577,6 +585,10 @@ const startEdit = (index: number) => {
editingData.value.stcd = null; editingData.value.stcd = null;
delWarning(null, "stcd"); delWarning(null, "stcd");
} }
if (editingData.value.warnings.includes("strdt")) {
editingData.value.strdt = null;
delWarning(null, "strdt");
}
// 3. ( editingData ) // 3. ( editingData )
if (editingData.value.hbrvcd == "" || editingData.value.hbrvcd == undefined) { if (editingData.value.hbrvcd == "" || editingData.value.hbrvcd == undefined) {
@ -652,6 +664,7 @@ const saveEdit = async (index: number) => {
// 2. // 2.
const newData = [...props.fileTableData]; const newData = [...props.fileTableData];
newData[index] = { ...editingData.value }; newData[index] = { ...editingData.value };
newData[index].strdt = newData[index].strdtStr;
emit("update:fileLoading", true); emit("update:fileLoading", true);
try { try {
const res: any = await revalidateAndUpdateRow({ const res: any = await revalidateAndUpdateRow({

View File

@ -185,142 +185,28 @@
@ok="handleEditSubmit" @ok="handleEditSubmit"
@preview-click="handlePreviewClick" @preview-click="handlePreviewClick"
/> />
<!-- 预览媒体组件 -->
<!-- 媒体预览 Modal --> <PreviewMedia
<a-modal ref="previewMediaRef"
v-model:open="mediaPreviewVisible" v-model:visible="mediaPreviewVisible"
:title="videoPreviewTitle" :previewList="previewList"
:footer="null" :previewListIndex="previewListIndex"
width="900px" :videoPreviewTitle="videoPreviewTitle"
@cancel="closeMediaPreview" :taskId="taskId"
:zIndex="2000" :isDel="isPreviewMediaDelete"
> @deleteMedia="handleDeleteMedia"
<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>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, h, nextTick, watch } from "vue"; import { ref, computed, onMounted, h, nextTick, watch } from "vue";
import { message, Modal } from "ant-design-vue"; // 使 ant-design-vue import { message, Modal } from "ant-design-vue"; // 使 ant-design-vue
import {
LeftOutlined,
RightOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined,
} from "@ant-design/icons-vue"; //
import BasicTable from "@/components/BasicTable/index.vue"; import BasicTable from "@/components/BasicTable/index.vue";
import GuoYuSheShiShuJuTianBaoSearch from "./guoYuSheShiShuJuTianBaoSearch.vue"; import GuoYuSheShiShuJuTianBaoSearch from "./guoYuSheShiShuJuTianBaoSearch.vue";
import GuoYuSheShiShuJuTianBaoTable from "./guoYuSheShiShuJuTianBaoTable.vue"; import GuoYuSheShiShuJuTianBaoTable from "./guoYuSheShiShuJuTianBaoTable.vue";
import EditModal from "./guoYuSheShiShuJuTianBaoForm.vue"; import EditModal from "./guoYuSheShiShuJuTianBaoForm.vue";
import PreviewMedia from "@/components/previewMedia/index.vue";
import { checkPerm } from "@/directive/permission"; import { checkPerm } from "@/directive/permission";
import { import {
getFishDraftPage, getFishDraftPage,
@ -335,11 +221,11 @@ import {
checkImportStatus, checkImportStatus,
batchSaveDraft, batchSaveDraft,
getLastImportResult, getLastImportResult,
deleteFile,
batchApproveAll, batchApproveAll,
} from "@/api/guoYuSheShiShuJuTianBao"; } from "@/api/guoYuSheShiShuJuTianBao";
import { Tag } from "ant-design-vue"; // Tag import { Tag } from "ant-design-vue"; // Tag
import { getDictItemsByCode } from "@/api/dict"; import { getDictItemsByCode } from "@/api/dict";
import { fr } from "element-plus/es/locale/index.mjs";
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL; const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
const baseUrlApi = import.meta.env.VITE_APP_BASE_API_URL; const baseUrlApi = import.meta.env.VITE_APP_BASE_API_URL;
@ -358,9 +244,6 @@ interface ColumnConfig {
} }
const fileInputRef = ref<any>(null); const fileInputRef = ref<any>(null);
const tableRef = ref<any>(null); const tableRef = ref<any>(null);
//
const imageLoading = ref(false);
const imageLoadError = ref(false);
// //
const direction = ref<any>([]); const direction = ref<any>([]);
const guoyuStatus = ref<any>([]); const guoyuStatus = ref<any>([]);
@ -435,20 +318,18 @@ const isView = ref(false);
const currentRecord = ref<FormData | null>(null); const currentRecord = ref<FormData | null>(null);
const mediaPreviewVisible = ref(false); const mediaPreviewVisible = ref(false);
const isPreviewMediaDelete = ref(false);
const videoPreviewTitle = ref("视频预览"); const videoPreviewTitle = ref("视频预览");
interface MediaItem { interface MediaItem {
id: string; id: string;
type: "image" | "video" | "formVideo" | "formImage"; type: "image" | "video";
name: string; name: string;
url: string; url: string;
} }
const previewList = ref<MediaItem[]>([]); const previewList = ref<MediaItem[]>([]);
const currentMediaIndex = ref(0); const previewListIndex = ref(0);
const tablePreviewRecord = ref<any>({}); const tablePreviewRecord = ref<any>({});
//
const currentMediaItem = computed(() => previewList.value[currentMediaIndex.value]);
// //
const fileTableData = ref<any[]>([]); const fileTableData = ref<any[]>([]);
const batchData = ref<any[]>([]); const batchData = ref<any[]>([]);
@ -810,6 +691,9 @@ const importBtn = async () => {
if (visible.value) importBtn(); if (visible.value) importBtn();
}, 5000); }, 5000);
} }
} else if (currentTask.status == "FAILED") {
message.error(currentTask.statusText);
fileLoading.value = false;
} else if (currentTask.status == "VALIDATED") { } else if (currentTask.status == "VALIDATED") {
nextTick(async () => { nextTick(async () => {
modalTableRef.value.editingRowIndex = null; modalTableRef.value.editingRowIndex = null;
@ -839,13 +723,6 @@ const fileChange = (file: File) => {
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
await importFishZip(formData); await importFishZip(formData);
try {
setTimeout(() => {
if (visible.value) importBtn();
}, 5000);
} catch (error) {
message.error("导入失败");
}
}); });
}; };
// //
@ -994,117 +871,74 @@ const handleSearchFinish = (values: any) => {
tableRef.value?.getList(filter); 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[] = []; const mixedList: MediaItem[] = [];
if (type === "image") { if (action === "delete") {
imageLoading.value = true; isPreviewMediaDelete.value = true;
imageLoadError.value = false;
tablePreviewRecord.value = record; tablePreviewRecord.value = record;
} else if (action === "preview") {
isPreviewMediaDelete.value = false;
}
if (type === "image") {
videoPreviewTitle.value = "图片预览"; videoPreviewTitle.value = "图片预览";
const nameList = record.picpthList; const nameList = record.picpthList;
nameList.forEach((item: any) => { nameList.forEach((item: any) => {
let url = "";
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({ mixedList.push({
id: record.id, id: record.id,
type: "image", type: "image",
name: item.name, 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 + baseUrlApi +
"/data/fishDraft/previewFile?filename=" + "/data/fishDraft/previewFile?filename=" +
item.name + item.name +
"&taskId=" + "&taskId=" +
taskId.value + taskId.value +
"&type=1", "&type=2";
}); }
});
} else if (type === "video") {
tablePreviewRecord.value = record;
videoPreviewTitle.value = "视频预览";
const nameList = record.vdpthList;
nameList.forEach((item: any) => {
mixedList.push({ mixedList.push({
id: record.id, id: record.id,
type: "video", type: "video",
name: item.name, // name: item.name, //
url: url: 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,
}); });
}); });
} }
mediaPreviewVisible.value = true; mediaPreviewVisible.value = true;
currentMediaIndex.value = index; previewListIndex.value = index;
nextTick(() => { nextTick(() => {
console.log(mixedList);
previewList.value = mixedList; previewList.value = mixedList;
}); });
}; };
// const handleDeleteMedia = (item: any, type: string, index: number) => {
const prevMedia = () => {
if (currentMediaIndex.value > 0) {
currentMediaIndex.value--;
}
};
//
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); previewList.value.splice(index, 1);
if (videoPreviewTitle.value == "图片预览") { if (type == "图片预览") {
if (previewList.value.length == 0) { if (previewList.value.length == 0) {
tablePreviewRecord.value.picpthList = []; tablePreviewRecord.value.picpthList = [];
tablePreviewRecord.value.picpthsWarnings = []; tablePreviewRecord.value.picpthsWarnings = [];
@ -1125,35 +959,6 @@ const handleDeleteMedia = (item: any, index: number) => {
); );
} }
} }
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 tableContainerRef = ref<HTMLElement | null>(null);
const tableScrollY = ref<number | undefined>(undefined); const tableScrollY = ref<number | undefined>(undefined);
@ -1174,28 +979,6 @@ const observer = new ResizeObserver(() => {
calcTableScrollY(); 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(() => { onMounted(() => {
nextTick(() => { 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 { .buttonStyle {
width: 100%; width: 100%;
display: flex; display: flex;

View File

@ -1,7 +1,14 @@
<template> <template>
<div class="approval-detail-search"> <div class="approval-detail-search">
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'fu'" @reset="handleReset" <BasicSearch
@finish="onSearchFinish" @values-change="onValuesChange"> ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
:zhujianfujian="'fu'"
@reset="handleReset"
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #ftp="{ onChange }"> <template #ftp="{ onChange }">
<fishSearch v-model="localTypeDate" width="200px" @update:modelValue="onChange" /> <fishSearch v-model="localTypeDate" width="200px" @update:modelValue="onChange" />
</template> </template>
@ -62,10 +69,10 @@ const searchList: any = computed(() => [
type: "RangePicker", type: "RangePicker",
name: "strdt", name: "strdt",
label: "过鱼时间", label: "过鱼时间",
picker: "month", picker: "date",
fieldProps: { fieldProps: {
format: "YYYY-MM", format: "YYYY-MM-DD",
valueFormat: "YYYY-MM", valueFormat: "YYYY-MM-DD",
allowClear: false, allowClear: false,
}, },
presets: DateSetting.RangeButton.month, presets: DateSetting.RangeButton.month,

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,14 @@
<template> <template>
<div class="guoYuSheShiShuJuTianBao-search"> <div class="guoYuSheShiShuJuTianBao-search">
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'zhu'" <BasicSearch
@finish="onSearchFinish" @values-change="onValuesChange" @reset="handleReset"> ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
:zhujianfujian="'zhu'"
@finish="onSearchFinish"
@values-change="onValuesChange"
@reset="handleReset"
>
<template #actions> <template #actions>
<a-tooltip title="批量审批"> <a-tooltip title="批量审批">
<a-button :disabled="selectedCount === 0" @click="$emit('batch-approve')"> <a-button :disabled="selectedCount === 0" @click="$emit('batch-approve')">
@ -20,7 +27,11 @@
批量驳回 批量驳回
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip title="批量删除">
<a-button :disabled="selectedCount === 0" @click="$emit('batch-delete')">
批量删除
</a-button>
</a-tooltip>
</template> </template>
</BasicSearch> </BasicSearch>
</div> </div>
@ -29,18 +40,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
import BasicSearch from "@/components/BasicSearch/index.vue"; // import BasicSearch from "@/components/BasicSearch/index.vue"; //
import { getDictItemsByCode } from '@/api/dict'; import { getDictItemsByCode } from "@/api/dict";
import { import { CheckSquareOutlined, CloseCircleOutlined } from "@ant-design/icons-vue";
CheckSquareOutlined,
CloseCircleOutlined,
} from "@ant-design/icons-vue";
// --- Props & Emits --- // --- Props & Emits ---
interface Props { interface Props {
selectedCount?: number; selectedCount?: number;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
selectedCount: 0 selectedCount: 0,
}); });
const emit = defineEmits<{ const emit = defineEmits<{
@ -48,14 +56,14 @@ const emit = defineEmits<{
(e: "searchFinish", values: any): void; (e: "searchFinish", values: any): void;
(e: "batch-approve"): void; (e: "batch-approve"): void;
(e: "batch-reject"): void; (e: "batch-reject"): void;
(e: "batch-delete"): void;
}>(); }>();
// initSearchData // initSearchData
const initSearchData = { const initSearchData = {
hbrvcd:'all', hbrvcd: "all",
stcd:'', stcd: "",
status: '', status: "",
}; };
const searchData = ref<any>({ ...initSearchData }); const searchData = ref<any>({ ...initSearchData });
@ -80,12 +88,9 @@ const searchList: any = computed(() => [
}, },
options: statusData.value, options: statusData.value,
}, },
]); ]);
// --- Methods --- // --- Methods ---
// 2. // 2.
const onSearchFinish = (values: any) => { const onSearchFinish = (values: any) => {
console.log(values); console.log(values);
@ -104,9 +109,9 @@ const onValuesChange = (changedValues: any, allValues: any) => {
// --- Lifecycle --- // --- Lifecycle ---
onMounted(() => { onMounted(() => {
emit("searchFinish", initSearchData); emit("searchFinish", initSearchData);
getstatusData() getstatusData();
}); });
const statusData = ref(false) const statusData = ref(false);
const getstatusData = () => { const getstatusData = () => {
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => { getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => {
statusData.value = res.data; statusData.value = res.data;