完善 预览功能
This commit is contained in:
parent
24502a526d
commit
bcd6f63bb3
|
|
@ -13,7 +13,7 @@
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "",
|
"launchUrl": "/swagger/index.html",
|
||||||
"applicationUrl": "http://*:5238",
|
"applicationUrl": "http://*:5238",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
|
|
||||||
|
|
@ -9,21 +9,21 @@ export class hTableAPI {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
PageList(data = {}) {
|
PageList(data = {}) {
|
||||||
return http.request<Res<any>>("post", `api/${this.url}/PageList`, { data });
|
return http.request<any>("post", `api/${this.url}/PageList`, { data });
|
||||||
}
|
}
|
||||||
Info(tag) {
|
Info(tag) {
|
||||||
const pUrl = `api/${this.url}/${tag}`;
|
const pUrl = `api/${this.url}/${tag}`;
|
||||||
let getUrl = pUrl;
|
let getUrl = pUrl;
|
||||||
return http.request<Res<any>>("get", getUrl);
|
return http.request<any>("get", getUrl);
|
||||||
}
|
}
|
||||||
edit(data) {
|
edit(data) {
|
||||||
return http.request<Res<any>>("post", `api/${this.url}/Edit`, { data });
|
return http.request<any>("post", `api/${this.url}/Edit`, { data });
|
||||||
}
|
}
|
||||||
delete(data) {
|
delete(data) {
|
||||||
return http.request<Res<any>>("post", `api/${this.url}/Del`, { data });
|
return http.request<any>("post", `api/${this.url}/Del`, { data });
|
||||||
}
|
}
|
||||||
querycombo(data = {}) {
|
querycombo(data = {}) {
|
||||||
return http.request<Res<ComboModel[]>>(
|
return http.request<ComboModel[]>(
|
||||||
"post",
|
"post",
|
||||||
`api/${this.url}/QueryCombo`,
|
`api/${this.url}/QueryCombo`,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ export interface Question {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoKnowRes {
|
export interface VideoKnowRes {
|
||||||
Theme: string;
|
theme: string;
|
||||||
Content: string;
|
content: string;
|
||||||
KnowPoint: string;
|
knowPoint: string;
|
||||||
KnowPointId: number;
|
knowPointId: number;
|
||||||
QuestionArr: Question[];
|
questionArr: Question[];
|
||||||
startTime: number;
|
startTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,8 +25,8 @@ export interface SenseVoiceRes {
|
||||||
export interface ShowTaskInfoRes {
|
export interface ShowTaskInfoRes {
|
||||||
captions: SenseVoiceRes[];
|
captions: SenseVoiceRes[];
|
||||||
captions1: SenseVoiceRes[];
|
captions1: SenseVoiceRes[];
|
||||||
VideoKnows: VideoKnowRes[];
|
videoKnows: VideoKnowRes[];
|
||||||
MediaUrl: string;
|
mediaUrl: string;
|
||||||
}
|
}
|
||||||
export interface RowRloadResult {
|
export interface RowRloadResult {
|
||||||
progress: string;
|
progress: string;
|
||||||
|
|
|
||||||
|
|
@ -303,13 +303,13 @@ export class SearchConditions {
|
||||||
this.defaultConditions = [];
|
this.defaultConditions = [];
|
||||||
this.Conditions = [];
|
this.Conditions = [];
|
||||||
}
|
}
|
||||||
/** 是否显示搜索 */
|
/** 是否显示搜索 [true]*/
|
||||||
show?: boolean;
|
show?: boolean;
|
||||||
/** 显示分页器 */
|
/** 显示分页器 [true]*/
|
||||||
showPage?:boolean;
|
showPage?:boolean;
|
||||||
/** 当前页码 */
|
/** 当前页码 [0]*/
|
||||||
PageIndex?: number;
|
PageIndex?: number;
|
||||||
/** 每页大小 */
|
/** 每页大小 [20]*/
|
||||||
PageSize?: number;
|
PageSize?: number;
|
||||||
/** 排序字段 */
|
/** 排序字段 */
|
||||||
OrderBy?: string;
|
OrderBy?: string;
|
||||||
|
|
|
||||||
|
|
@ -248,12 +248,9 @@ function handleResetForm() {
|
||||||
function tableClose() {
|
function tableClose() {
|
||||||
handleAddCallback();
|
handleAddCallback();
|
||||||
}
|
}
|
||||||
function pageSizeChange(o) {
|
function paginationChange(currentPage: number, pageSize: number) {
|
||||||
table.value.search.PageSize = o;
|
table.value.search.PageIndex = currentPage - 1;
|
||||||
fetchPagedData();
|
table.value.search.PageSize = pageSize;
|
||||||
}
|
|
||||||
function pageIndexChange(o) {
|
|
||||||
table.value.search.PageIndex = o - 1;
|
|
||||||
fetchPagedData();
|
fetchPagedData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -375,7 +372,7 @@ function fetchPagedData() {
|
||||||
table.value.data = res.data.map((s, i) => {
|
table.value.data = res.data.map((s, i) => {
|
||||||
return { ...s, customId: i };
|
return { ...s, customId: i };
|
||||||
});
|
});
|
||||||
table.value.pageData = res.data;
|
table.value.pageData = res;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -556,13 +553,10 @@ function fetchPagedData() {
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<el-pagination
|
<el-pagination
|
||||||
:current-page="table.search.PageIndex + 1"
|
|
||||||
:page-sizes="[20, 40, 80, 100]"
|
:page-sizes="[20, 40, 80, 100]"
|
||||||
:page-size="table.search.PageSize"
|
|
||||||
layout="prev, pager, next,sizes, total"
|
layout="prev, pager, next,sizes, total"
|
||||||
:total="table.pageData.total"
|
:total="table.pageData.total"
|
||||||
@size-change="pageSizeChange"
|
@change="paginationChange"
|
||||||
@current-change="pageIndexChange"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,20 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/welcome/showTask",
|
path: "/welcome/showTask",
|
||||||
name: "ShowTask",
|
name: "showTask",
|
||||||
component: () => import("@/views/welcome/showTask.vue"),
|
component: () => import("@/views/welcome/showTask.vue"),
|
||||||
|
meta: {
|
||||||
|
title: "预览任务",
|
||||||
|
showLink: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/welcome/runningTask",
|
||||||
|
name: "runningTask",
|
||||||
|
component: () => import("@/views/welcome/runningTask.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "进行中任务",
|
title: "进行中任务",
|
||||||
showLink: VITE_HIDE_HOME === "true" ? false : true
|
showLink: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,16 @@ import { ReStart, RowRload } from "@/api/videoTask";
|
||||||
import { Refresh } from "@element-plus/icons-vue";
|
import { Refresh } from "@element-plus/icons-vue";
|
||||||
import { message } from "@/utils/message";
|
import { message } from "@/utils/message";
|
||||||
import { json } from "stream/consumers";
|
import { json } from "stream/consumers";
|
||||||
|
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const ControllerName = "VideoTask";
|
const ControllerName = "VideoTask";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: ControllerName,
|
name: ControllerName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{
|
const route = useRouter();
|
||||||
data: any;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function searchCallback(data) {}
|
function searchCallback(data) {}
|
||||||
const table = ref<{ initTable: (config: TableConfig) => void }>();
|
const table = ref<{ initTable: (config: TableConfig) => void }>();
|
||||||
const tableData: TableConfig = intTableData({
|
const tableData: TableConfig = intTableData({
|
||||||
|
|
@ -40,6 +40,7 @@ const tableData: TableConfig = intTableData({
|
||||||
// 查询条件
|
// 查询条件
|
||||||
show: true,
|
show: true,
|
||||||
PageSize: 10,
|
PageSize: 10,
|
||||||
|
showPage: true,
|
||||||
},
|
},
|
||||||
operationColumn: true, // 显示操作按钮
|
operationColumn: true, // 显示操作按钮
|
||||||
operationColumnData: [],
|
operationColumnData: [],
|
||||||
|
|
@ -100,6 +101,7 @@ let redisChannelEnum = ref<ComboModel[]>([]);
|
||||||
const showTable = ref(false);
|
const showTable = ref(false);
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
//初始化数据
|
//初始化数据
|
||||||
|
|
||||||
tableData.column.videoType.setting.datasource = await getenum("AttachmentsInfoType");
|
tableData.column.videoType.setting.datasource = await getenum("AttachmentsInfoType");
|
||||||
tableData.column.lastEnum.setting.datasource = await getenum("RedisChannelEnum");
|
tableData.column.lastEnum.setting.datasource = await getenum("RedisChannelEnum");
|
||||||
tableData.column.subject.setting.datasource = await getenum("SubjectEnum");
|
tableData.column.subject.setting.datasource = await getenum("SubjectEnum");
|
||||||
|
|
@ -132,11 +134,12 @@ async function RloadTaskInfo(row: any) {
|
||||||
element.time = formatDateToChinese(
|
element.time = formatDateToChinese(
|
||||||
row.TaskInfo.startTime[element.title.toLowerCase()]
|
row.TaskInfo.startTime[element.title.toLowerCase()]
|
||||||
);
|
);
|
||||||
if (element.value < row.TaskInfo.lastEnum) {
|
let i = row.TaskInfo.stepData.indexOf(element);
|
||||||
|
if (i < row.TaskInfo.active) {
|
||||||
element.status = "finish";
|
element.status = "finish";
|
||||||
} else if (element.value == 60) {
|
} else if (element.value == 60) {
|
||||||
element.status = "success";
|
element.status = "success";
|
||||||
} else if (element.value == row.TaskInfo.lastEnum) {
|
} else if (i == row.TaskInfo.active) {
|
||||||
element.status = "process";
|
element.status = "process";
|
||||||
} else {
|
} else {
|
||||||
element.status = "wait";
|
element.status = "wait";
|
||||||
|
|
@ -198,7 +201,16 @@ const stepData = ref<StepData[]>([
|
||||||
<el-button type="danger" @click="showDialog(props.row.id)"
|
<el-button type="danger" @click="showDialog(props.row.id)"
|
||||||
>重试</el-button
|
>重试</el-button
|
||||||
>
|
>
|
||||||
<el-button type="primary">预览</el-button>
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
route.push({
|
||||||
|
path: '/welcome/showTask',
|
||||||
|
query: { id: props.row.id.toString() },
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>预览</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
<template>
|
||||||
|
<div id="video-container">
|
||||||
|
<div v-if="videoKnows.length > 0">
|
||||||
|
<div id="segmentsContainer" class="sc" :class="{ locked: isLocked }">
|
||||||
|
<h2>
|
||||||
|
<button class="gudingBtn" @click="toggleLock">
|
||||||
|
{{ isLocked ? "🔓" : "🔒" }}
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div v-for="(item, index) in videoKnows" :key="index" class="knowDiv">
|
||||||
|
<div class="knowTtile">
|
||||||
|
<div style="cursor: pointer" @click="spClick(index, $event)">
|
||||||
|
<div class="knowTtileTheme">{{ getTimeRange(item) }} {{ item.Theme }}</div>
|
||||||
|
<span class="kSpan">#{{ item.KnowPointId }} {{ item.KnowPoint }}</span>
|
||||||
|
</div>
|
||||||
|
<div>概览: {{ item.Content }}</div>
|
||||||
|
<br />
|
||||||
|
<div v-if="item.QuestionArr && item.QuestionArr.length > 0">
|
||||||
|
<div
|
||||||
|
v-for="(q, qIndex) in item.QuestionArr"
|
||||||
|
:key="qIndex"
|
||||||
|
class="knowQuestion"
|
||||||
|
@click="spClickTime(q.startTime)"
|
||||||
|
>
|
||||||
|
<h3>
|
||||||
|
问题: <span class="kSpan">{{ q.startTime }} 秒</span>
|
||||||
|
</h3>
|
||||||
|
<div class="kSpan">{{ q.topicStem }}</div>
|
||||||
|
<div>{{ q.question }}</div>
|
||||||
|
<img
|
||||||
|
style="text-align: center"
|
||||||
|
:src="q.pPTImageUrl"
|
||||||
|
width="320"
|
||||||
|
height="180"
|
||||||
|
:alt="'问题图片' + qIndex"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<button class="kBtn" @click="spClick(index, $event)">
|
||||||
|
<span>{{ getFirstChar(item.Theme) }} {{ item.Theme }}</span>
|
||||||
|
<br />
|
||||||
|
<span class="kSpan textEllipsis"
|
||||||
|
>#{{ item.KnowPointId }} {{ item.KnowPoint }}</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<video ref="videoPlayerEL" controls autoplay>
|
||||||
|
<source :src="videoSrc" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
<div ref="subtitleAreaEL" class="subtitles">{{ currentSubtitle }}</div>
|
||||||
|
<div ref="subtitleArea1EL" class="subtitles" :style="subtitleStyle">
|
||||||
|
{{ currentSubtitle1 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SenseVoiceRes, ShowTaskInfo, VideoKnowRes } from "@/api/videoTask";
|
||||||
|
import { message } from "@/utils/message";
|
||||||
|
import { isEmpty } from "@pureadmin/utils";
|
||||||
|
import { ref, onMounted, nextTick } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "runningTask",
|
||||||
|
});
|
||||||
|
|
||||||
|
const subtitleAreaEL = ref<HTMLElement | null>(null);
|
||||||
|
const subtitleArea1EL = ref<HTMLElement | null>(null);
|
||||||
|
const videoPlayerEL = ref<HTMLVideoElement | null>(null);
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const videoKnows = ref<VideoKnowRes[]>([]);
|
||||||
|
const currentSubtitle = ref("");
|
||||||
|
const currentSubtitle1 = ref("");
|
||||||
|
const isLocked = ref(false);
|
||||||
|
const displayButton = ref<any[]>([]);
|
||||||
|
const lastSegments = ref<any>(null);
|
||||||
|
const videoSrc = ref("");
|
||||||
|
const subtitles = ref<SenseVoiceRes[]>([]);
|
||||||
|
const b1 = ref([]);
|
||||||
|
const subtitles1 = ref<SenseVoiceRes[]>([]);
|
||||||
|
|
||||||
|
// 计算样式
|
||||||
|
const subtitleStyle = {
|
||||||
|
bottom: "101px",
|
||||||
|
backgroundColor: "rgb(99 129 103 / 50%)",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取首字符
|
||||||
|
const getFirstChar = (str: string): string => {
|
||||||
|
return str ? str.charAt(0) : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换锁定状态
|
||||||
|
const toggleLock = () => {
|
||||||
|
isLocked.value = !isLocked.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击知识点
|
||||||
|
const spClick = (index: number, event: Event) => {
|
||||||
|
if (videoPlayerEL && displayButton.value[index]) {
|
||||||
|
videoPlayerEL.value.currentTime = displayButton.value[index].startTime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击时间跳转
|
||||||
|
const spClickTime = (startTime: number) => {
|
||||||
|
if (videoPlayerEL) {
|
||||||
|
videoPlayerEL.value.currentTime = startTime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化知识点按钮
|
||||||
|
const initKD = () => {
|
||||||
|
const btns = document.getElementsByClassName("kBtn");
|
||||||
|
if (btns.length === 0) return;
|
||||||
|
|
||||||
|
displayButton.value = b1.value.map((s: any, i: number) => {
|
||||||
|
return { ...s, button: btns[i] };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置数据(模拟从后端获取)
|
||||||
|
const setDB = (
|
||||||
|
a: SenseVoiceRes[],
|
||||||
|
a1: SenseVoiceRes[],
|
||||||
|
videoKnows: VideoKnowRes[],
|
||||||
|
c: string
|
||||||
|
) => {
|
||||||
|
subtitles.value = a;
|
||||||
|
subtitles1.value = a1;
|
||||||
|
b1.value = videoKnows;
|
||||||
|
videoSrc.value = c;
|
||||||
|
|
||||||
|
// 初始化视频数据
|
||||||
|
init();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
const init = () => {
|
||||||
|
if (!videoPlayerEL) return;
|
||||||
|
|
||||||
|
// 视频时间变化监听
|
||||||
|
videoPlayerEL.value.addEventListener("timeupdate", function () {
|
||||||
|
if (displayButton.value.length === 0) initKD();
|
||||||
|
|
||||||
|
const currentTime = videoPlayerEL.value.currentTime;
|
||||||
|
if (subtitleAreaEL) subtitleAreaEL.value.textContent = "";
|
||||||
|
if (subtitleArea1EL) subtitleArea1EL.value.textContent = "";
|
||||||
|
|
||||||
|
// 更新字幕
|
||||||
|
subtitles.value.forEach((subtitle, index) => {
|
||||||
|
if (
|
||||||
|
currentTime >= subtitle.start &&
|
||||||
|
currentTime <= subtitle.end &&
|
||||||
|
subtitleAreaEL &&
|
||||||
|
subtitleAreaEL.value.textContent !== subtitle.text
|
||||||
|
) {
|
||||||
|
currentSubtitle.value = subtitle.text;
|
||||||
|
currentSubtitle1.value = subtitles1.value[index]?.text || "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新当前时间段
|
||||||
|
const segment = displayButton.value.findLast((s: any) => currentTime >= s.startTime);
|
||||||
|
if (segment) {
|
||||||
|
segment.button.style.backgroundColor = "rgb(238, 200, 118)";
|
||||||
|
if (lastSegments.value && lastSegments.value !== segment) {
|
||||||
|
lastSegments.value.button.style.backgroundColor = "rgb(240, 249, 235)";
|
||||||
|
}
|
||||||
|
lastSegments.value = segment;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 扩展功能:格式化开始和结束时间范围
|
||||||
|
* @param segment 包含 StartTime 和 EndTime 的对象
|
||||||
|
* @returns 格式化的时间范围字符串 (MM:SS - MM:SS)
|
||||||
|
*/
|
||||||
|
function getTimeRange(segment: VideoKnowRes): string {
|
||||||
|
const startTime = segment.startTime ?? 0;
|
||||||
|
|
||||||
|
const startMinutes = Math.floor(startTime / 60);
|
||||||
|
const startSeconds = Math.floor(startTime % 60);
|
||||||
|
|
||||||
|
const sf = startMinutes.toString().padStart(2, "0");
|
||||||
|
const sm = startSeconds.toString().padStart(2, "0");
|
||||||
|
|
||||||
|
return `${sf}:${sm}`;
|
||||||
|
}
|
||||||
|
// 组件挂载后
|
||||||
|
onMounted(async () => {
|
||||||
|
// 初始化MathJax
|
||||||
|
if (window.MathJax) {
|
||||||
|
window.MathJax = {
|
||||||
|
tex: {
|
||||||
|
inlineMath: [
|
||||||
|
["$", "$"],
|
||||||
|
["\\(", "\\)"],
|
||||||
|
],
|
||||||
|
displayMath: [
|
||||||
|
["$$", "$$"],
|
||||||
|
["\\[", "\\]"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 获取当前路由对象
|
||||||
|
const route = useRoute();
|
||||||
|
const data = isEmpty(route.params) ? route.query : route.params;
|
||||||
|
if (isEmpty(data.id) || data.id == null) {
|
||||||
|
message("无效的任务", { type: "warning" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let info = await ShowTaskInfo(data.id);
|
||||||
|
debugger;
|
||||||
|
setDB(info.captions, info.captions1, info.VideoKnows, info.MediaUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 扩展Window接口以包含全局变量
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
MathJax: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -10,14 +10,14 @@
|
||||||
<div v-for="(item, index) in videoKnows" :key="index" class="knowDiv">
|
<div v-for="(item, index) in videoKnows" :key="index" class="knowDiv">
|
||||||
<div class="knowTtile">
|
<div class="knowTtile">
|
||||||
<div style="cursor: pointer" @click="spClick(index, $event)">
|
<div style="cursor: pointer" @click="spClick(index, $event)">
|
||||||
<div class="knowTtileTheme">{{ getTimeRange(item) }} {{ item.Theme }}</div>
|
<div class="knowTtileTheme">{{ getTimeRange(item) }} {{ item.theme }}</div>
|
||||||
<span class="kSpan">#{{ item.KnowPointId }} {{ item.KnowPoint }}</span>
|
<span class="kSpan">#{{ item.knowPointId }} {{ item.knowPoint }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>概览: {{ item.Content }}</div>
|
<div>概览: {{ item.content }}</div>
|
||||||
<br />
|
<br />
|
||||||
<div v-if="item.QuestionArr && item.QuestionArr.length > 0">
|
<div v-if="item.questionArr && item.questionArr.length > 0">
|
||||||
<div
|
<div
|
||||||
v-for="(q, qIndex) in item.QuestionArr"
|
v-for="(q, qIndex) in item.questionArr"
|
||||||
:key="qIndex"
|
:key="qIndex"
|
||||||
class="knowQuestion"
|
class="knowQuestion"
|
||||||
@click="spClickTime(q.startTime)"
|
@click="spClickTime(q.startTime)"
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
:src="q.pPTImageUrl"
|
:src="q.pPTImageUrl"
|
||||||
width="320"
|
width="320"
|
||||||
height="180"
|
height="180"
|
||||||
:alt="'问题图片' + qIndex"
|
:alt="'试题' + qIndex"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
@ -41,16 +41,16 @@
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<button class="kBtn" @click="spClick(index, $event)">
|
<button class="kBtn" @click="spClick(index, $event)">
|
||||||
<span>{{ getFirstChar(item.Theme) }} {{ item.Theme }}</span>
|
<span>{{ getTimeRange(item) }} {{ item.theme }}</span>
|
||||||
<br />
|
<br />
|
||||||
<span class="kSpan textEllipsis"
|
<span class="kSpan textEllipsis"
|
||||||
>#{{ item.KnowPointId }} {{ item.KnowPoint }}</span
|
>#{{ item.knowPointId }} {{ item.knowPoint }}</span
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<video ref="videoPlayerEL" controls autoplay>
|
<video ref="videoPlayerEL" @timeupdate="timeupdateVideo" controls autoplay>
|
||||||
<source :src="videoSrc" type="video/mp4" />
|
<source :src="videoSrc" type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
<div ref="subtitleAreaEL" class="subtitles">{{ currentSubtitle }}</div>
|
<div ref="subtitleAreaEL" class="subtitles">{{ currentSubtitle }}</div>
|
||||||
|
|
@ -62,11 +62,14 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SenseVoiceRes, ShowTaskInfo, VideoKnowRes } from "@/api/videoTask";
|
import { SenseVoiceRes, ShowTaskInfo, VideoKnowRes } from "@/api/videoTask";
|
||||||
|
import { message } from "@/utils/message";
|
||||||
|
import { isEmpty } from "@pureadmin/utils";
|
||||||
import { ref, onMounted, nextTick } from "vue";
|
import { ref, onMounted, nextTick } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
const subtitleAreaEL = ref<HTMLElement | null>(null);
|
defineOptions({
|
||||||
const subtitleArea1EL = ref<HTMLElement | null>(null);
|
name: "showTask",
|
||||||
|
});
|
||||||
const videoPlayerEL = ref<HTMLVideoElement | null>(null);
|
const videoPlayerEL = ref<HTMLVideoElement | null>(null);
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
|
|
@ -125,54 +128,43 @@ const initKD = () => {
|
||||||
const setDB = (
|
const setDB = (
|
||||||
a: SenseVoiceRes[],
|
a: SenseVoiceRes[],
|
||||||
a1: SenseVoiceRes[],
|
a1: SenseVoiceRes[],
|
||||||
videoKnows: VideoKnowRes[],
|
vKnows: VideoKnowRes[],
|
||||||
c: string
|
c: string
|
||||||
) => {
|
) => {
|
||||||
subtitles.value = a;
|
subtitles.value = a;
|
||||||
subtitles1.value = a1;
|
subtitles1.value = a1;
|
||||||
b1.value = videoKnows;
|
b1.value = vKnows;
|
||||||
|
videoKnows.value = vKnows;
|
||||||
videoSrc.value = c;
|
videoSrc.value = c;
|
||||||
|
videoPlayerEL.value?.load();
|
||||||
// 初始化视频数据
|
videoPlayerEL.value?.play();
|
||||||
init();
|
|
||||||
};
|
};
|
||||||
|
function timeupdateVideo() {
|
||||||
|
if (displayButton.value.length === 0) initKD();
|
||||||
|
|
||||||
// 初始化
|
const currentTime = videoPlayerEL.value.currentTime;
|
||||||
const init = () => {
|
// 更新字幕
|
||||||
if (!videoPlayerEL) return;
|
let subtitleI = subtitles.value.findIndex(
|
||||||
|
(subtitle) => currentTime >= subtitle.start && currentTime <= subtitle.end
|
||||||
|
);
|
||||||
|
if (subtitleI > -1 && currentSubtitle.value !== subtitles.value[subtitleI].text) {
|
||||||
|
currentSubtitle.value = subtitles.value[subtitleI].text;
|
||||||
|
currentSubtitle1.value = subtitles1.value[subtitleI]?.text || "";
|
||||||
|
} else if (subtitleI == -1) {
|
||||||
|
currentSubtitle.value = "";
|
||||||
|
currentSubtitle1.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
// 视频时间变化监听
|
// 更新当前时间段
|
||||||
videoPlayerEL.value.addEventListener("timeupdate", function () {
|
const segment = displayButton.value.findLast((s: any) => currentTime >= s.startTime);
|
||||||
if (displayButton.value.length === 0) initKD();
|
if (segment) {
|
||||||
|
segment.button.style.backgroundColor = "rgb(238, 200, 118)";
|
||||||
const currentTime = videoPlayerEL.value.currentTime;
|
if (lastSegments.value && lastSegments.value !== segment) {
|
||||||
if (subtitleAreaEL) subtitleAreaEL.value.textContent = "";
|
lastSegments.value.button.style.backgroundColor = "rgb(240, 249, 235)";
|
||||||
if (subtitleArea1EL) subtitleArea1EL.value.textContent = "";
|
|
||||||
|
|
||||||
// 更新字幕
|
|
||||||
subtitles.value.forEach((subtitle, index) => {
|
|
||||||
if (
|
|
||||||
currentTime >= subtitle.start &&
|
|
||||||
currentTime <= subtitle.end &&
|
|
||||||
subtitleAreaEL &&
|
|
||||||
subtitleAreaEL.value.textContent !== subtitle.text
|
|
||||||
) {
|
|
||||||
currentSubtitle.value = subtitle.text;
|
|
||||||
currentSubtitle1.value = subtitles1.value[index]?.text || "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新当前时间段
|
|
||||||
const segment = displayButton.value.findLast((s: any) => currentTime >= s.startTime);
|
|
||||||
if (segment) {
|
|
||||||
segment.button.style.backgroundColor = "rgb(238, 200, 118)";
|
|
||||||
if (lastSegments.value && lastSegments.value !== segment) {
|
|
||||||
lastSegments.value.button.style.backgroundColor = "rgb(240, 249, 235)";
|
|
||||||
}
|
|
||||||
lastSegments.value = segment;
|
|
||||||
}
|
}
|
||||||
});
|
lastSegments.value = segment;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 扩展功能:格式化开始和结束时间范围
|
* 扩展功能:格式化开始和结束时间范围
|
||||||
* @param segment 包含 StartTime 和 EndTime 的对象
|
* @param segment 包含 StartTime 和 EndTime 的对象
|
||||||
|
|
@ -206,12 +198,15 @@ onMounted(async () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前路由对象
|
// 获取当前路由对象
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const id = route.params.id;
|
const data = isEmpty(route.params) ? route.query : route.params;
|
||||||
let info = await ShowTaskInfo(id);
|
if (isEmpty(data.id) || data.id == null) {
|
||||||
setDB(info.captions, info.captions1, info.VideoKnows, info.MediaUrl);
|
message("无效的任务", { type: "warning" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let info = await ShowTaskInfo(data.id);
|
||||||
|
setDB(info.captions, info.captions1, info.videoKnows, info.mediaUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 扩展Window接口以包含全局变量
|
// 扩展Window接口以包含全局变量
|
||||||
|
|
@ -221,3 +216,124 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.locked {
|
||||||
|
right: 0px !important;
|
||||||
|
}
|
||||||
|
#video-container {
|
||||||
|
position: relative;
|
||||||
|
width: 1660px;
|
||||||
|
height: 850px;
|
||||||
|
float: left;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kSpan {
|
||||||
|
color: rgba(120, 120, 120, 0.66);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
width: 330px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textEllipsis {
|
||||||
|
white-space: nowrap; /* 禁止换行 */
|
||||||
|
overflow: hidden; /* 隐藏溢出内容 */
|
||||||
|
text-overflow: ellipsis; /* 显示省略号 */
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 94%;
|
||||||
|
height: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gudingBtn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
/* border-radius: 16px; */
|
||||||
|
line-height: 27px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitles {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 200px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#segmentsContainer:is(:hover) {
|
||||||
|
right: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#segmentsContainer {
|
||||||
|
transition: right 0.7s;
|
||||||
|
z-index: 999;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background-color: #e3e3e3c2;
|
||||||
|
position: absolute;
|
||||||
|
right: -300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 400px;
|
||||||
|
height: 750px;
|
||||||
|
gap: 10px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
float: left;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
padding: 10px;
|
||||||
|
align-content: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kBtn {
|
||||||
|
width: 340px;
|
||||||
|
height: 60px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgb(103, 194, 58);
|
||||||
|
background-color: rgb(240, 249, 235);
|
||||||
|
border: 1px solid rgb(179, 225, 157);
|
||||||
|
}
|
||||||
|
|
||||||
|
.knowTtileTheme {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgb(103, 194, 58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kBtn:hover {
|
||||||
|
background-color: rgb(248, 230, 191) !important;
|
||||||
|
border: 1px solid rgb(206, 187, 81);
|
||||||
|
}
|
||||||
|
|
||||||
|
.knowDiv {
|
||||||
|
}
|
||||||
|
|
||||||
|
.knowQuestion {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #ff000059;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #cddc393d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.knowDiv:hover .knowTtile {
|
||||||
|
width: 340px;
|
||||||
|
display: block;
|
||||||
|
background-color: rgb(240, 249, 235);
|
||||||
|
border: 1px solid rgb(179, 225, 157);
|
||||||
|
}
|
||||||
|
|
||||||
|
.knowTtile {
|
||||||
|
position: absolute;
|
||||||
|
text-align: left;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue