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:
@@ -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
@@ -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>© 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>
|
||||
@@ -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
@@ -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();
|
||||
});
|
||||
@@ -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)
|
||||
|
||||
## 设计特色
|
||||
- 简洁现代的界面设计
|
||||
- 流畅的动画效果
|
||||
- 响应式布局
|
||||
- 优雅的色彩搭配
|
||||
- 直观的用户交互
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user