2026-07-20 10:21:30 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-07-20 14:07:43 +08:00
|
|
|
|
import { Button, Input, InputNumber, Radio, Select, Switch } from 'ant-design-vue'
|
|
|
|
|
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
|
|
|
|
import { ColorPicker } from 'vue3-colorpicker'
|
|
|
|
|
|
import 'vue3-colorpicker/style.css'
|
2026-07-20 10:21:30 +08:00
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
WaveformChart,
|
|
|
|
|
|
type WaveformAnnotation,
|
|
|
|
|
|
type WaveformData,
|
|
|
|
|
|
type WaveformDisplayMode,
|
2026-07-20 14:07:43 +08:00
|
|
|
|
type WaveformFrameStyle,
|
2026-07-20 10:21:30 +08:00
|
|
|
|
type WaveformInteractionMode,
|
2026-07-20 14:07:43 +08:00
|
|
|
|
type WaveformLegendOrientation,
|
|
|
|
|
|
type WaveformLegendPosition,
|
2026-07-20 15:35:19 +08:00
|
|
|
|
type WaveformOverlayMode,
|
2026-07-20 10:21:30 +08:00
|
|
|
|
type WaveformSeries,
|
2026-07-20 14:07:43 +08:00
|
|
|
|
type WaveformTitleOptions,
|
2026-07-21 12:09:00 +08:00
|
|
|
|
type WaveformZoomEndPayload,
|
2026-07-20 10:21:30 +08:00
|
|
|
|
} from './components'
|
2026-07-21 09:15:24 +08:00
|
|
|
|
import chartWaveformsJson from './data/chartWaveforms.json'
|
|
|
|
|
|
import demoWaveformsJson from './data/demoWaveforms.json'
|
|
|
|
|
|
|
|
|
|
|
|
interface WaveformSourcePoint {
|
|
|
|
|
|
x: number
|
|
|
|
|
|
y: number
|
|
|
|
|
|
error?: number
|
|
|
|
|
|
lowerError?: number
|
|
|
|
|
|
upperError?: number
|
|
|
|
|
|
}
|
2026-07-20 10:21:30 +08:00
|
|
|
|
|
|
|
|
|
|
interface WaveformSourceRow {
|
|
|
|
|
|
chnl: string
|
|
|
|
|
|
chnl_id: number
|
|
|
|
|
|
dat_unit: string
|
2026-07-21 09:15:24 +08:00
|
|
|
|
data: WaveformSourcePoint[]
|
2026-07-20 10:21:30 +08:00
|
|
|
|
dev: number
|
|
|
|
|
|
shot: number
|
2026-07-21 09:15:24 +08:00
|
|
|
|
time?: number[]
|
2026-07-20 10:21:30 +08:00
|
|
|
|
time_unit: 'ms'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 09:15:24 +08:00
|
|
|
|
const sourceRows = chartWaveformsJson as unknown as WaveformSourceRow[]
|
2026-07-20 10:21:30 +08:00
|
|
|
|
const displayMode = ref<WaveformDisplayMode>('independent')
|
2026-07-20 15:35:19 +08:00
|
|
|
|
const overlayMode = ref<WaveformOverlayMode>('single-axis')
|
2026-07-20 10:21:30 +08:00
|
|
|
|
const rowCount = ref(2)
|
|
|
|
|
|
const columnCount = ref(1)
|
2026-07-20 14:07:43 +08:00
|
|
|
|
const frameBorderColor = ref('#1f2937')
|
|
|
|
|
|
const frameBorderWidth = ref(1)
|
|
|
|
|
|
const frameBorderStyle = ref<'solid' | 'dashed'>('solid')
|
|
|
|
|
|
const frameBackgroundColor = ref('rgba(255, 255, 255, 0)')
|
|
|
|
|
|
const frameWatermarkVisible = ref(true)
|
2026-07-20 10:21:30 +08:00
|
|
|
|
const annotations = ref<WaveformAnnotation[]>([])
|
|
|
|
|
|
const annotationsVisible = ref(true)
|
|
|
|
|
|
const interactionMode = ref<WaveformInteractionMode>('zoom')
|
2026-07-20 14:07:43 +08:00
|
|
|
|
const legendPosition = ref<WaveformLegendPosition>('top-right')
|
|
|
|
|
|
const legendOrientation = ref<WaveformLegendOrientation>('auto')
|
|
|
|
|
|
const legendBackgroundColor = ref('rgba(255, 255, 255, 0.7)')
|
2026-07-20 17:57:52 +08:00
|
|
|
|
const hiddenSeriesIds = ref<string[]>([])
|
2026-07-20 14:07:43 +08:00
|
|
|
|
const titleVisible = ref(true)
|
|
|
|
|
|
const titleText = ref(`Shot:${sourceRows[0]?.shot ?? 4712}`)
|
|
|
|
|
|
const titleAlign = ref<NonNullable<WaveformTitleOptions['align']>>('center')
|
|
|
|
|
|
const titleFontFamily = ref('"Microsoft YaHei", "微软雅黑", sans-serif')
|
|
|
|
|
|
const titleFontSize = ref(14)
|
|
|
|
|
|
const titleRotation = ref(0)
|
|
|
|
|
|
const titleColor = ref('#1f2937')
|
|
|
|
|
|
const titleBold = ref(false)
|
|
|
|
|
|
const titleItalic = ref(false)
|
|
|
|
|
|
const titleUnderline = ref(false)
|
|
|
|
|
|
const controlsOpen = ref(false)
|
|
|
|
|
|
const legendPositionOptions: Array<{ label: string; value: WaveformLegendPosition }> = [
|
|
|
|
|
|
{ label: '左上', value: 'top-left' },
|
|
|
|
|
|
{ label: '上', value: 'top' },
|
|
|
|
|
|
{ label: '右上', value: 'top-right' },
|
|
|
|
|
|
{ label: '右', value: 'right' },
|
|
|
|
|
|
{ label: '右下', value: 'bottom-right' },
|
|
|
|
|
|
{ label: '下', value: 'bottom' },
|
|
|
|
|
|
{ label: '左下', value: 'bottom-left' },
|
|
|
|
|
|
{ label: '左', value: 'left' },
|
|
|
|
|
|
]
|
|
|
|
|
|
const legendOrientationOptions: Array<{ label: string; value: WaveformLegendOrientation }> = [
|
|
|
|
|
|
{ label: '自动', value: 'auto' },
|
|
|
|
|
|
{ label: '水平', value: 'horizontal' },
|
|
|
|
|
|
{ label: '垂直', value: 'vertical' },
|
|
|
|
|
|
]
|
|
|
|
|
|
const frameBorderStyleOptions = [
|
|
|
|
|
|
{ label: '实线', value: 'solid' },
|
|
|
|
|
|
{ label: '虚线', value: 'dashed' },
|
|
|
|
|
|
]
|
|
|
|
|
|
const titleAlignOptions: Array<{
|
|
|
|
|
|
label: string
|
|
|
|
|
|
value: NonNullable<WaveformTitleOptions['align']>
|
|
|
|
|
|
}> = [
|
|
|
|
|
|
{ label: '左对齐', value: 'left' },
|
|
|
|
|
|
{ label: '居中', value: 'center' },
|
|
|
|
|
|
{ label: '右对齐', value: 'right' },
|
|
|
|
|
|
]
|
|
|
|
|
|
const titleFontFamilyOptions = [
|
|
|
|
|
|
{ label: '微软雅黑', value: '"Microsoft YaHei", "微软雅黑", sans-serif' },
|
|
|
|
|
|
{ label: '宋体', value: 'SimSun, serif' },
|
|
|
|
|
|
{ label: '黑体', value: 'SimHei, sans-serif' },
|
|
|
|
|
|
{ label: 'Arial', value: 'Arial, sans-serif' },
|
|
|
|
|
|
{ label: 'Consolas', value: 'Consolas, monospace' },
|
|
|
|
|
|
]
|
|
|
|
|
|
const frameStyle = computed<WaveformFrameStyle>(() => ({
|
|
|
|
|
|
borderColor: frameBorderColor.value,
|
|
|
|
|
|
borderWidth: frameBorderWidth.value,
|
|
|
|
|
|
borderStyle: frameBorderStyle.value,
|
|
|
|
|
|
backgroundColor: frameBackgroundColor.value,
|
|
|
|
|
|
}))
|
2026-07-20 10:21:30 +08:00
|
|
|
|
|
2026-07-20 17:57:52 +08:00
|
|
|
|
const seriesStylePresets: Array<Pick<WaveformSeries, 'lineType' | 'pointType' | 'errorBar'>> = [
|
|
|
|
|
|
{ lineType: 'none', pointType: 'triangle', errorBar: { visible: true } },
|
|
|
|
|
|
{ lineType: 'linear', pointType: 'none' },
|
|
|
|
|
|
{ lineType: 'step-after', pointType: 'circle', errorBar: { visible: true } },
|
|
|
|
|
|
{ lineType: 'linear', pointType: 'diamond', errorBar: { visible: true } },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const waveformSeries: WaveformSeries[] = sourceRows.map((row, seriesIndex) => {
|
|
|
|
|
|
const presetStyle = seriesStylePresets[seriesIndex % seriesStylePresets.length]!
|
|
|
|
|
|
const style: Pick<WaveformSeries, 'lineType' | 'pointType' | 'errorBar'> =
|
|
|
|
|
|
row.chnl === 'TEST_CH_4'
|
|
|
|
|
|
? { lineType: 'linear', pointType: 'circle', errorBar: { visible: false } }
|
|
|
|
|
|
: presetStyle
|
2026-07-20 10:21:30 +08:00
|
|
|
|
return {
|
|
|
|
|
|
id: String(row.chnl_id),
|
2026-07-20 14:07:43 +08:00
|
|
|
|
trackId:
|
2026-07-20 15:35:19 +08:00
|
|
|
|
row.chnl.startsWith('TEST_CH_') && row.chnl !== 'TEST_CH_2'
|
2026-07-21 09:15:24 +08:00
|
|
|
|
? String(sourceRows[0]?.chnl_id ?? row.chnl_id)
|
2026-07-20 15:35:19 +08:00
|
|
|
|
: undefined,
|
2026-07-20 10:21:30 +08:00
|
|
|
|
name: row.chnl,
|
|
|
|
|
|
unit: row.dat_unit,
|
2026-07-20 17:57:52 +08:00
|
|
|
|
...style,
|
2026-07-20 10:21:30 +08:00
|
|
|
|
data: {
|
|
|
|
|
|
kind: 'points',
|
2026-07-21 09:15:24 +08:00
|
|
|
|
points: row.data,
|
2026-07-20 10:21:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-07-21 09:15:24 +08:00
|
|
|
|
const demoWaveforms = demoWaveformsJson as {
|
|
|
|
|
|
stepDemoValues: Array<{
|
|
|
|
|
|
id: string
|
|
|
|
|
|
name: string
|
|
|
|
|
|
color: string
|
|
|
|
|
|
lineType: 'step-start' | 'step-middle' | 'step-end'
|
|
|
|
|
|
values: number[]
|
|
|
|
|
|
}>
|
|
|
|
|
|
basicCurveDemoSeries: Array<{
|
|
|
|
|
|
id: string
|
|
|
|
|
|
name: string
|
|
|
|
|
|
color: string
|
|
|
|
|
|
lineType: 'none' | 'linear'
|
|
|
|
|
|
pointType: 'circle' | 'none'
|
|
|
|
|
|
points: Array<{ x: number; y: number }>
|
|
|
|
|
|
}>
|
|
|
|
|
|
}
|
2026-07-20 17:57:52 +08:00
|
|
|
|
|
2026-07-21 09:15:24 +08:00
|
|
|
|
const stepDemoSeries: WaveformSeries[] = demoWaveforms.stepDemoValues.map((series) => ({
|
2026-07-20 17:57:52 +08:00
|
|
|
|
id: series.id,
|
|
|
|
|
|
trackId: 'step-demo',
|
|
|
|
|
|
name: series.name,
|
|
|
|
|
|
color: series.color,
|
|
|
|
|
|
lineType: series.lineType,
|
|
|
|
|
|
pointType: 'circle',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
kind: 'points',
|
|
|
|
|
|
points: series.values.map((y, index) => ({ x: index / 1000, y })),
|
|
|
|
|
|
},
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
2026-07-21 09:15:24 +08:00
|
|
|
|
const frameOneTrackId = String(sourceRows[0]?.chnl_id ?? 'frame-one')
|
|
|
|
|
|
const basicCurveDemoSeries: WaveformSeries[] = demoWaveforms.basicCurveDemoSeries.map((series) => ({
|
|
|
|
|
|
id: series.id,
|
|
|
|
|
|
trackId: frameOneTrackId,
|
|
|
|
|
|
name: series.name,
|
|
|
|
|
|
color: series.color,
|
|
|
|
|
|
lineType: series.lineType,
|
|
|
|
|
|
pointType: series.pointType,
|
|
|
|
|
|
data: { kind: 'points', points: series.points },
|
|
|
|
|
|
}))
|
2026-07-20 17:57:52 +08:00
|
|
|
|
const frameOneSeries = waveformSeries.filter(
|
|
|
|
|
|
(series) => series.id === frameOneTrackId || series.trackId === frameOneTrackId,
|
|
|
|
|
|
)
|
|
|
|
|
|
const remainingSeries = waveformSeries.filter((series) => !frameOneSeries.includes(series))
|
2026-07-21 12:09:00 +08:00
|
|
|
|
const fullChartData: WaveformData = {
|
2026-07-20 17:57:52 +08:00
|
|
|
|
kind: 'series',
|
|
|
|
|
|
series: [...frameOneSeries, ...basicCurveDemoSeries, ...stepDemoSeries, ...remainingSeries],
|
|
|
|
|
|
}
|
2026-07-21 12:09:00 +08:00
|
|
|
|
const chartData = ref<WaveformData>(fullChartData)
|
|
|
|
|
|
let zoomRequestSequence = 0
|
|
|
|
|
|
|
|
|
|
|
|
function filterWaveformData(data: WaveformData, start: number, end: number): WaveformData {
|
|
|
|
|
|
const lower = Math.min(start, end)
|
|
|
|
|
|
const upper = Math.max(start, end)
|
|
|
|
|
|
if (data.kind === 'samples') return data
|
|
|
|
|
|
if (data.kind === 'points') {
|
|
|
|
|
|
return {
|
|
|
|
|
|
kind: 'points',
|
|
|
|
|
|
points: data.points.filter((point) => point.x >= lower && point.x <= upper),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
kind: 'series',
|
|
|
|
|
|
series: data.series.map((series) => ({
|
|
|
|
|
|
...series,
|
|
|
|
|
|
data:
|
|
|
|
|
|
series.data.kind === 'points'
|
|
|
|
|
|
? {
|
|
|
|
|
|
kind: 'points',
|
|
|
|
|
|
points: series.data.points.filter((point) => point.x >= lower && point.x <= upper),
|
|
|
|
|
|
}
|
|
|
|
|
|
: series.data,
|
|
|
|
|
|
})),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleZoomEnd(payload: WaveformZoomEndPayload) {
|
|
|
|
|
|
// Demo-only sequence number cancellation. Production code should use AbortController
|
|
|
|
|
|
// to cancel in-flight requests when a newer zoom gesture arrives.
|
|
|
|
|
|
const requestSequence = ++zoomRequestSequence
|
|
|
|
|
|
await new Promise((resolve) => window.setTimeout(resolve, 80))
|
|
|
|
|
|
if (requestSequence !== zoomRequestSequence) return
|
|
|
|
|
|
|
|
|
|
|
|
// Demo-only stand-in for the backend response. Production code should replace this
|
|
|
|
|
|
// with a request using payload.start/payload.end and the optional channel metadata.
|
|
|
|
|
|
chartData.value = filterWaveformData(fullChartData, payload.start, payload.end)
|
|
|
|
|
|
}
|
2026-07-20 14:07:43 +08:00
|
|
|
|
const titleOptions = computed<WaveformTitleOptions>(() => ({
|
|
|
|
|
|
visible: titleVisible.value,
|
|
|
|
|
|
text: titleText.value,
|
|
|
|
|
|
align: titleAlign.value,
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: titleColor.value,
|
|
|
|
|
|
fontSize: titleFontSize.value,
|
|
|
|
|
|
fontFamily: titleFontFamily.value,
|
|
|
|
|
|
rotation: titleRotation.value,
|
|
|
|
|
|
fontWeight: titleBold.value ? 700 : 400,
|
|
|
|
|
|
fontStyle: titleItalic.value ? 'italic' : 'normal',
|
|
|
|
|
|
textDecoration: titleUnderline.value ? 'underline' : 'none',
|
|
|
|
|
|
},
|
|
|
|
|
|
}))
|
|
|
|
|
|
function closeControls() {
|
|
|
|
|
|
controlsOpen.value = false
|
|
|
|
|
|
}
|
2026-07-20 10:21:30 +08:00
|
|
|
|
|
2026-07-20 14:07:43 +08:00
|
|
|
|
function resetTitleTextStyle() {
|
|
|
|
|
|
titleBold.value = false
|
|
|
|
|
|
titleItalic.value = false
|
|
|
|
|
|
titleUnderline.value = false
|
|
|
|
|
|
}
|
2026-07-20 10:21:30 +08:00
|
|
|
|
|
2026-07-20 14:07:43 +08:00
|
|
|
|
function handleWindowKeydown(event: KeyboardEvent) {
|
|
|
|
|
|
if (event.key === 'Escape') closeControls()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => window.addEventListener('keydown', handleWindowKeydown))
|
|
|
|
|
|
onBeforeUnmount(() => window.removeEventListener('keydown', handleWindowKeydown))
|
2026-07-20 10:21:30 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<main class="workspace">
|
2026-07-20 14:07:43 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
class="mobile-control-toggle"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
:aria-expanded="controlsOpen"
|
|
|
|
|
|
aria-controls="waveform-control-panel"
|
|
|
|
|
|
@click="controlsOpen = true"
|
|
|
|
|
|
>
|
|
|
|
|
|
控制面板
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="controlsOpen"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="control-backdrop"
|
|
|
|
|
|
aria-label="关闭控制面板"
|
|
|
|
|
|
@click="closeControls"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<aside
|
|
|
|
|
|
id="waveform-control-panel"
|
|
|
|
|
|
class="control-panel"
|
|
|
|
|
|
:class="{ 'is-open': controlsOpen }"
|
|
|
|
|
|
aria-label="波形图控制"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="control-panel__scroll">
|
|
|
|
|
|
<Button class="control-panel__close" type="text" size="small" @click="closeControls">
|
|
|
|
|
|
关闭
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="control-section">
|
|
|
|
|
|
<h2>显示方式</h2>
|
|
|
|
|
|
<Radio.Group
|
|
|
|
|
|
v-model:value="displayMode"
|
|
|
|
|
|
class="display-mode-control"
|
|
|
|
|
|
button-style="solid"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="波形展示方式"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Radio.Button value="independent">单独坐标</Radio.Button>
|
|
|
|
|
|
<Radio.Button value="separated">多道分离</Radio.Button>
|
|
|
|
|
|
<Radio.Button value="compact">多道紧凑</Radio.Button>
|
|
|
|
|
|
</Radio.Group>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-07-20 15:35:19 +08:00
|
|
|
|
<section class="control-section">
|
|
|
|
|
|
<h2>叠加方式</h2>
|
|
|
|
|
|
<Radio.Group
|
|
|
|
|
|
v-model:value="overlayMode"
|
|
|
|
|
|
class="display-mode-control"
|
|
|
|
|
|
button-style="solid"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="波形叠加方式"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Radio.Button value="single-axis">单值轴</Radio.Button>
|
|
|
|
|
|
<Radio.Button value="multi-axis">多值轴</Radio.Button>
|
|
|
|
|
|
</Radio.Group>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-07-20 14:07:43 +08:00
|
|
|
|
<section class="control-section">
|
|
|
|
|
|
<h2>图框布局</h2>
|
|
|
|
|
|
<div class="grid-size-control" aria-label="波形网格尺寸">
|
|
|
|
|
|
<InputNumber v-model:value="rowCount" :min="1" :max="10" size="small" />
|
|
|
|
|
|
<span>行</span>
|
|
|
|
|
|
<span class="control-separator">×</span>
|
|
|
|
|
|
<InputNumber v-model:value="columnCount" :min="1" :max="10" size="small" />
|
|
|
|
|
|
<span>列</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="control-section">
|
|
|
|
|
|
<h2>图框样式</h2>
|
|
|
|
|
|
<div class="frame-style-controls">
|
|
|
|
|
|
<label class="frame-style-control">
|
|
|
|
|
|
<span>边框颜色</span>
|
|
|
|
|
|
<ColorPicker
|
|
|
|
|
|
v-model:pure-color="frameBorderColor"
|
|
|
|
|
|
aria-label="图框边框颜色"
|
|
|
|
|
|
use-type="pure"
|
|
|
|
|
|
picker-type="chrome"
|
|
|
|
|
|
format="rgb"
|
|
|
|
|
|
:disable-alpha="false"
|
|
|
|
|
|
:blur-close="true"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="frame-style-control">
|
|
|
|
|
|
<span>背景颜色</span>
|
|
|
|
|
|
<ColorPicker
|
|
|
|
|
|
v-model:pure-color="frameBackgroundColor"
|
|
|
|
|
|
aria-label="图框背景颜色"
|
|
|
|
|
|
use-type="pure"
|
|
|
|
|
|
picker-type="chrome"
|
|
|
|
|
|
format="rgb"
|
|
|
|
|
|
:disable-alpha="false"
|
|
|
|
|
|
:blur-close="true"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="frame-style-control">
|
|
|
|
|
|
<span>线宽</span>
|
|
|
|
|
|
<InputNumber
|
|
|
|
|
|
v-model:value="frameBorderWidth"
|
|
|
|
|
|
:min="0"
|
|
|
|
|
|
:max="10"
|
|
|
|
|
|
:step="0.5"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="图框线宽"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="frame-style-control">
|
|
|
|
|
|
<span>线型</span>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
v-model:value="frameBorderStyle"
|
|
|
|
|
|
:options="frameBorderStyleOptions"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="图框线型"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="frame-style-control frame-style-control--switch">
|
|
|
|
|
|
<span>水印</span>
|
|
|
|
|
|
<Switch
|
|
|
|
|
|
v-model:checked="frameWatermarkVisible"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="显示图框水印"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="control-section title-control-section">
|
|
|
|
|
|
<div class="control-section__header">
|
|
|
|
|
|
<h2>标题</h2>
|
|
|
|
|
|
<Switch v-model:checked="titleVisible" size="small" aria-label="显示标题" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="title-controls">
|
|
|
|
|
|
<label class="title-control title-control--wide">
|
|
|
|
|
|
<span>标题名称</span>
|
|
|
|
|
|
<Input v-model:value="titleText" size="small" aria-label="标题名称" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="title-control title-control--wide">
|
|
|
|
|
|
<span>对齐方式</span>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
v-model:value="titleAlign"
|
|
|
|
|
|
:options="titleAlignOptions"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="标题对齐方式"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="title-control">
|
|
|
|
|
|
<span>字体</span>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
v-model:value="titleFontFamily"
|
|
|
|
|
|
:options="titleFontFamilyOptions"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="标题字体"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="title-control">
|
|
|
|
|
|
<span>字号</span>
|
|
|
|
|
|
<InputNumber
|
|
|
|
|
|
v-model:value="titleFontSize"
|
|
|
|
|
|
:min="8"
|
|
|
|
|
|
:max="72"
|
|
|
|
|
|
:step="1"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="标题字号"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div class="title-control">
|
|
|
|
|
|
<span>字体样式</span>
|
|
|
|
|
|
<div class="title-style-controls" role="group" aria-label="标题字体样式">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label="恢复标题常规样式"
|
|
|
|
|
|
title="恢复常规样式"
|
|
|
|
|
|
@click="resetTitleTextStyle"
|
|
|
|
|
|
>
|
|
|
|
|
|
A
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ 'is-active': titleBold }"
|
|
|
|
|
|
:aria-pressed="titleBold"
|
|
|
|
|
|
aria-label="标题粗体"
|
|
|
|
|
|
title="粗体"
|
|
|
|
|
|
@click="titleBold = !titleBold"
|
|
|
|
|
|
>
|
|
|
|
|
|
<b>B</b>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ 'is-active': titleItalic }"
|
|
|
|
|
|
:aria-pressed="titleItalic"
|
|
|
|
|
|
aria-label="标题斜体"
|
|
|
|
|
|
title="斜体"
|
|
|
|
|
|
@click="titleItalic = !titleItalic"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i>I</i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ 'is-active': titleUnderline }"
|
|
|
|
|
|
:aria-pressed="titleUnderline"
|
|
|
|
|
|
aria-label="标题下划线"
|
|
|
|
|
|
title="下划线"
|
|
|
|
|
|
@click="titleUnderline = !titleUnderline"
|
|
|
|
|
|
>
|
|
|
|
|
|
<u>U</u>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<label class="title-control">
|
|
|
|
|
|
<span>旋转</span>
|
|
|
|
|
|
<InputNumber
|
|
|
|
|
|
v-model:value="titleRotation"
|
|
|
|
|
|
:min="-180"
|
|
|
|
|
|
:max="180"
|
|
|
|
|
|
:step="1"
|
|
|
|
|
|
addon-after="°"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="标题旋转角度"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div class="title-control title-control--color">
|
|
|
|
|
|
<span>颜色</span>
|
|
|
|
|
|
<ColorPicker
|
|
|
|
|
|
v-model:pure-color="titleColor"
|
|
|
|
|
|
aria-label="标题颜色"
|
|
|
|
|
|
use-type="pure"
|
|
|
|
|
|
picker-type="chrome"
|
|
|
|
|
|
format="hex"
|
|
|
|
|
|
:disable-alpha="true"
|
|
|
|
|
|
:blur-close="true"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="control-section">
|
|
|
|
|
|
<h2>图例</h2>
|
|
|
|
|
|
<label class="select-control">
|
|
|
|
|
|
<span>位置</span>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
v-model:value="legendPosition"
|
|
|
|
|
|
:options="legendPositionOptions"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="图例位置"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="select-control">
|
|
|
|
|
|
<span>排列</span>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
v-model:value="legendOrientation"
|
|
|
|
|
|
:options="legendOrientationOptions"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
aria-label="图例排列"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="legend-color-control">
|
|
|
|
|
|
<span>背景</span>
|
|
|
|
|
|
<ColorPicker
|
|
|
|
|
|
v-model:pure-color="legendBackgroundColor"
|
|
|
|
|
|
aria-label="图例背景颜色"
|
|
|
|
|
|
use-type="pure"
|
|
|
|
|
|
picker-type="chrome"
|
|
|
|
|
|
format="rgb"
|
|
|
|
|
|
:disable-alpha="false"
|
|
|
|
|
|
:blur-close="true"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</section>
|
2026-07-20 10:21:30 +08:00
|
|
|
|
</div>
|
2026-07-20 14:07:43 +08:00
|
|
|
|
</aside>
|
2026-07-20 10:21:30 +08:00
|
|
|
|
|
|
|
|
|
|
<section class="chart-panel">
|
|
|
|
|
|
<WaveformChart
|
|
|
|
|
|
:data="chartData"
|
|
|
|
|
|
:display-mode="displayMode"
|
2026-07-20 15:35:19 +08:00
|
|
|
|
:overlay-mode="overlayMode"
|
2026-07-20 10:21:30 +08:00
|
|
|
|
:grid="{ rowCount, columnCount, showPagination: true }"
|
2026-07-20 14:07:43 +08:00
|
|
|
|
:title="titleOptions"
|
|
|
|
|
|
:legend="{
|
|
|
|
|
|
position: legendPosition,
|
|
|
|
|
|
orientation: legendOrientation,
|
|
|
|
|
|
backgroundColor: legendBackgroundColor,
|
2026-07-20 17:57:52 +08:00
|
|
|
|
interactive: true,
|
2026-07-20 14:07:43 +08:00
|
|
|
|
}"
|
|
|
|
|
|
:frame-style="frameStyle"
|
|
|
|
|
|
:frame-number="frameWatermarkVisible ? 1 : undefined"
|
2026-07-20 10:21:30 +08:00
|
|
|
|
v-model:annotations="annotations"
|
|
|
|
|
|
v-model:annotations-visible="annotationsVisible"
|
|
|
|
|
|
v-model:interaction-mode="interactionMode"
|
2026-07-20 17:57:52 +08:00
|
|
|
|
v-model:hidden-series-ids="hiddenSeriesIds"
|
2026-07-21 12:09:00 +08:00
|
|
|
|
@zoom-end="handleZoomEnd"
|
2026-07-20 10:21:30 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
</template>
|