You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
486 lines
14 KiB
486 lines
14 KiB
<template> |
|
<div class="fault-rule-drawer"> |
|
<el-drawer v-model="isShowDrawer" :title="title" direction="rtl" size="100%" |
|
modal-class="model-dev-opn" |
|
:before-close="handleBeforeClose" @opened="onDrawerOpened"> |
|
<main class="wh-full flex"> |
|
<EdfsWrap title="点位组" isCollapse :collapsed="collapsed" @collapse="onCollapse" |
|
:style="{width: `${collapsed ? 0 : 600}px`}"> |
|
<PointGroupTree |
|
v-if="isShowDrawer" |
|
:data="pointGroup" |
|
@device-select="onGroupChange" |
|
:groupChangeLoading="groupChangeLoading" |
|
@onchangePoints="onchangePoints" |
|
:pointList="pointList" |
|
:loadingPoints="loadingPoints" |
|
:isTransfer="props.isTransfer" |
|
:siteInfo="props.siteInfo" |
|
ref="pointGroupTreeRef"/> |
|
</EdfsWrap> |
|
<div class="flex-1 p-4 h-full overflow-hidden relative" v-loading="loading" |
|
element-loading-text="数据加载中请耐心等待..." |
|
> |
|
<div |
|
class="absolute inset-0 z-[99] flex items-center justify-center bg-white/80 backdrop-blur-sm" |
|
v-if="isFetching" |
|
> |
|
<div class="w-full max-w-[600px] space-y-10 p-6"> |
|
<el-scrollbar height="70vh" max-height="600px"> |
|
<div |
|
v-for="deviceInfo in pointInfoData" |
|
:key="deviceInfo.id" |
|
class="space-y-3 text-center bg-white/90 p-5 rounded-xl shadow" |
|
> |
|
<div class="font-semibold text-gray-700 text-lg"> |
|
{{ chartGroupMap?.get(deviceInfo.id)?.cnName }} 数据加载中… |
|
</div> |
|
|
|
<el-progress |
|
:text-inside="true" |
|
:stroke-width="20" |
|
:percentage="deviceInfo.progress" |
|
/> |
|
|
|
<div class="text-gray-600 text-base">已查询 |
|
<span class="text-green-600 font-semibold"> |
|
{{ deviceInfo.fetchLimit }} |
|
</span>/<span class="text-blue-600 font-semibold">{{ |
|
deviceInfo.total |
|
}}</span>,剩余<span |
|
class="text-red-500 font-semibold">{{ |
|
deviceInfo.total - deviceInfo.fetchLimit |
|
}}</span> |
|
</div> |
|
</div> |
|
|
|
</el-scrollbar> |
|
</div> |
|
</div> |
|
|
|
<div class="flex w-full items-center gap-16"> |
|
<div class="w-400px"> |
|
<el-date-picker |
|
v-model="time" |
|
type="datetimerange" |
|
value-format="YYYY-MM-DD HH:mm:ss" |
|
range-separator="到" |
|
:default-value="getBeforeMonth" |
|
start-placeholder="开始时间" |
|
end-placeholder="结束时间" |
|
:disabled-date="disabledDate" |
|
/> |
|
</div> |
|
<el-button type="primary" @click="loadChardData2">查询数据</el-button> |
|
查询最近 |
|
<el-input-number :key="refreshKey" v-model="num" :min="1" :max="diffHours" |
|
@input="handleInput"/> |
|
小时 |
|
</div> |
|
<NewDataChart v-if="isShowChart" :chart-datas="chartData" :legends="legends" |
|
:axis-data="Array.from(axisData)" ref="chartRef"/> |
|
</div> |
|
</main> |
|
</el-drawer> |
|
</div> |
|
</template> |
|
|
|
<script setup lang="ts"> |
|
import NewDataChart from '../../component/newDataChart.vue' |
|
import PointGroupTree from './PointGroupTree.vue' |
|
import type { IMyPoint, IOfflineDevice, IOnlineDevice } from '../../type' |
|
import { |
|
getDeviceDetails, |
|
getPointGroup, |
|
getPoints, |
|
type IGetDeviceDataParams, |
|
type IPointGroupOV, |
|
type IPointGroupParams, |
|
type IPointsParams, |
|
type ISite, |
|
} from '@/api/module/transfer' |
|
import { useMessage } from '@/composables/useMessage' |
|
import dayjs from 'dayjs' |
|
import EdfsWrap from "@/components/Edfs-wrap.vue"; |
|
import { bms_cellRewriteName } from "@/views/stationData/utils"; |
|
import { debounce } from 'lodash-es' |
|
import { |
|
type DeviceFetchInfo, |
|
useFetcher |
|
} from "@/views/stationData/transfer/components/useFetcher"; |
|
|
|
type ChartParamsMap = { |
|
name: string |
|
columns: IMyPoint[] |
|
} |
|
|
|
const time = ref<[string, string]>() |
|
|
|
const env = import.meta.env |
|
|
|
const num = ref<number>() |
|
const diffHours = ref<number>() |
|
const refreshKey = ref(0) |
|
|
|
const handleInput = debounce(async (val: number) => { |
|
if (diffHours.value && val > diffHours.value) { |
|
num.value = diffHours.value |
|
refreshKey.value++ |
|
await nextTick() |
|
} else if (val < 1) { |
|
num.value = 1 |
|
refreshKey.value++ |
|
await nextTick() |
|
} else { |
|
num.value = val |
|
} |
|
|
|
const device = curDevice.value as IOfflineDevice |
|
const endTime = |
|
device.end_time ? |
|
dayjs(device.end_time).valueOf() : dayjs().valueOf() |
|
|
|
const startTime = dayjs(endTime).subtract(num.value, 'hour').valueOf() |
|
time.value = [dayjs(startTime).format('YYYY-MM-DD HH:mm:ss'), dayjs(endTime).format('YYYY-MM-DD HH:mm:ss')] |
|
}, 300) |
|
|
|
|
|
const isShowDrawer = defineModel<boolean>() |
|
const title = computed(() => |
|
( |
|
props.isTransfer ? |
|
'数据详情' : env.VITE_APP_ENV === 'local' |
|
? `已导出数据详情` : '已导入数据详情' |
|
) |
|
) |
|
const message = useMessage() |
|
|
|
const pointGroupTreeRef = ref<InstanceType<typeof PointGroupTree>>() |
|
const props = defineProps<{ |
|
siteInfo: ISite | null |
|
isTransfer: boolean |
|
}>() |
|
|
|
const emit = defineEmits(['on-save']) |
|
|
|
const pointList = ref<IMyPoint[]>([]) |
|
|
|
const curDevice = ref<IOfflineDevice | IOnlineDevice>() |
|
|
|
async function open(device: IOfflineDevice | IOnlineDevice) { |
|
curDevice.value = device |
|
onCollapse(false) |
|
await loadPointGroup() |
|
.then(async () => { |
|
await loadDeviceDetails() |
|
}) |
|
.catch(() => { |
|
message.error('获取点位组数据失败') |
|
fullscreenLoading.value?.close() |
|
}) |
|
const deviceOff = curDevice.value as IOfflineDevice |
|
if (!deviceOff.start_time || !deviceOff.end_time) { |
|
diffHours.value = 168 // 默认一周时间 |
|
return |
|
} |
|
const start = dayjs(deviceOff.start_time) |
|
const end = dayjs(deviceOff.end_time) |
|
diffHours.value = end.diff(start, "hour") + 1; |
|
} |
|
|
|
|
|
const loadingPoints = ref(false) |
|
|
|
async function loadPoints() { |
|
if (!curGroup.value?.type) { |
|
message.error('请先选择点位组') |
|
return |
|
} |
|
const params: IPointsParams = { |
|
type: curGroup.value.type, |
|
} |
|
if (props.isTransfer) { |
|
const onlineDevice = curDevice.value as IOnlineDevice |
|
params.isLocal = false |
|
params.host = onlineDevice.clientIp |
|
} else { |
|
const offlineDevice = curDevice.value as IOfflineDevice |
|
params.sn = offlineDevice.sn |
|
params.site = props.siteInfo!.name |
|
params.isLocal = true |
|
} |
|
loadingPoints.value = true |
|
const res = await getPoints(params) |
|
if (res.code === 0) { |
|
const data = Array.isArray(res?.data) ? res.data : [] |
|
pointList.value = data.map((i: any) => ({ |
|
label: i.cnName, |
|
addr: i.addr, |
|
unit: i.unit || '', |
|
parentName: curGroupName.value, |
|
})) |
|
} |
|
loadingPoints.value = false |
|
return res |
|
} |
|
|
|
async function loadDeviceDetails() { |
|
if (!fullscreenLoading.value) { |
|
openFullScreen() |
|
} |
|
|
|
const pointsRes = await loadPoints() |
|
if (!pointsRes || pointsRes.code !== 0) { |
|
message.error('获取点位数据失败') |
|
fullscreenLoading.value?.close() |
|
return |
|
} |
|
isShowDrawer.value = true |
|
groupChangeLoading.value = false |
|
fullscreenLoading.value?.close() |
|
} |
|
|
|
const chartData = reactive(new Map<string, any[]>()) |
|
const axisData = new Set<string>() |
|
const legends = ref<{ addr: string; label: string, unit: string }[]>([]) |
|
|
|
const loadingChart = ref(false) |
|
const loading = ref(false) |
|
|
|
async function loadPointChartData(params: IGetDeviceDataParams, abort?: AbortController) { |
|
return await getDeviceDetails(params, abort) |
|
} |
|
|
|
function buildOptions(groupId: string): Omit<IGetDeviceDataParams, 'limit' | 'offset'> { |
|
const timeArr = time.value as [string, string] |
|
const currentGroup = chartParamsMap.get(groupId) |
|
if (!currentGroup) { |
|
return {} as Omit<IGetDeviceDataParams, 'limit' | 'offset'> |
|
} |
|
const options: Omit<IGetDeviceDataParams, 'limit' | 'offset'> = { |
|
columns: ['ts', ...currentGroup.columns.map(column => column.addr)], |
|
name: groupId, |
|
startTime: timeArr[0], |
|
endTime: timeArr[1], |
|
isLocal: !props.isTransfer, |
|
host: props.isTransfer ? (curDevice.value as IOnlineDevice).clientIp : '', |
|
} |
|
|
|
if (env.VITE_APP_ENV !== 'local' || !props.isTransfer) { |
|
options.site_id = props.siteInfo!.name || '' |
|
options.device_id = curDevice.value?.sn || '' |
|
} |
|
return options |
|
} |
|
|
|
const pointInfoData = ref<DeviceFetchInfo[]>([]) |
|
const { devices, isFetching, startFetching, abortAll } = useFetcher(loadPointChartData) |
|
|
|
watch(devices, (newValue) => { |
|
if (!newValue) return |
|
pointInfoData.value = newValue |
|
loading.value = false |
|
}, { deep: true }) |
|
|
|
async function loadChardData2() { |
|
loading.value = true |
|
clearData() |
|
const groupIds = Array.from(chartParamsMap.keys()) |
|
if (!groupIds.length) { |
|
loading.value = false |
|
message.error('请先选择点位') |
|
return |
|
} |
|
await startFetching(groupIds, buildOptions, 1000) |
|
if (!devices.value || !devices.value.length) { |
|
loading.value = false |
|
clearData() |
|
message.error('获取设备数据失败') |
|
return |
|
} |
|
for (const device of devices.value) { |
|
setChartData2(device.id, device.data) |
|
} |
|
} |
|
|
|
function setChartData2(groupName: string, data: any[]) { |
|
const chartParams = chartParamsMap.get(groupName) |
|
if (!chartParams) return |
|
|
|
for (const column of chartParams.columns) { |
|
const groupInfo = chartGroupMap.get(groupName) |
|
if (!groupInfo) continue |
|
legends.value.push({ |
|
addr: `${groupName}-${column.addr}`, |
|
label: `${groupInfo.cnName}-${column.label}`, |
|
unit: column.unit || '' |
|
}) |
|
} |
|
|
|
data.forEach((data: any[]) => { |
|
const [ts, val, addr] = data |
|
const time = dayjs(Number(ts)).format('YYYY-MM-DD HH:mm:ss.SSS') |
|
if (addr) { |
|
const colData = chartData.get(`${groupName}-${addr}`) |
|
const value = Number(Number(val).toFixed(5)) |
|
if (colData) { |
|
colData.push([time, value]) |
|
} else { |
|
chartData.set(`${groupName}-${addr}`, [[time, value]]) |
|
} |
|
} |
|
}) |
|
} |
|
|
|
function handleBeforeClose(done: () => void) { |
|
ElMessageBox.confirm('你确定要关闭吗?') |
|
.then(() => { |
|
if (isFetching) { |
|
abortAll() |
|
} |
|
time.value = undefined |
|
isShowDrawer.value = false |
|
clearData() |
|
fullscreenLoading.value = null |
|
chartParamsMap.clear() |
|
chartGroupMap.clear() |
|
done() |
|
}) |
|
.catch(() => { |
|
|
|
}) |
|
} |
|
|
|
function clearData() { |
|
legends.value = [] |
|
pointInfoData.value = [] |
|
chartData.clear() |
|
} |
|
|
|
function resetChartStatus() { |
|
loadingChart.value = false |
|
nextTick(() => { |
|
isShowChart.value = true |
|
}) |
|
} |
|
|
|
const chartRef = ref<InstanceType<typeof NewDataChart>>() |
|
const isShowChart = ref(false) |
|
|
|
function onDrawerOpened() { |
|
nextTick(() => { |
|
isShowChart.value = true |
|
}) |
|
} |
|
|
|
const fullscreenLoading = ref<any>(null) |
|
const openFullScreen = () => { |
|
fullscreenLoading.value = ElLoading.service({ |
|
lock: true, |
|
text: '数据加载中,请稍后...', |
|
background: 'rgba(255, 255, 255, 0.8)', |
|
}) |
|
} |
|
|
|
const pointGroup = ref<IPointGroupOV[]>([]) |
|
const curGroup = ref<IPointGroupOV>() |
|
const curGroupName = ref<string>() |
|
|
|
async function loadPointGroup() { |
|
const params: IPointGroupParams = {} |
|
if (props.isTransfer) { |
|
const onlineDevice = curDevice.value as IOnlineDevice |
|
params.isLocal = false |
|
params.host = onlineDevice.clientIp |
|
} else { |
|
const offlineDevice = curDevice.value as IOfflineDevice |
|
params.sn = offlineDevice.sn |
|
params.site = props.siteInfo!.name |
|
params.isLocal = true |
|
} |
|
|
|
const res = await getPointGroup(params) |
|
if (res.code === 0) { |
|
curGroup.value = res.data[0] |
|
curGroupName.value = res.data[0]?.name |
|
pointGroup.value = Array.isArray(res?.data) ? bms_cellRewriteName(res.data) : [] |
|
return Promise.resolve() |
|
} else { |
|
return Promise.reject() |
|
} |
|
} |
|
|
|
const groupChangeLoading = ref<boolean>(false) |
|
|
|
function onGroupChange(item: IPointGroupOV) { |
|
console.log(item) |
|
if (!item?.type) { |
|
return |
|
} |
|
groupChangeLoading.value = true |
|
curGroup.value = item |
|
curGroupName.value = item.name |
|
loadDeviceDetails() |
|
} |
|
|
|
|
|
const chartParamsMap = new Map<string, ChartParamsMap>() |
|
const chartGroupMap = new Map<string, IPointGroupOV>() |
|
|
|
function onchangePoints(checkPoints: IMyPoint[]) { |
|
if (!curGroup?.value || !curGroupName?.value) return message.error('请选择设备') |
|
|
|
const currentCheckPoints = checkPoints.filter((r: any) => r.parentName === curGroupName.value) |
|
chartParamsMap.set(curGroupName.value, { |
|
name: curGroupName.value, |
|
columns: currentCheckPoints |
|
}) |
|
chartGroupMap.set(curGroupName.value, curGroup.value) |
|
|
|
if (!currentCheckPoints.length) { |
|
chartParamsMap.delete(curGroupName.value) |
|
chartGroupMap.delete(curGroupName.value) |
|
} |
|
} |
|
|
|
const disabledDate = (time: any) => { |
|
const current = dayjs(time) |
|
if (props.isTransfer) return current.isAfter(dayjs(), 'day') |
|
try { |
|
const deviceOff = curDevice.value as IOfflineDevice |
|
const start = dayjs(deviceOff.start_time).format('YYYY-MM-DD') |
|
const end = dayjs(deviceOff.end_time).format('YYYY-MM-DD') |
|
return current.isBefore(start, 'day') || current.isAfter(end, 'day') |
|
} catch (e) { |
|
console.log('disableDate error', e) |
|
return current.isAfter(dayjs(), 'day') |
|
} |
|
|
|
} |
|
|
|
const collapsed = ref(false) |
|
|
|
function onCollapse(value?: boolean) { |
|
collapsed.value = value ?? !collapsed.value |
|
} |
|
|
|
const getBeforeMonth = dayjs().startOf('month').subtract(1, 'month').startOf('month').toDate() |
|
defineExpose({ |
|
open, |
|
openFullScreen, |
|
}) |
|
</script> |
|
|
|
<style scoped lang="scss"> |
|
.fault-rule-drawer { |
|
font-size: 16px; |
|
|
|
:deep(.edfs-wrap) { |
|
width: auto; |
|
} |
|
|
|
:deep(.el-drawer__header) { |
|
color: var(--text-color); |
|
} |
|
} |
|
</style>
|
|
|