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
+1 -1
View File
@@ -13,7 +13,7 @@
<span style='color:grey;font-size:small;'> <span style='color:grey;font-size:small;'>
<?php <?php
ini_set("display_errors", "1"); ini_set("display_errors", "1");
$mysqli = new mysqli('localhost','root','nancysimon','myquant'); $mysqli = new mysqli('localhost','myquant','_H(lU1_fF*9baRTp','myquant');
function log_pv(){ function log_pv(){
#echo "start log"; #echo "start log";
global $mysqli; global $mysqli;
+2 -2
View File
@@ -1,6 +1,6 @@
<?php <?php
ini_set("display_errors","1"); ini_set("display_errors","0");
$mysqli = new mysqli('localhost','root','nancysimon','myquant'); $mysqli = new mysqli('localhost','myquant','_H(lU1_fF*9baRTp','myquant');
//ajax: get stock list for select //ajax: get stock list for select
if($_REQUEST['t']=='stockList'){ if($_REQUEST['t']=='stockList'){
+59
View File
@@ -0,0 +1,59 @@
# 新闻流应用设计风格
## 设计理念
### 视觉语言
- **现代简约**:采用简洁的设计语言,突出内容本身
- **层次分明**:通过字体大小、颜色和间距建立清晰的信息层级
- **呼吸感强**:充足的留白空间,避免视觉拥挤
- **专业可信**:使用稳重的色彩搭配,传达权威感
### 色彩方案
- **主色调**:深蓝灰 (#1e293b) - 专业、稳重
- **辅助色**:浅灰蓝 (#64748b) - 次要信息
- **强调色**:天蓝 (#0ea5e9) - 链接、按钮
- **背景色**:纯白 (#ffffff) 和浅灰 (#f8fafc)
- **文本色**:深灰 (#334155) 主文本,中灰 (#64748b) 次要文本
### 字体系统
- **标题字体**Noto Serif SC - 优雅的衬线字体,适合标题
- **正文字体**Noto Sans SC - 清晰的无衬线字体,易读性强
- **字体层级**
- 主标题:text-2xl (24px) font-bold
- 副标题:text-lg (18px) font-semibold
- 正文:text-base (16px) font-normal
- 辅助文本:text-sm (14px) font-normal
## 视觉效果
### 动画效果
- **展开动画**:使用CSS transition实现平滑的内容展开/收起
- **加载动画**:简洁的旋转加载指示器
- **悬停效果**:微妙的阴影和颜色变化
- **滚动动画**:元素进入视口时的淡入效果
### 布局设计
- **卡片式设计**:每条新闻采用卡片容器,增强视觉分组
- **网格系统**:使用Tailwind CSS的网格系统确保响应式布局
- **弹性布局**:Flexbox实现灵活的组件排列
- **间距系统**:统一的间距规范,保持视觉一致性
### 交互样式
- **按钮样式**:圆角设计,悬停时轻微阴影效果
- **链接样式**:下划线动画,悬停时颜色变化
- **状态指示**:清晰的加载、成功、错误状态视觉反馈
- **焦点样式**:键盘导航时的可见焦点指示
### 响应式设计
- **断点设置**
- 移动端:< 768px
- 平板端:768px - 1024px
- 桌面端:> 1024px
- **布局适配**:不同屏幕尺寸下的最佳布局展示
- **触摸优化**:移动端更大的点击区域和手势支持
### 微交互细节
- **阴影效果**:卡片悬停时的微妙阴影提升
- **颜色渐变**:按钮和链接的状态过渡
- **图标使用**:简洁的线性图标,增强视觉引导
- **状态反馈**:清晰的视觉状态变化,提升用户体验
+190
View File
@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>新闻联播 - 实时资讯</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<link href="https://fonts.loli.net/css2?family=Noto+Serif+SC:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
.news-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.news-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.news-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.news-content.expanded {
max-height: 1000px;
transition: max-height 0.5s ease-in;
}
.loading-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.fade-in {
opacity: 0;
animation: fadeIn 0.6s ease-out forwards;
}
@keyframes fadeIn {
to { opacity: 1; }
}
.title-font {
font-family: 'Noto Serif SC', serif;
}
.body-font {
font-family: 'Noto Sans SC', sans-serif;
}
</style>
</head>
<body class="bg-gray-50 body-font">
<!-- Header -->
<header class="bg-white shadow-sm border-b border-gray-200 sticky top-0 z-50">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M2 5a2 2 0 012-2h8a2 2 0 012 2v10a2 2 0 002 2H4a2 2 0 01-2-2V5zm3 1h6v4H5V6zm6 6H5v2h6v-2z" clip-rule="evenodd"/>
</svg>
</div>
<h1 class="text-xl font-bold text-gray-900 title-font">新闻联播-新闻流</h1>
</div>
<div class="text-sm text-gray-500">
实时资讯更新
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Date Filter -->
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between space-y-3 sm:space-y-0">
<div class="flex items-center space-x-4">
<label class="text-sm font-medium text-gray-700">日期范围:</label>
<input type="date" id="startDate" class="border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<span class="text-gray-500"></span>
<input type="date" id="endDate" class="border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
<button id="refreshBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors duration-200">
刷新
</button>
</div>
</div>
<!-- News Container -->
<div id="newsContainer" class="space-y-4">
<!-- News items will be dynamically inserted here -->
</div>
<!-- Loading Indicator -->
<div id="loadingIndicator" class="flex justify-center items-center py-8 hidden">
<div class="flex items-center space-x-3">
<div class="loading-spinner w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full"></div>
<span class="text-gray-600 text-sm">正在加载更多新闻...</span>
</div>
</div>
<!-- End of Content -->
<div id="endOfContent" class="text-center py-8 text-gray-500 hidden">
<div class="flex flex-col items-center space-y-2">
<svg class="w-12 h-12 text-gray-300" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"/>
</svg>
<p class="text-sm">已加载所有新闻</p>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-white border-t border-gray-200 mt-16">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="text-center text-sm text-gray-500">
<p>&copy; 2025 新闻流应用. 实时获取最新资讯</p>
</div>
<div class="text-center text-sm text-gray-500">
<span style="color: grey; font-size: x-small; ">
<a href="https://beian.miit.gov.cn" target="_blank">浙ICP备18056264号-1</a>
</span>
<span style='color:grey;font-size:small;'>
<?php
ini_set("display_errors", "1");
$mysqli = new mysqli('localhost','myquant','_H(lU1_fF*9baRTp','myquant');
function log_pv(){
#echo "start log";
global $mysqli;
$vpage = addslashes($_SERVER['PHP_SELF']);
$ua = addslashes($_SERVER['HTTP_USER_AGENT']);
$ip = $_SERVER['REMOTE_ADDR'];
$qs = addslashes($_SERVER['QUERY_STRING']);
$vtime = date("Y-m-d H:i:s");
$sql="insert into pv_log(vpage,querystring,remoteip,ua,vtime)
values ('".$vpage."','".$qs."','".$ip."', '".$ua."','".$vtime."')";
$mysqli->query($sql);
}
function pv_counter($user=''){
global $mysqli;
$hits=0; //set default page hits 0;
$page=$_SERVER['PHP_SELF'];
$lmtime=date("Y-m-d H:i:s");
$sql="select hits from pv_counter where page_name='".$page."'";
$result=$mysqli->query($sql);
$rt=$result->fetch_all(MYSQLI_ASSOC);
$n=count($rt);
if($n<1){
$sql = "insert into pv_counter(page_id,Page_name,hits,lm_date,lm_ip,lm_user)
values (null,
'".$page."','1','".$lmtime."',
'".$_SERVER['REMOTE_ADDR']."','".$user."')";
$mysqli->query($sql);
$hits=1;
}
if($n==1){
$hits=$rt[0]['hits'];
$hits++;
$sql="update pv_counter set hits='".$hits."',lm_date='".$lmtime."',
lm_ip='".$_SERVER['REMOTE_ADDR']."',lm_user='".$user."' where page_name='".$page."'";
$mysqli->query($sql);
}
if($n>1){
$display= "<p align='center'>There is error on pv_counter </p>";
return $display;
}
$sql="select sum(hits) hits_all from pv_counter";
$result=$mysqli->query($sql);
$hits_all=$result->fetch_all(MYSQLI_ASSOC);
$display="Page hits: ".$hits." Total: ".$hits_all[0]['hits_all'];
echo $display;
}
log_pv();
pv_counter($user='');
$mysqli->close();
?>
</span>
</div>
</div>
</footer>
<script src="main.js"></script>
</body>
</html>
+61
View File
@@ -0,0 +1,61 @@
# 新闻流应用交互设计
## 核心交互功能
### 1. 新闻展示与展开
- **初始状态**:显示新闻标题和日期,内容区域收起
- **点击交互**:用户点击标题时,平滑展开显示完整新闻内容
- **收起功能**:再次点击可收起内容,保持界面整洁
- **视觉反馈**:展开/收起时有流畅的动画效果
### 2. 日期排序与分组
- **默认排序**:按日期倒序排列(最新在前)
- **日期分组**:同一天的新闻在视觉上分组显示
- **日期标识**:每个日期组有清晰的日期标题
- **时间格式**:友好的日期显示格式(如"今天"、"昨天"、具体日期)
### 3. 无限滚动加载
- **触发机制**:用户滚动到页面底部时自动触发
- **加载逻辑**:基于当前最早日期往前加载前一天的新闻
- **加载状态**:显示加载指示器,避免重复请求
- **错误处理**:加载失败时显示友好提示
### 4. 初始参数处理
- **日期范围**:支持通过URL参数传递start_date和end_date
- **默认值**:如无参数,使用当前日期作为end_date,往前7天作为start_date
- **参数验证**:确保日期格式正确且在合理范围内
## 用户体验流程
### 首次访问
1. 页面加载,显示初始日期范围内的新闻
2. 新闻按日期倒序排列,内容收起
3. 用户可以浏览标题,点击感兴趣的内容查看详情
### 浏览过程
1. 用户向下滚动查看更多新闻
2. 到达底部时自动加载前一天的新闻
3. 新加载的内容追加到列表末尾
4. 保持流畅的浏览体验
### 内容查看
1. 点击新闻标题展开内容
2. 内容区域平滑展开,显示完整文本
3. 可以滚动阅读,再次点击收起
## 交互细节
### 视觉反馈
- 悬停效果:鼠标悬停在标题上时显示视觉反馈
- 加载动画:数据加载时显示优雅的加载指示器
- 状态变化:展开/收起状态有清晰的视觉区分
### 响应式设计
- 移动端优化:确保在小屏幕设备上良好显示
- 触摸友好:按钮和交互区域足够大,便于触摸操作
- 滚动流畅:优化滚动性能,避免卡顿
### 性能考虑
- 懒加载:只加载可视区域附近的内容
- 防抖处理:滚动事件添加防抖,避免频繁触发
- 内存管理:及时清理不需要的DOM元素和事件监听器
+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();
});
+46
View File
@@ -0,0 +1,46 @@
# 新闻流前端应用项目结构
## 项目概述
创建一个现代化的新闻流阅读器,支持新闻标题展示、内容展开、按日期排序和无限滚动加载功能。
## 文件结构
```
/mnt/okcomputer/output/
├── index.html # 主页面
├── main.js # 主要JavaScript逻辑
├── resources/ # 资源文件夹
│ └── mock-data.json # 模拟API数据
└── README.md # 项目说明
```
## 核心功能模块
### 1. 主页面 (index.html)
- 响应式布局设计
- 新闻列表容器
- 加载状态指示器
- 导航栏和页脚
### 2. JavaScript逻辑 (main.js)
- 新闻数据获取和渲染
- 点击展开/收起功能
- 无限滚动加载机制
- 日期排序和格式化
- 加载状态管理
### 3. 模拟数据 (mock-data.json)
- 符合API格式的测试数据
- 包含多条新闻记录
- 不同日期的新闻内容
## 技术栈
- HTML5 + CSS3 + JavaScript (ES6+)
- Tailwind CSS (通过CDN)
- 现代浏览器API (Intersection Observer)
## 设计特色
- 简洁现代的界面设计
- 流畅的动画效果
- 响应式布局
- 优雅的色彩搭配
- 直观的用户交互
+102
View File
@@ -0,0 +1,102 @@
{
"status": "success",
"data": {
"news": [
{
"news_days": "2024-10-13",
"daily_sub_id": 0,
"news_title": "科技创新推动经济发展新模式",
"news_improve": "近日,相关部门发布最新数据显示,我国科技创新能力持续提升,多项技术指标达到国际先进水平。专家分析认为,这得益于长期的技术积累和持续的研发投入,为经济社会发展注入了强劲动力。科技创新不仅推动了传统产业的转型升级,也催生了新兴产业的发展壮大。"
},
{
"news_days": "2024-10-13",
"daily_sub_id": 1,
"news_title": "环保政策实施效果显著提升",
"news_improve": "根据最新统计报告,环保政策的实施取得了显著成效。空气质量改善、水质提升、绿化覆盖率增加等指标均呈现积极变化,表明生态文明建设正在稳步推进。各地政府积极响应国家号召,加大环保投入,推动绿色发展理念深入人心。"
},
{
"news_days": "2024-10-13",
"daily_sub_id": 2,
"news_title": "教育改革深化促进学生全面发展",
"news_improve": "教育领域的改革不断深化,新的教学模式和评价体系逐步建立。学生的综合素质得到全面提升,创新能力和实践能力的培养受到更多重视,为未来发展奠定了坚实基础。教育公平性也得到进一步保障,城乡教育差距持续缩小。"
},
{
"news_days": "2024-10-12",
"daily_sub_id": 0,
"news_title": "医疗卫生服务水平持续改善",
"news_improve": "医疗卫生服务体系持续完善,基层医疗服务能力显著增强。新的医疗技术和设备的应用,使得诊疗水平不断提高,人民群众的健康获得感明显增强。医疗保障覆盖面进一步扩大,看病难、看病贵问题得到有效缓解。"
},
{
"news_days": "2024-10-12",
"daily_sub_id": 1,
"news_title": "文化传承与创新并重发展",
"news_improve": "文化传承与创新工作取得新进展,传统文化保护力度加大,同时新兴文化业态蓬勃发展。这种双轮驱动的发展模式,为文化繁荣注入了新的活力。文化产业成为经济增长的新亮点,文化软实力不断提升。"
},
{
"news_days": "2024-10-11",
"daily_sub_id": 0,
"news_title": "基础设施建设稳步推进",
"news_improve": "基础设施建设保持稳步推进态势,交通、能源、通信等领域的重大项目相继完工。这些项目的建成投用,为区域协调发展和城乡一体化提供了有力支撑。现代化基础设施网络的完善,为经济社会发展奠定了坚实基础。"
},
{
"news_days": "2024-10-11",
"daily_sub_id": 1,
"news_title": "国际合作交流日益频繁",
"news_improve": "国际交流合作日益频繁,多边合作机制不断完善。在贸易、投资、科技、文化等领域的合作不断深化,为我国参与全球治理积累了宝贵经验。开放合作的姿态赢得了国际社会的广泛认可和赞誉。"
},
{
"news_days": "2024-10-10",
"daily_sub_id": 0,
"news_title": "数字经济蓬勃发展态势良好",
"news_improve": "数字经济发展势头良好,新技术、新业态、新模式不断涌现。传统产业数字化转型加速,新兴产业集群效应显现,为经济高质量发展提供了新动能。数字技术与实体经济深度融合,创新活力持续迸发。"
},
{
"news_days": "2024-10-10",
"daily_sub_id": 1,
"news_title": "乡村振兴战略深入实施",
"news_improve": "乡村振兴战略深入实施,农业农村现代化水平不断提升。产业发展、基础设施建设、公共服务改善等方面都取得了积极进展,农民生活水平显著提高。城乡融合发展体制机制不断完善,乡村面貌焕然一新。"
},
{
"news_days": "2024-10-09",
"daily_sub_id": 0,
"news_title": "城市治理现代化水平提升",
"news_improve": "城市治理现代化水平持续提升,智慧城市建设成效显著。城市管理更加精细化、智能化,市民的生活品质和满意度不断提高。科技创新在城市管理中的应用日益广泛,治理效能显著增强。"
},
{
"news_days": "2024-10-09",
"daily_sub_id": 1,
"news_title": "科技创新成果转化率提高",
"news_improve": "科技创新成果转化率持续提高,产学研合作更加紧密。一批重大科技成果成功转化为现实生产力,为产业发展提供了强有力的技术支撑。创新生态体系不断完善,科技成果转化机制更加顺畅。"
},
{
"news_days": "2024-10-08",
"daily_sub_id": 0,
"news_title": "绿色发展理念深入人心",
"news_improve": "绿色发展理念深入人心,生态文明建设成效显著。清洁能源使用比例不断提升,节能减排效果明显,环境质量持续改善。绿色生产方式和生活方式得到广泛推广,可持续发展能力不断增强。"
},
{
"news_days": "2024-10-08",
"daily_sub_id": 1,
"news_title": "公共服务体系不断完善",
"news_improve": "公共服务体系不断完善,服务质量和效率显著提升。教育、医疗、养老、文化等公共服务资源配置更加合理,覆盖范围不断扩大。人民群众的获得感、幸福感、安全感不断增强。"
},
{
"news_days": "2024-10-07",
"daily_sub_id": 0,
"news_title": "社会治理创新实践丰富",
"news_improve": "社会治理创新实践丰富多样,基层治理能力持续增强。多元主体参与的社会治理格局逐步形成,治理方式更加科学化、精细化。社会矛盾预防和化解机制不断完善,社会和谐稳定局面持续巩固。"
},
{
"news_days": "2024-10-07",
"daily_sub_id": 1,
"news_title": "开放型经济新体制构建",
"news_improve": "开放型经济新体制构建取得重要进展,贸易投资自由化便利化水平不断提升。营商环境持续优化,外资吸引力显著增强。国际合作平台作用充分发挥,为我国经济发展注入了新的活力。"
}
]
},
"meta": {
"total": 15,
"start_date": "2024-10-07",
"end_date": "2024-10-13"
}
}