Files
PCAdmissionOfficer/index.html
DESKTOP-RQ919RC\Pc 047ddd57f6 feat: 添加HEVC转H.264播放器和打字机效果页面
添加HEVC转H.264播放器功能,包含文件选择、转码进度显示和视频播放
新增打字机效果展示页面,使用Vue实现逐字显示效果
2025-07-25 18:49:11 +08:00

970 lines
60 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>你好,招生官专题 - 寄托天下</title>
<meta name="Description" content="【你好,招生官】是寄托天下将海外及各类合作办学招生项目直接介绍给寄托用户的品牌活动。 我们提供学生和招生官直接互动的平台,帮助学生更好的了解项目,做出选择。" />
<meta name="Keywords" content="美国留学,加拿大留学,香港留学,新加坡留学,英国留学,欧洲留学, 留学经验分享,DIY留学,留学申请,留学流程,留学费用,出国留学,留学论坛, 留学网站,留学考试,GRE,TOEFL,IBT,GMAT,IELTS,SAT,VISA,文书,签证" />
<link rel="stylesheet" href="https://app.gter.net/image/gter/admissionofficer/css/admissionOfficerV2.css" />
<link rel="stylesheet" href="{~forum/css/header-floor.css}" />
<style>
[v-cloak] {
display: none;
}
</style>
<script src="https://app.gter.net/bottom?tpl=header&menukey=admissionOfficer"></script>
</head>
<body>
<video class="vvideo" src=""></video>
<div id="admission-officer" class="admission-officer" v-cloak>
<div class="admission-head">
<div class="admission-head-box">
<img class="admission-head-logo" src="./img/admission-head-logo.png" />
<!-- <div class="admission-head-gray"></div> -->
<!-- <img class="admission-head-1" src="{~admissionofficer/imgV2/1.svg}" /> -->
<img class="admission-head-1" src="https://app.gter.net/image/gter/admissionofficer/imgV2/2.svg" />
<!-- https://app.gter.net/image/gter/passport/index.html?islogin=1#/loginSuccess -->
<!-- <img class="admission-head-2" src="{~admissionofficer/imgV2/admission-head-gray.svg}" /> -->
</div>
</div>
<div class="admission-body">
<div class="interview-box flexflex" v-if="theme == 1">
<img class="bj" src="./img/interview-bj.svg" />
<div class="left">
<div class="head flexacenter">
<img class="icon" src="./img/interview-icon.png" />
<img class="name" src="./img/interview-name.png" />
</div>
<div class="content flexcenter">
<img class="img" :src="interviewData.video_cover" />
<img class="play-btn" src="./img/play-btn.svg" @click="getVideoUrl(interviewData.token)" :style="{ display: isPlaying ? 'none' : 'block' }" />
<div class="bottom" :style="{ transform: isPlaying ? 'translateY(100%)' : 'translateY(0)', opacity: isPlaying ? '0' : '1' }">
<div class="title">{{ interviewData.title }}</div>
<div class="subtitle">{{ interviewData.subtitle }}</div>
</div>
</div>
</div>
<div class="focus-box">
<img class="ok" src="./img/ok.png" />
<div class="head flexcenter">
<img class="arrows" src="./img/arrows-three-black.png" />
<img class="name" src="./img/focus-name.png" />
<img class="arrows" style="transform: rotate(180deg)" src="./img/arrows-three-black.png" />
</div>
<div class="content flexflex">
<div class="info">
<div class="label flexcenter">
<img class="icon" src="./img/quadrangle-blue.svg" />
招生官
</div>
<div class="name flexflex">
{{ interviewData.admission_officer_name }}
<div class="professional">{{ interviewData.admission_officer_rank }}</div>
</div>
<div class="subheading">{{ interviewData.name }}</div>
<div class="subheading">{{ interviewData.admission_officer_position }}</div>
</div>
<div class="introduce-box flexflex">
<div class="introduce-list" v-if="interviewData.focus_of_this_issue">
<div class="introduce-item" v-for="item in interviewData.focus_of_this_issue">{{ item }}</div>
</div>
<div class="play-btn flexcenter" @click="getVideoUrl(interviewData.token)">
立即播放
<img class="icon" src="./img/play-btn.svg" />
</div>
</div>
</div>
</div>
</div>
<div class="preach-box flexflex" v-else>
<img class="bj" src="./img/interview-bj.svg" />
<div class="left">
<div class="head flexacenter">
<img class="icon" src="./img/interview-icon.png" />
<img class="name" :src="preachList[preachIndex]?.istop ? './img/interview-name.png' : './img/preach-name.png'" />
</div>
<div class="box" @mouseenter="preachMouseEnter" @mouseleave="preachMouseLeave">
<div class="indicators flexcenter">
<div class="item" :class="{'pitch': index == preachIndex}" v-for="(item, index) in preachList.length" @click="cutPreach(index)"></div>
</div>
<div class="list" ref="leftPreach">
<div class="item flexacenter" :class="[{'pitch': preachIndex == index},{'pitch-last': preachIndex - 1 == index}]" v-for="(item, index) in preachList" @click="cutPreach(index)" ref="leftPreachItem">
<div class="info flex1">
<div class="name one-line-display">{{ item.title }}</div>
<div class="time">{{ item.lecture_time || "长期答疑" }}</div>
</div>
<img class="icon" src="./img/arrows-full-circle-white.svg" />
</div>
</div>
</div>
</div>
<div class="right" @mouseenter="preachMouseEnter" @mouseleave="preachMouseLeave">
<img class="ok" src="./img/ok.png" />
<div class="img-box flexacenter" ref="rightImgBox">
<template v-for="(item, index) in preachList" :key="index">
<div v-if="item.istop" class="video-img flexacenter">
<img class="play-btn" @click="getVideoUrl(item.token)" src="./img/play-btn.svg" />
<img class="img" :src="item.video_cover" />
<div class="bottom" :style="{ transform: isPlaying ? 'translateY(100%)' : 'translateY(0)', opacity: isPlaying ? '0' : '1' }">
<div class="title">{{ item.title }}</div>
<div class="subtitle">{{ item.subtitle }}</div>
</div>
<div class="interview-info flexflex">
<img class="interview-side-bj" src="https://app.gter.net/image/gter/admissionofficer/imgV2/interview-side-bj.svg" />
<div class="interview-title">
<img class="interview-title-icon" src="https://app.gter.net/image/gter/admissionofficer/imgV2/interview-title-bj.svg" />
访谈人物
</div>
<div class="interview-name flexflex">
{{ item.admission_officer_name }}
<div class="interview-professional">{{ item.admission_officer_rank }}</div>
</div>
<div class="interview-subheading margin-b-10">{{ item.name }}</div>
<div class="interview-subheading">{{ item.admission_officer_position }}</div>
</div>
</div>
<a v-else class="item" target="_blank" :href="item.link_url">
<img class="img" :src="item.image_url" />
</a>
</template>
</div>
</div>
</div>
<div class="interview-more flexacenter" v-if="interviewList.length">
<div class="case flexcenter" v-if="theme == 1">
<div class="name">更多访谈</div>
<div class="english">More interviews</div>
</div>
<div class="case preach flexcenter" v-else>
<div class="name">招生官访谈</div>
<div class="english">Interview video</div>
</div>
<div class="swiper-box">
<div class="btn left flexcenter" @click="scrollLeft" v-if="showLeftBtn">
<img class="arrows" src="./img/arrows-black.svg" />
</div>
<div class="btn right flexcenter" @click="scrollRight" v-if="showRightBtn">
<img class="arrows" src="./img/arrows-black.svg" />
</div>
<div class="swiper flexacenter" ref="swiperRef">
<div class="swiper-item" v-for="item in interviewList" @click="getVideoUrl(item.token)">
<img class="img" :src="item.video_cover" />
<div class="bottom">
<div class="title">{{ item.title }}</div>
<div class="subtitle">{{ item.subtitle }}</div>
</div>
</div>
</div>
</div>
</div>
<div v-else style="margin-bottom: 50px"></div>
<div class="school-list">
<div class="school-item flexflex" v-for="(item, index) in admissionList">
<div class="left">
<div class="abbreviation flexcenter" :style="{ backgroundColor:item.color }">{{ item.abbreviation }}</div>
<img class="icon" src="./img/malformation-icon.svg" />
<img class="img" :src="item.banner" />
</div>
<div class="right">
<div class="info flexacenter">
<img class="img" :src="item.logo" @click="goDetails(item.sid)" />
<div class="flexflex" style="flex-direction: column">
<div class="name flexacenter" :href="`https://schools.gter.net/details/${item.sid}`" target="_blank" @click="goDetails(item.sid)">
<div>{{ item.name }}</div>
<img class="arrows" src="./img/arrows-circle-black.svg" />
</div>
<div class="english" :href="`https://schools.gter.net/details/${item.sid}`" target="_blank" @click="goDetails(item.sid)">{{ item.enname }}</div>
</div>
</div>
<div class="introduce">{{ item.introduction }}</div>
<div class="content">
<div class="year flexacenter">
<div class="item" :class="{'pitch': item.pitch == it}" v-for="it in item.yList" @click="cutSchoolYear(index, it)">
<svg class="svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100px" height="28px" :style="{ fill: item.pitch == it ? item.color : '#ffffff' }">
<g transform="matrix(1 0 0 1 -910 -1138 )"><path d="M 910 1166 L 918.4 1138 L 1010 1138 L 1001.6 1166 L 910 1166 Z " fill-rule="nonzero" stroke="none" /></g>
</svg>
{{ it }}季
</div>
<div class="item more" :class="[{'unfold': item.state}, {'pitch': item.yListMore.includes(item.pitch)}]" v-if="item.yListMore.length > 0" @click="openSchoolYearState(index)">
<svg class="svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100px" height="28px" :style="{ fill: item.yListMore.includes(item.pitch) || item.state ? item.color : '' }">
<g transform="matrix(1 0 0 1 -910 -1138 )"><path d="M 910 1166 L 918.4 1138 L 1010 1138 L 1001.6 1166 L 910 1166 Z " fill-rule="nonzero" stroke="none" /></g>
</svg>
<!-- 更多 -->
{{ item.yListMore.includes(item.pitch) ? `${ item.pitch }季` : '更多' }}
<svg class="arrows" fill="#000000" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="9px" height="5px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -1370 -1149 )"><path d="M 8.8330078125 0.164930555555556 C 8.9443359375 0.274884259259259 9 0.405092592592591 9 0.555555555555556 C 9 0.706018518518518 8.9443359375 0.836226851851851 8.8330078125 0.946180555555556 L 4.8955078125 4.83506944444444 C 4.7841796875 4.94502314814815 4.65234375 5 4.5 5 C 4.34765625 5 4.2158203125 4.94502314814815 4.1044921875 4.83506944444444 L 0.1669921875 0.946180555555556 C 0.0556640625 0.836226851851851 0 0.706018518518518 0 0.555555555555556 C 0 0.405092592592591 0.0556640625 0.274884259259259 0.1669921875 0.164930555555556 C 0.2783203125 0.0549768518518512 0.41015625 0 0.5625 0 L 8.4375 0 C 8.58984375 0 8.7216796875 0.0549768518518512 8.8330078125 0.164930555555556 Z " fill-rule="nonzero" stroke="none" transform="matrix(1 0 0 1 1370 1149 )" /></g>
</svg>
<div class="more-mask" @click.stop="closeSchoolYearState(index)"></div>
<div class="more-box" :style="{ backgroundColor: item.color }">
<div class="more-item" v-for="it in item.yListMore" @click.stop="selectSchoolYearState(index, it)">{{ it }}季</div>
</div>
</div>
</div>
<div class="course-list">
<div class="item" v-for="it in item.list[item.pitch]">
<div class="name flexflex">
<div class="icon flexcenter">
<img class="img" src="./img/course-icon.png" />
</div>
<div class="text flex1">
{{ it.title }}
<div class="label" v-if="it.tag">
<img class="arrows" src="./img/arrows-triangle-blue.svg" />
{{ it.tag }}
</div>
</div>
</div>
<div class="bottom flexacenter">
<div class="time flexacenter">
<div class="icon flexcenter">
<img class="img" src="./img/time-icon.png" />
</div>
{{ it.date || "长期答疑" }}
</div>
<a class="btn flexcenter" :style="{ 'background-color': item.color }" target="_blank" :href="it.url">
了解详情
<img class="arrows" src="./img/arrows-circle-white.svg" />
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="retrospect">
<img class="more-icon" src="./img/more-name.png" />
<div class="list flexflex">
<div class="item flexflex" v-for="item in retrospectList.slice((retrospectPage - 1) * retrospectInterval, retrospectPage * retrospectInterval)" @click="openMoreSchool(item.sid)">
<img class="bg" :src="item.logo" />
<img class="img" :src="item.logo" />
<div class="right">
<div class="name">{{ item.name }}</div>
<div class="english">{{ item.enname }}</div>
<div class="year flexacenter">
<div class="year-item" v-for="it in item.year.length > 5 ? item.year.slice(0,4) : item.year" @click="openMoreSchool(item.sid,it)">
<svg class="svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" width="80px" height="24px">
<defs>
<linearGradient gradientUnits="userSpaceOnUse" x1="841" y1="2920.97" x2="920.847764034253" y2="2920.97" id="LinearGradient1581">
<stop id="Stop1582" stop-color="#d6e5e5" offset="0" />
<stop id="Stop1583" stop-color="#f6f2ea" offset="1" />
</linearGradient>
</defs>
<g transform="matrix(1 0 0 1 -841 -2909 )"><path d="M 841 2933 L 848.2 2909 L 921 2909 L 913.8 2933 L 841 2933 Z " fill-rule="nonzero" fill="url(#LinearGradient1581)" stroke="none" /></g>
</svg>
{{ it }}季
</div>
<div class="year-item" v-if="item.year.length > 5">
<svg class="svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" width="80px" height="24px">
<defs>
<linearGradient gradientUnits="userSpaceOnUse" x1="841" y1="2920.97" x2="920.847764034253" y2="2920.97" id="LinearGradient1581">
<stop id="Stop1582" stop-color="#d6e5e5" offset="0" />
<stop id="Stop1583" stop-color="#f6f2ea" offset="1" />
</linearGradient>
</defs>
<g transform="matrix(1 0 0 1 -841 -2909 )">
<path d="M 841 2933 L 848.2 2909 L 921 2909 L 913.8 2933 L 841 2933 Z " fill-rule="nonzero" fill="url(#LinearGradient1581)" stroke="none" />
</g>
</svg>
更多<img class="arrows" src="./img/arrows-triangle-black.svg" />
</div>
</div>
</div>
</div>
</div>
<div class="pages flexcenter">
<img v-if="retrospectPage == 1" class="arrows gray" src="./img/arrows-thin-gray.svg" />
<img v-else class="arrows rotate180" @click="retrospectPage--" src="./img/arrows-thin-black.svg" />
<div class="item flexcenter" :class="{'pitch': item == retrospectPage}" v-for="item in retrospectPages" @click="retrospectPage = item">{{ item }}</div>
<img v-if="retrospectPage == retrospectPages" class="arrows gray rotate180" src="{~admissionofficer/imgV2/arrows-thin-gray.svg}" />
<img v-else class="arrows" @click="retrospectPage++" src="{~admissionofficer/imgV2/arrows-thin-black.svg}" />
</div>
</div>
<div class="more-school-mask flexcenter" v-if="moreSchoolSid > 0" @click="closeMoreSchool">
<div class="more-school" @click.stop="">
<img class="close" @click.stop="closeMoreSchool" src="{~admissionofficer/imgV2/cross.png}" />
<div class="head flexacenter">
<a :href="moreSchoolUrl" target="_blank"><img class="img" :src="moreSchoolData.logo" /></a>
<div class="info">
<a class="name flexacenter" :href="moreSchoolUrl" target="_blank"> {{ moreSchoolData.name }}<img class="icon" src="{~admissionofficer/imgV2/arrows-circle-black.svg}" /> </a>
<a class="english" :href="moreSchoolUrl" target="_blank">{{ moreSchoolData.enname }}</a>
</div>
</div>
<div class="content">
<div class="year flexacenter">
<div class="item" :class="{'pitch': item == moreSchoolPitch}" v-for="(item,index) in moreSchoolYList" @click="moreSchoolPitch = item">
<svg class="svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100px" height="28px" :style="{ fill: moreSchoolPitch == item ? (moreSchoolData?.color || '#3c7de9') : '' }">
<g transform="matrix(1 0 0 1 -910 -1138 )"><path d="M 910 1166 L 918.4 1138 L 1010 1138 L 1001.6 1166 L 910 1166 Z " fill-rule="nonzero" stroke="none" /></g>
</svg>
{{ item }}季
</div>
<div class="item more" :class="[{'unfold': moreYearState},{'pitch': moreSchoolYMList.includes(moreSchoolPitch)}]" v-if="moreSchoolYMList.length > 0" @click="openMoreYearState">
<svg class="svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100px" height="28px" :style="{ fill: moreSchoolYMList.includes(moreSchoolPitch) || moreYearState ? (moreSchoolData?.color || '#3c7de9') : '' }">
<g transform="matrix(1 0 0 1 -910 -1138 )"><path d="M 910 1166 L 918.4 1138 L 1010 1138 L 1001.6 1166 L 910 1166 Z " fill-rule="nonzero" stroke="none" /></g>
</svg>
{{ moreSchoolYMList.includes(moreSchoolPitch) ? `${ moreSchoolPitch }季` : '更多' }}
<svg class="arrows" fill="#000000" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="9px" height="5px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -1370 -1149 )"><path d="M 8.8330078125 0.164930555555556 C 8.9443359375 0.274884259259259 9 0.405092592592591 9 0.555555555555556 C 9 0.706018518518518 8.9443359375 0.836226851851851 8.8330078125 0.946180555555556 L 4.8955078125 4.83506944444444 C 4.7841796875 4.94502314814815 4.65234375 5 4.5 5 C 4.34765625 5 4.2158203125 4.94502314814815 4.1044921875 4.83506944444444 L 0.1669921875 0.946180555555556 C 0.0556640625 0.836226851851851 0 0.706018518518518 0 0.555555555555556 C 0 0.405092592592591 0.0556640625 0.274884259259259 0.1669921875 0.164930555555556 C 0.2783203125 0.0549768518518512 0.41015625 0 0.5625 0 L 8.4375 0 C 8.58984375 0 8.7216796875 0.0549768518518512 8.8330078125 0.164930555555556 Z " fill-rule="nonzero" stroke="none" transform="matrix(1 0 0 1 1370 1149 )" /></g>
</svg>
<div class="more-mask" @click.stop="moreYearState = false"></div>
<div class="more-box" :style="{ backgroundColor: moreSchoolData.color || '#3c7de9' }">
<div class="more-item" v-for="it in moreSchoolYMList" @click.stop="moreSchoolPitch = it;moreYearState = false;">{{ it }}季</div>
</div>
</div>
</div>
<div class="list">
<div class="item" v-for="item in moreSchoolList[moreSchoolPitch]">
<div class="name flexacenter">
<div class="icon flexcenter">
<img class="img" src="{~admissionofficer/imgV2/course-icon.png}" />
</div>
<div v-html="item.title"></div>
<!-- {{ item.title }} -->
</div>
<div class="bottom flexacenter">
<div class="time flexacenter">
<div class="icon flexcenter">
<img class="img" src="{~admissionofficer/imgV2/time-icon.png}" />
</div>
{{ item.date || "长期答疑" }}
</div>
<a class="btn flexcenter" :href="item.url" target="_blank">
了解详情
<img class="arrows" src="{~admissionofficer/imgV2/arrows-circle-white.svg}" />
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="paly-box-mask flexcenter" v-if="palyState">
<div class="paly-box">
<img class="close" src="admissionofficer/imgV2/cross-white.svg" @click="closePalyState" />
<div class="paly-video" id="playerContainer">
<!-- <video ref="videoPlayer" autoplay loop muted style="height: 100%; width: 100%"></video> -->
</div>
</div>
</div>
</div>
</div>
<script src="https://app.gter.net/bottom?tpl=footer,popupnotification"></script>
<!-- 引入了 vue3 js 创建 vue3 实例 -->
<script src="/js/vue.global.min.js"></script>
<script src="/js/artplayer.js"></script>
<script src="https://app.gter.net/image/gter/admissionofficer/js/hls.min.js"></script>
<script src="//unpkg.byted-static.com/xgplayer/2.31.6/browser/index.js" charset="utf-8"></script>
<script src="//unpkg.byted-static.com/xgplayer-hls/2.5.2/dist/index.min.js" charset="utf-8"></script>
<script>
const { createApp, onMounted, ref, onUnmounted, watch } = Vue;
const admissionApp = createApp({
setup() {
const isPlaying = ref(false);
const swiperRef = ref(null);
const showLeftBtn = ref(true);
const showRightBtn = ref(true);
const checkBtnVisibility = () => {
if (!swiperRef.value) return;
const { scrollLeft, scrollWidth, clientWidth } = swiperRef.value;
showLeftBtn.value = scrollLeft > 0;
showRightBtn.value = scrollLeft + clientWidth < scrollWidth;
};
const scrollLeft = () => {
if (!swiperRef.value) return;
swiperRef.value.scrollTo({
left: swiperRef.value.scrollLeft - 366,
behavior: "smooth",
});
};
const scrollRight = () => {
if (!swiperRef.value) return;
swiperRef.value.scrollTo({
left: swiperRef.value.scrollLeft + 366,
behavior: "smooth",
});
};
const theme = ref(2); // 主题 1 2
onMounted(() => {
init();
getAdmissionLists();
getRetrospectList();
const header = document.querySelector("header.page-header");
header.style.setProperty("background-color", "#3c7de9", "important");
header.style.position = "relative";
header.style.zIndex = 2;
});
let interviewData = ref({}); // 模式一 的第一个访谈
let interviewList = ref({}); // 访谈 列表
const init = () => {
fetchData("/v1/admissionsOfficer/interview").then((res) => {
if (res.code != 200) return;
let data = res.data || [];
// for (let i = 0; i < 5; i++) {
// data.push({
// title: "岭南大学保险",
// subtitle: "香港商学院硕士都去卖保险了",
// video_cover: "https://oss.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-Z0kdXrqqsgFptxhcq_cQnrlIKYkXFcXBq_D-81qNDQyOQ~~",
// sid: 350 + i,
// name: "岭南大学",
// enname: "Lingnan University",
// admission_officer_name: "施林佟",
// admission_officer_rank: "副教授",
// admission_officer_position: "数据科学学院助理院长",
// focus_of_this_issue: "寄托建立了具有公信力的品牌评价系统\n致力打造一个华人区最专业的留学交流平台\n成为一个寄托或关于梦想的垂直门户网站",
// istop: i < 1 ? 1 : 0,
// token: "D6zdTJ2cMaKd0g6nFqsa1JR5scupqIIg5Wuab33kATaY-myKGtBtHiygo4F5z8iY_Le6VEcmblBwOvlbiqvh1pyXKaET8hp8OsXbY1fFQwRKjq42OWQ3" + i,
// });
// }
// const svg = ["https://app.gter.net/image/gter/admissionofficer/imgV2/md5__034ba3bbb7a45df24b94bf3c9db05a1a.svg", "https://app.gter.net/image/gter/admissionofficer/imgV2/md5__71e35a50f46c4eba0d18cd6348c7311d.svg", "https://app.gter.net/image/gter/admissionofficer/imgV2/md5__7b0f31351c4f716f8d1c2282e889e986.svg", "https://app.gter.net/image/gter/admissionofficer/imgV2/md5__81e38e5a3645073d75888475689f68c8.svg"];
// // 将 svg 赋值给 图片
// data.forEach((element, index) => {
// element.video_cover = svg[index % svg.length];
// });
// data.forEach((element) => (element.focus_of_this_issue = element.focus_of_this_issue.split(/\r?\n/)));
// if (theme.value == 1) {
// let target = null;
// const topItems = data.filter((item) => item.istop === 1);
// if (topItems.length > 0) target = topItems[Math.floor(Math.random() * topItems.length)];
// data = data.filter((item) => item.token !== target.token);
// interviewData.value = target;
// }
data.forEach((element) => videoToken.push(element.token));
// 将 istop = 1 的拿出来
const topItems = data.filter((item) => item.istop === 1);
data = data.filter((item) => item.istop !== 1);
preachList.value = topItems;
data = data.filter((item) => item.istop !== 1);
interviewList.value = data || [];
getBannerList();
setTimeout(() => {
if (!swiperRef.value) return;
swiperRef.value.addEventListener("scroll", checkBtnVisibility);
checkBtnVisibility();
}, 400);
});
};
let palyState = ref(false); // 播放弹窗状态
watch(palyState, (newValue) => {
if (newValue) document.body.style.overflow = "hidden";
else document.body.style.overflow = "unset";
});
let art = null; // 播放器实例
let videoPlayer = ref(null);
let hls = ref(null);
let videoToken = [];
const getVideoUrl = (token) => {
// https://api.gter.net/v1/video/slice?token=QTwxtvbQlEvCrLRhmDc8SwmHy2VUsrlWZ0b8y1H9aFqDBWPTHl6mXG8N92Irk7pfXw3k8QHfAqTnCFUwVWQ0Yzk~
fetchData(`/v1/video/task?token=${token}`).then((res) => {
if (!res.data.url) {
creationAlertBox("error", "视频不存在");
return;
}
palyState.value = true;
setTimeout(() => {
const m3u8Url = res.data.url;
// 初始化播放器,集成 HLS 插件
const player = new HlsPlayer({
id: "playerContainer", // 容器 DOM ID
url: "http://183.6.121.121:8000/v/media/69b1c500a22ff943c7091d2cfcfc246b/preset.m3u8", // HLS 视频地址m3u8
// plugins: [XGPlayerHLS], // 注册 HLS 插件
width: 800, // 宽度
height: 450, // 高度
autoplay: true, // 是否自动播放(受浏览器政策限制)
controls: true, // 是否显示控制栏
poster: res.data.preview_image, // 封面图
});
// let player = new HlsPlayer({
// id: "mse",
// url: "//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/hls/xgplayer-demo.m3u8",
// autoplay: true,
// playsinline: true,
// height: window.innerHeight,
// width: window.innerWidth,
// });
// 事件监听
player.on("play", () => {
console.log("视频开始播放");
});
player.on("error", (err) => {
console.error("播放错误:", err);
});
return;
if (!art) {
console.log("res.data.url", res.data.url);
art = new Artplayer({
container: ".paly-box-mask .paly-box .paly-video",
url: res.data.url,
autoplay: true,
fullscreen: true,
poster: res.data.preview_image,
type: res.data.type,
muted: true, // 建议开启静音自动播放,减少被浏览器拦截的概率
// hevc: isHEVCSupported,
// events: {
// error: function (err) {
// console.error("视频播放错误:", err);
// // 如果是编码不支持的错误,可以给出提示
// if (err.code === 4) {
// alert("您的浏览器可能不支持 H.265 编码的视频,请尝试使用 Safari 浏览器或更新您的浏览器");
// } else {
// alert("视频加载失败,请稍后重试");
// }
// closePalyState();
// },
// },
customType: {
m3u8: playM3u8,
},
});
} else {
art.url = res.data.url;
art.poster = res.data.preview_image;
art.play();
}
// 监听播放完成事件
art.on("video:ended", () => {
const index = videoToken.indexOf(token);
if (index + 1 == videoToken.length) getVideoUrl(videoToken[0]);
else getVideoUrl(videoToken[index + 1]);
});
art.on("error", (error, reconnectTime) => {
console.info(error, reconnectTime);
});
art.on("ready", () => {
// console.info(art.hls);
});
}, 50);
});
};
const checkHEVCSupport = () => {
const video = document.querySelector(".vvideo");
return video.canPlayType('video/mp4; codecs="hev1.1.6.L93.B0"') !== "";
};
const playM3u8 = (video, url, art) => {
if (Hls.isSupported()) {
if (art.hls) art.hls.destroy();
const hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
art.hls = hls;
art.on("destroy", () => hls.destroy());
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = url;
} else {
art.notice.show = "Unsupported playback format: m3u8";
}
};
// const playM3u8 = (video, url, art) => {
// if (Hls.isSupported()) {
// if (art.hls) art.hls.destroy();
// const hls = new Hls({
// // 添加调试日志配置
// debug: true,
// // 增加超时设置
// timeout: 10000,
// maxBufferLength: 30,
// maxMaxBufferLength: 600,
// });
// // 清除超时计时器
// const clearLoadTimeout = () => {
// if (loadTimeout) {
// clearTimeout(loadTimeout);
// loadTimeout = null;
// }
// };
// // 设置15秒加载超时
// loadTimeout = setTimeout(() => {
// console.error("HLS加载超时");
// art.notice.show = "视频加载超时,请刷新重试";
// hls.destroy();
// }, 15000);
// // 监听HLS各阶段事件
// hls.on("manifestParsed", () => {
// clearLoadTimeout();
// console.log("HLS: 清单解析完成");
// });
// hls.on("levelLoaded", (event, data) => {
// console.log(`HLS: 级别加载完成 [${data.level}]`);
// console.log(`分辨率: ${data.details.width}x${data.details.height}, 带宽: ${data.details.bitrate}bps`);
// });
// hls.on("fragmentLoaded", (event, data) => {
// console.log(`HLS: 分片加载完成 [${data.level}] ${data.frag.url}`);
// console.log(`已加载大小: ${Math.round(data.stats.loaded / 1024 / 1024)}MB`);
// });
// hls.on("error", (event, data) => {
// clearLoadTimeout();
// console.error("HLS错误:", data);
// // ... 保留原错误处理逻辑 ...
// });
// // 视频元素事件监听
// video.addEventListener("loadedmetadata", () => {
// console.log("视频元数据加载完成");
// });
// video.addEventListener("canplay", () => {
// clearLoadTimeout();
// console.log("视频可播放");
// });
// hls.loadSource(url);
// hls.attachMedia(video);
// art.hls = hls;
// art.on("destroy", () => {
// clearLoadTimeout();
// hls.destroy();
// });
// } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
// video.src = url;
// } else {
// art.notice.show = "Unsupported playback format: m3u8";
// }
// };
const closePalyState = () => {
art?.pause();
art?.destroy();
art = null;
palyState.value = false;
};
let admissionList = ref([]); // 学校 招生官 列表
// 获取 院校 招生官 列表
const getAdmissionLists = () => {
fetchData(`/v1/admissionsOfficer/lists`).then((res) => {
if (res.code != 200) return;
const data = res.data;
let target = data.data || [];
target.forEach((item) => {
let year = [];
let obj = {};
item.articles.forEach((e) => {
year.push(e.year);
obj[e.year] = e.data;
});
item["pitch"] = year[0];
if (year.length > 5) {
item["yList"] = year.slice(0, 4);
item["yListMore"] = year.slice(4);
} else {
item["yList"] = year;
item["yListMore"] = [];
}
item["list"] = obj;
});
admissionList.value = target;
});
};
// 切换招生官 院校 的 年份
const cutSchoolYear = (index, year) => (admissionList.value[index]["pitch"] = year);
// 点击 招生官 年份 更多弹窗
const openSchoolYearState = (index) => (admissionList.value[index]["state"] = true);
// 选择 招生官 年份 更多弹窗
const selectSchoolYearState = (index, year) => {
admissionList.value[index]["state"] = false;
admissionList.value[index]["pitch"] = year;
};
// 关闭 招生官 年份 更多弹窗
const closeSchoolYearState = (index) => (admissionList.value[index]["state"] = false);
let retrospectCount = ref([]);
let retrospectList = ref([]);
let retrospectPage = ref(1);
let retrospectPages = ref(9);
let retrospectInterval = ref(9);
// 获取 更多回顾 数据
const getRetrospectList = () => {
fetchData("/v1/admissionsOfficer/listsPast").then((res) => {
if (res.code != 200) return;
const data = res.data || {};
let target = data.data || [];
target.forEach((item) => (item.year = item.year.sort((a, b) => b - a)));
target = target.sort((a, b) => {
const latestA = Math.max(...a.year);
const latestB = Math.max(...b.year);
return latestB - latestA;
});
const count = data.count || 0;
const pages = Math.ceil(count / retrospectInterval.value);
retrospectPages.value = pages;
retrospectCount.value = count;
retrospectList.value = target;
});
};
let moreSchoolUrl = ref(0); // 选中 学校 url
let moreSchoolSid = ref(0); // 选中 学校 sid
let moreSchoolData = ref({}); // 学校信息
let moreSchoolList = ref([]); // 年份下 列表 数据
let moreSchoolPitch = ref(0); // 选中年份
let moreSchoolYList = ref([]); // 年份 列表
let moreSchoolYMList = ref([]); // 年份 更多
const openMoreSchool = (sid, year) => {
fetchData(`/v1/admissionsOfficer/articles?sid=${sid}`).then((res) => {
if (res.code != 200) return;
const data = res.data || {};
moreSchoolData.value = data.school;
let target = data.data || [];
let obj = {};
let yearList = [];
target.forEach((element) => {
obj[element.year] = element.data;
yearList.push(element.year);
});
const isprogram = data.school.isprogram || false;
let url = `https://schools.gter.net/details/${sid}`;
if (isprogram) url = `https://program.gter.net/college/${sid}`;
moreSchoolUrl.value = url;
if (yearList.length > 5) {
moreSchoolYList.value = yearList.slice(0, 4);
moreSchoolYMList.value = yearList.slice(4);
} else {
moreSchoolYList.value = yearList;
moreSchoolYMList.value = [];
}
moreSchoolPitch.value = year || yearList[0];
moreSchoolList.value = obj;
moreSchoolSid.value = sid;
document.body.style.overflow = "hidden";
});
};
let moreYearState = ref(false);
const openMoreYearState = () => (moreYearState.value = true);
const closeMoreSchool = () => {
moreSchoolSid.value = 0;
document.body.style.overflow = "auto";
};
onUnmounted(() => {
if (swiperRef.value) swiperRef.value.removeEventListener("scroll", checkBtnVisibility);
});
let preachList = ref([]); // 宣讲会 列表
let preachIndex = ref(0); // 宣讲会 下标
let preachI = ref(0); // 宣讲会 下标
let preachTimer = null;
let preachInterval = 4; // 每页 4 条
const getBannerList = () => {
fetchData(`/v1/admissionsOfficer/banner`).then((res) => {
if (res.code != 200) return;
// for (let i = 0; i < 8; i++) {
// let obj = {
// id: 1,
// title: "昆山杜克大学 | 五大硕士项目",
// lecture_time: "2025.7.29 19:00",
// image_url: "https://oss.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-Z0rfHjqqsgFptxhT66SWgrlI64uMxcfWaHf9cJWpdxZDnzZ5RLrizQ0Mjk~",
// image_id: 977797,
// sort: 1,
// link_url: "https://bbs.gter.net/thread-2626517-1-1.html",
// status: 1,
// created_at: "2025-07-11 16:59:10",
// updated_at: "2025-07-11 17:09:33",
// };
// res.data.push(obj);
// }
const data = res.data;
data.forEach((element) => (element["lecture_time"] = timeformat(element["lecture_time"])));
preachList.value = preachList.value.concat(data);
openPreachSwiper();
});
};
const timeformat = (time) => {
time = time.replaceAll("-", "/"); // 修改格式
time = time.replaceAll(".", "/"); // 修改格式
let result = "";
var datetime = new Date(time);
var Nyear = datetime.getFullYear();
var Nmonth = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
var Ndate = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
var Nhour = datetime.getHours() < 10 ? "0" + datetime.getHours() : datetime.getHours();
var Nmin = datetime.getMinutes() < 10 ? "0" + datetime.getMinutes() : datetime.getMinutes();
result = `${Nyear}${Nmonth}${Ndate}${Nhour}:${Nmin}`;
return result;
};
const rightImgBox = ref(null);
// 宣讲会 轮播图 的 定时器
const openPreachSwiper = () => {
clearTimeout(preachTimer);
preachTimer = setTimeout(() => {
preachIndex.value += 1;
if (preachIndex.value >= preachList.value.length) preachIndex.value = 0;
openPreachSwiper();
headRolling();
}, 2000);
};
// 鼠标 移入 轮播图
const preachMouseEnter = () => clearTimeout(preachTimer);
// 鼠标 离开 轮播图
const preachMouseLeave = () => openPreachSwiper();
let leftPreach = ref(null);
let leftPreachItem = ref(null);
const cutPreach = (index) => {
preachIndex.value = index;
headRolling();
};
// 顶部 轮播图列表 和 右侧 轮播图 同步滚动
const headRolling = () => {
setTimeout(() => {
const index = preachIndex.value;
const container = leftPreach.value;
const maxScrollTop = container.scrollHeight - container.clientHeight;
// const top = leftPreachItem.value[index].getBoundingClientRect().top;
const listRect = container.getBoundingClientRect();
const itemRect = leftPreachItem.value[index].getBoundingClientRect();
// 计算需要滚动的距离(使目标元素居中偏下)
// const top = itemRect.top - listRect.top + container.scrollTop - container.clientHeight / 2 + itemRect.height / 2;
const top = itemRect.top - listRect.top + container.scrollTop - container.clientHeight / 2;
// 滚动的距离不能超过最大滚动距离
const finalScrollTop = Math.max(0, Math.min(top, maxScrollTop));
leftPreach.value.scrollTo({
top: finalScrollTop,
behavior: "smooth",
});
rightImgBox.value.scrollTo({
left: 690 * index,
behavior: "smooth",
});
}, 100);
};
const fetchData = (url, data) => {
return new Promise((resolve, reject) => {
const baseUrl = "https://api.gter.net";
if (url.indexOf("http") == -1) url = baseUrl + url;
// 构建查询字符串
const queryString = Object.keys(data || {})
.map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
.join("&");
// 将查询字符串添加到URL
if (queryString) url += (url.indexOf("?") === -1 ? "?" : "&") + queryString;
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.withCredentials = true;
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let response = xhr.response;
resolve(response);
}
};
xhr.send();
});
};
const goDetails = (sid) => {
fetchData(`/v1/admissionsOfficer/articles?sid=${sid}`).then((res) => {
if (res.code != 200) return;
const data = res.data || {};
const isprogram = data.school.isprogram || false;
let url = `https://schools.gter.net/details/${sid}`;
if (isprogram) url = `https://program.gter.net/college/${sid}`;
window.open(url, "_blank");
});
};
return { videoPlayer, leftPreach, leftPreachItem, cutPreach, moreSchoolUrl, goDetails, closePalyState, getVideoUrl, preachMouseEnter, preachMouseLeave, rightImgBox, palyState, theme, interviewList, closeSchoolYearState, selectSchoolYearState, openSchoolYearState, cutSchoolYear, admissionList, moreSchoolYMList, openMoreYearState, moreYearState, moreSchoolPitch, moreSchoolYList, moreSchoolList, moreSchoolData, moreSchoolSid, closeMoreSchool, openMoreSchool, retrospectInterval, retrospectPage, retrospectPages, retrospectList, retrospectCount, retrospectList, interviewData, preachList, preachIndex, preachI, isPlaying, scrollLeft, scrollRight, swiperRef, showLeftBtn, showRightBtn };
},
});
// 挂载到页面中的#app元素
admissionApp.mount("#admission-officer");
</script>
</body>
</html>