commit
e020d0a6bb
@ -0,0 +1,24 @@
|
|||||||
|
# 期货智析缓冲平台 - 环境配置
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
BUFFER_DB_PATH=/app/data/buffer.db
|
||||||
|
|
||||||
|
# 服务配置
|
||||||
|
BUFFER_HOST=0.0.0.0
|
||||||
|
BUFFER_PORT=8600
|
||||||
|
|
||||||
|
# 缓存配置(秒)
|
||||||
|
CACHE_TTL=300
|
||||||
|
|
||||||
|
# 日志级别 (DEBUG/INFO/WARNING/ERROR/CRITICAL)
|
||||||
|
BUFFER_LOG_LEVEL=INFO
|
||||||
|
|
||||||
|
# 并发采集数
|
||||||
|
MAX_WORKERS=2
|
||||||
|
|
||||||
|
# Docker端口映射
|
||||||
|
HOST_PORT=9600
|
||||||
|
CONTAINER_PORT=8600
|
||||||
|
|
||||||
|
# 数据目录
|
||||||
|
DATA_DIR=E:\docker_workspace\futures_datas
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,149 @@
|
|||||||
|
"""
|
||||||
|
用户权限系统 - API路由
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from app.database import get_db
|
||||||
|
from app.user_models import User
|
||||||
|
from app import auth_service
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/auth", tags=["用户认证"])
|
||||||
|
security = HTTPBearer()
|
||||||
|
|
||||||
|
|
||||||
|
# 请求模型
|
||||||
|
class LoginRequest(BaseModel):
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class LoginResponse(BaseModel):
|
||||||
|
success: bool
|
||||||
|
token: Optional[str] = None
|
||||||
|
user: Optional[dict] = None
|
||||||
|
message: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfo(BaseModel):
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
role: str
|
||||||
|
email: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
# 登录接口
|
||||||
|
@router.post("/login", response_model=LoginResponse)
|
||||||
|
def login(request: LoginRequest, db: Session = Depends(get_db)):
|
||||||
|
"""用户登录"""
|
||||||
|
user = auth_service.authenticate_user(db, request.username, request.password)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return LoginResponse(
|
||||||
|
success=False,
|
||||||
|
message="用户名或密码错误"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建会话
|
||||||
|
token = auth_service.create_session(db, user.id)
|
||||||
|
auth_service.update_last_login(db, user)
|
||||||
|
|
||||||
|
return LoginResponse(
|
||||||
|
success=True,
|
||||||
|
token=token,
|
||||||
|
user={
|
||||||
|
"id": user.id,
|
||||||
|
"username": user.username,
|
||||||
|
"role": user.role,
|
||||||
|
"email": user.email
|
||||||
|
},
|
||||||
|
message="登录成功"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 登出接口
|
||||||
|
@router.post("/logout")
|
||||||
|
def logout(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
|
||||||
|
"""用户登出"""
|
||||||
|
auth_service.invalidate_session(db, credentials.credentials)
|
||||||
|
return {"success": True, "message": "已登出"}
|
||||||
|
|
||||||
|
|
||||||
|
# 验证令牌接口
|
||||||
|
@router.get("/verify")
|
||||||
|
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
|
||||||
|
"""验证用户令牌"""
|
||||||
|
user = auth_service.validate_session(db, credentials.credentials)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="无效的会话令牌"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"user": {
|
||||||
|
"id": user.id,
|
||||||
|
"username": user.username,
|
||||||
|
"role": user.role,
|
||||||
|
"email": user.email,
|
||||||
|
"last_login": user.last_login.isoformat() if user.last_login else None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 获取当前用户信息
|
||||||
|
@router.get("/me")
|
||||||
|
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
|
||||||
|
"""获取当前用户信息"""
|
||||||
|
user = auth_service.validate_session(db, credentials.credentials)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="无效的会话令牌"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": user.id,
|
||||||
|
"username": user.username,
|
||||||
|
"role": user.role,
|
||||||
|
"email": user.email,
|
||||||
|
"is_active": user.is_active,
|
||||||
|
"created_at": user.created_at.isoformat(),
|
||||||
|
"last_login": user.last_login.isoformat() if user.last_login else None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 依赖注入:验证用户已登录
|
||||||
|
def get_current_user_dependency(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
|
||||||
|
"""依赖注入:获取当前用户"""
|
||||||
|
user = auth_service.validate_session(db, credentials.credentials)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="请先登录"
|
||||||
|
)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
# 依赖注入:仅管理员可访问
|
||||||
|
def require_admin(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
|
||||||
|
"""依赖注入:验证管理员权限"""
|
||||||
|
user = auth_service.validate_session(db, credentials.credentials)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="请先登录"
|
||||||
|
)
|
||||||
|
if user.role != 'admin':
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="需要管理员权限"
|
||||||
|
)
|
||||||
|
return user
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
"""
|
||||||
|
用户权限系统 - 认证服务
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import secrets
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Optional
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.user_models import User, Session as UserSession
|
||||||
|
|
||||||
|
|
||||||
|
# 密码加密工具
|
||||||
|
def hash_password(password: str) -> str:
|
||||||
|
"""对密码进行哈希加密"""
|
||||||
|
salt = secrets.token_hex(16)
|
||||||
|
pwd_hash = hashlib.sha256((salt + password).encode()).hexdigest()
|
||||||
|
return f"{salt}${pwd_hash}"
|
||||||
|
|
||||||
|
|
||||||
|
def verify_password(password: str, hashed: str) -> bool:
|
||||||
|
"""验证密码"""
|
||||||
|
try:
|
||||||
|
salt, pwd_hash = hashed.split('$')
|
||||||
|
return hashlib.sha256((salt + password).encode()).hexdigest() == pwd_hash
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def generate_token() -> str:
|
||||||
|
"""生成会话令牌"""
|
||||||
|
return secrets.token_urlsafe(32)
|
||||||
|
|
||||||
|
|
||||||
|
# 用户操作
|
||||||
|
def create_user(db: Session, username: str, password: str, email: str = None, role: str = 'user') -> User:
|
||||||
|
"""创建新用户"""
|
||||||
|
user = User(
|
||||||
|
username=username,
|
||||||
|
password_hash=hash_password(password),
|
||||||
|
email=email,
|
||||||
|
role=role,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
db.add(user)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate_user(db: Session, username: str, password: str) -> Optional[User]:
|
||||||
|
"""验证用户登录"""
|
||||||
|
user = db.query(User).filter(User.username == username).first()
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
if not user.is_active:
|
||||||
|
return None
|
||||||
|
if not verify_password(password, user.password_hash):
|
||||||
|
return None
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def update_last_login(db: Session, user: User):
|
||||||
|
"""更新最后登录时间"""
|
||||||
|
user.last_login = datetime.utcnow()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
# 会话操作
|
||||||
|
def create_session(db: Session, user_id: int, expires_hours: int = 24) -> str:
|
||||||
|
"""创建用户会话"""
|
||||||
|
token = generate_token()
|
||||||
|
session = UserSession(
|
||||||
|
user_id=user_id,
|
||||||
|
token=token,
|
||||||
|
expires_at=datetime.utcnow() + timedelta(hours=expires_hours),
|
||||||
|
is_valid=True
|
||||||
|
)
|
||||||
|
db.add(session)
|
||||||
|
db.commit()
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def validate_session(db: Session, token: str) -> Optional[User]:
|
||||||
|
"""验证会话令牌"""
|
||||||
|
session = db.query(UserSession).filter(
|
||||||
|
UserSession.token == token,
|
||||||
|
UserSession.is_valid == True,
|
||||||
|
UserSession.expires_at > datetime.utcnow()
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not session:
|
||||||
|
return None
|
||||||
|
|
||||||
|
user = db.query(User).filter(User.id == session.user_id).first()
|
||||||
|
if not user or not user.is_active:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_session(db: Session, token: str):
|
||||||
|
"""使会话失效(登出)"""
|
||||||
|
session = db.query(UserSession).filter(UserSession.token == token).first()
|
||||||
|
if session:
|
||||||
|
session.is_valid = False
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_expired_sessions(db: Session):
|
||||||
|
"""清理过期会话"""
|
||||||
|
db.query(UserSession).filter(
|
||||||
|
UserSession.expires_at < datetime.utcnow()
|
||||||
|
).update({"is_valid": False})
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
# 默认用户创建
|
||||||
|
def create_default_admin(db: Session):
|
||||||
|
"""创建默认管理员账户"""
|
||||||
|
admin = db.query(User).filter(User.username == 'lxy_root').first()
|
||||||
|
if not admin:
|
||||||
|
create_user(
|
||||||
|
db=db,
|
||||||
|
username='lxy_root',
|
||||||
|
password='admin123',
|
||||||
|
email='admin@system.local',
|
||||||
|
role='admin'
|
||||||
|
)
|
||||||
|
print("✓ 默认管理员账户已创建: lxy_root / admin123")
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,284 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>登录 - 期货智析系统</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
padding: 40px;
|
||||||
|
animation: slideIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1D1D1F;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #86868B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1D1D1F;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #E5E5E7;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 15px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #FFF3F3;
|
||||||
|
color: #DC2626;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: none;
|
||||||
|
border-left: 4px solid #DC2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message.show {
|
||||||
|
display: block;
|
||||||
|
animation: shake 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
25% { transform: translateX(-10px); }
|
||||||
|
75% { transform: translateX(10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86868B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: white;
|
||||||
|
animation: spin 1s ease-in-out infinite;
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-header">
|
||||||
|
<div class="login-logo">◈</div>
|
||||||
|
<h1 class="login-title">期货智析</h1>
|
||||||
|
<p class="login-subtitle">智能期货期权分析系统</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-message" id="error-message"></div>
|
||||||
|
|
||||||
|
<form id="login-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="username">用户名</label>
|
||||||
|
<input type="text" id="username" class="form-input" placeholder="请输入用户名" required autocomplete="username">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="password">密码</label>
|
||||||
|
<input type="password" id="password" class="form-input" placeholder="请输入密码" required autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="login-btn" id="login-btn">
|
||||||
|
登录
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2026 期货智析系统 · 安全登录</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const loginForm = document.getElementById('login-form');
|
||||||
|
const loginBtn = document.getElementById('login-btn');
|
||||||
|
const errorMessage = document.getElementById('error-message');
|
||||||
|
|
||||||
|
loginForm.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const username = document.getElementById('username').value;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
|
||||||
|
loginBtn.disabled = true;
|
||||||
|
loginBtn.innerHTML = '<span class="loading-spinner"></span>登录中...';
|
||||||
|
errorMessage.classList.remove('show');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, password })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// 保存令牌
|
||||||
|
localStorage.setItem('auth_token', data.token);
|
||||||
|
localStorage.setItem('user_info', JSON.stringify(data.user));
|
||||||
|
|
||||||
|
// 根据角色跳转
|
||||||
|
if (data.user.role === 'admin') {
|
||||||
|
window.location.href = '/role-select';
|
||||||
|
} else {
|
||||||
|
window.location.href = '/futures-analysis';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showError(data.message || '登录失败,请重试');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError('网络错误,请检查连接后重试');
|
||||||
|
} finally {
|
||||||
|
loginBtn.disabled = false;
|
||||||
|
loginBtn.innerHTML = '登录';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
errorMessage.textContent = message;
|
||||||
|
errorMessage.classList.add('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMessage.classList.remove('show');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已登录
|
||||||
|
window.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const token = localStorage.getItem('auth_token');
|
||||||
|
if (token) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/auth/verify', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.user.role === 'admin') {
|
||||||
|
window.location.href = '/role-select';
|
||||||
|
} else {
|
||||||
|
window.location.href = '/futures-analysis';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 令牌无效,继续显示登录页
|
||||||
|
localStorage.removeItem('auth_token');
|
||||||
|
localStorage.removeItem('user_info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,38 @@
|
|||||||
|
"""
|
||||||
|
用户权限系统 - 数据库模型
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import Column, Integer, String, Boolean, DateTime
|
||||||
|
from app.database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
"""用户表"""
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
username = Column(String(50), unique=True, nullable=False, index=True)
|
||||||
|
password_hash = Column(String(255), nullable=False)
|
||||||
|
email = Column(String(100), unique=True, nullable=True)
|
||||||
|
role = Column(String(20), nullable=False, default='user') # 'admin' 或 'user'
|
||||||
|
is_active = Column(Boolean, default=True)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
last_login = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<User(id={self.id}, username='{self.username}', role='{self.role}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class Session(Base):
|
||||||
|
"""用户会话表"""
|
||||||
|
__tablename__ = "sessions"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
user_id = Column(Integer, nullable=False, index=True)
|
||||||
|
token = Column(String(255), unique=True, nullable=False, index=True)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
expires_at = Column(DateTime, nullable=False)
|
||||||
|
is_valid = Column(Boolean, default=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Session(id={self.id}, user_id={self.user_id}, token='{self.token}')>"
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
echo ========================================
|
||||||
|
echo 数据库备份工具
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM 生成备份文件名
|
||||||
|
set BACKUP_DATE=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%
|
||||||
|
set BACKUP_DATE=%BACKUP_DATE: =0%
|
||||||
|
set BACKUP_FILE=buffer_backup_%BACKUP_DATE%.db
|
||||||
|
|
||||||
|
echo 备份文件: %BACKUP_FILE%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM 停止服务
|
||||||
|
echo [1/3] 停止服务...
|
||||||
|
docker-compose stop
|
||||||
|
echo ✓ 服务已停止
|
||||||
|
|
||||||
|
REM 备份数据库
|
||||||
|
echo [2/3] 备份数据库...
|
||||||
|
if exist "E:\docker_workspace\futures_datas\buffer.db" (
|
||||||
|
copy "E:\docker_workspace\futures_datas\buffer.db" "E:\docker_workspace\futures_datas\%BACKUP_FILE%"
|
||||||
|
echo ✓ 数据库已备份到: E:\docker_workspace\futures_datas\%BACKUP_FILE%
|
||||||
|
) else (
|
||||||
|
echo ✗ 数据库文件不存在
|
||||||
|
)
|
||||||
|
|
||||||
|
REM 启动服务
|
||||||
|
echo [3/3] 启动服务...
|
||||||
|
docker-compose start
|
||||||
|
echo ✓ 服务已启动
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo 备份完成!
|
||||||
|
echo ========================================
|
||||||
|
pause
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
db_path = 'data/futures_analysis.db'
|
|
||||||
|
|
||||||
print(f'数据库文件路径: {os.path.abspath(db_path)}')
|
|
||||||
print(f'数据库存在: {os.path.exists(db_path)}')
|
|
||||||
|
|
||||||
if os.path.exists(db_path):
|
|
||||||
print(f'数据库大小: {os.path.getsize(db_path)} 字节')
|
|
||||||
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
||||||
tables = [row[0] for row in cursor.fetchall()]
|
|
||||||
print(f'数据库表列表: {tables}')
|
|
||||||
|
|
||||||
if 'ai_analysis_cache' in tables:
|
|
||||||
cursor = conn.execute("SELECT COUNT(*) FROM ai_analysis_cache")
|
|
||||||
count = cursor.fetchone()[0]
|
|
||||||
print(f'AI分析记录数: {count}')
|
|
||||||
|
|
||||||
cursor = conn.execute("SELECT id, symbol, created_at FROM ai_analysis_cache ORDER BY created_at DESC LIMIT 5")
|
|
||||||
records = cursor.fetchall()
|
|
||||||
print(f'最近5条记录:')
|
|
||||||
for r in records:
|
|
||||||
print(f' ID: {r[0]}, 合约: {r[1]}, 时间: {r[2]}')
|
|
||||||
else:
|
|
||||||
print('❌ ai_analysis_cache 表不存在!')
|
|
||||||
|
|
||||||
conn.close()
|
|
||||||
else:
|
|
||||||
print('❌ 数据库文件不存在!')
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,462 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - Web Prototype (Apple Style)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
/* v3.0 Apple Style Colors */
|
||||||
|
--bg-page: #F5F5F7;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
--text-primary: #1D1D1F;
|
||||||
|
--text-secondary: #6E6E73;
|
||||||
|
--text-tertiary: #86868B;
|
||||||
|
--color-up: #FF3B30;
|
||||||
|
--color-down: #34C759;
|
||||||
|
--color-neutral: #FF9F0A;
|
||||||
|
--color-brand: #007AFF;
|
||||||
|
--color-ai: #AF52DE;
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
|
--shadow-lg: 0 8px 24px rgba(0,0,0,0.08);
|
||||||
|
|
||||||
|
/* Radius */
|
||||||
|
--radius-card: 20px;
|
||||||
|
--radius-btn: 12px;
|
||||||
|
--radius-pill: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
|
||||||
|
background-color: var(--bg-page);
|
||||||
|
color: var(--text-primary);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header - Glassmorphism */
|
||||||
|
.header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
height: 64px;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
backdrop-filter: saturate(180%) blur(20px);
|
||||||
|
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo { font-size: 18px; font-weight: 600; letter-spacing: -0.02em; margin-right: 40px; }
|
||||||
|
.nav-items { display: flex; gap: 24px; flex: 1; }
|
||||||
|
.nav-item { font-size: 14px; color: var(--text-secondary); cursor: pointer; transition: color 0.2s; }
|
||||||
|
.nav-item:hover { color: var(--text-primary); }
|
||||||
|
.nav-item.active { color: var(--color-brand); font-weight: 500; }
|
||||||
|
.live-badge { font-size: 11px; color: var(--color-down); display: flex; align-items: center; gap: 4px; }
|
||||||
|
.dot { width: 6px; height: 6px; background: var(--color-down); border-radius: 50%; }
|
||||||
|
|
||||||
|
/* Main Container */
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; padding: 24px 32px; }
|
||||||
|
|
||||||
|
/* Search & Filter */
|
||||||
|
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px; }
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-btn);
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 300px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
outline: none;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
.search-box:focus { box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2); }
|
||||||
|
|
||||||
|
.pills { display: flex; gap: 8px; }
|
||||||
|
.pill {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.pill:hover { background: #F5F5F7; }
|
||||||
|
.pill.active { background: var(--color-brand); color: #FFFFFF; box-shadow: 0 4px 10px rgba(0,122,255,0.3); }
|
||||||
|
|
||||||
|
/* Stats Row */
|
||||||
|
.stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 32px; }
|
||||||
|
.stat-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
.stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
|
||||||
|
.stat-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; }
|
||||||
|
.stat-icon.blue { background: rgba(0,122,255,0.1); color: var(--color-brand); }
|
||||||
|
.stat-icon.red { background: rgba(255,59,48,0.1); color: var(--color-up); }
|
||||||
|
.stat-icon.green { background: rgba(52,199,89,0.1); color: var(--color-down); }
|
||||||
|
.stat-icon.orange { background: rgba(255,159,10,0.1); color: var(--color-neutral); }
|
||||||
|
.stat-val { font-size: 24px; font-weight: 700; line-height: 1.2; }
|
||||||
|
.stat-label { font-size: 13px; color: var(--text-secondary); }
|
||||||
|
|
||||||
|
/* Grid */
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 20px; }
|
||||||
|
|
||||||
|
/* Card */
|
||||||
|
.card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
|
||||||
|
|
||||||
|
.card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }
|
||||||
|
.code-box { width: 36px; height: 36px; background: #F5F5F7; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; color: var(--text-tertiary); margin-bottom: 8px; }
|
||||||
|
.price-area { text-align: right; }
|
||||||
|
.price { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; line-height: 1; }
|
||||||
|
.price.up { color: var(--color-up); }
|
||||||
|
.price.down { color: var(--color-down); }
|
||||||
|
.change { font-size: 13px; font-weight: 600; margin-top: 4px; }
|
||||||
|
.change.up { color: var(--color-up); }
|
||||||
|
.change.down { color: var(--color-down); }
|
||||||
|
|
||||||
|
.action-pill {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.action-pill.do-more { background: rgba(52,199,89,0.1); color: var(--color-down); }
|
||||||
|
.action-pill.watch { background: rgba(255,159,10,0.1); color: var(--color-neutral); }
|
||||||
|
|
||||||
|
.metrics { display: flex; gap: 24px; margin-bottom: 16px; }
|
||||||
|
.metric { flex: 1; }
|
||||||
|
.metric-label { font-size: 11px; color: var(--text-tertiary); margin-bottom: 4px; }
|
||||||
|
.metric-val { font-size: 14px; font-weight: 600; }
|
||||||
|
.bar-bg { height: 4px; background: #F5F5F7; border-radius: 2px; margin-top: 6px; overflow: hidden; }
|
||||||
|
.bar-fill { height: 100%; border-radius: 2px; }
|
||||||
|
|
||||||
|
.timeframes { display: flex; gap: 8px; margin-bottom: 16px; }
|
||||||
|
.tf { padding: 4px 10px; border-radius: 8px; font-size: 11px; color: var(--text-tertiary); background: #F5F5F7; cursor: pointer; }
|
||||||
|
.tf.active { background: #E8F2FF; color: var(--color-brand); font-weight: 500; }
|
||||||
|
|
||||||
|
.card-footer { display: flex; justify-content: space-between; align-items: center; border-top: 1px solid #F5F5F7; padding-top: 12px; font-size: 12px; }
|
||||||
|
.support-resist { color: var(--text-tertiary); }
|
||||||
|
.support-resist span { margin-right: 8px; }
|
||||||
|
.support-resist .red { color: var(--color-up); }
|
||||||
|
.support-resist .green { color: var(--color-down); }
|
||||||
|
.link { color: var(--color-brand); font-weight: 500; text-decoration: none; }
|
||||||
|
.link:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
<div class="logo">◈ 期货智析</div>
|
||||||
|
<div class="nav-items">
|
||||||
|
<div class="nav-item active">品种分析</div>
|
||||||
|
<div class="nav-item">自选</div>
|
||||||
|
<div class="nav-item">市场概览</div>
|
||||||
|
<div class="nav-item">风险预警</div>
|
||||||
|
</div>
|
||||||
|
<div class="live-badge">
|
||||||
|
<div class="dot"></div> LIVE
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- Toolbar -->
|
||||||
|
<div class="toolbar">
|
||||||
|
<input type="text" class="search-box" placeholder="🔍 搜索品种名称或代码...">
|
||||||
|
<div class="pills">
|
||||||
|
<div class="pill active">全部 32</div>
|
||||||
|
<div class="pill">能源</div>
|
||||||
|
<div class="pill">金属</div>
|
||||||
|
<div class="pill">农产品</div>
|
||||||
|
<div class="pill">金融</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<div class="stats-row">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon blue">≡</div>
|
||||||
|
<div><div class="stat-val">32</div><div class="stat-label">监控品种</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon green">↗</div>
|
||||||
|
<div><div class="stat-val">7</div><div class="stat-label">上涨趋势</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon red">↘</div>
|
||||||
|
<div><div class="stat-val">10</div><div class="stat-label">下跌趋势</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon orange">↔</div>
|
||||||
|
<div><div class="stat-val">15</div><div class="stat-label">震荡整理</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grid -->
|
||||||
|
<div class="grid">
|
||||||
|
<!-- Card 1: Crude Oil -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div>
|
||||||
|
<div class="code-box">SC</div>
|
||||||
|
<div style="font-weight:600;">原油</div>
|
||||||
|
<div style="font-size:12px; color:var(--text-tertiary);">SC2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-area">
|
||||||
|
<div class="price down">¥644.5</div>
|
||||||
|
<div class="change down">↓ -13.3 (-2.02%)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill watch">观望</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">成功率 60%</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">趋势评分 30</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:30%; background:var(--color-up);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes">
|
||||||
|
<div class="tf">5M</div>
|
||||||
|
<div class="tf active">15M</div>
|
||||||
|
<div class="tf">30M</div>
|
||||||
|
<div class="tf">1H</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="support-resist">
|
||||||
|
<span>压力 <b class="red">642.4</b></span>
|
||||||
|
<span>支撑 <b class="green">631.7</b></span>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="link">详情 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card 2: Fuel Oil -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div>
|
||||||
|
<div class="code-box">FU</div>
|
||||||
|
<div style="font-weight:600;">燃油</div>
|
||||||
|
<div style="font-size:12px; color:var(--text-tertiary);">FU2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-area">
|
||||||
|
<div class="price up">¥4,680</div>
|
||||||
|
<div class="change up">↑ +72 (+1.56%)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill do-more">做多</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">成功率 85%</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:85%; background:var(--color-down);"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">趋势评分 65</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:65%; background:var(--color-down);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes">
|
||||||
|
<div class="tf">5M</div>
|
||||||
|
<div class="tf active">15M</div>
|
||||||
|
<div class="tf">30M</div>
|
||||||
|
<div class="tf">1H</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="support-resist">
|
||||||
|
<span>压力 <b class="red">4,600</b></span>
|
||||||
|
<span>支撑 <b class="green">4,530</b></span>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="link">详情 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card 3: Silver -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div>
|
||||||
|
<div class="code-box">AG</div>
|
||||||
|
<div style="font-weight:600;">沪银</div>
|
||||||
|
<div style="font-size:12px; color:var(--text-tertiary);">AG2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-area">
|
||||||
|
<div class="price up">¥18,643</div>
|
||||||
|
<div class="change up">↑ +413 (+2.27%)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill do-more">做多 (短线反弹)</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">成功率 60%</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">趋势评分 60</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes">
|
||||||
|
<div class="tf">5M</div>
|
||||||
|
<div class="tf active">15M</div>
|
||||||
|
<div class="tf">30M</div>
|
||||||
|
<div class="tf">1H</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="support-resist">
|
||||||
|
<span>压力 <b class="red">18,685</b></span>
|
||||||
|
<span>支撑 <b class="green">18,584</b></span>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="link">详情 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 4: Gold -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div>
|
||||||
|
<div class="code-box">AU</div>
|
||||||
|
<div style="font-weight:600;">沪金</div>
|
||||||
|
<div style="font-size:12px; color:var(--text-tertiary);">AU2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-area">
|
||||||
|
<div class="price up">¥993.34</div>
|
||||||
|
<div class="change up">↑ +4.36 (+0.44%)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill watch">观望</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">成功率 60%</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">趋势评分 40</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:40%; background:var(--color-up);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes">
|
||||||
|
<div class="tf">5M</div>
|
||||||
|
<div class="tf active">15M</div>
|
||||||
|
<div class="tf">30M</div>
|
||||||
|
<div class="tf">1H</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="support-resist">
|
||||||
|
<span>压力 <b class="red">993</b></span>
|
||||||
|
<span>支撑 <b class="green">989.5</b></span>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="link">详情 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 5: Copper -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div>
|
||||||
|
<div class="code-box">CU</div>
|
||||||
|
<div style="font-weight:600;">沪铜</div>
|
||||||
|
<div style="font-size:12px; color:var(--text-tertiary);">CU2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-area">
|
||||||
|
<div class="price up">¥104,770</div>
|
||||||
|
<div class="change up">↑ +1,090 (+1.05%)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill do-more">做多</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">成功率 85%</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:85%; background:var(--color-down);"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">趋势评分 65</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:65%; background:var(--color-down);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes">
|
||||||
|
<div class="tf">5M</div>
|
||||||
|
<div class="tf active">15M</div>
|
||||||
|
<div class="tf">30M</div>
|
||||||
|
<div class="tf">1H</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="support-resist">
|
||||||
|
<span>压力 <b class="red">105,250</b></span>
|
||||||
|
<span>支撑 <b class="green">104,450</b></span>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="link">详情 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 6: Nickel -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div>
|
||||||
|
<div class="code-box">NI</div>
|
||||||
|
<div style="font-weight:600;">沪镍</div>
|
||||||
|
<div style="font-size:12px; color:var(--text-tertiary);">NI2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-area">
|
||||||
|
<div class="price down">¥143,330</div>
|
||||||
|
<div class="change down">↓ -440 (-0.31%)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill watch">观望</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">成功率 60%</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">趋势评分 45</div>
|
||||||
|
<div class="bar-bg"><div class="bar-fill" style="width:45%; background:var(--color-up);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes">
|
||||||
|
<div class="tf">5M</div>
|
||||||
|
<div class="tf active">15M</div>
|
||||||
|
<div class="tf">30M</div>
|
||||||
|
<div class="tf">1H</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="support-resist">
|
||||||
|
<span>压力 <b class="red">144,550</b></span>
|
||||||
|
<span>支撑 <b class="green">143,940</b></span>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="link">详情 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,352 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - 品种详情页 (Apple Style)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-page: #F5F5F7;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
--text-primary: #1D1D1F;
|
||||||
|
--text-secondary: #6E6E73;
|
||||||
|
--text-tertiary: #86868B;
|
||||||
|
--border-light: rgba(0,0,0,0.05);
|
||||||
|
|
||||||
|
/* China Market: Red=Up, Green=Down */
|
||||||
|
--color-up: #FF3B30;
|
||||||
|
--color-down: #34C759;
|
||||||
|
|
||||||
|
--color-brand: #007AFF;
|
||||||
|
--color-ai: #AF52DE;
|
||||||
|
--color-ai-bg: rgba(175, 82, 222, 0.08);
|
||||||
|
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
|
|
||||||
|
--radius-card: 16px;
|
||||||
|
--radius-btn: 8px;
|
||||||
|
--radius-pill: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
|
||||||
|
background-color: var(--bg-page);
|
||||||
|
color: var(--text-primary);
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.header {
|
||||||
|
height: 64px;
|
||||||
|
background: rgba(255,255,255,0.8);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 24px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-brand);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.header-title { font-size: 18px; font-weight: 600; }
|
||||||
|
.header-actions { margin-left: auto; display: flex; gap: 12px; }
|
||||||
|
.icon-btn { width: 32px; height: 32px; border-radius: 50%; background: #F5F5F7; display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--text-secondary); }
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; padding: 24px; display: grid; grid-template-columns: 1fr 340px; gap: 24px; }
|
||||||
|
|
||||||
|
/* Info Bar */
|
||||||
|
.info-bar {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
padding: 20px 24px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.info-left { display: flex; align-items: baseline; gap: 16px; }
|
||||||
|
.symbol { font-size: 20px; font-weight: 700; }
|
||||||
|
.contract { font-size: 13px; color: var(--text-tertiary); background: #F5F5F7; padding: 4px 8px; border-radius: 6px; }
|
||||||
|
.price-main { font-size: 32px; font-weight: 700; letter-spacing: -0.02em; }
|
||||||
|
.price-up { color: var(--color-up); }
|
||||||
|
.price-down { color: var(--color-down); }
|
||||||
|
.price-change { font-size: 15px; font-weight: 600; margin-left: 8px; }
|
||||||
|
.ohlc { display: flex; gap: 24px; font-size: 13px; color: var(--text-secondary); }
|
||||||
|
.ohlc span b { font-weight: 600; color: var(--text-primary); margin-left: 4px; }
|
||||||
|
|
||||||
|
/* Chart Area */
|
||||||
|
.chart-section {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
.chart-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
||||||
|
.timeframes { display: flex; gap: 4px; background: #F5F5F7; padding: 4px; border-radius: 8px; }
|
||||||
|
.tf-btn { padding: 4px 10px; border-radius: 6px; font-size: 12px; font-weight: 500; color: var(--text-tertiary); cursor: pointer; }
|
||||||
|
.tf-btn.active { background: #FFFFFF; color: var(--text-primary); box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
|
||||||
|
|
||||||
|
.chart-placeholder {
|
||||||
|
height: 360px;
|
||||||
|
background: #FAFAFA;
|
||||||
|
border-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
/* Simulated Chart Grid */
|
||||||
|
.grid-line { position: absolute; background: #F0F0F0; }
|
||||||
|
.grid-h { width: 100%; height: 1px; }
|
||||||
|
.grid-v { height: 100%; width: 1px; }
|
||||||
|
|
||||||
|
/* Side Panel */
|
||||||
|
.side-panel { display: flex; flex-direction: column; gap: 20px; }
|
||||||
|
|
||||||
|
.panel-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.panel-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.ai-tag { font-size: 10px; background: var(--color-ai-bg); color: var(--color-ai); padding: 2px 6px; border-radius: 4px; }
|
||||||
|
|
||||||
|
.analysis-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: #FAFAFA;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 3px solid var(--color-ai);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; }
|
||||||
|
.data-item { display: flex; justify-content: space-between; font-size: 12px; }
|
||||||
|
.label { color: var(--text-tertiary); }
|
||||||
|
.val { font-weight: 600; }
|
||||||
|
|
||||||
|
.indicator-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
|
||||||
|
.indicator-box { background: #F5F5F7; padding: 12px; border-radius: 10px; }
|
||||||
|
.ind-name { font-size: 11px; color: var(--text-tertiary); margin-bottom: 4px; font-weight: 500; }
|
||||||
|
.ind-val { font-size: 14px; font-weight: 700; }
|
||||||
|
|
||||||
|
.pill-row { display: flex; gap: 8px; margin-top: 12px; }
|
||||||
|
.status-pill { padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 500; background: #F5F5F7; color: var(--text-secondary); }
|
||||||
|
.status-pill.red { background: rgba(255,59,48,0.08); color: var(--color-up); }
|
||||||
|
.status-pill.green { background: rgba(52,199,89,0.08); color: var(--color-down); }
|
||||||
|
.status-pill.orange { background: rgba(255,159,10,0.08); color: var(--color-neutral); }
|
||||||
|
|
||||||
|
/* Key Points */
|
||||||
|
.kp-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #F5F5F7; font-size: 13px; }
|
||||||
|
.kp-row:last-child { border-bottom: none; }
|
||||||
|
.kp-tag { font-weight: 600; color: var(--text-secondary); }
|
||||||
|
.kp-val { font-weight: 700; }
|
||||||
|
|
||||||
|
/* History */
|
||||||
|
.history-bar {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
padding: 16px 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.hist-title { font-size: 14px; font-weight: 600; color: var(--text-secondary); }
|
||||||
|
.hist-link { font-size: 13px; color: var(--color-brand); font-weight: 500; cursor: pointer; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
<div class="back-btn">← 返回</div>
|
||||||
|
<div class="header-title">原油 SC2606</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<div class="icon-btn">🔔</div>
|
||||||
|
<div class="icon-btn">⋮</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- Info Bar -->
|
||||||
|
<div class="info-bar">
|
||||||
|
<div class="info-left">
|
||||||
|
<span class="symbol">SC2606</span>
|
||||||
|
<span class="contract">SC</span>
|
||||||
|
<div>
|
||||||
|
<span class="price-main price-down">644.5</span>
|
||||||
|
<span class="price-change price-down">↓ -13.3 (-2.02%)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ohlc">
|
||||||
|
<span>开 <b>657.8</b></span>
|
||||||
|
<span>高 <b class="price-up">664.8</b></span>
|
||||||
|
<span>低 <b class="price-down">630.2</b></span>
|
||||||
|
<span>量 <b>1,668,293</b></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Section -->
|
||||||
|
<div class="chart-section">
|
||||||
|
<div class="chart-header">
|
||||||
|
<div class="timeframes">
|
||||||
|
<div class="tf-btn">分时</div>
|
||||||
|
<div class="tf-btn">5M</div>
|
||||||
|
<div class="tf-btn active">15M</div>
|
||||||
|
<div class="tf-btn">30M</div>
|
||||||
|
<div class="tf-btn">1H</div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:12px; color:var(--text-tertiary);">MA5: 652.1 MA10: 655.4 MA20: 660.2</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-placeholder">
|
||||||
|
<!-- Grid Lines -->
|
||||||
|
<div class="grid-line grid-h" style="top: 20%;"></div>
|
||||||
|
<div class="grid-line grid-h" style="top: 40%;"></div>
|
||||||
|
<div class="grid-line grid-h" style="top: 60%;"></div>
|
||||||
|
<div class="grid-line grid-h" style="top: 80%;"></div>
|
||||||
|
<div class="grid-line grid-v" style="left: 20%;"></div>
|
||||||
|
<div class="grid-line grid-v" style="left: 40%;"></div>
|
||||||
|
<div class="grid-line grid-v" style="left: 60%;"></div>
|
||||||
|
<div class="grid-line grid-v" style="left: 80%;"></div>
|
||||||
|
|
||||||
|
<!-- Simulated Candles via CSS/SVG is complex, using text placeholder for prototype -->
|
||||||
|
<div style="text-align:center;">
|
||||||
|
<div style="font-size:48px; color:var(--color-down); opacity:0.1; margin-bottom:10px;">📉</div>
|
||||||
|
<div>K 线图区域 (ECharts / TradingView)</div>
|
||||||
|
<div style="font-size:12px; margin-top:4px;">15 分钟周期 | 2026-05-23</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Side Panel -->
|
||||||
|
<div class="side-panel">
|
||||||
|
|
||||||
|
<!-- AI Analysis -->
|
||||||
|
<div class="panel-card">
|
||||||
|
<div class="card-title">
|
||||||
|
<span> AI 思维分析</span>
|
||||||
|
<span class="ai-tag">智能</span>
|
||||||
|
</div>
|
||||||
|
<div class="analysis-text">
|
||||||
|
SC2606 呈多周期空头排列,均线全面压制且量能极度萎缩,短期 30/60 分钟 KDJ 进入超卖区但无底背离与放量配合,整体处于缩量阴跌格局。
|
||||||
|
</div>
|
||||||
|
<div class="data-grid">
|
||||||
|
<div class="data-item"><span class="label">建议方向</span><span class="val price-down">观望</span></div>
|
||||||
|
<div class="data-item"><span class="label">置信度</span><span class="val">30%</span></div>
|
||||||
|
<div class="data-item"><span class="label">入场区间</span><span class="val">640.5 - 642.5</span></div>
|
||||||
|
<div class="data-item"><span class="label">止损位</span><span class="val price-up">645.5</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Indicators -->
|
||||||
|
<div class="panel-card">
|
||||||
|
<div class="card-title"> 技术指标</div>
|
||||||
|
<div class="indicator-grid">
|
||||||
|
<div class="indicator-box">
|
||||||
|
<div class="ind-name">MACD</div>
|
||||||
|
<div class="ind-val price-down">Down</div>
|
||||||
|
<div style="font-size:10px; color:var(--text-tertiary);">零轴下</div>
|
||||||
|
</div>
|
||||||
|
<div class="indicator-box">
|
||||||
|
<div class="ind-name">KDJ</div>
|
||||||
|
<div class="ind-val" style="color:var(--color-neutral);">超卖</div>
|
||||||
|
<div style="font-size:10px; color:var(--text-tertiary);">未金叉</div>
|
||||||
|
</div>
|
||||||
|
<div class="indicator-box">
|
||||||
|
<div class="ind-name">RSI</div>
|
||||||
|
<div class="ind-val">--</div>
|
||||||
|
<div style="font-size:10px; color:var(--text-tertiary);">--</div>
|
||||||
|
</div>
|
||||||
|
<div class="indicator-box">
|
||||||
|
<div class="ind-name">BOLL</div>
|
||||||
|
<div class="ind-val">--</div>
|
||||||
|
<div style="font-size:10px; color:var(--text-tertiary);">--</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Key Points -->
|
||||||
|
<div class="panel-card">
|
||||||
|
<div class="card-title">📍 关键点位</div>
|
||||||
|
<div class="kp-row">
|
||||||
|
<span class="kp-tag">R1 (阻力)</span>
|
||||||
|
<span class="kp-val price-up">642.4</span>
|
||||||
|
</div>
|
||||||
|
<div class="kp-row">
|
||||||
|
<span class="kp-tag">R2 (阻力)</span>
|
||||||
|
<span class="kp-val price-up">648.8</span>
|
||||||
|
</div>
|
||||||
|
<div class="kp-row" style="background: #F5F5F7; margin: 4px -12px; padding: 8px 12px; border-radius: 6px;">
|
||||||
|
<span class="kp-tag" style="color:var(--color-brand);">PP (中枢)</span>
|
||||||
|
<span class="kp-val" style="color:var(--color-brand);">638.1</span>
|
||||||
|
</div>
|
||||||
|
<div class="kp-row">
|
||||||
|
<span class="kp-tag">S1 (支撑)</span>
|
||||||
|
<span class="kp-val price-down">631.7</span>
|
||||||
|
</div>
|
||||||
|
<div class="kp-row">
|
||||||
|
<span class="kp-tag">S2 (支撑)</span>
|
||||||
|
<span class="kp-val price-down">627.4</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Multi-TF -->
|
||||||
|
<div class="panel-card">
|
||||||
|
<div class="card-title">🕒 多周期趋势</div>
|
||||||
|
<div class="pill-row">
|
||||||
|
<div class="status-pill red">60M 偏空</div>
|
||||||
|
<div class="status-pill orange">30M 震荡</div>
|
||||||
|
<div class="status-pill red">15M 偏空</div>
|
||||||
|
<div class="status-pill orange">5M 震荡</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- History Bar -->
|
||||||
|
<div class="history-bar">
|
||||||
|
<div>
|
||||||
|
<span class="hist-title">🕰 历史分析记录</span>
|
||||||
|
<span style="font-size:12px; color:var(--text-tertiary); margin-left:12px;">SC2606 呈多周期空头排列...</span>
|
||||||
|
</div>
|
||||||
|
<span class="hist-link">查看全部 →</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,340 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - AI 分析报告 (Apple Style)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-page: #F5F5F7;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
--text-primary: #1D1D1F;
|
||||||
|
--text-secondary: #6E6E73;
|
||||||
|
--text-tertiary: #86868B;
|
||||||
|
|
||||||
|
--color-up: #FF3B30;
|
||||||
|
--color-down: #34C759;
|
||||||
|
--color-neutral: #FF9F0A;
|
||||||
|
--color-brand: #007AFF;
|
||||||
|
--color-ai: #AF52DE;
|
||||||
|
|
||||||
|
--shadow-modal: 0 24px 80px rgba(0,0,0,0.15);
|
||||||
|
--radius-modal: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
|
||||||
|
background-color: var(--bg-page);
|
||||||
|
color: var(--text-primary);
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Background Simulation */
|
||||||
|
.bg-blur {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect fill="%23F5F5F7" width="100" height="100"/><rect fill="%23E5E5EA" x="10" y="10" width="80" height="80" rx="16"/></svg>');
|
||||||
|
filter: blur(10px) brightness(0.95);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
width: 560px;
|
||||||
|
max-height: 85vh;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-modal);
|
||||||
|
box-shadow: var(--shadow-modal);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(255,255,255,0.95);
|
||||||
|
}
|
||||||
|
.modal-title { font-size: 18px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
||||||
|
.modal-date { font-size: 12px; color: var(--text-tertiary); font-weight: 400; }
|
||||||
|
.close-btn { width: 28px; height: 28px; background: #F5F5F7; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--text-secondary); font-size: 14px; }
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
.modal-body::-webkit-scrollbar { width: 6px; }
|
||||||
|
.modal-body::-webkit-scrollbar-thumb { background: #E5E5EA; border-radius: 3px; }
|
||||||
|
|
||||||
|
/* Quote */
|
||||||
|
.quote {
|
||||||
|
background: rgba(175, 82, 222, 0.06);
|
||||||
|
border-left: 4px solid var(--color-ai);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section */
|
||||||
|
.section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-ai);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.table-container {
|
||||||
|
border: 1px solid rgba(0,0,0,0.05);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
th { background: #F5F5F7; text-align: left; padding: 10px 12px; font-weight: 600; color: var(--text-secondary); }
|
||||||
|
td { padding: 12px; border-top: 1px solid rgba(0,0,0,0.05); color: var(--text-primary); }
|
||||||
|
tr:last-child td { border-bottom: none; }
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.status-down { background: rgba(52,199,89,0.1); color: var(--color-down); }
|
||||||
|
.status-up { background: rgba(255,59,48,0.1); color: var(--color-up); }
|
||||||
|
.status-neutral { background: rgba(142,142,147,0.1); color: var(--text-secondary); }
|
||||||
|
|
||||||
|
/* Stats Row */
|
||||||
|
.stats-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.stat-box {
|
||||||
|
background: #F5F5F7;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label { font-size: 11px; color: var(--text-tertiary); margin-bottom: 4px; }
|
||||||
|
.stat-val { font-size: 16px; font-weight: 700; }
|
||||||
|
.stat-val.red { color: var(--color-up); }
|
||||||
|
.stat-val.blue { color: var(--color-brand); }
|
||||||
|
|
||||||
|
/* KDJ Grid */
|
||||||
|
.kdj-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.kdj-card {
|
||||||
|
background: #F5F5F7;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.kdj-label { font-size: 11px; color: var(--text-tertiary); font-weight: 500; margin-bottom: 4px; }
|
||||||
|
.kdj-text { font-size: 13px; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Key Points */
|
||||||
|
.kp-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #F5F5F7;
|
||||||
|
}
|
||||||
|
.kp-row:last-child { border-bottom: none; }
|
||||||
|
.kp-tag { font-weight: 600; color: var(--text-secondary); font-size: 13px; }
|
||||||
|
.kp-val { font-weight: 700; font-size: 15px; }
|
||||||
|
|
||||||
|
/* Risk */
|
||||||
|
.risk-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.risk-icon { color: var(--color-neutral); margin-top: 2px; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="bg-blur"></div>
|
||||||
|
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title">
|
||||||
|
<span style="font-size:18px;">📄</span> SC2606 AI 分析报告
|
||||||
|
</div>
|
||||||
|
<div class="modal-date">2026/5/23 18:04</div>
|
||||||
|
<div class="close-btn">✕</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Quote -->
|
||||||
|
<div class="quote">
|
||||||
|
"SC2606 呈多周期空头排列,均线全面压制且量能极度萎缩,短期 30/60 分钟 KDJ 进入超卖区但无底背离与放量配合,整体处于缩量阴跌格局。建议观望或极轻仓逢高试空。"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div>
|
||||||
|
<div class="section-title">🔵 AI 思维分析</div>
|
||||||
|
<div class="table-container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>周期</th>
|
||||||
|
<th>MACD</th>
|
||||||
|
<th>成交量</th>
|
||||||
|
<th>KDJ 状态</th>
|
||||||
|
<th>结论</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><b>60 分钟</b></td>
|
||||||
|
<td><span class="status-badge status-down">Down</span></td>
|
||||||
|
<td>极度缩量</td>
|
||||||
|
<td><span class="status-badge status-down">超卖</span></td>
|
||||||
|
<td>空头趋势明确,下跌动能衰竭但无资金承接。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>30 分钟</b></td>
|
||||||
|
<td><span class="status-badge status-down">Down</span></td>
|
||||||
|
<td>缩量</td>
|
||||||
|
<td><span class="status-badge status-down">超卖</span></td>
|
||||||
|
<td>超卖引发技术性反弹预期,但缺乏量能验证。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>15 分钟</b></td>
|
||||||
|
<td><span class="status-badge status-down">Down</span></td>
|
||||||
|
<td>缩量</td>
|
||||||
|
<td><span class="status-badge status-neutral">中性偏弱</span></td>
|
||||||
|
<td>反弹力度羸弱,受制于 MA10/20 压制。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>5 分钟</b></td>
|
||||||
|
<td><span class="status-badge status-neutral">Neutral</span></td>
|
||||||
|
<td>极度缩量</td>
|
||||||
|
<td><span class="status-badge status-neutral">中性</span></td>
|
||||||
|
<td>微观结构处于无序震荡,等待放量选择方向。</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<div class="stats-row">
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">入场区间</div>
|
||||||
|
<div class="stat-val">640.5-642.5</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">止损位</div>
|
||||||
|
<div class="stat-val red">645.5</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">建议仓位</div>
|
||||||
|
<div class="stat-val">轻仓</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">纪律评分</div>
|
||||||
|
<div class="stat-val blue">4/11</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- KDJ -->
|
||||||
|
<div>
|
||||||
|
<div class="section-title">🟣 KDJ 诊断</div>
|
||||||
|
<div class="kdj-grid">
|
||||||
|
<div class="kdj-card">
|
||||||
|
<div class="kdj-label">当前状态</div>
|
||||||
|
<div class="kdj-text">30/60 分钟超卖区</div>
|
||||||
|
</div>
|
||||||
|
<div class="kdj-card">
|
||||||
|
<div class="kdj-label">背离情况</div>
|
||||||
|
<div class="kdj-text">无底背离 (价格新低 KDJ 未新低)</div>
|
||||||
|
</div>
|
||||||
|
<div class="kdj-card">
|
||||||
|
<div class="kdj-label">钝化</div>
|
||||||
|
<div class="kdj-text">否</div>
|
||||||
|
</div>
|
||||||
|
<div class="kdj-card">
|
||||||
|
<div class="kdj-label">建议</div>
|
||||||
|
<div class="kdj-text" style="font-size:11px; font-weight:400; line-height:1.4;">超卖区仅提示反弹概率,非反转信号。严禁左侧接飞刀。</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Key Points -->
|
||||||
|
<div>
|
||||||
|
<div class="section-title">📍 关键点位</div>
|
||||||
|
<div style="display:flex; gap:8px;">
|
||||||
|
<div class="stat-box" style="flex:1;">
|
||||||
|
<div class="stat-label">R2</div>
|
||||||
|
<div class="stat-val red">648.8</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box" style="flex:1;">
|
||||||
|
<div class="stat-label">R1</div>
|
||||||
|
<div class="stat-val red">642.4</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box" style="flex:1; background:rgba(0,122,255,0.08);">
|
||||||
|
<div class="stat-label" style="color:var(--color-brand);">PP</div>
|
||||||
|
<div class="stat-val blue">638.1</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box" style="flex:1;">
|
||||||
|
<div class="stat-label">S1</div>
|
||||||
|
<div class="stat-val" style="color:var(--color-down);">631.7</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box" style="flex:1;">
|
||||||
|
<div class="stat-label">S2</div>
|
||||||
|
<div class="stat-val" style="color:var(--color-down);">627.4</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Risk -->
|
||||||
|
<div>
|
||||||
|
<div class="section-title">⚠️ 风险提示</div>
|
||||||
|
<div class="risk-item">
|
||||||
|
<span class="risk-icon">▸</span>
|
||||||
|
<span>技术指标具有滞后性,历史表现不代表未来。</span>
|
||||||
|
</div>
|
||||||
|
<div class="risk-item">
|
||||||
|
<span class="risk-icon">▸</span>
|
||||||
|
<span>需结合基本面和市场情绪综合判断。</span>
|
||||||
|
</div>
|
||||||
|
<div class="risk-item">
|
||||||
|
<span class="risk-icon">▸</span>
|
||||||
|
<span>SC2606 为远月合约,流动性极差,买卖价差大,实际交易滑点可能远超技术测算。</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,280 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - Mobile Detail (Apple Style)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-page: #F5F5F7;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
--text-primary: #1D1D1F;
|
||||||
|
--text-secondary: #6E6E73;
|
||||||
|
--text-tertiary: #86868B;
|
||||||
|
|
||||||
|
--color-up: #FF3B30;
|
||||||
|
--color-down: #34C759;
|
||||||
|
--color-brand: #007AFF;
|
||||||
|
--color-ai: #AF52DE;
|
||||||
|
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
|
||||||
|
background-color: #E5E5E5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone {
|
||||||
|
width: 390px;
|
||||||
|
height: 844px;
|
||||||
|
background: var(--bg-page);
|
||||||
|
border-radius: 40px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Bar */
|
||||||
|
.status-bar { height: 44px; display: flex; justify-content: space-between; align-items: center; padding: 0 24px; font-size: 14px; font-weight: 600; color: var(--text-primary); background: var(--bg-page); z-index: 10; }
|
||||||
|
|
||||||
|
/* Nav Bar */
|
||||||
|
.nav-bar { height: 44px; padding: 0 16px; display: flex; align-items: center; justify-content: space-between; background: var(--bg-page); position: sticky; top: 0; z-index: 10; }
|
||||||
|
.nav-back { font-size: 15px; color: var(--color-brand); font-weight: 500; display: flex; align-items: center; gap: 2px; }
|
||||||
|
.nav-title { font-size: 16px; font-weight: 600; position: absolute; left: 50%; transform: translateX(-50%); }
|
||||||
|
.nav-actions { display: flex; gap: 16px; font-size: 16px; color: var(--color-brand); }
|
||||||
|
|
||||||
|
/* Content Area */
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.content::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
/* Ticker Header */
|
||||||
|
.ticker {
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.price-main { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; line-height: 1; }
|
||||||
|
.price-up { color: var(--color-up); }
|
||||||
|
.price-down { color: var(--color-down); }
|
||||||
|
.change-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; font-size: 14px; font-weight: 600; }
|
||||||
|
|
||||||
|
.ohlc-row { display: flex; gap: 16px; margin-top: 12px; font-size: 12px; color: var(--text-tertiary); border-top: 1px solid #F5F5F7; padding-top: 10px; }
|
||||||
|
.ohlc-item b { color: var(--text-primary); font-weight: 600; margin-left: 4px; }
|
||||||
|
|
||||||
|
/* Chart */
|
||||||
|
.chart-area {
|
||||||
|
height: 260px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.chart-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 12px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.tf-btn { padding: 4px 8px; background: rgba(255,255,255,0.8); border-radius: 6px; font-size: 11px; font-weight: 500; color: var(--text-secondary); }
|
||||||
|
.tf-btn.active { background: var(--color-brand); color: #fff; }
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
background: #F5F5F7;
|
||||||
|
margin: 0 16px 12px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tab.active { background: #FFFFFF; color: var(--text-primary); font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
margin: 0 16px 16px 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.card-title { font-size: 15px; font-weight: 700; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
.ai-tag { font-size: 10px; background: rgba(175, 82, 222, 0.1); color: var(--color-ai); padding: 2px 6px; border-radius: 4px; font-weight: 600; }
|
||||||
|
|
||||||
|
.analysis-text { font-size: 13px; color: var(--text-secondary); line-height: 1.5; background: #FAFAFA; padding: 12px; border-radius: 8px; border-left: 3px solid var(--color-ai); margin-bottom: 12px; }
|
||||||
|
|
||||||
|
.data-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
|
.data-item { display: flex; justify-content: space-between; font-size: 13px; }
|
||||||
|
.label { color: var(--text-tertiary); }
|
||||||
|
.val { font-weight: 600; }
|
||||||
|
|
||||||
|
/* Indicators Grid */
|
||||||
|
.ind-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||||
|
.ind-box { background: #F5F5F7; padding: 12px; border-radius: 10px; text-align: center; }
|
||||||
|
.ind-name { font-size: 11px; color: var(--text-tertiary); margin-bottom: 4px; }
|
||||||
|
.ind-val { font-size: 14px; font-weight: 700; }
|
||||||
|
|
||||||
|
/* Key Points */
|
||||||
|
.kp-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #F5F5F7; font-size: 13px; }
|
||||||
|
.kp-row:last-child { border-bottom: none; }
|
||||||
|
.kp-tag { font-weight: 600; color: var(--text-secondary); }
|
||||||
|
.kp-val { font-weight: 700; }
|
||||||
|
|
||||||
|
/* History */
|
||||||
|
.history-card { margin: 0 16px 40px 16px; padding: 16px; background: var(--bg-card); border-radius: 16px; box-shadow: var(--shadow-sm); display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
.hist-label { font-size: 14px; font-weight: 600; }
|
||||||
|
.hist-link { font-size: 13px; color: var(--color-brand); font-weight: 500; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="phone">
|
||||||
|
<div class="status-bar">
|
||||||
|
<span>9:41</span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-bar">
|
||||||
|
<div class="nav-back">←</div>
|
||||||
|
<div class="nav-title">原油 SC2606</div>
|
||||||
|
<div class="nav-actions">
|
||||||
|
<span>🔔</span>
|
||||||
|
<span>⋮</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<!-- Ticker -->
|
||||||
|
<div class="ticker">
|
||||||
|
<div class="price-main price-down">644.5</div>
|
||||||
|
<div class="change-row">
|
||||||
|
<span class="price-down">↓ 13.3</span>
|
||||||
|
<span class="price-down" style="background:rgba(52,199,89,0.1); padding:2px 6px; border-radius:4px;">-2.02%</span>
|
||||||
|
</div>
|
||||||
|
<div class="ohlc-row">
|
||||||
|
<div class="ohlc-item">开 <b>657.8</b></div>
|
||||||
|
<div class="ohlc-item">高 <b class="price-up">664.8</b></div>
|
||||||
|
<div class="ohlc-item">低 <b class="price-down">630.2</b></div>
|
||||||
|
<div class="ohlc-item">量 <b>1.6M</b></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart -->
|
||||||
|
<div class="chart-area">
|
||||||
|
<div class="chart-toolbar">
|
||||||
|
<div class="tf-btn">5M</div>
|
||||||
|
<div class="tf-btn active">15M</div>
|
||||||
|
<div class="tf-btn">1H</div>
|
||||||
|
<div class="tf-btn">日</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align:center;">
|
||||||
|
<div style="font-size:40px; margin-bottom:8px;"></div>
|
||||||
|
<div>K 线图区域</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="tab active">AI 分析</div>
|
||||||
|
<div class="tab">指标</div>
|
||||||
|
<div class="tab">点位</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- AI Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">
|
||||||
|
<span>🧠 思维分析</span>
|
||||||
|
<span class="ai-tag">智能</span>
|
||||||
|
</div>
|
||||||
|
<div class="analysis-text">
|
||||||
|
SC2606 呈多周期空头排列,均线全面压制且量能极度萎缩。建议观望或极轻仓逢高试空。
|
||||||
|
</div>
|
||||||
|
<div class="data-grid">
|
||||||
|
<div class="data-item"><span class="label">置信度</span><span class="val">30%</span></div>
|
||||||
|
<div class="data-item"><span class="label">止损位</span><span class="val price-up">645.5</span></div>
|
||||||
|
<div class="data-item"><span class="label">入场</span><span class="val">640.5-642.5</span></div>
|
||||||
|
<div class="data-item"><span class="label">仓位</span><span class="val">轻仓</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Indicators Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">📊 技术指标</div>
|
||||||
|
<div class="ind-grid">
|
||||||
|
<div class="ind-box">
|
||||||
|
<div class="ind-name">MACD</div>
|
||||||
|
<div class="ind-val price-down">Down</div>
|
||||||
|
</div>
|
||||||
|
<div class="ind-box">
|
||||||
|
<div class="ind-name">KDJ</div>
|
||||||
|
<div class="ind-val" style="color:var(--color-up);">超卖</div>
|
||||||
|
</div>
|
||||||
|
<div class="ind-box">
|
||||||
|
<div class="ind-name">RSI</div>
|
||||||
|
<div class="ind-val">--</div>
|
||||||
|
</div>
|
||||||
|
<div class="ind-box">
|
||||||
|
<div class="ind-name">BOLL</div>
|
||||||
|
<div class="ind-val">--</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Points Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">📍 关键点位</div>
|
||||||
|
<div class="kp-row">
|
||||||
|
<span class="kp-tag">R1</span>
|
||||||
|
<span class="kp-val price-up">642.4</span>
|
||||||
|
</div>
|
||||||
|
<div class="kp-row">
|
||||||
|
<span class="kp-tag">PP (中枢)</span>
|
||||||
|
<span class="kp-val" style="color:var(--color-brand);">638.1</span>
|
||||||
|
</div>
|
||||||
|
<div class="kp-row">
|
||||||
|
<span class="kp-tag">S1</span>
|
||||||
|
<span class="kp-val price-down">631.7</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- History -->
|
||||||
|
<div class="history-card">
|
||||||
|
<div class="hist-label">🕰 历史分析记录</div>
|
||||||
|
<div class="hist-link">查看 ></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,369 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - Mobile Prototype (Apple Style)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
/* v3.0 Apple Style Colors */
|
||||||
|
--bg-page: #F5F5F7;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
--text-primary: #1D1D1F;
|
||||||
|
--text-secondary: #6E6E73;
|
||||||
|
--text-tertiary: #86868B;
|
||||||
|
--color-up: #FF3B30;
|
||||||
|
--color-down: #34C759;
|
||||||
|
--color-neutral: #FF9F0A;
|
||||||
|
--color-brand: #007AFF;
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
|
|
||||||
|
/* Radius */
|
||||||
|
--radius-card: 16px;
|
||||||
|
--radius-btn: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
|
||||||
|
background-color: #E5E5E5; /* Darker bg to show phone */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone {
|
||||||
|
width: 390px;
|
||||||
|
height: 844px;
|
||||||
|
background: var(--bg-page);
|
||||||
|
border-radius: 40px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Bar */
|
||||||
|
.status-bar {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: var(--bg-page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav Bar */
|
||||||
|
.nav-bar {
|
||||||
|
height: 56px;
|
||||||
|
padding: 0 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: var(--bg-page);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.nav-title { font-size: 20px; font-weight: 700; }
|
||||||
|
.nav-icons { display: flex; gap: 16px; }
|
||||||
|
.icon-btn { width: 32px; height: 32px; background: #FFFFFF; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow-sm); font-size: 14px; cursor: pointer;}
|
||||||
|
|
||||||
|
/* Content Scroll */
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 20px 100px 20px; /* Padding bottom for tab bar */
|
||||||
|
}
|
||||||
|
.content::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
/* Filter Pills */
|
||||||
|
.filter-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.filter-row::-webkit-scrollbar { display: none; }
|
||||||
|
.f-pill {
|
||||||
|
padding: 6px 14px;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.f-pill.active { background: var(--color-brand); color: #FFFFFF; box-shadow: 0 4px 10px rgba(0,122,255,0.3); }
|
||||||
|
|
||||||
|
/* Stats Mini */
|
||||||
|
.stats-mini {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.s-card {
|
||||||
|
flex: 1;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.s-val { font-size: 18px; font-weight: 700; color: var(--text-primary); }
|
||||||
|
.s-label { font-size: 10px; color: var(--text-tertiary); margin-top: 2px; }
|
||||||
|
.s-card.up .s-val { color: var(--color-down); }
|
||||||
|
.s-card.down .s-val { color: var(--color-up); }
|
||||||
|
|
||||||
|
/* List Cards */
|
||||||
|
.card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-top { display: flex; justify-content: space-between; align-items: flex-start; }
|
||||||
|
.code-badge { background: #F5F5F7; padding: 4px 8px; border-radius: 6px; font-size: 11px; font-weight: 600; color: var(--text-tertiary); margin-bottom: 4px; display: inline-block; }
|
||||||
|
.name { font-size: 16px; font-weight: 600; }
|
||||||
|
|
||||||
|
.price-col { text-align: right; }
|
||||||
|
.price { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; }
|
||||||
|
.price.up { color: var(--color-up); }
|
||||||
|
.price.down { color: var(--color-down); }
|
||||||
|
.change { font-size: 12px; font-weight: 600; }
|
||||||
|
.change.up { color: var(--color-up); }
|
||||||
|
.change.down { color: var(--color-down); }
|
||||||
|
|
||||||
|
.action-row { display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
.action-tag { padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; }
|
||||||
|
.tag-watch { background: rgba(255,159,10,0.1); color: var(--color-neutral); }
|
||||||
|
.tag-do { background: rgba(52,199,89,0.1); color: var(--color-down); }
|
||||||
|
|
||||||
|
.score-bar { height: 3px; background: #F5F5F7; border-radius: 2px; width: 80px; overflow: hidden; }
|
||||||
|
.score-fill { height: 100%; border-radius: 2px; }
|
||||||
|
|
||||||
|
.card-meta { display: flex; justify-content: space-between; font-size: 12px; color: var(--text-tertiary); border-top: 1px solid #F5F5F7; padding-top: 10px; }
|
||||||
|
.meta-item span { color: var(--text-primary); font-weight: 600; }
|
||||||
|
.meta-item .red { color: var(--color-up); }
|
||||||
|
.meta-item .green { color: var(--color-down); }
|
||||||
|
|
||||||
|
/* Tab Bar */
|
||||||
|
.tab-bar {
|
||||||
|
height: 83px;
|
||||||
|
background: rgba(255,255,255,0.9);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-top: 1px solid rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-top: 12px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.tab-item { display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; }
|
||||||
|
.tab-icon { width: 24px; height: 24px; background: #E5E5EA; border-radius: 6px; }
|
||||||
|
.tab-icon.active { background: var(--color-brand); }
|
||||||
|
.tab-label { font-size: 10px; color: var(--text-tertiary); font-weight: 500; }
|
||||||
|
.tab-label.active { color: var(--color-brand); }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="phone">
|
||||||
|
<!-- Status Bar -->
|
||||||
|
<div class="status-bar">
|
||||||
|
<span>9:41</span>
|
||||||
|
<span>🔋</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nav Bar -->
|
||||||
|
<div class="nav-bar">
|
||||||
|
<div class="nav-title">期货智析</div>
|
||||||
|
<div class="nav-icons">
|
||||||
|
<div class="icon-btn">🔍</div>
|
||||||
|
<div class="icon-btn">⋮</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="content">
|
||||||
|
<!-- Filters -->
|
||||||
|
<div class="filter-row">
|
||||||
|
<div class="f-pill active">全部</div>
|
||||||
|
<div class="f-pill">自选</div>
|
||||||
|
<div class="f-pill">能源</div>
|
||||||
|
<div class="f-pill">金属</div>
|
||||||
|
<div class="f-pill">农产品</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<div class="stats-mini">
|
||||||
|
<div class="s-card"><div class="s-val">32</div><div class="s-label">监控</div></div>
|
||||||
|
<div class="s-card up"><div class="s-val">7</div><div class="s-label">上涨</div></div>
|
||||||
|
<div class="s-card down"><div class="s-val">10</div><div class="s-label">下跌</div></div>
|
||||||
|
<div class="s-card"><div class="s-val">15</div><div class="s-label">震荡</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-top">
|
||||||
|
<div>
|
||||||
|
<div class="code-badge">SC 原油</div>
|
||||||
|
<div class="name">SC2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-col">
|
||||||
|
<div class="price down">644.5</div>
|
||||||
|
<div class="change down">↓ 2.02%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-row">
|
||||||
|
<div class="action-tag tag-watch">观望</div>
|
||||||
|
<div style="display:flex; align-items:center; gap:6px;">
|
||||||
|
<span style="font-size:11px; color:var(--text-tertiary);">评分</span>
|
||||||
|
<div class="score-bar"><div class="score-fill" style="width:30%; background:var(--color-up);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-meta">
|
||||||
|
<div class="meta-item">压力 <span class="red">642.4</span></div>
|
||||||
|
<div class="meta-item">支撑 <span class="green">631.7</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-top">
|
||||||
|
<div>
|
||||||
|
<div class="code-badge">FU 燃油</div>
|
||||||
|
<div class="name">FU2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-col">
|
||||||
|
<div class="price up">4,680</div>
|
||||||
|
<div class="change up">↑ 1.56%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-row">
|
||||||
|
<div class="action-tag tag-do">做多</div>
|
||||||
|
<div style="display:flex; align-items:center; gap:6px;">
|
||||||
|
<span style="font-size:11px; color:var(--text-tertiary);">评分</span>
|
||||||
|
<div class="score-bar"><div class="score-fill" style="width:65%; background:var(--color-down);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-meta">
|
||||||
|
<div class="meta-item">压力 <span class="red">4,600</span></div>
|
||||||
|
<div class="meta-item">支撑 <span class="green">4,530</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-top">
|
||||||
|
<div>
|
||||||
|
<div class="code-badge">AG 沪银</div>
|
||||||
|
<div class="name">AG2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-col">
|
||||||
|
<div class="price up">18,643</div>
|
||||||
|
<div class="change up">↑ 2.27%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-row">
|
||||||
|
<div class="action-tag tag-do">做多 (反弹)</div>
|
||||||
|
<div style="display:flex; align-items:center; gap:6px;">
|
||||||
|
<span style="font-size:11px; color:var(--text-tertiary);">评分</span>
|
||||||
|
<div class="score-bar"><div class="score-fill" style="width:60%; background:var(--color-neutral);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-meta">
|
||||||
|
<div class="meta-item">压力 <span class="red">18,685</span></div>
|
||||||
|
<div class="meta-item">支撑 <span class="green">18,584</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-top">
|
||||||
|
<div>
|
||||||
|
<div class="code-badge">AU 沪金</div>
|
||||||
|
<div class="name">AU2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-col">
|
||||||
|
<div class="price up">993.34</div>
|
||||||
|
<div class="change up">↑ 0.44%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-row">
|
||||||
|
<div class="action-tag tag-watch">观望</div>
|
||||||
|
<div style="display:flex; align-items:center; gap:6px;">
|
||||||
|
<span style="font-size:11px; color:var(--text-tertiary);">评分</span>
|
||||||
|
<div class="score-bar"><div class="score-fill" style="width:40%; background:var(--color-up);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-meta">
|
||||||
|
<div class="meta-item">压力 <span class="red">993</span></div>
|
||||||
|
<div class="meta-item">支撑 <span class="green">989.5</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-top">
|
||||||
|
<div>
|
||||||
|
<div class="code-badge">CU 沪铜</div>
|
||||||
|
<div class="name">CU2606</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-col">
|
||||||
|
<div class="price up">104,770</div>
|
||||||
|
<div class="change up">↑ 1.05%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-row">
|
||||||
|
<div class="action-tag tag-do">做多</div>
|
||||||
|
<div style="display:flex; align-items:center; gap:6px;">
|
||||||
|
<span style="font-size:11px; color:var(--text-tertiary);">评分</span>
|
||||||
|
<div class="score-bar"><div class="score-fill" style="width:65%; background:var(--color-down);"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-meta">
|
||||||
|
<div class="meta-item">压力 <span class="red">105,250</span></div>
|
||||||
|
<div class="meta-item">支撑 <span class="green">104,450</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab Bar -->
|
||||||
|
<div class="tab-bar">
|
||||||
|
<div class="tab-item">
|
||||||
|
<div class="tab-icon active"></div>
|
||||||
|
<div class="tab-label active">首页</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-item">
|
||||||
|
<div class="tab-icon"></div>
|
||||||
|
<div class="tab-label">自选</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-item">
|
||||||
|
<div class="tab-icon"></div>
|
||||||
|
<div class="tab-label">分析</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-item">
|
||||||
|
<div class="tab-icon"></div>
|
||||||
|
<div class="tab-label">我的</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,247 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - Mobile Report (Apple Style)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-page: #F5F5F7;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
--text-primary: #1D1D1F;
|
||||||
|
--text-secondary: #6E6E73;
|
||||||
|
--text-tertiary: #86868B;
|
||||||
|
|
||||||
|
--color-up: #FF3B30;
|
||||||
|
--color-down: #34C759;
|
||||||
|
--color-brand: #007AFF;
|
||||||
|
--color-ai: #AF52DE;
|
||||||
|
|
||||||
|
--shadow-sheet: 0 -10px 40px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
|
||||||
|
background-color: #E5E5E5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone {
|
||||||
|
width: 390px;
|
||||||
|
height: 844px;
|
||||||
|
background: var(--bg-page);
|
||||||
|
border-radius: 40px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Background Content (Blurred) */
|
||||||
|
.bg-content {
|
||||||
|
padding: 60px 20px;
|
||||||
|
filter: blur(4px) brightness(0.9);
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.skeleton { background: #D1D1D6; border-radius: 8px; margin-bottom: 16px; }
|
||||||
|
.sk-title { height: 30px; width: 60%; margin-bottom: 24px; }
|
||||||
|
.sk-card { height: 150px; width: 100%; margin-bottom: 16px; }
|
||||||
|
.sk-text { height: 12px; width: 100%; margin-bottom: 8px; }
|
||||||
|
.sk-text.short { width: 40%; }
|
||||||
|
|
||||||
|
/* Modal Overlay */
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.3);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom Sheet */
|
||||||
|
.sheet {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 24px 24px 0 0;
|
||||||
|
box-shadow: var(--shadow-sheet);
|
||||||
|
z-index: 20;
|
||||||
|
max-height: 85%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: slideUp 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from { transform: translateY(100%); }
|
||||||
|
to { transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle */
|
||||||
|
.handle-bar {
|
||||||
|
width: 40px;
|
||||||
|
height: 5px;
|
||||||
|
background: #E5E5EA;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 12px auto 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.sheet-header {
|
||||||
|
padding: 16px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
.sheet-title { font-size: 18px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
||||||
|
.close-btn { width: 24px; height: 24px; background: #F5F5F7; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); font-size: 12px; }
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
.sheet-body {
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.sheet-body::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
background: rgba(175, 82, 222, 0.08);
|
||||||
|
border-left: 3px solid var(--color-ai);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title { font-size: 15px; font-weight: 700; color: var(--color-ai); margin-bottom: 10px; display: flex; align-items: center; gap: 6px; }
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.table-wrap {
|
||||||
|
background: #F5F5F7;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid #E5E5EA;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.row:last-child { border-bottom: none; }
|
||||||
|
.row-label { color: var(--text-tertiary); font-weight: 500; }
|
||||||
|
.row-val { font-weight: 700; }
|
||||||
|
.tag { padding: 2px 6px; border-radius: 4px; font-size: 10px; font-weight: 600; }
|
||||||
|
.tag-down { background: rgba(52,199,89,0.15); color: var(--color-down); }
|
||||||
|
.tag-up { background: rgba(255,59,48,0.15); color: var(--color-up); }
|
||||||
|
|
||||||
|
/* Stats Grid */
|
||||||
|
.stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 20px; }
|
||||||
|
.s-box { background: #F5F5F7; padding: 12px; border-radius: 10px; text-align: center; }
|
||||||
|
.s-label { font-size: 11px; color: var(--text-tertiary); margin-bottom: 4px; }
|
||||||
|
.s-val { font-size: 16px; font-weight: 700; }
|
||||||
|
|
||||||
|
/* Risk */
|
||||||
|
.risk-list { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
.risk-item { display: flex; gap: 8px; font-size: 12px; color: var(--text-secondary); line-height: 1.4; }
|
||||||
|
.risk-icon { color: #FF9500; font-size: 14px; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="phone">
|
||||||
|
<!-- Blurred Background -->
|
||||||
|
<div class="bg-content">
|
||||||
|
<div class="skeleton sk-title"></div>
|
||||||
|
<div class="skeleton sk-card"></div>
|
||||||
|
<div class="skeleton sk-card"></div>
|
||||||
|
<div class="skeleton sk-text"></div>
|
||||||
|
<div class="skeleton sk-text short"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Overlay -->
|
||||||
|
<div class="overlay"></div>
|
||||||
|
|
||||||
|
<!-- Bottom Sheet -->
|
||||||
|
<div class="sheet">
|
||||||
|
<div class="handle-bar"></div>
|
||||||
|
|
||||||
|
<div class="sheet-header">
|
||||||
|
<div class="sheet-title">
|
||||||
|
<span></span> AI 分析报告
|
||||||
|
</div>
|
||||||
|
<div class="close-btn">✕</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sheet-body">
|
||||||
|
<div class="quote">
|
||||||
|
"SC2606 呈多周期空头排列,均线全面压制且量能极度萎缩。建议观望或极轻仓逢高试空。"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">🔵 思维分析</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<div class="row">
|
||||||
|
<span class="row-label">60M 周期</span>
|
||||||
|
<span class="row-val"><span class="tag tag-down">超卖</span> 空头趋势明确</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="row-label">30M 周期</span>
|
||||||
|
<span class="row-val"><span class="tag tag-down">超卖</span> 缺乏量能验证</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="row-label">15M 周期</span>
|
||||||
|
<span class="row-val"><span class="tag" style="background:#E5E5EA; color:#6E6E73;">中性</span> 受均线压制</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="s-box">
|
||||||
|
<div class="s-label">入场区间</div>
|
||||||
|
<div class="s-val">640.5-642.5</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-box">
|
||||||
|
<div class="s-label">止损位</div>
|
||||||
|
<div class="s-val" style="color:var(--color-up);">645.5</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-box">
|
||||||
|
<div class="s-label">仓位</div>
|
||||||
|
<div class="s-val">轻仓</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-box">
|
||||||
|
<div class="s-label">评分</div>
|
||||||
|
<div class="s-val" style="color:var(--color-brand);">4/11</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">⚠️ 风险提示</div>
|
||||||
|
<div class="risk-list">
|
||||||
|
<div class="risk-item">
|
||||||
|
<span class="risk-icon">•</span>
|
||||||
|
<span>技术指标具有滞后性,历史表现不代表未来。</span>
|
||||||
|
</div>
|
||||||
|
<div class="risk-item">
|
||||||
|
<span class="risk-icon">•</span>
|
||||||
|
<span>SC2606 为远月合约,流动性极差,注意滑点风险。</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="height: 40px;"></div> <!-- Bottom padding for scroll -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,348 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - Tablet List (Compact 3-Col)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-page: #F5F5F7;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
--text-primary: #1D1D1F;
|
||||||
|
--text-secondary: #6E6E73;
|
||||||
|
--text-tertiary: #86868B;
|
||||||
|
--color-up: #FF3B30;
|
||||||
|
--color-down: #34C759;
|
||||||
|
--color-neutral: #FF9F0A;
|
||||||
|
--color-brand: #007AFF;
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
|
--radius-card: 16px; /* Slightly smaller radius for compact */
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
|
||||||
|
background-color: #E5E5E5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tablet {
|
||||||
|
width: 1024px;
|
||||||
|
height: 640px;
|
||||||
|
background: var(--bg-page);
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 8px solid #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.header {
|
||||||
|
height: 56px;
|
||||||
|
background: rgba(255,255,255,0.8);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.logo { font-size: 16px; font-weight: 700; }
|
||||||
|
.search-bar {
|
||||||
|
background: #F5F5F7;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
width: 300px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.user-avatar { width: 32px; height: 32px; background: #E5E5EA; border-radius: 50%; }
|
||||||
|
|
||||||
|
/* Content */
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16px 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Row (Compact) */
|
||||||
|
.stats-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.stat-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.stat-icon {
|
||||||
|
width: 32px; height: 32px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.stat-icon.blue { background: rgba(0,122,255,0.1); color: var(--color-brand); }
|
||||||
|
.stat-icon.green { background: rgba(52,199,89,0.1); color: var(--color-down); }
|
||||||
|
.stat-icon.red { background: rgba(255,59,48,0.1); color: var(--color-up); }
|
||||||
|
.stat-icon.orange { background: rgba(255,159,10,0.1); color: var(--color-neutral); }
|
||||||
|
.stat-val { font-size: 18px; font-weight: 700; line-height: 1.2; }
|
||||||
|
.stat-label { font-size: 11px; color: var(--text-secondary); }
|
||||||
|
|
||||||
|
/* Filters */
|
||||||
|
.filters { display: flex; gap: 8px; }
|
||||||
|
.pill {
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.pill.active { background: var(--color-brand); color: #FFFFFF; }
|
||||||
|
|
||||||
|
/* Grid (3 Columns) */
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact Card */
|
||||||
|
.card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
|
||||||
|
|
||||||
|
.card-header { display: flex; justify-content: space-between; align-items: flex-start; }
|
||||||
|
.code-box {
|
||||||
|
width: 32px; height: 32px;
|
||||||
|
background: #F5F5F7;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 11px; font-weight: 600; color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
.price-area { text-align: right; }
|
||||||
|
.price { font-size: 20px; font-weight: 700; letter-spacing: -0.01em; line-height: 1; }
|
||||||
|
.price.up { color: var(--color-up); }
|
||||||
|
.price.down { color: var(--color-down); }
|
||||||
|
.change { font-size: 11px; font-weight: 600; margin-top: 2px; }
|
||||||
|
.change.up { color: var(--color-up); }
|
||||||
|
.change.down { color: var(--color-down); }
|
||||||
|
|
||||||
|
.action-pill {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.action-pill.do-more { background: rgba(52,199,89,0.1); color: var(--color-down); }
|
||||||
|
.action-pill.watch { background: rgba(255,159,10,0.1); color: var(--color-neutral); }
|
||||||
|
|
||||||
|
.metrics { display: flex; gap: 12px; }
|
||||||
|
.metric { flex: 1; }
|
||||||
|
.metric-label { font-size: 10px; color: var(--text-tertiary); margin-bottom: 4px; display: flex; justify-content: space-between; }
|
||||||
|
.bar-bg { height: 3px; background: #F5F5F7; border-radius: 2px; overflow: hidden; }
|
||||||
|
.bar-fill { height: 100%; border-radius: 2px; }
|
||||||
|
|
||||||
|
.timeframes { display: flex; gap: 6px; }
|
||||||
|
.tf { padding: 3px 8px; border-radius: 6px; font-size: 10px; color: var(--text-tertiary); background: #F5F5F7; }
|
||||||
|
.tf.active { background: #E8F2FF; color: var(--color-brand); font-weight: 500; }
|
||||||
|
|
||||||
|
.card-footer { display: flex; justify-content: space-between; align-items: center; border-top: 1px solid #F5F5F7; padding-top: 10px; font-size: 11px; }
|
||||||
|
.support-resist { color: var(--text-tertiary); }
|
||||||
|
.support-resist span { margin-right: 8px; }
|
||||||
|
.support-resist .red { color: var(--color-up); }
|
||||||
|
.support-resist .green { color: var(--color-down); }
|
||||||
|
.link { color: var(--color-brand); font-weight: 500; text-decoration: none; }
|
||||||
|
|
||||||
|
/* Bottom Nav */
|
||||||
|
.nav-bar {
|
||||||
|
height: 64px;
|
||||||
|
background: rgba(255,255,255,0.95);
|
||||||
|
border-top: 1px solid rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.nav-item { display: flex; flex-direction: column; align-items: center; gap: 2px; color: var(--text-tertiary); font-size: 10px; }
|
||||||
|
.nav-item.active { color: var(--color-brand); }
|
||||||
|
.nav-icon { width: 20px; height: 20px; background: #E5E5EA; border-radius: 4px; }
|
||||||
|
.nav-item.active .nav-icon { background: var(--color-brand); }
|
||||||
|
.fab {
|
||||||
|
width: 48px; height: 48px;
|
||||||
|
background: var(--color-brand);
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
color: white; font-size: 20px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,122,255,0.3);
|
||||||
|
margin-top: -24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="tablet">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">◈ 期货智析</div>
|
||||||
|
<div class="search-bar">🔍 搜索品种...</div>
|
||||||
|
<div class="user-avatar"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="stats-row">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon blue">≡</div>
|
||||||
|
<div><div class="stat-val">32</div><div class="stat-label">监控</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon green">↗</div>
|
||||||
|
<div><div class="stat-val">7</div><div class="stat-label">上涨</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon red">↘</div>
|
||||||
|
<div><div class="stat-val">10</div><div class="stat-label">下跌</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon orange">↔</div>
|
||||||
|
<div><div class="stat-val">15</div><div class="stat-label">震荡</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filters">
|
||||||
|
<div class="pill active">全部</div>
|
||||||
|
<div class="pill">能源</div>
|
||||||
|
<div class="pill">金属</div>
|
||||||
|
<div class="pill">农产品</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<!-- 6 Cards -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="code-box">SC</div>
|
||||||
|
<div class="price-area"><div class="price down">644.5</div><div class="change down">↓ 2.02%</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill watch">观望</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric"><div class="metric-label">成功率 <b>60%</b></div><div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div></div>
|
||||||
|
<div class="metric"><div class="metric-label">评分 <b>30</b></div><div class="bar-bg"><div class="bar-fill" style="width:30%; background:var(--color-up);"></div></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes"><div class="tf">5M</div><div class="tf active">15M</div><div class="tf">30M</div></div>
|
||||||
|
<div class="card-footer"><div class="support-resist"><span>压 <b class="red">642</b></span><span>支 <b class="green">631</b></span></div><a href="#" class="link">详情 →</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="code-box">FU</div>
|
||||||
|
<div class="price-area"><div class="price up">4,680</div><div class="change up">↑ 1.56%</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill do-more">做多</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric"><div class="metric-label">成功率 <b>85%</b></div><div class="bar-bg"><div class="bar-fill" style="width:85%; background:var(--color-down);"></div></div></div>
|
||||||
|
<div class="metric"><div class="metric-label">评分 <b>65</b></div><div class="bar-bg"><div class="bar-fill" style="width:65%; background:var(--color-down);"></div></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes"><div class="tf">5M</div><div class="tf active">15M</div><div class="tf">30M</div></div>
|
||||||
|
<div class="card-footer"><div class="support-resist"><span>压 <b class="red">4.6k</b></span><span>支 <b class="green">4.5k</b></span></div><a href="#" class="link">详情 →</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="code-box">AG</div>
|
||||||
|
<div class="price-area"><div class="price up">18,643</div><div class="change up">↑ 2.27%</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill do-more">做多</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric"><div class="metric-label">成功率 <b>60%</b></div><div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div></div>
|
||||||
|
<div class="metric"><div class="metric-label">评分 <b>60</b></div><div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes"><div class="tf">5M</div><div class="tf active">15M</div><div class="tf">30M</div></div>
|
||||||
|
<div class="card-footer"><div class="support-resist"><span>压 <b class="red">18.6k</b></span><span>支 <b class="green">18.5k</b></span></div><a href="#" class="link">详情 →</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="code-box">AU</div>
|
||||||
|
<div class="price-area"><div class="price up">993.34</div><div class="change up">↑ 0.44%</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill watch">观望</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric"><div class="metric-label">成功率 <b>60%</b></div><div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div></div>
|
||||||
|
<div class="metric"><div class="metric-label">评分 <b>40</b></div><div class="bar-bg"><div class="bar-fill" style="width:40%; background:var(--color-up);"></div></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes"><div class="tf">5M</div><div class="tf active">15M</div><div class="tf">30M</div></div>
|
||||||
|
<div class="card-footer"><div class="support-resist"><span>压 <b class="red">993</b></span><span>支 <b class="green">989</b></span></div><a href="#" class="link">详情 →</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="code-box">CU</div>
|
||||||
|
<div class="price-area"><div class="price up">104,770</div><div class="change up">↑ 1.05%</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill do-more">做多</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric"><div class="metric-label">成功率 <b>85%</b></div><div class="bar-bg"><div class="bar-fill" style="width:85%; background:var(--color-down);"></div></div></div>
|
||||||
|
<div class="metric"><div class="metric-label">评分 <b>65</b></div><div class="bar-bg"><div class="bar-fill" style="width:65%; background:var(--color-down);"></div></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes"><div class="tf">5M</div><div class="tf active">15M</div><div class="tf">30M</div></div>
|
||||||
|
<div class="card-footer"><div class="support-resist"><span>压 <b class="red">105k</b></span><span>支 <b class="green">104k</b></span></div><a href="#" class="link">详情 →</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="code-box">NI</div>
|
||||||
|
<div class="price-area"><div class="price down">143,330</div><div class="change down">↓ 0.31%</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="action-pill watch">观望</div>
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric"><div class="metric-label">成功率 <b>60%</b></div><div class="bar-bg"><div class="bar-fill" style="width:60%; background:var(--color-neutral);"></div></div></div>
|
||||||
|
<div class="metric"><div class="metric-label">评分 <b>45</b></div><div class="bar-bg"><div class="bar-fill" style="width:45%; background:var(--color-up);"></div></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="timeframes"><div class="tf">5M</div><div class="tf active">15M</div><div class="tf">30M</div></div>
|
||||||
|
<div class="card-footer"><div class="support-resist"><span>压 <b class="red">144k</b></span><span>支 <b class="green">143k</b></span></div><a href="#" class="link">详情 →</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-bar">
|
||||||
|
<div class="nav-item active"><div class="nav-icon"></div><span>首页</span></div>
|
||||||
|
<div class="nav-item"><div class="nav-icon"></div><span>自选</span></div>
|
||||||
|
<div class="fab">+</div>
|
||||||
|
<div class="nav-item"><div class="nav-icon"></div><span>分析</span></div>
|
||||||
|
<div class="nav-item"><div class="nav-icon"></div><span>我的</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,209 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - Tablet Report (Right Drawer)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-page: #F5F5F7;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
--text-primary: #1D1D1F;
|
||||||
|
--text-secondary: #6E6E73;
|
||||||
|
--text-tertiary: #86868B;
|
||||||
|
--color-up: #FF3B30;
|
||||||
|
--color-down: #34C759;
|
||||||
|
--color-brand: #007AFF;
|
||||||
|
--color-ai: #AF52DE;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "PingFang SC", sans-serif;
|
||||||
|
background-color: #E5E5E5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tablet {
|
||||||
|
width: 1024px;
|
||||||
|
height: 640px;
|
||||||
|
background: var(--bg-page);
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 8px solid #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Background (Blurred) */
|
||||||
|
.bg-content {
|
||||||
|
padding: 60px 20px;
|
||||||
|
filter: blur(4px) brightness(0.9);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.skeleton { background: #D1D1D6; border-radius: 8px; margin-bottom: 16px; }
|
||||||
|
.sk-title { height: 30px; width: 40%; margin-bottom: 24px; }
|
||||||
|
.sk-card { height: 120px; width: 100%; margin-bottom: 16px; }
|
||||||
|
.sk-text { height: 12px; width: 100%; margin-bottom: 8px; }
|
||||||
|
|
||||||
|
/* Right Drawer */
|
||||||
|
.drawer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 450px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
box-shadow: -10px 0 40px rgba(0,0,0,0.1);
|
||||||
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: slideLeft 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideLeft {
|
||||||
|
from { transform: translateX(100%); }
|
||||||
|
to { transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-header {
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.drawer-title { font-size: 18px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
||||||
|
.close-btn { width: 28px; height: 28px; background: #F5F5F7; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); font-size: 14px; cursor: pointer; }
|
||||||
|
|
||||||
|
.drawer-body {
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.drawer-body::-webkit-scrollbar { width: 4px; }
|
||||||
|
.drawer-body::-webkit-scrollbar-thumb { background: #E5E5EA; border-radius: 2px; }
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
background: rgba(175, 82, 222, 0.08);
|
||||||
|
border-left: 3px solid var(--color-ai);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title { font-size: 14px; font-weight: 700; color: var(--color-ai); margin-bottom: 10px; display: flex; align-items: center; gap: 6px; }
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.table-wrap { background: #F5F5F7; border-radius: 10px; padding: 2px; margin-bottom: 20px; }
|
||||||
|
.row { display: flex; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid #E5E5EA; font-size: 12px; }
|
||||||
|
.row:last-child { border-bottom: none; }
|
||||||
|
.row-label { color: var(--text-tertiary); font-weight: 500; }
|
||||||
|
.row-val { font-weight: 700; }
|
||||||
|
.tag { padding: 2px 6px; border-radius: 4px; font-size: 10px; font-weight: 600; }
|
||||||
|
.tag-down { background: rgba(52,199,89,0.15); color: var(--color-down); }
|
||||||
|
.tag-up { background: rgba(255,59,48,0.15); color: var(--color-up); }
|
||||||
|
|
||||||
|
/* Stats Grid */
|
||||||
|
.stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 20px; }
|
||||||
|
.s-box { background: #F5F5F7; padding: 12px; border-radius: 10px; text-align: center; }
|
||||||
|
.s-label { font-size: 11px; color: var(--text-tertiary); margin-bottom: 4px; }
|
||||||
|
.s-val { font-size: 16px; font-weight: 700; }
|
||||||
|
|
||||||
|
/* Risk */
|
||||||
|
.risk-list { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
.risk-item { display: flex; gap: 8px; font-size: 12px; color: var(--text-secondary); line-height: 1.4; }
|
||||||
|
.risk-icon { color: #FF9500; font-size: 14px; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="tablet">
|
||||||
|
<!-- Blurred Background -->
|
||||||
|
<div class="bg-content">
|
||||||
|
<div class="skeleton sk-title"></div>
|
||||||
|
<div class="skeleton sk-card"></div>
|
||||||
|
<div class="skeleton sk-card"></div>
|
||||||
|
<div class="skeleton sk-text"></div>
|
||||||
|
<div class="skeleton sk-text"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Drawer -->
|
||||||
|
<div class="drawer">
|
||||||
|
<div class="drawer-header">
|
||||||
|
<div class="drawer-title">
|
||||||
|
<span></span> AI 分析报告
|
||||||
|
</div>
|
||||||
|
<div class="close-btn">✕</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="drawer-body">
|
||||||
|
<div class="quote">
|
||||||
|
"SC2606 呈多周期空头排列,均线全面压制且量能极度萎缩。建议观望或极轻仓逢高试空。"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">🔵 思维分析</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<div class="row">
|
||||||
|
<span class="row-label">60M 周期</span>
|
||||||
|
<span class="row-val"><span class="tag tag-down">超卖</span> 空头趋势明确</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="row-label">30M 周期</span>
|
||||||
|
<span class="row-val"><span class="tag tag-down">超卖</span> 缺乏量能验证</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="row-label">15M 周期</span>
|
||||||
|
<span class="row-val"><span class="tag" style="background:#E5E5EA; color:#6E6E73;">中性</span> 受均线压制</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="s-box">
|
||||||
|
<div class="s-label">入场区间</div>
|
||||||
|
<div class="s-val">640.5-642.5</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-box">
|
||||||
|
<div class="s-label">止损位</div>
|
||||||
|
<div class="s-val" style="color:var(--color-up);">645.5</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-box">
|
||||||
|
<div class="s-label">仓位</div>
|
||||||
|
<div class="s-val">轻仓</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-box">
|
||||||
|
<div class="s-label">评分</div>
|
||||||
|
<div class="s-val" style="color:var(--color-brand);">4/11</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">⚠️ 风险提示</div>
|
||||||
|
<div class="risk-list">
|
||||||
|
<div class="risk-item">
|
||||||
|
<span class="risk-icon">•</span>
|
||||||
|
<span>技术指标具有滞后性,历史表现不代表未来。</span>
|
||||||
|
</div>
|
||||||
|
<div class="risk-item">
|
||||||
|
<span class="risk-icon">•</span>
|
||||||
|
<span>SC2606 为远月合约,流动性极差,注意滑点风险。</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="height: 40px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,36 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
buffer-platform:
|
buffer-platform:
|
||||||
build: .
|
build:
|
||||||
container_name: buffer-platform
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: futures-buffer-platform
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "9600:8600"
|
- "9600:8600"
|
||||||
volumes:
|
|
||||||
- E:\docker_workspace\futures_datas:/app/data
|
|
||||||
environment:
|
environment:
|
||||||
- BUFFER_DB_PATH=/app/data/buffer.db
|
- BUFFER_DB_PATH=/app/data/buffer.db
|
||||||
- BUFFER_HOST=0.0.0.0
|
- BUFFER_HOST=0.0.0.0
|
||||||
- BUFFER_PORT=8600
|
- BUFFER_PORT=8600
|
||||||
- CACHE_TTL=300
|
- CACHE_TTL=300
|
||||||
- BUFFER_LOG_LEVEL=INFO
|
- BUFFER_LOG_LEVEL=INFO
|
||||||
restart: unless-stopped
|
- MAX_WORKERS=2
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
volumes:
|
||||||
|
# 数据持久化 - SQLite数据库
|
||||||
|
- ./data:/app/data
|
||||||
|
# 静态文件(开发模式,生产环境应该打包到镜像中)
|
||||||
|
- ./app/static:/app/app/static
|
||||||
|
# 日志持久化(可选)
|
||||||
|
- ./logs:/app/logs
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8600/api/v1/health')"]
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8600/api/v1/health')"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 10s
|
||||||
|
networks:
|
||||||
|
- futures-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
futures-network:
|
||||||
|
driver: bridge
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
echo ========================================
|
||||||
|
echo 查看期货智析缓冲平台日志
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
echo 请选择日志查看模式:
|
||||||
|
echo 1. 查看最近100行日志
|
||||||
|
echo 2. 实时跟踪日志
|
||||||
|
echo 3. 查看最近1小时日志
|
||||||
|
echo.
|
||||||
|
set /p MODE=请输入选项 (1/2/3):
|
||||||
|
|
||||||
|
if "%MODE%"=="1" (
|
||||||
|
docker-compose logs --tail=100
|
||||||
|
) else if "%MODE%"=="2" (
|
||||||
|
docker-compose logs -f
|
||||||
|
) else if "%MODE%"=="3" (
|
||||||
|
docker-compose logs --since 1h
|
||||||
|
) else (
|
||||||
|
echo 无效选项
|
||||||
|
)
|
||||||
|
|
||||||
|
pause
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
echo ====================================
|
||||||
|
echo 期货智析平台 - Docker 管理脚本
|
||||||
|
echo ====================================
|
||||||
|
echo.
|
||||||
|
echo 请选择操作:
|
||||||
|
echo 1. 启动服务
|
||||||
|
echo 2. 停止服务
|
||||||
|
echo 3. 重启服务
|
||||||
|
echo 4. 查看日志
|
||||||
|
echo 5. 查看状态
|
||||||
|
echo 6. 进入容器
|
||||||
|
echo 7. 清理资源
|
||||||
|
echo 0. 退出
|
||||||
|
echo.
|
||||||
|
set /p choice=请输入选项 (0-7):
|
||||||
|
|
||||||
|
if "%choice%"=="1" goto start
|
||||||
|
if "%choice%"=="2" goto stop
|
||||||
|
if "%choice%"=="3" goto restart
|
||||||
|
if "%choice%"=="4" goto logs
|
||||||
|
if "%choice%"=="5" goto status
|
||||||
|
if "%choice%"=="6" goto shell
|
||||||
|
if "%choice%"=="7" goto clean
|
||||||
|
if "%choice%"=="0" goto end
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:start
|
||||||
|
echo.
|
||||||
|
echo [启动服务...]
|
||||||
|
docker-compose up -d
|
||||||
|
echo.
|
||||||
|
echo 服务已启动!访问 http://localhost:9600
|
||||||
|
pause
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:stop
|
||||||
|
echo.
|
||||||
|
echo [停止服务...]
|
||||||
|
docker-compose stop
|
||||||
|
echo.
|
||||||
|
echo 服务已停止!
|
||||||
|
pause
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:restart
|
||||||
|
echo.
|
||||||
|
echo [重启服务...]
|
||||||
|
docker-compose restart
|
||||||
|
echo.
|
||||||
|
echo 服务已重启!
|
||||||
|
pause
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:logs
|
||||||
|
echo.
|
||||||
|
echo [查看日志 - 按Ctrl+C退出]
|
||||||
|
docker-compose logs -f --tail=100
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:status
|
||||||
|
echo.
|
||||||
|
echo [服务状态]
|
||||||
|
docker-compose ps
|
||||||
|
echo.
|
||||||
|
echo [容器资源使用]
|
||||||
|
docker stats --no-stream futures-buffer-platform
|
||||||
|
pause
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:shell
|
||||||
|
echo.
|
||||||
|
echo [进入容器...]
|
||||||
|
docker exec -it futures-buffer-platform /bin/bash
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:clean
|
||||||
|
echo.
|
||||||
|
echo [警告] 这将停止并删除容器,但保留数据卷
|
||||||
|
set /p confirm=确认继续? (y/n):
|
||||||
|
if /i not "%confirm%"=="y" goto menu
|
||||||
|
echo.
|
||||||
|
echo [清理资源...]
|
||||||
|
docker-compose down
|
||||||
|
echo.
|
||||||
|
echo 资源已清理!
|
||||||
|
pause
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:menu
|
||||||
|
cls
|
||||||
|
goto start
|
||||||
|
|
||||||
|
:end
|
||||||
|
echo.
|
||||||
|
echo 再见!
|
||||||
|
exit
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
echo ========================================
|
||||||
|
echo 启动期货智析缓冲平台
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
docker-compose start
|
||||||
|
echo.
|
||||||
|
|
||||||
|
echo 等待服务启动...
|
||||||
|
timeout /t 3 /nobreak >nul
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo 访问地址:
|
||||||
|
echo 品种分析页面: http://localhost:9600/futures-analysis
|
||||||
|
echo 配置管理页面: http://localhost:9600/ui
|
||||||
|
echo AI配置页面: http://localhost:9600/ai-config
|
||||||
|
echo API文档: http://localhost:9600/docs
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
docker-compose ps
|
||||||
|
pause
|
||||||
Loading…
Reference in new issue