Merge branch 'feature_2025/251117_pref' of https://git.valuefrontier.cn/vf/vf_react into feature_2025/251117_pref
* 'feature_2025/251117_pref' of https://git.valuefrontier.cn/vf/vf_react: update pay function update pay function update pay function
This commit is contained in:
4
app.py
4
app.py
@@ -6904,12 +6904,12 @@ def get_daily_kline(stock_code, event_datetime, stock_name):
|
||||
stock_code = stock_code.split('.')[0]
|
||||
|
||||
with engine.connect() as conn:
|
||||
# 获取事件日期前后的数据(前730天/2年,后30天)
|
||||
# 获取事件日期前后的数据(前365天/1年,后30天)
|
||||
kline_sql = """
|
||||
WITH date_range AS (SELECT TRADEDATE \
|
||||
FROM ea_trade \
|
||||
WHERE SECCODE = :stock_code \
|
||||
AND TRADEDATE BETWEEN DATE_SUB(:trade_date, INTERVAL 730 DAY) \
|
||||
AND TRADEDATE BETWEEN DATE_SUB(:trade_date, INTERVAL 365 DAY) \
|
||||
AND DATE_ADD(:trade_date, INTERVAL 30 DAY) \
|
||||
GROUP BY TRADEDATE \
|
||||
ORDER BY TRADEDATE)
|
||||
|
||||
@@ -170,6 +170,21 @@ const KLineChartModal: React.FC<KLineChartModalProps> = ({
|
||||
d.close >= d.open ? '#ef5350' : '#26a69a'
|
||||
);
|
||||
|
||||
// 提取事件发生日期(YYYY-MM-DD格式)
|
||||
let eventDateStr: string | null = null;
|
||||
if (eventTime) {
|
||||
try {
|
||||
const eventDate = new Date(eventTime);
|
||||
const year = eventDate.getFullYear();
|
||||
const month = (eventDate.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = eventDate.getDate().toString().padStart(2, '0');
|
||||
eventDateStr = `${year}-${month}-${day}`;
|
||||
console.log('[KLineChartModal] 事件发生日期:', eventDateStr);
|
||||
} catch (e) {
|
||||
console.error('[KLineChartModal] 解析事件日期失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 图表配置
|
||||
const option: echarts.EChartsOption = {
|
||||
backgroundColor: '#1a1a1a',
|
||||
@@ -346,6 +361,34 @@ const KLineChartModal: React.FC<KLineChartModalProps> = ({
|
||||
borderColor: '#ef5350',
|
||||
borderColor0: '#26a69a',
|
||||
},
|
||||
markLine: eventDateStr ? {
|
||||
silent: false,
|
||||
symbol: 'none',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideEndTop',
|
||||
formatter: '事件发生',
|
||||
color: '#ffd700',
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
padding: [4, 8],
|
||||
borderRadius: 4,
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#ffd700',
|
||||
width: 2,
|
||||
type: 'solid',
|
||||
},
|
||||
data: [
|
||||
{
|
||||
xAxis: eventDateStr,
|
||||
label: {
|
||||
formatter: '⚡ 事件发生',
|
||||
},
|
||||
},
|
||||
],
|
||||
} : undefined,
|
||||
},
|
||||
{
|
||||
name: '成交量',
|
||||
@@ -420,7 +463,7 @@ const KLineChartModal: React.FC<KLineChartModalProps> = ({
|
||||
</Text>
|
||||
{data.length > 0 && (
|
||||
<Text fontSize="xs" color="#666" fontStyle="italic">
|
||||
共{data.length}个交易日(最多2年)
|
||||
共{data.length}个交易日(最多1年)
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
@@ -172,6 +172,20 @@ const TimelineChartModal: React.FC<TimelineChartModalProps> = ({
|
||||
d.price >= basePrice ? '#ef5350' : '#26a69a'
|
||||
);
|
||||
|
||||
// 提取事件发生时间(HH:MM格式)
|
||||
let eventTimeStr: string | null = null;
|
||||
if (eventTime) {
|
||||
try {
|
||||
const eventDate = new Date(eventTime);
|
||||
const hours = eventDate.getHours().toString().padStart(2, '0');
|
||||
const minutes = eventDate.getMinutes().toString().padStart(2, '0');
|
||||
eventTimeStr = `${hours}:${minutes}`;
|
||||
console.log('[TimelineChartModal] 事件发生时间:', eventTimeStr);
|
||||
} catch (e) {
|
||||
console.error('[TimelineChartModal] 解析事件时间失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 图表配置
|
||||
const option: echarts.EChartsOption = {
|
||||
backgroundColor: '#1a1a1a',
|
||||
@@ -341,6 +355,34 @@ const TimelineChartModal: React.FC<TimelineChartModalProps> = ({
|
||||
{ offset: 1, color: 'rgba(33, 150, 243, 0.05)' },
|
||||
]),
|
||||
},
|
||||
markLine: eventTimeStr ? {
|
||||
silent: false,
|
||||
symbol: 'none',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideEndTop',
|
||||
formatter: '事件发生',
|
||||
color: '#ffd700',
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
padding: [4, 8],
|
||||
borderRadius: 4,
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#ffd700',
|
||||
width: 2,
|
||||
type: 'solid',
|
||||
},
|
||||
data: [
|
||||
{
|
||||
xAxis: eventTimeStr,
|
||||
label: {
|
||||
formatter: '⚡ 事件发生',
|
||||
},
|
||||
},
|
||||
],
|
||||
} : undefined,
|
||||
},
|
||||
{
|
||||
name: '均价',
|
||||
|
||||
@@ -56,6 +56,8 @@ import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import { logger } from '../../../utils/logger';
|
||||
import { getApiBase } from '../../../utils/apiConfig';
|
||||
import TimelineChartModal from '../../../components/StockChart/TimelineChartModal';
|
||||
import KLineChartModal from '../../../components/StockChart/KLineChartModal';
|
||||
import './InvestmentCalendar.css';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
@@ -63,6 +65,8 @@ dayjs.locale('zh-cn');
|
||||
export default function InvestmentCalendarChakra() {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { isOpen: isAddOpen, onOpen: onAddOpen, onClose: onAddClose } = useDisclosure();
|
||||
const { isOpen: isTimelineModalOpen, onOpen: onTimelineModalOpen, onClose: onTimelineModalClose } = useDisclosure();
|
||||
const { isOpen: isKLineModalOpen, onOpen: onKLineModalOpen, onClose: onKLineModalClose } = useDisclosure();
|
||||
const toast = useToast();
|
||||
|
||||
// 颜色主题
|
||||
@@ -74,6 +78,7 @@ export default function InvestmentCalendarChakra() {
|
||||
const [events, setEvents] = useState([]);
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
const [selectedDateEvents, setSelectedDateEvents] = useState([]);
|
||||
const [selectedStock, setSelectedStock] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [newEvent, setNewEvent] = useState({
|
||||
title: '',
|
||||
@@ -262,6 +267,35 @@ export default function InvestmentCalendarChakra() {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理股票点击 - 打开图表弹窗
|
||||
const handleStockClick = (stockCodeOrName, eventDate) => {
|
||||
// 解析股票代码(可能是 "600000" 或 "600000 平安银行" 格式)
|
||||
let stockCode = stockCodeOrName;
|
||||
let stockName = '';
|
||||
|
||||
if (typeof stockCodeOrName === 'string') {
|
||||
const parts = stockCodeOrName.trim().split(/\s+/);
|
||||
stockCode = parts[0];
|
||||
stockName = parts.slice(1).join(' ');
|
||||
}
|
||||
|
||||
// 添加交易所后缀(如果没有)
|
||||
if (!stockCode.includes('.')) {
|
||||
if (stockCode.startsWith('6')) {
|
||||
stockCode = `${stockCode}.SH`;
|
||||
} else if (stockCode.startsWith('0') || stockCode.startsWith('3')) {
|
||||
stockCode = `${stockCode}.SZ`;
|
||||
} else if (stockCode.startsWith('688')) {
|
||||
stockCode = `${stockCode}.SH`;
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedStock({
|
||||
stock_code: stockCode,
|
||||
stock_name: stockName || stockCode,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card bg={bgColor} shadow="md">
|
||||
<CardHeader pb={4}>
|
||||
@@ -386,15 +420,47 @@ export default function InvestmentCalendarChakra() {
|
||||
)}
|
||||
|
||||
{event.extendedProps?.stocks && event.extendedProps.stocks.length > 0 && (
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Text fontSize="sm" color={secondaryText}>相关股票:</Text>
|
||||
{event.extendedProps.stocks.map((stock, i) => (
|
||||
<Tag key={i} size="sm" colorScheme="blue">
|
||||
<TagLeftIcon as={FiTrendingUp} />
|
||||
<TagLabel>{stock}</TagLabel>
|
||||
</Tag>
|
||||
))}
|
||||
</HStack>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Text fontSize="sm" color={secondaryText}>相关股票:</Text>
|
||||
{event.extendedProps.stocks.map((stock, i) => (
|
||||
<Tag
|
||||
key={i}
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
cursor="pointer"
|
||||
onClick={() => handleStockClick(stock, event.start)}
|
||||
_hover={{ transform: 'scale(1.05)', shadow: 'md' }}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
<TagLeftIcon as={FiTrendingUp} />
|
||||
<TagLabel>{stock}</TagLabel>
|
||||
</Tag>
|
||||
))}
|
||||
</HStack>
|
||||
{selectedStock && (
|
||||
<HStack spacing={2}>
|
||||
<Button
|
||||
size="xs"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
leftIcon={<FiClock />}
|
||||
onClick={onTimelineModalOpen}
|
||||
>
|
||||
分时图
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
colorScheme="purple"
|
||||
variant="outline"
|
||||
leftIcon={<FiTrendingUp />}
|
||||
onClick={onKLineModalOpen}
|
||||
>
|
||||
日K线
|
||||
</Button>
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
@@ -489,6 +555,32 @@ export default function InvestmentCalendarChakra() {
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* 分时图弹窗 */}
|
||||
{selectedStock && (
|
||||
<TimelineChartModal
|
||||
isOpen={isTimelineModalOpen}
|
||||
onClose={() => {
|
||||
onTimelineModalClose();
|
||||
setSelectedStock(null);
|
||||
}}
|
||||
stock={selectedStock}
|
||||
eventTime={selectedDate?.toISOString()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* K线图弹窗 */}
|
||||
{selectedStock && (
|
||||
<KLineChartModal
|
||||
isOpen={isKLineModalOpen}
|
||||
onClose={() => {
|
||||
onKLineModalClose();
|
||||
setSelectedStock(null);
|
||||
}}
|
||||
stock={selectedStock}
|
||||
eventTime={selectedDate?.toISOString()}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user