Files
cpzs-frontend-new/src/App.vue
2026-01-15 18:16:50 +08:00

512 lines
11 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.

<script setup>
import { onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { userStore } from './store/user'
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
import BottomNavigation from './components/BottomNavigation.vue'
import CozeChat from './components/CozeChat.vue'
// 获取当前路由
const route = useRoute()
// 判断是否是后台管理路由
const isAdminRoute = computed(() => {
return route.path.startsWith('/admin')
})
// 处理发现按钮点击事件
const handleDiscoveryClick = () => {
// 触发全局事件让CozeChat组件处理
window.dispatchEvent(new CustomEvent('showAIAssistant'))
}
// 应用启动时获取用户信息
onMounted(async () => {
// 如果用户已登录但没有完整的用户信息,则从后端获取
if (userStore.isLoggedIn && (!userStore.user?.id || typeof userStore.user.id === 'number')) {
try {
console.log('应用启动,尝试获取用户信息...')
await userStore.fetchLoginUser()
console.log('用户信息获取成功:', userStore.user)
} catch (error) {
console.warn('获取用户信息失败,可能是后端服务不可用:', error)
// 不强制清除登录状态,让用户可以继续浏览页面
// 如果是401错误才清除登录状态
if (error.response?.status === 401) {
userStore.logout()
}
}
}
// 如果是后台路由给body添加admin-body类
if (isAdminRoute.value) {
document.body.classList.add('admin-body')
} else {
document.body.classList.remove('admin-body')
}
})
</script>
<template>
<!-- 后台管理路由直接显示内容不显示底部导航 -->
<template v-if="isAdminRoute">
<router-view />
</template>
<!-- 前台用户路由显示应用容器和底部导航 -->
<div v-else class="app-container">
<!-- 全局顶部导航栏 -->
<header class="global-header">
<div class="header-content">
<div class="app-logo">
<span class="logo-text">精彩猪手</span>
</div>
</div>
</header>
<main class="main-content">
<router-view />
</main>
<!-- 底部导航栏 -->
<BottomNavigation @discovery-click="handleDiscoveryClick" />
<!-- Coze AI 聊天助手组件 -->
<CozeChat />
</div>
</template>
<style>
/* 全局重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
background: #f0f2f5 !important;
min-height: 100vh;
width: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #f5f5f5 !important;
line-height: 1.5;
min-height: 100vh;
margin: 0 !important;
padding: 0 !important;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
/* 应用容器 - 包含主内容和底部导航 */
.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
max-width: 850px;
width: 100%;
margin: 0 auto;
background: white;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.08);
border-radius: 0;
overflow: hidden;
}
/* 全局顶部导航栏 */
.global-header {
height: 60px;
background: linear-gradient(135deg, #ff7b7b 0%, #ff6363 50%, #f85555 100%);
position: sticky;
top: 0;
z-index: 999;
box-shadow: 0 2px 12px rgba(248, 85, 85, 0.3);
position: relative;
overflow: hidden;
}
/* 装饰性波浪背景 */
.global-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 60' preserveAspectRatio='none'%3E%3Cpath d='M0,30 Q100,10 200,30 T400,30 T600,30 T800,30 L800,60 L0,60 Z' fill='rgba(255,255,255,0.08)'/%3E%3Cpath d='M0,40 Q150,20 300,40 T600,40 T800,35 L800,60 L0,60 Z' fill='rgba(255,255,255,0.05)'/%3E%3Ccircle cx='50' cy='15' r='3' fill='rgba(255,255,255,0.15)'/%3E%3Ccircle cx='150' cy='45' r='2' fill='rgba(255,255,255,0.12)'/%3E%3Ccircle cx='300' cy='12' r='2.5' fill='rgba(255,255,255,0.1)'/%3E%3Ccircle cx='500' cy='48' r='2' fill='rgba(255,255,255,0.15)'/%3E%3Ccircle cx='650' cy='18' r='3' fill='rgba(255,255,255,0.1)'/%3E%3Ccircle cx='750' cy='40' r='2' fill='rgba(255,255,255,0.12)'/%3E%3C/svg%3E");
background-size: cover;
background-position: center;
opacity: 1;
z-index: 0;
}
.header-content {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 0 20px;
max-width: 1200px;
margin: 0 auto;
position: relative;
z-index: 2;
}
.app-logo {
display: flex;
align-items: center;
justify-content: center;
transform: translateY(0);
transition: all 0.3s ease;
}
.app-logo:hover {
transform: translateY(-1px) scale(1.02);
}
.logo-text {
font-size: 22px;
font-weight: 700;
color: white;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3),
0 1px 2px rgba(0, 0, 0, 0.5);
letter-spacing: 2px;
position: relative;
display: inline-block;
}
.logo-text::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
width: 0;
height: 2px;
background: rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
transform: translateX(-50%);
}
.app-logo:hover .logo-text::after {
width: 100%;
}
/* 主要内容区域 */
.main-content {
flex: 1;
background: #f0f2f5;
position: relative;
padding-bottom: 65px;
min-height: calc(100vh - 130px);
}
/* 通用容器样式 */
.container {
width: 100%;
background: #f0f2f5;
position: relative;
}
/* 页面头部样式 */
.page-header {
background: url('@/assets/banner/banner.png') center/cover no-repeat, linear-gradient(135deg, #e53e3e 0%, #ff6b6b 100%);
color: white;
padding: 60px 20px 30px;
text-align: center;
position: relative;
overflow: hidden;
}
.page-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="20" cy="20" r="2" fill="rgba(255,255,255,0.1)"/><circle cx="80" cy="40" r="1.5" fill="rgba(255,255,255,0.1)"/><circle cx="40" cy="70" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="70" cy="80" r="2.5" fill="rgba(255,255,255,0.1)"/></svg>');
pointer-events: none;
}
.page-title {
font-size: 28px;
font-weight: 700;
margin-bottom: 8px;
position: relative;
z-index: 1;
}
.page-subtitle {
font-size: 16px;
opacity: 0.9;
position: relative;
z-index: 1;
}
/* 通用按钮样式 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 12px;
padding: 14px 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(135deg, #e53e3e, #ff6b6b);
color: white;
box-shadow: 0 4px 15px rgba(229, 62, 62, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(229, 62, 62, 0.4);
}
.btn-secondary {
background: #f8f9fa;
color: #6c757d;
border: 1px solid #e9ecef;
}
.btn-secondary:hover {
background: #e9ecef;
border-color: #d1d5db;
transform: translateY(-1px);
}
/* 球号样式 */
.ball-red {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: linear-gradient(135deg, #e53e3e, #ff6b6b);
color: white;
border-radius: 50%;
font-size: 14px;
font-weight: bold;
margin: 3px;
box-shadow: 0 2px 8px rgba(229, 62, 62, 0.3);
transition: transform 0.2s ease;
}
.ball-red:hover {
transform: scale(1.1);
}
.ball-blue {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: white;
border-radius: 50%;
font-size: 14px;
font-weight: bold;
margin: 3px;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
transition: transform 0.2s ease;
}
.ball-blue:hover {
transform: scale(1.1);
}
/* 输入框样式 */
.form-input {
width: 100%;
padding: 16px;
border: 2px solid #e9ecef;
border-radius: 12px;
font-size: 16px;
transition: all 0.3s ease;
background: #f8f9fa;
}
.form-input:focus {
outline: none;
border-color: #e53e3e;
background: white;
box-shadow: 0 0 0 3px rgba(229, 62, 62, 0.1);
}
.form-input::placeholder {
color: #9ca3af;
}
/* 卡片样式 */
.card {
background: white;
border-radius: 16px;
padding: 20px;
margin: 16px;
box-shadow: 0 4px 25px rgba(0, 0, 0, 0.1);
border: 1px solid #f0f0f0;
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 35px rgba(0, 0, 0, 0.15);
}
/* 加载动画 */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 响应式设计 */
@media (max-width: 1024px) {
.app-container {
max-width: 850px;
}
}
@media (max-width: 768px) {
body {
padding: 0 !important;
}
.app-container {
max-width: 100%;
min-height: 100vh;
border-radius: 0;
}
.global-header {
height: 55px;
}
.header-content {
padding: 0 16px;
}
.logo-text {
font-size: 20px;
}
.main-content {
padding-bottom: 55px;
min-height: calc(100vh - 120px);
}
}
@media (max-width: 480px) {
body {
padding: 0 !important;
margin: 0 !important;
}
.app-container {
border-radius: 0;
min-height: 100vh;
box-shadow: none;
}
.global-header {
height: 50px;
}
.header-content {
padding: 0 12px;
}
.logo-text {
font-size: 18px;
}
.main-content {
padding-bottom: 55px;
min-height: calc(100vh - 110px);
}
.page-header {
background: url('@/assets/banner/banner.png') center/cover no-repeat, linear-gradient(135deg, #e53e3e 0%, #ff6b6b 100%);
padding: 50px 16px 25px;
}
.page-title {
font-size: 24px;
}
.page-subtitle {
font-size: 14px;
}
.card {
margin: 12px;
padding: 16px;
}
.btn {
padding: 12px 20px;
font-size: 14px;
}
}
/* 平滑滚动 */
html {
scroll-behavior: smooth;
}
/* 选择高亮颜色 */
::selection {
background: rgba(229, 62, 62, 0.2);
color: #e53e3e;
}
</style>