|
|
|
|
@ -11,54 +11,66 @@
|
|
|
|
|
/>
|
|
|
|
|
<div class="mt-12px">
|
|
|
|
|
<el-tree
|
|
|
|
|
ref="treeRef" v-loading="treeLoading" :data="treeData" :props="treeProps" node-key="id"
|
|
|
|
|
highlight-current @node-click="handleTreeNodeClick" />
|
|
|
|
|
ref="treeRef"
|
|
|
|
|
v-loading="treeLoading"
|
|
|
|
|
:data="treeData"
|
|
|
|
|
:props="treeProps"
|
|
|
|
|
node-key="id"
|
|
|
|
|
show-checkbox
|
|
|
|
|
check-strictly
|
|
|
|
|
@check="handleTreeCheck"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
<el-col :span="18" :xs="24">
|
|
|
|
|
<ContentWrap>
|
|
|
|
|
<el-form class="-mb-15px device-param-analysis-form" :inline="true" label-width="auto">
|
|
|
|
|
<el-form-item :label="t('DataCollection.DeviceParamAnalysis.formTimeLabel')">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="dateRange"
|
|
|
|
|
type="daterange"
|
|
|
|
|
:start-placeholder="t('DataCollection.DeviceParamAnalysis.formTimeStartPlaceholder')"
|
|
|
|
|
:end-placeholder="t('DataCollection.DeviceParamAnalysis.formTimeEndPlaceholder')"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
:shortcuts="dateShortcuts"
|
|
|
|
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
|
|
|
class="!w-360px"
|
|
|
|
|
<div class="analysis-groups">
|
|
|
|
|
<ContentWrap v-if="!selectedGroups.length">
|
|
|
|
|
<el-empty :description="t('DataCollection.DeviceParamAnalysis.emptySelectNodeDescription')" />
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
|
|
|
|
|
<ContentWrap v-for="group in selectedGroups" :key="group.id">
|
|
|
|
|
<el-form class="-mb-15px device-param-analysis-form" :inline="true" label-width="auto">
|
|
|
|
|
<el-form-item :label="t('DataCollection.DeviceParamAnalysis.formTimeLabel')">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="group.dateRange"
|
|
|
|
|
type="daterange"
|
|
|
|
|
:start-placeholder="t('DataCollection.DeviceParamAnalysis.formTimeStartPlaceholder')"
|
|
|
|
|
:end-placeholder="t('DataCollection.DeviceParamAnalysis.formTimeEndPlaceholder')"
|
|
|
|
|
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="group.chartLoading" @click="handleQuery(group)">
|
|
|
|
|
<Icon icon="ep:search" class="mr-5px" />
|
|
|
|
|
{{ t('DataCollection.DeviceParamAnalysis.searchButtonText') }}
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button :disabled="group.chartLoading" @click="resetQuery(group)">
|
|
|
|
|
<Icon icon="ep:refresh" class="mr-5px" />
|
|
|
|
|
{{ t('DataCollection.DeviceParamAnalysis.resetButtonText') }}
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<el-alert :title="buildParamTitle(group.param)" type="info" :closable="false" class="mb-12px" />
|
|
|
|
|
<div v-loading="group.chartLoading">
|
|
|
|
|
<el-empty
|
|
|
|
|
v-if="group.chartState === 'empty'"
|
|
|
|
|
:description="t('DataCollection.DeviceParamAnalysis.emptyDescription')"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button :disabled="!selectedParam" @click="handleQuery">
|
|
|
|
|
<Icon icon="ep:search" class="mr-5px" />
|
|
|
|
|
{{ t('DataCollection.DeviceParamAnalysis.searchButtonText') }}
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button :disabled="!selectedParam" @click="resetQuery">
|
|
|
|
|
<Icon icon="ep:refresh" class="mr-5px" />
|
|
|
|
|
{{ t('DataCollection.DeviceParamAnalysis.resetButtonText') }}
|
|
|
|
|
</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="chartState === 'empty'"
|
|
|
|
|
:description="t('DataCollection.DeviceParamAnalysis.emptyDescription')"
|
|
|
|
|
/>
|
|
|
|
|
<el-empty
|
|
|
|
|
v-else-if="!selectedParam"
|
|
|
|
|
:description="t('DataCollection.DeviceParamAnalysis.emptySelectNodeDescription')"
|
|
|
|
|
/>
|
|
|
|
|
<EChart v-else-if="chartState === 'ready'" :key="chartRenderKey" :options="chartOption" height="70vh" />
|
|
|
|
|
</div>
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
<EChart
|
|
|
|
|
v-else-if="group.chartState === 'ready'"
|
|
|
|
|
:key="group.chartRenderKey"
|
|
|
|
|
:options="buildChartOption(group)"
|
|
|
|
|
height="360px"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</template>
|
|
|
|
|
@ -87,6 +99,7 @@ type DeviceTreeNode = {
|
|
|
|
|
paramKey?: string
|
|
|
|
|
unit?: string
|
|
|
|
|
paramCount?: number
|
|
|
|
|
disabled?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ApiTreeParameter = {
|
|
|
|
|
@ -116,27 +129,15 @@ const message = useMessage()
|
|
|
|
|
const treeRef = ref()
|
|
|
|
|
const treeLoading = ref(false)
|
|
|
|
|
const keyword = ref('')
|
|
|
|
|
const treeProps = { children: 'children', label: 'label' }
|
|
|
|
|
const treeProps = { children: 'children', label: 'label', disabled: 'disabled' }
|
|
|
|
|
const treeData = ref<DeviceTreeNode[]>([])
|
|
|
|
|
|
|
|
|
|
const selectedDeviceId = ref<number | undefined>(undefined)
|
|
|
|
|
const selectedModelId = ref<number | undefined>(undefined)
|
|
|
|
|
|
|
|
|
|
const selectedParam = ref<DeviceTreeNode | null>(null)
|
|
|
|
|
const chartLoading = ref(false)
|
|
|
|
|
const chartState = ref<'idle' | 'loading' | 'empty' | 'ready'>('idle')
|
|
|
|
|
const chartXAxis = ref<string[]>([])
|
|
|
|
|
const chartSeries = ref<{ name: string; data: Array<number | null> }[]>([])
|
|
|
|
|
const chartRenderKey = ref(0)
|
|
|
|
|
|
|
|
|
|
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: t('DataCollection.DeviceParamAnalysis.shortcutLast7Days'),
|
|
|
|
|
@ -172,18 +173,67 @@ const dateShortcuts = [
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const selectedParamTitle = computed(() => {
|
|
|
|
|
const param = selectedParam.value
|
|
|
|
|
if (!param) return ''
|
|
|
|
|
type ChartState = 'idle' | 'loading' | 'empty' | 'ready'
|
|
|
|
|
|
|
|
|
|
type SelectedGroup = {
|
|
|
|
|
id: string
|
|
|
|
|
param: DeviceTreeNode
|
|
|
|
|
deviceId?: number
|
|
|
|
|
modelId?: number
|
|
|
|
|
dateRange: [string, string]
|
|
|
|
|
requestSeq: number
|
|
|
|
|
chartLoading: boolean
|
|
|
|
|
chartState: ChartState
|
|
|
|
|
chartXAxis: string[]
|
|
|
|
|
chartSeries: Array<{ name: string; data: Array<number | null> }>
|
|
|
|
|
chartRenderKey: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectedGroups = ref<SelectedGroup[]>([])
|
|
|
|
|
|
|
|
|
|
let keywordTimer: number | undefined
|
|
|
|
|
const handleKeywordChange = () => {
|
|
|
|
|
if (keywordTimer) window.clearTimeout(keywordTimer)
|
|
|
|
|
keywordTimer = window.setTimeout(() => {
|
|
|
|
|
keywordTimer = undefined
|
|
|
|
|
loadTree()
|
|
|
|
|
}, 300)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const toFiniteId = (value: any): number | undefined => {
|
|
|
|
|
if (typeof value === 'number') return Number.isFinite(value) ? value : undefined
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
if (/^\d+$/.test(value.trim())) return Number(value)
|
|
|
|
|
const n = Number(value)
|
|
|
|
|
return Number.isFinite(n) ? n : undefined
|
|
|
|
|
}
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const toNodeIds = (node: DeviceTreeNode): { deviceId?: number; modelId?: number } => {
|
|
|
|
|
const modelId = typeof node.modelId === 'number' && Number.isFinite(node.modelId) ? node.modelId : undefined
|
|
|
|
|
const deviceId = typeof node.deviceId === 'number' && Number.isFinite(node.deviceId) ? node.deviceId : undefined
|
|
|
|
|
if (typeof deviceId === 'number') return { deviceId, modelId }
|
|
|
|
|
|
|
|
|
|
const parts = String(node.id ?? '').split('-').filter(Boolean)
|
|
|
|
|
const last = parts.length ? parts[parts.length - 1] : undefined
|
|
|
|
|
const parsed = toFiniteId(last)
|
|
|
|
|
if (typeof parsed !== 'number' || !Number.isFinite(parsed) || parsed <= 0) {
|
|
|
|
|
return { modelId }
|
|
|
|
|
}
|
|
|
|
|
return { deviceId: parsed, modelId }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildParamTitle = (param: DeviceTreeNode) => {
|
|
|
|
|
const unitText = param.unit ? ` (${param.unit})` : ''
|
|
|
|
|
return t('DataCollection.DeviceParamAnalysis.selectedParamTitle', {
|
|
|
|
|
label: param.label,
|
|
|
|
|
unit: unitText
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const chartOption = computed<EChartsOption>(() => {
|
|
|
|
|
const unit = selectedParam.value?.unit
|
|
|
|
|
const buildChartOption = (group: SelectedGroup): EChartsOption => {
|
|
|
|
|
const unit = group.param?.unit
|
|
|
|
|
return {
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
legend: { top: 10, left: 'center', type: 'scroll' },
|
|
|
|
|
@ -191,13 +241,13 @@ const chartOption = computed<EChartsOption>(() => {
|
|
|
|
|
xAxis: {
|
|
|
|
|
type: 'category',
|
|
|
|
|
boundaryGap: false,
|
|
|
|
|
data: chartXAxis.value
|
|
|
|
|
data: group.chartXAxis
|
|
|
|
|
},
|
|
|
|
|
yAxis: {
|
|
|
|
|
type: 'value',
|
|
|
|
|
name: unit || ''
|
|
|
|
|
},
|
|
|
|
|
series: chartSeries.value.map((s) => ({
|
|
|
|
|
series: group.chartSeries.map((s) => ({
|
|
|
|
|
type: 'line',
|
|
|
|
|
name: s.name,
|
|
|
|
|
smooth: false,
|
|
|
|
|
@ -205,25 +255,6 @@ const chartOption = computed<EChartsOption>(() => {
|
|
|
|
|
data: s.data
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
let keywordTimer: number | undefined
|
|
|
|
|
const handleKeywordChange = () => {
|
|
|
|
|
if (keywordTimer) window.clearTimeout(keywordTimer)
|
|
|
|
|
keywordTimer = window.setTimeout(() => {
|
|
|
|
|
keywordTimer = undefined
|
|
|
|
|
loadTree()
|
|
|
|
|
}, 300)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const toFiniteId = (value: any): number | undefined => {
|
|
|
|
|
if (typeof value === 'number') return Number.isFinite(value) ? value : undefined
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
if (/^\d+$/.test(value.trim())) return Number(value)
|
|
|
|
|
const n = Number(value)
|
|
|
|
|
return Number.isFinite(n) ? n : undefined
|
|
|
|
|
}
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildTreeFromApi = (orgs: ApiTreeOrg[]): DeviceTreeNode[] => {
|
|
|
|
|
@ -258,7 +289,8 @@ const buildTreeFromApi = (orgs: ApiTreeOrg[]): DeviceTreeNode[] => {
|
|
|
|
|
deviceId: toFiniteId(eq.id),
|
|
|
|
|
modelId: toFiniteId(p.id),
|
|
|
|
|
paramKey: p?.code ? String(p.code) : undefined,
|
|
|
|
|
unit: p?.unit ? String(p.unit) : undefined
|
|
|
|
|
unit: p?.unit ? String(p.unit) : undefined,
|
|
|
|
|
disabled: false
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
@ -267,7 +299,8 @@ const buildTreeFromApi = (orgs: ApiTreeOrg[]): DeviceTreeNode[] => {
|
|
|
|
|
type: 'device',
|
|
|
|
|
deviceId: toFiniteId(eq.id),
|
|
|
|
|
paramCount: params.length,
|
|
|
|
|
children: paramNodes.length ? paramNodes : undefined
|
|
|
|
|
children: paramNodes.length ? paramNodes : undefined,
|
|
|
|
|
disabled: true
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@ -276,7 +309,8 @@ const buildTreeFromApi = (orgs: ApiTreeOrg[]): DeviceTreeNode[] => {
|
|
|
|
|
id: `org-${org.id}`,
|
|
|
|
|
label: org?.name ?? String(org?.id ?? ''),
|
|
|
|
|
type: 'device',
|
|
|
|
|
children: children.length ? children : undefined
|
|
|
|
|
children: children.length ? children : undefined,
|
|
|
|
|
disabled: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -298,12 +332,8 @@ const loadTree = async () => {
|
|
|
|
|
treeData.value = buildTreeFromApi(extractApiOrgs(res))
|
|
|
|
|
if (keyword.value) {
|
|
|
|
|
treeRef.value?.setCurrentKey?.(undefined)
|
|
|
|
|
selectedParam.value = null
|
|
|
|
|
selectedDeviceId.value = undefined
|
|
|
|
|
selectedModelId.value = undefined
|
|
|
|
|
chartState.value = 'idle'
|
|
|
|
|
chartXAxis.value = []
|
|
|
|
|
chartSeries.value = []
|
|
|
|
|
treeRef.value?.setCheckedKeys?.([])
|
|
|
|
|
selectedGroups.value = []
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
message.error(t('DataCollection.DeviceParamAnalysis.messageLoadTreeFailed'))
|
|
|
|
|
@ -312,16 +342,16 @@ const loadTree = async () => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ensureDateRange = () => {
|
|
|
|
|
if (!dateRange.value || dateRange.value.length !== 2) {
|
|
|
|
|
dateRange.value = buildDefaultDateRange()
|
|
|
|
|
const ensureDateRange = (group: SelectedGroup) => {
|
|
|
|
|
if (!group.dateRange || group.dateRange.length !== 2) {
|
|
|
|
|
group.dateRange = buildDefaultDateRange()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetChartData = () => {
|
|
|
|
|
chartXAxis.value = []
|
|
|
|
|
chartSeries.value = []
|
|
|
|
|
chartRenderKey.value++
|
|
|
|
|
const resetChartData = (group: SelectedGroup) => {
|
|
|
|
|
group.chartXAxis = []
|
|
|
|
|
group.chartSeries = []
|
|
|
|
|
group.chartRenderKey++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const toNumber = (value: any) => {
|
|
|
|
|
@ -339,37 +369,48 @@ const toNumber = (value: any) => {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchChart = async () => {
|
|
|
|
|
const param = selectedParam.value
|
|
|
|
|
if (typeof selectedDeviceId.value !== 'number' || !Number.isFinite(selectedDeviceId.value)) return
|
|
|
|
|
const extractChartRows = (res: any): Record<string, any>[] => {
|
|
|
|
|
if (Array.isArray(res)) return res as Record<string, any>[]
|
|
|
|
|
if (Array.isArray(res?.data)) return res.data as Record<string, any>[]
|
|
|
|
|
if (Array.isArray(res?.result)) return res.result as Record<string, any>[]
|
|
|
|
|
if (Array.isArray(res?.list)) return res.list as Record<string, any>[]
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchGroupChart = async (group: SelectedGroup, notify: boolean) => {
|
|
|
|
|
if (typeof group.deviceId !== 'number' || !Number.isFinite(group.deviceId)) return
|
|
|
|
|
|
|
|
|
|
ensureDateRange()
|
|
|
|
|
const [start, end] = dateRange.value
|
|
|
|
|
ensureDateRange(group)
|
|
|
|
|
const [start, end] = group.dateRange
|
|
|
|
|
if (!start || !end) return
|
|
|
|
|
|
|
|
|
|
const buildDateTime = (date: string, suffix: string) => {
|
|
|
|
|
return date.includes(':') ? date : `${date} ${suffix}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chartState.value = 'loading'
|
|
|
|
|
chartLoading.value = true
|
|
|
|
|
resetChartData()
|
|
|
|
|
const requestSeq = ++group.requestSeq
|
|
|
|
|
group.chartState = 'loading'
|
|
|
|
|
group.chartLoading = true
|
|
|
|
|
resetChartData(group)
|
|
|
|
|
try {
|
|
|
|
|
const req: Record<string, any> = {
|
|
|
|
|
deviceId: selectedDeviceId.value,
|
|
|
|
|
deviceId: group.deviceId,
|
|
|
|
|
collectionStartTime: buildDateTime(start, '00:00:00'),
|
|
|
|
|
collectionEndTime: buildDateTime(end, '23:59:59')
|
|
|
|
|
}
|
|
|
|
|
if (typeof selectedModelId.value === 'number' && Number.isFinite(selectedModelId.value)) {
|
|
|
|
|
req.modelId = selectedModelId.value
|
|
|
|
|
if (typeof group.modelId === 'number' && Number.isFinite(group.modelId)) {
|
|
|
|
|
req.modelId = group.modelId
|
|
|
|
|
}
|
|
|
|
|
const data = await DeviceModelAttributeApi.operationAnalysisDetails(req as any)
|
|
|
|
|
const rows: Record<string, any>[] = Array.isArray(data) ? data : []
|
|
|
|
|
if (requestSeq !== group.requestSeq) return
|
|
|
|
|
const rows: Record<string, any>[] = extractChartRows(data)
|
|
|
|
|
if (!rows.length) {
|
|
|
|
|
chartXAxis.value = []
|
|
|
|
|
chartSeries.value = []
|
|
|
|
|
chartState.value = 'empty'
|
|
|
|
|
message.warning(t('DataCollection.DeviceParamAnalysis.messageNodeNoParams'))
|
|
|
|
|
group.chartXAxis = []
|
|
|
|
|
group.chartSeries = []
|
|
|
|
|
group.chartState = 'empty'
|
|
|
|
|
if (notify) {
|
|
|
|
|
message.warning(t('DataCollection.DeviceParamAnalysis.messageNodeNoParams'))
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -380,7 +421,8 @@ const fetchChart = async () => {
|
|
|
|
|
const sortedRows = [...rows].sort((a, b) =>
|
|
|
|
|
String(a?.[xKey] ?? '').localeCompare(String(b?.[xKey] ?? ''))
|
|
|
|
|
)
|
|
|
|
|
chartXAxis.value = sortedRows.map((r) => String(r?.[xKey] ?? ''))
|
|
|
|
|
if (requestSeq !== group.requestSeq) return
|
|
|
|
|
group.chartXAxis = sortedRows.map((r) => String(r?.[xKey] ?? ''))
|
|
|
|
|
|
|
|
|
|
const seriesKeys = keys.filter((k) => k !== xKey)
|
|
|
|
|
const seriesMap = new Map<string, Array<number | null>>()
|
|
|
|
|
@ -407,77 +449,76 @@ const fetchChart = async () => {
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
chartSeries.value = Array.from(seriesMap.entries()).map(([name, data]) => ({ name, data }))
|
|
|
|
|
group.chartSeries = Array.from(seriesMap.entries()).map(([name, data]) => ({ name, data }))
|
|
|
|
|
} else {
|
|
|
|
|
chartXAxis.value = keys
|
|
|
|
|
group.chartXAxis = keys
|
|
|
|
|
const values = keys.map((k) => toNumber(firstRow[k]))
|
|
|
|
|
chartSeries.value = [
|
|
|
|
|
{ name: param?.label || t('DataCollection.DeviceParamAnalysis.defaultSeriesName'), data: values }
|
|
|
|
|
group.chartSeries = [
|
|
|
|
|
{ name: group.param?.label || t('DataCollection.DeviceParamAnalysis.defaultSeriesName'), data: values }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chartState.value = chartXAxis.value.length && chartSeries.value.length ? 'ready' : 'empty'
|
|
|
|
|
group.chartState = group.chartXAxis.length && group.chartSeries.length ? 'ready' : 'empty'
|
|
|
|
|
} catch {
|
|
|
|
|
message.error(t('DataCollection.DeviceParamAnalysis.messageFetchChartFailed'))
|
|
|
|
|
chartState.value = 'idle'
|
|
|
|
|
if (notify) {
|
|
|
|
|
message.error(t('DataCollection.DeviceParamAnalysis.messageFetchChartFailed'))
|
|
|
|
|
}
|
|
|
|
|
group.chartState = 'idle'
|
|
|
|
|
} finally {
|
|
|
|
|
chartLoading.value = false
|
|
|
|
|
if (requestSeq === group.requestSeq) {
|
|
|
|
|
group.chartLoading = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleTreeNodeClick = async (data: DeviceTreeNode) => {
|
|
|
|
|
if (keywordTimer) {
|
|
|
|
|
window.clearTimeout(keywordTimer)
|
|
|
|
|
keywordTimer = undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hasChildren = Array.isArray(data?.children) && data.children.length > 0
|
|
|
|
|
if (hasChildren) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isEmptyOrgNode = typeof data?.id === 'string' && data.id.startsWith('org-')
|
|
|
|
|
const isEquipmentNode = typeof data?.id === 'string' && data.id.startsWith('equipment-')
|
|
|
|
|
if (isEquipmentNode && (data.paramCount ?? 0) <= 0 || isEmptyOrgNode) {
|
|
|
|
|
selectedParam.value = data
|
|
|
|
|
selectedDeviceId.value = undefined
|
|
|
|
|
selectedModelId.value = undefined
|
|
|
|
|
chartState.value = 'empty'
|
|
|
|
|
resetChartData()
|
|
|
|
|
message.warning(t('DataCollection.DeviceParamAnalysis.messageDeviceNoParams'))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const syncGroupsByCheckedNodes = (checkedNodes: DeviceTreeNode[]) => {
|
|
|
|
|
const ids = checkedNodes.map((n) => String(n.id))
|
|
|
|
|
const existingIds = new Set(selectedGroups.value.map((g) => g.id))
|
|
|
|
|
const nextIds = new Set(ids)
|
|
|
|
|
|
|
|
|
|
const toNodeIds = (node: DeviceTreeNode): { deviceId?: number; modelId?: number } => {
|
|
|
|
|
const modelId = typeof node.modelId === 'number' && Number.isFinite(node.modelId) ? node.modelId : undefined
|
|
|
|
|
const deviceId = typeof node.deviceId === 'number' && Number.isFinite(node.deviceId) ? node.deviceId : undefined
|
|
|
|
|
if (typeof deviceId === 'number') return { deviceId, modelId }
|
|
|
|
|
selectedGroups.value = selectedGroups.value.filter((g) => nextIds.has(g.id))
|
|
|
|
|
|
|
|
|
|
const parts = String(node.id ?? '').split('-').filter(Boolean)
|
|
|
|
|
const last = parts.length ? parts[parts.length - 1] : undefined
|
|
|
|
|
const parsed = toFiniteId(last)
|
|
|
|
|
if (typeof parsed !== 'number' || !Number.isFinite(parsed) || parsed <= 0) {
|
|
|
|
|
return { modelId }
|
|
|
|
|
const toAdd = checkedNodes.filter((n) => !existingIds.has(String(n.id)))
|
|
|
|
|
const addedGroups: SelectedGroup[] = []
|
|
|
|
|
for (const node of toAdd) {
|
|
|
|
|
const { deviceId, modelId } = toNodeIds(node)
|
|
|
|
|
if (typeof deviceId !== 'number') {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
return { deviceId: parsed, modelId }
|
|
|
|
|
const group: SelectedGroup = {
|
|
|
|
|
id: String(node.id),
|
|
|
|
|
param: node,
|
|
|
|
|
deviceId,
|
|
|
|
|
modelId,
|
|
|
|
|
dateRange: buildDefaultDateRange(),
|
|
|
|
|
requestSeq: 0,
|
|
|
|
|
chartLoading: false,
|
|
|
|
|
chartState: 'idle',
|
|
|
|
|
chartXAxis: [],
|
|
|
|
|
chartSeries: [],
|
|
|
|
|
chartRenderKey: 0
|
|
|
|
|
}
|
|
|
|
|
selectedGroups.value.push(group)
|
|
|
|
|
addedGroups.push(group)
|
|
|
|
|
}
|
|
|
|
|
return addedGroups
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { deviceId, modelId } = toNodeIds(data)
|
|
|
|
|
selectedParam.value = data
|
|
|
|
|
selectedDeviceId.value = deviceId
|
|
|
|
|
selectedModelId.value = modelId
|
|
|
|
|
dateRange.value = buildDefaultDateRange()
|
|
|
|
|
await fetchChart()
|
|
|
|
|
const handleTreeCheck = async () => {
|
|
|
|
|
const checkedNodes = (treeRef.value?.getCheckedNodes?.(true) ?? []) as DeviceTreeNode[]
|
|
|
|
|
const addedGroups = syncGroupsByCheckedNodes(checkedNodes)
|
|
|
|
|
const needFetchGroups = selectedGroups.value.filter((g) => g.chartState === 'idle' && !g.chartLoading)
|
|
|
|
|
const groups = [...new Set([...addedGroups, ...needFetchGroups])]
|
|
|
|
|
await Promise.allSettled(groups.map((g) => fetchGroupChart(g, false)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleQuery = async () => {
|
|
|
|
|
await fetchChart()
|
|
|
|
|
const handleQuery = async (group: SelectedGroup) => {
|
|
|
|
|
await fetchGroupChart(group, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetQuery = async () => {
|
|
|
|
|
dateRange.value = buildDefaultDateRange()
|
|
|
|
|
await fetchChart()
|
|
|
|
|
const resetQuery = async (group: SelectedGroup) => {
|
|
|
|
|
group.dateRange = buildDefaultDateRange()
|
|
|
|
|
await fetchGroupChart(group, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
@ -486,10 +527,19 @@ onMounted(async () => {
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.device-param-analysis-form{
|
|
|
|
|
margin-bottom: 0px
|
|
|
|
|
}
|
|
|
|
|
.device-param-analysis-form :deep(.el-form-item__label) {
|
|
|
|
|
min-width: 100px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.analysis-groups {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-tree) {
|
|
|
|
|
max-height: calc(100vh - 280px);
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|