导出报表功能

This commit is contained in:
guanj
2025-09-30 14:54:41 +08:00
parent 167e676838
commit 10511a92a4
3 changed files with 571 additions and 375 deletions

View File

@@ -269,4 +269,31 @@ export function rightEventDevOpen(data: object) {
}); });
} }
// 部门集合
export function getDept(data: object) {
return service({
url: "/report/getDept",
method: "post",
data,
});
}
// 报表导出
export function exportForms(data: object) {
return service({
url: "/report/get",
method: "post",
data,
responseType: "blob",
});
}

View File

@@ -0,0 +1,186 @@
<!--报表导出-->
<template>
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
:title="title"
width="500"
>
<div>
<div style="height: 160px" class="smsConfig">
<el-form :model="form" inline label-width="auto" class="mb10 ml30 mt20">
<el-form-item label="时间">
<el-date-picker
v-model="timeValue"
size="small"
type="daterange"
:disabled-date="isFutureDate"
style="width: 250px; margin-right: 10px"
unlink-panels
:clearable="false"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="部门集合">
<el-select
v-model="form.deptList"
placeholder="请选择"
style="width: 250px"
size="small"
multiple
>
<el-option
v-for="item in deptLists"
:key="item.deptsIndex"
:label="item.deptsname"
:value="item.deptsIndex"
/>
</el-select>
</el-form-item>
</el-form>
<el-divider />
<div style="text-align: center">
<el-button
type="primary"
:icon="Check"
@click="save"
class="mt10"
size="small"
>确定</el-button
>
<el-button :icon="Close" @click="setUp" class="mt10" size="small"
>取消</el-button
>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { Check, Close } from "@element-plus/icons-vue";
import { getDept, exportForms } from "@/api/statistics/index";
import { useStore } from "vuex";
import { log } from "console";
const store = useStore();
const machineVisible = ref(false);
const title = ref("报表导出");
const timeValue = ref([]);
const deptLists = ref();
//form表单校验规则
const emit = defineEmits(["flushed"]);
const form = ref({
searchBeginTime: "",
searchEndTime: "",
deptList: [],
deptId: "",
});
const open = (text: string, data?: any) => {
timeValue.value = [];
form.value = {
searchBeginTime: "",
searchEndTime: "",
deptList: [],
deptId: "",
};
machineVisible.value = true;
init();
};
const init = () => {
getDept({}).then((res) => {
deptLists.value = res.data;
});
};
const save = () => {
(form.value.deptId = store.state.deptId),
(form.value.searchBeginTime = timeValue.value[0]),
(form.value.searchEndTime = timeValue.value[1]
? timeValue.value[1].split(" ")[0] + " 23:59:59"
: "");
exportForms(form.value).then((res: any) => {
let blob = new Blob([res], {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8",
});
// createObjectURL(blob); //创建下载的链接
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a"); // 创建a标签
link.href = url;
link.download = "导出报表.docx"; // 设置下载的文件名
document.body.appendChild(link);
link.click(); //执行下载
document.body.removeChild(link);
machineVisible.value = false;
});
};
const isFutureDate = (time: any) => {
return time && time > Date.now();
};
// 取消
const setUp = () => {
machineVisible.value = false;
timeValue.value = [];
form.value = {
searchBeginTime: "",
searchEndTime: "",
deptList: [],
deptId: "",
};
};
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.smsConfig {
color: #fff;
}
.title {
background-color: #0a73ff40;
height: 30px;
line-height: 30px;
font-weight: 600;
padding-left: 10px;
margin-bottom: 10px;
border-left: 10px solid var(--el-color-primary);
}
:deep(.el-card__body) {
padding: 10px !important;
}
:deep(.el-select) {
min-width: 80px !important;
}
.checkbox {
display: flex;
justify-content: space-between;
}
:deep(.el-form-item__label, ) {
color: #fff;
}
:deep(.el-checkbox__label) {
color: #fff;
}
:deep(.el-form-item__label, ) {
color: #fff;
}
</style>

