feat: 新增下载页
This commit is contained in:
parent
adce307e67
commit
42c6810705
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,3 +22,5 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
.temp
|
||||||
BIN
2026_05_18_18_02.gz
Normal file
BIN
2026_05_18_18_02.gz
Normal file
Binary file not shown.
@ -1,11 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
import { Sparkles, ArrowRight, Play, Monitor } from 'lucide-vue-next'
|
import { Sparkles, ArrowRight, Play, Monitor } from 'lucide-vue-next'
|
||||||
import HeroIllustrationNew from '@/components/new/illustrations/HeroIllustrationNew.vue'
|
import HeroIllustrationNew from '@/components/new/illustrations/HeroIllustrationNew.vue'
|
||||||
|
|
||||||
function scrollToSection(href: string) {
|
|
||||||
const el = document.querySelector(href)
|
|
||||||
el?.scrollIntoView({ behavior: 'smooth' })
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -55,14 +51,13 @@ function scrollToSection(href: string) {
|
|||||||
<Play class="w-4 h-4 sm:w-4.5 sm:h-4.5" />
|
<Play class="w-4 h-4 sm:w-4.5 sm:h-4.5" />
|
||||||
<span>管理入口</span>
|
<span>管理入口</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<RouterLink
|
||||||
href="#classroom-monitor"
|
to="/teacher/download"
|
||||||
class="inline-flex items-center gap-2 px-6 sm:px-7 py-3.5 sm:py-4 text-sm sm:text-base font-semibold text-cyan-800 bg-gradient-to-r from-cyan-50 to-teal-50 border-2 border-cyan-500/25 rounded-2xl hover:border-cyan-500/45 hover:shadow-lg hover:shadow-cyan-500/10 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer group"
|
class="inline-flex items-center gap-2 px-6 sm:px-7 py-3.5 sm:py-4 text-sm sm:text-base font-semibold text-cyan-800 bg-gradient-to-r from-cyan-50 to-teal-50 border-2 border-cyan-500/25 rounded-2xl hover:border-cyan-500/45 hover:shadow-lg hover:shadow-cyan-500/10 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer group"
|
||||||
@click.prevent="scrollToSection('#classroom-monitor')"
|
|
||||||
>
|
>
|
||||||
<Monitor class="w-4 h-4 sm:w-4.5 sm:h-4.5 text-cyan-600" />
|
<Monitor class="w-4 h-4 sm:w-4.5 sm:h-4.5 text-cyan-600" />
|
||||||
<span>教师端</span>
|
<span>教师端</span>
|
||||||
</a>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-6 pt-2">
|
<div class="flex items-center gap-6 pt-2">
|
||||||
|
|||||||
8
src/constants/teacherApp.ts
Normal file
8
src/constants/teacherApp.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const TEACHER_APP_DOWNLOAD_URL =
|
||||||
|
'https://lxx-tea-1313840333.cos.ap-chengdu.myqcloud.com/%E7%81%B5%E7%8A%80%E5%AD%A6AI-1.0.0-x64-win.exe'
|
||||||
|
|
||||||
|
export const TEACHER_APP_FILE_NAME = '灵犀学AI-1.0.0-x64-win.exe'
|
||||||
|
|
||||||
|
export const TEACHER_APP_FILE_SIZE = '84.38 MB'
|
||||||
|
|
||||||
|
export const TEACHER_DOWNLOAD_COUNTDOWN_SECONDS = 3
|
||||||
@ -8,6 +8,11 @@ const router = createRouter({
|
|||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: () => import('@/views/HomeViewNew.vue'),
|
component: () => import('@/views/HomeViewNew.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/teacher/download',
|
||||||
|
name: 'TeacherDownload',
|
||||||
|
component: () => import('@/views/TeacherDownloadView.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/legacy',
|
path: '/legacy',
|
||||||
name: 'HomeLegacy',
|
name: 'HomeLegacy',
|
||||||
|
|||||||
178
src/views/TeacherDownloadView.vue
Normal file
178
src/views/TeacherDownloadView.vue
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
import { Monitor, Download, ArrowLeft, CheckCircle2, AlertCircle } from 'lucide-vue-next'
|
||||||
|
import logoImg from '@/assets/logo.png'
|
||||||
|
import {
|
||||||
|
TEACHER_APP_DOWNLOAD_URL,
|
||||||
|
TEACHER_APP_FILE_NAME,
|
||||||
|
TEACHER_APP_FILE_SIZE,
|
||||||
|
TEACHER_DOWNLOAD_COUNTDOWN_SECONDS,
|
||||||
|
} from '@/constants/teacherApp'
|
||||||
|
|
||||||
|
const secondsLeft = ref(TEACHER_DOWNLOAD_COUNTDOWN_SECONDS)
|
||||||
|
const downloadStarted = ref(false)
|
||||||
|
|
||||||
|
let timer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
const progress = computed(() => {
|
||||||
|
const total = TEACHER_DOWNLOAD_COUNTDOWN_SECONDS
|
||||||
|
return ((total - secondsLeft.value) / total) * 100
|
||||||
|
})
|
||||||
|
|
||||||
|
const countdownLabel = computed(() => String(Math.max(secondsLeft.value, 0)))
|
||||||
|
|
||||||
|
function triggerDownload() {
|
||||||
|
if (downloadStarted.value) return
|
||||||
|
downloadStarted.value = true
|
||||||
|
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = TEACHER_APP_DOWNLOAD_URL
|
||||||
|
link.target = '_blank'
|
||||||
|
link.rel = 'noopener noreferrer'
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipCountdown() {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
secondsLeft.value = 0
|
||||||
|
triggerDownload()
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCountdown() {
|
||||||
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
if (prefersReducedMotion) {
|
||||||
|
secondsLeft.value = 0
|
||||||
|
triggerDownload()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = setInterval(() => {
|
||||||
|
if (secondsLeft.value > 0) {
|
||||||
|
secondsLeft.value -= 1
|
||||||
|
}
|
||||||
|
if (secondsLeft.value <= 0) {
|
||||||
|
if (timer) clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
triggerDownload()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(startCountdown)
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer) clearInterval(timer)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="min-h-screen relative overflow-hidden flex flex-col">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-br from-bg-soft via-white to-indigo-50/80" />
|
||||||
|
<div class="absolute top-16 -left-24 w-80 h-80 rounded-full bg-primary/10 blur-3xl" />
|
||||||
|
<div class="absolute bottom-20 -right-32 w-96 h-96 rounded-full bg-cyan-500/10 blur-3xl" />
|
||||||
|
|
||||||
|
<header class="relative z-10 max-w-7xl mx-auto w-full px-4 sm:px-6 lg:px-8 pt-6">
|
||||||
|
<RouterLink
|
||||||
|
to="/"
|
||||||
|
class="inline-flex items-center gap-2 text-sm font-medium text-text-muted hover:text-primary transition-colors duration-200 cursor-pointer"
|
||||||
|
>
|
||||||
|
<ArrowLeft class="w-4 h-4" />
|
||||||
|
返回首页
|
||||||
|
</RouterLink>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="relative z-10 flex-1 flex items-center justify-center px-4 sm:px-6 py-12">
|
||||||
|
<div class="w-full max-w-lg">
|
||||||
|
<div class="glass-card-strong rounded-3xl p-8 sm:p-10 shadow-xl shadow-indigo-100/50 border border-white/60">
|
||||||
|
<div class="flex flex-col items-center text-center">
|
||||||
|
<img :src="logoImg" alt="灵犀学" class="h-10 w-auto mb-6" />
|
||||||
|
|
||||||
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-cyan-50 to-teal-50 border border-cyan-500/20 flex items-center justify-center mb-5">
|
||||||
|
<Monitor class="w-7 h-7 text-cyan-600" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="text-2xl sm:text-3xl font-bold text-text-heading tracking-tight">
|
||||||
|
灵犀学AI 教师端
|
||||||
|
</h1>
|
||||||
|
<p class="mt-2 text-text-muted text-sm sm:text-base">
|
||||||
|
{{ downloadStarted ? '下载已启动,请查看浏览器下载栏' : '即将自动开始下载,请稍候' }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="relative my-10" aria-live="polite" :aria-label="`倒计时 ${countdownLabel} 秒`">
|
||||||
|
<svg class="w-36 h-36 -rotate-90" viewBox="0 0 120 120" aria-hidden="true">
|
||||||
|
<circle cx="60" cy="60" r="52" fill="none" stroke="currentColor" stroke-width="8" class="text-indigo-100" />
|
||||||
|
<circle
|
||||||
|
cx="60"
|
||||||
|
cy="60"
|
||||||
|
r="52"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="8"
|
||||||
|
stroke-linecap="round"
|
||||||
|
class="text-primary transition-all duration-1000 ease-linear"
|
||||||
|
:stroke-dasharray="`${2 * Math.PI * 52}`"
|
||||||
|
:stroke-dashoffset="`${2 * Math.PI * 52 * (1 - progress / 100)}`"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
|
<span v-if="!downloadStarted" class="text-5xl font-extrabold tabular-nums gradient-text">
|
||||||
|
{{ countdownLabel }}
|
||||||
|
</span>
|
||||||
|
<CheckCircle2 v-else class="w-12 h-12 text-accent" />
|
||||||
|
<span class="mt-1 text-xs font-medium text-text-muted uppercase tracking-wider">
|
||||||
|
{{ downloadStarted ? '已开始' : '秒后下载' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full rounded-2xl bg-slate-50/80 border border-slate-200/80 px-4 py-3 text-left mb-6">
|
||||||
|
<p class="text-xs font-semibold text-text-muted uppercase tracking-wide mb-1">安装包信息</p>
|
||||||
|
<p class="text-sm font-semibold text-text-heading break-all">{{ TEACHER_APP_FILE_NAME }}</p>
|
||||||
|
<p class="text-xs text-text-muted mt-1">Windows 64 位 · {{ TEACHER_APP_FILE_SIZE }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row gap-3 w-full">
|
||||||
|
<button
|
||||||
|
v-if="!downloadStarted"
|
||||||
|
type="button"
|
||||||
|
class="flex-1 inline-flex items-center justify-center gap-2 px-6 py-3.5 text-sm font-semibold text-white bg-gradient-to-r from-primary to-primary-light rounded-2xl hover:shadow-lg hover:shadow-primary/25 transition-all duration-200 cursor-pointer"
|
||||||
|
@click="skipCountdown"
|
||||||
|
>
|
||||||
|
<Download class="w-4 h-4" />
|
||||||
|
立即下载
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
:href="TEACHER_APP_DOWNLOAD_URL"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="flex-1 inline-flex items-center justify-center gap-2 px-6 py-3.5 text-sm font-semibold text-white bg-gradient-to-r from-primary to-primary-light rounded-2xl hover:shadow-lg hover:shadow-primary/25 transition-all duration-200 cursor-pointer"
|
||||||
|
>
|
||||||
|
<Download class="w-4 h-4" />
|
||||||
|
重新下载
|
||||||
|
</a>
|
||||||
|
<RouterLink
|
||||||
|
to="/"
|
||||||
|
class="flex-1 inline-flex items-center justify-center gap-2 px-6 py-3.5 text-sm font-semibold text-primary bg-white border-2 border-primary/20 rounded-2xl hover:border-primary/40 hover:bg-primary/5 transition-all duration-200 cursor-pointer"
|
||||||
|
>
|
||||||
|
返回首页
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 flex items-start gap-2 text-left w-full rounded-xl bg-amber-50/80 border border-amber-200/60 px-3 py-2.5">
|
||||||
|
<AlertCircle class="w-4 h-4 text-amber-600 shrink-0 mt-0.5" />
|
||||||
|
<p class="text-xs text-amber-900/80 leading-relaxed">
|
||||||
|
若未弹出下载,请检查浏览器是否拦截弹窗,或点击「重新下载」。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Loading…
x
Reference in New Issue
Block a user