feat: Update database connection and error display settings

- 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.
This commit is contained in:
杨水淼
2025-10-13 16:16:46 +08:00
parent 521e0687a4
commit 4a56455151
8 changed files with 959 additions and 3 deletions
+498
View File
@@ -0,0 +1,498 @@
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();
});