更新ios
This commit is contained in:
@@ -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;
|
|
||||||
|
|
||||||
bars.push(
|
|
||||||
<Box
|
<Box
|
||||||
key={`${event.concept}-${event.startDate}-${rowIndex}`}
|
key={`${concept}-${segment.startDay}-${row}-${index}`}
|
||||||
position="absolute"
|
position="absolute"
|
||||||
left={position.left}
|
left={left}
|
||||||
top={rowIndex * CELL_HEIGHT + CELL_HEIGHT - 28}
|
top={top}
|
||||||
w={position.width}
|
w={width}
|
||||||
zIndex={10}
|
zIndex={10 + stackIndex}
|
||||||
>
|
>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={color.bg}
|
colors={color.bg}
|
||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
end={{ x: 1, y: 0 }}
|
end={{ x: 1, y: 0 }}
|
||||||
style={styles.conceptBar}
|
style={[
|
||||||
|
styles.conceptBar,
|
||||||
|
{
|
||||||
|
// 根据是否是起始/结束段调整圆角
|
||||||
|
borderTopLeftRadius: isStart ? 6 : 0,
|
||||||
|
borderBottomLeftRadius: isStart ? 6 : 0,
|
||||||
|
borderTopRightRadius: segment.isEnd ? 6 : 0,
|
||||||
|
borderBottomRightRadius: segment.isEnd ? 6 : 0,
|
||||||
|
},
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
fontSize="xs"
|
fontSize="2xs"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color={color.text}
|
color={color.text}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
{event.concept}
|
{concept}
|
||||||
{daysCount > 1 && (
|
{totalDays > 1 && isStart && (
|
||||||
<Text fontSize="2xs" opacity={0.8}>
|
<Text fontSize="2xs" opacity={0.8}>
|
||||||
{' '}({daysCount}天)
|
({totalDays}天)
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
</Box>
|
</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,
|
||||||
|
|||||||
@@ -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: [] };
|
||||||
|
|||||||
Reference in New Issue
Block a user