325 lines
9.3 KiB
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>
|