|
@@ -17,9 +17,12 @@ const windowSize = ref(4)
|
|
|
const maxPoints = ref(200000)
|
|
const maxPoints = ref(200000)
|
|
|
const noSampling = ref(false)
|
|
const noSampling = ref(false)
|
|
|
const includeStopped = ref(false)
|
|
const includeStopped = ref(false)
|
|
|
|
|
+const abnormalOnly = ref(false)
|
|
|
|
|
+const noCycleOnly = ref(false)
|
|
|
const minStatus = ref<number | null>(null)
|
|
const minStatus = ref<number | null>(null)
|
|
|
const ruler = ref<{ min: number; max: number } | null>(null)
|
|
const ruler = ref<{ min: number; max: number } | null>(null)
|
|
|
const timePoints = ref<TimePoint[]>([])
|
|
const timePoints = ref<TimePoint[]>([])
|
|
|
|
|
+const referencePoints = ref<TimePoint[]>([])
|
|
|
const startIndex = ref(0)
|
|
const startIndex = ref(0)
|
|
|
const waveData = ref<WaveWindowResponse | null>(null)
|
|
const waveData = ref<WaveWindowResponse | null>(null)
|
|
|
const chartMode = ref<'split' | 'merge'>('split')
|
|
const chartMode = ref<'split' | 'merge'>('split')
|
|
@@ -56,8 +59,20 @@ const selectedOptionRows = computed<QueryOption[]>(() => (
|
|
|
row.pointName === selectedPointName.value && selectedTypes.value.includes(row.measurementType)
|
|
row.pointName === selectedPointName.value && selectedTypes.value.includes(row.measurementType)
|
|
|
)) ?? []
|
|
)) ?? []
|
|
|
))
|
|
))
|
|
|
-const selectedWindowPoints = computed(() => timePoints.value.slice(startIndex.value, startIndex.value + windowSize.value))
|
|
|
|
|
-const endIndex = computed(() => Math.min(timePoints.value.length, startIndex.value + windowSize.value))
|
|
|
|
|
|
|
+const selectedWindowPoints = computed(() => {
|
|
|
|
|
+ const slice = timePoints.value.slice(startIndex.value, startIndex.value + stripWindowSize.value)
|
|
|
|
|
+ return hasReference.value ? [...referencePoints.value, ...slice] : slice
|
|
|
|
|
+})
|
|
|
|
|
+const hasReference = computed(() => referencePoints.value.length > 0)
|
|
|
|
|
+const stripWindowSize = computed(() => Math.max(0, windowSize.value - (hasReference.value ? 1 : 0)))
|
|
|
|
|
+const maxStart = computed(() => Math.max(0, timePoints.value.length - stripWindowSize.value))
|
|
|
|
|
+const endIndex = computed(() => Math.min(timePoints.value.length, startIndex.value + stripWindowSize.value))
|
|
|
|
|
+const statusFilter = computed(() => {
|
|
|
|
|
+ const list: string[] = []
|
|
|
|
|
+ if (abnormalOnly.value) list.push('abnormal')
|
|
|
|
|
+ if (noCycleOnly.value) list.push('no_cycle')
|
|
|
|
|
+ return list
|
|
|
|
|
+})
|
|
|
const currentCycles = computed(() => waveData.value?.cycles ?? [])
|
|
const currentCycles = computed(() => waveData.value?.cycles ?? [])
|
|
|
const currentSource = computed(() => waveData.value?.source ?? timePointsSource.value)
|
|
const currentSource = computed(() => waveData.value?.source ?? timePointsSource.value)
|
|
|
const timePointsSource = ref<'database' | 'demo'>('demo')
|
|
const timePointsSource = ref<'database' | 'demo'>('demo')
|
|
@@ -148,18 +163,21 @@ async function loadTimePoints() {
|
|
|
minTime: toApiTime(minTime.value),
|
|
minTime: toApiTime(minTime.value),
|
|
|
maxTime: toApiTime(maxTime.value),
|
|
maxTime: toApiTime(maxTime.value),
|
|
|
includeStopped: includeStopped.value,
|
|
includeStopped: includeStopped.value,
|
|
|
- minStatus: minStatus.value,
|
|
|
|
|
|
|
+ minStatus: noCycleOnly.value ? null : minStatus.value,
|
|
|
|
|
+ statusFilter: statusFilter.value,
|
|
|
})
|
|
})
|
|
|
timePoints.value = result.points
|
|
timePoints.value = result.points
|
|
|
|
|
+ referencePoints.value = result.referencePoints ?? []
|
|
|
timePointsSource.value = result.source
|
|
timePointsSource.value = result.source
|
|
|
startIndex.value = Math.max(
|
|
startIndex.value = Math.max(
|
|
|
0,
|
|
0,
|
|
|
- Math.min(firstRunningIndex(), Math.max(0, timePoints.value.length - windowSize.value)),
|
|
|
|
|
|
|
+ Math.min(firstRunningIndex(), Math.max(0, timePoints.value.length - stripWindowSize.value)),
|
|
|
)
|
|
)
|
|
|
chartDirty.value = true
|
|
chartDirty.value = true
|
|
|
void loadAnnotations()
|
|
void loadAnnotations()
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
timePoints.value = []
|
|
timePoints.value = []
|
|
|
|
|
+ referencePoints.value = []
|
|
|
errorMessage.value = error instanceof Error ? error.message : '时间点读取失败'
|
|
errorMessage.value = error instanceof Error ? error.message : '时间点读取失败'
|
|
|
} finally {
|
|
} finally {
|
|
|
timePointsLoading.value = false
|
|
timePointsLoading.value = false
|
|
@@ -227,13 +245,13 @@ function onTimeChange() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function onStartIndexChange(value: number) {
|
|
function onStartIndexChange(value: number) {
|
|
|
- startIndex.value = Math.max(0, Math.min(value, Math.max(0, timePoints.value.length - windowSize.value)))
|
|
|
|
|
|
|
+ startIndex.value = Math.max(0, Math.min(value, maxStart.value))
|
|
|
markChartDirty()
|
|
markChartDirty()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function onWindowSizeChange(value: number) {
|
|
function onWindowSizeChange(value: number) {
|
|
|
windowSize.value = Math.max(1, Math.min(200, Math.round(value || 1)))
|
|
windowSize.value = Math.max(1, Math.min(200, Math.round(value || 1)))
|
|
|
- startIndex.value = Math.min(startIndex.value, Math.max(0, timePoints.value.length - windowSize.value))
|
|
|
|
|
|
|
+ startIndex.value = Math.min(startIndex.value, maxStart.value)
|
|
|
markChartDirty()
|
|
markChartDirty()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -243,8 +261,8 @@ function resetWindow() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function pageWindow(direction: -1 | 1) {
|
|
function pageWindow(direction: -1 | 1) {
|
|
|
- const next = startIndex.value + direction * windowSize.value
|
|
|
|
|
- startIndex.value = Math.max(0, Math.min(next, Math.max(0, timePoints.value.length - windowSize.value)))
|
|
|
|
|
|
|
+ const next = startIndex.value + direction * stripWindowSize.value
|
|
|
|
|
+ startIndex.value = Math.max(0, Math.min(next, maxStart.value))
|
|
|
markChartDirty()
|
|
markChartDirty()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -444,6 +462,12 @@ watch(noSampling, () => {
|
|
|
watch(includeStopped, () => {
|
|
watch(includeStopped, () => {
|
|
|
if (initialized.value) scheduleTimePoints()
|
|
if (initialized.value) scheduleTimePoints()
|
|
|
})
|
|
})
|
|
|
|
|
+watch(abnormalOnly, () => {
|
|
|
|
|
+ if (initialized.value) scheduleTimePoints()
|
|
|
|
|
+})
|
|
|
|
|
+watch(noCycleOnly, () => {
|
|
|
|
|
+ if (initialized.value) scheduleTimePoints()
|
|
|
|
|
+})
|
|
|
watch(minStatus, () => {
|
|
watch(minStatus, () => {
|
|
|
if (initialized.value) scheduleTimePoints()
|
|
if (initialized.value) scheduleTimePoints()
|
|
|
})
|
|
})
|
|
@@ -674,9 +698,11 @@ onBeforeUnmount(() => {
|
|
|
<label class="field field-check">
|
|
<label class="field field-check">
|
|
|
<span class="field-label">运行状态</span>
|
|
<span class="field-label">运行状态</span>
|
|
|
<el-checkbox v-model="includeStopped" class="query-checkbox" size="large">停机</el-checkbox>
|
|
<el-checkbox v-model="includeStopped" class="query-checkbox" size="large">停机</el-checkbox>
|
|
|
|
|
+ <el-checkbox v-model="abnormalOnly" class="query-checkbox" size="large">异常</el-checkbox>
|
|
|
|
|
+ <el-checkbox v-model="noCycleOnly" class="query-checkbox" size="large">无周期</el-checkbox>
|
|
|
</label>
|
|
</label>
|
|
|
<label class="field field-status">
|
|
<label class="field field-status">
|
|
|
- <span class="field-label">质心距离 <em>异常级别 ≥ 输入值</em></span>
|
|
|
|
|
|
|
+ <span class="field-label">质心距离</span>
|
|
|
<el-input-number
|
|
<el-input-number
|
|
|
v-model="minStatus"
|
|
v-model="minStatus"
|
|
|
class="query-control"
|
|
class="query-control"
|
|
@@ -686,8 +712,10 @@ onBeforeUnmount(() => {
|
|
|
:step="1"
|
|
:step="1"
|
|
|
:step-strictly="true"
|
|
:step-strictly="true"
|
|
|
controls-position="right"
|
|
controls-position="right"
|
|
|
|
|
+ :disabled="queryLoading || noCycleOnly"
|
|
|
clearable
|
|
clearable
|
|
|
placeholder="可空"
|
|
placeholder="可空"
|
|
|
|
|
+ :title="noCycleOnly ? '勾选无周期时忽略质心距离' : undefined"
|
|
|
/>
|
|
/>
|
|
|
</label>
|
|
</label>
|
|
|
</div>
|
|
</div>
|
|
@@ -700,7 +728,7 @@ onBeforeUnmount(() => {
|
|
|
:points="timePoints"
|
|
:points="timePoints"
|
|
|
:measurement-types="selectedTypes"
|
|
:measurement-types="selectedTypes"
|
|
|
:start-index="startIndex"
|
|
:start-index="startIndex"
|
|
|
- :window-size="windowSize"
|
|
|
|
|
|
|
+ :window-size="stripWindowSize"
|
|
|
:loading="timePointsLoading"
|
|
:loading="timePointsLoading"
|
|
|
:min-time="minTime"
|
|
:min-time="minTime"
|
|
|
:max-time="maxTime"
|
|
:max-time="maxTime"
|
|
@@ -712,6 +740,9 @@ onBeforeUnmount(() => {
|
|
|
<div class="selection-readout">
|
|
<div class="selection-readout">
|
|
|
<span class="selection-accent"></span>
|
|
<span class="selection-accent"></span>
|
|
|
<span>当前窗口覆盖 <strong>{{ selectedWindowPoints.length }}</strong> 个时间点</span>
|
|
<span>当前窗口覆盖 <strong>{{ selectedWindowPoints.length }}</strong> 个时间点</span>
|
|
|
|
|
+ <span v-if="hasReference" class="reference-badge" title="始终保留一个 status=0 的正常数据用于对比,固定不随翻页变化">
|
|
|
|
|
+ 正常对比:{{ formatDateTime(referencePoints[0]?.sampleTime) }}
|
|
|
|
|
+ </span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="selection-actions">
|
|
<div class="selection-actions">
|
|
|
<el-button class="ghost-button" plain :disabled="!startIndex" @click="resetWindow">回到起点</el-button>
|
|
<el-button class="ghost-button" plain :disabled="!startIndex" @click="resetWindow">回到起点</el-button>
|