Merge from Cassianvale: feat: 适配手机端
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -21,6 +21,7 @@ build_upload.log
|
||||
*.spec
|
||||
*.zip
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# frontend
|
||||
node_modules/
|
||||
@@ -38,6 +39,7 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.vite
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
/* 移动端通用样式优化 */
|
||||
|
||||
/* 标准断点变量 -- 通过CSS变量实现统一断点管理 */
|
||||
:root {
|
||||
--mobile-xs-breakpoint: 480px; /* 小型手机设备 */
|
||||
--mobile-sm-breakpoint: 576px; /* 普通手机设备 */
|
||||
--mobile-md-breakpoint: 768px; /* 平板和大型手机 */
|
||||
}
|
||||
|
||||
/* ===== 基础移动端组件 ===== */
|
||||
|
||||
/* 增大触摸目标区域 */
|
||||
.mobile-touch-target {
|
||||
min-height: 44px; /* 推荐的最小触摸目标尺寸 */
|
||||
@@ -34,22 +43,22 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 自适应字体大小 */
|
||||
@media (max-width: 480px) {
|
||||
.mobile-adaptive-text {
|
||||
font-size: 14px;
|
||||
/* ===== 移动端布局类 ===== */
|
||||
|
||||
/* 全宽容器 */
|
||||
.mobile-full-width {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.mobile-adaptive-heading {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端表格优化 */
|
||||
.mobile-table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
/* 移动端内容容器 */
|
||||
.mobile-content-container {
|
||||
padding: 0.75rem !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 底部操作区固定 */
|
||||
@@ -71,6 +80,15 @@
|
||||
height: 60px; /* 预留底部空间 */
|
||||
}
|
||||
|
||||
/* 底部背景延伸 */
|
||||
.mobile-bottom-extend {
|
||||
position: relative;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
margin-bottom: -1px; /* 防止底部出现缝隙 */
|
||||
}
|
||||
|
||||
/* ===== 移动端UI元素 ===== */
|
||||
|
||||
/* 移动端友好的卡片样式 */
|
||||
.mobile-card {
|
||||
border-radius: 12px !important;
|
||||
@@ -78,26 +96,23 @@
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
/* 移动端卡片边距优化 */
|
||||
.mobile-card-spacing {
|
||||
margin: 0.5rem 0 !important;
|
||||
border-radius: 0.75rem !important;
|
||||
}
|
||||
|
||||
/* 移动端阴影优化 - 更轻微的阴影效果 */
|
||||
.mobile-shadow {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
/* 移动端列表样式优化 */
|
||||
.mobile-list-item {
|
||||
padding: 12px !important;
|
||||
}
|
||||
|
||||
/* 可滑动区域提示 */
|
||||
.mobile-scrollable-hint {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mobile-scrollable-hint::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 24px;
|
||||
background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.8));
|
||||
pointer-events: none;
|
||||
}
|
||||
/* ===== 特殊效果类 ===== */
|
||||
|
||||
/* 边框优化 */
|
||||
.mobile-border-fix {
|
||||
@@ -122,42 +137,415 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 底部背景延伸 */
|
||||
.mobile-bottom-extend {
|
||||
/* 可滑动区域提示 */
|
||||
.mobile-scrollable-hint {
|
||||
position: relative;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
margin-bottom: -1px; /* 防止底部出现缝隙 */
|
||||
}
|
||||
|
||||
/* 全宽容器 */
|
||||
.mobile-full-width {
|
||||
.mobile-scrollable-hint::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 24px;
|
||||
background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.8));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 表格横向滚动指示器 */
|
||||
.mobile-table-scroll-indicator {
|
||||
position: relative;
|
||||
}
|
||||
.mobile-table-scroll-indicator::after {
|
||||
content: '←→';
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
color: rgba(32, 128, 240, 0.6);
|
||||
font-size: 14px;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
animation: fadeInOut 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes fadeInOut {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 0.8; }
|
||||
}
|
||||
|
||||
/* ===== 响应式文本 ===== */
|
||||
|
||||
/* 自适应字体大小 */
|
||||
@media (max-width: 480px) {
|
||||
.mobile-adaptive-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mobile-adaptive-heading {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 网格系统优化 ===== */
|
||||
|
||||
/* 网格布局基础类 */
|
||||
.mobile-grid {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-grid-item {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* ===== API配置面板专用类 ===== */
|
||||
|
||||
.mobile-connection-status {
|
||||
padding: 0.75rem !important;
|
||||
border-radius: 0.5rem !important;
|
||||
margin-top: 0.75rem !important;
|
||||
}
|
||||
|
||||
.mobile-api-config-section {
|
||||
margin-bottom: 1.5rem !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile-api-config-card {
|
||||
padding: 0.5rem !important;
|
||||
border-radius: 0.625rem !important;
|
||||
margin-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
.mobile-api-actions {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile-api-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mobile-api-info-alert {
|
||||
padding: 0.75rem !important;
|
||||
margin-bottom: 0.75rem !important;
|
||||
border-radius: 0.5rem !important;
|
||||
}
|
||||
|
||||
.mobile-model-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mobile-toggle-text {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.mobile-api-save-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile-api-button {
|
||||
height: 36px;
|
||||
min-width: 40%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mobile-toggle-button {
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 16px;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
/* ===== StockCard专用类 ===== */
|
||||
|
||||
.mobile-stock-card {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 移动端卡片边距优化 */
|
||||
.mobile-card-spacing {
|
||||
margin: 0.5rem 0 !important;
|
||||
border-radius: 0.75rem !important;
|
||||
overflow: hidden !important;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* 移动端阴影优化 - 更轻微的阴影效果 */
|
||||
.mobile-shadow {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
.mobile-card-header {
|
||||
padding: 0.75rem !important;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
/* 移动端内容容器 - 确保内容不会太靠近边缘 */
|
||||
.mobile-card-content {
|
||||
padding: 0.75rem !important;
|
||||
}
|
||||
|
||||
/* ===== StockSearch专用类 ===== */
|
||||
|
||||
.mobile-search-results {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid var(--n-border-color, rgba(0, 0, 0, 0.1));
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mobile-search-result-item {
|
||||
padding: 0.625rem 0.875rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
min-height: 44px; /* 确保触摸区域足够大 */
|
||||
}
|
||||
|
||||
.mobile-result-name {
|
||||
max-width: 170px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 小屏幕适配 */
|
||||
@media (max-width: 480px) {
|
||||
.mobile-result-name {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.mobile-search-result-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.mobile-search-results {
|
||||
border-radius: 0.625rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== MarketTimeDisplay专用类 ===== */
|
||||
|
||||
.mobile-market-time-card {
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
min-height: 180px; /* 移动端下的最小高度 */
|
||||
}
|
||||
|
||||
.mobile-time-block {
|
||||
padding: 0.625rem;
|
||||
margin-bottom: 0.75rem; /* 增加底部外边距 */
|
||||
}
|
||||
|
||||
.mobile-current-time {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.mobile-time-label {
|
||||
font-size: 0.9375rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mobile-status-tag {
|
||||
min-width: 100px; /* 减小移动端下的最小宽度 */
|
||||
height: 36px !important;
|
||||
font-size: 0.875rem; /* 减小字体大小 */
|
||||
}
|
||||
|
||||
.mobile-time-counter {
|
||||
font-size: 0.75rem; /* 减小字体大小 */
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
|
||||
/* 小屏幕特殊适配 */
|
||||
@media (max-width: 480px) {
|
||||
.mobile-market-time-card {
|
||||
padding: 0.375rem;
|
||||
min-height: 160px; /* 小屏幕下的最小高度 */
|
||||
}
|
||||
|
||||
.mobile-time-block {
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 1rem; /* 增加小屏幕下的底部外边距 */
|
||||
}
|
||||
|
||||
.mobile-current-time {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.mobile-time-label {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.mobile-time-counter {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.mobile-status-tag {
|
||||
min-width: 90px; /* 进一步减小最小宽度 */
|
||||
font-size: 0.8125rem;
|
||||
padding: 0 12px !important; /* 减小内边距 */
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 媒体查询部分 ===== */
|
||||
|
||||
/* 平板和大型手机 - 768px以下 */
|
||||
@media (max-width: 768px) {
|
||||
.mobile-content-container {
|
||||
padding: 0.75rem !important;
|
||||
}
|
||||
|
||||
.mobile-api-actions {
|
||||
flex-direction: column !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.mobile-api-buttons {
|
||||
width: 100% !important;
|
||||
justify-content: space-between !important;
|
||||
margin-top: 0.75rem !important;
|
||||
}
|
||||
|
||||
.mobile-api-config-card {
|
||||
padding: 0.75rem !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 0.75rem !important;
|
||||
overflow: hidden !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.mobile-url-feedback {
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 网格优化 */
|
||||
:deep(.n-grid) {
|
||||
gap: 12px !important;
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
|
||||
:deep(.n-grid-item) {
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 普通手机设备 - 480px以下 */
|
||||
@media (max-width: 480px) {
|
||||
.mobile-content-container {
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
.mobile-api-config-section {
|
||||
padding-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.mobile-api-config-card {
|
||||
padding: 0.5rem !important;
|
||||
min-height: 80px !important;
|
||||
}
|
||||
|
||||
/* 小屏幕网格优化 */
|
||||
.mobile-grid-small {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.mobile-grid-item-small {
|
||||
padding: 0 !important;
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
:deep(.n-grid) {
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-grid-item) {
|
||||
padding: 0 !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-grid-item) > * {
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.mobile-form-item {
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.mobile-api-buttons-small {
|
||||
flex-wrap: wrap !important;
|
||||
gap: 0.5rem !important;
|
||||
}
|
||||
|
||||
.mobile-api-button {
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-toggle-button {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile-api-info-alert-small {
|
||||
padding: 0.5rem !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
font-size: 0.75rem !important;
|
||||
}
|
||||
|
||||
.mobile-model-tag {
|
||||
font-size: 0.75rem !important;
|
||||
padding: 0 0.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== AnnouncementBanner专用类 ===== */
|
||||
|
||||
.mobile-announcement-container {
|
||||
max-width: 100%;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.mobile-announcement-header {
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.mobile-announcement-content {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.mobile-announcement-timer {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.mobile-login-announcement {
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
left: 0.75rem;
|
||||
}
|
||||
|
||||
/* 小屏幕适配 */
|
||||
@media (max-width: 480px) {
|
||||
.mobile-announcement-container {
|
||||
top: 0.25rem;
|
||||
right: 0.25rem;
|
||||
left: 0.25rem;
|
||||
max-width: calc(100% - 0.5rem);
|
||||
}
|
||||
|
||||
.mobile-announcement-header {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.mobile-announcement-content {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.mobile-announcement-timer {
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div v-if="showAnnouncement" class="announcement-container" :class="{ 'login-page-announcement': isLoginPage }">
|
||||
<div v-if="showAnnouncement" class="announcement-container mobile-announcement-container" :class="{ 'login-page-announcement mobile-login-announcement': isLoginPage }">
|
||||
<n-card class="announcement-card mobile-card" :class="{ 'login-card-style': isLoginPage }">
|
||||
<template #header>
|
||||
<div class="announcement-header">
|
||||
<div class="announcement-header mobile-announcement-header">
|
||||
<n-icon size="18" :component="InformationCircleIcon" class="info-icon" />
|
||||
<span>系统公告</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="announcement-content" v-html="processedContent"></div>
|
||||
<div class="announcement-timer">{{ remainingTimeText }}</div>
|
||||
<div class="announcement-content mobile-announcement-content" v-html="processedContent"></div>
|
||||
<div class="announcement-timer mobile-announcement-timer">{{ remainingTimeText }}</div>
|
||||
<template #action>
|
||||
<n-button quaternary circle size="small" @click="closeAnnouncement" class="mobile-touch-target">
|
||||
<template #icon>
|
||||
@@ -154,54 +154,4 @@ onBeforeUnmount(() => {
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.announcement-container {
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
left: 0.5rem;
|
||||
max-width: calc(100% - 1rem);
|
||||
}
|
||||
|
||||
.announcement-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.announcement-header {
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.announcement-content {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* 登录页面移动端适配 */
|
||||
.login-page-announcement {
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
left: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏幕手机适配 */
|
||||
@media (max-width: 480px) {
|
||||
.announcement-container {
|
||||
top: 0.25rem;
|
||||
right: 0.25rem;
|
||||
left: 0.25rem;
|
||||
max-width: calc(100% - 0.5rem);
|
||||
}
|
||||
|
||||
.announcement-header {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.announcement-content {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.announcement-timer {
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="api-config-section">
|
||||
<div class="api-config-section mobile-api-config-section">
|
||||
<n-button
|
||||
class="toggle-button mobile-touch-target"
|
||||
class="toggle-button mobile-touch-target mobile-toggle-button"
|
||||
size="small"
|
||||
@click="toggleConfig"
|
||||
:quaternary="true"
|
||||
@@ -10,12 +10,12 @@
|
||||
<template #icon>
|
||||
<n-icon :component="expanded ? ChevronUpIcon : ChevronDownIcon" />
|
||||
</template>
|
||||
<span class="toggle-text">API配置 {{ expanded ? '收起' : '展开' }}</span>
|
||||
<span class="toggle-text mobile-toggle-text">API配置 {{ expanded ? '收起' : '展开' }}</span>
|
||||
</n-button>
|
||||
|
||||
<n-collapse-transition :show="expanded">
|
||||
<n-card class="api-config-card mobile-card mobile-shadow" :bordered="false">
|
||||
<n-alert title="OpenAI API配置" type="info" v-if="isApiInfoVisible" class="api-info-alert">
|
||||
<n-card class="api-config-card mobile-card mobile-shadow mobile-api-config-card" :bordered="false">
|
||||
<n-alert title="OpenAI API配置" type="info" v-if="isApiInfoVisible" class="api-info-alert mobile-api-info-alert mobile-api-info-alert-small">
|
||||
<template #icon>
|
||||
<n-icon :component="InformationCircleIcon" />
|
||||
</template>
|
||||
@@ -29,9 +29,9 @@
|
||||
</div>
|
||||
</n-alert>
|
||||
|
||||
<n-grid :cols="24" :x-gap="16" :y-gap="16" responsive="screen">
|
||||
<n-grid-item :span="24" :md-span="14" :lg-span="14">
|
||||
<n-form-item label="API URL" path="apiUrl">
|
||||
<n-grid :cols="24" :x-gap="16" :y-gap="16" responsive="screen" class="mobile-grid mobile-grid-small">
|
||||
<n-grid-item :span="24" :md-span="14" :lg-span="14" class="mobile-grid-item mobile-grid-item-small">
|
||||
<n-form-item label="API URL" path="apiUrl" class="mobile-form-item">
|
||||
<n-input
|
||||
v-model:value="apiConfig.apiUrl"
|
||||
placeholder="https://api.openai.com/v1/chat/completions"
|
||||
@@ -43,7 +43,7 @@
|
||||
</template>
|
||||
</n-input>
|
||||
<template #feedback>
|
||||
<div class="url-feedback">
|
||||
<div class="url-feedback mobile-url-feedback">
|
||||
<span class="formatted-url">实际请求地址: {{ formattedUrl }}</span>
|
||||
<div class="url-tips">
|
||||
<div>提示: URL以/结尾将忽略v1路径</div>
|
||||
@@ -54,8 +54,8 @@
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item :span="24" :md-span="10" :lg-span="10">
|
||||
<n-form-item label="API Key" path="apiKey">
|
||||
<n-grid-item :span="24" :md-span="10" :lg-span="10" class="mobile-grid-item mobile-grid-item-small">
|
||||
<n-form-item label="API Key" path="apiKey" class="mobile-form-item">
|
||||
<n-input
|
||||
v-model:value="apiConfig.apiKey"
|
||||
type="password"
|
||||
@@ -71,8 +71,8 @@
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item :span="12" :md-span="12" :lg-span="12">
|
||||
<n-form-item label="模型" path="apiModel">
|
||||
<n-grid-item :span="12" :md-span="12" :lg-span="12" class="mobile-grid-item mobile-grid-item-small">
|
||||
<n-form-item label="模型" path="apiModel" class="mobile-form-item">
|
||||
<n-input
|
||||
v-model:value="apiConfig.apiModel"
|
||||
placeholder="输入或选择模型名称"
|
||||
@@ -101,7 +101,7 @@
|
||||
<div class="model-suggestions">
|
||||
<div class="model-tip">您可以直接输入模型名称,或点击右侧按钮从下拉菜单选择</div>
|
||||
<span>常用模型:</span>
|
||||
<div class="model-chips">
|
||||
<div class="model-chips mobile-model-chips">
|
||||
<n-tag
|
||||
v-for="model in commonModels"
|
||||
:key="model.key"
|
||||
@@ -109,6 +109,7 @@
|
||||
round
|
||||
clickable
|
||||
@click="selectModel(model.key)"
|
||||
class="mobile-model-tag"
|
||||
>
|
||||
{{ model.label }}
|
||||
</n-tag>
|
||||
@@ -118,8 +119,8 @@
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item :span="12" :md-span="12" :lg-span="12">
|
||||
<n-form-item label="超时时间(秒)" path="apiTimeout">
|
||||
<n-grid-item :span="12" :md-span="12" :lg-span="12" class="mobile-grid-item mobile-grid-item-small">
|
||||
<n-form-item label="超时时间(秒)" path="apiTimeout" class="mobile-form-item">
|
||||
<n-input-number
|
||||
v-model:value="apiTimeout"
|
||||
placeholder="60"
|
||||
@@ -144,13 +145,14 @@
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<div class="api-actions">
|
||||
<div class="api-save-option">
|
||||
<div class="api-actions mobile-api-actions">
|
||||
<div class="api-save-option mobile-api-save-option">
|
||||
<n-button
|
||||
tertiary
|
||||
size="small"
|
||||
@click="saveConfig"
|
||||
round
|
||||
class="mobile-api-save-option-button"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="SaveIcon" />
|
||||
@@ -159,13 +161,14 @@
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<div class="api-buttons">
|
||||
<div class="api-buttons mobile-api-buttons mobile-api-buttons-small">
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="testingConnection"
|
||||
:disabled="!isConfigValid"
|
||||
@click="testConnection"
|
||||
round
|
||||
class="mobile-api-button"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="CheckmarkIcon" />
|
||||
@@ -173,7 +176,7 @@
|
||||
测试连接
|
||||
</n-button>
|
||||
|
||||
<n-button @click="resetConfig" round>
|
||||
<n-button @click="resetConfig" round class="mobile-api-button">
|
||||
<template #icon>
|
||||
<n-icon :component="RefreshIcon" />
|
||||
</template>
|
||||
@@ -184,7 +187,7 @@
|
||||
|
||||
<n-divider v-if="connectionStatus" style="margin: 16px 0 12px" />
|
||||
|
||||
<div v-if="connectionStatus" class="connection-status" :class="connectionStatus.type">
|
||||
<div v-if="connectionStatus" class="connection-status mobile-connection-status" :class="connectionStatus.type">
|
||||
<n-icon :component="connectionStatus.icon" class="status-icon" />
|
||||
<span class="status-message">{{ connectionStatus.message }}</span>
|
||||
</div>
|
||||
@@ -611,123 +614,6 @@ onMounted(() => {
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.api-actions {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.api-buttons {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.api-config-card {
|
||||
padding: 0.75rem;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 0.75rem !important;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.url-feedback {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.api-info-alert {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.model-chips {
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toggle-text {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.api-save-option {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.api-save-option button {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 确保输入框在移动端正确显示 */
|
||||
:deep(.n-input) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 确保下拉菜单在移动端正确显示 */
|
||||
:deep(.n-dropdown-menu) {
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
/* 确保连接状态在移动端正确显示 */
|
||||
.connection-status {
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.api-config-section {
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.api-config-card {
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.625rem !important;
|
||||
margin-bottom: 1rem;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.api-buttons {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.api-buttons .n-button {
|
||||
flex: 1;
|
||||
min-width: 40%;
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
width: 100%;
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
.api-info-alert {
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.model-chips :deep(.n-tag) {
|
||||
font-size: 0.75rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
/* 确保边框在小屏幕上清晰可见 */
|
||||
.api-config-card, .api-info-alert, .connection-status {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.model-suggestions {
|
||||
margin-top: 6px;
|
||||
font-size: 0.75rem;
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<n-card class="market-time-card mobile-card mobile-shadow">
|
||||
<n-grid :x-gap="16" :y-gap="16" :cols="gridCols" responsive="screen">
|
||||
<n-card class="market-time-card mobile-card mobile-shadow mobile-market-time-card">
|
||||
<n-grid :x-gap="16" :y-gap="16" cols="1 s:2 m:4" responsive="screen">
|
||||
<!-- 当前时间 -->
|
||||
<n-grid-item :span="24" :md-span="6">
|
||||
<div class="time-block current-time-block">
|
||||
<p class="time-label">当前时间</p>
|
||||
<p class="current-time">{{ marketInfo.currentTime }}</p>
|
||||
<n-grid-item>
|
||||
<div class="time-block current-time-block mobile-time-block">
|
||||
<p class="time-label mobile-time-label">当前时间</p>
|
||||
<p class="current-time mobile-current-time">{{ marketInfo.currentTime }}</p>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
|
||||
<!-- A股状态 -->
|
||||
<n-grid-item :span="24" :md-span="6">
|
||||
<div class="time-block market-block" :class="{'market-open-block': marketInfo.cnMarket.isOpen, 'market-closed-block': !marketInfo.cnMarket.isOpen}">
|
||||
<p class="time-label">A股市场</p>
|
||||
<n-grid-item>
|
||||
<div class="time-block market-block mobile-time-block" :class="{'market-open-block mobile-market-open-block': marketInfo.cnMarket.isOpen, 'market-closed-block mobile-market-closed-block': !marketInfo.cnMarket.isOpen}">
|
||||
<p class="time-label mobile-time-label">A股市场</p>
|
||||
<div class="market-status" :class="marketInfo.cnMarket.isOpen ? 'status-open' : 'status-closed'">
|
||||
<n-tag v-if="marketInfo.cnMarket.isOpen" type="success" size="medium" round class="status-tag mobile-touch-target">
|
||||
<n-tag v-if="marketInfo.cnMarket.isOpen" type="success" size="medium" round class="status-tag mobile-touch-target mobile-status-tag">
|
||||
<template #icon><n-icon size="18"><pulse-icon /></n-icon></template>
|
||||
交易中
|
||||
</n-tag>
|
||||
<n-tag v-else type="default" size="medium" round class="status-tag mobile-touch-target">
|
||||
<n-tag v-else type="default" size="medium" round class="status-tag mobile-touch-target mobile-status-tag">
|
||||
<template #icon><n-icon size="18"><time-icon /></n-icon></template>
|
||||
已休市
|
||||
</n-tag>
|
||||
</div>
|
||||
<p class="time-counter">{{ marketInfo.cnMarket.nextTime }}</p>
|
||||
<p class="time-counter mobile-time-counter">{{ marketInfo.cnMarket.nextTime }}</p>
|
||||
<div class="market-progress-container">
|
||||
<div class="market-progress-bar"
|
||||
:class="marketInfo.cnMarket.isOpen ? 'progress-open' : 'progress-closed'"
|
||||
@@ -38,7 +38,7 @@
|
||||
</n-grid-item>
|
||||
|
||||
<!-- 港股状态 -->
|
||||
<n-grid-item :span="24" :md-span="6">
|
||||
<n-grid-item>
|
||||
<div class="time-block market-block" :class="{'market-open-block': marketInfo.hkMarket.isOpen, 'market-closed-block': !marketInfo.hkMarket.isOpen}">
|
||||
<p class="time-label">港股市场</p>
|
||||
<div class="market-status" :class="marketInfo.hkMarket.isOpen ? 'status-open' : 'status-closed'">
|
||||
@@ -66,7 +66,7 @@
|
||||
</n-grid-item>
|
||||
|
||||
<!-- 美股状态 -->
|
||||
<n-grid-item :span="24" :md-span="6">
|
||||
<n-grid-item>
|
||||
<div class="time-block market-block" :class="{'market-open-block': marketInfo.usMarket.isOpen, 'market-closed-block': !marketInfo.usMarket.isOpen}">
|
||||
<p class="time-label">美股市场</p>
|
||||
<div class="market-status" :class="marketInfo.usMarket.isOpen ? 'status-open' : 'status-closed'">
|
||||
@@ -97,7 +97,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { NCard, NGrid, NGridItem, NTag, NIcon } from 'naive-ui';
|
||||
import {
|
||||
PulseOutline as PulseIcon,
|
||||
@@ -113,11 +113,6 @@ const marketInfo = ref<MarketTimeInfo>({
|
||||
usMarket: { isOpen: false, nextTime: '' }
|
||||
});
|
||||
|
||||
// 根据屏幕尺寸自动调整布局
|
||||
const gridCols = computed(() => {
|
||||
return 24;
|
||||
});
|
||||
|
||||
let intervalId: number | null = null;
|
||||
|
||||
function updateMarketTime() {
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<n-layout class="main-layout">
|
||||
<n-layout-content class="main-content mobile-content-container">
|
||||
|
||||
<!-- 市场时间显示 PC也显示移动端布局,暂时隐藏-->
|
||||
<!-- <MarketTimeDisplay :is-mobile="isMobile" /> -->
|
||||
<!-- 市场时间显示 -->
|
||||
<MarketTimeDisplay :is-mobile="isMobile" />
|
||||
|
||||
<!-- API配置面板 -->
|
||||
<ApiConfigPanel
|
||||
@@ -25,9 +25,9 @@
|
||||
<!-- 主要内容 -->
|
||||
<n-card class="analysis-container mobile-card mobile-card-spacing mobile-shadow">
|
||||
|
||||
<n-grid :cols="24" :x-gap="16" :y-gap="16" responsive="screen">
|
||||
<n-grid cols="1 xl:24" :x-gap="16" :y-gap="16" responsive="screen">
|
||||
<!-- 左侧配置区域 -->
|
||||
<n-grid-item :span="24" :sm-span="24" :md-span="10" :lg-span="8">
|
||||
<n-grid-item span="1 xl:8">
|
||||
<div class="config-section">
|
||||
<n-form-item label="选择市场类型">
|
||||
<n-select
|
||||
@@ -71,7 +71,7 @@
|
||||
</n-grid-item>
|
||||
|
||||
<!-- 右侧结果区域 -->
|
||||
<n-grid-item :span="24" :sm-span="24" :md-span="14" :lg-span="16">
|
||||
<n-grid-item span="1 xl:16">
|
||||
<div class="results-section">
|
||||
<div class="results-header">
|
||||
<n-space align="center" justify="space-between">
|
||||
@@ -121,7 +121,7 @@
|
||||
</template>
|
||||
|
||||
<template v-else-if="displayMode === 'card'">
|
||||
<n-grid :cols="1" :x-gap="16" :y-gap="16" :lg-cols="2">
|
||||
<n-grid cols="1" :x-gap="8" :y-gap="8" responsive="screen">
|
||||
<n-grid-item v-for="stock in analyzedStocks" :key="stock.code">
|
||||
<StockCard :stock="stock" />
|
||||
</n-grid-item>
|
||||
@@ -129,6 +129,7 @@
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="table-container">
|
||||
<n-data-table
|
||||
:columns="stockTableColumns"
|
||||
:data="analyzedStocks"
|
||||
@@ -137,7 +138,9 @@
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
striped
|
||||
:scroll-x="1200"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
@@ -176,7 +179,7 @@ import {
|
||||
DownloadOutline as DownloadIcon,
|
||||
} from '@vicons/ionicons5';
|
||||
|
||||
// import MarketTimeDisplay from './MarketTimeDisplay.vue';
|
||||
import MarketTimeDisplay from './MarketTimeDisplay.vue';
|
||||
import ApiConfigPanel from './ApiConfigPanel.vue';
|
||||
import StockSearch from './StockSearch.vue';
|
||||
import StockCard from './StockCard.vue';
|
||||
@@ -215,9 +218,9 @@ const apiConfig = ref<ApiConfig>({
|
||||
});
|
||||
|
||||
// 移动端检测
|
||||
// const isMobile = computed(() => {
|
||||
// return window.innerWidth <= 768;
|
||||
// });
|
||||
const isMobile = computed(() => {
|
||||
return window.innerWidth <= 768;
|
||||
});
|
||||
|
||||
// 监听窗口大小变化
|
||||
function handleResize() {
|
||||
@@ -413,10 +416,13 @@ function handleMarketTypeChange() {
|
||||
|
||||
// 添加选择的股票
|
||||
function addSelectedStock(symbol: string) {
|
||||
// 确保symbol不包含序号或其他不需要的信息
|
||||
const cleanSymbol = symbol.trim().replace(/^\d+\.\s*/, '');
|
||||
|
||||
if (stockCodes.value) {
|
||||
stockCodes.value += ', ' + symbol;
|
||||
stockCodes.value += ', ' + cleanSymbol;
|
||||
} else {
|
||||
stockCodes.value = symbol;
|
||||
stockCodes.value = cleanSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1004,6 +1010,11 @@ function handleAnnouncementClose() {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 修改卡片内容区域的内边距 */
|
||||
.analysis-container :deep(.n-card__content) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.config-section {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
@@ -1031,6 +1042,29 @@ function handleAnnouncementClose() {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 表格容器基础样式 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch; /* 支持iOS的滚动惯性 */
|
||||
position: relative;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* 表格横向滚动指示器 */
|
||||
.table-container::after {
|
||||
content: '←→';
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
color: rgba(32, 128, 240, 0.6);
|
||||
font-size: 14px;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
animation: fadeInOut 2s infinite;
|
||||
display: none; /* 默认隐藏,只在移动端显示 */
|
||||
}
|
||||
|
||||
/* 移动端适配的媒体查询 */
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
@@ -1039,6 +1073,26 @@ function handleAnnouncementClose() {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 显示滚动指示器 */
|
||||
.table-container::after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 减少移动端卡片内容区域的内边距 */
|
||||
.analysis-container :deep(.n-card__content) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
/* 确保卡片内部没有多余边距 */
|
||||
:deep(.n-card > .n-card__content) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
/* 减少结果区域的内边距 */
|
||||
.results-section {
|
||||
padding: 0.25rem 0.125rem;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
@@ -1060,26 +1114,38 @@ function handleAnnouncementClose() {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.config-section, .results-section {
|
||||
.config-section {
|
||||
padding: 0.25rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 确保表格内容在移动端可滚动 */
|
||||
.n-data-table-wrapper {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
width: 100%;
|
||||
/* 移动端表格样式优化 */
|
||||
.table-container {
|
||||
margin: 0 -4px; /* 抵消父容器的padding */
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* 表格组件移动端优化 */
|
||||
:deep(.n-data-table-wrapper) {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* 改善表单项在移动端的间距 */
|
||||
:deep(.n-data-table-base-table-header, .n-data-table-base-table-body) {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
:deep(.n-pagination) {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* 保留原有移动端优化样式 */
|
||||
:deep(.n-form-item) {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* 确保卡片视图在移动端正确显示 */
|
||||
:deep(.n-grid) {
|
||||
width: 100% !important;
|
||||
}
|
||||
@@ -1089,6 +1155,22 @@ function handleAnnouncementClose() {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
:deep(.n-grid[cols="1 m\\:24"]) {
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
:deep(.n-grid[cols="1 l\\:2"]) {
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-grid-item) > * {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.n-dropdown-menu) {
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
padding-bottom: 30px; /* 增加移动端底部内边距 */
|
||||
}
|
||||
@@ -1100,10 +1182,31 @@ function handleAnnouncementClose() {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
/* 进一步减少小屏幕卡片内容区域的内边距 */
|
||||
.analysis-container :deep(.n-card__content) {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
/* 使用更精确的选择器确保覆盖 */
|
||||
:deep(.n-card) > :deep(.n-card__content),
|
||||
:deep(.n-card-header) {
|
||||
padding: 6px 4px !important;
|
||||
}
|
||||
|
||||
/* 减少网格间距到最小 */
|
||||
:deep(.n-grid[cols="1 l\\:2"]) {
|
||||
gap: 4px !important;
|
||||
}
|
||||
|
||||
.results-section {
|
||||
padding: 0.15rem 0.05rem;
|
||||
}
|
||||
|
||||
.results-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
:deep(.n-space) {
|
||||
@@ -1118,15 +1221,76 @@ function handleAnnouncementClose() {
|
||||
|
||||
.analysis-container {
|
||||
border-radius: 0.625rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* 确保下拉菜单在小屏幕上正确显示 */
|
||||
:deep(.n-dropdown-menu) {
|
||||
max-width: 90vw;
|
||||
/* 小屏幕下进一步优化n-grid */
|
||||
:deep(.n-grid) {
|
||||
gap: 4px !important;
|
||||
}
|
||||
|
||||
:deep(.n-grid-item) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保n-grid-item内容在小屏幕下有更紧凑的间距 */
|
||||
:deep(.n-grid-item) > * {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* 小屏幕表格样式调整 */
|
||||
.table-container {
|
||||
margin: 0 -2px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
/* 小屏幕分页控件优化 */
|
||||
:deep(.n-pagination .n-pagination-item) {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
padding-bottom: 40px; /* 增加小屏幕底部内边距 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕适配 */
|
||||
@media (max-width: 375px) {
|
||||
/* 超小屏幕卡片内容区域几乎无内边距 */
|
||||
.analysis-container :deep(.n-card__content) {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
/* 使用更精确的选择器确保覆盖 */
|
||||
:deep(.n-card) > :deep(.n-card__content),
|
||||
:deep(.n-card-header) {
|
||||
padding: 3px 2px !important;
|
||||
}
|
||||
|
||||
/* 网格间距最小化 */
|
||||
:deep(.n-grid[cols="1 l\\:2"]),
|
||||
:deep(.n-grid[cols="1 m\\:24"]) {
|
||||
gap: 3px !important;
|
||||
}
|
||||
|
||||
/* 极简边距 */
|
||||
.results-section {
|
||||
padding: 0.1rem 0.025rem;
|
||||
}
|
||||
|
||||
/* 进一步调整超小屏幕的间距和尺寸 */
|
||||
.main-content {
|
||||
padding: 0.15rem;
|
||||
}
|
||||
|
||||
.config-section {
|
||||
padding: 0.15rem;
|
||||
}
|
||||
|
||||
/* 确保StockCard组件最大化利用空间 */
|
||||
:deep(.stock-card) {
|
||||
margin: 2px 0 !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<n-card class="stock-card mobile-card mobile-shadow" :bordered="false" :class="{ 'is-analyzing': isAnalyzing }">
|
||||
<div class="card-header">
|
||||
<n-card class="stock-card mobile-card mobile-shadow mobile-stock-card" :bordered="false" :class="{ 'is-analyzing': isAnalyzing }">
|
||||
<div class="card-header mobile-card-header">
|
||||
<div class="header-main">
|
||||
<div class="header-left">
|
||||
<div class="stock-info">
|
||||
@@ -126,7 +126,10 @@
|
||||
</template>
|
||||
|
||||
<template v-else-if="stock.analysisStatus === 'analyzing'">
|
||||
<div class="analysis-result analysis-streaming" v-if="parsedAnalysis" v-html="parsedAnalysis" :key="analysisContentKey"></div>
|
||||
<div class="analysis-result analysis-streaming"
|
||||
ref="analysisResultRef"
|
||||
v-html="parsedAnalysis">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="stock.analysisStatus === 'completed'">
|
||||
@@ -138,7 +141,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import { computed, watch, ref, nextTick, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { NCard, NDivider, NIcon, NTag, NButton, useMessage } from 'naive-ui';
|
||||
import {
|
||||
AlertCircleOutline as AlertCircleIcon,
|
||||
@@ -159,22 +162,17 @@ const isAnalyzing = computed(() => {
|
||||
});
|
||||
|
||||
const lastAnalysisLength = ref(0);
|
||||
const lastAnalysisText = ref('');
|
||||
|
||||
// 监听分析内容变化
|
||||
watch(() => props.stock.analysis, (newVal) => {
|
||||
if (newVal && props.stock.analysisStatus === 'analyzing') {
|
||||
lastAnalysisLength.value = newVal.length;
|
||||
lastAnalysisText.value = newVal;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 添加一个计算属性,用于监控分析内容是否更新
|
||||
const analysisContentKey = ref(0);
|
||||
watch(() => props.stock.analysis, (newVal, oldVal) => {
|
||||
if (newVal && oldVal && newVal.length > oldVal.length && props.stock.analysisStatus === 'analyzing') {
|
||||
analysisContentKey.value++;
|
||||
}
|
||||
}, { immediate: false });
|
||||
|
||||
// 分析内容的解析
|
||||
const parsedAnalysis = computed(() => {
|
||||
if (props.stock.analysis) {
|
||||
let result = parseMarkdown(props.stock.analysis);
|
||||
@@ -414,6 +412,99 @@ const getStatusText = computed(() => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
// 添加滚动控制相关变量
|
||||
const analysisResultRef = ref<HTMLElement | null>(null);
|
||||
const userScrolling = ref(false);
|
||||
const scrollPosition = ref(0);
|
||||
const scrollThreshold = 30; // 底部阈值,小于这个值认为用户已滚动到底部
|
||||
|
||||
// 检测用户滚动行为
|
||||
function handleScroll() {
|
||||
if (!analysisResultRef.value) return;
|
||||
|
||||
const element = analysisResultRef.value;
|
||||
const atBottom = element.scrollHeight - element.scrollTop - element.clientHeight < scrollThreshold;
|
||||
|
||||
// 记录当前滚动位置
|
||||
scrollPosition.value = element.scrollTop;
|
||||
|
||||
// 判断用户是否正在主动滚动
|
||||
if (atBottom) {
|
||||
// 用户滚动到底部,标记为非主动滚动状态
|
||||
userScrolling.value = false;
|
||||
} else {
|
||||
// 用户未在底部,标记为主动滚动状态
|
||||
userScrolling.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听滚动事件
|
||||
onMounted(() => {
|
||||
if (analysisResultRef.value) {
|
||||
// 初始滚动到底部
|
||||
analysisResultRef.value.scrollTop = analysisResultRef.value.scrollHeight;
|
||||
analysisResultRef.value.addEventListener('scroll', handleScroll);
|
||||
}
|
||||
});
|
||||
|
||||
// 清理事件监听
|
||||
onBeforeUnmount(() => {
|
||||
if (analysisResultRef.value) {
|
||||
analysisResultRef.value.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
});
|
||||
|
||||
// 改进流式更新监听,更保守地控制滚动行为
|
||||
let isProcessingUpdate = false; // 防止重复处理更新
|
||||
watch(() => props.stock.analysis, (newVal, oldVal) => {
|
||||
// 只在分析中且内容增加时处理
|
||||
if (newVal && oldVal && newVal.length > oldVal.length &&
|
||||
props.stock.analysisStatus === 'analyzing' && !isProcessingUpdate) {
|
||||
|
||||
isProcessingUpdate = true; // 标记正在处理更新
|
||||
|
||||
// 检查是否应该自动滚动
|
||||
let shouldAutoScroll = false;
|
||||
if (analysisResultRef.value) {
|
||||
const element = analysisResultRef.value;
|
||||
// 仅当滚动接近底部或用户尚未开始滚动时自动滚动
|
||||
const atBottom = element.scrollHeight - element.scrollTop - element.clientHeight < scrollThreshold;
|
||||
shouldAutoScroll = atBottom || !userScrolling.value;
|
||||
}
|
||||
|
||||
// 使用nextTick确保DOM已更新
|
||||
nextTick(() => {
|
||||
if (analysisResultRef.value && shouldAutoScroll) {
|
||||
// 使用smoothScroll而非直接设置scrollTop,减少视觉跳动
|
||||
smoothScrollToBottom(analysisResultRef.value);
|
||||
}
|
||||
|
||||
// 重置处理标记
|
||||
setTimeout(() => {
|
||||
isProcessingUpdate = false;
|
||||
}, 50); // 短暂延迟,防止过快连续处理
|
||||
});
|
||||
}
|
||||
}, { immediate: false });
|
||||
|
||||
// 平滑滚动到底部的辅助函数
|
||||
function smoothScrollToBottom(element: HTMLElement) {
|
||||
const targetPosition = element.scrollHeight;
|
||||
|
||||
// 如果已经很接近底部,直接跳转避免不必要的动画
|
||||
const currentGap = targetPosition - element.scrollTop - element.clientHeight;
|
||||
if (currentGap < 100) {
|
||||
element.scrollTop = targetPosition;
|
||||
return;
|
||||
}
|
||||
|
||||
// 否则使用平滑滚动
|
||||
element.scrollTo({
|
||||
top: targetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -423,6 +514,8 @@ const getStatusText = computed(() => {
|
||||
flex-direction: column;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
width: 100%; /* 确保宽度不会超过容器 */
|
||||
max-width: 100%; /* 限制最大宽度 */
|
||||
}
|
||||
|
||||
.stock-card.is-analyzing {
|
||||
@@ -439,6 +532,7 @@ const getStatusText = computed(() => {
|
||||
position: relative;
|
||||
background: linear-gradient(to bottom, rgba(240, 240, 245, 0.3), transparent);
|
||||
border-radius: 8px 8px 0 0;
|
||||
width: 100%; /* 确保宽度不会超过容器 */
|
||||
}
|
||||
|
||||
.header-main {
|
||||
@@ -525,6 +619,7 @@ const getStatusText = computed(() => {
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
@@ -738,6 +833,8 @@ const getStatusText = computed(() => {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%; /* 确保宽度不会超过容器 */
|
||||
overflow-x: hidden; /* 防止内容横向溢出 */
|
||||
}
|
||||
|
||||
.error-status {
|
||||
@@ -771,10 +868,18 @@ const getStatusText = computed(() => {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
display: block; /* 确保显示为块级元素 */
|
||||
box-sizing: border-box; /* 确保padding不增加宽度 */
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
scrollbar-width: thin; /* Firefox */
|
||||
scrollbar-color: rgba(32, 128, 240, 0.3) transparent; /* Firefox */
|
||||
/* 改进滚动行为 */
|
||||
scroll-behavior: smooth;
|
||||
overflow-anchor: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-y;
|
||||
will-change: scroll-position;
|
||||
}
|
||||
|
||||
/* Webkit浏览器的滚动条样式 */
|
||||
@@ -807,6 +912,13 @@ const getStatusText = computed(() => {
|
||||
position: relative;
|
||||
border-left: 2px solid var(--n-info-color);
|
||||
animation: fadePulse 2s infinite;
|
||||
/* 改进滚动行为 */
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
will-change: scroll-position;
|
||||
/* 防止内容更新时的布局抖动 */
|
||||
contain: content;
|
||||
scroll-padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 改进流式输出的动画效果,消除闪烁 */
|
||||
@@ -886,6 +998,8 @@ const getStatusText = computed(() => {
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
font-size: 0.85em;
|
||||
white-space: pre-wrap; /* 允许代码内容自动换行 */
|
||||
word-break: break-word; /* 确保长单词可以换行 */
|
||||
}
|
||||
|
||||
.analysis-result :deep(pre) {
|
||||
@@ -896,12 +1010,16 @@ const getStatusText = computed(() => {
|
||||
margin: 0.75rem 0;
|
||||
border-left: 3px solid #2080f0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
white-space: pre-wrap; /* 允许代码块自动换行 */
|
||||
word-break: break-word; /* 允许长单词换行 */
|
||||
}
|
||||
|
||||
.analysis-result :deep(pre code) {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
white-space: inherit; /* 继承pre的换行行为 */
|
||||
}
|
||||
|
||||
/* 优化引用样式 */
|
||||
@@ -923,6 +1041,8 @@ const getStatusText = computed(() => {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
table-layout: fixed; /* 固定表格布局 */
|
||||
max-width: 100%;
|
||||
display: block; /* 使表格成为块级元素 */
|
||||
overflow-x: auto; /* 允许表格滚动 */
|
||||
}
|
||||
|
||||
.analysis-result :deep(th), .analysis-result :deep(td) {
|
||||
@@ -994,6 +1114,10 @@ const getStatusText = computed(() => {
|
||||
border-bottom: 1px dotted #2080f0;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.analysis-result :deep(a:hover) {
|
||||
@@ -1009,16 +1133,13 @@ const getStatusText = computed(() => {
|
||||
margin: 0.75rem auto;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
object-fit: contain; /* 保持图片比例 */
|
||||
}
|
||||
|
||||
/* 移动端适配样式 */
|
||||
@media (max-width: 768px) {
|
||||
.stock-card {
|
||||
margin-bottom: 0.75rem;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 0.75rem !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -1027,19 +1148,77 @@ const getStatusText = computed(() => {
|
||||
}
|
||||
|
||||
.header-main {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stock-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.stock-code {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.stock-name {
|
||||
font-size: 0.8rem;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
margin-top: 0.5rem;
|
||||
align-self: flex-end;
|
||||
width: 320px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.stock-price-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin-top: 0.5rem;
|
||||
gap: 0.75rem;
|
||||
gap: 16px;
|
||||
border-left: none;
|
||||
border-top: 1px dashed rgba(0, 0, 0, 0.09);
|
||||
padding-top: 8px;
|
||||
padding-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stock-price, .stock-change {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stock-price .label,
|
||||
.stock-change .label {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.stock-price .value {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.stock-change .value {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stock-summary {
|
||||
@@ -1049,10 +1228,43 @@ const getStatusText = computed(() => {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.indicators-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
.technical-indicators {
|
||||
margin: 0.75rem 0.5rem;
|
||||
background-color: rgba(240, 240, 245, 0.5);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.indicators-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.75rem;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.indicator-item {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.625rem 0.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.indicator-item:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.indicator-value {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.indicator-label {
|
||||
font-size: 0.7rem;
|
||||
color: var(--n-text-color-3);
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
@@ -1066,35 +1278,242 @@ const getStatusText = computed(() => {
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
.analysis-result {
|
||||
font-size: 0.8125rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
max-height: 300px;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
margin: 0.5rem;
|
||||
.card-content {
|
||||
padding: 0.5rem 0.3rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(pre) {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.375rem;
|
||||
.analysis-result {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.65;
|
||||
padding: 0.6rem 0.5rem;
|
||||
max-height: 350px;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.07);
|
||||
margin: 0.4rem 0;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
-webkit-overflow-scrolling: touch; /* 加强iOS滚动平滑性 */
|
||||
overscroll-behavior: contain; /* 防止滚动传播 */
|
||||
touch-action: pan-y; /* 优化触摸滚动体验 */
|
||||
width: 100%; /* 占据全部可用宽度 */
|
||||
box-sizing: border-box;
|
||||
position: relative; /* 确保滚动提示正确定位 */
|
||||
overflow-x: hidden !important; /* 强制禁止横向滚动 */
|
||||
}
|
||||
|
||||
/* 优化表格在移动端的显示 */
|
||||
.analysis-result :deep(table) {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
font-size: 0.8rem;
|
||||
border: none;
|
||||
border-radius: 0.4rem;
|
||||
margin: 0.7rem 0;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 优化代码块在移动端的显示 */
|
||||
.analysis-result :deep(pre) {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.75rem 0.5rem;
|
||||
border-radius: 0.4rem;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
margin: 0.7rem 0;
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
border-left: 3px solid rgba(32, 128, 240, 0.5);
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 拖动滚动提示效果 - 恢复并优化 */
|
||||
.analysis-result :deep(pre)::after,
|
||||
.analysis-result :deep(table)::after {
|
||||
content: '⟷';
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
color: rgba(32, 128, 240, 0.5);
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
/* 改进链接触摸体验 */
|
||||
.analysis-result :deep(a) {
|
||||
padding: 0.1rem 0;
|
||||
margin: 0 0.1rem;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 改进按钮和交互元素触摸体验 */
|
||||
.analysis-result :deep(button),
|
||||
.analysis-result :deep(.interactive) {
|
||||
min-height: 36px; /* 最小触摸高度 */
|
||||
min-width: 36px; /* 最小触摸宽度 */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 确保所有内容在移动端都能正确换行和显示 */
|
||||
.analysis-result :deep(*) {
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.analysis-streaming {
|
||||
background-color: rgba(32, 128, 240, 0.03);
|
||||
}
|
||||
|
||||
.analysis-completed {
|
||||
background-color: rgba(24, 160, 88, 0.02);
|
||||
}
|
||||
|
||||
/* 优化标题样式 */
|
||||
.analysis-result :deep(h1),
|
||||
.analysis-result :deep(h2),
|
||||
.analysis-result :deep(h3) {
|
||||
margin: 1rem 0 0.7rem 0;
|
||||
line-height: 1.3;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h1) {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h2) {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h3) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 优化段落间距 */
|
||||
.analysis-result :deep(p) {
|
||||
margin: 0.6rem 0;
|
||||
}
|
||||
|
||||
/* 优化列表样式 */
|
||||
.analysis-result :deep(ul),
|
||||
.analysis-result :deep(ol) {
|
||||
padding-left: 1.2rem;
|
||||
margin: 0.6rem 0;
|
||||
}
|
||||
|
||||
.analysis-result :deep(li) {
|
||||
margin-bottom: 0.35rem;
|
||||
padding-left: 0.3rem;
|
||||
}
|
||||
|
||||
/* 优化引用块 */
|
||||
.analysis-result :deep(blockquote) {
|
||||
margin: 0.7rem 0;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-left: 4px solid #f0a020;
|
||||
background-color: rgba(240, 160, 32, 0.07);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
/* 优化代码块 */
|
||||
.analysis-result :deep(pre) {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.75rem 0.5rem;
|
||||
border-radius: 0.4rem;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
margin: 0.7rem 0;
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
border-left: 3px solid rgba(32, 128, 240, 0.5);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.analysis-result :deep(code) {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.15rem 0.3rem;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
/* 优化表格显示 */
|
||||
.analysis-result :deep(table) {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
width: 100%;
|
||||
border-radius: 0.375rem;
|
||||
border-radius: 0.4rem;
|
||||
margin: 0.7rem 0;
|
||||
font-size: 0.8rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.indicator-item {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.625rem;
|
||||
.analysis-result :deep(th),
|
||||
.analysis-result :deep(td) {
|
||||
padding: 0.5rem 0.4rem;
|
||||
}
|
||||
|
||||
/* 优化强调文本 */
|
||||
.analysis-result :deep(strong) {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 优化专业术语显示 */
|
||||
.analysis-result :deep(.buy),
|
||||
.analysis-result :deep(.sell),
|
||||
.analysis-result :deep(.hold) {
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(.indicator) {
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
/* 优化图片显示 */
|
||||
.analysis-result :deep(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.4rem;
|
||||
margin: 0.7rem auto;
|
||||
}
|
||||
|
||||
/* 优化滚动条样式 */
|
||||
.analysis-result::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.analysis-result::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(32, 128, 240, 0.3);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 滚动提示 */
|
||||
.analysis-result::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 20px;
|
||||
background: linear-gradient(to top, rgba(255, 255, 255, 0.7), transparent);
|
||||
pointer-events: none;
|
||||
opacity: 0.8;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1106,28 +1525,78 @@ const getStatusText = computed(() => {
|
||||
}
|
||||
|
||||
.stock-info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.stock-code {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.stock-name {
|
||||
margin-left: 0;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0;
|
||||
font-size: 0.75rem;
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.stock-price-info {
|
||||
gap: 12px;
|
||||
padding-top: 6px;
|
||||
margin-top: 6px;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.stock-price, .stock-change {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stock-price .label,
|
||||
.stock-change .label {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.stock-price .value {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.stock-change .value {
|
||||
font-size: 0.8rem;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.technical-indicators {
|
||||
margin: 0.5rem 0.25rem;
|
||||
border-radius: 0.45rem;
|
||||
padding: 0.4rem 0.3rem;
|
||||
}
|
||||
|
||||
.indicators-grid {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.5rem;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.indicator-item {
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.45rem;
|
||||
padding: 0.5rem 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.analysis-result {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
margin: 0.375rem;
|
||||
.indicator-value {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.indicator-label {
|
||||
font-size: 0.7rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -1138,5 +1607,208 @@ const getStatusText = computed(() => {
|
||||
.stock-card, .indicator-item, .analysis-result {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
/* 为不同类型的指标设置不同的边框颜色 */
|
||||
.indicator-item .rsi-overbought {
|
||||
border-bottom: 2px solid #d03050;
|
||||
}
|
||||
|
||||
.indicator-item .rsi-oversold {
|
||||
border-bottom: 2px solid #18a058;
|
||||
}
|
||||
|
||||
.indicator-item .trend-up {
|
||||
border-bottom: 2px solid #d03050;
|
||||
}
|
||||
|
||||
.indicator-item .trend-down {
|
||||
border-bottom: 2px solid #18a058;
|
||||
}
|
||||
|
||||
.indicator-item .signal-buy {
|
||||
border-bottom: 2px solid #d03050;
|
||||
}
|
||||
|
||||
.indicator-item .signal-sell {
|
||||
border-bottom: 2px solid #18a058;
|
||||
}
|
||||
|
||||
/* 分析结果小屏幕样式 */
|
||||
.analysis-result {
|
||||
font-size: 0.825rem;
|
||||
line-height: 1.6;
|
||||
padding: 0.5rem 0.4rem;
|
||||
margin: 0.2rem 0;
|
||||
max-height: 300px;
|
||||
max-width: none; /* 移除宽度限制 */
|
||||
width: 100%; /* 占据全部可用宽度 */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 0.3rem 0.1rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h1) {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0.85rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h2) {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h3) {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(ul),
|
||||
.analysis-result :deep(ol) {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(blockquote) {
|
||||
padding: 0.5rem 0.625rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(pre) {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.6rem 0.4rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(code) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(th),
|
||||
.analysis-result :deep(td) {
|
||||
padding: 0.4rem 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕适配 */
|
||||
@media (max-width: 375px) {
|
||||
.indicators-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.indicator-item {
|
||||
padding: 0.4rem 0.2rem;
|
||||
}
|
||||
|
||||
.indicator-value {
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.indicator-label {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
/* 分析结果超小屏幕样式 */
|
||||
.analysis-result {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.4rem 0.3rem;
|
||||
margin: 0.1rem 0;
|
||||
width: 100%; /* 占据全部可用宽度 */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h1) {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h2) {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.analysis-result :deep(h3) {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 0.2rem 0.05rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 添加PC端特定样式,确保纵向布局 */
|
||||
@media (min-width: 769px) {
|
||||
.stock-card {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-main {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.stock-price-info {
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.stock-summary {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.analysis-result {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 优化技术指标在PC端的显示 */
|
||||
.indicators-grid {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保所有嵌套元素不会超出容器 */
|
||||
.analysis-result :deep(*) {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 对于图片特别控制 */
|
||||
.analysis-result :deep(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0.75rem auto;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
object-fit: contain; /* 保持图片比例 */
|
||||
}
|
||||
|
||||
/* 修复长链接可能导致的溢出 */
|
||||
.analysis-result :deep(a) {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 删除滚动控制面板样式 */
|
||||
.scroll-controls {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
</n-input>
|
||||
|
||||
<div class="search-results" v-show="showResults">
|
||||
<div class="search-results mobile-search-results" v-show="showResults">
|
||||
<div v-if="loading" class="loading-results">
|
||||
<n-spin size="small" />
|
||||
<span>搜索中...</span>
|
||||
@@ -28,12 +28,12 @@
|
||||
<div
|
||||
v-for="item in results"
|
||||
:key="item.symbol"
|
||||
class="search-result-item"
|
||||
class="search-result-item mobile-search-result-item"
|
||||
@click="selectStock(item)"
|
||||
>
|
||||
<div class="result-symbol-name">
|
||||
<span class="result-symbol">{{ item.symbol }}</span>
|
||||
<span class="result-name">{{ item.name }}</span>
|
||||
<span class="result-name mobile-result-name">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="result-meta">
|
||||
<span class="result-market">{{ item.market }}</span>
|
||||
@@ -106,7 +106,10 @@ function handleSearchInput() {
|
||||
}
|
||||
|
||||
function selectStock(item: SearchResult) {
|
||||
emit('select', item.symbol);
|
||||
// 处理symbol,确保不包含序号
|
||||
// 假设symbol格式可能是"1. AAPL"这样的格式,我们只需要"AAPL"部分
|
||||
const cleanSymbol = item.symbol.replace(/^\d+\.\s*/, '');
|
||||
emit('select', cleanSymbol);
|
||||
searchKeyword.value = '';
|
||||
showResults.value = false;
|
||||
}
|
||||
@@ -224,39 +227,14 @@ onBeforeUnmount(() => {
|
||||
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.search-results {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid var(--n-border-color, rgba(0, 0, 0, 0.1));
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: 0.625rem 0.875rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.search-result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.result-name {
|
||||
max-width: 170px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 确保输入框在移动端正确显示 */
|
||||
:deep(.n-input) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 增大触摸区域 */
|
||||
.search-result-item {
|
||||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@@ -264,30 +242,14 @@ onBeforeUnmount(() => {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.result-name, .result-market, .result-market-value {
|
||||
.result-market, .result-market-value {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.result-name {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
|
||||
.loading-results, .no-results {
|
||||
padding: 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* 确保边框在小屏幕上清晰可见 */
|
||||
.search-results {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user