diff --git a/src/views/iot/deviceParamAnalysis/index.vue b/src/views/iot/deviceParamAnalysis/index.vue
index cf3bd5e0..cb36eed0 100644
--- a/src/views/iot/deviceParamAnalysis/index.vue
+++ b/src/views/iot/deviceParamAnalysis/index.vue
@@ -11,54 +11,66 @@
/>
+ ref="treeRef"
+ v-loading="treeLoading"
+ :data="treeData"
+ :props="treeProps"
+ node-key="id"
+ show-checkbox
+ check-strictly
+ @check="handleTreeCheck"
+ />
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('DataCollection.DeviceParamAnalysis.searchButtonText') }}
+
+
+
+ {{ t('DataCollection.DeviceParamAnalysis.resetButtonText') }}
+
+
+
+
+
+
+
-
-
-
-
- {{ t('DataCollection.DeviceParamAnalysis.searchButtonText') }}
-
-
-
- {{ t('DataCollection.DeviceParamAnalysis.resetButtonText') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
@@ -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([])
-const selectedDeviceId = ref(undefined)
-const selectedModelId = ref(undefined)
-
-const selectedParam = ref(null)
-const chartLoading = ref(false)
-const chartState = ref<'idle' | 'loading' | 'empty' | 'ready'>('idle')
-const chartXAxis = ref([])
-const chartSeries = ref<{ name: string; data: Array }[]>([])
-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 }>
+ chartRenderKey: number
+}
+
+const selectedGroups = ref([])
+
+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(() => {
- 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(() => {
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(() => {
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[] => {
+ if (Array.isArray(res)) return res as Record[]
+ if (Array.isArray(res?.data)) return res.data as Record[]
+ if (Array.isArray(res?.result)) return res.result as Record[]
+ if (Array.isArray(res?.list)) return res.list as Record[]
+ 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 = {
- 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[] = Array.isArray(data) ? data : []
+ if (requestSeq !== group.requestSeq) return
+ const rows: Record[] = 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>()
@@ -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 () => {