update
This commit is contained in:
parent
ce92c70f4a
commit
2505a2bbfd
@ -5,7 +5,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: tradus-web:1.2.0
|
image: tradus-web:1.2.1
|
||||||
container_name: tradus-web
|
container_name: tradus-web
|
||||||
ports:
|
ports:
|
||||||
- '6000:80'
|
- '6000:80'
|
||||||
|
|||||||
@ -20,34 +20,12 @@ const router = createRouter({
|
|||||||
title: 'AI 助理团队',
|
title: 'AI 助理团队',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/tools',
|
|
||||||
name: 'tools',
|
|
||||||
component: () => import('../views/ToolsView.vue'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/ai-agent',
|
path: '/ai-agent',
|
||||||
name: 'ai-agent',
|
name: 'ai-agent',
|
||||||
component: () => import('../views/AIAgentView.vue'),
|
component: () => import('../views/AIAgentView.vue'),
|
||||||
meta: { requiresVIP: true },
|
meta: { requiresVIP: true },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/ai-feed',
|
|
||||||
name: 'ai-feed',
|
|
||||||
component: () => import('../views/AIFeedView.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/login',
|
|
||||||
name: 'login',
|
|
||||||
component: () => import('../views/LoginView.vue'),
|
|
||||||
meta: { requiresGuest: true },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/register',
|
|
||||||
name: 'register',
|
|
||||||
component: () => import('../views/RegisterView.vue'),
|
|
||||||
meta: { requiresGuest: true },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/download',
|
path: '/download',
|
||||||
name: 'download',
|
name: 'download',
|
||||||
|
|||||||
@ -1,767 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, computed } from 'vue'
|
|
||||||
import { marked } from 'marked'
|
|
||||||
import { useUserStore } from '../stores/user'
|
|
||||||
|
|
||||||
// 获取用户状态
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const isAuthenticated = computed(() => userStore.isAuthenticated)
|
|
||||||
|
|
||||||
// 根据环境选择API基础URL
|
|
||||||
const apiBaseUrl =
|
|
||||||
import.meta.env.MODE === 'development' ? 'http://127.0.0.1:8000' : 'https://api.ibtc.work'
|
|
||||||
|
|
||||||
// 定义Feed项的类型
|
|
||||||
interface FeedItem {
|
|
||||||
id: number
|
|
||||||
agent_name: string
|
|
||||||
avatar_url: string | null
|
|
||||||
content: string
|
|
||||||
create_time: string
|
|
||||||
rendered_content?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const feedItems = ref<FeedItem[]>([])
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const error = ref<string | null>(null)
|
|
||||||
const limit = ref(10)
|
|
||||||
const skip = ref(0)
|
|
||||||
const hasMore = ref(true)
|
|
||||||
|
|
||||||
// 格式化日期
|
|
||||||
const formatDate = (dateString: string) => {
|
|
||||||
const date = new Date(dateString)
|
|
||||||
return new Intl.DateTimeFormat('zh-CN', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
}).format(date)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染Markdown内容
|
|
||||||
const renderMarkdown = (content: string): string => {
|
|
||||||
return marked.parse(content) as string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载数据
|
|
||||||
const loadFeed = async () => {
|
|
||||||
if (isLoading.value || !hasMore.value || !isAuthenticated.value) return
|
|
||||||
|
|
||||||
isLoading.value = true
|
|
||||||
error.value = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
const headers: Record<string, string> = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加认证头
|
|
||||||
if (userStore.authHeader) {
|
|
||||||
headers['Authorization'] = userStore.authHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${apiBaseUrl}/feed?limit=${limit.value}&skip=${skip.value}`, {
|
|
||||||
headers,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`请求失败: ${response.status}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
hasMore.value = false
|
|
||||||
} else {
|
|
||||||
// 处理每个项目的Markdown内容
|
|
||||||
const processedData = data.map((item: FeedItem) => ({
|
|
||||||
...item,
|
|
||||||
rendered_content: renderMarkdown(item.content),
|
|
||||||
}))
|
|
||||||
|
|
||||||
feedItems.value = [...feedItems.value, ...processedData]
|
|
||||||
skip.value += data.length
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
error.value = err instanceof Error ? err.message : '加载数据时出错'
|
|
||||||
console.error('获取Feed数据失败:', err)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载更多数据
|
|
||||||
const loadMore = () => {
|
|
||||||
loadFeed()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新数据
|
|
||||||
const refreshFeed = () => {
|
|
||||||
feedItems.value = []
|
|
||||||
skip.value = 0
|
|
||||||
hasMore.value = true
|
|
||||||
loadFeed()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始加载
|
|
||||||
onMounted(() => {
|
|
||||||
if (isAuthenticated.value) {
|
|
||||||
loadFeed()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="ai-feed-view">
|
|
||||||
<!-- <h1 class="page-title">AI Agent 分析流</h1>
|
|
||||||
<p class="page-description">实时区块链市场分析和交易建议</p> -->
|
|
||||||
|
|
||||||
<!-- 未登录状态显示登录提示 -->
|
|
||||||
<div v-if="!isAuthenticated" class="login-prompt">
|
|
||||||
<div class="login-prompt-content">
|
|
||||||
<h2>需要登录</h2>
|
|
||||||
<p>请登录或注册账号以查看AI分析内容</p>
|
|
||||||
<div class="login-prompt-actions">
|
|
||||||
<router-link to="/login" class="login-button">登录</router-link>
|
|
||||||
<router-link to="/register" class="register-button">注册</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 已登录状态显示Feed内容 -->
|
|
||||||
<template v-else>
|
|
||||||
<div class="feed-actions">
|
|
||||||
<button @click="refreshFeed" class="refresh-button" :disabled="isLoading">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
class="refresh-icon"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span v-if="!isLoading">刷新</span>
|
|
||||||
<span v-else>加载中...</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="error" class="error-message">
|
|
||||||
{{ error }}
|
|
||||||
<button @click="refreshFeed" class="retry-button">重试</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feed-container">
|
|
||||||
<div v-if="feedItems.length === 0 && !isLoading" class="empty-state">暂无数据</div>
|
|
||||||
|
|
||||||
<div v-for="item in feedItems" :key="item.id" class="feed-item">
|
|
||||||
<div class="feed-header">
|
|
||||||
<div class="avatar">
|
|
||||||
<img
|
|
||||||
v-if="item.avatar_url"
|
|
||||||
:src="item.avatar_url"
|
|
||||||
:alt="item.agent_name"
|
|
||||||
class="avatar-img"
|
|
||||||
/>
|
|
||||||
<div v-else class="avatar-placeholder">
|
|
||||||
{{ item.agent_name.charAt(0) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="feed-info">
|
|
||||||
<div class="feed-info-row">
|
|
||||||
<h3 class="agent-name">{{ item.agent_name }}</h3>
|
|
||||||
<p class="timestamp">{{ formatDate(item.create_time) }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feed-content markdown-body" v-html="item.rendered_content"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="isLoading" class="loading-indicator">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<span>加载中...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="hasMore && !isLoading && feedItems.length > 0" class="load-more">
|
|
||||||
<button @click="loadMore" class="load-more-button">加载更多</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.ai-feed-view {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
max-width: 700px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-description {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-button {
|
|
||||||
background-color: #2c2c2c;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
color: #ffffff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-icon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-button:hover {
|
|
||||||
background-color: #3c3c3c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-button:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-item {
|
|
||||||
background-color: #121212;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
transition:
|
|
||||||
transform 0.2s ease,
|
|
||||||
box-shadow 0.2s ease;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-item:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1.2rem 1.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-placeholder {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #3355ff;
|
|
||||||
color: white;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-info-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-name {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-content {
|
|
||||||
padding: 1.5rem 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
background-color: rgba(255, 0, 0, 0.1);
|
|
||||||
border: 1px solid rgba(255, 0, 0, 0.3);
|
|
||||||
color: #ff4d4d;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-button {
|
|
||||||
background-color: transparent;
|
|
||||||
border: 1px solid #ff4d4d;
|
|
||||||
color: #ff4d4d;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 0.3rem 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-button:hover {
|
|
||||||
background-color: rgba(255, 77, 77, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 3rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
background-color: #121212;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-indicator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 2rem;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.1);
|
|
||||||
border-top-color: #3355ff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-button {
|
|
||||||
background-color: #2c2c2c;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
color: #ffffff;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-button:hover {
|
|
||||||
background-color: #3c3c3c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义markdown样式 */
|
|
||||||
.markdown-body {
|
|
||||||
line-height: 1.7;
|
|
||||||
color: rgba(255, 255, 255, 0.9);
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h1,
|
|
||||||
.markdown-body h2,
|
|
||||||
.markdown-body h3,
|
|
||||||
.markdown-body h4 {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: 600;
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
padding-bottom: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h1 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
color: #3355ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h2 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: #4466ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body p {
|
|
||||||
margin-bottom: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body ul,
|
|
||||||
.markdown-body ol {
|
|
||||||
padding-left: 1.8rem;
|
|
||||||
margin-bottom: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body li {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body code {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 0.2rem 0.4rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
||||||
color: #ff9955;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body pre {
|
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 1rem 0;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body pre code {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
color: #dddddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body blockquote {
|
|
||||||
border-left: 4px solid #3355ff;
|
|
||||||
padding-left: 1rem;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
font-style: italic;
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 1rem 0;
|
|
||||||
display: block;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body table th,
|
|
||||||
.markdown-body table td {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
padding: 0.5rem;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body table th {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body hr {
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body a {
|
|
||||||
color: #66aaff;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body img {
|
|
||||||
max-width: 100%;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body strong {
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body em {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.ai-feed-view {
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-actions {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-item {
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-header {
|
|
||||||
padding: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
margin-right: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-placeholder {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-info-row {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-name {
|
|
||||||
font-size: 1rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-content {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h1 {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h2 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h3 {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body pre {
|
|
||||||
padding: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body table {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body table th,
|
|
||||||
.markdown-body table td {
|
|
||||||
padding: 0.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.ai-feed-view {
|
|
||||||
padding: 0 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-header {
|
|
||||||
padding: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin-right: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-placeholder {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-info-row {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-name {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-content {
|
|
||||||
padding: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h1 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h2 {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body h3 {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body p {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body ul,
|
|
||||||
.markdown-body ol {
|
|
||||||
padding-left: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body li {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body pre {
|
|
||||||
padding: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body code {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body table {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-body table th,
|
|
||||||
.markdown-body table td {
|
|
||||||
padding: 0.3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 登录提示样式 */
|
|
||||||
.login-prompt {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 3rem 1rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-prompt-content {
|
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
max-width: 400px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-prompt-content h2 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-prompt-content p {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-prompt-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button,
|
|
||||||
.register-button {
|
|
||||||
padding: 0.7rem 1.5rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
font-weight: 600;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button {
|
|
||||||
background-color: #3355ff;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button:hover {
|
|
||||||
background-color: #2244ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-button {
|
|
||||||
background-color: transparent;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-button:hover {
|
|
||||||
background-color: var(--color-bg-elevated);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1465,6 +1465,7 @@ loadSearchHistory()
|
|||||||
|
|
||||||
.search-history-container {
|
.search-history-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 0 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-header {
|
.history-header {
|
||||||
|
|||||||
@ -1,182 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { authApi } from '@/services/api'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const email = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
const loading = ref(false)
|
|
||||||
const error = ref('')
|
|
||||||
|
|
||||||
const handleLogin = async () => {
|
|
||||||
if (!email.value || !password.value) {
|
|
||||||
error.value = '请填写邮箱和密码'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
error.value = ''
|
|
||||||
|
|
||||||
await authApi.login({
|
|
||||||
mail: email.value,
|
|
||||||
password: password.value,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 登录成功,跳转到首页
|
|
||||||
router.push('/')
|
|
||||||
} catch (err) {
|
|
||||||
error.value = err instanceof Error ? err.message : '登录失败,请重试'
|
|
||||||
console.error('登录失败:', err)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="login-view">
|
|
||||||
<div class="auth-container">
|
|
||||||
<h1 class="auth-title">登录</h1>
|
|
||||||
|
|
||||||
<form @submit.prevent="handleLogin" class="auth-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">邮箱</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
v-model="email"
|
|
||||||
placeholder="请输入邮箱"
|
|
||||||
required
|
|
||||||
autocomplete="email"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">密码</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="password"
|
|
||||||
v-model="password"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
required
|
|
||||||
autocomplete="current-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="error" class="error-message">{{ error }}</div>
|
|
||||||
|
|
||||||
<button type="submit" class="auth-button" :disabled="loading">
|
|
||||||
{{ loading ? '登录中...' : '登录' }}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="auth-links">
|
|
||||||
<router-link to="/register">没有账号?立即注册</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.login-view {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: calc(100vh - var(--header-height) - 100px);
|
|
||||||
padding: 2rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-title {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input {
|
|
||||||
padding: 0.8rem 1rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--color-bg-primary);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
font-size: 1rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #3355ff;
|
|
||||||
box-shadow: 0 0 0 2px rgba(51, 85, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: #ff4d4d;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button {
|
|
||||||
margin-top: 1rem;
|
|
||||||
padding: 0.8rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: #3355ff;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button:hover:not(:disabled) {
|
|
||||||
background-color: #2244ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button:disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-links {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-links a {
|
|
||||||
color: #3355ff;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-links a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,315 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { authApi } from '@/services/api'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const email = ref('')
|
|
||||||
const nickname = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
const confirmPassword = ref('')
|
|
||||||
const verificationCode = ref('')
|
|
||||||
const loading = ref(false)
|
|
||||||
const error = ref('')
|
|
||||||
const sendingCode = ref(false)
|
|
||||||
const codeSent = ref(false)
|
|
||||||
const countdown = ref(0)
|
|
||||||
|
|
||||||
// 发送验证码
|
|
||||||
const sendVerificationCode = async () => {
|
|
||||||
if (!email.value) {
|
|
||||||
error.value = '请填写邮箱'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
sendingCode.value = true
|
|
||||||
error.value = ''
|
|
||||||
|
|
||||||
await authApi.sendVerificationCode(email.value)
|
|
||||||
|
|
||||||
codeSent.value = true
|
|
||||||
countdown.value = 60
|
|
||||||
|
|
||||||
// 倒计时
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
countdown.value--
|
|
||||||
if (countdown.value <= 0) {
|
|
||||||
clearInterval(timer)
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
} catch (err) {
|
|
||||||
error.value = err instanceof Error ? err.message : '发送验证码失败,请重试'
|
|
||||||
console.error('发送验证码失败:', err)
|
|
||||||
} finally {
|
|
||||||
sendingCode.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册
|
|
||||||
const handleRegister = async () => {
|
|
||||||
if (!email.value || !nickname.value || !password.value || !verificationCode.value) {
|
|
||||||
error.value = '请填写所有必填项'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password.value !== confirmPassword.value) {
|
|
||||||
error.value = '两次输入的密码不一致'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
error.value = ''
|
|
||||||
|
|
||||||
await authApi.register({
|
|
||||||
mail: email.value,
|
|
||||||
nickname: nickname.value,
|
|
||||||
password: password.value,
|
|
||||||
verification_code: verificationCode.value,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 注册成功,跳转到登录页
|
|
||||||
router.push('/login')
|
|
||||||
} catch (err) {
|
|
||||||
error.value = err instanceof Error ? err.message : '注册失败,请重试'
|
|
||||||
console.error('注册失败:', err)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="register-view">
|
|
||||||
<div class="auth-container">
|
|
||||||
<h1 class="auth-title">注册</h1>
|
|
||||||
|
|
||||||
<form @submit.prevent="handleRegister" class="auth-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">邮箱</label>
|
|
||||||
<div class="input-with-button">
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
v-model="email"
|
|
||||||
placeholder="请输入邮箱"
|
|
||||||
required
|
|
||||||
autocomplete="email"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="code-button"
|
|
||||||
@click="sendVerificationCode"
|
|
||||||
:disabled="sendingCode || countdown > 0"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
countdown > 0 ? `${countdown}秒后重试` : sendingCode ? '发送中...' : '获取验证码'
|
|
||||||
}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="verification-code">验证码</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="verification-code"
|
|
||||||
v-model="verificationCode"
|
|
||||||
placeholder="请输入验证码"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="nickname">昵称</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="nickname"
|
|
||||||
v-model="nickname"
|
|
||||||
placeholder="请输入昵称"
|
|
||||||
required
|
|
||||||
autocomplete="nickname"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">密码</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="password"
|
|
||||||
v-model="password"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
required
|
|
||||||
autocomplete="new-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="confirm-password">确认密码</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="confirm-password"
|
|
||||||
v-model="confirmPassword"
|
|
||||||
placeholder="请再次输入密码"
|
|
||||||
required
|
|
||||||
autocomplete="new-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="error" class="error-message">{{ error }}</div>
|
|
||||||
|
|
||||||
<button type="submit" class="auth-button" :disabled="loading">
|
|
||||||
{{ loading ? '注册中...' : '注册' }}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="auth-links">
|
|
||||||
<router-link to="/login">已有账号?立即登录</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.register-view {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: calc(100vh - var(--header-height) - 100px);
|
|
||||||
padding: 2rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-title {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input {
|
|
||||||
padding: 0.8rem 1rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--color-bg-primary);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
font-size: 1rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #3355ff;
|
|
||||||
box-shadow: 0 0 0 2px rgba(51, 85, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-with-button {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-with-button input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-button {
|
|
||||||
padding: 0 1rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--color-bg-elevated);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-button:hover:not(:disabled) {
|
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-button:disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: #ff4d4d;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button {
|
|
||||||
margin-top: 1rem;
|
|
||||||
padding: 0.8rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: #3355ff;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button:hover:not(:disabled) {
|
|
||||||
background-color: #2244ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button:disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-links {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-links a {
|
|
||||||
color: #3355ff;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-links a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.input-with-button {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-button {
|
|
||||||
padding: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,392 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const activeTab = ref('wallet-creation')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="tools-view">
|
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">工具集合</h1>
|
|
||||||
<p class="page-description">一系列实用的加密货币工具,助您高效管理数字资产</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tools-tabs">
|
|
||||||
<button
|
|
||||||
class="tab-button"
|
|
||||||
:class="{ active: activeTab === 'wallet-creation' }"
|
|
||||||
@click="activeTab = 'wallet-creation'"
|
|
||||||
>
|
|
||||||
批量钱包创建
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="tab-button"
|
|
||||||
:class="{ active: activeTab === 'wallet-collection' }"
|
|
||||||
@click="activeTab = 'wallet-collection'"
|
|
||||||
>
|
|
||||||
钱包归集
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="tab-button"
|
|
||||||
:class="{ active: activeTab === 'other-tools' }"
|
|
||||||
@click="activeTab = 'other-tools'"
|
|
||||||
>
|
|
||||||
其他工具
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tool-content card">
|
|
||||||
<!-- 批量钱包创建工具 -->
|
|
||||||
<div v-if="activeTab === 'wallet-creation'" class="tool-panel">
|
|
||||||
<h2 class="tool-title">批量钱包创建</h2>
|
|
||||||
<p class="tool-desc">批量创建以太坊兼容的钱包地址和私钥</p>
|
|
||||||
|
|
||||||
<div class="tool-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>创建钱包数量</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="form-input"
|
|
||||||
placeholder="输入需要创建的钱包数量"
|
|
||||||
min="1"
|
|
||||||
max="100"
|
|
||||||
value="10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>钱包类型</label>
|
|
||||||
<select class="form-select">
|
|
||||||
<option value="eth">以太坊 (ETH)</option>
|
|
||||||
<option value="bsc">币安智能链 (BSC)</option>
|
|
||||||
<option value="polygon">Polygon</option>
|
|
||||||
<option value="arbitrum">Arbitrum</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>导出格式</label>
|
|
||||||
<select class="form-select">
|
|
||||||
<option value="json">JSON</option>
|
|
||||||
<option value="csv">CSV</option>
|
|
||||||
<option value="txt">TXT</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary btn-block">开始创建</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="results-preview">
|
|
||||||
<h3>预览结果</h3>
|
|
||||||
<div class="preview-content">
|
|
||||||
<p>暂无数据,请先创建钱包</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 钱包归集工具 -->
|
|
||||||
<div v-if="activeTab === 'wallet-collection'" class="tool-panel">
|
|
||||||
<h2 class="tool-title">钱包归集</h2>
|
|
||||||
<p class="tool-desc">将多个钱包中的资产归集到一个目标钱包</p>
|
|
||||||
|
|
||||||
<div class="tool-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>目标钱包地址</label>
|
|
||||||
<input type="text" class="form-input" placeholder="输入归集目标钱包地址" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>源钱包私钥列表</label>
|
|
||||||
<textarea class="form-textarea" placeholder="每行输入一个私钥..."></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>网络</label>
|
|
||||||
<select class="form-select">
|
|
||||||
<option value="eth">以太坊 (ETH)</option>
|
|
||||||
<option value="bsc">币安智能链 (BSC)</option>
|
|
||||||
<option value="polygon">Polygon</option>
|
|
||||||
<option value="arbitrum">Arbitrum</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>GAS设置</label>
|
|
||||||
<div class="gas-settings">
|
|
||||||
<input type="text" class="form-input" placeholder="Gas Price (Gwei)" />
|
|
||||||
<input type="text" class="form-input" placeholder="Gas Limit" value="21000" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary btn-block">开始归集</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="results-preview">
|
|
||||||
<h3>归集状态</h3>
|
|
||||||
<div class="preview-content">
|
|
||||||
<p>暂无数据,请先开始归集</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 其他工具 -->
|
|
||||||
<div v-if="activeTab === 'other-tools'" class="tool-panel">
|
|
||||||
<h2 class="tool-title">其他工具</h2>
|
|
||||||
<p class="tool-desc">更多实用的加密货币工具</p>
|
|
||||||
|
|
||||||
<div class="tools-grid">
|
|
||||||
<div class="tool-card card">
|
|
||||||
<h3>Token授权查询</h3>
|
|
||||||
<p>查询并撤销钱包中的Token授权</p>
|
|
||||||
<button class="btn btn-secondary">使用工具</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tool-card card">
|
|
||||||
<h3>交易解码</h3>
|
|
||||||
<p>解码链上交易数据,了解交易详情</p>
|
|
||||||
<button class="btn btn-secondary">使用工具</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tool-card card">
|
|
||||||
<h3>Gas计算器</h3>
|
|
||||||
<p>计算不同网络的Gas费用</p>
|
|
||||||
<button class="btn btn-secondary">使用工具</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tool-card card">
|
|
||||||
<h3>Merkle Tree生成器</h3>
|
|
||||||
<p>为空投活动生成Merkle Tree</p>
|
|
||||||
<button class="btn btn-secondary">使用工具</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.tools-view {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-description {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tools-tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
padding: 0.8rem 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
font-weight: 500;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button:hover {
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background-color: var(--color-bg-elevated);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button.active {
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background-color: var(--color-bg-card);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-content {
|
|
||||||
background-color: var(--color-bg-card);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-panel {
|
|
||||||
padding: 2rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-desc {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-form {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input,
|
|
||||||
.form-select,
|
|
||||||
.form-textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.8rem 1rem;
|
|
||||||
background-color: var(--color-bg-primary);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
font-size: 1rem;
|
|
||||||
transition:
|
|
||||||
border-color 0.2s ease,
|
|
||||||
box-shadow 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input:focus,
|
|
||||||
.form-select:focus,
|
|
||||||
.form-textarea:focus {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
box-shadow: 0 0 0 2px var(--color-accent-light);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-textarea {
|
|
||||||
min-height: 120px;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gas-settings {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gas-settings .form-input {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-block {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-preview {
|
|
||||||
background-color: var(--color-bg-primary);
|
|
||||||
padding: 1.5rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-preview h3 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-content {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tools-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1.5rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-card {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 280px;
|
|
||||||
background-color: var(--color-bg-primary);
|
|
||||||
padding: 1.5rem;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-card:hover {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 0 7px 14px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-card h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-card p {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
margin-bottom: 1.2rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 980px) {
|
|
||||||
.tool-card {
|
|
||||||
min-width: calc(50% - 1rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.tools-tabs {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button {
|
|
||||||
text-align: left;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-card {
|
|
||||||
min-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-panel {
|
|
||||||
padding: 1.5rem 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.page-title {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
Reference in New Issue
Block a user