feat:添加设备运行参数分析页面

main
黄伟杰 4 weeks ago
parent 5dbf02a2f2
commit 5ca63c5a85

@ -0,0 +1,315 @@
<template>
<el-row :gutter="20">
<el-col :span="6" :xs="24">
<ContentWrap class="h-1/1">
<el-input
v-model="keyword"
clearable
placeholder="搜索设备或参数"
class="!w-1/1"
@input="handleKeywordChange"
/>
<div class="mt-12px">
<el-tree
ref="treeRef"
v-loading="treeLoading"
:data="treeData"
:props="treeProps"
node-key="id"
highlight-current
:expand-on-click-node="false"
:filter-node-method="filterTreeNode"
@node-click="handleTreeNodeClick"
/>
</div>
</ContentWrap>
</el-col>
<el-col :span="18" :xs="24">
<ContentWrap>
<el-form class="-mb-15px" :inline="true">
<el-form-item label="时间">
<el-date-picker
v-model="dateRange"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
:shortcuts="dateShortcuts"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-360px"
/>
</el-form-item>
<el-form-item>
<el-button :disabled="!selectedParam" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-button :disabled="!selectedParam" @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-alert
v-if="selectedParam"
:title="selectedParamTitle"
type="info"
:closable="false"
class="mb-12px"
/>
<div v-loading="chartLoading">
<el-empty v-if="!selectedParam" description="请选择左侧参数" />
<EChart v-else :options="chartOption" height="520px" />
</div>
</ContentWrap>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import type { EChartsOption } from 'echarts'
import dayjs from 'dayjs'
import { Echart as EChart } from '@/components/Echart'
defineOptions({ name: 'DeviceParamAnalysis' })
type TreeNodeType = 'device' | 'param'
type DeviceTreeNode = {
id: string
label: string
type: TreeNodeType
children?: DeviceTreeNode[]
deviceId?: string
paramKey?: string
unit?: string
}
const message = useMessage()
const treeRef = ref()
const treeLoading = ref(false)
const keyword = ref('')
const treeProps = { children: 'children', label: 'label' }
const treeData = ref<DeviceTreeNode[]>([])
const selectedParam = ref<DeviceTreeNode | null>(null)
const chartLoading = ref(false)
const chartXAxis = ref<string[]>([])
const chartSeries = ref<number[]>([])
const buildDefaultDateRange = (): [string, string] => {
const end = dayjs().endOf('day')
const start = end.subtract(6, 'day').startOf('day')
return [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')]
}
const dateRange = ref<[string, string]>(buildDefaultDateRange())
const dateShortcuts = [
{
text: '最近 7 天',
value: () => {
const end = dayjs().endOf('day').toDate()
const start = dayjs().subtract(6, 'day').startOf('day').toDate()
return [start, end]
}
},
{
text: '上周',
value: () => {
const start = dayjs().subtract(1, 'week').startOf('week').toDate()
const end = dayjs().subtract(1, 'week').endOf('week').toDate()
return [start, end]
}
},
{
text: '上个月',
value: () => {
const start = dayjs().subtract(1, 'month').startOf('month').toDate()
const end = dayjs().subtract(1, 'month').endOf('month').toDate()
return [start, end]
}
},
{
text: '三个月内',
value: () => {
const end = dayjs().endOf('day').toDate()
const start = dayjs().subtract(3, 'month').startOf('day').toDate()
return [start, end]
}
}
]
const selectedParamTitle = computed(() => {
const param = selectedParam.value
if (!param) return ''
const unitText = param.unit ? `${param.unit}` : ''
return `参数:${param.label}${unitText}`
})
const chartOption = computed<EChartsOption>(() => {
const unit = selectedParam.value?.unit
return {
tooltip: { trigger: 'axis' },
grid: { left: 30, right: 20, top: 20, bottom: 40, containLabel: true },
xAxis: {
type: 'category',
boundaryGap: false,
data: chartXAxis.value
},
yAxis: {
type: 'value',
name: unit || ''
},
series: [
{
type: 'line',
name: selectedParam.value?.label || '参数',
smooth: true,
showSymbol: false,
data: chartSeries.value
}
]
}
})
const handleKeywordChange = () => {
treeRef.value?.filter(keyword.value)
}
const filterTreeNode = (value: string, data: DeviceTreeNode) => {
if (!value) return true
return data.label?.toLowerCase().includes(value.toLowerCase())
}
const mockFetchTree = async (): Promise<DeviceTreeNode[]> => {
const devices = [
{ id: 'D-1001', label: '压合机-01' },
{ id: 'D-1002', label: '烘干线-02' },
{ id: 'D-1003', label: '制浆机-03' },
{ id: 'D-1004', label: '包装机-04' }
]
const params = [
{ key: 'temp', label: '温度', unit: '℃' },
{ key: 'pressure', label: '压力', unit: 'kPa' },
{ key: 'speed', label: '速度', unit: 'm/s' },
{ key: 'power', label: '功率', unit: 'kW' },
{ key: 'current', label: '电流', unit: 'A' }
]
await new Promise((resolve) => setTimeout(resolve, 200))
return devices.map((d) => ({
id: d.id,
label: d.label,
type: 'device',
deviceId: d.id,
children: params.map((p) => ({
id: `${d.id}::${p.key}`,
label: p.label,
type: 'param',
deviceId: d.id,
paramKey: p.key,
unit: p.unit
}))
}))
}
const buildSeriesSeed = (key: string) => {
let hash = 0
for (let i = 0; i < key.length; i++) {
hash = (hash << 5) - hash + key.charCodeAt(i)
hash |= 0
}
return Math.abs(hash)
}
const mockFetchSeries = async (args: { deviceId: string; paramKey: string; start: string; end: string }) => {
await new Promise((resolve) => setTimeout(resolve, 250))
const start = dayjs(args.start)
const end = dayjs(args.end)
const diff = Math.max(end.startOf('day').diff(start.startOf('day'), 'day'), 0)
const count = Math.min(diff + 1, 366)
const x: string[] = []
const y: number[] = []
const seed = buildSeriesSeed(`${args.deviceId}::${args.paramKey}`)
const base = 20 + (seed % 30)
const amp = 5 + (seed % 10)
for (let i = 0; i < count; i++) {
const d = start.add(i, 'day')
x.push(d.format('YYYY-MM-DD'))
const v = base + Math.sin(i / 2) * amp + ((seed % 7) - 3) * 0.6
y.push(Number(v.toFixed(2)))
}
return { x, y }
}
const loadTree = async () => {
treeLoading.value = true
try {
treeData.value = await mockFetchTree()
} finally {
treeLoading.value = false
}
}
const ensureDateRange = () => {
if (!dateRange.value || dateRange.value.length !== 2) {
dateRange.value = buildDefaultDateRange()
}
}
const fetchChart = async () => {
const param = selectedParam.value
if (!param?.deviceId || !param.paramKey) return
ensureDateRange()
const [start, end] = dateRange.value
if (!start || !end) return
chartLoading.value = true
try {
const res = await mockFetchSeries({ deviceId: param.deviceId, paramKey: param.paramKey, start, end })
chartXAxis.value = res.x
chartSeries.value = res.y
} catch {
message.error('获取图表数据失败')
} finally {
chartLoading.value = false
}
}
const handleTreeNodeClick = async (data: DeviceTreeNode) => {
if (data.type !== 'param') return
selectedParam.value = data
dateRange.value = buildDefaultDateRange()
await fetchChart()
}
const handleQuery = async () => {
if (!selectedParam.value) return
await fetchChart()
}
const resetQuery = async () => {
dateRange.value = buildDefaultDateRange()
await fetchChart()
}
onMounted(async () => {
await loadTree()
})
</script>
<style scoped lang="scss">
:deep(.el-tree) {
max-height: calc(100vh - 280px);
overflow: auto;
}
</style>
Loading…
Cancel
Save