feat: 新增下载页

This commit is contained in:
阿梦 2026-05-18 18:12:03 +09:00
parent adce307e67
commit 42c6810705
7 changed files with 197 additions and 9 deletions

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.temp

BIN
2026_05_18_18_02.gz Normal file

Binary file not shown.

BIN
dist.zip

Binary file not shown.

View File

@ -1,11 +1,7 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import { Sparkles, ArrowRight, Play, Monitor } from 'lucide-vue-next'
import HeroIllustrationNew from '@/components/new/illustrations/HeroIllustrationNew.vue'
function scrollToSection(href: string) {
const el = document.querySelector(href)
el?.scrollIntoView({ behavior: 'smooth' })
}
</script>
<template>
@ -55,14 +51,13 @@ function scrollToSection(href: string) {
<Play class="w-4 h-4 sm:w-4.5 sm:h-4.5" />
<span>管理入口</span>
</a>
<a
href="#classroom-monitor"
<RouterLink
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"
@click.prevent="scrollToSection('#classroom-monitor')"
>
<Monitor class="w-4 h-4 sm:w-4.5 sm:h-4.5 text-cyan-600" />
<span>教师端</span>
</a>
</RouterLink>
</div>
<div class="flex items-center gap-6 pt-2">

View 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

View File

@ -8,6 +8,11 @@ const router = createRouter({
name: 'Home',
component: () => import('@/views/HomeViewNew.vue'),
},
{
path: '/teacher/download',
name: 'TeacherDownload',
component: () => import('@/views/TeacherDownloadView.vue'),
},
{
path: '/legacy',
name: 'HomeLegacy',

View 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>