1164 lines
25 KiB
Vue
1164 lines
25 KiB
Vue
<script setup lang="ts">
|
||
import { ref, onMounted, computed, nextTick, onUnmounted } from 'vue'
|
||
import { http } from '../services/api'
|
||
import { useUserStore } from '../stores/user'
|
||
import { marked } from 'marked'
|
||
|
||
// 根据环境选择API基础URL
|
||
const apiBaseUrl =
|
||
import.meta.env.MODE === 'development' ? 'http://127.0.0.1:8000' : 'https://api.ibtc.work'
|
||
|
||
const userStore = useUserStore()
|
||
const isAuthenticated = computed(() => userStore.isAuthenticated)
|
||
|
||
// 分析历史记录列表
|
||
const historyList = ref<
|
||
Array<{
|
||
id: string
|
||
user_id: string
|
||
type: string
|
||
symbol: string
|
||
timeframe?: string
|
||
content: string
|
||
create_time: string
|
||
}>
|
||
>([])
|
||
|
||
// 分页参数
|
||
const limit = ref(10)
|
||
const totalRecords = ref(0)
|
||
const isLoading = ref(false)
|
||
const currentPage = ref(1)
|
||
const hasMoreData = ref(true)
|
||
|
||
// 滚动容器引用
|
||
const historyContentRef = ref<HTMLElement | null>(null)
|
||
|
||
// 计算当前偏移量
|
||
const currentOffset = computed(() => (currentPage.value - 1) * limit.value)
|
||
|
||
// 获取类型显示名称
|
||
const getTypeName = (type: string) => {
|
||
return type === 'astock' ? 'A股分析' : type === 'crypto' ? '加密货币分析' : type
|
||
}
|
||
|
||
// 格式化日期时间
|
||
const formatDateTime = (dateString: string) => {
|
||
const date = new Date(dateString)
|
||
return date.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
})
|
||
}
|
||
|
||
// 获取内容摘要
|
||
const getContentSummary = (content: string, maxLength: number = 100) => {
|
||
// 移除Markdown标记的标题和其他格式
|
||
const cleanedContent = content
|
||
.replace(/^#+\s+.*$/gm, '') // 移除标题行
|
||
.replace(/\*\*(.*?)\*\*/g, '$1') // 移除粗体
|
||
.replace(/\*(.*?)\*/g, '$1') // 移除斜体
|
||
.replace(/\[(.*?)\]\(.*?\)/g, '$1') // 移除链接,只保留文本
|
||
.replace(/^\s*[-*+]\s+/gm, '') // 移除列表标记
|
||
.replace(/^\s*>\s+/gm, '') // 移除引用标记
|
||
.replace(/---/g, '') // 移除分隔线
|
||
.trim()
|
||
|
||
// 获取第一段非空文本
|
||
const firstParagraph =
|
||
cleanedContent
|
||
.split('\n')
|
||
.filter((line) => line.trim())
|
||
.shift() || ''
|
||
|
||
// 截取适当长度的摘要
|
||
if (firstParagraph.length <= maxLength) {
|
||
return firstParagraph
|
||
}
|
||
|
||
// 截取并确保不会截断单词
|
||
const summary = firstParagraph.substring(0, maxLength)
|
||
const lastSpaceIndex = summary.lastIndexOf(' ')
|
||
|
||
return summary.substring(0, lastSpaceIndex > 0 ? lastSpaceIndex : maxLength) + '...'
|
||
}
|
||
|
||
// 滚动监听函数
|
||
const handleScroll = () => {
|
||
if (!historyContentRef.value || isLoading.value || !hasMoreData.value) return
|
||
|
||
const { scrollTop, scrollHeight, clientHeight } = historyContentRef.value
|
||
// 当滚动到距离底部50px以内时触发加载
|
||
if (scrollTop + clientHeight >= scrollHeight - 50) {
|
||
loadMore()
|
||
}
|
||
}
|
||
|
||
// 加载更多数据
|
||
const loadMore = () => {
|
||
if (isLoading.value || !hasMoreData.value) return
|
||
currentPage.value++
|
||
loadHistoryData(true)
|
||
}
|
||
|
||
// 加载分析历史数据
|
||
const loadHistoryData = async (append = false) => {
|
||
if (!isAuthenticated.value) return
|
||
|
||
try {
|
||
isLoading.value = true
|
||
|
||
const response = await http.get(
|
||
`${apiBaseUrl}/analysis/analysis_histories?limit=${limit.value}&offset=${currentOffset.value}`,
|
||
)
|
||
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
// 服务器直接返回数组格式
|
||
const newData = Array.isArray(data) ? data : []
|
||
|
||
if (append && currentPage.value > 1) {
|
||
// 追加模式:将新数据添加到现有列表
|
||
historyList.value = [...historyList.value, ...newData]
|
||
} else {
|
||
// 替换模式:直接替换列表(第一页或刷新)
|
||
historyList.value = newData
|
||
}
|
||
|
||
// 如果返回的数据量小于limit,说明已经没有更多数据了
|
||
hasMoreData.value = newData.length === limit.value
|
||
|
||
// 根据当前页和数据量估算总记录数
|
||
if (newData.length < limit.value) {
|
||
// 如果返回的数据少于limit,说明这是最后一页
|
||
totalRecords.value = currentOffset.value + newData.length
|
||
} else if (currentPage.value === 1) {
|
||
// 如果是第一页且数据量等于limit,暂时将总记录数设为比当前已知的多一页
|
||
totalRecords.value = Math.max(totalRecords.value, limit.value * 2)
|
||
}
|
||
|
||
// 如果没有更多数据且不是第一页,更新总记录数
|
||
if (!hasMoreData.value && currentPage.value > 1) {
|
||
totalRecords.value = currentOffset.value + newData.length
|
||
}
|
||
} else {
|
||
console.error('获取分析历史失败:', response.status)
|
||
}
|
||
} catch (error) {
|
||
console.error('获取分析历史异常:', error)
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 查看详情
|
||
const selectedHistory = ref<(typeof historyList.value)[0] | null>(null)
|
||
const showDetail = ref(false)
|
||
|
||
const viewHistoryDetail = (history: (typeof historyList.value)[0]) => {
|
||
selectedHistory.value = history
|
||
showDetail.value = true
|
||
}
|
||
|
||
const closeDetail = () => {
|
||
showDetail.value = false
|
||
selectedHistory.value = null
|
||
}
|
||
|
||
// 解析markdown内容
|
||
const parsedContent = computed(() => {
|
||
if (!selectedHistory.value?.content) {
|
||
return ''
|
||
}
|
||
|
||
// 处理markdown内容
|
||
let html = marked(selectedHistory.value.content) as string
|
||
|
||
// 将表格包装在div中以提供更好的滚动支持
|
||
html = html.replace(/<table>/g, '<div class="table-container"><table>')
|
||
html = html.replace(/<\/table>/g, '</table></div>')
|
||
|
||
return html
|
||
})
|
||
|
||
// 在组件挂载时加载数据
|
||
onMounted(() => {
|
||
// 配置marked选项
|
||
marked.setOptions({
|
||
breaks: true,
|
||
gfm: true, // GitHub风格Markdown,支持表格等扩展语法
|
||
})
|
||
|
||
// 重置状态
|
||
currentPage.value = 1
|
||
hasMoreData.value = true
|
||
historyList.value = []
|
||
|
||
loadHistoryData()
|
||
|
||
// 添加滚动监听
|
||
nextTick(() => {
|
||
if (historyContentRef.value) {
|
||
historyContentRef.value.addEventListener('scroll', handleScroll)
|
||
}
|
||
})
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
// 移除滚动监听
|
||
if (historyContentRef.value) {
|
||
historyContentRef.value.removeEventListener('scroll', handleScroll)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="analysis-history-view">
|
||
<div class="content-container">
|
||
<div class="header-section">
|
||
<h1 class="title">分析历史记录</h1>
|
||
<p class="description">您可以在这里查看您所有的AI分析结果</p>
|
||
</div>
|
||
|
||
<div v-if="!isAuthenticated" class="login-prompt">
|
||
<p>请先登录后查看分析历史记录</p>
|
||
</div>
|
||
|
||
<div v-else class="history-content" ref="historyContentRef">
|
||
<div v-if="isLoading && currentPage === 1" class="loading-container">
|
||
<div class="loading-spinner"></div>
|
||
<p>加载中...</p>
|
||
</div>
|
||
|
||
<div v-else-if="historyList.length === 0" class="empty-state">
|
||
<p>暂无分析历史记录</p>
|
||
</div>
|
||
|
||
<div v-else class="history-list">
|
||
<div
|
||
v-for="item in historyList"
|
||
:key="item.id"
|
||
class="history-item"
|
||
@click="viewHistoryDetail(item)"
|
||
>
|
||
<div class="history-header">
|
||
<div class="history-main-info">
|
||
<div class="history-symbol">
|
||
{{ item.symbol }}
|
||
<span v-if="item.timeframe" class="symbol-timeframe">{{ item.timeframe }}</span>
|
||
</div>
|
||
<div
|
||
class="history-type-badge"
|
||
:class="{ crypto: item.type === 'crypto', astock: item.type === 'astock' }"
|
||
>
|
||
{{ getTypeName(item.type) }}
|
||
</div>
|
||
</div>
|
||
<div class="history-time">
|
||
<svg
|
||
class="time-icon"
|
||
viewBox="0 0 24 24"
|
||
width="14"
|
||
height="14"
|
||
stroke="currentColor"
|
||
stroke-width="2"
|
||
fill="none"
|
||
>
|
||
<circle cx="12" cy="12" r="10"></circle>
|
||
<polyline points="12 6 12 12 16 14"></polyline>
|
||
</svg>
|
||
{{ formatDateTime(item.create_time) }}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="history-summary">
|
||
{{ getContentSummary(item.content) }}
|
||
</div>
|
||
|
||
<div class="history-footer">
|
||
<div class="view-detail-hint">
|
||
<svg
|
||
class="arrow-icon"
|
||
viewBox="0 0 24 24"
|
||
width="16"
|
||
height="16"
|
||
stroke="currentColor"
|
||
stroke-width="2"
|
||
fill="none"
|
||
>
|
||
<path d="M9 18l6-6-6-6"></path>
|
||
</svg>
|
||
点击查看详情
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 底部加载状态 -->
|
||
<div v-if="isLoading && currentPage > 1" class="loading-more">
|
||
<div class="loading-spinner-small"></div>
|
||
<span>加载更多中...</span>
|
||
</div>
|
||
|
||
<!-- 没有更多数据提示 -->
|
||
<div v-else-if="!hasMoreData && historyList.length > 0" class="no-more-data">
|
||
<span>已加载全部数据</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 详情弹窗 -->
|
||
<Transition name="modal" appear>
|
||
<div v-if="showDetail && selectedHistory" class="detail-modal" @click="closeDetail">
|
||
<div class="detail-content" @click.stop>
|
||
<div class="detail-header">
|
||
<h2>分析详情</h2>
|
||
<button class="close-btn" @click="closeDetail">×</button>
|
||
</div>
|
||
|
||
<div class="detail-info">
|
||
<div class="info-row">
|
||
<span class="info-label">标的代码:</span>
|
||
<span class="info-value">{{ selectedHistory.symbol }}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">分析类型:</span>
|
||
<span class="info-value">{{ getTypeName(selectedHistory.type) }}</span>
|
||
</div>
|
||
<div v-if="selectedHistory.timeframe" class="info-row">
|
||
<span class="info-label">时间周期:</span>
|
||
<span class="info-value">{{ selectedHistory.timeframe }}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">分析时间:</span>
|
||
<span class="info-value">{{ formatDateTime(selectedHistory.create_time) }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-content-wrapper">
|
||
<div class="analysis-content markdown-content" v-html="parsedContent"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.analysis-history-view {
|
||
min-height: 100vh;
|
||
background-color: var(--color-bg-primary);
|
||
padding: 1rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden; /* 防止整个页面滚动,改为内部容器滚动 */
|
||
}
|
||
|
||
.content-container {
|
||
max-width: 900px;
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
padding: 2rem 1rem;
|
||
height: 100%; /* 高度100%确保内容填充可用空间 */
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.header-section {
|
||
text-align: center;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.title {
|
||
font-size: 2rem;
|
||
font-weight: 700;
|
||
color: var(--color-text-primary);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.description {
|
||
font-size: 0.95rem;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.login-prompt {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
background-color: var(--color-bg-secondary);
|
||
border-radius: var(--border-radius);
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.history-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
flex: 1;
|
||
overflow-y: auto; /* 启用内容区域滚动 */
|
||
max-height: calc(100vh); /* 限制最大高度,确保可滚动 */
|
||
}
|
||
|
||
.loading-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 2rem;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 3px solid var(--color-border);
|
||
border-top-color: var(--color-accent);
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
background-color: var(--color-bg-secondary);
|
||
border-radius: var(--border-radius);
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.history-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.75rem;
|
||
overflow-y: visible; /* 确保列表项可见 */
|
||
padding-bottom: 2rem; /* 底部留出空间,避免最后一项被遮挡 */
|
||
}
|
||
|
||
.history-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 1.25rem;
|
||
background-color: var(--color-bg-secondary);
|
||
border-radius: var(--border-radius);
|
||
transition: all 0.3s ease;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
gap: 1rem;
|
||
border-left: 3px solid transparent;
|
||
border: 1px solid var(--color-border);
|
||
}
|
||
|
||
.history-item:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||
background-color: var(--color-bg-hover);
|
||
border-left-color: var(--color-accent);
|
||
border-color: var(--color-accent);
|
||
}
|
||
|
||
.history-item:hover .view-detail-hint {
|
||
color: var(--color-accent);
|
||
}
|
||
|
||
.history-item:hover .arrow-icon {
|
||
transform: translateX(2px);
|
||
}
|
||
|
||
.history-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.history-main-info {
|
||
display: flex;
|
||
gap: 1rem;
|
||
align-items: center;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.history-symbol {
|
||
font-weight: 600;
|
||
font-size: 1.2rem;
|
||
color: var(--color-text-primary);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
flex-wrap: nowrap;
|
||
min-width: 0;
|
||
}
|
||
|
||
.symbol-timeframe {
|
||
font-size: 0.85rem;
|
||
font-weight: 500;
|
||
color: var(--color-accent);
|
||
background-color: rgba(51, 85, 255, 0.1);
|
||
padding: 0.2rem 0.5rem;
|
||
border-radius: 4px;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-type-badge {
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
padding: 0.3rem 0.7rem;
|
||
border-radius: 20px;
|
||
background-color: rgba(51, 85, 255, 0.1);
|
||
color: var(--color-accent);
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||
border: 1px solid rgba(51, 85, 255, 0.2);
|
||
}
|
||
|
||
.history-type-badge.crypto {
|
||
background-color: rgba(255, 152, 0, 0.1);
|
||
color: #ff9800;
|
||
border-color: rgba(255, 152, 0, 0.2);
|
||
}
|
||
|
||
.history-type-badge.astock {
|
||
background-color: rgba(76, 175, 80, 0.1);
|
||
color: #4caf50;
|
||
border-color: rgba(76, 175, 80, 0.2);
|
||
}
|
||
|
||
.history-time {
|
||
font-size: 0.8rem;
|
||
color: var(--color-text-secondary);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.time-icon {
|
||
opacity: 0.7;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-summary {
|
||
font-size: 0.9rem;
|
||
color: var(--color-text-secondary);
|
||
line-height: 1.5;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
.history-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
margin-top: 0.5rem;
|
||
padding-top: 0.5rem;
|
||
border-top: 1px solid var(--color-border);
|
||
}
|
||
|
||
.view-detail-hint {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
color: var(--color-text-secondary);
|
||
font-size: 0.85rem;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.arrow-icon {
|
||
width: 16px;
|
||
height: 16px;
|
||
transition: transform 0.2s ease;
|
||
}
|
||
|
||
.loading-more {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.75rem;
|
||
padding: 1.5rem;
|
||
color: var(--color-text-secondary);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.loading-spinner-small {
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 2px solid var(--color-border);
|
||
border-top-color: var(--color-accent);
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
.no-more-data {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0.5rem;
|
||
color: var(--color-text-secondary);
|
||
font-size: 0.85rem;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
/* 详情弹窗样式 */
|
||
.detail-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
backdrop-filter: blur(4px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 9999; /* 提高z-index值,确保在侧边栏菜单按钮上层 */
|
||
padding: 1rem;
|
||
}
|
||
|
||
/* 深色主题下的模态框背景遮罩增强 */
|
||
[data-theme='dark'] .detail-modal {
|
||
background-color: rgba(0, 0, 0, 0.8);
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
.detail-content {
|
||
background-color: var(--color-bg-primary);
|
||
border-radius: var(--border-radius);
|
||
width: 100%;
|
||
max-width: 900px;
|
||
max-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||
border: 1px solid var(--color-border);
|
||
position: relative;
|
||
}
|
||
|
||
/* 深色主题下的详情模态框增强 */
|
||
[data-theme='dark'] .detail-content {
|
||
background-color: #1a1a1a;
|
||
border: 2px solid rgba(255, 255, 255, 0.15);
|
||
box-shadow:
|
||
0 8px 32px rgba(0, 0, 0, 0.6),
|
||
0 0 0 1px rgba(255, 255, 255, 0.1),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.detail-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 1rem;
|
||
border-bottom: 1px solid var(--color-border);
|
||
background-color: var(--color-bg-secondary);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
/* 深色主题下的详情模态框头部增强 */
|
||
[data-theme='dark'] .detail-header {
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
||
background-color: rgba(255, 255, 255, 0.03);
|
||
}
|
||
|
||
.detail-header h2 {
|
||
font-size: 1.25rem;
|
||
font-weight: 600;
|
||
color: var(--color-text-primary);
|
||
margin: 0;
|
||
}
|
||
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
font-size: 1.5rem;
|
||
color: var(--color-text-secondary);
|
||
cursor: pointer;
|
||
padding: 0.25rem 0.5rem;
|
||
line-height: 1;
|
||
border-radius: 4px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.close-btn:hover {
|
||
background-color: var(--color-bg-hover);
|
||
color: var(--color-text-primary);
|
||
}
|
||
|
||
.detail-info {
|
||
padding: 1rem;
|
||
background-color: var(--color-bg-secondary);
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 1rem;
|
||
border-bottom: 1px solid var(--color-border);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
/* 深色主题下的详情信息区域增强 */
|
||
[data-theme='dark'] .detail-info {
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
||
background-color: rgba(255, 255, 255, 0.03);
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.info-label {
|
||
color: var(--color-text-secondary);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.info-value {
|
||
color: var(--color-text-primary);
|
||
font-weight: 500;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.detail-content-wrapper {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 0;
|
||
background-color: var(--color-bg-primary);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
/* 深色主题下的详情内容包装器增强 */
|
||
[data-theme='dark'] .detail-content-wrapper {
|
||
background-color: #1a1a1a;
|
||
}
|
||
|
||
.analysis-content {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
/* 响应式样式 */
|
||
@media (max-width: 1024px) {
|
||
.content-container {
|
||
max-width: 100%;
|
||
padding: 1.5rem 1rem;
|
||
}
|
||
|
||
.history-item {
|
||
padding: 1.25rem;
|
||
}
|
||
|
||
.history-symbol {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.history-summary {
|
||
font-size: 0.9rem;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.title {
|
||
font-size: 1.75rem;
|
||
}
|
||
|
||
.history-item {
|
||
padding: 1rem;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.history-header {
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.history-main-info {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.history-symbol {
|
||
font-size: 1rem;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
flex-wrap: nowrap;
|
||
}
|
||
|
||
.symbol-timeframe {
|
||
font-size: 0.75rem;
|
||
padding: 0.15rem 0.4rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-type-badge {
|
||
font-size: 0.75rem;
|
||
padding: 0.25rem 0.6rem;
|
||
}
|
||
|
||
.history-time {
|
||
font-size: 0.75rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-summary {
|
||
-webkit-line-clamp: 2;
|
||
font-size: 0.85rem;
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
.view-detail-hint {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.history-footer {
|
||
margin-top: 0.5rem;
|
||
padding-top: 0.5rem;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.content-container {
|
||
padding: 3.5rem 0rem 1rem 0rem;
|
||
}
|
||
|
||
.title {
|
||
font-size: 1.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.header-section {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.history-item {
|
||
padding: 1rem;
|
||
gap: 0.75rem;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.history-header {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.history-main-info {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.75rem;
|
||
width: 100%;
|
||
}
|
||
|
||
.history-symbol {
|
||
font-size: 1.1rem;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
flex: 1;
|
||
min-width: 0;
|
||
flex-wrap: nowrap;
|
||
}
|
||
|
||
.symbol-timeframe {
|
||
font-size: 0.8rem;
|
||
padding: 0.2rem 0.5rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-type-badge {
|
||
font-size: 0.8rem;
|
||
padding: 0.3rem 0.7rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-time {
|
||
font-size: 0.8rem;
|
||
align-self: flex-start;
|
||
text-align: right;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-summary {
|
||
font-size: 0.85rem;
|
||
margin: 0.5rem 0;
|
||
-webkit-line-clamp: 3;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.history-footer {
|
||
margin-top: 0.5rem;
|
||
padding-top: 0.5rem;
|
||
justify-content: center;
|
||
}
|
||
|
||
.view-detail-hint {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.detail-content {
|
||
max-height: 85vh;
|
||
margin: 0.5rem;
|
||
}
|
||
|
||
.detail-header {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.detail-header h2 {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.detail-info {
|
||
padding: 1rem;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.info-row {
|
||
flex-direction: column;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 0.9rem;
|
||
}
|
||
}
|
||
|
||
/* 优化滚动条样式 */
|
||
.history-content::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
.history-content::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
.history-content::-webkit-scrollbar-thumb {
|
||
background-color: rgba(0, 0, 0, 0.1);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.history-content::-webkit-scrollbar-thumb:hover {
|
||
background-color: rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
/* 模态框动画效果 */
|
||
.modal-enter-active,
|
||
.modal-leave-active {
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.modal-enter-from,
|
||
.modal-leave-to {
|
||
opacity: 0;
|
||
}
|
||
|
||
.modal-enter-from .detail-content,
|
||
.modal-leave-to .detail-content {
|
||
transform: scale(0.9) translateY(-20px);
|
||
opacity: 0;
|
||
}
|
||
|
||
.modal-enter-to .detail-content,
|
||
.modal-leave-from .detail-content {
|
||
transform: scale(1) translateY(0);
|
||
opacity: 1;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
/* Markdown 内容样式 - 与分析页面保持一致 */
|
||
.markdown-content {
|
||
white-space: normal !important;
|
||
}
|
||
|
||
.markdown-content h1,
|
||
.markdown-content h2,
|
||
.markdown-content h3,
|
||
.markdown-content h4,
|
||
.markdown-content h5,
|
||
.markdown-content h6 {
|
||
margin-top: 1.5rem;
|
||
margin-bottom: 1rem;
|
||
font-weight: 600;
|
||
line-height: 1.25;
|
||
color: var(--color-text-primary);
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.markdown-content h1 {
|
||
border-bottom: 1px solid var(--color-border);
|
||
padding-bottom: 0.3em;
|
||
}
|
||
|
||
.markdown-content h2 {
|
||
border-bottom: 1px solid var(--color-border);
|
||
padding-bottom: 0.3em;
|
||
}
|
||
|
||
.markdown-content p {
|
||
margin-top: 0;
|
||
margin-bottom: 1.5rem;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.markdown-content ul,
|
||
.markdown-content ol {
|
||
margin-top: 0;
|
||
margin-bottom: 1.5rem;
|
||
padding-left: 2rem;
|
||
}
|
||
|
||
.markdown-content li {
|
||
margin-bottom: 0.5rem;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.markdown-content li p {
|
||
margin-bottom: 0.7rem;
|
||
}
|
||
|
||
.markdown-content li:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.markdown-content blockquote {
|
||
padding: 0.5rem 1.5rem;
|
||
color: var(--color-text-secondary);
|
||
border-left: 0.3rem solid var(--color-border);
|
||
margin: 0 0 1.5rem;
|
||
}
|
||
|
||
.markdown-content blockquote p:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.markdown-content pre {
|
||
margin-top: 0;
|
||
margin-bottom: 1rem;
|
||
padding: 1rem;
|
||
overflow: auto;
|
||
font-size: 85%;
|
||
line-height: 1.45;
|
||
background-color: var(--color-bg-secondary);
|
||
border-radius: var(--border-radius);
|
||
}
|
||
|
||
.markdown-content code {
|
||
font-family:
|
||
SFMono-Regular,
|
||
Consolas,
|
||
Liberation Mono,
|
||
Menlo,
|
||
monospace;
|
||
font-size: 85%;
|
||
padding: 0.2em 0.4em;
|
||
margin: 0;
|
||
background-color: var(--color-bg-secondary);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.markdown-content pre code {
|
||
padding: 0;
|
||
background-color: transparent;
|
||
}
|
||
|
||
.markdown-content .table-container {
|
||
width: 100%;
|
||
overflow-x: auto;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.markdown-content table {
|
||
width: 100%;
|
||
margin-top: 0;
|
||
margin-bottom: 0;
|
||
border-spacing: 0;
|
||
border-collapse: collapse;
|
||
max-width: 100%;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||
border: 1px solid var(--color-border);
|
||
overflow-x: auto;
|
||
display: block;
|
||
}
|
||
|
||
.markdown-content table th,
|
||
.markdown-content table td {
|
||
padding: 0.75rem 1rem;
|
||
border: 1px solid var(--color-border);
|
||
text-align: left;
|
||
min-width: 100px;
|
||
vertical-align: top;
|
||
white-space: normal;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.markdown-content table th {
|
||
font-weight: 600;
|
||
background-color: var(--color-bg-secondary);
|
||
white-space: nowrap;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 1;
|
||
}
|
||
|
||
.markdown-content table tr {
|
||
background-color: var(--color-bg-primary);
|
||
border-top: 1px solid var(--color-border);
|
||
}
|
||
|
||
.markdown-content table tr:nth-child(2n) {
|
||
background-color: var(--color-bg-secondary);
|
||
}
|
||
|
||
.markdown-content a {
|
||
color: var(--color-accent);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.markdown-content a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.markdown-content img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
display: block;
|
||
margin: 1rem auto;
|
||
}
|
||
|
||
.markdown-content hr {
|
||
height: 0.25em;
|
||
padding: 0;
|
||
margin: 1.5rem 0;
|
||
background-color: var(--color-border);
|
||
border: 0;
|
||
}
|
||
</style>
|