View File

@@ -1,375 +1,358 @@
<template> <template>
<div id="index" ref="appRef"> <div id="index" ref="appRef">
<div <div class="bg" :class="store.state.screenNotic == 1
class="bg" ? pushFlag
:class=" ? bottomTextRef?.urgentList.length > 0
store.state.screenNotic == 1 ? 'bg-red'
? pushFlag : ''
? bottomTextRef?.urgentList.length > 0 : ''
? 'bg-red' : ''
: '' ">
: '' <dv-loading v-if="loading">Loading...</dv-loading>
: ''
" <div v-else class="host-body">
> <div class="d-flex jc-center">
<dv-loading v-if="loading">Loading...</dv-loading> <div class="react-left">
<span class="text fw-b">
<div v-else class="host-body"> {{ timeInfo.dateYear }} {{ timeInfo.dateWeek }}
<div class="d-flex jc-center"> {{ timeInfo.dateDay }}</span>
<div class="react-left"> </div>
<span class="text fw-b">
{{ timeInfo.dateYear }} {{ timeInfo.dateWeek }} <dv-decoration-10 class="dv-dec-10" :color="color[1]" />
{{ timeInfo.dateDay }}</span <div class="d-flex jc-center">
> <dv-decoration-8 class="dv-dec-8" :color="color[2]" />
</div> <div class="title">
<span class="title-text">{{ title }}</span>
<dv-decoration-10 class="dv-dec-10" :color="color[1]" /> </div>
<div class="d-flex jc-center"> <dv-decoration-8 class="dv-dec-8" :reverse="true" :color="color[2]" />
<dv-decoration-8 class="dv-dec-8" :color="color[2]" /> </div>
<div class="title"> <dv-decoration-10 class="dv-dec-10-s" :color="color[1]" />
<span class="title-text">{{ title }}</span> </div>
</div>
<dv-decoration-8 <div class="d-flex secondLine">
class="dv-dec-8" <div class="react-right mr-1">
:reverse="true" <span class="text fw-b" style="display: flex">
:color="color[2]" <datePicker ref="datePickerRef" @timeChangeInfo="timeChangeInfo" />
/> </span>
</div> </div>
<dv-decoration-10 class="dv-dec-10-s" :color="color[1]" />
</div> <el-dropdown placement="bottom">
<el-icon :size="22" :color="color[1][0]" class="mt-0.5 mt5">
<div class="d-flex secondLine"> <Menu />
<div class="react-right mr-1"> </el-icon>
<span class="text fw-b" style="display: flex"> <template #dropdown>
<datePicker <el-dropdown-menu>
ref="datePickerRef" <!-- <el-dropdown-item @click="handleClick('1')"
@timeChangeInfo="timeChangeInfo" >已发送短信查询</el-dropdown-item
/> >
</span>
</div> <el-dropdown-item @click="handleClick('2')"
>模拟短信发送</el-dropdown-item
<el-dropdown placement="bottom"> > -->
<el-icon :size="22" :color="color[1][0]" class="mt-0.5 mt5"> <el-dropdown-item @click="handleClick('3')">系统配置</el-dropdown-item>
<Menu /> <el-dropdown-item @click="handleClick('4')">报表导出</el-dropdown-item>
</el-icon> </el-dropdown-menu>
<template #dropdown> </template>
<el-dropdown-menu> </el-dropdown>
<!-- <el-dropdown-item @click="handleClick('1')" </div>
>已发送短信查询</el-dropdown-item
> <div class="body-box">
<!-- 第三行数据 -->
<el-dropdown-item @click="handleClick('2')" <div class="content-box">
>模拟短信发送</el-dropdown-item <dv-border-box-13 :color="color[0]">
> --> <eventStatistics ref="eventStatisticsRef" />
<el-dropdown-item @click="handleClick('3')" </dv-border-box-13>
>系统配置</el-dropdown-item
> <dv-border-box-10 :color="color[0]">
</el-dropdown-menu> <Map ref="mapRef" />
</template> <!-- <center-left /> -->
</el-dropdown> </dv-border-box-10>
</div>
<dv-border-box-13 :color="color[0]">
<div class="body-box"> <alarm ref="alarmRef" />
<!-- 第三行数据 --> <!-- 紧急告警 -->
<div class="content-box">
<dv-border-box-13 :color="color[0]"> <div class="icon" v-if="!(bottomTextRef?.urgentList.length == 0)" :color="color[0][1]" :class="bottomTextRef?.urgentList.length > 0
<eventStatistics ref="eventStatisticsRef" /> ? 'animate-flash-red'
</dv-border-box-13> : ''
" @click="drawerClick">
<dv-border-box-10 :color="color[0]"> <!-- <WarnTriangleFilled /> -->
<Map ref="mapRef" /> <span class="iconfont icon-gaojing" :style="{
<!-- <center-left /> --> color:
</dv-border-box-10> bottomTextRef?.urgentList.length > 0
? '#ff0000'
<dv-border-box-13 :color="color[0]"> : color[0][1],
<alarm ref="alarmRef" /> }"></span>
<!-- 紧急告警 --> <span class="count" v-if="bottomTextRef?.urgentList.length > 0">{{
bottomTextRef?.urgentList.length > 99
<div ? "99+"
class="icon" : bottomTextRef?.urgentList.length
v-if="!(bottomTextRef?.urgentList.length == 0)" }}</span>
:color="color[0][1]" </div>
:class=" </dv-border-box-13>
bottomTextRef?.urgentList.length > 0 </div>
? 'animate-flash-red'
: '' <!-- 第四行数据 -->
" <div class="bototm-box">
@click="drawerClick" <dv-border-box-13 :color="color[0]">
> <endpointStatistics ref="endpointStatisticsRef" />
<!-- <WarnTriangleFilled /> -->
<span <!-- <bottom-left /> -->
class="iconfont icon-gaojing" </dv-border-box-13>
:style="{ <dv-border-box-13 :color="color[0]">
color: <informationTable ref="informationTableRef" @handleCurrentChange="handleCurrentChange" />
bottomTextRef?.urgentList.length > 0 </dv-border-box-13>
? '#ff0000' <dv-border-box-13 :color="color[0]">
: color[0][1], <sendTrends ref="sendTrendsRef" />
}" </dv-border-box-13>
></span> </div>
<span <!-- 底部 -->
class="count" </div>
v-if="bottomTextRef?.urgentList.length > 0" </div>
>{{ </div>
bottomTextRef?.urgentList.length > 99 <bottomText ref="bottomTextRef" @handleCurrentChange="handleCurrentChange" />
? "99+"
: bottomTextRef?.urgentList.length <!-- 已发短信查询 -->
}}</span <smsQueries ref="smsQueriesRef" v-if="smsQueriesFlag" @close="smsQueriesFlag = false" />
>
</div> <!-- 系统配置 -->
</dv-border-box-13> <Config ref="ConfigRef" @flushed="inquire" />
</div>
<!-- 报表导出 -->
<!-- 第四行数据 --> <ReportForms ref="reportForms" />
<div class="bototm-box"> </div>
<dv-border-box-13 :color="color[0]"> </template>
<endpointStatistics ref="endpointStatisticsRef" />
<!--index.vue-->
<!-- <bottom-left /> --> <script lang="ts" setup>
</dv-border-box-13> import {
<dv-border-box-13 :color="color[0]"> defineComponent,
<informationTable ref,
ref="informationTableRef" reactive,
@handleCurrentChange="handleCurrentChange" onMounted,
/> onUnmounted,
</dv-border-box-13> onBeforeUnmount,
<dv-border-box-13 :color="color[0]"> watch,
<sendTrends ref="sendTrendsRef" /> } from "vue";
</dv-border-box-13>
</div> import { formatTime, getDateRange, stopSpeak } from "@/utils/index"; //引入封装好的
<!-- 底部 --> import useDraw from "@/utils/useDraw"; // 引入封装好的屏幕适配方法
</div> import { WEEK, title, subtitle, moduleInfo, color } from "@/constant/index"; //引入封装的标题日期
</div> import datePicker from "@/components/datePicker/index.vue";
</div> import { Menu, BellFilled, WarnTriangleFilled } from "@element-plus/icons-vue";
<bottomText //页面组件
ref="bottomTextRef" import eventStatistics from "./components/eventStatistics.vue"; //统计事件
@handleCurrentChange="handleCurrentChange" import endpointStatistics from "./components/endpointStatistics.vue"; //终端在线统计
/> import sendTrends from "./components/sendTrends.vue"; //终端在线统计
import informationTable from "./components/informationTable.vue"; //实时暂态信息
<!-- 已发短信查询 --> import alarm from "./components/alarm.vue"; //实时暂态信息
<smsQueries import Map from "./components/bdMap.vue"; //地图
ref="smsQueriesRef" import smsQueries from "./components/smsQueries.vue"; // //短信查询
v-if="smsQueriesFlag" import bottomText from "./components/bottomText.vue"; //边框组件
@close="smsQueriesFlag = false" import socketClient from "@/utils/webSocketClient";
/> import { useStore } from "vuex";
import Config from "./components/config.vue";
<!-- 系统配置 --> import ReportForms from "./components/reportForms.vue";
<Config ref="ConfigRef" @flushed="inquire" />
</div> const store = useStore();
</template> const smsQueriesRef = ref(); // 短信查询组件引用
const endpointStatisticsRef = ref(); // 终端在线统计组件引用
<!--index.vue--> const informationTableRef = ref(); // 实时暂态信息组件引用
<script lang="ts" setup> const sendTrendsRef = ref();
import { const ConfigRef = ref();
defineComponent, const eventStatisticsRef = ref();
ref, const alarmRef = ref();
reactive, const mapRef = ref();
onMounted, //开始创建webSocket客户端
onUnmounted, const dataSocket = reactive({
onBeforeUnmount, socketServe: socketClient.Instance,
watch, });
} from "vue"; const smsQueriesFlag = ref(false);
const pushFlag = ref(true);
import { formatTime, getDateRange, stopSpeak } from "@/utils/index"; //引入封装好的 const bottomTextRef = ref(); // 底部滚动组件引用
import useDraw from "@/utils/useDraw"; // 引入封装好的屏幕适配方法 // * 加载标识
import { WEEK, title, subtitle, moduleInfo, color } from "@/constant/index"; //引入封装的标题日期 const loading = ref<boolean>(true);
import datePicker from "@/components/datePicker/index.vue"; // * 时间内容
import { Menu, BellFilled, WarnTriangleFilled } from "@element-plus/icons-vue"; const timeInfo: any = reactive({
//页面组件 setInterval: 0,
import eventStatistics from "./components/eventStatistics.vue"; //统计事件 dateDay: "",
import endpointStatistics from "./components/endpointStatistics.vue"; //终端在线统计 dateYear: "",
import sendTrends from "./components/sendTrends.vue"; //终端在线统计 dateWeek: "",
import informationTable from "./components/informationTable.vue"; //实时暂态信息 });
import alarm from "./components/alarm.vue"; //实时暂态信息 const timeType = ref(3);
import Map from "./components/bdMap.vue"; //地图 const reportForms = ref();
import smsQueries from "./components/smsQueries.vue"; // //短信查询 // 适配处理
import bottomText from "./components/bottomText.vue"; //边框组件 const { appRef, calcRate, windowDraw, unWindowDraw } = useDraw();
import socketClient from "@/utils/webSocketClient"; // 连接webSocket客户端
import { useStore } from "vuex"; const init = () => {
import Config from "./components/config.vue"; if (!dataSocket.socketServe) {
console.error("WebSocket 客户端实例不存在");
const store = useStore(); return;
const smsQueriesRef = ref(); // 短信查询组件引用 }
const endpointStatisticsRef = ref(); // 终端在线统计组件引用 dataSocket.socketServe.connect(new Date().getTime());
const informationTableRef = ref(); // 实时暂态信息组件引用 dataSocket.socketServe.registerCallBack("message", (res: any) => {
const sendTrendsRef = ref(); pushFlag.value = true;
const ConfigRef = ref(); inquire();
const eventStatisticsRef = ref(); bottomTextRef.value?.updateData(res);
const alarmRef = ref(); });
const mapRef = ref(); };
//开始创建webSocket客户端 // 打开重要告警弹框
const dataSocket = reactive({ const drawerClick = () => {
socketServe: socketClient.Instance, bottomTextRef.value?.openDrawer();
}); };
const smsQueriesFlag = ref(false);
const pushFlag = ref(true); // 生命周期
const bottomTextRef = ref(); // 底部滚动组件引用 onMounted(() => {
// * 加载标识 stopSpeak();
const loading = ref<boolean>(true);
// * 时间内容 cancelLoading();
const timeInfo: any = reactive({
setInterval: 0, handleTime();
dateDay: "", // todo 屏幕适应
dateYear: "", windowDraw();
dateWeek: "", calcRate();
}); setTimeout(() => {
const timeType = ref(3); store.dispatch("setConfig");
// 适配处理 }, 500);
const { appRef, calcRate, windowDraw, unWindowDraw } = useDraw(); init();
// 连接webSocket客户端 });
const init = () => { const handleCurrentChange = (val: string) => {
if (!dataSocket.socketServe) { mapRef.value?.setIcon(val);
console.error("WebSocket 客户端实例不存在"); };
return; onUnmounted(() => {
} unWindowDraw();
dataSocket.socketServe.connect(new Date().getTime()); clearInterval(timeInfo.setInterval);
dataSocket.socketServe.registerCallBack("message", (res: any) => { });
pushFlag.value = true; onBeforeUnmount(() => {
inquire(); dataSocket.socketServe?.closeWs();
bottomTextRef.value?.updateData(res); });
});
}; // methods
// 打开重要告警弹框 // todo 处理 loading 展示
const drawerClick = () => { const cancelLoading = () => {
bottomTextRef.value?.openDrawer(); setTimeout(() => {
}; loading.value = false;
}, 500);
};
// 生命周期
onMounted(() => { // todo 处理时间监听
stopSpeak(); const handleTime = () => {
timeInfo.setInterval = setInterval(() => {
cancelLoading(); const date = new Date();
timeInfo.dateDay = formatTime(date, "HH: mm: ss");
handleTime(); timeInfo.dateYear = formatTime(date, "yyyy-MM-dd");
// todo 屏幕适应 timeInfo.dateWeek = WEEK[date.getDay()];
windowDraw(); }, 1000);
calcRate(); };
setTimeout(() => { // todo 处理菜单点击事件
store.dispatch("setConfig"); const handleClick = (type: string) => {
}, 500); smsQueriesFlag.value = true;
init();
}); setTimeout(() => {
const handleCurrentChange = (val: string) => { if (type === "3") {
mapRef.value?.setIcon(val); ConfigRef.value.open("系统配置");
}; }
onUnmounted(() => { if (type === "4") {
unWindowDraw(); reportForms.value.open("报表导出");
clearInterval(timeInfo.setInterval); }
}); }, 100);
onBeforeUnmount(() => { };
dataSocket.socketServe?.closeWs();
}); // 切换时间
const timeChangeInfo = async () => {
// methods inquire();
// todo 处理 loading 展示 };
const cancelLoading = () => { const inquireTimer: any = ref(null);
setTimeout(() => { const inquire = async () => {
loading.value = false; // 清除上一次的定时器
}, 500); if (inquireTimer.value) {
}; clearTimeout(inquireTimer.value);
}
// todo 处理时间监听
const handleTime = () => { // 设置新的定时器延迟300毫秒执行
timeInfo.setInterval = setInterval(() => { inquireTimer.value = setTimeout(async () => {
const date = new Date(); eventStatisticsRef.value.init(); //电能质量监测终端运行状态
timeInfo.dateDay = formatTime(date, "HH: mm: ss"); endpointStatisticsRef.value.init(); //各区域终端运行状态
timeInfo.dateYear = formatTime(date, "yyyy-MM-dd"); mapRef.value.init(); //地图
timeInfo.dateWeek = WEEK[date.getDay()]; informationTableRef.value.init(true); //暂降事件列表
}, 1000); sendTrendsRef.value.init();
};
// todo 处理菜单点击事件 alarmRef.value.init();
const handleClick = (type: string) => {
smsQueriesFlag.value = true; // 执行完毕后重置定时器变量
inquireTimer.value = null;
setTimeout(() => { }, 500);
if (type === "3") { };
ConfigRef.value.open("系统配置");
} watch(
}, 100); store.state,
}; (val) => {
timeChangeInfo();
// 切换时间 },
const timeChangeInfo = async () => { {
inquire(); deep: true,
}; }
const inquireTimer: any = ref(null); );
const inquire = async () => { </script>
// 清除上一次的定时器
if (inquireTimer.value) { <style lang="scss" scoped>
clearTimeout(inquireTimer.value); @use "@/assets/scss/index.scss";
}
.react-right {
// 设置新的定时器延迟300毫秒执行 width: 460px !important;
inquireTimer.value = setTimeout(async () => { }
eventStatisticsRef.value.init(); //电能质量监测终端运行状态
endpointStatisticsRef.value.init(); //各区域终端运行状态 .count {
mapRef.value.init(); //地图 position: absolute;
informationTableRef.value.init(true); //暂降事件列表 top: -8px;
sendTrendsRef.value.init(); left: 23px;
background-color: #ff2501;
alarmRef.value.init(); color: #fff;
height: 15px;
// 执行完毕后重置定时器变量 line-height: 15px;
inquireTimer.value = null; padding: 0 3px;
}, 500); border-radius: 40%;
}; font-size: 10px;
text-align: center;
watch(store.state, (val) => { }
timeChangeInfo()
}, { .icon {
deep: true, position: absolute;
}) top: 8px;
</script> right: 25px;
}
<style lang="scss" scoped>
@use "@/assets/scss/index.scss"; .significant {
.react-right { position: absolute;
width: 460px !important; top: 12px;
} left: 15px;
.count {
position: absolute; .count {
top: -8px; top: -3px;
left: 23px; left: 13px;
background-color: #ff2501; height: 13px;
color: #fff; }
height: 15px; }
line-height: 15px;
padding: 0 3px; .react-left {
border-radius: 40%; position: absolute;
font-size: 10px; left: 140px;
text-align: center; top: 30px;
} font-size: 18px;
.icon { line-height: 35px;
position: absolute; }
top: 8px;
right: 25px; .titles {
} margin-right: 10px;
.significant {
position: absolute; .react-right {
top: 12px; width: 50px !important;
left: 15px; line-height: 20px !important;
.count { font-size: 14px !important;
top: -3px; }
left: 13px; }
height: 13px;
} .iconfont {
} font-size: 35px !important;
.react-left { }
position: absolute; </style>
left: 140px;
top: 30px;
font-size: 18px;
line-height: 35px;
}
.titles {
margin-right: 10px;
.react-right {
width: 50px !important;
line-height: 20px !important;
font-size: 14px !important;
}
}
.iconfont {
font-size: 35px !important;
}
</style>