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]
|
stock_code = stock_code.split('.')[0]
|
||||||
|
|
||||||
with engine.connect() as conn:
|
with engine.connect() as conn:
|
||||||
# 获取事件日期前后的数据(前730天/2年,后30天)
|
# 获取事件日期前后的数据(前365天/1年,后30天)
|
||||||
kline_sql = """
|
kline_sql = """
|
||||||
WITH date_range AS (SELECT TRADEDATE \
|
WITH date_range AS (SELECT TRADEDATE \
|
||||||
FROM ea_trade \
|
FROM ea_trade \
|
||||||
WHERE SECCODE = :stock_code \
|
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) \
|
AND DATE_ADD(:trade_date, INTERVAL 30 DAY) \
|
||||||
GROUP BY TRADEDATE \
|
GROUP BY TRADEDATE \
|
||||||
ORDER BY TRADEDATE)
|
ORDER BY TRADEDATE)
|
||||||
|
|||||||
@@ -170,6 +170,21 @@ const KLineChartModal: React.FC<KLineChartModalProps> = ({
|
|||||||
d.close >= d.open ? '#ef5350' : '#26a69a'
|
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 = {
|
const option: echarts.EChartsOption = {
|
||||||
backgroundColor: '#1a1a1a',
|
backgroundColor: '#1a1a1a',
|
||||||
@@ -346,6 +361,34 @@ const KLineChartModal: React.FC<KLineChartModalProps> = ({
|
|||||||
borderColor: '#ef5350',
|
borderColor: '#ef5350',
|
||||||
borderColor0: '#26a69a',
|
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: '成交量',
|
name: '成交量',
|
||||||
@@ -420,7 +463,7 @@ const KLineChartModal: React.FC<KLineChartModalProps> = ({
|
|||||||
</Text>
|
</Text>
|
||||||
{data.length > 0 && (
|
{data.length > 0 && (
|
||||||
<Text fontSize="xs" color="#666" fontStyle="italic">
|
<Text fontSize="xs" color="#666" fontStyle="italic">
|
||||||
共{data.length}个交易日(最多2年)
|
共{data.length}个交易日(最多1年)
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -172,6 +172,20 @@ const TimelineChartModal: React.FC<TimelineChartModalProps> = ({
|
|||||||
d.price >= basePrice ? '#ef5350' : '#26a69a'
|
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 = {
|
const option: echarts.EChartsOption = {
|
||||||
backgroundColor: '#1a1a1a',
|
backgroundColor: '#1a1a1a',
|
||||||
@@ -341,6 +355,34 @@ const TimelineChartModal: React.FC<TimelineChartModalProps> = ({
|
|||||||
{ offset: 1, color: 'rgba(33, 150, 243, 0.05)' },
|
{ 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: '均价',
|
name: '均价',
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ import dayjs from 'dayjs';
|
|||||||
import 'dayjs/locale/zh-cn';
|
import 'dayjs/locale/zh-cn';
|
||||||
import { logger } from '../../../utils/logger';
|
import { logger } from '../../../utils/logger';
|
||||||
import { getApiBase } from '../../../utils/apiConfig';
|
import { getApiBase } from '../../../utils/apiConfig';
|
||||||
|
import TimelineChartModal from '../../../components/StockChart/TimelineChartModal';
|
||||||
|
import KLineChartModal from '../../../components/StockChart/KLineChartModal';
|
||||||
import './InvestmentCalendar.css';
|
import './InvestmentCalendar.css';
|
||||||
|
|
||||||
dayjs.locale('zh-cn');
|
dayjs.locale('zh-cn');
|
||||||
@@ -63,6 +65,8 @@ dayjs.locale('zh-cn');
|
|||||||
export default function InvestmentCalendarChakra() {
|
export default function InvestmentCalendarChakra() {
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const { isOpen: isAddOpen, onOpen: onAddOpen, onClose: onAddClose } = 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();
|
const toast = useToast();
|
||||||
|
|
||||||
// 颜色主题
|
// 颜色主题
|
||||||
@@ -74,6 +78,7 @@ export default function InvestmentCalendarChakra() {
|
|||||||
const [events, setEvents] = useState([]);
|
const [events, setEvents] = useState([]);
|
||||||
const [selectedDate, setSelectedDate] = useState(null);
|
const [selectedDate, setSelectedDate] = useState(null);
|
||||||
const [selectedDateEvents, setSelectedDateEvents] = useState([]);
|
const [selectedDateEvents, setSelectedDateEvents] = useState([]);
|
||||||
|
const [selectedStock, setSelectedStock] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [newEvent, setNewEvent] = useState({
|
const [newEvent, setNewEvent] = useState({
|
||||||
title: '',
|
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 (
|
return (
|
||||||
<Card bg={bgColor} shadow="md">
|
<Card bg={bgColor} shadow="md">
|
||||||
<CardHeader pb={4}>
|
<CardHeader pb={4}>
|
||||||
@@ -386,15 +420,47 @@ export default function InvestmentCalendarChakra() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{event.extendedProps?.stocks && event.extendedProps.stocks.length > 0 && (
|
{event.extendedProps?.stocks && event.extendedProps.stocks.length > 0 && (
|
||||||
<HStack spacing={2} flexWrap="wrap">
|
<VStack align="stretch" spacing={2}>
|
||||||
<Text fontSize="sm" color={secondaryText}>相关股票:</Text>
|
<HStack spacing={2} flexWrap="wrap">
|
||||||
{event.extendedProps.stocks.map((stock, i) => (
|
<Text fontSize="sm" color={secondaryText}>相关股票:</Text>
|
||||||
<Tag key={i} size="sm" colorScheme="blue">
|
{event.extendedProps.stocks.map((stock, i) => (
|
||||||
<TagLeftIcon as={FiTrendingUp} />
|
<Tag
|
||||||
<TagLabel>{stock}</TagLabel>
|
key={i}
|
||||||
</Tag>
|
size="sm"
|
||||||
))}
|
colorScheme="blue"
|
||||||
</HStack>
|
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>
|
</Box>
|
||||||
))}
|
))}
|
||||||
@@ -489,6 +555,32 @@ export default function InvestmentCalendarChakra() {
|
|||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</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>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user