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

View File

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