feat: add transcode workspace and media browser

This commit is contained in:
2026-05-05 02:14:52 +08:00
parent f0f9ed3456
commit 56432a9ece
26 changed files with 2013 additions and 267 deletions
+74 -24
View File
@@ -1,22 +1,31 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { useViewport } from "@/composables/useViewport";
import { ElMessage } from "element-plus";
import axios from "axios";
import apiClient, { getApiErrorMessage } from "@/api/client";
import type { DailyReviewReport } from "@/types";
import { useViewport } from "@/composables/useViewport";
import type {
DailyReviewPushResult,
DailyReviewReport,
PushDailyReviewRequest
} from "@/types";
const router = useRouter();
const { isMobile } = useViewport();
const loading = ref(false);
const pushing = ref(false);
const pushDialogVisible = ref(false);
const loadError = ref("");
const selectedDate = ref(defaultReviewDate());
const report = ref<DailyReviewReport | null>(null);
const roomTableHeight = computed(() => (isMobile.value ? undefined : 420));
const momentTableHeight = computed(() => (isMobile.value ? undefined : 360));
const pushChannels = reactive({
webhook: true,
email: false
});
async function loadReport() {
loading.value = true;
@@ -40,16 +49,39 @@ async function loadReport() {
}
}
function openPushDialog() {
pushDialogVisible.value = true;
}
async function pushReport() {
const channels = Object.entries(pushChannels)
.filter(([, enabled]) => enabled)
.map(([channel]) => channel);
if (channels.length === 0) {
ElMessage.warning("请至少选择一种推送方式。");
return;
}
pushing.value = true;
try {
const { data } = await apiClient.post("/reports/daily/push", null, {
params: {
date: selectedDate.value,
utcOffsetMinutes: getLocalUtcOffsetMinutes()
}
});
ElMessage.success(data.message);
const payload: PushDailyReviewRequest = {
date: selectedDate.value,
utcOffsetMinutes: getLocalUtcOffsetMinutes(),
channels
};
const { data } = await apiClient.post<DailyReviewPushResult>("/reports/daily/push", payload);
const successCount = data.results.filter((item) => item.success).length;
const summary = data.results.map((item) => `${item.channel}: ${item.message}`).join(" | ");
if (successCount === data.results.length) {
ElMessage.success(summary);
} else if (successCount > 0) {
ElMessage.warning(summary);
} else {
ElMessage.error(summary);
}
pushDialogVisible.value = false;
} catch (error) {
ElMessage.error(getApiErrorMessage(error, "日报推送失败。"));
} finally {
@@ -107,7 +139,7 @@ onMounted(loadReport);
<div>
<h1 class="page-title">回顾日报</h1>
<p class="page-subtitle">
按天聚合录制时长异常和弹幕热度支持手动推送到已配置的 Webhook
按天聚合录制时长异常和弹幕热度支持手动推送到已配置的 Webhook 或邮件
</p>
</div>
@@ -122,7 +154,7 @@ onMounted(loadReport);
@change="loadReport"
/>
<el-button @click="loadReport">刷新日报</el-button>
<el-button type="primary" :loading="pushing" :disabled="!report" @click="pushReport">推送日报</el-button>
<el-button type="primary" :disabled="!report" @click="openPushDialog">推送日报</el-button>
</el-space>
</div>
@@ -275,6 +307,24 @@ onMounted(loadReport);
</div>
</el-card>
</template>
<el-dialog
v-model="pushDialogVisible"
title="推送日报"
width="420px"
destroy-on-close
>
<div class="push-dialog">
<p class="push-dialog__hint">选择要推送的通道第一版支持 Webhook 和邮件可同时勾选</p>
<el-checkbox v-model="pushChannels.webhook">Webhook</el-checkbox>
<el-checkbox v-model="pushChannels.email">邮件</el-checkbox>
</div>
<template #footer>
<el-button @click="pushDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="pushing" @click="pushReport">开始推送</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -292,16 +342,6 @@ onMounted(loadReport);
border-radius: 14px;
}
.section-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
padding-bottom: 18px;
border-bottom: 1px solid var(--border-subtle);
}
.highlight-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -373,6 +413,17 @@ onMounted(loadReport);
flex-wrap: nowrap;
}
.push-dialog {
display: grid;
gap: 14px;
}
.push-dialog__hint {
margin: 0;
color: var(--text-secondary);
line-height: 1.7;
}
@media (max-width: 1100px) {
.highlight-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -405,4 +456,3 @@ onMounted(loadReport);
}
}
</style>