|
|
@@ -0,0 +1,453 @@
|
|
|
+<template>
|
|
|
+ <div class="task-page">
|
|
|
+ <section class="task-page__top">
|
|
|
+ <section class="overview-strip">
|
|
|
+ <div class="overview-item">
|
|
|
+ <span class="overview-item__label">全部任务</span>
|
|
|
+ <strong class="overview-item__value">{{ totalCount }}</strong>
|
|
|
+ </div>
|
|
|
+ <div class="overview-item">
|
|
|
+ <span class="overview-item__label">待处理</span>
|
|
|
+ <strong class="overview-item__value">{{ statusCount("PENDING") }}</strong>
|
|
|
+ </div>
|
|
|
+ <div class="overview-item">
|
|
|
+ <span class="overview-item__label">处理中</span>
|
|
|
+ <strong class="overview-item__value">{{ statusCount("RUNNING") }}</strong>
|
|
|
+ </div>
|
|
|
+ <div class="overview-item">
|
|
|
+ <span class="overview-item__label">已完成</span>
|
|
|
+ <strong class="overview-item__value">{{ statusCount("DONE") }}</strong>
|
|
|
+ </div>
|
|
|
+ <div class="overview-item overview-item--danger">
|
|
|
+ <span class="overview-item__label">失败</span>
|
|
|
+ <strong class="overview-item__value">{{ statusCount("FAILED") }}</strong>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ <div class="task-page__actions">
|
|
|
+ <el-button type="primary" size="large" @click="openCreatePage">新增任务</el-button>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="page-card workspace-card">
|
|
|
+ <div class="workspace-toolbar">
|
|
|
+ <div class="workspace-toolbar__left">
|
|
|
+ <el-input
|
|
|
+ v-model="keyword"
|
|
|
+ placeholder="搜索任务名称、用户要求或视频链接"
|
|
|
+ clearable
|
|
|
+ class="filter-input"
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ @clear="handleSearch"
|
|
|
+ />
|
|
|
+ <el-select
|
|
|
+ v-model="statusFilter"
|
|
|
+ placeholder="全部状态"
|
|
|
+ clearable
|
|
|
+ class="filter-select"
|
|
|
+ @change="handleSearch"
|
|
|
+ @clear="handleSearch"
|
|
|
+ >
|
|
|
+ <el-option label="待处理" value="PENDING" />
|
|
|
+ <el-option label="处理中" value="RUNNING" />
|
|
|
+ <el-option label="已完成" value="DONE" />
|
|
|
+ <el-option label="失败" value="FAILED" />
|
|
|
+ </el-select>
|
|
|
+ <el-button type="primary" @click="handleSearch">查询</el-button>
|
|
|
+ <el-button @click="handleReset">重置</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="workspace-toolbar__right">默认排序:创建时间从新到旧</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="pagedData" class="task-table" empty-text="暂无任务数据">
|
|
|
+ <el-table-column label="任务名称" min-width="220">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="task-title-cell">
|
|
|
+ <div class="task-title-cell__title">{{ row.taskName || "-" }}</div>
|
|
|
+ <div class="task-title-cell__sub">
|
|
|
+ <span>#{{ row.id }}</span>
|
|
|
+ <span>{{ formatScriptType(row.scriptType) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="任务发起人" width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="creator-cell">
|
|
|
+ <el-avatar :size="30" class="creator-cell__avatar">
|
|
|
+ {{ creatorInitial(row.creatorName) }}
|
|
|
+ </el-avatar>
|
|
|
+ <span>{{ row.creatorName || "-" }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="视频数量" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="count-pill">{{ formatVideoCount(row.videoUrls, row.videoUrl) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="用户要求" min-width="220" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ summarizeText(row.promptHint, 48) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="摘要" min-width="240" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ summarizeText(row.summary || row.analysisContent, 52) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="状态" width="110" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="statusTagType(row.status)" effect="light" round>
|
|
|
+ {{ formatStatus(row.status) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column prop="createTime" label="创建时间" width="180" />
|
|
|
+
|
|
|
+ <el-table-column label="操作" width="180" fixed="right" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="action-links">
|
|
|
+ <el-button link type="primary" @click="openDetail(row.id)">详情</el-button>
|
|
|
+ <el-button link type="primary" @click="openEditPage(row.id)">编辑</el-button>
|
|
|
+ <el-button link type="danger" @click="handleDelete(row.id)">删除</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="workspace-footer">
|
|
|
+ <span class="workspace-footer__meta">共 {{ tableData.length }} 条任务</span>
|
|
|
+ <el-pagination
|
|
|
+ background
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ :total="tableData.length"
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
+ v-model:current-page="currentPage"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, onMounted, ref } from "vue";
|
|
|
+import { useRouter } from "vue-router";
|
|
|
+import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
+import { deleteTaskApi, listTasksApi } from "../../api/modules";
|
|
|
+
|
|
|
+const router = useRouter();
|
|
|
+const keyword = ref("");
|
|
|
+const statusFilter = ref("");
|
|
|
+const tableData = ref([]);
|
|
|
+const currentPage = ref(1);
|
|
|
+const pageSize = ref(10);
|
|
|
+
|
|
|
+const totalCount = computed(() => tableData.value.length);
|
|
|
+
|
|
|
+const pagedData = computed(() => {
|
|
|
+ const start = (currentPage.value - 1) * pageSize.value;
|
|
|
+ return tableData.value.slice(start, start + pageSize.value);
|
|
|
+});
|
|
|
+
|
|
|
+const loadData = async () => {
|
|
|
+ try {
|
|
|
+ const result = await listTasksApi({
|
|
|
+ keyword: keyword.value?.trim() || undefined,
|
|
|
+ status: statusFilter.value || undefined,
|
|
|
+ });
|
|
|
+ tableData.value = Array.isArray(result) ? result : [];
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error(error.message || "加载任务列表失败");
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleSearch = async () => {
|
|
|
+ currentPage.value = 1;
|
|
|
+ await loadData();
|
|
|
+};
|
|
|
+
|
|
|
+const handleReset = async () => {
|
|
|
+ keyword.value = "";
|
|
|
+ statusFilter.value = "";
|
|
|
+ currentPage.value = 1;
|
|
|
+ await loadData();
|
|
|
+};
|
|
|
+
|
|
|
+const openCreatePage = () => {
|
|
|
+ router.push("/tasks/create");
|
|
|
+};
|
|
|
+
|
|
|
+const openEditPage = (id) => {
|
|
|
+ router.push(`/tasks/${id}/edit`);
|
|
|
+};
|
|
|
+
|
|
|
+const openDetail = (id) => {
|
|
|
+ router.push(`/tasks/${id}`);
|
|
|
+};
|
|
|
+
|
|
|
+const handleDelete = async (id) => {
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm("确认删除该任务?", "提示", { type: "warning" });
|
|
|
+ await deleteTaskApi(id);
|
|
|
+ ElMessage.success("任务已删除");
|
|
|
+ await loadData();
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== "cancel") {
|
|
|
+ ElMessage.error(error.message || "删除任务失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const formatVideoCount = (videoUrls, videoUrl) => {
|
|
|
+ if (!videoUrls) {
|
|
|
+ return videoUrl ? "1条" : "0条";
|
|
|
+ }
|
|
|
+ const count = String(videoUrls)
|
|
|
+ .split(/\r?\n|,/)
|
|
|
+ .map((item) => item.trim())
|
|
|
+ .filter(Boolean).length;
|
|
|
+ return `${count || 0}条`;
|
|
|
+};
|
|
|
+
|
|
|
+const formatStatus = (value) => {
|
|
|
+ const map = {
|
|
|
+ PENDING: "待处理",
|
|
|
+ RUNNING: "处理中",
|
|
|
+ DONE: "已完成",
|
|
|
+ FAILED: "失败",
|
|
|
+ };
|
|
|
+ return map[value] || value || "-";
|
|
|
+};
|
|
|
+
|
|
|
+const statusTagType = (value) => {
|
|
|
+ if (value === "DONE") return "success";
|
|
|
+ if (value === "RUNNING") return "warning";
|
|
|
+ if (value === "FAILED") return "danger";
|
|
|
+ return "info";
|
|
|
+};
|
|
|
+
|
|
|
+const formatScriptType = (value) => {
|
|
|
+ const map = {
|
|
|
+ VOICEOVER: "口播稿",
|
|
|
+ STORYBOARD: "分镜稿",
|
|
|
+ SUMMARY: "摘要稿",
|
|
|
+ };
|
|
|
+ return map[value] || value || "-";
|
|
|
+};
|
|
|
+
|
|
|
+const summarizeText = (value, limit = 48) => {
|
|
|
+ if (!value) {
|
|
|
+ return "-";
|
|
|
+ }
|
|
|
+ const normalized = String(value).replace(/\s+/g, " ").trim();
|
|
|
+ return normalized.length > limit ? `${normalized.slice(0, limit)}...` : normalized;
|
|
|
+};
|
|
|
+
|
|
|
+const creatorInitial = (value) => String(value || "U").slice(0, 1).toUpperCase();
|
|
|
+const statusCount = (status) => tableData.value.filter((item) => item.status === status).length;
|
|
|
+
|
|
|
+onMounted(loadData);
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.task-page {
|
|
|
+ display: grid;
|
|
|
+ gap: 18px;
|
|
|
+}
|
|
|
+
|
|
|
+.task-page__top {
|
|
|
+ display: flex;
|
|
|
+ align-items: stretch;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 16px;
|
|
|
+ padding-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.task-page__actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.overview-strip {
|
|
|
+ display: grid;
|
|
|
+ flex: 1;
|
|
|
+ grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.overview-item {
|
|
|
+ padding: 18px 20px;
|
|
|
+ border-radius: 16px;
|
|
|
+ background: rgba(255, 255, 255, 0.82);
|
|
|
+ border: 1px solid rgba(226, 232, 240, 0.9);
|
|
|
+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.overview-item__label {
|
|
|
+ display: block;
|
|
|
+ color: #64748b;
|
|
|
+ font-size: 13px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.overview-item__value {
|
|
|
+ color: #0f172a;
|
|
|
+ font-size: 30px;
|
|
|
+ line-height: 1;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+
|
|
|
+.overview-item--danger .overview-item__value {
|
|
|
+ color: #b42318;
|
|
|
+}
|
|
|
+
|
|
|
+.workspace-card {
|
|
|
+ padding: 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.workspace-toolbar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+ padding: 18px 20px;
|
|
|
+ border-bottom: 1px solid #eef2f7;
|
|
|
+}
|
|
|
+
|
|
|
+.workspace-toolbar__left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.workspace-toolbar__right {
|
|
|
+ color: #94a3b8;
|
|
|
+ font-size: 13px;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-input {
|
|
|
+ width: 360px;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-select {
|
|
|
+ width: 150px;
|
|
|
+}
|
|
|
+
|
|
|
+.task-table {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.task-title-cell {
|
|
|
+ display: grid;
|
|
|
+ gap: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.task-title-cell__title {
|
|
|
+ color: #0f172a;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.task-title-cell__sub {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ color: #64748b;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.creator-cell {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ color: #334155;
|
|
|
+}
|
|
|
+
|
|
|
+.creator-cell__avatar {
|
|
|
+ background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
|
|
+ color: #fff;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+
|
|
|
+.count-pill {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-width: 52px;
|
|
|
+ padding: 4px 10px;
|
|
|
+ border-radius: 999px;
|
|
|
+ background: #eff6ff;
|
|
|
+ color: #1d4ed8;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.action-links {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 6px;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.workspace-footer {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 16px;
|
|
|
+ padding: 18px 20px 20px;
|
|
|
+ border-top: 1px solid #eef2f7;
|
|
|
+}
|
|
|
+
|
|
|
+.workspace-footer__meta {
|
|
|
+ color: #64748b;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 1360px) {
|
|
|
+ .overview-strip {
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 900px) {
|
|
|
+ .task-page__top,
|
|
|
+ .workspace-toolbar,
|
|
|
+ .workspace-footer {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-page__actions {
|
|
|
+ align-items: stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ .overview-strip {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-input,
|
|
|
+ .filter-select {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .workspace-toolbar__right {
|
|
|
+ white-space: normal;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 640px) {
|
|
|
+ .overview-strip {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|