web/src/views/AnalysisHistoryView.vue
2025-05-25 23:14:06 +08:00

1164 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup 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>