| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- <template>
- <div class="detail-page">
- <div class="detail-topbar">
- <el-button text class="detail-topbar__back" @click="goBack">返回列表</el-button>
- </div>
- <section class="hero-card">
- <div class="hero-main">
- <div class="hero-kicker">Task Detail</div>
- <h1 class="hero-title">{{ task.taskName || "任务详情" }}</h1>
- </div>
- <div class="hero-actions">
- <el-button type="primary" @click="reload">刷新数据</el-button>
- </div>
- </section>
- <section class="stats-grid">
- <article class="stat-card stat-card--status">
- <div class="stat-label">任务状态</div>
- <div class="stat-status">
- <el-tag :type="statusTagType(task.status)" effect="dark" size="large">{{ formatStatus(task.status) }}</el-tag>
- </div>
- <div class="stat-desc">{{ statusDescription }}</div>
- </article>
- <article class="stat-card">
- <div class="stat-label">任务编号</div>
- <div class="stat-value">#{{ task.id || "-" }}</div>
- </article>
- <article class="stat-card">
- <div class="stat-label">脚本类型</div>
- <div class="stat-value">{{ formatScriptType(task.scriptType) }}</div>
- </article>
- <article class="stat-card">
- <div class="stat-label">视频数量</div>
- <div class="stat-value">{{ videoUrls.length || 0 }}</div>
- </article>
- <article class="stat-card">
- <div class="stat-label">创建人</div>
- <div class="stat-value">{{ task.creatorName || "-" }}</div>
- </article>
- <article class="stat-card">
- <div class="stat-label">CodeDesk任务ID</div>
- <div class="stat-value stat-value--small">{{ task.codedeskTaskId || "-" }}</div>
- </article>
- </section>
- <section class="section-card">
- <div class="section-header">
- <div>
- <div class="section-kicker">Summary</div>
- <h2 class="section-title">摘要</h2>
- </div>
- </div>
- <pre class="content-block content-block--summary">{{ task.summary || "-" }}</pre>
- </section>
- <section class="section-card">
- <div class="section-header">
- <div>
- <div class="section-kicker">Analysis</div>
- <h2 class="section-title">视频分析文案</h2>
- </div>
- </div>
- <pre class="content-block content-block--analysis">{{ task.analysisContent || "-" }}</pre>
- </section>
- <section class="section-card">
- <div class="section-header">
- <div>
- <div class="section-kicker">Brief</div>
- <h2 class="section-title">用户要求</h2>
- </div>
- </div>
- <pre class="content-block content-block--brief">{{ task.promptHint || "-" }}</pre>
- </section>
- <section class="section-card section-card--script">
- <div class="section-header">
- <div>
- <div class="section-kicker">Final Script</div>
- <h2 class="section-title">脚本内容</h2>
- </div>
- </div>
- <pre class="content-block content-block--script">{{ task.scriptContent || "-" }}</pre>
- </section>
- <section class="section-card" v-if="task.errorMessage">
- <div class="section-header">
- <div>
- <div class="section-kicker">Error</div>
- <h2 class="section-title">失败原因</h2>
- </div>
- </div>
- <pre class="content-block content-block--danger">{{ task.errorMessage }}</pre>
- </section>
- <section class="section-card">
- <div class="section-header">
- <div>
- <div class="section-kicker">Inputs</div>
- <h2 class="section-title">素材输入</h2>
- </div>
- </div>
- <el-tabs class="material-tabs">
- <el-tab-pane label="结构化内容">
- <pre class="content-block content-block--material">{{ task.videoContent || "-" }}</pre>
- </el-tab-pane>
- <el-tab-pane label="转写文本">
- <pre class="content-block content-block--material">{{ task.transcript || "-" }}</pre>
- </el-tab-pane>
- </el-tabs>
- </section>
- <section class="section-card">
- <div class="section-header">
- <div>
- <div class="section-kicker">Source URLs</div>
- <h2 class="section-title">视频链接</h2>
- </div>
- </div>
- <div class="link-list">
- <div class="link-item link-item--lead">
- <div class="link-label">首个链接</div>
- <div class="link-text">{{ task.videoUrl || "-" }}</div>
- </div>
- <div v-for="(item, index) in videoUrls" :key="index" class="link-item">
- <div class="link-label">视频 {{ index + 1 }}</div>
- <div class="link-text">{{ item }}</div>
- </div>
- <div v-if="!videoUrls.length" class="empty-tip">暂无视频链接</div>
- </div>
- </section>
- </div>
- </template>
- <script setup>
- import { computed, onMounted, reactive } from "vue";
- import { useRoute, useRouter } from "vue-router";
- import { ElMessage } from "element-plus";
- import { getTaskApi } from "../api/modules";
- const route = useRoute();
- const router = useRouter();
- const task = reactive({});
- const videoUrls = computed(() => {
- if (!task.videoUrls) {
- return task.videoUrl ? [task.videoUrl] : [];
- }
- return String(task.videoUrls)
- .split(/\r?\n|,/)
- .map((item) => item.trim())
- .filter(Boolean);
- });
- const statusDescription = computed(() => {
- if (task.status === "DONE") return "结果已生成,可直接审阅与修改。";
- if (task.status === "RUNNING") return "任务处理中,建议稍后刷新。";
- if (task.status === "FAILED") return "任务执行失败,建议优先排查错误原因。";
- return "等待进入处理队列。";
- });
- const loadData = async () => {
- const id = route.params.id;
- const payload = await getTaskApi(id);
- Object.assign(task, payload);
- };
- const reload = async () => {
- try {
- await loadData();
- } catch (error) {
- ElMessage.error(error.message || "加载任务详情失败");
- }
- };
- const goBack = () => {
- router.push("/tasks");
- };
- const statusTagType = (status) => {
- if (status === "DONE") return "success";
- if (status === "RUNNING") return "warning";
- if (status === "FAILED") return "danger";
- return "info";
- };
- const formatStatus = (value) => {
- const map = {
- PENDING: "待处理",
- RUNNING: "处理中",
- DONE: "已完成",
- FAILED: "失败",
- };
- return map[value] || value || "-";
- };
- const formatScriptType = (value) => {
- const map = {
- VOICEOVER: "口播稿",
- STORYBOARD: "分镜稿",
- SUMMARY: "摘要稿",
- };
- return map[value] || value || "-";
- };
- onMounted(reload);
- </script>
- <style scoped>
- .detail-page {
- display: grid;
- gap: 18px;
- }
- .detail-topbar {
- display: flex;
- align-items: center;
- }
- .detail-topbar__back {
- padding-left: 0;
- font-size: 14px;
- font-weight: 600;
- }
- .hero-card {
- display: flex;
- justify-content: space-between;
- gap: 20px;
- padding: 24px 28px;
- border-radius: 20px;
- background: linear-gradient(135deg, #17324d 0%, #234869 100%);
- color: #f8fafc;
- box-shadow: 0 18px 36px rgba(15, 23, 42, 0.12);
- }
- .hero-main {
- max-width: 760px;
- }
- .hero-kicker {
- font-size: 12px;
- font-weight: 800;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- color: rgba(255, 255, 255, 0.68);
- }
- .hero-title {
- margin: 10px 0;
- font-size: 36px;
- line-height: 1.08;
- font-weight: 800;
- }
- .hero-actions {
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 12px;
- min-width: 132px;
- }
- .stats-grid {
- display: grid;
- grid-template-columns: 1.8fr repeat(5, minmax(0, 1fr));
- gap: 14px;
- }
- .stat-card,
- .section-card {
- background: #fff;
- border: 1px solid rgba(226, 232, 240, 0.92);
- border-radius: 18px;
- box-shadow: 0 12px 26px rgba(15, 23, 42, 0.05);
- }
- .stat-card {
- padding: 18px;
- }
- .stat-card--status {
- background: linear-gradient(135deg, #fff9dd 0%, #fffef3 100%);
- }
- .stat-label {
- color: #64748b;
- font-size: 12px;
- font-weight: 700;
- letter-spacing: 0.04em;
- text-transform: uppercase;
- }
- .stat-status {
- margin-top: 14px;
- }
- .stat-desc {
- margin-top: 12px;
- color: #4b5563;
- line-height: 1.7;
- }
- .stat-value {
- margin-top: 14px;
- color: #0f172a;
- font-size: 24px;
- line-height: 1.15;
- font-weight: 800;
- word-break: break-word;
- }
- .stat-value--small {
- font-size: 15px;
- }
- .section-card {
- padding: 20px;
- }
- .section-card--script {
- background: linear-gradient(180deg, #ffffff 0%, #fbfcff 100%);
- }
- .section-header {
- margin-bottom: 14px;
- }
- .section-kicker {
- color: #64748b;
- font-size: 11px;
- font-weight: 800;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- }
- .section-title {
- margin: 8px 0 6px;
- color: #0f172a;
- font-size: 24px;
- line-height: 1.15;
- font-weight: 800;
- }
- .content-block {
- margin: 0;
- min-height: 140px;
- padding: 16px 18px;
- border-radius: 14px;
- background: #f8fafc;
- white-space: pre-wrap;
- word-break: break-word;
- line-height: 1.8;
- color: #0f172a;
- overflow-x: auto;
- }
- .content-block--summary {
- background: linear-gradient(135deg, #fff8e8 0%, #fffdf5 100%);
- }
- .content-block--analysis {
- background: linear-gradient(135deg, #eefcf4 0%, #f8fffb 100%);
- }
- .content-block--brief {
- background: linear-gradient(135deg, #fffdf2 0%, #fffaf0 100%);
- }
- .content-block--script {
- min-height: 340px;
- background: linear-gradient(180deg, #f6f8ff 0%, #ffffff 100%);
- font-size: 15px;
- }
- .content-block--danger {
- background: linear-gradient(135deg, #fff1f2 0%, #fff8f8 100%);
- color: #991b1b;
- }
- .content-block--material {
- min-height: 260px;
- }
- .link-list {
- display: grid;
- gap: 10px;
- }
- .link-item {
- padding: 14px 16px;
- border-radius: 14px;
- background: #f8fafc;
- }
- .link-item--lead {
- background: linear-gradient(135deg, #eef6ff 0%, #f8fbff 100%);
- }
- .link-label {
- color: #0369a1;
- font-size: 12px;
- font-weight: 700;
- }
- .link-text {
- margin-top: 8px;
- line-height: 1.75;
- word-break: break-all;
- color: #0f172a;
- }
- .empty-tip {
- padding: 16px;
- border-radius: 14px;
- background: #f8fafc;
- color: #94a3b8;
- text-align: center;
- }
- :deep(.material-tabs .el-tabs__nav-wrap::after) {
- background-color: #e2e8f0;
- }
- :deep(.material-tabs .el-tabs__item) {
- font-weight: 700;
- }
- :deep(.material-tabs .el-tabs__item.is-active) {
- color: #2563eb;
- }
- :deep(.material-tabs .el-tabs__active-bar) {
- background-color: #2563eb;
- }
- @media (max-width: 1440px) {
- .stats-grid {
- grid-template-columns: repeat(3, minmax(0, 1fr));
- }
- }
- @media (max-width: 960px) {
- .hero-card {
- flex-direction: column;
- }
- .hero-actions {
- flex-direction: row;
- min-width: 0;
- }
- .stats-grid {
- grid-template-columns: 1fr;
- }
- }
- @media (max-width: 640px) {
- .hero-card,
- .stat-card,
- .section-card {
- padding: 16px;
- border-radius: 16px;
- }
- .hero-title {
- font-size: 30px;
- }
- .section-title {
- font-size: 21px;
- }
- .content-block {
- padding: 14px;
- }
- }
- </style>
|