|
|
|
|
@ -33,19 +33,15 @@
|
|
|
|
|
<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')">
|
|
|
|
|
<div>
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="group.dateRange"
|
|
|
|
|
type="datetimerange"
|
|
|
|
|
:start-placeholder="t('DataCollection.DeviceParamAnalysis.formTimeStartPlaceholder')"
|
|
|
|
|
:end-placeholder="t('DataCollection.DeviceParamAnalysis.formTimeEndPlaceholder')"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
:shortcuts="dateShortcuts"
|
|
|
|
|
class="!w-360px"
|
|
|
|
|
:disabled-date="disableFutureDate"
|
|
|
|
|
@change="(value) => handleDateRangeChange(group, value)"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="group.dateRange"
|
|
|
|
|
type="datetimerange"
|
|
|
|
|
:start-placeholder="t('DataCollection.DeviceParamAnalysis.formTimeStartPlaceholder')"
|
|
|
|
|
:end-placeholder="t('DataCollection.DeviceParamAnalysis.formTimeEndPlaceholder')"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
:shortcuts="dateShortcuts"
|
|
|
|
|
class="!w-360px"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button :disabled="group.chartLoading" @click="handleQuery(group)">
|
|
|
|
|
@ -135,37 +131,45 @@ const keyword = ref('')
|
|
|
|
|
const treeProps = { children: 'children', label: 'label', disabled: 'disabled' }
|
|
|
|
|
const treeData = ref<DeviceTreeNode[]>([])
|
|
|
|
|
|
|
|
|
|
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
|
|
|
|
|
const MAX_DATE_RANGE_HOURS = 8
|
|
|
|
|
const MAX_DATE_RANGE_MS = MAX_DATE_RANGE_HOURS * 60 * 60 * 1000
|
|
|
|
|
const lastValidDateRange = new Map<string, [string, string]>()
|
|
|
|
|
|
|
|
|
|
const disableFutureDate = (date: Date) => {
|
|
|
|
|
return dayjs(date).isAfter(dayjs(), 'day')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildDefaultDateRange = (): [string, string] => {
|
|
|
|
|
const end = dayjs()
|
|
|
|
|
const start = end.subtract(2, 'hour')
|
|
|
|
|
return [start.format(DATE_TIME_FORMAT), end.format(DATE_TIME_FORMAT)]
|
|
|
|
|
return [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createLastHoursShortcut = (hours: number) => {
|
|
|
|
|
return {
|
|
|
|
|
text: `最近${hours}小时`,
|
|
|
|
|
const dateShortcuts = [
|
|
|
|
|
{
|
|
|
|
|
text: t('DataCollection.DeviceParamAnalysis.shortcutLast7Days'),
|
|
|
|
|
value: () => {
|
|
|
|
|
const end = dayjs().endOf('day').toDate()
|
|
|
|
|
const start = dayjs().subtract(6, 'day').startOf('day').toDate()
|
|
|
|
|
return [start, end]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text: t('DataCollection.DeviceParamAnalysis.shortcutLastWeek'),
|
|
|
|
|
value: () => {
|
|
|
|
|
const end = dayjs().toDate()
|
|
|
|
|
const start = dayjs().subtract(hours, 'hour').toDate()
|
|
|
|
|
const start = dayjs().subtract(1, 'week').startOf('week').toDate()
|
|
|
|
|
const end = dayjs().subtract(1, 'week').endOf('week').toDate()
|
|
|
|
|
return [start, end]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text: t('DataCollection.DeviceParamAnalysis.shortcutLastMonth'),
|
|
|
|
|
value: () => {
|
|
|
|
|
const start = dayjs().subtract(1, 'month').startOf('month').toDate()
|
|
|
|
|
const end = dayjs().subtract(1, 'month').endOf('month').toDate()
|
|
|
|
|
return [start, end]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text: t('DataCollection.DeviceParamAnalysis.shortcutLast3Months'),
|
|
|
|
|
value: () => {
|
|
|
|
|
const end = dayjs().endOf('day').toDate()
|
|
|
|
|
const start = dayjs().subtract(3, 'month').startOf('day').toDate()
|
|
|
|
|
return [start, end]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dateShortcuts = [
|
|
|
|
|
createLastHoursShortcut(1),
|
|
|
|
|
createLastHoursShortcut(2),
|
|
|
|
|
createLastHoursShortcut(4),
|
|
|
|
|
createLastHoursShortcut(8)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
type ChartState = 'idle' | 'loading' | 'empty' | 'ready'
|
|
|
|
|
@ -342,78 +346,10 @@ const loadTree = async () => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const normalizeDateRangeValue = (value: any): [string, string] | undefined => {
|
|
|
|
|
if (!Array.isArray(value) || value.length !== 2) return undefined
|
|
|
|
|
const start = value[0]
|
|
|
|
|
const end = value[1]
|
|
|
|
|
if (!start || !end) return undefined
|
|
|
|
|
return [String(start), String(end)]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ensureDateRangeWithin8Hours = (group: SelectedGroup, dateRange: [string, string]) => {
|
|
|
|
|
const now = dayjs()
|
|
|
|
|
let start = dayjs(dateRange[0])
|
|
|
|
|
let end = dayjs(dateRange[1])
|
|
|
|
|
if (!start.isValid() || !end.isValid()) return dateRange
|
|
|
|
|
|
|
|
|
|
if (start.isAfter(now) && end.isAfter(now)) {
|
|
|
|
|
end = now
|
|
|
|
|
start = now.subtract(2, 'hour')
|
|
|
|
|
} else {
|
|
|
|
|
if (end.isAfter(now)) end = now
|
|
|
|
|
if (start.isAfter(now)) start = end.subtract(2, 'hour')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const diffMs = end.valueOf() - start.valueOf()
|
|
|
|
|
if (diffMs < 0) {
|
|
|
|
|
end = now
|
|
|
|
|
start = now.subtract(2, 'hour')
|
|
|
|
|
const nextRange: [string, string] = [start.format(DATE_TIME_FORMAT), end.format(DATE_TIME_FORMAT)]
|
|
|
|
|
return nextRange
|
|
|
|
|
}
|
|
|
|
|
if (diffMs <= MAX_DATE_RANGE_MS) {
|
|
|
|
|
return [start.format(DATE_TIME_FORMAT), end.format(DATE_TIME_FORMAT)]
|
|
|
|
|
}
|
|
|
|
|
const clampedEnd = start.add(MAX_DATE_RANGE_HOURS, 'hour')
|
|
|
|
|
const finalEnd = clampedEnd.isAfter(now) ? now : clampedEnd
|
|
|
|
|
return [start.format(DATE_TIME_FORMAT), finalEnd.format(DATE_TIME_FORMAT)]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ensureDateRange = (group: SelectedGroup) => {
|
|
|
|
|
if (!group.dateRange || group.dateRange.length !== 2) {
|
|
|
|
|
group.dateRange = buildDefaultDateRange()
|
|
|
|
|
lastValidDateRange.set(group.id, group.dateRange)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const normalized = normalizeDateRangeValue(group.dateRange)
|
|
|
|
|
if (!normalized) {
|
|
|
|
|
group.dateRange = buildDefaultDateRange()
|
|
|
|
|
lastValidDateRange.set(group.id, group.dateRange)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const nextRange = ensureDateRangeWithin8Hours(group, normalized)
|
|
|
|
|
group.dateRange = nextRange
|
|
|
|
|
lastValidDateRange.set(group.id, nextRange)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleDateRangeChange = (group: SelectedGroup, value: any) => {
|
|
|
|
|
const normalized = normalizeDateRangeValue(value) || normalizeDateRangeValue(group.dateRange)
|
|
|
|
|
if (!normalized) return
|
|
|
|
|
|
|
|
|
|
const now = dayjs()
|
|
|
|
|
const selectedStart = dayjs(normalized[0])
|
|
|
|
|
const selectedEnd = dayjs(normalized[1])
|
|
|
|
|
const hasFuture = (selectedStart.isValid() && selectedStart.isAfter(now)) || (selectedEnd.isValid() && selectedEnd.isAfter(now))
|
|
|
|
|
|
|
|
|
|
const nextRange = ensureDateRangeWithin8Hours(group, normalized)
|
|
|
|
|
const isClamped = nextRange[0] !== normalized[0] || nextRange[1] !== normalized[1]
|
|
|
|
|
if (hasFuture) {
|
|
|
|
|
message.warning('不能选择未来日期')
|
|
|
|
|
} else if (isClamped) {
|
|
|
|
|
message.warning('时间范围最多只能选择 8 小时')
|
|
|
|
|
}
|
|
|
|
|
group.dateRange = nextRange
|
|
|
|
|
lastValidDateRange.set(group.id, nextRange)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetChartData = (group: SelectedGroup) => {
|
|
|
|
|
@ -600,7 +536,6 @@ const syncGroupsByCheckedNodes = (checkedNodes: DeviceTreeNode[]) => {
|
|
|
|
|
chartRenderKey: 0
|
|
|
|
|
}
|
|
|
|
|
selectedGroups.value.push(group)
|
|
|
|
|
lastValidDateRange.set(group.id, group.dateRange)
|
|
|
|
|
addedGroups.push(group)
|
|
|
|
|
}
|
|
|
|
|
return addedGroups
|
|
|
|
|
|