- Changed database connection credentials in footer.php for improved security. - Disabled error display in ajax.inc.php to prevent sensitive information exposure. - Added comprehensive design documentation for the news flow application. - Implemented a modernized index.php layout with responsive design and enhanced user experience. - Developed main.js for dynamic news loading, including infinite scroll and date filtering functionalities. - Structured interaction.md to detail core interactive features of the application. - Organized outline.md to clarify project structure and core functionalities. - Populated mock-data.json with realistic news data for testing and development purposes.
498 lines
17 KiB
JavaScript
498 lines
17 KiB
JavaScript
class NewsApp {
|
|
constructor() {
|
|
this.newsContainer = document.getElementById('newsContainer');
|
|
this.loadingIndicator = document.getElementById('loadingIndicator');
|
|
this.endOfContent = document.getElementById('endOfContent');
|
|
this.startDateInput = document.getElementById('startDate');
|
|
this.endDateInput = document.getElementById('endDate');
|
|
this.refreshBtn = document.getElementById('refreshBtn');
|
|
|
|
this.currentStartDate = null;
|
|
this.currentEndDate = null;
|
|
this.isLoading = false;
|
|
this.hasMoreNews = true;
|
|
this.allNews = new Map(); // 用于存储所有新闻数据
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupEventListeners();
|
|
this.initializeDates();
|
|
this.loadInitialNews();
|
|
this.setupInfiniteScroll();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// 刷新按钮点击事件
|
|
this.refreshBtn.addEventListener('click', () => {
|
|
this.refreshNews();
|
|
});
|
|
|
|
// 日期输入变化事件
|
|
this.startDateInput.addEventListener('change', () => {
|
|
this.updateDateRange();
|
|
});
|
|
|
|
this.endDateInput.addEventListener('change', () => {
|
|
this.updateDateRange();
|
|
});
|
|
}
|
|
|
|
initializeDates() {
|
|
const today = new Date();
|
|
const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
|
|
// 设置默认日期范围(最近一周)
|
|
this.currentEndDate = this.formatDate(today);
|
|
this.currentStartDate = this.formatDate(weekAgo);
|
|
|
|
this.startDateInput.value = this.currentStartDate;
|
|
this.endDateInput.value = this.currentEndDate;
|
|
}
|
|
|
|
formatDate(date) {
|
|
return date.toISOString().split('T')[0];
|
|
}
|
|
|
|
updateDateRange() {
|
|
const newStartDate = this.startDateInput.value;
|
|
const newEndDate = this.endDateInput.value;
|
|
|
|
if (newStartDate && newEndDate) {
|
|
if (new Date(newStartDate) > new Date(newEndDate)) {
|
|
alert('开始日期不能晚于结束日期');
|
|
return;
|
|
}
|
|
|
|
this.currentStartDate = newStartDate;
|
|
this.currentEndDate = newEndDate;
|
|
this.refreshNews();
|
|
}
|
|
}
|
|
|
|
async loadInitialNews() {
|
|
this.showLoading(true);
|
|
try {
|
|
const newsData = await this.fetchNews(this.currentStartDate, this.currentEndDate);
|
|
this.renderNews(newsData);
|
|
this.hasMoreNews = newsData.length > 0;
|
|
} catch (error) {
|
|
console.error('加载新闻失败:', error);
|
|
this.showError('加载新闻失败,请稍后重试');
|
|
} finally {
|
|
this.showLoading(false);
|
|
}
|
|
}
|
|
|
|
async refreshNews() {
|
|
this.allNews.clear();
|
|
this.newsContainer.innerHTML = '';
|
|
this.hasMoreNews = true;
|
|
this.endOfContent.classList.add('hidden');
|
|
|
|
await this.loadInitialNews();
|
|
}
|
|
|
|
async fetchNews(startDate, endDate) {
|
|
// 显示加载状态
|
|
this.showLoading(true);
|
|
|
|
try {
|
|
// 构建API URL - 请替换为您的实际API地址
|
|
const apiUrl = `https://api.doorcome.cn/api/xwlbNews/?start_date=${startDate}&end_date=${endDate}`;
|
|
|
|
console.log('Fetching news from:', apiUrl); // 调试日志
|
|
|
|
const response = await fetch(apiUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('API Response:', result); // 调试日志
|
|
|
|
// 根据您的API返回格式调整
|
|
// 常见格式示例:
|
|
// 1. { "data": { "news": [...] } }
|
|
// 2. { "news": [...] }
|
|
// 3. [...] // 直接是数组
|
|
|
|
if (result.data && result.data.news) {
|
|
result.data.news.forEach(item => {
|
|
if (item.news_improve) {
|
|
item.news_improve = item.news_improve.replace(/\n/g, '<br />');
|
|
}
|
|
});
|
|
return result.data.news;
|
|
} else if (result.news) {
|
|
return result.news;
|
|
} else if (Array.isArray(result)) {
|
|
return result;
|
|
} else {
|
|
console.error('无法识别的API返回格式:', result);
|
|
return [];
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('API调用失败:', error);
|
|
this.showError('网络请求失败,请检查网络连接');
|
|
throw error;
|
|
} finally {
|
|
this.showLoading(false);
|
|
}
|
|
}
|
|
|
|
renderNews(newsData) {
|
|
// 按日期分组并排序
|
|
const groupedNews = this.groupNewsByDate(newsData);
|
|
|
|
groupedNews.forEach((dayNews, date) => {
|
|
// 如果该日期的新闻已经渲染过,则跳过
|
|
if (this.allNews.has(date)) {
|
|
return;
|
|
}
|
|
|
|
this.allNews.set(date, dayNews);
|
|
|
|
// 创建日期分组
|
|
const dateGroup = this.createDateGroup(date, dayNews);
|
|
this.newsContainer.appendChild(dateGroup);
|
|
|
|
// 添加淡入动画
|
|
anime({
|
|
targets: dateGroup,
|
|
opacity: [0, 1],
|
|
translateY: [20, 0],
|
|
duration: 600,
|
|
easing: 'easeOutQuart'
|
|
});
|
|
});
|
|
}
|
|
|
|
groupNewsByDate(newsData) {
|
|
const grouped = new Map();
|
|
|
|
// 按日期分组
|
|
newsData.forEach(news => {
|
|
const date = news.news_days;
|
|
if (!grouped.has(date)) {
|
|
grouped.set(date, []);
|
|
}
|
|
grouped.get(date).push(news);
|
|
});
|
|
|
|
// 按日期倒序排列
|
|
const sortedGrouped = new Map();
|
|
const sortedDates = Array.from(grouped.keys()).sort((a, b) => new Date(b) - new Date(a));
|
|
|
|
sortedDates.forEach(date => {
|
|
// 同一天的新闻按daily_sub_id排序
|
|
const dayNews = grouped.get(date).sort((a, b) => a.daily_sub_id - b.daily_sub_id);
|
|
sortedGrouped.set(date, dayNews);
|
|
});
|
|
|
|
return sortedGrouped;
|
|
}
|
|
|
|
createDateGroup(date, dayNews) {
|
|
const dateGroup = document.createElement('div');
|
|
dateGroup.className = 'mb-8';
|
|
|
|
// 日期标题
|
|
const dateHeader = document.createElement('div');
|
|
dateHeader.className = 'mb-4';
|
|
dateHeader.innerHTML = `
|
|
<h2 class="text-lg font-semibold text-gray-900 title-font">
|
|
${this.formatDisplayDate(date)}
|
|
</h2>
|
|
<div class="w-16 h-0.5 bg-blue-500 mt-2"></div>
|
|
`;
|
|
dateGroup.appendChild(dateHeader);
|
|
|
|
// 新闻列表
|
|
const newsList = document.createElement('div');
|
|
newsList.className = 'space-y-3';
|
|
|
|
dayNews.forEach((news, index) => {
|
|
const newsItem = this.createNewsItem(news, index);
|
|
newsList.appendChild(newsItem);
|
|
});
|
|
|
|
dateGroup.appendChild(newsList);
|
|
return dateGroup;
|
|
}
|
|
|
|
createNewsItem(news, index) {
|
|
const newsItem = document.createElement('div');
|
|
newsItem.className = 'news-card bg-white rounded-lg shadow-sm border border-gray-200 p-4 cursor-pointer';
|
|
|
|
const newsId = `news-${news.news_days}-${news.daily_sub_id}`;
|
|
|
|
newsItem.innerHTML = `
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex-1">
|
|
<h3 class="text-base font-medium text-gray-900 title-font mb-2 hover:text-blue-600 transition-colors duration-200"
|
|
onclick="toggleNewsContent('${newsId}')">
|
|
${news.news_title}
|
|
</h3>
|
|
<div class="flex items-center space-x-4 text-sm text-gray-500">
|
|
<span class="flex items-center">
|
|
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/>
|
|
</svg>
|
|
${this.formatDisplayDate(news.news_days)}
|
|
</span>
|
|
<span class="flex items-center">
|
|
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/>
|
|
</svg>
|
|
${news.daily_sub_id + 1}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex-shrink-0 ml-4">
|
|
<svg class="w-5 h-5 text-gray-400 transform transition-transform duration-200"
|
|
id="arrow-${newsId}" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="content-${newsId}" class="news-content mt-4">
|
|
<div class="prose prose-sm max-w-none text-gray-700 body-font">
|
|
<p>${news.news_improve}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
return newsItem;
|
|
}
|
|
|
|
formatDisplayDate(dateStr) {
|
|
const date = new Date(dateStr);
|
|
const today = new Date();
|
|
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
|
|
|
if (dateStr === this.formatDate(today)) {
|
|
return '今天';
|
|
} else if (dateStr === this.formatDate(yesterday)) {
|
|
return '昨天';
|
|
} else {
|
|
return date.toLocaleDateString('zh-CN', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
weekday: 'long'
|
|
});
|
|
}
|
|
}
|
|
/*
|
|
setupInfiniteScroll() {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting && !this.isLoading && this.hasMoreNews) {
|
|
this.loadMoreNews();
|
|
}
|
|
});
|
|
}, {
|
|
rootMargin: '100px'
|
|
});
|
|
|
|
// 观察加载指示器
|
|
observer.observe(this.loadingIndicator);
|
|
}*/
|
|
|
|
setupInfiniteScroll() {
|
|
const options = {
|
|
root: null, // 使用视口作为根
|
|
rootMargin: '100px', // 提前100px开始加载
|
|
threshold: 0.1 // 元素10%可见时触发
|
|
};
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting && !this.isLoading && this.hasMoreNews) {
|
|
console.log('触发滚动加载'); // 调试日志
|
|
this.loadMoreNews();
|
|
}
|
|
});
|
|
}, options);
|
|
|
|
// 创建一个触发元素,确保它始终在页面底部
|
|
const triggerElement = document.createElement('div');
|
|
triggerElement.id = 'scrollTrigger';
|
|
triggerElement.style.height = '1px';
|
|
triggerElement.style.marginTop = '100px';
|
|
document.body.appendChild(triggerElement);
|
|
|
|
// 观察触发元素
|
|
observer.observe(triggerElement);
|
|
|
|
// 同时观察加载指示器
|
|
observer.observe(this.loadingIndicator);
|
|
}
|
|
/*
|
|
async loadMoreNews() {
|
|
if (this.isLoading || !this.hasMoreNews) return;
|
|
|
|
this.showLoading(true);
|
|
|
|
try {
|
|
// 找到当前最早的日期
|
|
const earliestDate = this.getEarliestDate();
|
|
if (!earliestDate) {
|
|
this.hasMoreNews = false;
|
|
return;
|
|
}
|
|
|
|
// 计算前一天的日期
|
|
const prevDate = new Date(earliestDate);
|
|
prevDate.setDate(prevDate.getDate() - 1);
|
|
const prevDateStr = this.formatDate(prevDate);
|
|
|
|
// 加载前一天的新闻
|
|
const moreNews = await this.fetchNews(prevDateStr, prevDateStr);
|
|
|
|
if (moreNews.length === 0) {
|
|
this.hasMoreNews = false;
|
|
this.endOfContent.classList.remove('hidden');
|
|
} else {
|
|
this.renderNews(moreNews);
|
|
}
|
|
} catch (error) {
|
|
console.error('加载更多新闻失败:', error);
|
|
this.showError('加载更多新闻失败,请稍后重试');
|
|
} finally {
|
|
this.showLoading(false);
|
|
}
|
|
}
|
|
*/
|
|
|
|
async loadMoreNews() {
|
|
if (this.isLoading || !this.hasMoreNews) {
|
|
console.log('跳过加载:isLoading=', this.isLoading, 'hasMoreNews=', this.hasMoreNews);
|
|
return;
|
|
}
|
|
|
|
console.log('开始加载更多新闻...');
|
|
this.showLoading(true);
|
|
|
|
try {
|
|
// 找到当前最早的日期
|
|
const earliestDate = this.getEarliestDate();
|
|
if (!earliestDate) {
|
|
console.log('没有找到最早日期');
|
|
this.hasMoreNews = false;
|
|
return;
|
|
}
|
|
|
|
// 计算前一天的日期
|
|
const prevDate = new Date(earliestDate);
|
|
prevDate.setDate(prevDate.getDate() - 1);
|
|
const prevDateStr = this.formatDate(prevDate);
|
|
|
|
console.log('加载日期:', prevDateStr);
|
|
|
|
// 加载前一天的新闻
|
|
const moreNews = await this.fetchNews(prevDateStr, prevDateStr);
|
|
|
|
console.log('加载到新闻数量:', moreNews.length);
|
|
|
|
if (moreNews.length === 0) {
|
|
this.hasMoreNews = false;
|
|
this.endOfContent.classList.remove('hidden');
|
|
console.log('没有更多新闻了');
|
|
} else {
|
|
this.renderNews(moreNews);
|
|
this.hasMoreNews = true;
|
|
}
|
|
} catch (error) {
|
|
console.error('加载更多新闻失败:', error);
|
|
this.showError('加载更多新闻失败,请稍后重试');
|
|
} finally {
|
|
this.showLoading(false);
|
|
}
|
|
}
|
|
getEarliestDate() {
|
|
const dates = Array.from(this.allNews.keys());
|
|
if (dates.length === 0) return null;
|
|
|
|
return dates.sort((a, b) => new Date(a) - new Date(b))[0];
|
|
}
|
|
/*
|
|
showLoading(show) {
|
|
this.isLoading = show;
|
|
if (show) {
|
|
this.loadingIndicator.classList.remove('hidden');
|
|
} else {
|
|
this.loadingIndicator.classList.add('hidden');
|
|
}
|
|
}
|
|
*/
|
|
showLoading(show) {
|
|
this.isLoading = show;
|
|
if (show) {
|
|
this.loadingIndicator.classList.remove('hidden');
|
|
// 确保加载指示器可见
|
|
this.loadingIndicator.style.display = 'flex';
|
|
} else {
|
|
this.loadingIndicator.classList.add('hidden');
|
|
this.loadingIndicator.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.className = 'bg-red-50 border border-red-200 rounded-lg p-4 mb-4';
|
|
errorDiv.innerHTML = `
|
|
<div class="flex items-center">
|
|
<svg class="w-5 h-5 text-red-400 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<span class="text-red-700">${message}</span>
|
|
</div>
|
|
`;
|
|
|
|
this.newsContainer.insertBefore(errorDiv, this.newsContainer.firstChild);
|
|
|
|
// 3秒后自动移除错误提示
|
|
setTimeout(() => {
|
|
if (errorDiv.parentNode) {
|
|
errorDiv.parentNode.removeChild(errorDiv);
|
|
}
|
|
}, 3000);
|
|
}
|
|
|
|
delay(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
}
|
|
|
|
// 全局函数:切换新闻内容显示/隐藏
|
|
window.toggleNewsContent = function(newsId) {
|
|
const contentElement = document.getElementById(`content-${newsId}`);
|
|
const arrowElement = document.getElementById(`arrow-${newsId}`);
|
|
|
|
if (contentElement && arrowElement) {
|
|
const isExpanded = contentElement.classList.contains('expanded');
|
|
|
|
if (isExpanded) {
|
|
contentElement.classList.remove('expanded');
|
|
arrowElement.style.transform = 'rotate(0deg)';
|
|
} else {
|
|
contentElement.classList.add('expanded');
|
|
arrowElement.style.transform = 'rotate(180deg)';
|
|
}
|
|
}
|
|
};
|
|
|
|
// 初始化应用
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new NewsApp();
|
|
}); |