|
|
@@ -1,18 +1,18 @@
|
|
|
<script setup lang="ts">
|
|
|
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
|
|
import { ElMessageBox } from 'element-plus'
|
|
|
-import { createAnnotation, deleteAnnotation, deletePretrain, fetchAnnotationConfig, fetchAnnotationsByIds, fetchPeriodDetail, fetchPretrainStatus, fetchQueryOptions, fetchTimePoints, fetchTspluseRuler, fetchWaveWindow, getToken, login as apiLogin, logout as apiLogout, recordPretrain } from './api'
|
|
|
+import { createAnnotation, deleteAnnotation, deletePretrain, fetchAnnotationConfig, fetchAnnotationsByIds, fetchPeriodDetail, fetchPretrainStatus, fetchQueryOptions, fetchSitePoints, fetchTimePoints, fetchTspluseRuler, fetchWaveWindow, getToken, login as apiLogin, logout as apiLogout, recordPretrain } from './api'
|
|
|
import PeriodModal from './components/PeriodModal.vue'
|
|
|
import { fixedAxisRange } from './utils/axis'
|
|
|
import TimePointStrip from './components/TimePointStrip.vue'
|
|
|
import WaveChart from './components/WaveChart.vue'
|
|
|
import { STOPPED_COLOR, statusGradientColor } from './statusColor'
|
|
|
-import { DEVICE_POINT_TO_TYPE, DEVICE_POINTS, PRIMARY_DEVICE_POINT, type Annotation, type AnnotationLabel, type Cycle, type DevicePoint, type MeasurementType, type PeriodDetail, type QueryOption, type TimePoint, type QueryOptionsResponse, type WaveWindowFile, type WaveWindowResponse } from './types'
|
|
|
+import { DEVICE_POINT_TO_TYPE, DEVICE_POINTS, PRIMARY_DEVICE_POINT, type Annotation, type AnnotationLabel, type Cycle, type DevicePoint, type MeasurementType, type PeriodDetail, type QueryOption, type SitePoint, type TimePoint, type QueryOptionsResponse, type WaveWindowFile, type WaveWindowResponse } from './types'
|
|
|
|
|
|
const queryMeta = ref<QueryOptionsResponse | null>(null)
|
|
|
const selectedDevicePart = ref('')
|
|
|
const selectedDevicePoints = ref<DevicePoint[]>([PRIMARY_DEVICE_POINT])
|
|
|
-const fileListPoint = ref<DevicePoint>(PRIMARY_DEVICE_POINT)
|
|
|
+const fileListPoint = ref<DevicePoint | string>(PRIMARY_DEVICE_POINT)
|
|
|
const minTime = ref('')
|
|
|
const maxTime = ref('')
|
|
|
const selectedTimeRangePoint = ref<DevicePoint | ''>('')
|
|
|
@@ -24,6 +24,8 @@ const abnormalOnly = ref(false)
|
|
|
const noCycleOnly = ref(false)
|
|
|
const minStatus = ref<number | null>(null)
|
|
|
const firstCycleOnly = ref(false)
|
|
|
+const sitePointOptions = ref<SitePoint[]>([])
|
|
|
+const selectedSitePoints = ref<string[]>([])
|
|
|
const ruler = ref<{ min: number; max: number } | null>(null)
|
|
|
const timePoints = ref<TimePoint[]>([])
|
|
|
const referencePoints = ref<TimePoint[]>([])
|
|
|
@@ -55,6 +57,49 @@ let waveRequestId = 0
|
|
|
const deviceParts = computed(() => queryMeta.value?.deviceParts ?? [])
|
|
|
const devicePoints = computed(() => queryMeta.value?.devicePoints ?? DEVICE_POINTS)
|
|
|
|
|
|
+const unitNumber = computed(() => {
|
|
|
+ const match = /^([789])号机组/.exec(selectedDevicePart.value)
|
|
|
+ return match ? match[1] : null
|
|
|
+})
|
|
|
+const isPksMode = computed(() => firstCycleOnly.value && !!unitNumber.value)
|
|
|
+const pointModel = computed({
|
|
|
+ get: () => (isPksMode.value
|
|
|
+ ? [...selectedDevicePoints.value as unknown as string[], ...selectedSitePoints.value]
|
|
|
+ : selectedDevicePoints.value as unknown as string[]),
|
|
|
+ set: (value: string[]) => {
|
|
|
+ const wave = value.filter((item) => (DEVICE_POINTS as readonly string[]).includes(item)) as DevicePoint[]
|
|
|
+ const site = value.filter((item) => !(DEVICE_POINTS as readonly string[]).includes(item))
|
|
|
+ selectedDevicePoints.value = wave
|
|
|
+ selectedSitePoints.value = site
|
|
|
+ },
|
|
|
+})
|
|
|
+const sitePointLabel = (point: SitePoint) => `${point.itemName} ${point.itemDescription}`.trim()
|
|
|
+const SITE_TAB_LABEL = '全场点位'
|
|
|
+const siteDescriptionMap = computed<Record<string, string>>(() => {
|
|
|
+ const map: Record<string, string> = {}
|
|
|
+ for (const item of sitePointOptions.value) map[item.itemName] = item.itemDescription
|
|
|
+ return map
|
|
|
+})
|
|
|
+const siteDescription = (itemName: string) => siteDescriptionMap.value[itemName] || itemName
|
|
|
+const fileTabs = computed(() => {
|
|
|
+ if (!isPksMode.value) return selectedDevicePoints.value as unknown as string[]
|
|
|
+ return [
|
|
|
+ ...selectedDevicePoints.value as unknown as string[],
|
|
|
+ ...(selectedSitePoints.value.length ? [SITE_TAB_LABEL] : []),
|
|
|
+ ]
|
|
|
+})
|
|
|
+const isSiteTab = computed(() => fileListPoint.value === SITE_TAB_LABEL)
|
|
|
+const windowSiteRows = computed(() => {
|
|
|
+ if (!isSiteTab.value) return []
|
|
|
+ return (waveData.value?.points ?? []).map((point, index) => ({
|
|
|
+ index,
|
|
|
+ sampleTime: point.sampleTime,
|
|
|
+ values: Object.fromEntries(
|
|
|
+ selectedSitePoints.value.map((itemName) => [itemName, point.siteValues?.[itemName] ?? null]),
|
|
|
+ ),
|
|
|
+ }))
|
|
|
+})
|
|
|
+
|
|
|
const devicePointCounts = computed<Record<string, { pre: number | null; ab: number | null }>>(() => {
|
|
|
const map: Record<string, { pre: number | null; ab: number | null }> = {}
|
|
|
for (const point of devicePoints.value) {
|
|
|
@@ -176,6 +221,19 @@ async function loadOptions() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+async function loadSitePoints() {
|
|
|
+ if (!isPksMode.value || !selectedDevicePart.value) {
|
|
|
+ sitePointOptions.value = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const result = await fetchSitePoints(selectedDevicePart.value)
|
|
|
+ sitePointOptions.value = result.items
|
|
|
+ } catch {
|
|
|
+ sitePointOptions.value = []
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
async function loadTspluseRuler() {
|
|
|
try {
|
|
|
ruler.value = await fetchTspluseRuler()
|
|
|
@@ -205,6 +263,7 @@ async function loadTimePoints() {
|
|
|
includeStopped: includeStopped.value,
|
|
|
minStatus: noCycleOnly.value ? null : minStatus.value,
|
|
|
statusFilter: statusFilter.value,
|
|
|
+ sitePoints: isPksMode.value ? selectedSitePoints.value : [],
|
|
|
})
|
|
|
timePoints.value = result.points
|
|
|
referencePoints.value = result.referencePoints ?? []
|
|
|
@@ -241,6 +300,7 @@ async function loadWaveWindowNow() {
|
|
|
maxPoints: maxPoints.value,
|
|
|
noSampling: noSampling.value,
|
|
|
firstCycleOnly: firstCycleOnly.value,
|
|
|
+ sitePoints: isPksMode.value ? selectedSitePoints.value : [],
|
|
|
})
|
|
|
if (requestId === waveRequestId) {
|
|
|
waveData.value = result
|
|
|
@@ -279,6 +339,7 @@ function onTimeRangeChange(value: DevicePoint | '') {
|
|
|
|
|
|
function onDevicePartChange() {
|
|
|
selectedTimeRangePoint.value = ''
|
|
|
+ selectedSitePoints.value = []
|
|
|
syncTimeBounds(true)
|
|
|
scheduleTimePoints()
|
|
|
}
|
|
|
@@ -552,6 +613,19 @@ watch(firstCycleOnly, () => {
|
|
|
markChartDirty()
|
|
|
void loadWaveWindowNow()
|
|
|
})
|
|
|
+watch([firstCycleOnly, unitNumber], async () => {
|
|
|
+ if (!initialized.value) return
|
|
|
+ if (isPksMode.value) {
|
|
|
+ await loadSitePoints()
|
|
|
+ if (selectedSitePoints.value.length) fileListPoint.value = SITE_TAB_LABEL
|
|
|
+ } else {
|
|
|
+ sitePointOptions.value = []
|
|
|
+ }
|
|
|
+})
|
|
|
+watch(selectedSitePoints, (points) => {
|
|
|
+ if (initialized.value && isPksMode.value) scheduleTimePoints()
|
|
|
+ if (points.length) fileListPoint.value = SITE_TAB_LABEL
|
|
|
+})
|
|
|
watch(waveData, () => void refreshPretrainStatus(), { deep: false })
|
|
|
|
|
|
async function refreshPretrainStatus() {
|
|
|
@@ -720,7 +794,7 @@ onBeforeUnmount(() => {
|
|
|
<div class="field field-types">
|
|
|
<span class="field-label">测试点位</span>
|
|
|
<el-select
|
|
|
- v-model="selectedDevicePoints"
|
|
|
+ v-model="pointModel"
|
|
|
class="query-control"
|
|
|
size="large"
|
|
|
multiple
|
|
|
@@ -729,19 +803,46 @@ onBeforeUnmount(() => {
|
|
|
:max-collapse-tags="2"
|
|
|
placeholder="选择测试点位"
|
|
|
>
|
|
|
- <el-option
|
|
|
- v-for="point in devicePoints"
|
|
|
- :key="point"
|
|
|
- :label="devicePointLabel(point)"
|
|
|
- :value="point"
|
|
|
- >
|
|
|
- <span>{{ point }}</span>
|
|
|
- <template v-if="devicePointCounts[point]?.pre !== null || devicePointCounts[point]?.ab !== null">
|
|
|
- <span class="point-counts">
|
|
|
- (<span v-if="devicePointCounts[point]?.pre !== null">{{ devicePointCounts[point]?.pre }}</span><template v-if="devicePointCounts[point]?.pre !== null && devicePointCounts[point]?.ab !== null"> · </template><span v-if="devicePointCounts[point]?.ab !== null" class="abnormal-count">{{ devicePointCounts[point]?.ab }}</span>)
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-option>
|
|
|
+ <template v-if="isPksMode">
|
|
|
+ <el-option-group label="波形点位">
|
|
|
+ <el-option
|
|
|
+ v-for="point in devicePoints"
|
|
|
+ :key="point"
|
|
|
+ :label="devicePointLabel(point)"
|
|
|
+ :value="point"
|
|
|
+ >
|
|
|
+ <span>{{ point }}</span>
|
|
|
+ <template v-if="devicePointCounts[point]?.pre !== null || devicePointCounts[point]?.ab !== null">
|
|
|
+ <span class="point-counts">
|
|
|
+ (<span v-if="devicePointCounts[point]?.pre !== null">{{ devicePointCounts[point]?.pre }}</span><template v-if="devicePointCounts[point]?.pre !== null && devicePointCounts[point]?.ab !== null"> · </template><span v-if="devicePointCounts[point]?.ab !== null" class="abnormal-count">{{ devicePointCounts[point]?.ab }}</span>)
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-option>
|
|
|
+ </el-option-group>
|
|
|
+ <el-option-group label="全场点位 (PKS)">
|
|
|
+ <el-option
|
|
|
+ v-for="point in sitePointOptions"
|
|
|
+ :key="point.itemName"
|
|
|
+ :value="point.itemName"
|
|
|
+ :label="sitePointLabel(point)"
|
|
|
+ />
|
|
|
+ </el-option-group>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <el-option
|
|
|
+ v-for="point in devicePoints"
|
|
|
+ :key="point"
|
|
|
+ :label="devicePointLabel(point)"
|
|
|
+ :value="point"
|
|
|
+ >
|
|
|
+ <span>{{ point }}</span>
|
|
|
+ <template v-if="devicePointCounts[point]?.pre !== null || devicePointCounts[point]?.ab !== null">
|
|
|
+ <span class="point-counts">
|
|
|
+ (<span v-if="devicePointCounts[point]?.pre !== null">{{ devicePointCounts[point]?.pre }}</span><template v-if="devicePointCounts[point]?.pre !== null && devicePointCounts[point]?.ab !== null"> · </template><span v-if="devicePointCounts[point]?.ab !== null" class="abnormal-count">{{ devicePointCounts[point]?.ab }}</span>)
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-option>
|
|
|
+ </template>
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<label class="field">
|
|
|
@@ -855,6 +956,8 @@ onBeforeUnmount(() => {
|
|
|
:max-time="maxTime"
|
|
|
:annotated-file-ids="annotatedFileIds"
|
|
|
:ruler="ruler"
|
|
|
+ :site-points="isPksMode ? selectedSitePoints : []"
|
|
|
+ :site-descriptions="siteDescriptionMap"
|
|
|
@update:start-index="onStartIndexChange"
|
|
|
/>
|
|
|
<div class="selection-controls">
|
|
|
@@ -897,15 +1000,31 @@ onBeforeUnmount(() => {
|
|
|
>查询图表</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div v-if="waveData?.files.length" class="window-files">
|
|
|
+ <div v-if="(waveData?.files.length || windowSiteRows.length)" class="window-files">
|
|
|
<div class="window-files-title">
|
|
|
- <span>当前窗口 wave_file 记录</span>
|
|
|
+ <span>{{ isSiteTab ? '当前窗口 全场点位记录' : '当前窗口 wave_file 记录' }}</span>
|
|
|
<el-radio-group v-model="fileListPoint" class="file-point-radio" size="small">
|
|
|
- <el-radio-button v-for="point in devicePoints" :key="point" :value="point">{{ point }}</el-radio-button>
|
|
|
+ <el-radio-button v-for="point in fileTabs" :key="point" :value="point">{{ point }}</el-radio-button>
|
|
|
</el-radio-group>
|
|
|
</div>
|
|
|
<div class="window-files-scroll">
|
|
|
- <table class="window-files-table">
|
|
|
+ <table v-if="isSiteTab" class="window-files-table">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>#</th>
|
|
|
+ <th>sample_time</th>
|
|
|
+ <th v-for="itemName in selectedSitePoints" :key="itemName" :title="itemName">{{ siteDescription(itemName) }}</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr v-for="row in windowSiteRows" :key="row.index">
|
|
|
+ <td class="mono">{{ row.index + 1 }}</td>
|
|
|
+ <td class="mono">{{ row.sampleTime }}</td>
|
|
|
+ <td v-for="itemName in selectedSitePoints" :key="itemName" class="mono">{{ row.values[itemName] ?? '—' }}</td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ <table v-else class="window-files-table">
|
|
|
<thead>
|
|
|
<tr>
|
|
|
<th>id</th>
|
|
|
@@ -986,6 +1105,7 @@ onBeforeUnmount(() => {
|
|
|
:mode="chartMode"
|
|
|
:loading="waveLoading"
|
|
|
:annotations="annotations"
|
|
|
+ :site-descriptions="siteDescriptionMap"
|
|
|
@period-dblclick="openPeriod"
|
|
|
@annotation-toggle="onAnnotationToggle"
|
|
|
/>
|
|
|
@@ -1009,7 +1129,7 @@ onBeforeUnmount(() => {
|
|
|
<div class="readout-card"><span>显示策略</span><strong>MIN / MAX</strong></div>
|
|
|
</div>
|
|
|
<div class="data-footprint">
|
|
|
- <div><span>测试点位</span><strong>{{ selectedDevicePoints.join(' / ') }}</strong></div>
|
|
|
+ <div><span>测试点位</span><strong>{{ [...selectedDevicePoints, ...(isPksMode ? selectedSitePoints : [])].join(' / ') || '未选择' }}</strong></div>
|
|
|
<div><span>可用文件</span><strong>{{ availableTypeCount.toLocaleString() }}</strong></div>
|
|
|
<div><span>抽样上限</span><strong>{{ maxPoints.toLocaleString() }} 点</strong></div>
|
|
|
</div>
|