parent
6bf57ef398
commit
1106b3375f
@ -0,0 +1,151 @@
|
|||||||
|
"""
|
||||||
|
指数数据查询路由
|
||||||
|
"""
|
||||||
|
from typing import List
|
||||||
|
from fastapi import APIRouter, Depends, Query
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy import and_
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from app.db.session import get_db
|
||||||
|
from app.schemas.base import ResponseModel
|
||||||
|
from app.models.stock_basic import IndexBasic, IndexTrade
|
||||||
|
from app.core.security import get_current_user
|
||||||
|
from app.models.user import User
|
||||||
|
from app.utils.date_utils import parse_date, format_date
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/list", response_model=ResponseModel)
|
||||||
|
async def get_index_list(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""获取指数列表"""
|
||||||
|
indexes = db.query(IndexBasic).order_by(IndexBasic.code).all()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for idx in indexes:
|
||||||
|
result.append({
|
||||||
|
"code": idx.code,
|
||||||
|
"name": idx.name,
|
||||||
|
"component_count": idx.component_count
|
||||||
|
})
|
||||||
|
|
||||||
|
return ResponseModel(data=result)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/trade", response_model=ResponseModel)
|
||||||
|
async def get_index_trade_data(
|
||||||
|
codes: str = Query(..., description="指数代码列表,逗号分隔"),
|
||||||
|
start_date: str = Query(..., description="开始日期 YYYYMMDD"),
|
||||||
|
end_date: str = Query(..., description="结束日期 YYYYMMDD"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""获取指数交易数据"""
|
||||||
|
code_list = codes.split(",")
|
||||||
|
start = parse_date(start_date)
|
||||||
|
end = parse_date(end_date)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for code in code_list:
|
||||||
|
code = code.strip()
|
||||||
|
|
||||||
|
index_basic = db.query(IndexBasic).filter(IndexBasic.code == code).first()
|
||||||
|
|
||||||
|
trades = db.query(IndexTrade).filter(
|
||||||
|
and_(
|
||||||
|
IndexTrade.index_code == code,
|
||||||
|
IndexTrade.trade_date >= start,
|
||||||
|
IndexTrade.trade_date <= end
|
||||||
|
)
|
||||||
|
).order_by(IndexTrade.trade_date).all()
|
||||||
|
|
||||||
|
trade_list = []
|
||||||
|
for trade in trades:
|
||||||
|
trade_list.append({
|
||||||
|
"trade_date": format_date(trade.trade_date),
|
||||||
|
"open": float(trade.open) if trade.open else None,
|
||||||
|
"close": float(trade.close) if trade.close else None,
|
||||||
|
"high": float(trade.high) if trade.high else None,
|
||||||
|
"low": float(trade.low) if trade.low else None,
|
||||||
|
"change_pct": float(trade.change_pct) if trade.change_pct else None,
|
||||||
|
"volume": trade.volume,
|
||||||
|
"amount": float(trade.amount) if trade.amount else None,
|
||||||
|
"total_market_value": float(trade.total_market_value) if trade.total_market_value else None,
|
||||||
|
"float_market_value": float(trade.float_market_value) if trade.float_market_value else None,
|
||||||
|
"up_count": trade.up_count,
|
||||||
|
"down_count": trade.down_count,
|
||||||
|
"flat_count": trade.flat_count,
|
||||||
|
"limit_up_count": trade.limit_up_count,
|
||||||
|
"limit_down_count": trade.limit_down_count,
|
||||||
|
"suspend_count": trade.suspend_count,
|
||||||
|
"pe_ratio": float(trade.pe_ratio) if trade.pe_ratio else None,
|
||||||
|
"pe_median": float(trade.pe_median) if trade.pe_median else None,
|
||||||
|
"is_new_high": trade.is_new_high,
|
||||||
|
"is_new_low": trade.is_new_low
|
||||||
|
})
|
||||||
|
|
||||||
|
result[code] = {
|
||||||
|
"basic": {
|
||||||
|
"code": index_basic.code if index_basic else code,
|
||||||
|
"name": index_basic.name if index_basic else None,
|
||||||
|
"component_count": index_basic.component_count if index_basic else None
|
||||||
|
},
|
||||||
|
"trades": trade_list
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseModel(data=result)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{code}/chart", response_model=ResponseModel)
|
||||||
|
async def get_index_chart_data(
|
||||||
|
code: str,
|
||||||
|
start_date: str = Query(..., description="开始日期 YYYYMMDD"),
|
||||||
|
end_date: str = Query(..., description="结束日期 YYYYMMDD"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""获取指数K线图表数据(ECharts格式)"""
|
||||||
|
start = parse_date(start_date)
|
||||||
|
end = parse_date(end_date)
|
||||||
|
|
||||||
|
trades = db.query(IndexTrade).filter(
|
||||||
|
and_(
|
||||||
|
IndexTrade.index_code == code,
|
||||||
|
IndexTrade.trade_date >= start,
|
||||||
|
IndexTrade.trade_date <= end
|
||||||
|
)
|
||||||
|
).order_by(IndexTrade.trade_date).all()
|
||||||
|
|
||||||
|
category_data = []
|
||||||
|
values = []
|
||||||
|
volumes = []
|
||||||
|
|
||||||
|
for trade in trades:
|
||||||
|
category_data.append(format_date(trade.trade_date))
|
||||||
|
values.append([
|
||||||
|
float(trade.open) if trade.open else 0,
|
||||||
|
float(trade.close) if trade.close else 0,
|
||||||
|
float(trade.low) if trade.low else 0,
|
||||||
|
float(trade.high) if trade.high else 0,
|
||||||
|
trade.volume if trade.volume else 0
|
||||||
|
])
|
||||||
|
volumes.append([
|
||||||
|
trade.volume if trade.volume else 0,
|
||||||
|
1 if (trade.close and trade.open and trade.close >= trade.open) else -1
|
||||||
|
])
|
||||||
|
|
||||||
|
index_basic = db.query(IndexBasic).filter(IndexBasic.code == code).first()
|
||||||
|
|
||||||
|
return ResponseModel(data={
|
||||||
|
"code": code,
|
||||||
|
"name": index_basic.name if index_basic else None,
|
||||||
|
"component_count": index_basic.component_count if index_basic else None,
|
||||||
|
"categoryData": category_data,
|
||||||
|
"values": values,
|
||||||
|
"volumes": volumes
|
||||||
|
})
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
"""
|
||||||
|
股票基础数据模型
|
||||||
|
"""
|
||||||
|
from datetime import datetime, date
|
||||||
|
from sqlalchemy import Column, Integer, BigInteger, String, Numeric, Text, Date, DateTime, ForeignKey, Boolean
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
from app.db.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
class StockBasic(Base):
|
||||||
|
"""股票基础数据表"""
|
||||||
|
__tablename__ = "stock_basic"
|
||||||
|
|
||||||
|
id = Column(BigInteger, primary_key=True, index=True)
|
||||||
|
code = Column(String(20), unique=True, nullable=False, index=True)
|
||||||
|
name = Column(String(50))
|
||||||
|
total_shares = Column(BigInteger)
|
||||||
|
float_shares = Column(BigInteger)
|
||||||
|
industry_index_name = Column(String(100))
|
||||||
|
industry_index_code = Column(String(20), ForeignKey("index_basic.code"))
|
||||||
|
institution_hold_ratio = Column(Numeric(10, 4))
|
||||||
|
industry_level3 = Column(String(100))
|
||||||
|
list_date = Column(Date)
|
||||||
|
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
industry_index = relationship("IndexBasic", back_populates="stocks")
|
||||||
|
|
||||||
|
|
||||||
|
class IndexBasic(Base):
|
||||||
|
"""指数基础表"""
|
||||||
|
__tablename__ = "index_basic"
|
||||||
|
|
||||||
|
id = Column(BigInteger, primary_key=True, index=True)
|
||||||
|
code = Column(String(20), unique=True, nullable=False, index=True)
|
||||||
|
name = Column(String(100))
|
||||||
|
component_count = Column(Integer)
|
||||||
|
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
stocks = relationship("StockBasic", back_populates="industry_index")
|
||||||
|
trades = relationship("IndexTrade", back_populates="index")
|
||||||
|
|
||||||
|
|
||||||
|
class IndexTrade(Base):
|
||||||
|
"""指数交易表"""
|
||||||
|
__tablename__ = "index_trade"
|
||||||
|
|
||||||
|
id = Column(BigInteger, primary_key=True, index=True)
|
||||||
|
index_code = Column(String(20), ForeignKey("index_basic.code"), nullable=False, index=True)
|
||||||
|
trade_date = Column(Date, nullable=False, index=True)
|
||||||
|
open = Column(Numeric(10, 3))
|
||||||
|
close = Column(Numeric(10, 3))
|
||||||
|
high = Column(Numeric(10, 3))
|
||||||
|
low = Column(Numeric(10, 3))
|
||||||
|
change_pct = Column(Numeric(10, 4))
|
||||||
|
volume = Column(BigInteger)
|
||||||
|
amount = Column(Numeric(18, 2))
|
||||||
|
total_market_value = Column(Numeric(18, 2))
|
||||||
|
float_market_value = Column(Numeric(18, 2))
|
||||||
|
up_count = Column(Integer)
|
||||||
|
down_count = Column(Integer)
|
||||||
|
flat_count = Column(Integer)
|
||||||
|
limit_up_count = Column(Integer)
|
||||||
|
limit_down_count = Column(Integer)
|
||||||
|
suspend_count = Column(Integer)
|
||||||
|
pe_ratio = Column(Numeric(10, 4))
|
||||||
|
pe_median = Column(Numeric(10, 4))
|
||||||
|
is_new_high = Column(Boolean, default=False)
|
||||||
|
is_new_low = Column(Boolean, default=False)
|
||||||
|
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
index = relationship("IndexBasic", back_populates="trades")
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
{'unique_constraint': None},
|
||||||
|
)
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
创建股票基础数据相关表
|
||||||
|
"""
|
||||||
|
from sqlalchemy import text
|
||||||
|
from app.db.session import SessionLocal
|
||||||
|
|
||||||
|
db = SessionLocal()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建股票基础数据表
|
||||||
|
db.execute(text("""
|
||||||
|
CREATE TABLE IF NOT EXISTS stock_basic (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
code VARCHAR(20) UNIQUE NOT NULL,
|
||||||
|
name VARCHAR(50),
|
||||||
|
total_shares BIGINT,
|
||||||
|
float_shares BIGINT,
|
||||||
|
industry_index_name VARCHAR(100),
|
||||||
|
industry_index_code VARCHAR(20),
|
||||||
|
institution_hold_ratio DECIMAL(10, 4),
|
||||||
|
industry_level3 VARCHAR(100),
|
||||||
|
list_date DATE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 创建指数基础表
|
||||||
|
db.execute(text("""
|
||||||
|
CREATE TABLE IF NOT EXISTS index_basic (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
code VARCHAR(20) UNIQUE NOT NULL,
|
||||||
|
name VARCHAR(100),
|
||||||
|
component_count INTEGER,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 创建指数交易表
|
||||||
|
db.execute(text("""
|
||||||
|
CREATE TABLE IF NOT EXISTS index_trade (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
index_code VARCHAR(20) NOT NULL,
|
||||||
|
trade_date DATE NOT NULL,
|
||||||
|
open DECIMAL(10, 3),
|
||||||
|
close DECIMAL(10, 3),
|
||||||
|
high DECIMAL(10, 3),
|
||||||
|
low DECIMAL(10, 3),
|
||||||
|
change_pct DECIMAL(10, 4),
|
||||||
|
volume BIGINT,
|
||||||
|
amount DECIMAL(18, 2),
|
||||||
|
total_market_value DECIMAL(18, 2),
|
||||||
|
float_market_value DECIMAL(18, 2),
|
||||||
|
up_count INTEGER,
|
||||||
|
down_count INTEGER,
|
||||||
|
flat_count INTEGER,
|
||||||
|
limit_up_count INTEGER,
|
||||||
|
limit_down_count INTEGER,
|
||||||
|
suspend_count INTEGER,
|
||||||
|
pe_ratio DECIMAL(10, 4),
|
||||||
|
pe_median DECIMAL(10, 4),
|
||||||
|
is_new_high BOOLEAN DEFAULT FALSE,
|
||||||
|
is_new_low BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE(index_code, trade_date)
|
||||||
|
)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 创建索引
|
||||||
|
db.execute(text("CREATE INDEX IF NOT EXISTS idx_stock_basic_code ON stock_basic(code)"))
|
||||||
|
db.execute(text("CREATE INDEX IF NOT EXISTS idx_index_basic_code ON index_basic(code)"))
|
||||||
|
db.execute(text("CREATE INDEX IF NOT EXISTS idx_index_trade_code ON index_trade(index_code)"))
|
||||||
|
db.execute(text("CREATE INDEX IF NOT EXISTS idx_index_trade_date ON index_trade(trade_date)"))
|
||||||
|
|
||||||
|
# 添加外键约束
|
||||||
|
db.execute(text("""
|
||||||
|
ALTER TABLE stock_basic
|
||||||
|
ADD CONSTRAINT fk_stock_basic_index_code
|
||||||
|
FOREIGN KEY (industry_index_code) REFERENCES index_basic(code)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
db.execute(text("""
|
||||||
|
ALTER TABLE index_trade
|
||||||
|
ADD CONSTRAINT fk_index_trade_index_code
|
||||||
|
FOREIGN KEY (index_code) REFERENCES index_basic(code)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
print("表创建成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"创建表失败: {str(e)}")
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export const importStockBasic = (file: File) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return request.post('/import/stock-basic', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const importIndexBasic = (file: File) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return request.post('/import/index-basic', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const importIndexTrade = (file: File) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return request.post('/import/index-trade', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const importIndexData = (file: File, tradeDate: string) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
return request.post(`/import/index-data?trade_date=${tradeDate}`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export const getIndexList = () => {
|
||||||
|
return request.get('/index/list')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getIndexTradeData = (params: {
|
||||||
|
codes: string
|
||||||
|
start_date: string
|
||||||
|
end_date: string
|
||||||
|
}) => {
|
||||||
|
return request.get('/index/trade', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getIndexChartData = (
|
||||||
|
code: string,
|
||||||
|
params: {
|
||||||
|
start_date: string
|
||||||
|
end_date: string
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
return request.get(`/index/${code}/chart`, { params })
|
||||||
|
}
|
||||||
@ -0,0 +1,303 @@
|
|||||||
|
<template>
|
||||||
|
<div class="index-query">
|
||||||
|
<el-card>
|
||||||
|
<el-form :model="queryForm" inline>
|
||||||
|
<el-form-item label="指数代码">
|
||||||
|
<el-input v-model="queryForm.code" placeholder="如: BK0001, BK0002" style="width: 150px;" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="开始日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryForm.startDate"
|
||||||
|
type="date"
|
||||||
|
placeholder="开始日期"
|
||||||
|
value-format="YYYYMMDD"
|
||||||
|
style="width: 140px;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="结束日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryForm.endDate"
|
||||||
|
type="date"
|
||||||
|
placeholder="结束日期"
|
||||||
|
value-format="YYYYMMDD"
|
||||||
|
style="width: 140px;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery" :loading="loading">
|
||||||
|
<el-icon><Search /></el-icon> 查询
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="basic-card" v-if="indexBasic">
|
||||||
|
<template #header>
|
||||||
|
<span>指数基础信息</span>
|
||||||
|
</template>
|
||||||
|
<el-descriptions :column="3" border>
|
||||||
|
<el-descriptions-item label="指数代码">{{ indexBasic.code }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="指数名称">{{ indexBasic.name }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="成分个数">{{ indexBasic.component_count }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="chart-card" v-if="chartData.categoryData.length > 0">
|
||||||
|
<template #header>
|
||||||
|
<span>指数K线图 - {{ indexBasic?.name || queryForm.code }}</span>
|
||||||
|
</template>
|
||||||
|
<div ref="chartRef" class="kline-chart"></div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="data-card" v-if="tableData.length > 0">
|
||||||
|
<template #header>
|
||||||
|
<span>交易数据列表</span>
|
||||||
|
</template>
|
||||||
|
<el-table :data="tableData" stripe height="300">
|
||||||
|
<el-table-column prop="trade_date" label="日期" width="120" />
|
||||||
|
<el-table-column prop="open" label="开盘" :formatter="formatNumber" />
|
||||||
|
<el-table-column prop="close" label="收盘" :formatter="formatNumber" />
|
||||||
|
<el-table-column prop="high" label="最高" :formatter="formatNumber" />
|
||||||
|
<el-table-column prop="low" label="最低" :formatter="formatNumber" />
|
||||||
|
<el-table-column prop="change_pct" label="涨跌幅%">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.change_pct >= 0 ? 'success' : 'danger'" size="small">
|
||||||
|
{{ row.change_pct?.toFixed(2) || '-' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="volume" label="成交量" :formatter="formatVolume" />
|
||||||
|
<el-table-column prop="amount" label="成交额(百万)" :formatter="formatNumber" />
|
||||||
|
<el-table-column prop="up_count" label="上涨家数" width="90" />
|
||||||
|
<el-table-column prop="down_count" label="下跌家数" width="90" />
|
||||||
|
<el-table-column prop="limit_up_count" label="涨停" width="70" />
|
||||||
|
<el-table-column prop="limit_down_count" label="跌停" width="70" />
|
||||||
|
<el-table-column prop="pe_ratio" label="市盈率" :formatter="formatNumber" />
|
||||||
|
<el-table-column label="新高/新低" width="90">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.is_new_high" type="success" size="small">新高</el-tag>
|
||||||
|
<el-tag v-if="row.is_new_low" type="danger" size="small">新低</el-tag>
|
||||||
|
<span v-if="!row.is_new_high && !row.is_new_low">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, nextTick } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import { getIndexChartData, getIndexTradeData } from '@/api/index'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const chartRef = ref<HTMLElement>()
|
||||||
|
let chartInstance: echarts.ECharts | null = null
|
||||||
|
|
||||||
|
const queryForm = reactive({
|
||||||
|
code: 'BK0001',
|
||||||
|
startDate: getDefaultStartDate(),
|
||||||
|
endDate: getDefaultEndDate()
|
||||||
|
})
|
||||||
|
|
||||||
|
const indexBasic = ref<any>(null)
|
||||||
|
const chartData = reactive({
|
||||||
|
categoryData: [] as string[],
|
||||||
|
values: [] as number[][],
|
||||||
|
volumes: [] as number[][]
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableData = ref<any[]>([])
|
||||||
|
|
||||||
|
function getDefaultStartDate() {
|
||||||
|
const date = new Date()
|
||||||
|
date.setMonth(date.getMonth() - 6)
|
||||||
|
return formatDate(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultEndDate() {
|
||||||
|
return formatDate(new Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(date: Date) {
|
||||||
|
return date.toISOString().slice(0, 10).replace(/-/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(row: any, column: any, value: number) {
|
||||||
|
return value?.toFixed(2) || '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatVolume(row: any, column: any, value: number) {
|
||||||
|
if (!value) return '-'
|
||||||
|
if (value >= 100000000) {
|
||||||
|
return (value / 100000000).toFixed(2) + '亿'
|
||||||
|
}
|
||||||
|
if (value >= 10000) {
|
||||||
|
return (value / 10000).toFixed(2) + '万'
|
||||||
|
}
|
||||||
|
return value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleQuery = async () => {
|
||||||
|
if (!queryForm.code) {
|
||||||
|
ElMessage.warning('请输入指数代码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await getIndexChartData(queryForm.code, {
|
||||||
|
start_date: queryForm.startDate,
|
||||||
|
end_date: queryForm.endDate
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.data) {
|
||||||
|
indexBasic.value = {
|
||||||
|
code: res.data.code,
|
||||||
|
name: res.data.name,
|
||||||
|
component_count: res.data.component_count
|
||||||
|
}
|
||||||
|
|
||||||
|
chartData.categoryData = res.data.categoryData || []
|
||||||
|
chartData.values = res.data.values || []
|
||||||
|
chartData.volumes = res.data.volumes || []
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
renderChart()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const tradeRes: any = await getIndexTradeData({
|
||||||
|
codes: queryForm.code,
|
||||||
|
start_date: queryForm.startDate,
|
||||||
|
end_date: queryForm.endDate
|
||||||
|
})
|
||||||
|
|
||||||
|
if (tradeRes.data && tradeRes.data[queryForm.code]) {
|
||||||
|
const tradeData = tradeRes.data[queryForm.code]
|
||||||
|
indexBasic.value = tradeData.basic || indexBasic.value
|
||||||
|
tableData.value = tradeData.trades || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('查询失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderChart = () => {
|
||||||
|
if (!chartRef.value) return
|
||||||
|
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value)
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'cross' }
|
||||||
|
},
|
||||||
|
grid: [
|
||||||
|
{ left: '10%', right: '8%', height: '50%' },
|
||||||
|
{ left: '10%', right: '8%', top: '68%', height: '16%' }
|
||||||
|
],
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: chartData.categoryData,
|
||||||
|
scale: true,
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: { onZero: false },
|
||||||
|
splitLine: { show: false },
|
||||||
|
min: 'dataMin',
|
||||||
|
max: 'dataMax'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
gridIndex: 1,
|
||||||
|
data: chartData.categoryData,
|
||||||
|
scale: true,
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: { onZero: false },
|
||||||
|
axisTick: { show: false },
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLabel: { show: false },
|
||||||
|
min: 'dataMin',
|
||||||
|
max: 'dataMax'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
scale: true,
|
||||||
|
splitArea: { show: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scale: true,
|
||||||
|
gridIndex: 1,
|
||||||
|
splitNumber: 2,
|
||||||
|
axisLabel: { show: false },
|
||||||
|
axisLine: { show: false },
|
||||||
|
axisTick: { show: false },
|
||||||
|
splitLine: { show: false }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{ type: 'inside', xAxisIndex: [0, 1], start: 50, end: 100 },
|
||||||
|
{ show: true, xAxisIndex: [0, 1], type: 'slider', top: '85%', start: 50, end: 100 }
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'K线',
|
||||||
|
type: 'candlestick',
|
||||||
|
data: chartData.values,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#ef232a',
|
||||||
|
color0: '#14b143',
|
||||||
|
borderColor: '#ef232a',
|
||||||
|
borderColor0: '#14b143'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '成交量',
|
||||||
|
type: 'bar',
|
||||||
|
xAxisIndex: 1,
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: chartData.volumes
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance.setOption(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
chartInstance?.resize()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.index-query {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basic-card {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kline-chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-card {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in new issue