You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

316 lines
8.2 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>