diff --git a/yunkes/src/components/VoiceClipModal.vue b/yunkes/src/components/VoiceClipModal.vue
index e2e9182..9b337f6 100644
--- a/yunkes/src/components/VoiceClipModal.vue
+++ b/yunkes/src/components/VoiceClipModal.vue
@@ -109,6 +109,42 @@ function durationDisplay(row: VoiceClip): string {
return row.duration_format || formatCallDuration(row.mp3_duration)
}
+// ----- 录音异常检查(本地元数据判定,无网络/无后端) -----
+interface Anomaly {
+ type: string
+ label: string
+}
+
+/**
+ * 基于本地元数据判定单条 voice_clip 的异常:
+ * - 无录音文件(oss_file_name 缺失)
+ * - 时长为 0(mp3_duration 缺失或为 0)
+ * - 时间缺失(start_time / end_time 缺失)
+ * 纯前端计算,仅作用于当前已加载分页(≤50 条),不涉及服务端查询。
+ */
+function clipAnomalies(clip: VoiceClip): Anomaly[] {
+ const anomalies: Anomaly[] = []
+ if (!clip.oss_file_name) anomalies.push({ type: 'no_file', label: '无录音文件' })
+ if (!clip.mp3_duration || clip.mp3_duration === 0) {
+ anomalies.push({ type: 'zero_duration', label: '时长为0' })
+ }
+ if (!clip.start_time || !clip.end_time) {
+ anomalies.push({ type: 'no_time', label: '时间缺失' })
+ }
+ return anomalies
+}
+
+// 「仅看异常」开关:开启时仅展示当前分页内命中异常的片段。
+const onlyAnomaly = ref(false)
+
+// 当前分页命中异常的片段数量(用于"N 条异常"汇总,不受开关影响)。
+const anomalyCount = computed(() => items.value.filter((c) => clipAnomalies(c).length > 0).length)
+
+// 表格实际渲染的数据:开关 ON 时过滤为仅异常项,OFF 时原样展示。
+const displayItems = computed(() =>
+ onlyAnomaly.value ? items.value.filter((c) => clipAnomalies(c).length > 0) : items.value,
+)
+
function playClip(row: VoiceClip): void {
const url = row.oss_file_name
if (!url) {
@@ -296,6 +332,17 @@ watch(
@change="onFilterChange"
/>
重置
+
+
+ 仅看异常
+
+ {{ anomalyCount }} 条异常
+
{{ durationDisplay(row) }}
+
+
+
+
+ {{ a.label }}
+
+
+ ✓
+
+
@@ -475,6 +539,14 @@ watch(
color: #606266;
}
+.yk-voice-clip__filter-spacer {
+ flex: 1 1 auto;
+}
+
+.yk-voice-clip__anomaly-count {
+ margin-left: 4px;
+}
+
.yk-voice-clip__player {
display: flex;
flex-direction: column;