This commit is contained in:
aaron 2025-05-09 14:46:08 +08:00
parent 30502d1c3e
commit e0f8cee288
4 changed files with 353 additions and 323 deletions

View File

@ -5,7 +5,7 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
image: tradus-web:1.0.13 image: tradus-web:1.0.14
container_name: tradus-web container_name: tradus-web
ports: ports:
- '6000:80' - '6000:80'

View File

@ -7,6 +7,7 @@ const userStore = useUserStore()
const isAuthenticated = computed(() => userStore.isAuthenticated) const isAuthenticated = computed(() => userStore.isAuthenticated)
const userInfo = computed(() => userStore.userInfo) const userInfo = computed(() => userStore.userInfo)
const showMobileMenu = ref(false) const showMobileMenu = ref(false)
const showUserMenu = ref(false)
const handleLogout = () => { const handleLogout = () => {
userStore.logout() userStore.logout()
@ -14,18 +15,27 @@ const handleLogout = () => {
window.location.href = '/' window.location.href = '/'
} }
showMobileMenu.value = false showMobileMenu.value = false
showUserMenu.value = false
} }
const toggleMobileMenu = () => { const toggleMobileMenu = () => {
showMobileMenu.value = !showMobileMenu.value showMobileMenu.value = !showMobileMenu.value
} }
const toggleUserMenu = (event: Event) => {
event.stopPropagation()
showUserMenu.value = !showUserMenu.value
}
// //
const closeMenus = (event: MouseEvent) => { const closeMenus = (event: MouseEvent) => {
const target = event.target as HTMLElement const target = event.target as HTMLElement
if (!target.closest('.mobile-menu') && !target.closest('.menu-button')) { if (!target.closest('.mobile-menu') && !target.closest('.menu-button')) {
showMobileMenu.value = false showMobileMenu.value = false
} }
if (!target.closest('.user-info-box') && !target.closest('.user-menu')) {
showUserMenu.value = false
}
} }
onMounted(() => { onMounted(() => {
@ -99,13 +109,42 @@ onUnmounted(() => {
<!-- 桌面端用户信息 --> <!-- 桌面端用户信息 -->
<div class="desktop-user-info" v-if="isAuthenticated"> <div class="desktop-user-info" v-if="isAuthenticated">
<div class="user-info-box"> <div class="user-info-box" @click.stop="toggleUserMenu" :aria-expanded="showUserMenu">
<div class="user-avatar"> <div class="user-avatar">
<span>{{ userInfo?.nickname?.charAt(0) || 'U' }}</span> <span>{{ userInfo?.nickname?.charAt(0) || 'U' }}</span>
</div> </div>
<span class="user-nickname">{{ userInfo?.nickname }}</span> <span class="user-nickname">{{ userInfo?.nickname }}</span>
<svg
class="dropdown-icon"
viewBox="0 0 24 24"
width="16"
height="16"
stroke="currentColor"
stroke-width="2"
fill="none"
>
<polyline points="6 9 12 15 18 9" />
</svg>
</div>
<!-- 用户菜单 -->
<div class="user-menu" v-if="showUserMenu" @click.stop>
<button class="menu-item" @click="handleLogout">
<svg
class="menu-icon"
viewBox="0 0 24 24"
width="16"
height="16"
stroke="currentColor"
stroke-width="2"
fill="none"
>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" y1="12" x2="9" y2="12" />
</svg>
退出登录
</button>
</div> </div>
<button class="logout-button" @click="handleLogout">退出登录</button>
</div> </div>
<div class="desktop-user-info" v-else> <div class="desktop-user-info" v-else>
<RouterLink to="/login" class="auth-link">登录</RouterLink> <RouterLink to="/login" class="auth-link">登录</RouterLink>
@ -114,13 +153,42 @@ onUnmounted(() => {
<!-- 移动端用户信息 --> <!-- 移动端用户信息 -->
<div class="mobile-user-info" v-if="isAuthenticated"> <div class="mobile-user-info" v-if="isAuthenticated">
<div class="user-info-box"> <div class="user-info-box" @click.stop="toggleUserMenu" :aria-expanded="showUserMenu">
<div class="user-avatar"> <div class="user-avatar">
<span>{{ userInfo?.nickname?.charAt(0) || 'U' }}</span> <span>{{ userInfo?.nickname?.charAt(0) || 'U' }}</span>
</div> </div>
<span class="user-nickname">{{ userInfo?.nickname }}</span> <span class="user-nickname">{{ userInfo?.nickname }}</span>
<svg
class="dropdown-icon"
viewBox="0 0 24 24"
width="16"
height="16"
stroke="currentColor"
stroke-width="2"
fill="none"
>
<polyline points="6 9 12 15 18 9" />
</svg>
</div>
<!-- 用户菜单 -->
<div class="user-menu mobile" v-if="showUserMenu" @click.stop>
<button class="menu-item" @click="handleLogout">
<svg
class="menu-icon"
viewBox="0 0 24 24"
width="16"
height="16"
stroke="currentColor"
stroke-width="2"
fill="none"
>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" y1="12" x2="9" y2="12" />
</svg>
退出登录
</button>
</div> </div>
<button class="logout-button" @click="handleLogout">退出登录</button>
</div> </div>
<div class="mobile-user-info" v-else> <div class="mobile-user-info" v-else>
<RouterLink to="/login" class="mobile-auth-link" @click="showMobileMenu = false" <RouterLink to="/login" class="mobile-auth-link" @click="showMobileMenu = false"
@ -365,15 +433,15 @@ body {
/* 用户菜单样式 */ /* 用户菜单样式 */
.user-menu { .user-menu {
position: absolute; position: absolute;
bottom: 100%; bottom: calc(100% - 0.5rem);
right: 0; right: 1.25rem;
margin-bottom: 0.5rem; background-color: var(--color-bg-primary);
background-color: var(--color-bg-elevated);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--border-radius); border-radius: var(--border-radius);
min-width: 120px; min-width: 140px;
overflow: hidden; overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 1100;
} }
.menu-item { .menu-item {
@ -385,11 +453,15 @@ body {
font-size: 0.9rem; font-size: 0.9rem;
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease; transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
} }
.menu-item:hover { .menu-item:hover {
background-color: var(--color-bg-secondary); background-color: var(--color-bg-hover);
color: var(--color-accent);
} }
/* 移动端适配 */ /* 移动端适配 */
@ -547,6 +619,59 @@ body {
padding: 1rem; padding: 1rem;
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
margin-top: auto; margin-top: auto;
background-color: var(--color-bg-primary);
position: relative;
z-index: 1100;
}
.mobile-user-info .user-info-box {
display: flex;
align-items: center;
gap: 1rem;
cursor: pointer;
padding: 0.75rem;
border-radius: var(--border-radius);
transition: all 0.2s ease;
}
.mobile-user-info .user-info-box:hover {
background-color: var(--color-bg-hover);
}
.mobile-user-info .user-avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background: var(--color-bg-hover);
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
color: var(--color-accent);
font-weight: bold;
box-shadow: 0 2px 8px rgba(51, 85, 255, 0.2);
}
.mobile-user-info .user-nickname {
font-size: 1rem;
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
flex: 1;
}
/* 移动端用户菜单样式 */
.user-menu.mobile {
position: absolute;
bottom: 100%;
left: 1rem;
right: 1rem;
margin-bottom: 0.5rem;
background-color: var(--color-bg-primary);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 1100;
} }
.mobile-auth-link { .mobile-auth-link {
@ -558,32 +683,39 @@ body {
transition: all 0.2s ease; transition: all 0.2s ease;
text-align: center; text-align: center;
margin: 0.5rem 0; margin: 0.5rem 0;
background-color: var(--color-bg-secondary); background-color: var(--color-bg-hover);
} }
.mobile-auth-link:hover { .mobile-auth-link:hover {
background-color: var(--color-bg-elevated);
}
.logout-button {
display: block;
width: 100%;
padding: 0.75rem 1rem;
margin-top: 1rem;
background-color: var(--color-bg-hover);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: var(--border-radius);
color: var(--color-text-primary);
cursor: pointer;
transition: all 0.2s ease;
}
.logout-button:hover {
background-color: var(--color-bg-active); background-color: var(--color-bg-active);
border-color: var(--color-accent);
color: var(--color-accent); color: var(--color-accent);
} }
/* 箭头动画 */
.mobile-user-info .dropdown-icon {
color: var(--color-text-secondary);
transition: transform 0.2s ease;
}
.mobile-user-info .user-info-box:hover .dropdown-icon {
color: var(--color-text-primary);
}
.mobile-user-info .user-info-box[aria-expanded='true'] .dropdown-icon {
transform: rotate(180deg);
}
@media (max-width: 768px) {
.mobile-user-info {
display: block;
}
/* 移除旧的退出按钮样式 */
.logout-button {
display: none;
}
}
/* 响应式设计 */ /* 响应式设计 */
@media (max-width: 768px) { @media (max-width: 768px) {
.menu-button { .menu-button {
@ -600,8 +732,8 @@ body {
max-width: 300px; max-width: 300px;
z-index: 1002; z-index: 1002;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background-color: var(--color-bg-elevated); background-color: var(--color-bg-primary);
box-shadow: 4px 0 16px rgba(0, 0, 0, 0.1); box-shadow: none;
transform: translateX(0); transform: translateX(0);
opacity: 0; opacity: 0;
} }
@ -609,16 +741,45 @@ body {
.sidebar-open { .sidebar-open {
left: 0; left: 0;
opacity: 1; opacity: 1;
box-shadow: 6px 0 25px rgba(0, 0, 0, 0.15);
} }
.sidebar-overlay { .sidebar-overlay {
display: block; display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
z-index: 1000; z-index: 1000;
backdrop-filter: blur(2px);
opacity: 0;
transition: opacity 0.3s ease;
}
.sidebar-overlay.sidebar-open {
display: block;
opacity: 1;
}
.chat-container {
margin-left: 0;
padding-top: 4rem;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 当侧栏打开时,主内容区域右移并添加阴影效果 */
.sidebar-open + .sidebar-overlay + .chat-container {
transform: translateX(16px);
filter: brightness(0.95);
} }
.mobile-user-info { .mobile-user-info {
display: block; display: block;
animation: slideUp 0.3s ease; animation: slideUp 0.3s ease;
background-color: var(--color-bg-primary);
border-top: 1px solid rgba(0, 0, 0, 0.06);
} }
@keyframes slideUp { @keyframes slideUp {
@ -636,11 +797,6 @@ body {
display: none; display: none;
} }
.chat-container {
margin-left: 0;
padding-top: 4rem;
}
.agent-item { .agent-item {
transform: translateX(-10px); transform: translateX(-10px);
opacity: 0; opacity: 0;
@ -675,13 +831,22 @@ body {
margin-top: auto; margin-top: auto;
border-top: 1px solid rgba(0, 0, 0, 0.06); border-top: 1px solid rgba(0, 0, 0, 0.06);
background-color: var(--color-bg-secondary); background-color: var(--color-bg-secondary);
position: relative;
z-index: 1100;
} }
.desktop-user-info .user-info-box { .desktop-user-info .user-info-box {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
margin-bottom: 1rem; cursor: pointer;
padding: 0.5rem;
border-radius: var(--border-radius);
transition: all 0.2s ease;
}
.desktop-user-info .user-info-box:hover {
background-color: var(--color-bg-hover);
} }
.desktop-user-info .user-avatar { .desktop-user-info .user-avatar {
@ -703,8 +868,23 @@ body {
font-size: 1rem; font-size: 1rem;
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
color: var(--color-text-primary); color: var(--color-text-primary);
flex: 1;
} }
.dropdown-icon {
color: var(--color-text-secondary);
transition: transform 0.2s ease;
}
.user-info-box:hover .dropdown-icon {
color: var(--color-text-primary);
}
.user-info-box[aria-expanded='true'] .dropdown-icon {
transform: rotate(180deg);
}
/* 登录注册链接样式 */
.desktop-user-info .auth-link { .desktop-user-info .auth-link {
display: block; display: block;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
@ -722,25 +902,6 @@ body {
color: var(--color-accent); color: var(--color-accent);
} }
.desktop-user-info .logout-button {
display: block;
width: 100%;
padding: 0.75rem 1rem;
background-color: var(--color-bg-hover);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: var(--border-radius);
color: var(--color-text-primary);
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.95rem;
}
.desktop-user-info .logout-button:hover {
background-color: var(--color-bg-active);
border-color: var(--color-accent);
color: var(--color-accent);
}
/* 移动端适配 */ /* 移动端适配 */
@media (max-width: 768px) { @media (max-width: 768px) {
.desktop-user-info { .desktop-user-info {

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, nextTick, computed, onMounted, watch, onUnmounted } from 'vue' import { ref, nextTick, computed, onMounted, watch } from 'vue'
import { useUserStore } from '../stores/user' import { useUserStore } from '../stores/user'
import { marked } from 'marked' import { marked } from 'marked'
@ -71,10 +71,8 @@ const fetchAgents = async () => {
const data = await response.json() const data = await response.json()
agents.value = data.map((agent: AgentResponse) => ({ agents.value = data.map((agent: AgentResponse) => ({
...agent, ...agent,
icon: '📊', // agent
})) }))
// Agent
if (agents.value.length > 0 && !selectedAgent.value) { if (agents.value.length > 0 && !selectedAgent.value) {
selectedAgent.value = agents.value[0] selectedAgent.value = agents.value[0]
addInitialGreeting(agents.value[0]) addInitialGreeting(agents.value[0])
@ -305,38 +303,30 @@ const sendMessage = async () => {
} }
} }
//
const showAgentMenu = ref(false)
//
const toggleAgentMenu = () => {
showAgentMenu.value = !showAgentMenu.value
}
// Agent // Agent
const selectAgent = (agent: Agent) => { const selectAgent = (agent: Agent) => {
selectedAgent.value = agent selectedAgent.value = agent
showAgentMenu.value = false
} }
// // script
const closeAgentMenu = (event: MouseEvent) => { const getAgentIcon = (agent: Agent) => {
const target = event.target as HTMLElement // agent SVG
if (!target.closest('.agent-selector')) { return `<svg class="agent-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
showAgentMenu.value = false ${getIconPath(agent)}
} </svg>`
} }
onMounted(() => { const getIconPath = (agent: Agent) => {
if (isAuthenticated.value) { // agent id
fetchAgents() switch (agent.id) {
case 'market_analysis':
return '<path d="M7 12l5-5 5 5M7 17l5-5 5 5"/>'
case 'trading_strategy':
return '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1zM4 22v-7"/>'
default:
return '<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line"/>'
} }
document.addEventListener('click', closeAgentMenu) }
})
onUnmounted(() => {
document.removeEventListener('click', closeAgentMenu)
})
</script> </script>
<template> <template>
@ -379,6 +369,25 @@ onUnmounted(() => {
</div> </div>
</div> </div>
<!-- Agent 选择器移到这里 -->
<div class="agent-selector">
<div class="agent-list">
<div v-if="isLoadingAgents" class="agent-loading">加载中...</div>
<template v-else>
<button
v-for="agent in agents"
:key="agent.id"
class="agent-item"
:class="{ active: selectedAgent?.id === agent.id }"
@click="selectAgent(agent)"
>
<span class="agent-icon" v-html="getAgentIcon(agent)"></span>
<span class="agent-name">{{ agent.name }}</span>
</button>
</template>
</div>
</div>
<div class="input-container"> <div class="input-container">
<div class="input-wrapper"> <div class="input-wrapper">
<textarea <textarea
@ -399,32 +408,6 @@ onUnmounted(() => {
</div> </div>
</div> </div>
</div> </div>
<!-- 添加新的 Agent 选择器 -->
<div class="agent-selector">
<button class="agent-selector-button" @click="toggleAgentMenu">
<span class="agent-icon">{{ selectedAgent?.icon || '🤖' }}</span>
<span class="agent-name">{{ selectedAgent?.name || '选择 Agent' }}</span>
</button>
<div v-if="showAgentMenu" class="agent-selector-menu">
<div v-if="isLoadingAgents" class="agent-loading">加载中...</div>
<template v-else>
<div
v-for="agent in agents"
:key="agent.id"
class="agent-menu-item"
:class="{ active: selectedAgent?.id === agent.id }"
@click="selectAgent(agent)"
>
<div class="agent-icon">{{ agent.icon }}</div>
<div class="agent-info">
<div class="agent-name">{{ agent.name }}</div>
<div class="agent-description">{{ agent.description }}</div>
</div>
</div>
</template>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -684,7 +667,7 @@ onUnmounted(() => {
} }
.input-container { .input-container {
padding: 1.5rem 2rem; padding: 0.5rem 0.5rem 5.5rem 0.5rem;
background-color: var(--color-bg-primary); background-color: var(--color-bg-primary);
width: 100%; width: 100%;
max-width: 900px; max-width: 900px;
@ -732,7 +715,7 @@ onUnmounted(() => {
.send-button { .send-button {
position: absolute; position: absolute;
right: 0.75rem; right: 0.75rem;
bottom: 0.75rem; bottom: 0.5rem;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
display: flex; display: flex;
@ -819,217 +802,92 @@ onUnmounted(() => {
text-decoration: underline; text-decoration: underline;
} }
/* 添加新的 Agent 选择器样式 */ /* 修改 Agent 选择器样式 */
.agent-selector { .agent-selector {
position: absolute; /* padding: 1rem 2rem; */
top: 1rem; background-color: #ffffff;
right: 1rem; max-width: 900px;
z-index: 10; margin: 0 auto;
width: 100%;
} }
.agent-selector-button { .agent-list {
display: flex; display: flex;
flex-direction: row;
gap: 0.75rem;
padding: 0.75rem;
overflow-x: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
.agent-list::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
.agent-item {
display: inline-flex;
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
padding: 0.75rem 1.25rem; padding: 0.75rem 1rem;
background-color: #ffffff; background-color: var(--color-bg-secondary);
border: 1px solid rgba(51, 85, 255, 0.2); border: 1px solid rgba(51, 85, 255, 0.15);
border-radius: 12px; border-radius: 8px;
color: var(--color-text-primary); color: var(--color-text-primary);
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
white-space: nowrap;
font-size: 0.95rem;
font-weight: 500; font-weight: 500;
box-shadow: 0 2px 8px rgba(51, 85, 255, 0.08); min-width: 120px;
min-width: 160px; width: fit-content;
justify-content: flex-start;
flex-shrink: 0;
} }
.agent-selector-button:hover { .agent-item:hover {
border-color: var(--color-accent); border-color: var(--color-accent);
background-color: rgba(51, 85, 255, 0.04); background-color: rgba(51, 85, 255, 0.04);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(51, 85, 255, 0.12);
} }
.agent-selector-button .agent-icon { .agent-item.active {
font-size: 1.25rem; background-color: var(--color-accent);
color: var(--color-accent); border-color: var(--color-accent);
color: #ffffff;
} }
.agent-selector-button .agent-name { .agent-icon {
font-size: 0.95rem; width: 20px;
color: var(--color-text-primary); height: 20px;
flex: 1; flex-shrink: 0;
stroke: currentColor;
} }
.agent-selector-button::after { .agent-item.active .agent-icon {
content: '▼'; stroke: #ffffff;
font-size: 0.8rem;
color: var(--color-accent);
opacity: 0.8;
transition: transform 0.2s ease;
} }
.agent-selector-menu { .agent-name {
position: absolute; text-align: left;
top: calc(100% + 0.5rem);
right: 0;
background-color: #ffffff;
border: 1px solid rgba(51, 85, 255, 0.15);
border-radius: 12px;
min-width: 280px;
max-width: 320px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(51, 85, 255, 0.15);
animation: slideDown 0.2s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.agent-menu-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem;
cursor: pointer;
transition: all 0.2s ease;
border-bottom: 1px solid rgba(51, 85, 255, 0.08);
background-color: #ffffff;
}
.agent-menu-item:last-child {
border-bottom: none;
}
.agent-menu-item:hover {
background-color: rgba(51, 85, 255, 0.04);
}
.agent-menu-item.active {
background-color: rgba(51, 85, 255, 0.08);
}
.agent-menu-item .agent-icon {
font-size: 1.5rem;
color: var(--color-accent);
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(51, 85, 255, 0.08);
border-radius: 8px;
}
.agent-menu-item .agent-info {
flex: 1;
min-width: 0;
}
.agent-menu-item .agent-name {
font-weight: 600;
color: var(--color-text-primary);
margin-bottom: 0.25rem;
font-size: 0.95rem;
}
.agent-menu-item .agent-description {
font-size: 0.85rem;
color: var(--color-text-secondary);
line-height: 1.4;
} }
.agent-loading { .agent-loading {
padding: 1rem; padding: 0.75rem;
text-align: center; text-align: center;
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
/* 移动端适配 */ /* 移动端适配 */
@media (max-width: 768px) { @media (max-width: 768px) {
.ai-agent-view { .agent-list {
height: 100%; padding: 0.5rem;
gap: 0.5rem;
} }
.main-container { .agent-item {
height: 100%;
padding-bottom: env(safe-area-inset-bottom);
}
.messages-container {
padding: 1rem 0.5rem;
padding-bottom: calc(5rem + env(safe-area-inset-bottom));
}
.input-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 0.75rem;
padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));
background-color: var(--color-bg-elevated);
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 100;
border-top: 1px solid var(--color-border);
}
.input-wrapper {
max-width: 900px;
margin: 0 auto;
}
.message-input {
font-size: 16px; /* 防止 iOS 自动缩放 */
padding: 0.6rem 2.5rem 0.6rem 0.75rem;
min-height: 2.5rem;
height: 2.5rem;
}
.send-button {
width: 1.8rem;
height: 1.8rem;
right: 0.5rem;
bottom: 0.35rem;
}
.send-icon {
font-size: 0.9rem;
}
.agent-selector {
top: 0.75rem;
right: 0.75rem;
}
.agent-selector-button {
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
min-width: 140px;
font-size: 0.9rem; font-size: 0.9rem;
} min-width: 100px;
.agent-selector-menu {
min-width: 260px;
max-width: calc(100vw - 2rem);
right: -0.75rem;
}
.agent-menu-item {
padding: 0.875rem;
}
.agent-menu-item .agent-icon {
font-size: 1.25rem;
width: 28px;
height: 28px;
} }
} }

View File

@ -11,8 +11,8 @@ const isAuthenticated = computed(() => userStore.isAuthenticated)
<div class="home-view"> <div class="home-view">
<section class="hero-section"> <section class="hero-section">
<div class="hero-content"> <div class="hero-content">
<h1 class="hero-title"><span class="accent">Tradus</span></h1> <h1 class="hero-title"><span class="accent">tradus</span></h1>
<p class="hero-subtitle">AI Agent for trading</p> <p class="hero-subtitle">A intelligent trading assistant</p>
<div class="hero-actions"> <div class="hero-actions">
<!-- 未登录状态显示登录和注册按钮 --> <!-- 未登录状态显示登录和注册按钮 -->
<template v-if="!isAuthenticated"> <template v-if="!isAuthenticated">
@ -21,7 +21,7 @@ const isAuthenticated = computed(() => userStore.isAuthenticated)
</template> </template>
</div> </div>
<div class="discord-link"> <!-- <div class="discord-link">
<a href="https://discord.gg/8vMDD4kC" target="_blank" class="discord-text"> <a href="https://discord.gg/8vMDD4kC" target="_blank" class="discord-text">
<svg class="discord-icon" viewBox="0 0 24 24" width="16" height="16"> <svg class="discord-icon" viewBox="0 0 24 24" width="16" height="16">
<path <path
@ -31,7 +31,7 @@ const isAuthenticated = computed(() => userStore.isAuthenticated)
</svg> </svg>
加入Discord社区 加入Discord社区
</a> </a>
</div> </div> -->
</div> </div>
</section> </section>
</div> </div>
@ -72,40 +72,51 @@ const isAuthenticated = computed(() => userStore.isAuthenticated)
} }
.hero-title { .hero-title {
font-size: 5rem; font-size: 1.8rem;
font-weight: 800; font-weight: 800;
margin-bottom: 1.5rem; margin-bottom: 0.3rem;
line-height: 1.1; font-family:
letter-spacing: -0.02em; 'SF Pro Display',
color: #3355ff; -apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
sans-serif;
letter-spacing: -0.5px;
display: block;
} }
.accent { .accent {
position: relative; font-size: 2.5rem;
display: inline-block; font-weight: 800;
} background: linear-gradient(135deg, var(--color-accent) 0%, #2244ee 100%);
-webkit-background-clip: text;
.accent::after { -webkit-text-fill-color: transparent;
content: ''; background-clip: text;
position: absolute; text-fill-color: transparent;
bottom: 8px; font-family:
left: 0; 'SF Pro Display',
width: 100%; -apple-system,
height: 12px; BlinkMacSystemFont,
background-color: rgba(51, 85, 255, 0.1); 'Segoe UI',
z-index: -1; Roboto,
border-radius: 6px; sans-serif;
opacity: 0.5;
transform: skewX(-12deg);
} }
.hero-subtitle { .hero-subtitle {
font-size: 1.5rem; font-size: 0.9rem;
color: #6c757d; color: var(--color-text-secondary);
max-width: 700px;
margin: 0 auto 4rem;
letter-spacing: -0.01em;
font-weight: 400; font-weight: 400;
font-family:
'SF Pro Display',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
sans-serif;
letter-spacing: 0.5px;
text-transform: uppercase;
margin: 0 auto 4rem;
} }
.hero-actions { .hero-actions {
@ -193,7 +204,7 @@ const isAuthenticated = computed(() => userStore.isAuthenticated)
} }
.hero-title { .hero-title {
font-size: 3.5rem; font-size: 1.8rem;
} }
.hero-subtitle { .hero-subtitle {
@ -214,7 +225,7 @@ const isAuthenticated = computed(() => userStore.isAuthenticated)
} }
.hero-title { .hero-title {
font-size: 2.75rem; font-size: 1.5rem;
} }
.hero-subtitle { .hero-subtitle {