更新ios

This commit is contained in:
2026-01-19 18:26:58 +08:00
parent 639325b7c9
commit 3513a46f03
2 changed files with 170 additions and 96 deletions

View File

@@ -31,7 +31,7 @@ import ztService from '../../services/ztService';
const { width: SCREEN_WIDTH } = Dimensions.get('window'); const { width: SCREEN_WIDTH } = Dimensions.get('window');
const CELL_WIDTH = (SCREEN_WIDTH - 32) / 7; const CELL_WIDTH = (SCREEN_WIDTH - 32) / 7;
const CELL_HEIGHT = 90; // 增加高度以容纳更多内容 const CELL_HEIGHT = 95; // 增加高度以容纳跨天概念条
// 概念颜色调色板 // 概念颜色调色板
const CONCEPT_COLORS = [ const CONCEPT_COLORS = [
@@ -72,26 +72,32 @@ const formatDateStr = (year, month, day) => {
return `${year}${m}${d}`; return `${year}${m}${d}`;
}; };
// 检查是否连续日期(跳过周末) // 解析日期字符串为 Date 对象
const isConsecutiveDate = (date1, date2) => { const parseDateStr = (dateStr) => {
const d1 = new Date( return new Date(
parseInt(date1.slice(0, 4)), parseInt(dateStr.slice(0, 4)),
parseInt(date1.slice(4, 6)) - 1, parseInt(dateStr.slice(4, 6)) - 1,
parseInt(date1.slice(6, 8)) parseInt(dateStr.slice(6, 8))
);
const d2 = new Date(
parseInt(date2.slice(0, 4)),
parseInt(date2.slice(4, 6)) - 1,
parseInt(date2.slice(6, 8))
); );
};
// 检查是否是下一个交易日(跳过周末)
const isNextTradingDay = (date1, date2) => {
const d1 = parseDateStr(date1);
const d2 = parseDateStr(date2);
const diff = (d2 - d1) / (1000 * 60 * 60 * 24); const diff = (d2 - d1) / (1000 * 60 * 60 * 24);
// 直接相邻
if (diff === 1) return true; if (diff === 1) return true;
if (diff === 2 && d1.getDay() === 5) return true; // 周五到周 // 周五到周一(跳过周末)
if (diff === 3 && d1.getDay() === 5) return true; // 周五到周一 if (diff === 3 && d1.getDay() === 5) return true;
// 周六到周一
if (diff === 2 && d1.getDay() === 6) return true;
return false; return false;
}; };
// 合并连续相同概念 // 合并连续相同概念(跨周显示由分段逻辑处理)
const mergeConsecutiveConcepts = (calendarData, year, month) => { const mergeConsecutiveConcepts = (calendarData, year, month) => {
const sorted = [...calendarData] const sorted = [...calendarData]
.filter(d => d.topSector) .filter(d => d.topSector)
@@ -102,17 +108,22 @@ const mergeConsecutiveConcepts = (calendarData, year, month) => {
sorted.forEach((item, index) => { sorted.forEach((item, index) => {
const prevItem = sorted[index - 1]; const prevItem = sorted[index - 1];
const isConsecutive = prevItem &&
item.topSector === prevItem.topSector &&
isConsecutiveDate(prevItem.date, item.date);
if (isConsecutive && currentEvent) { // 检查是否应该继续当前事件
const shouldContinue = prevItem &&
currentEvent &&
item.topSector === prevItem.topSector &&
isNextTradingDay(prevItem.date, item.date);
if (shouldContinue) {
currentEvent.endDate = item.date; currentEvent.endDate = item.date;
currentEvent.dates.push(item.date); currentEvent.dates.push(item.date);
} else { } else {
// 保存之前的事件(如果有多天)
if (currentEvent && currentEvent.dates.length > 1) { if (currentEvent && currentEvent.dates.length > 1) {
events.push(currentEvent); events.push(currentEvent);
} }
// 开始新事件
currentEvent = { currentEvent = {
concept: item.topSector, concept: item.topSector,
startDate: item.date, startDate: item.date,
@@ -122,6 +133,7 @@ const mergeConsecutiveConcepts = (calendarData, year, month) => {
} }
}); });
// 保存最后一个事件
if (currentEvent && currentEvent.dates.length > 1) { if (currentEvent && currentEvent.dates.length > 1) {
events.push(currentEvent); events.push(currentEvent);
} }
@@ -129,6 +141,56 @@ const mergeConsecutiveConcepts = (calendarData, year, month) => {
return events; return events;
}; };
// 将概念事件分割成多行显示的段(处理跨周情况)
// 基于实际的 dates 数组而不是日期范围
const splitEventIntoSegments = (event, startingDay, daysInMonth) => {
const segments = [];
const { dates, concept } = event;
if (!dates || dates.length === 0) return segments;
let currentSegment = null;
dates.forEach((dateStr, idx) => {
const day = parseInt(dateStr.slice(6, 8));
const col = (startingDay + day - 1) % 7;
const row = Math.floor((startingDay + day - 1) / 7);
// 检查是否应该开始新段
// 条件:没有当前段,或者行变化了
if (!currentSegment || currentSegment.row !== row) {
// 保存之前的段
if (currentSegment) {
segments.push(currentSegment);
}
// 开始新段
currentSegment = {
startDay: day,
endDay: day,
startCol: col,
endCol: col,
row: row,
totalDays: dates.length,
concept: concept,
isStart: idx === 0,
isEnd: idx === dates.length - 1,
};
} else {
// 扩展当前段
currentSegment.endDay = day;
currentSegment.endCol = col;
currentSegment.isEnd = idx === dates.length - 1;
}
});
// 保存最后一个段
if (currentSegment) {
segments.push(currentSegment);
}
return segments;
};
// 火焰图标组件 // 火焰图标组件
const FlameIcon = ({ color, size = 14 }) => ( const FlameIcon = ({ color, size = 14 }) => (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none"> <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
@@ -222,27 +284,39 @@ const EventCalendar = ({ navigation }) => {
navigation.navigate('MarketHot', { date: dateStr }); navigation.navigate('MarketHot', { date: dateStr });
}, [currentYear, currentMonth, navigation]); }, [currentYear, currentMonth, navigation]);
// 计算概念条位置 // 获取所有概念条的显示段
const getConceptBarPosition = (event, rowIndex) => { const conceptSegments = useMemo(() => {
const startDay = parseInt(event.startDate.slice(6, 8)); const allSegments = [];
const endDay = parseInt(event.endDate.slice(6, 8));
// 计算在网格中的位置 conceptEvents.forEach((event, eventIndex) => {
const startCol = (startingDay + startDay - 1) % 7; const segments = splitEventIntoSegments(event, startingDay, daysInMonth);
const startRow = Math.floor((startingDay + startDay - 1) / 7); segments.forEach((segment, segIndex) => {
const endCol = (startingDay + endDay - 1) % 7; allSegments.push({
const endRow = Math.floor((startingDay + endDay - 1) / 7); ...segment,
eventIndex,
segmentIndex: segIndex,
color: getConceptColor(event.concept),
});
});
});
// 只处理同一行的情况(简化版) // 按行分组,用于计算同一行内的堆叠
if (startRow !== endRow) return null; const byRow = {};
if (startRow !== rowIndex) return null; allSegments.forEach(seg => {
if (!byRow[seg.row]) byRow[seg.row] = [];
byRow[seg.row].push(seg);
});
return { // 为每个段分配垂直偏移(同一行内多个概念条时)
left: startCol * CELL_WIDTH + 4, Object.values(byRow).forEach(rowSegments => {
width: (endCol - startCol + 1) * CELL_WIDTH - 8, rowSegments.forEach((seg, idx) => {
row: startRow, seg.stackIndex = idx;
}; seg.stackTotal = rowSegments.length;
}; });
});
return allSegments;
}, [conceptEvents, startingDay, daysInMonth]);
// 渲染日历格子 // 渲染日历格子
const renderCalendarCells = () => { const renderCalendarCells = () => {
@@ -373,52 +447,58 @@ const EventCalendar = ({ navigation }) => {
// 渲染概念横条 // 渲染概念横条
const renderConceptBars = () => { const renderConceptBars = () => {
const totalRows = Math.ceil((startingDay + daysInMonth) / 7); return conceptSegments.map((segment, index) => {
const bars = []; const { startCol, endCol, row, totalDays, concept, color, isStart, stackIndex } = segment;
conceptEvents.forEach((event, eventIndex) => { // 计算位置和尺寸
for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) { const left = startCol * CELL_WIDTH + 2;
const position = getConceptBarPosition(event, rowIndex); const width = (endCol - startCol + 1) * CELL_WIDTH - 4;
if (!position) continue; // 根据堆叠索引调整垂直位置(每个概念条高度 20间隔 2
const barHeight = 20;
const verticalOffset = stackIndex * (barHeight + 2);
const top = row * CELL_HEIGHT + CELL_HEIGHT - 26 - verticalOffset;
const color = getConceptColor(event.concept); return (
const daysCount = event.dates.length; <Box
key={`${concept}-${segment.startDay}-${row}-${index}`}
bars.push( position="absolute"
<Box left={left}
key={`${event.concept}-${event.startDate}-${rowIndex}`} top={top}
position="absolute" w={width}
left={position.left} zIndex={10 + stackIndex}
top={rowIndex * CELL_HEIGHT + CELL_HEIGHT - 28} >
w={position.width} <LinearGradient
zIndex={10} colors={color.bg}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={[
styles.conceptBar,
{
// 根据是否是起始/结束段调整圆角
borderTopLeftRadius: isStart ? 6 : 0,
borderBottomLeftRadius: isStart ? 6 : 0,
borderTopRightRadius: segment.isEnd ? 6 : 0,
borderBottomRightRadius: segment.isEnd ? 6 : 0,
},
]}
> >
<LinearGradient <Text
colors={color.bg} fontSize="2xs"
start={{ x: 0, y: 0 }} fontWeight="bold"
end={{ x: 1, y: 0 }} color={color.text}
style={styles.conceptBar} numberOfLines={1}
> >
<Text {concept}
fontSize="xs" {totalDays > 1 && isStart && (
fontWeight="bold" <Text fontSize="2xs" opacity={0.8}>
color={color.text} ({totalDays})
numberOfLines={1} </Text>
> )}
{event.concept} </Text>
{daysCount > 1 && ( </LinearGradient>
<Text fontSize="2xs" opacity={0.8}> </Box>
{' '}({daysCount}) );
</Text>
)}
</Text>
</LinearGradient>
</Box>
);
}
}); });
return bars;
}; };
return ( return (
@@ -641,11 +721,10 @@ const styles = StyleSheet.create({
borderRadius: 16, borderRadius: 16,
}, },
conceptBar: { conceptBar: {
height: 22, height: 20,
borderRadius: 6,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingHorizontal: 8, paddingHorizontal: 6,
}, },
legendBar: { legendBar: {
width: 20, width: 20,

View File

@@ -266,34 +266,29 @@ export const ztService = {
}, },
/** /**
* 快速获取日历数据(dates.json 获取基础信息 * 快速获取日历数据(从 API 获取完整信息包括 top_sector
* @param {number} year - 年份 * @param {number} year - 年份
* @param {number} month - 月份 (1-12) * @param {number} month - 月份 (1-12)
* @returns {Promise<object>} 日历数据 * @returns {Promise<object>} 日历数据
*/ */
getCalendarDataFast: async (year, month) => { getCalendarDataFast: async (year, month) => {
try { try {
const datesResult = await ztService.getAvailableDates(); // 使用后端日历 API 获取包含 top_sector 的完整数据
if (!datesResult.success) { const response = await apiRequest(`/api/zt/calendar?year=${year}&month=${month}`);
return { success: false, data: [] };
}
const dates = datesResult.data.dates || []; if (response.success && response.data) {
const monthStr = String(month).padStart(2, '0'); const calendarData = response.data.map(d => ({
const monthPrefix = `${year}${monthStr}`;
const calendarData = dates
.filter(d => d.date.startsWith(monthPrefix))
.map(d => ({
date: d.date, date: d.date,
ztCount: d.count || 0, ztCount: d.zt_count || 0,
formattedDate: d.formatted_date,
topSector: d.top_sector || '', topSector: d.top_sector || '',
eventCount: d.event_count || 0, eventCount: d.event_count || 0,
indexChange: d.index_change || null, indexChange: d.index_change || null,
})); }));
return { success: true, data: calendarData }; return { success: true, data: calendarData };
}
return { success: false, data: [] };
} catch (error) { } catch (error) {
console.error('获取日历数据失败:', error); console.error('获取日历数据失败:', error);
return { success: false, data: [] }; return { success: false, data: [] };