This commit is contained in:
Free-sss 2025-08-28 10:33:55 +08:00
parent 98c219c17b
commit a24d064951
9 changed files with 287 additions and 239 deletions

View File

@ -4,7 +4,22 @@ import { AlarmNowListConfig } from './index'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json' import dataJson from './data.json'
export const styleConfig = {
titleText: '实时报警',
titleOption: {
color: '#ffffff',
fontSize: '17px',
fontStyle: 'normal',
fontWeight: 'normal',
fontFamily: 'CustomFont',
},
headerOption: {
paddingLeft: 30,
paddingRight: 0,
paddingTop: 0,
paddingBottom: 0
}
}
export const option = { export const option = {
dataset: dataJson, dataset: dataJson,
rowNum: 4, rowNum: 4,

View File

@ -1,27 +1,29 @@
<template> <template>
<div class="content_box"> <SmallBorder :titleText="styleConfig.titleText" :title-option="styleConfig.titleOption"
<vue3-seamless-scroll v-if="option.dataset && option.dataset.length > 0" class="seamless" :list="option.dataset" :headerOption="styleConfig.headerOption">
:limitScrollNum="rowNum" :hover="true" :step="waitTime" :wheel="true" :isWatch="true"> <div class="content_box">
<div v-for="(item, index) in option.dataset" class="detail flex_column" :key="index"> <vue3-seamless-scroll v-if="option.dataset && option.dataset.length > 0" class="seamless" :list="option.dataset"
<div class="flex_v cursor" :style="!showRankNum ? { paddingLeft: 0 } : { paddingLeft: '24px' }" :limitScrollNum="rowNum" :hover="true" :step="waitTime" :wheel="true" :isWatch="true">
@click="handleOpenDialog(item)"> <div v-for="(item, index) in option.dataset" class="detail flex_column" :key="index">
<div v-if="index === 0 && showRankNum" class="levelOneIcon flex_c">{{ index + 1 }}</div> <div class="flex_v cursor" :style="!showRankNum ? { paddingLeft: 0 } : { paddingLeft: '24px' }"
<div v-else-if="index === 1 && showRankNum" class="levelTwoIcon flex_c">{{ index + 1 }}</div> @click="handleOpenDialog(item)">
<div v-else-if="showRankNum" class="levelOtherIcon flex_c">{{ index + 1 }}</div> <div v-if="index === 0 && showRankNum" class="levelOneIcon flex_c">{{ index + 1 }}</div>
<div class="item_content"> <div v-else-if="index === 1 && showRankNum" class="levelTwoIcon flex_c">{{ index + 1 }}</div>
<div class="item_header flex"> <div v-else-if="showRankNum" class="levelOtherIcon flex_c">{{ index + 1 }}</div>
<div class="item_level_text">{{ item.alarmLevel }}</div> <div class="item_content">
<div class="item_title_text">{{ item.alarmDescname }}</div> <div class="item_header flex">
</div> <div class="item_level_text">{{ item.alarmLevel }}</div>
<div class="item_footer"> <div class="item_title_text">{{ item.alarmDescname }}</div>
<div class="item_dept">{{ item.compName }}</div> </div>
<div class="item_time">{{ item.alarmTime }}</div> <div class="item_footer">
<div class="item_dept">{{ item.compName }}</div>
<div class="item_time">{{ item.alarmTime }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </vue3-seamless-scroll>
</vue3-seamless-scroll> <div v-else style="
<div v-else style="
color: #fff; color: #fff;
font-size: 14px; font-size: 14px;
width: 100%; width: 100%;
@ -30,109 +32,114 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
"> ">
暂无数据 暂无数据
</div>
<n-modal v-model:show="status.showDialog" :class="['custom-tab-modal']" title="报警详情" preset="card"
@on-after-leave="handleCloseDialog" :draggable="{ bounds: 'none' }" :style="{ width: '800px', height: '516px' }">
<div v-if="isLoading" style="display: flex; width: 100%; justify-content: center; margin-top: 16px">
<n-spin size="large" />
</div> </div>
<n-grid v-else cols="24" x-gap="12" style="overflow-y: auto; padding-bottom: 12px"> <n-modal v-model:show="status.showDialog" :class="['custom-tab-modal']" title="报警详情" preset="card"
<!-- <div class="enterBtn" @click="handleMeetingBtnClick">会商</div> --> @on-after-leave="handleCloseDialog" :draggable="{ bounds: 'none' }"
<!-- 左侧详情 --> :style="{ width: '800px', height: '516px' }">
<n-gi :span="14"> <div v-if="isLoading" style="display: flex; width: 100%; justify-content: center; margin-top: 16px">
<!-- 基础信息 --> <n-spin size="large" />
<div class="detail-item"> </div>
<label>报警时间</label> <n-grid v-else cols="24" x-gap="12" style="overflow-y: auto; padding-bottom: 12px">
<span>{{ convertTimestampToDateTime((status.selectedRow as any)?.alarmRecord.alarmTime) }}</span> <!-- <div class="enterBtn" @click="handleMeetingBtnClick">会商</div> -->
</div> <!-- 左侧详情 -->
<div class="detail-item"> <n-gi :span="14">
<label>处置状态</label> <!-- 基础信息 -->
<span style="color: #ff9100">{{ (status.selectedRow as any)?.handleStatus }}</span> <div class="detail-item">
</div> <label>报警时间</label>
<div class="detail-item"> <span>{{ convertTimestampToDateTime((status.selectedRow as any)?.alarmRecord.alarmTime) }}</span>
<label>风险等级</label>
<span :style="{ color: '' }">{{ (status.selectedRow as any)?.alarmRecord.riskIdentName }}</span>
</div>
<div class="detail-item">
<label>报警等级</label>
<span class="highlight" style="color: #ff5454">{{
(status.selectedRow as any)?.alarmRecord.alarmLevel
}}</span>
</div>
<div class="detail-item">
<label>风险描述</label>
<span class="highlight">{{ (status.selectedRow as any)?.alarmRecord.alarmDesc }}</span>
</div>
<div class="detail-item">
<label>报警区域</label>
<span>{{
(status.selectedRow as any)?.pathName
.substring(1, (status.selectedRow as any)?.pathName.length)
.replaceAll('/', '-') + (((status.selectedRow as any)?.subareaName && (status.selectedRow as
any)?.subareaName !== '') ? (status.selectedRow as any)?.subareaName : '')
}}</span>
</div>
<div class="detail-item" style="display: flex; flex-direction: column"
v-if="(status.selectedRow as any)?.camerainfos.length > 0">
<label>警情关联设备</label>
<div class="screenshot-placeholder">
<n-tabs type="line" animated>
<n-tab-pane v-for="(item, index) in (status.selectedRow as any)?.camerainfos" :name="item.deviceCode"
:tab="item.deviceName">
<div class="detail-item" style="display: flex; flex-direction: column">
<label>报警截图</label>
<div style="display: flex">
<img :src="'/awimg/' + removeProtocolIpPort((status.selectedRow as any)?.alarmRecord.originImg)"
style="width: 180px; margin-right: 16px" />
<img :src="'/awimg/' + removeProtocolIpPort((status.selectedRow as any)?.alarmRecord.alarmImg)"
style="width: 180px" />
</div>
</div>
<div class="detail-item" style="display: flex; flex-direction: column">
<label>警情录像回放</label>
<div class="screenshot-placeholder playback-placeholder" style="width: 350px; height: 200px">
<play-back :device-id="item.deviceCode" :start="new Date((status.selectedRow as any)?.startTime)"
:end="new Date((status.selectedRow as any)?.endTime)"></play-back>
</div>
</div>
<div class="detail-item" style="display: flex; flex-direction: column">
<label>视频实时画面</label>
<div class="screenshot-placeholder playback-placeholder" style="width: 350px; height: 200px">
<play-live :device-id="item.deviceCode"></play-live>
</div>
</div>
</n-tab-pane>
</n-tabs>
<br />
</div> </div>
</div> <div class="detail-item">
</n-gi> <label>处置状态</label>
<n-gi :span="10" style="height: 100%; display: flex; align-items: center"> <span style="color: #ff9100">{{ (status.selectedRow as any)?.handleStatus }}</span>
<n-timeline size="medium"> </div>
<n-timeline-item class="timeLineClass" v-for="(item, index) in (status.selectedRow as any)?.taskLogs" <div class="detail-item">
:key="index" type="info" :title="item.task_name" :content="item.task_detail" :time="item.task_begin" /> <label>风险等级</label>
</n-timeline> <span :style="{ color: '' }">{{ (status.selectedRow as any)?.alarmRecord.riskIdentName }}</span>
</n-gi> </div>
</n-grid> <div class="detail-item">
</n-modal> <label>报警等级</label>
</div> <span class="highlight" style="color: #ff5454">{{
(status.selectedRow as any)?.alarmRecord.alarmLevel
}}</span>
</div>
<div class="detail-item">
<label>风险描述</label>
<span class="highlight">{{ (status.selectedRow as any)?.alarmRecord.alarmDesc }}</span>
</div>
<div class="detail-item">
<label>报警区域</label>
<span>{{
(status.selectedRow as any)?.pathName
.substring(1, (status.selectedRow as any)?.pathName.length)
.replaceAll('/', '-') + (((status.selectedRow as any)?.subareaName && (status.selectedRow as
any)?.subareaName !== '') ? (status.selectedRow as any)?.subareaName : '')
}}</span>
</div>
<div class="detail-item" style="display: flex; flex-direction: column"
v-if="(status.selectedRow as any)?.camerainfos.length > 0">
<label>警情关联设备</label>
<div class="screenshot-placeholder">
<n-tabs type="line" animated>
<n-tab-pane v-for="(item, index) in (status.selectedRow as any)?.camerainfos" :name="item.deviceCode"
:tab="item.deviceName">
<div class="detail-item" style="display: flex; flex-direction: column">
<label>报警截图</label>
<div style="display: flex">
<img :src="'/awimg/' + removeProtocolIpPort((status.selectedRow as any)?.alarmRecord.originImg)"
style="width: 180px; margin-right: 16px" />
<img :src="'/awimg/' + removeProtocolIpPort((status.selectedRow as any)?.alarmRecord.alarmImg)"
style="width: 180px" />
</div>
</div>
<div class="detail-item" style="display: flex; flex-direction: column">
<label>警情录像回放</label>
<div class="screenshot-placeholder playback-placeholder" style="width: 350px; height: 200px">
<play-back :device-id="item.deviceCode"
:start="new Date((status.selectedRow as any)?.startTime)"
:end="new Date((status.selectedRow as any)?.endTime)"></play-back>
</div>
</div>
<div class="detail-item" style="display: flex; flex-direction: column">
<label>视频实时画面</label>
<div class="screenshot-placeholder playback-placeholder" style="width: 350px; height: 200px">
<play-live :device-id="item.deviceCode"></play-live>
</div>
</div>
</n-tab-pane>
</n-tabs>
<br />
</div>
</div>
</n-gi>
<n-gi :span="10" style="height: 100%; display: flex; align-items: center">
<n-timeline size="medium">
<n-timeline-item class="timeLineClass" v-for="(item, index) in (status.selectedRow as any)?.taskLogs"
:key="index" type="info" :title="item.task_name" :content="item.task_detail" :time="item.task_begin" />
</n-timeline>
</n-gi>
</n-grid>
</n-modal>
</div>
</SmallBorder>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// @ts-nocheck // @ts-nocheck
import { PropType, toRefs, shallowReactive, watch, ref, reactive, onMounted, onUnmounted } from 'vue' import { PropType, toRefs, shallowReactive, watch, computed, ref, reactive, onMounted, onUnmounted } from 'vue'
import { CreateComponentType } from '@/packages/index.d' import { CreateComponentType } from '@/packages/index.d'
import { useChartDataFetch } from '@/hooks' import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { option as configOption } from './config' // config import { option as configOption, styleConfig } from './config'
import { Vue3SeamlessScroll } from 'vue3-seamless-scroll' import { Vue3SeamlessScroll } from 'vue3-seamless-scroll'
import PlayBack from '@/components/Pages/yushiVideo/playback.vue' import PlayBack from '@/components/Pages/yushiVideo/playback.vue'
import PlayLive from '@/components/Pages/yushiVideo/playLive.vue' import PlayLive from '@/components/Pages/yushiVideo/playLive.vue'
import axiosInstance from '@/api/axios' import axiosInstance from '@/api/axios'
import VChart from 'vue-echarts' import VChart from 'vue-echarts'
import SmallBorder from '../SmallBorder01Co/index.vue'
import { height } from 'dom-helpers'
import axios from 'axios'
const props = defineProps({ const props = defineProps({
chartConfig: { chartConfig: {
type: Object as PropType<CreateComponentType & typeof option>, type: Object as PropType<CreateComponentType & typeof option>,
@ -244,19 +251,19 @@ const handleCloseDialog = () => {
const fetchRecentAlarms = async () => { const fetchRecentAlarms = async () => {
try { try {
isLoading.value = true; isLoading.value = true;
const res = await axios.get(
`/dev/space/getRecentAlarms`, // dev
{
responseType: 'json',
}
);
const res = await axiosInstance({ if (res.data.state === true) {
method: 'GET',
url: `/space/getRecentAlarms`,
responseType: 'json'
});
if (res && res.value) {
let rawData = []; let rawData = [];
if (Array.isArray(res.value.items)) { if (Array.isArray(res.data.value.items)) {
rawData = res.value.items; rawData = res.data.value.items;
} else if (Array.isArray(res.value)) { } else if (Array.isArray(res.data.value)) {
rawData = res.value; rawData = res.data.value;
} }
const formattedData = rawData.map(item => { const formattedData = rawData.map(item => {
@ -303,13 +310,16 @@ onUnmounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.content_box { .content_box {
width: v-bind('w') + 'px'; z-index: -1;
height: v-bind('h') + 'px';
overflow: hidden;
} }
.seamless { .seamless {
// padding: 0 30px 14px 30px; z-index: 1;
height: 240px;
margin: 20px 10px 30px 10px;
overflow: hidden;
} }
.detail { .detail {
@ -336,9 +346,9 @@ onUnmounted(() => {
.flex_v { .flex_v {
display: flex; display: flex;
align-items: center; align-items: center;
background-color: #072230; // background-color: #0a0c1260;
border-bottom: 1.5px solid #123E54; border-bottom: 1.5px solid #123E54;
margin: 3px;
} }
.item_level_text { .item_level_text {

View File

@ -43,7 +43,7 @@ const otherConfig = {
} }
] ]
}, },
titleText: '今日数据', titleText: '报警统计',
titleOption: { titleOption: {
color: '#ffffff', color: '#ffffff',
fontSize: '17px', fontSize: '17px',
@ -60,7 +60,6 @@ const otherConfig = {
} }
} }
// ECharts配置 // ECharts配置
export const option = { export const option = {
...otherConfig, ...otherConfig,

View File

@ -1,9 +1,7 @@
{ {
"dimensions": [ "dimensions": [
"alarmLevel", "alarmLevel",
"count", "count"
"itemColor",
"borderColor"
], ],
"source": [ "source": [
{ {

View File

@ -30,7 +30,7 @@ import {
TitleComponent, GraphicComponent TitleComponent, GraphicComponent
} from 'echarts/components' } from 'echarts/components'
import axiosInstance from '@/api/axios' import axios from 'axios'
const props = defineProps({ const props = defineProps({
@ -180,24 +180,26 @@ function generateTimeRange(period: string): { startTime: string; endTime: string
} }
const fetchAlarmData = async ({ startTime, endTime }: { startTime: string; endTime: string }) => { const fetchAlarmData = async ({ startTime, endTime }: { startTime: string; endTime: string }) => {
try { try {
const res = await axiosInstance({ const res = await axios.get(
method: 'GET', `/dev/space/getNumberByAlarmLevel`, // dev
url: `/space/getNumberByAlarmLevel`, {
params: { startTime, endTime }, params: { startTime, endTime }, // params
responseType: 'json', responseType: 'json',
}); }
if (res && (res as any).value && Array.isArray((res as any).value)) { );
if (res.data.state === true) {
console.log(res.data)
return { return {
dimensions: props.chartConfig.option.dataset.dimensions, // dimensions: props.chartConfig.option.dataset.dimensions,
source: (res as any).value, source: res.data.value,
}; };
} else { } else {
console.warn('API returned unexpected data structure:', res); console.error('API调用失败:', res.data)
return undefined; return {}
} }
} catch (error) { } catch (error) {
console.error('Error fetching alarm data:', error); console.error('获取图表数据失败:', error)
return undefined; // undefined return {}
} }
}; };

View File

@ -213,10 +213,10 @@
<!-- ... 省略以上 --> <!-- ... 省略以上 -->
<div class="content"> <div class="content">
<div class="header" :style="{ <div class="header" :style="{
paddingLeft: (props.headerOption?.paddingLeft ?? '0') + 'px', marginLeft: (props.headerOption?.paddingLeft ?? '0') + 'px',
paddingTop: (props.headerOption?.paddingTop ?? '0') + 'px', marginTop: (props.headerOption?.paddingTop ?? '0') + 'px',
paddingRight: (props.headerOption?.paddingRight ?? '0') + 'px', marginRight: (props.headerOption?.paddingRight ?? '0') + 'px',
paddingBottom: (props.headerOption?.paddingBottom ?? '0') + 'px' marginBottom: (props.headerOption?.paddingBottom ?? '0') + 'px'
}"> }">
<span class="title" :style="props.titleOption"> <span class="title" :style="props.titleOption">
{{ props.titleText }} {{ props.titleText }}
@ -224,7 +224,7 @@
<span class="right-select"> <span class="right-select">
<ConsumSelect v-show="selectOption.show" :options="selectOption.dataset" <ConsumSelect v-show="selectOption.show" :options="selectOption.dataset"
:selectedValue="selectOption.selectValue" @change="handleSelectChange" :selectedValue="selectOption.selectValue" @change="handleSelectChange"
:select-style-config="selectStyleConfig" /> select-style-config="selectStyleConfig" />
</span> </span>
</div> </div>
<div class="body"> <div class="body">
@ -302,7 +302,7 @@ const handleSelectChange = (value: any) => {
height: 100%; height: 100%;
// --- --- // --- ---
$border-outside-padding: 5px; // $border-outside-padding: 5px; //
$bg-top-offset: 0px; // SVGtop $bg-top-offset: -10px; // SVGtop
padding: $border-outside-padding; padding: $border-outside-padding;
box-sizing: border-box; box-sizing: border-box;
@ -319,6 +319,7 @@ const handleSelectChange = (value: any) => {
// background-color: rgba(255, 0, 0, 0.2); // background-color: rgba(255, 0, 0, 0.2);
.svg-border { .svg-border {
z-index: 2;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -334,7 +335,7 @@ const handleSelectChange = (value: any) => {
width: 100%; width: 100%;
// = 100% - top // = 100% - top
height: calc(100% - #{$bg-top-offset}); height: calc(100% - #{$bg-top-offset});
object-fit: scale-down; object-fit: fill;
z-index: -2; z-index: -2;
} }
@ -352,25 +353,37 @@ const handleSelectChange = (value: any) => {
// background-color: antiquewhite; // background-color: antiquewhite;
.header { .header {
margin-top: 5px; // margin-top: 8px;
margin-bottom: 0px; margin-bottom: 0px;
padding: 0 20px; padding: 0 20px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-left: 25px;
padding-top: 5px;
// background-color: aliceblue;
.title { .title {
font-family: 'CustomFont'; font-family: 'CustomFont';
letter-spacing: 1px; letter-spacing: 1px;
color: #ffffff;
font-size: 17px; font-size: 17px;
padding-left: 45px; padding-left: 45px;
} }
} }
.body { .body {
// padding-top: 0px;
// margin-top: 30px;
// background-color: aquamarine;
// display: flex;
// align-content: center;
// justify-content: center;
margin-top: 0px; margin-top: 0px;
flex: 1; // flex: 1;
width: 100%;
height: 100%;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
// background-color: lightblue; // background-color: lightblue;

View File

@ -3,5 +3,6 @@ import { PieCircleCommenConfig } from './PieCircleCommen'
import { AlarmNowListConfig } from './AlarmNowList' import { AlarmNowListConfig } from './AlarmNowList'
import { LineDropdownConfig } from './LineDropdown/index' import { LineDropdownConfig } from './LineDropdown/index'
import { SmallBorder01CoConfig } from './SmallBorder01Co' import { SmallBorder01CoConfig } from './SmallBorder01Co'
export default [MapConfig, LineDropdownConfig, PieCircleCommenConfig, AlarmNowListConfig, SmallBorder01CoConfig] import { yushiVideoConfig } from './yushiVideo'
export default [MapConfig, yushiVideoConfig, LineDropdownConfig, PieCircleCommenConfig, AlarmNowListConfig, SmallBorder01CoConfig]

View File

@ -59,8 +59,7 @@
import { PropType, computed, ref, onMounted, onUnmounted } from 'vue' import { PropType, computed, ref, onMounted, onUnmounted } from 'vue'
import { option as configOption } from './config' import { option as configOption } from './config'
import SmallBorder from '../SmallBorder/index.vue' import SmallBorder from '../SmallBorder/index.vue'
import axiosInstance from '@/api/axios' import axios from 'axios'
const props = defineProps({ const props = defineProps({
chartConfig: { chartConfig: {
type: Object as PropType<{ option: typeof configOption }>, type: Object as PropType<{ option: typeof configOption }>,
@ -88,19 +87,20 @@ const convertTimestampToDateTime = (timestamp: number | string) => {
const fetchRecentAlarms = async () => { const fetchRecentAlarms = async () => {
try { try {
const res = await axios.get(
`/dev/space/getRecentAlarms`, // dev
{
responseType: 'json',
}
);
const res: any = await axiosInstance({
method: 'GET',
url: `/space/getRecentAlarms`,
responseType: 'json'
});
if (res && res?.value) { if (res.data.state === true) {
let rawData = []; let rawData = [];
if (Array.isArray(res.value.items)) { if (Array.isArray(res.data.value.items)) {
rawData = res.value.items; rawData = res.data.value.items;
} else if (Array.isArray(res.value)) { } else if (Array.isArray(res.data.value)) {
rawData = res.value; rawData = res.data.value;
} }
const formattedData = rawData.map((item: any) => { const formattedData = rawData.map((item: any) => {

View File

@ -24,7 +24,7 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent, GraphicComponent } from 'echarts/components' import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent, GraphicComponent } from 'echarts/components'
import CustomSelect from './select.vue' import CustomSelect from './select.vue'
import dataJson from './data.json' import dataJson from './data.json'
import axiosInstance from '@/api/axios'; import axios from 'axios'
const props = defineProps({ const props = defineProps({
themeSetting: { themeSetting: {
@ -61,24 +61,27 @@ function calculatePercentage(value: number, total: number) {
} }
const updateChartData = (newData: any) => { const updateChartData = (newData: any) => {
if (!newData) return if (!newData) {
//
props.chartConfig.option.dataset = { dimensions: ['name', 'value'], source: [] };
} else {
props.chartConfig.option.dataset = newData;
}
//
props.chartConfig.option.dataset = newData
const total = calculateTotal(newData) const total = calculateTotal(props.chartConfig.option.dataset)
if (props.chartConfig.option.graphic?.[0]) { if (props.chartConfig.option.graphic?.[0]) {
props.chartConfig.option.graphic[0].style.text = total.toString() props.chartConfig.option.graphic[0].style.text = total.toString()
} }
// " " // " "
props.chartConfig.option.legend.formatter = (name: string) => { props.chartConfig.option.legend.formatter = (name: string) => {
const item = newData.source.find((it: any) => it.name === name) const item = props.chartConfig.option.dataset.source.find((it: any) => it.name === name)
if (!item) return name if (!item) return name
const val = item.value ?? 0 const val = item.value ?? 0
let p = calculatePercentage(val, total) let p = calculatePercentage(val, total)
if(Number(p)<10){ if (Number(p) < 10) {
p=' '+p p = ' ' + p
} }
return `{name|${name}}{value|${p}}{unit|%}`; return `{name|${name}}{value|${p}}{unit|%}`;
} }
@ -143,12 +146,13 @@ const updateChartData = (newData: any) => {
} }
} }
const totalValue = newData.source.reduce((total: number, item: any) => { const totalValue = props.chartConfig.option.dataset.source.reduce((total: number, item: any) => {
return total + (item.value || 0) return total + (item.value || 0)
}, 0) }, 0)
props.chartConfig.option.series[props.chartConfig.option.series.length-1].label.formatter = `{a|${totalValue}}\n\n{b|总数}` props.chartConfig.option.series[props.chartConfig.option.series.length - 1].label.formatter = `{a|${totalValue}}\n\n{b|总数}`
} }
// watch
watch( watch(
() => props.chartConfig.option.dataset, () => props.chartConfig.option.dataset,
newData => { newData => {
@ -163,18 +167,10 @@ const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (n
updateChartData(newData) updateChartData(newData)
}) })
onMounted(async () => { /**
// * 格式化日期时间为 ISO 8601 字符串 (YYYY-MM-DDTHH:mm:ss.SSS)
const timeRange = generateTimeRange(props.chartConfig.option.dateTime.selectValue); * JavaScript Date 对象只支持毫秒精度
const fetchedData = await fetchAlarmData(timeRange); */
if (fetchedData) {
updateChartData(fetchedData);
} else {
// 使
updateChartData(props.chartConfig.option.dataset);
}
});
//
function formatDateTime(date: Date): string { function formatDateTime(date: Date): string {
const year = date.getFullYear(); const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0'); const month = (date.getMonth() + 1).toString().padStart(2, '0');
@ -182,94 +178,108 @@ function formatDateTime(date: Date): string {
const hours = date.getHours().toString().padStart(2, '0'); const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0'); const seconds = date.getSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; const milliseconds = date.getMilliseconds().toString().padStart(3, '0'); //
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}`; // 使 'T'
} }
/**
* 根据时间段生成开始和结束时间范围
* 结束时间设置为该时间段的最后一天的 23:59:59.999
*/
function generateTimeRange(period: string): { startTime: string; endTime: string } { function generateTimeRange(period: string): { startTime: string; endTime: string } {
const now = new Date(); const now = new Date(); //
let startTime: Date;
let endTime: Date; let startTimeDate: Date;
endTime = new Date(now); let endTimeDate: Date;
endTime.setDate(endTime.getDate() + 1);
endTime.setHours(0, 0, 0, 0);
switch (period) { switch (period) {
case 'day': case 'day':
startTime = new Date(now); startTimeDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0); // 00:00:00.000
startTime.setHours(0, 0, 0, 0); endTimeDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999); // 23:59:59.999
break; break;
case 'week': case 'week':
startTime = new Date(now); // 7
startTime.setDate(startTime.getDate() - 7); startTimeDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6, 0, 0, 0, 0);
startTime.setHours(0, 0, 0, 0); // 23:59:59.999
endTimeDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
break; break;
case 'month': case 'month':
startTime = new Date(now.getFullYear(), now.getMonth(), 1); startTimeDate = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0); // 00:00:00.000
startTime.setHours(0, 0, 0, 0); // 23:59:59.999
endTimeDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
break; break;
case 'quarter': case 'quarter':
const currentMonth = now.getMonth(); const currentMonth = now.getMonth();
const quarterStartMonth = Math.floor(currentMonth / 3) * 3; const quarterStartMonth = Math.floor(currentMonth / 3) * 3;
startTime = new Date(now.getFullYear(), quarterStartMonth, 1); startTimeDate = new Date(now.getFullYear(), quarterStartMonth, 1, 0, 0, 0, 0); // 00:00:00.000
startTime.setHours(0, 0, 0, 0); // 23:59:59.999
endTimeDate = new Date(now.getFullYear(), quarterStartMonth + 3, 0, 23, 59, 59, 999);
break; break;
case 'year': case 'year':
startTime = new Date(now.getFullYear(), 0, 1); startTimeDate = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0); // 00:00:00.000
startTime.setHours(0, 0, 0, 0); // 23:59:59.999 ()
endTimeDate = new Date(now.getFullYear(), 12, 0, 23, 59, 59, 999);
break; break;
default: default: // 'day'
startTime = new Date(now); startTimeDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
startTime.setHours(0, 0, 0, 0); endTimeDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
break; break;
} }
return { return {
startTime: formatDateTime(startTime), startTime: formatDateTime(startTimeDate),
endTime: formatDateTime(endTime), endTime: formatDateTime(endTimeDate),
}; };
} }
//
async function fetchAlarmData({ startTime, endTime }: { startTime: string; endTime: string }) { async function fetchAlarmData({ startTime, endTime }: { startTime: string; endTime: string }) {
try { try {
const res = await axiosInstance({ const res = await axios.get(
method: 'GET', `/dev/getAlarmdataRecord`,
url: '/getAlarmdataRecord', {
params: { startTime, endTime }, params: { startTime, endTime }, //
responseType: 'json', responseType: 'json',
}); }
if (Array.isArray(res)) { );
if (res.status === 200 && res.data) {
console.log("API 响应数据:", res.data);
// //
return { return {
dimensions: ['name', 'value'], dimensions: ['name', 'value'],
source: res.map((item: any) => ({ source: res.data.map((item: any) => ({
name: item.alarmLevel, name: item.alarmLevel,
value: item.alarmNun value: item.alarmNun
})) }))
}; };
} else { } else {
console.warn('API returned unexpected data structure:', res); console.warn('API 返回非预期数据结构或空数据:', res);
return undefined; return undefined;
} }
} catch (error) { } catch (error) {
console.error('Error fetching alarm data:', error); console.error('获取报警数据失败:', error);
return undefined; return undefined;
} }
} }
async function dataHandle(newData: any) {
if (!newData) { //
props.chartConfig.option.dataset = { dimensions: ['name', 'value'], source: [] };
updateChartData(props.chartConfig.option.dataset);
return;
}
updateChartData(newData);
}
const handleSelectChange = async (value: string) => {
props.chartConfig.option.dateTime.selectValue = value;
const { startTime, endTime } = generateTimeRange(value);
const fetchedData = await fetchAlarmData({ startTime, endTime });
await dataHandle(fetchedData);
};
onMounted(async () => { onMounted(async () => {
const timeRange = generateTimeRange(props.chartConfig.option.dateTime.selectValue); const timeRange = generateTimeRange(props.chartConfig.option.dateTime.selectValue);
console.log("onMounted - 请求参数:", timeRange); //
const fetchedData = await fetchAlarmData(timeRange); const fetchedData = await fetchAlarmData(timeRange);
await dataHandle(fetchedData); updateChartData(fetchedData); // updateChartData
}); });
//
const handleSelectChange = async (value: string) => {
props.chartConfig.option.dateTime.selectValue = value; //
const timeRange = generateTimeRange(value);
console.log("handleSelectChange - 请求参数:", timeRange); //
const fetchedData = await fetchAlarmData(timeRange);
updateChartData(fetchedData); // updateChartData
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>