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:
+498
@@ -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();
|
||||
});
|
||||
Reference in New Issue
Block a user