Learn.VideoAnalysis/VideoAnalysis/WebUI/src/views/welcome/index.vue

325 lines
9.3 KiB
Vue

<script setup lang="ts">
import ahTable from "@/components/hTable/index.vue";
import {
ComboModel,
ConditionalType,
intTableData,
SearchConditions,
TableColumnSearch,
TableConfig,
} from "@/components/hTable/hTable";
import { onMounted, ref } from "vue";
import { de, fa } from "element-plus/es/locales.mjs";
import { hTableAPI } from "@/api/hTable";
import { getenum } from "@/api/enum";
import { ruleRequired, ruleRequiredNumber } from "@/utils/rules";
import { ElMessage } from "element-plus";
import { ReStart, RowRload } from "@/api/videoTask";
import { Refresh } from "@element-plus/icons-vue";
import { message } from "@/utils/message";
import { json } from "stream/consumers";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { useRouter } from "vue-router";
const ControllerName = "VideoTask";
defineOptions({
name: ControllerName,
});
const props = defineProps<{
searchCallback?: (
s: SearchConditions,
tv: TableConfig
) => boolean | Promise<boolean> | void;
}>();
const route = useRouter();
const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = intTableData({
apiUrl: ControllerName,
expandColumn: true,
selectColumn: false, // 列表选择
border: false, // 是否显示表格边框
searchCallback: props.searchCallback,
expandChange: expandChange,
search: {
// 查询条件
show: true,
PageSize: 10,
showPage: true,
},
operationColumn: true, // 显示操作按钮
operationColumnData: [],
column: {
// 行数据
id: {
label: "ID",
width: "140px",
search: new TableColumnSearch(true),
},
tagId: {
label: "标签ID",
width: "140px",
search: new TableColumnSearch(true),
},
videoType: {
label: "任务类型",
width: "100px",
type: "dropdown",
search: new TableColumnSearch(true),
},
lastEnum: {
label: "最后状态",
width: "200px",
type: "dropdown",
search: new TableColumnSearch(true),
},
subject: {
label: "学科",
width: "100px",
type: "dropdown",
search: new TableColumnSearch(true),
},
comeFrom: {
label: "IP",
width: "120px",
},
mediaUrl: {
label: "媒体地址",
width: "300px",
},
createTime: {
label: "创建时间",
},
},
data: [],
pageData: {
total: 0,
},
selectRows: [],
});
const dialogRef = ref({
visible: false,
value: null as any,
data: Object as any,
});
let redisChannelEnum = ref<ComboModel[]>([]);
const showTable = ref(false);
onMounted(async () => {
//初始化数据
tableData.column.videoType.setting.datasource = await getenum("AttachmentsInfoType");
tableData.column.lastEnum.setting.datasource = await getenum("RedisChannelEnum");
tableData.column.subject.setting.datasource = await getenum("SubjectEnum");
redisChannelEnum.value = tableData.column.lastEnum.setting.datasource;
showTable.value = true;
});
async function showDialog(row) {
dialogRef.value.data = row;
dialogRef.value.value = row.lastEnum;
dialogRef.value.visible = true;
}
async function submitRowRload() {
await ReStart(dialogRef.value.data.id, dialogRef.value.value);
dialogRef.value.visible = false;
message("重试任务", { type: "success" });
}
async function expandChange(row: any, expandedRows: any[]) {
if (expandedRows.find((s) => s == row)) RloadTaskInfo(row);
}
function previewTask(row: any) {
let pageName = "showTask";
let queryData = { id: row.id.toString() };
useMultiTagsStoreHook().handleTags("push", {
path: `/welcome/showTask_` + row.id,
name: pageName,
query: queryData,
meta: {
title: `任务预览` + row.id.toString().slice(-4),
dynamicLevel: 3,
},
});
// 路由跳转
route.push({ name: pageName, query: queryData });
}
function firstLetterToLower(str) {
if (typeof str !== "string" || !str) return str;
return str[0].toLowerCase() + str.slice(1);
}
async function RloadTaskInfo(row: any) {
let res = await RowRload(row.id);
row.TaskInfo = res;
row.TaskInfo.stepData = JSON.parse(JSON.stringify(stepData.value));
row.TaskInfo.active = row.TaskInfo.stepData.findIndex(
(s) => s.title == row.TaskInfo.lastEnum
);
if (row.TaskInfo.startTime != null) {
for (const element of row.TaskInfo.stepData) {
element.time = formatDateToChinese(
row.TaskInfo.startTime[firstLetterToLower(element.title)]
);
let i = row.TaskInfo.stepData.indexOf(element);
if (i < row.TaskInfo.active) {
element.status = "finish";
} else if (element.value == 60) {
element.status = "success";
} else if (i == row.TaskInfo.active) {
element.status = "process";
} else {
element.status = "wait";
}
}
}
}
function formatDateToChinese(dateString) {
const date = new Date(dateString);
if (isNaN(date.getTime())) return "";
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
interface StepData {
status: "" | "wait" | "process" | "finish" | "error" | "success";
time: string | null;
title: string;
value: number;
}
const stepData = ref<StepData[]>([
{ status: "wait", time: null, title: "下载文件", value: 5 },
{ status: "wait", time: null, title: "分离音频", value: 10 },
{ status: "wait", time: null, title: "解析字幕", value: 20 },
{ status: "wait", time: null, title: "AI课程类型", value: 30 },
{ status: "wait", time: null, title: "AI模型分析", value: 40 },
{ status: "wait", time: null, title: "AI分析试题", value: 50 },
{ status: "wait", time: null, title: "结束任务", value: 60 },
]);
</script>
<template>
<div>
<ahTable v-if="showTable" ref="table" :tableConfig="tableData">
<template #expandSlot="{ props }">
<!-- 拓展内容 -->
<div class="expanded-content expandSlot">
<h3>任务详情</h3>
<div class="InfoEx" v-if="props.row.TaskInfo != null">
<div>
<span>进度</span>
<div class="content">
{{ props.row.TaskInfo.lastEnum }} {{ props.row.TaskInfo.progress }}
</div>
</div>
<div>
<span>操作</span>
<div class="content">
<el-button
type="primary"
:icon="Refresh"
@click="RloadTaskInfo(props.row)"
circle
/>
<el-button type="danger" @click="showDialog(props.row)">重试</el-button>
<el-button type="primary" @click="previewTask(props.row)">预览</el-button>
</div>
</div>
<div class="grid_item_full_width" v-if="props.row.TaskInfo != null">
<span>步骤</span>
<el-steps
class="content"
style="max-width: 100%"
:active="props.row.TaskInfo?.active"
align-center
>
<el-step
v-for="s in props.row.TaskInfo.stepData"
:title="s.title"
:description="s.time"
:status="s.status"
/>
</el-steps>
</div>
<div
v-if="
props.row.TaskInfo?.errorMessage != null &&
props.row.TaskInfo?.errorMessage.length > 0
"
>
<span>错误信息</span>
<div class="content">{{ props.row.TaskInfo?.errorMessage }}</div>
</div>
</div>
</div>
</template>
</ahTable>
<el-dialog v-model="dialogRef.visible" title="重试任务" width="430">
<h3>ID : {{ dialogRef.data.id }}</h3>
<p></p>
<p>将从哪个步骤重试?</p>
<el-select
v-model="dialogRef.value"
clearable
placeholder="Select"
style="width: 240px"
>
<el-option
v-for="item in redisChannelEnum"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogRef.visible = false">取消</el-button>
<el-button type="primary" @click="submitRowRload()"> 提交 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.expandSlot {
padding: 10px 20px;
background: #f5f7fa;
}
.container {
width: 100%;
max-width: 1200px;
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.InfoEx {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
margin-top: 20px;
}
.InfoEx span {
font-weight: bold;
margin-right: 10px;
}
.InfoEx .content {
padding: 5px;
}
/* 关键:让某个网格项占满一行(跨所有列) */
.grid_item_full_width {
grid-column: 1 / -1; /* 从第1列跨到最后一列 */
}
/* :deep(.el-step__description) {
font-size: 16px !important;
} */
</style>