| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- <script setup lang="ts">
- import * as echarts from 'echarts'
- import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
- import type { PeriodDetail } from '../types'
- const props = defineProps<{
- detail: PeriodDetail | null
- visible: boolean
- loading?: boolean
- }>()
- const emit = defineEmits<{
- close: []
- }>()
- const chartElement = ref<HTMLDivElement | null>(null)
- const chartMode = ref<'angle' | 'volume'>('volume')
- let chart: echarts.ECharts | undefined
- function render() {
- if (!chart || !props.detail) return
- const detail = props.detail
- const volumeMode = chartMode.value === 'volume' && detail.volume
- const series: echarts.SeriesOption[] = detail.phases.map((phase, phaseIndex) => {
- const isLast = phaseIndex === detail.phases.length - 1
- const data: Array<{ value: [number, number]; idx: number }> = []
- detail.angles360.forEach((angle360, index) => {
- const x = volumeMode ? detail.volume?.[index] : detail.angles[index]
- if (x == null || !Number.isFinite(x)) return
- const inPhase = isLast ? angle360 >= phase.start : angle360 >= phase.start && angle360 < phase.end
- if (inPhase) data.push({ value: [x, detail.pressure[index]], idx: index })
- })
- return {
- name: phase.name,
- type: 'line' as const,
- showSymbol: false,
- lineStyle: { color: phase.color, width: 1.6 },
- itemStyle: { color: phase.color },
- data,
- }
- })
- chart.setOption({
- animation: false,
- grid: { left: 64, right: 28, top: 38, bottom: 52 },
- tooltip: {
- trigger: 'axis',
- backgroundColor: '#162b3c',
- borderWidth: 0,
- textStyle: { color: '#fff', fontSize: 11 },
- formatter: (params: any[]) => {
- const item = params?.[0]
- if (!item) return ''
- const index = item.data?.idx ?? 0
- const angle = detail.angles[index]
- const pressure = detail.pressure[index]
- const volume = detail.volume?.[index]
- const phase = detail.phases.find((p) => detail.angles360[index] >= p.start && detail.angles360[index] < p.end)
- return [
- `曲轴角度:${angle?.toFixed(1)}°${volumeMode ? `(0-360:${detail.angles360[index]?.toFixed(1)}°)` : ''}`,
- `压力原始值:${pressure?.toPrecision(8)}`,
- volumeMode ? `体积:${volume?.toFixed(4)} L` : '',
- phase ? `<span style="color:${phase.color}">●</span> ${phase.name}` : '',
- ].filter(Boolean).join('<br/>')
- },
- },
- legend: { top: 0, left: 70, itemWidth: 16, itemHeight: 7, textStyle: { color: '#60717b', fontSize: 11 } },
- xAxis: {
- type: 'value',
- name: volumeMode ? '气缸体积 (L)' : '曲轴角度 (°)',
- nameLocation: 'middle',
- nameGap: 30,
- min: volumeMode ? undefined : 0,
- max: volumeMode ? undefined : 180,
- axisLine: { lineStyle: { color: '#9eabb1' } },
- axisLabel: { color: '#71808a' },
- },
- yAxis: {
- type: 'value',
- name: '压力原始值',
- nameLocation: 'middle',
- nameGap: 45,
- nameTextStyle: { color: '#e4572e' },
- axisLine: { show: true, lineStyle: { color: '#e4572e' } },
- axisLabel: { color: '#71808a' },
- splitLine: { lineStyle: { color: '#edf1f2' } },
- },
- series,
- }, true)
- chart.resize()
- }
- async function ensureChart() {
- if (!props.visible) {
- chart?.dispose()
- chart = undefined
- return
- }
- await nextTick()
- if (!chartElement.value) return
- if (!chart) chart = echarts.init(chartElement.value, undefined, { renderer: 'canvas' })
- render()
- }
- watch(() => [props.detail, props.visible, chartMode.value], () => void ensureChart(), { deep: true })
- onMounted(() => {
- void ensureChart()
- })
- onBeforeUnmount(() => chart?.dispose())
- </script>
- <template>
- <Teleport to="body">
- <div v-if="visible" class="modal-backdrop" @click.self="emit('close')">
- <section class="period-modal" role="dialog" aria-modal="true">
- <header class="period-modal-head">
- <div>
- <h2>周期功图</h2>
- <p>{{ detail?.waveFile.pointName }} · wave_file #{{ detail?.waveFile.id }} · {{ detail?.waveFile.sampleTime }}</p>
- </div>
- <button class="icon-button" type="button" aria-label="关闭" @click="emit('close')">×</button>
- </header>
- <div v-if="loading" class="modal-loading">正在生成角度与体积数据…</div>
- <template v-else-if="detail">
- <div class="period-stat-row">
- <div><span>采样范围</span><strong>{{ detail.period.startSampleIndex }} — {{ detail.period.endSampleIndex }}</strong></div>
- <div><span>周期采样数</span><strong>{{ detail.period.sampleCount.toLocaleString() }}</strong></div>
- <div><span>键相点</span><strong>{{ detail.period.triggerSampleIndices.length }} 个</strong></div>
- <div v-if="detail.volumeInfo"><span>缸径 / 余隙</span><strong>{{ detail.volumeInfo.boreMm }} mm / {{ detail.volumeInfo.clearanceVolumeL }} L</strong></div>
- </div>
- <div class="modal-tabs">
- <button :class="{ active: chartMode === 'volume' }" type="button" :disabled="!detail.volume" @click="chartMode = 'volume'">压力-体积功图</button>
- <button :class="{ active: chartMode === 'angle' }" type="button" @click="chartMode = 'angle'">压力-角度图</button>
- </div>
- <div ref="chartElement" class="period-chart"></div>
- </template>
- </section>
- </div>
- </Teleport>
- </template>
|