Files
geekai/web/src/views/Index.vue
2025-05-26 15:56:18 +08:00

224 lines
6.2 KiB
Vue
Raw 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.

<template>
<div class="index-page">
<!-- 主题切换 -->
<ThemeChange />
<div class="menu-box">
<el-menu mode="horizontal" :ellipsis="false">
<div class="menu-item">
<img :src="logo" class="logo" alt="Geek-AI" />
</div>
<div class="menu-item">
<span v-if="!license?.de_copy">
<el-tooltip class="box-item" content="部署文档" placement="bottom">
<a :href="docsURL" class="link-button mr-3" target="_blank">
<i class="iconfont icon-book"></i>
</a>
</el-tooltip>
<el-tooltip class="box-item" content="Github 源码" placement="bottom">
<a :href="githubURL" class="link-button mr-3" target="_blank">
<i class="iconfont icon-github"></i>
</a>
</el-tooltip>
<el-tooltip class="box-item" content="Gitee 源码" placement="bottom">
<a :href="giteeURL" class="link-button" target="_blank">
<i class="iconfont icon-gitee"></i>
</a>
</el-tooltip>
</span>
<span v-if="!isLogin">
<el-button
@click="router.push('/login')"
class="btn-go animate__animated animate__pulse animate__infinite"
round
>登录/注册</el-button
>
</span>
<span v-if="isLogin">
<el-button
@click="logout"
class="btn-go animate__animated animate__pulse animate__infinite"
round
>
退出登录
</el-button>
</span>
</div>
</el-menu>
</div>
<div class="content">
<div style="height: 158px"></div>
<h1 class="animate__animated animate__backInDown">
{{ title }}
</h1>
<!-- <div class="msg-text cursor-ani">
<span
v-for="(char, index) in displayedChars"
:key="index"
:style="{ color: rainbowColor(index) }"
>
{{ char }}
</span>
</div> -->
<div class="navs animate__animated animate__backInDown">
<el-space wrap :size="14">
<div
v-for="item in navs"
:key="item.url"
class="nav-item-box"
@click="router.push(item.url)"
>
<i :class="'iconfont ' + iconMap[item.url]"></i>
<div>{{ item.name }}</div>
</div>
</el-space>
</div>
</div>
<footer-bar />
</div>
</template>
<script setup>
import FooterBar from '@/components/FooterBar.vue'
import ThemeChange from '@/components/ThemeChange.vue'
import { checkSession, getLicenseInfo, getSystemInfo } from '@/store/cache'
import { removeUserToken } from '@/store/session'
import { httpGet } from '@/utils/http'
import { isMobile } from '@/utils/libs'
import { ElMessage } from 'element-plus'
import { onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
if (isMobile()) {
router.push('/mobile/index')
}
const title = ref('')
const logo = ref('')
const slogan = ref('')
const license = ref({ de_copy: true })
const isLogin = ref(false)
const docsURL = ref(import.meta.env.VITE_DOCS_URL)
const githubURL = ref(import.meta.env.VITE_GITHUB_URL)
const giteeURL = ref(import.meta.env.VITE_GITEE_URL)
const navs = ref([])
const iconMap = ref({
'/chat': 'icon-chat',
'/mj': 'icon-mj',
'/sd': 'icon-sd',
'/dalle': 'icon-dalle',
'/images-wall': 'icon-image',
'/suno': 'icon-suno',
'/xmind': 'icon-xmind',
'/apps': 'icon-app',
'/member': 'icon-vip-user',
'/invite': 'icon-share',
'/luma': 'icon-luma',
})
const displayedChars = ref([])
const initAnimation = ref('')
let timer = null // 定时器句柄
// 初始化间隔时间和随机时间数组
const interTime = ref(50)
const interArr = [90, 100, 70, 88, 80, 110, 85, 400, 90, 99]
onMounted(() => {
getSystemInfo()
.then((res) => {
title.value = res.data.title
logo.value = res.data.logo
slogan.value = res.data.slogan
// 确保获取数据后再启动定时器
if (timer) clearInterval(timer) // 清除已有定时器
timer = setInterval(setContent, interTime.value)
})
.catch((e) => {
ElMessage.error('获取系统配置失败:' + e.message)
})
getLicenseInfo()
.then((res) => {
license.value = res.data
})
.catch((e) => {
license.value = { de_copy: false }
ElMessage.error('获取 License 配置失败:' + e.message)
})
httpGet('/api/menu/list?index=1')
.then((res) => {
navs.value = res.data
})
.catch((e) => {
ElMessage.error('获取导航菜单失败:' + e.message)
})
checkSession()
.then(() => {
isLogin.value = true
})
.catch(() => {})
})
// 组件销毁时清除定时器
onUnmounted(() => {
if (timer) {
clearInterval(timer)
timer = null
}
})
// 打字机内容逐字符显示
const setContent = () => {
if (!slogan.value) {
if (timer) clearInterval(timer)
timer = setTimeout(setContent, 100)
return
}
if (initAnimation.value.length >= slogan.value.length) {
// 文本已全部输出
initAnimation.value = ''
displayedChars.value = []
if (timer) clearInterval(timer)
timer = setInterval(setContent, interTime.value)
} else {
const nextChar = slogan.value.charAt(initAnimation.value.length)
initAnimation.value += nextChar // 逐字符追加
displayedChars.value.push(nextChar)
interTime.value = interArr[Math.floor(Math.random() * interArr.length)] // 设置随机间隔
if (timer) clearInterval(timer)
timer = setInterval(setContent, interTime.value)
}
}
// 计算彩虹色
const rainbowColor = (index) => {
const hue = (index * 40) % 360 // 每个字符间隔40度形成彩虹色
return `hsl(${hue}, 90%, 50%)` // 色调(hue),饱和度(70%),亮度(50%)
}
const logout = function () {
httpGet('/api/user/logout')
.then(() => {
removeUserToken()
router.push('/login')
})
.catch((e) => {
ElMessage.error('注销失败:' + e.message)
})
}
</script>
<style lang="stylus" scoped>
@import '../assets/css/index.styl'
</style>