mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2026-03-21 09:13:10 +00:00
feat: 优化噪音监测设置,添加晚自习时间段配置和校准功能
This commit is contained in:
parent
21a63661cd
commit
ba6aab2ba2
@ -72,15 +72,6 @@
|
|||||||
</v-icon>
|
</v-icon>
|
||||||
统计报告
|
统计报告
|
||||||
</v-tab>
|
</v-tab>
|
||||||
<v-tab value="settings">
|
|
||||||
<v-icon
|
|
||||||
start
|
|
||||||
size="18"
|
|
||||||
>
|
|
||||||
mdi-cog
|
|
||||||
</v-icon>
|
|
||||||
自习配置
|
|
||||||
</v-tab>
|
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
|
|
||||||
<v-divider />
|
<v-divider />
|
||||||
@ -370,14 +361,12 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn
|
<v-btn
|
||||||
color="deep-purple"
|
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
|
color="deep-purple"
|
||||||
prepend-icon="mdi-crosshairs-gps"
|
prepend-icon="mdi-crosshairs-gps"
|
||||||
:disabled="!isMonitoring"
|
@click="openCalibrateDialog"
|
||||||
:loading="isCalibrating"
|
|
||||||
@click="doCalibrate"
|
|
||||||
>
|
>
|
||||||
校准({{ calibrateTarget }}dB)
|
校准
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-tabs-window-item>
|
</v-tabs-window-item>
|
||||||
@ -772,177 +761,195 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</v-tabs-window-item>
|
</v-tabs-window-item>
|
||||||
|
|
||||||
<!-- ==================== 自习配置 ==================== -->
|
|
||||||
<v-tabs-window-item value="settings">
|
|
||||||
<div class="pa-5">
|
|
||||||
<div class="d-flex align-center mb-4">
|
|
||||||
<v-icon
|
|
||||||
class="mr-2"
|
|
||||||
color="teal"
|
|
||||||
>
|
|
||||||
mdi-clock-edit-outline
|
|
||||||
</v-icon>
|
|
||||||
<span class="text-subtitle-1 font-weight-bold">晚自习时间段</span>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn
|
|
||||||
color="primary"
|
|
||||||
variant="tonal"
|
|
||||||
size="small"
|
|
||||||
prepend-icon="mdi-plus"
|
|
||||||
@click="addSession"
|
|
||||||
>
|
|
||||||
添加时段
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-caption text-medium-emphasis mb-4">
|
|
||||||
配置晚自习时间段后,系统会在对应时段内自动开启噪音监测并记录统计报告。时间段外不会长期记录。
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-for="(session, idx) in editSessions"
|
|
||||||
:key="idx"
|
|
||||||
class="mb-3"
|
|
||||||
>
|
|
||||||
<v-card
|
|
||||||
variant="outlined"
|
|
||||||
rounded="xl"
|
|
||||||
>
|
|
||||||
<v-card-text class="pa-4">
|
|
||||||
<div class="d-flex align-center ga-3 flex-wrap">
|
|
||||||
<v-text-field
|
|
||||||
v-model="session.name"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
label="名称"
|
|
||||||
hide-details
|
|
||||||
style="max-width: 160px;"
|
|
||||||
/>
|
|
||||||
<v-menu
|
|
||||||
v-model="timePickerMenus[idx]"
|
|
||||||
:close-on-content-click="false"
|
|
||||||
location="bottom"
|
|
||||||
>
|
|
||||||
<template #activator="{ props: menuProps }">
|
|
||||||
<v-text-field
|
|
||||||
v-bind="menuProps"
|
|
||||||
:model-value="session.start"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
label="开始时间"
|
|
||||||
readonly
|
|
||||||
hide-details
|
|
||||||
prepend-inner-icon="mdi-clock-outline"
|
|
||||||
style="max-width: 170px;"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<v-time-picker
|
|
||||||
v-model="session.start"
|
|
||||||
color="primary"
|
|
||||||
format="24hr"
|
|
||||||
scrollable
|
|
||||||
@update:model-value="timePickerMenus[idx] = false"
|
|
||||||
/>
|
|
||||||
</v-menu>
|
|
||||||
<v-text-field
|
|
||||||
v-model.number="session.duration"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
type="number"
|
|
||||||
label="时长"
|
|
||||||
suffix="分钟"
|
|
||||||
hide-details
|
|
||||||
style="max-width: 130px;"
|
|
||||||
:min="10"
|
|
||||||
:max="300"
|
|
||||||
/>
|
|
||||||
<span class="text-caption text-medium-emphasis">
|
|
||||||
至 {{ sessionEndTime(session) }}
|
|
||||||
</span>
|
|
||||||
<v-switch
|
|
||||||
v-model="session.enabled"
|
|
||||||
density="compact"
|
|
||||||
color="primary"
|
|
||||||
hide-details
|
|
||||||
label="启用"
|
|
||||||
/>
|
|
||||||
<v-btn
|
|
||||||
icon="mdi-delete"
|
|
||||||
color="error"
|
|
||||||
size="x-small"
|
|
||||||
variant="text"
|
|
||||||
@click="editSessions.splice(idx, 1)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-divider class="my-5" />
|
|
||||||
|
|
||||||
<!-- 报警阈值 -->
|
|
||||||
<div class="d-flex align-center mb-4">
|
|
||||||
<v-icon
|
|
||||||
class="mr-2"
|
|
||||||
color="orange"
|
|
||||||
>
|
|
||||||
mdi-alert-decagram
|
|
||||||
</v-icon>
|
|
||||||
<span class="text-subtitle-1 font-weight-bold">监测参数</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex align-center flex-wrap ga-4 mb-4">
|
|
||||||
<v-text-field
|
|
||||||
v-model.number="editAlertThreshold"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
type="number"
|
|
||||||
label="噪音报警阈值"
|
|
||||||
suffix="dB"
|
|
||||||
hide-details
|
|
||||||
style="max-width: 200px;"
|
|
||||||
:min="30"
|
|
||||||
:max="90"
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
v-model.number="calibrateTarget"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
type="number"
|
|
||||||
label="校准基准"
|
|
||||||
suffix="dB"
|
|
||||||
hide-details
|
|
||||||
style="max-width: 200px;"
|
|
||||||
:min="20"
|
|
||||||
:max="80"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-divider class="my-5" />
|
|
||||||
|
|
||||||
<div class="d-flex justify-end ga-3">
|
|
||||||
<v-btn
|
|
||||||
variant="text"
|
|
||||||
@click="resetEditConfig"
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
color="primary"
|
|
||||||
variant="elevated"
|
|
||||||
prepend-icon="mdi-content-save"
|
|
||||||
@click="saveConfig"
|
|
||||||
>
|
|
||||||
保存配置
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-tabs-window-item>
|
|
||||||
</v-tabs-window>
|
</v-tabs-window>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
|
<!-- 校准对话框 -->
|
||||||
|
<v-dialog
|
||||||
|
v-model="showCalibrateDialog"
|
||||||
|
max-width="560"
|
||||||
|
scrollable
|
||||||
|
>
|
||||||
|
<v-card class="rounded-xl">
|
||||||
|
<v-card-title class="d-flex align-center pa-4">
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
color="deep-purple"
|
||||||
|
>
|
||||||
|
mdi-crosshairs-gps
|
||||||
|
</v-icon>
|
||||||
|
<span class="text-h6 font-weight-bold">分贝校准</span>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-close"
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
@click="showCalibrateDialog = false"
|
||||||
|
/>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-divider />
|
||||||
|
|
||||||
|
<v-card-text class="pa-5">
|
||||||
|
<!-- 当前校准状态 -->
|
||||||
|
<v-card
|
||||||
|
variant="outlined"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<v-card-text class="py-3">
|
||||||
|
<div class="text-caption text-medium-emphasis mb-1">
|
||||||
|
当前校准值
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center ga-6 flex-wrap">
|
||||||
|
<div>
|
||||||
|
<span class="text-body-2 text-medium-emphasis">基准分贝:</span>
|
||||||
|
<span class="text-body-1 font-weight-bold">
|
||||||
|
{{ calibrationSettings.baselineDb }} dB
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-body-2 text-medium-emphasis">基准 RMS:</span>
|
||||||
|
<span class="text-body-1 font-weight-bold font-monospace">
|
||||||
|
{{ calibrationSettings.baselineRms != null ? calibrationSettings.baselineRms.toFixed(6) : '未校准' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-body-2 text-medium-emphasis">最大分贝:</span>
|
||||||
|
<span class="text-body-1 font-weight-bold">
|
||||||
|
{{ calibrationSettings.maxLevelDb }} dB
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<!-- 自动校准 -->
|
||||||
|
<div class="d-flex align-center mb-2">
|
||||||
|
<v-icon
|
||||||
|
size="18"
|
||||||
|
class="mr-2"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
mdi-auto-fix
|
||||||
|
</v-icon>
|
||||||
|
<span class="text-subtitle-2 font-weight-medium">自动校准</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-caption text-medium-emphasis mb-3">
|
||||||
|
在已知环境分贝的场景下,输入当前环境的真实分贝值,点击开始后保持环境安静 3 秒。
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center ga-3 mb-5 flex-wrap">
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="calibrateTargetDb"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
label="目标分贝"
|
||||||
|
suffix="dB"
|
||||||
|
hide-details
|
||||||
|
style="max-width: 160px;"
|
||||||
|
:min="20"
|
||||||
|
:max="80"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
color="deep-purple"
|
||||||
|
variant="tonal"
|
||||||
|
prepend-icon="mdi-crosshairs-gps"
|
||||||
|
:loading="isCalibrating"
|
||||||
|
:disabled="!isMonitoring"
|
||||||
|
@click="doAutoCalibrate"
|
||||||
|
>
|
||||||
|
开始校准
|
||||||
|
</v-btn>
|
||||||
|
<span
|
||||||
|
v-if="!isMonitoring"
|
||||||
|
class="text-caption text-warning"
|
||||||
|
>
|
||||||
|
需先开启监测
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="calibrateMessage"
|
||||||
|
class="text-caption"
|
||||||
|
:class="calibrateSuccess ? 'text-success' : 'text-error'"
|
||||||
|
>
|
||||||
|
{{ calibrateMessage }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-divider class="mb-5" />
|
||||||
|
|
||||||
|
<!-- 手动校准 -->
|
||||||
|
<div class="d-flex align-center mb-2">
|
||||||
|
<v-icon
|
||||||
|
size="18"
|
||||||
|
class="mr-2"
|
||||||
|
color="orange"
|
||||||
|
>
|
||||||
|
mdi-pencil-ruler
|
||||||
|
</v-icon>
|
||||||
|
<span class="text-subtitle-2 font-weight-medium">手动校准 / 参数调整</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-caption text-medium-emphasis mb-3">
|
||||||
|
直接输入校准参数。修改后点击保存生效。
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center ga-3 mb-4 flex-wrap">
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="editBaselineDb"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
label="基准分贝"
|
||||||
|
suffix="dB"
|
||||||
|
hide-details
|
||||||
|
style="max-width: 160px;"
|
||||||
|
:min="20"
|
||||||
|
:max="80"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="editBaselineRms"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
label="基准 RMS"
|
||||||
|
hide-details
|
||||||
|
style="max-width: 200px;"
|
||||||
|
placeholder="如 0.003200"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="editMaxLevelDb"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
label="最大显示分贝"
|
||||||
|
suffix="dB"
|
||||||
|
hide-details
|
||||||
|
style="max-width: 180px;"
|
||||||
|
:min="40"
|
||||||
|
:max="120"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions class="px-4 pb-4">
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
prepend-icon="mdi-restore"
|
||||||
|
@click="resetCalibration"
|
||||||
|
>
|
||||||
|
恢复默认
|
||||||
|
</v-btn>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="elevated"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
@click="saveManualCalibration"
|
||||||
|
>
|
||||||
|
保存校准
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
<!-- 清空报告确认 -->
|
<!-- 清空报告确认 -->
|
||||||
<v-dialog
|
<v-dialog
|
||||||
v-model="showConfirmClear"
|
v-model="showConfirmClear"
|
||||||
@ -977,6 +984,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {
|
||||||
|
noiseService,
|
||||||
|
getNoiseControlSettings,
|
||||||
|
saveNoiseControlSettings,
|
||||||
|
resetNoiseControlSettings,
|
||||||
|
} from '@wydev/noise-core';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NoiseMonitorDetail',
|
name: 'NoiseMonitorDetail',
|
||||||
props: {
|
props: {
|
||||||
@ -992,27 +1006,31 @@ export default {
|
|||||||
lastSlice: { type: Object, default: null },
|
lastSlice: { type: Object, default: null },
|
||||||
history: { type: Array, default: () => [] },
|
history: { type: Array, default: () => [] },
|
||||||
isMonitoring: { type: Boolean, default: false },
|
isMonitoring: { type: Boolean, default: false },
|
||||||
sessionConfig: { type: Object, default: null },
|
|
||||||
sessionActive: { type: Boolean, default: false },
|
sessionActive: { type: Boolean, default: false },
|
||||||
sessionData: { type: Object, default: null },
|
sessionData: { type: Object, default: null },
|
||||||
reportMeta: { type: Object, default: () => ({ dates: {} }) },
|
reportMeta: { type: Object, default: () => ({ dates: {} }) },
|
||||||
selectedDate: { type: String, default: '' },
|
selectedDate: { type: String, default: '' },
|
||||||
dateReports: { type: Array, default: () => [] },
|
dateReports: { type: Array, default: () => [] },
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'start', 'stop', 'calibrate', 'clear-history', 'save-config', 'select-date', 'clear-date-reports', 'clear-all-reports'],
|
emits: ['update:modelValue', 'start', 'stop', 'clear-history', 'select-date', 'clear-date-reports', 'clear-all-reports'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeTab: 'realtime',
|
activeTab: 'realtime',
|
||||||
calibrateTarget: 40,
|
|
||||||
isCalibrating: false,
|
|
||||||
confirmClearMode: '', // '' | 'date' | 'all'
|
confirmClearMode: '', // '' | 'date' | 'all'
|
||||||
waveformWidth: 600,
|
waveformWidth: 600,
|
||||||
reportChartWidth: 600,
|
reportChartWidth: 600,
|
||||||
selectedReportIndex: 0,
|
selectedReportIndex: 0,
|
||||||
// 编辑态配置
|
// 校准
|
||||||
editSessions: [],
|
showCalibrateDialog: false,
|
||||||
editAlertThreshold: 55,
|
calibrationSettings: {},
|
||||||
timePickerMenus: {},
|
calibrateTargetDb: 40,
|
||||||
|
isCalibrating: false,
|
||||||
|
calibrateMessage: '',
|
||||||
|
calibrateSuccess: false,
|
||||||
|
editBaselineDb: 40,
|
||||||
|
editBaselineRms: '',
|
||||||
|
editMaxLevelDb: 100,
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -1150,7 +1168,6 @@ export default {
|
|||||||
this.updateWaveformWidth()
|
this.updateWaveformWidth()
|
||||||
this.updateReportChartWidth()
|
this.updateReportChartWidth()
|
||||||
})
|
})
|
||||||
this.resetEditConfig()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
activeTab() {
|
activeTab() {
|
||||||
@ -1169,7 +1186,6 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.updateWaveformWidth()
|
this.updateWaveformWidth()
|
||||||
window.addEventListener('resize', this.handleResize)
|
window.addEventListener('resize', this.handleResize)
|
||||||
this.resetEditConfig()
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener('resize', this.handleResize)
|
window.removeEventListener('resize', this.handleResize)
|
||||||
@ -1195,11 +1211,6 @@ export default {
|
|||||||
reportDbToY(db) {
|
reportDbToY(db) {
|
||||||
return 140 - Math.max(0, Math.min(100, db)) / 100 * 140
|
return 140 - Math.max(0, Math.min(100, db)) / 100 * 140
|
||||||
},
|
},
|
||||||
doCalibrate() {
|
|
||||||
this.isCalibrating = true
|
|
||||||
this.$emit('calibrate', this.calibrateTarget)
|
|
||||||
setTimeout(() => { this.isCalibrating = false }, 3500)
|
|
||||||
},
|
|
||||||
doClearReports() {
|
doClearReports() {
|
||||||
if (this.confirmClearMode === 'all') {
|
if (this.confirmClearMode === 'all') {
|
||||||
this.$emit('clear-all-reports')
|
this.$emit('clear-all-reports')
|
||||||
@ -1209,34 +1220,6 @@ export default {
|
|||||||
this.confirmClearMode = ''
|
this.confirmClearMode = ''
|
||||||
this.selectedReportIndex = 0
|
this.selectedReportIndex = 0
|
||||||
},
|
},
|
||||||
resetEditConfig() {
|
|
||||||
if (this.sessionConfig) {
|
|
||||||
this.editSessions = JSON.parse(JSON.stringify(this.sessionConfig.sessions || []))
|
|
||||||
this.editAlertThreshold = this.sessionConfig.alertThresholdDb || 55
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addSession() {
|
|
||||||
this.editSessions.push({
|
|
||||||
name: `第${this.editSessions.length + 1}节晚自习`,
|
|
||||||
start: '19:00',
|
|
||||||
duration: 70,
|
|
||||||
enabled: true,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
sessionEndTime(session) {
|
|
||||||
if (!session?.start || !session?.duration) return '--:--'
|
|
||||||
const [h, m] = session.start.split(':').map(Number)
|
|
||||||
const totalMin = h * 60 + m + (session.duration || 0)
|
|
||||||
const eh = Math.floor(totalMin / 60) % 24
|
|
||||||
const em = totalMin % 60
|
|
||||||
return `${String(eh).padStart(2, '0')}:${String(em).padStart(2, '0')}`
|
|
||||||
},
|
|
||||||
saveConfig() {
|
|
||||||
this.$emit('save-config', {
|
|
||||||
sessions: this.editSessions,
|
|
||||||
alertThresholdDb: this.editAlertThreshold,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
formatDateLabel(dateStr) {
|
formatDateLabel(dateStr) {
|
||||||
if (!dateStr) return ''
|
if (!dateStr) return ''
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
@ -1285,6 +1268,48 @@ export default {
|
|||||||
if (score >= 40) return '较差'
|
if (score >= 40) return '较差'
|
||||||
return '极差'
|
return '极差'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ===== 校准 =====
|
||||||
|
openCalibrateDialog() {
|
||||||
|
this.refreshCalibrationSettings()
|
||||||
|
this.showCalibrateDialog = true
|
||||||
|
},
|
||||||
|
refreshCalibrationSettings() {
|
||||||
|
const s = getNoiseControlSettings()
|
||||||
|
this.calibrationSettings = s
|
||||||
|
this.editBaselineDb = s.baselineDb
|
||||||
|
this.editBaselineRms = s.baselineRms != null ? String(s.baselineRms) : ''
|
||||||
|
this.editMaxLevelDb = s.maxLevelDb
|
||||||
|
},
|
||||||
|
doAutoCalibrate() {
|
||||||
|
this.isCalibrating = true
|
||||||
|
this.calibrateMessage = ''
|
||||||
|
noiseService.calibrate(this.calibrateTargetDb, (success, msg) => {
|
||||||
|
this.isCalibrating = false
|
||||||
|
this.calibrateSuccess = success
|
||||||
|
this.calibrateMessage = msg
|
||||||
|
if (success) {
|
||||||
|
this.refreshCalibrationSettings()
|
||||||
|
}
|
||||||
|
setTimeout(() => { this.calibrateMessage = '' }, 5000)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
saveManualCalibration() {
|
||||||
|
const patch = {
|
||||||
|
baselineDb: this.editBaselineDb,
|
||||||
|
maxLevelDb: this.editMaxLevelDb,
|
||||||
|
}
|
||||||
|
const rmsVal = parseFloat(this.editBaselineRms)
|
||||||
|
if (!isNaN(rmsVal) && rmsVal > 0) {
|
||||||
|
patch.baselineRms = rmsVal
|
||||||
|
}
|
||||||
|
saveNoiseControlSettings(patch)
|
||||||
|
this.refreshCalibrationSettings()
|
||||||
|
},
|
||||||
|
resetCalibration() {
|
||||||
|
resetNoiseControlSettings()
|
||||||
|
this.refreshCalibrationSettings()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -80,7 +80,6 @@
|
|||||||
:last-slice="noiseLastSlice"
|
:last-slice="noiseLastSlice"
|
||||||
:history="noiseHistory"
|
:history="noiseHistory"
|
||||||
:is-monitoring="noiseMonitoring"
|
:is-monitoring="noiseMonitoring"
|
||||||
:session-config="noiseSessionConfig"
|
|
||||||
:session-active="noiseSessionActive"
|
:session-active="noiseSessionActive"
|
||||||
:session-data="noiseSessionData"
|
:session-data="noiseSessionData"
|
||||||
:report-meta="noiseReportMeta"
|
:report-meta="noiseReportMeta"
|
||||||
@ -88,9 +87,7 @@
|
|||||||
:date-reports="noiseCurrentDateReports"
|
:date-reports="noiseCurrentDateReports"
|
||||||
@start="startNoise"
|
@start="startNoise"
|
||||||
@stop="stopNoise"
|
@stop="stopNoise"
|
||||||
@calibrate="calibrateNoise"
|
|
||||||
@clear-history="clearNoiseHistory"
|
@clear-history="clearNoiseHistory"
|
||||||
@save-config="onSaveSessionConfig"
|
|
||||||
@select-date="onSelectReportDate"
|
@select-date="onSelectReportDate"
|
||||||
@clear-date-reports="onClearDateReports"
|
@clear-date-reports="onClearDateReports"
|
||||||
@clear-all-reports="onClearAllReports"
|
@clear-all-reports="onClearAllReports"
|
||||||
@ -1200,11 +1197,6 @@ export default {
|
|||||||
this.noiseScore = null
|
this.noiseScore = null
|
||||||
this.noiseScoreDetail = null
|
this.noiseScoreDetail = null
|
||||||
},
|
},
|
||||||
calibrateNoise(targetDb) {
|
|
||||||
noiseService.calibrate(targetDb, (success, msg) => {
|
|
||||||
console.log(success ? '校准成功' : `校准失败: ${msg}`)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clearNoiseHistory() {
|
clearNoiseHistory() {
|
||||||
noiseService.clearHistory()
|
noiseService.clearHistory()
|
||||||
this.noiseHistory = []
|
this.noiseHistory = []
|
||||||
@ -1470,10 +1462,6 @@ export default {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
onSaveSessionConfig(config) {
|
|
||||||
this.noiseSessionConfig = config
|
|
||||||
this.saveNoiseSessionConfig()
|
|
||||||
},
|
|
||||||
async onSelectReportDate(dateStr) {
|
async onSelectReportDate(dateStr) {
|
||||||
await this.loadReportsForDate(dateStr)
|
await this.loadReportsForDate(dateStr)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,15 +13,272 @@
|
|||||||
<v-divider class="my-2" />
|
<v-divider class="my-2" />
|
||||||
<setting-item :setting-key="'noiseMonitor.permissionDismissed'" />
|
<setting-item :setting-key="'noiseMonitor.permissionDismissed'" />
|
||||||
</v-list>
|
</v-list>
|
||||||
|
|
||||||
|
<v-divider class="mb-4" />
|
||||||
|
|
||||||
|
<!-- 晚自习时间段配置 -->
|
||||||
|
<div class="px-4 pb-4">
|
||||||
|
<div class="d-flex align-center mb-4">
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
color="teal"
|
||||||
|
>
|
||||||
|
mdi-clock-edit-outline
|
||||||
|
</v-icon>
|
||||||
|
<span class="text-subtitle-1 font-weight-bold">晚自习时间段</span>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="small"
|
||||||
|
prepend-icon="mdi-plus"
|
||||||
|
@click="addSession"
|
||||||
|
>
|
||||||
|
添加时段
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-caption text-medium-emphasis mb-4">
|
||||||
|
配置晚自习时间段后,系统会在对应时段内自动开启噪音监测并记录统计报告。时间段外不会长期记录。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-skeleton-loader
|
||||||
|
v-if="sessionLoading"
|
||||||
|
type="card"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="(session, idx) in editSessions"
|
||||||
|
:key="idx"
|
||||||
|
class="mb-3"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
variant="outlined"
|
||||||
|
rounded="xl"
|
||||||
|
>
|
||||||
|
<v-card-text class="pa-4">
|
||||||
|
<div class="d-flex align-center ga-3 flex-wrap">
|
||||||
|
<v-text-field
|
||||||
|
v-model="session.name"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
label="名称"
|
||||||
|
hide-details
|
||||||
|
style="max-width: 160px;"
|
||||||
|
/>
|
||||||
|
<v-menu
|
||||||
|
v-model="timePickerMenus[idx]"
|
||||||
|
:close-on-content-click="false"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
<template #activator="{ props: menuProps }">
|
||||||
|
<v-text-field
|
||||||
|
v-bind="menuProps"
|
||||||
|
:model-value="session.start"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
label="开始时间"
|
||||||
|
readonly
|
||||||
|
hide-details
|
||||||
|
prepend-inner-icon="mdi-clock-outline"
|
||||||
|
style="max-width: 170px;"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<v-time-picker
|
||||||
|
v-model="session.start"
|
||||||
|
color="primary"
|
||||||
|
format="24hr"
|
||||||
|
scrollable
|
||||||
|
@update:model-value="timePickerMenus[idx] = false"
|
||||||
|
/>
|
||||||
|
</v-menu>
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="session.duration"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
label="时长"
|
||||||
|
suffix="分钟"
|
||||||
|
hide-details
|
||||||
|
style="max-width: 130px;"
|
||||||
|
:min="10"
|
||||||
|
:max="300"
|
||||||
|
/>
|
||||||
|
<span class="text-caption text-medium-emphasis">
|
||||||
|
至 {{ sessionEndTime(session) }}
|
||||||
|
</span>
|
||||||
|
<v-switch
|
||||||
|
v-model="session.enabled"
|
||||||
|
density="compact"
|
||||||
|
color="primary"
|
||||||
|
hide-details
|
||||||
|
label="启用"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-delete"
|
||||||
|
color="error"
|
||||||
|
size="x-small"
|
||||||
|
variant="text"
|
||||||
|
@click="editSessions.splice(idx, 1)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="editSessions.length === 0"
|
||||||
|
class="text-center text-medium-emphasis py-4"
|
||||||
|
>
|
||||||
|
<v-icon class="mb-1">
|
||||||
|
mdi-clock-outline
|
||||||
|
</v-icon>
|
||||||
|
<div class="text-caption">
|
||||||
|
暂无时间段,点击上方「添加时段」创建
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-divider class="my-5" />
|
||||||
|
|
||||||
|
<!-- 监测参数 -->
|
||||||
|
<div class="d-flex align-center mb-4">
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
color="orange"
|
||||||
|
>
|
||||||
|
mdi-alert-decagram
|
||||||
|
</v-icon>
|
||||||
|
<span class="text-subtitle-1 font-weight-bold">监测参数</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex align-center flex-wrap ga-4 mb-4">
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="editAlertThreshold"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
label="噪音报警阈值"
|
||||||
|
suffix="dB"
|
||||||
|
hide-details
|
||||||
|
style="max-width: 200px;"
|
||||||
|
:min="30"
|
||||||
|
:max="90"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-end ga-3 mb-2">
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
prepend-icon="mdi-restore"
|
||||||
|
@click="resetSessionConfig"
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="elevated"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
:loading="sessionSaving"
|
||||||
|
@click="saveSessionConfig"
|
||||||
|
>
|
||||||
|
保存配置
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</settings-card>
|
</settings-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SettingsCard from '@/components/SettingsCard.vue';
|
import SettingsCard from '@/components/SettingsCard.vue';
|
||||||
import SettingItem from '@/components/settings/SettingItem.vue';
|
import SettingItem from '@/components/settings/SettingItem.vue';
|
||||||
|
import dataProvider from '@/utils/dataProvider';
|
||||||
|
|
||||||
|
const DEFAULT_SESSION_CONFIG = {
|
||||||
|
sessions: [
|
||||||
|
{ name: '第1节晚自习', start: '19:20', duration: 70, enabled: true },
|
||||||
|
{ name: '第2节晚自习', start: '20:20', duration: 110, enabled: true },
|
||||||
|
],
|
||||||
|
alertThresholdDb: 55,
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NoiseSettingsCard',
|
name: 'NoiseSettingsCard',
|
||||||
components: { SettingsCard, SettingItem },
|
components: { SettingsCard, SettingItem },
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 自习配置
|
||||||
|
sessionLoading: true,
|
||||||
|
sessionSaving: false,
|
||||||
|
editSessions: [],
|
||||||
|
editAlertThreshold: 55,
|
||||||
|
timePickerMenus: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.loadSessionConfig();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// ===== 自习配置 =====
|
||||||
|
async loadSessionConfig() {
|
||||||
|
this.sessionLoading = true;
|
||||||
|
try {
|
||||||
|
const res = await dataProvider.loadData('noise-session-config');
|
||||||
|
const data = res?.data || res;
|
||||||
|
if (data && data.sessions) {
|
||||||
|
this.editSessions = JSON.parse(JSON.stringify(data.sessions));
|
||||||
|
this.editAlertThreshold = data.alertThresholdDb || 55;
|
||||||
|
} else {
|
||||||
|
this.resetSessionConfig();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.resetSessionConfig();
|
||||||
|
} finally {
|
||||||
|
this.sessionLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveSessionConfig() {
|
||||||
|
this.sessionSaving = true;
|
||||||
|
try {
|
||||||
|
const config = {
|
||||||
|
sessions: this.editSessions,
|
||||||
|
alertThresholdDb: this.editAlertThreshold,
|
||||||
|
};
|
||||||
|
await dataProvider.saveData('noise-session-config', config);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('保存自习配置失败:', e);
|
||||||
|
} finally {
|
||||||
|
this.sessionSaving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resetSessionConfig() {
|
||||||
|
this.editSessions = JSON.parse(JSON.stringify(DEFAULT_SESSION_CONFIG.sessions));
|
||||||
|
this.editAlertThreshold = DEFAULT_SESSION_CONFIG.alertThresholdDb;
|
||||||
|
},
|
||||||
|
|
||||||
|
addSession() {
|
||||||
|
this.editSessions.push({
|
||||||
|
name: `第${this.editSessions.length + 1}节晚自习`,
|
||||||
|
start: '19:00',
|
||||||
|
duration: 70,
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sessionEndTime(session) {
|
||||||
|
if (!session?.start || !session?.duration) return '--:--';
|
||||||
|
const [h, m] = session.start.split(':').map(Number);
|
||||||
|
const totalMin = h * 60 + m + (session.duration || 0);
|
||||||
|
const eh = Math.floor(totalMin / 60) % 24;
|
||||||
|
const em = totalMin % 60;
|
||||||
|
return `${String(eh).padStart(2, '0')}:${String(em).padStart(2, '0')}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -172,10 +172,10 @@
|
|||||||
border
|
border
|
||||||
@saved="onSettingsSaved"
|
@saved="onSettingsSaved"
|
||||||
/>
|
/>
|
||||||
<noise-settings-card
|
</v-tabs-window-item>
|
||||||
border
|
|
||||||
class="mt-4"
|
<v-tabs-window-item value="noise">
|
||||||
/>
|
<noise-settings-card border />
|
||||||
</v-tabs-window-item>
|
</v-tabs-window-item>
|
||||||
|
|
||||||
<v-tabs-window-item value="notification">
|
<v-tabs-window-item value="notification">
|
||||||
@ -420,6 +420,11 @@ export default {
|
|||||||
icon: "mdi-eye",
|
icon: "mdi-eye",
|
||||||
value: "display",
|
value: "display",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "噪音监测",
|
||||||
|
icon: "mdi-microphone",
|
||||||
|
value: "noise",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "通知铃声",
|
title: "通知铃声",
|
||||||
icon: "mdi-bell-ring",
|
icon: "mdi-bell-ring",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user