这个系统将模拟一个真实的舆情监控后台,具备以下核心功能:

(图片来源网络,侵删)
- 实时数据概览:显示监控的关键指标,如总提及量、情感倾向、热门话题等。
- 舆情信息列表:以表格形式展示最新的舆情信息,包括来源、标题、情感、时间等。
- 情感分析仪表盘:使用图表直观展示正面、负面、中性情感的比例。
- 热门关键词云:展示当前最受关注的关键词。
- 交互功能:包括筛选、搜索、查看详情等。
我们将使用纯前端技术来实现,并模拟动态数据更新,以展示其“实时”监控的能力。
第一步:项目结构
创建一个清晰的项目文件夹结构。
public-opinion-monitor/
├── index.html # 主页面
├── css/
│ └── style.css # 样式文件
├── js/
│ ├── data.js # 模拟数据
│ └── main.js # 主要逻辑
└── images/
└── logo.png # (可选) Logo 图片
第二步:HTML 结构 (index.html)
这是页面的骨架,使用 HTML5 语义化标签来组织内容,有助于 SEO 和可读性。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">舆情监控系统</title>
<link rel="stylesheet" href="css/style.css">
<!-- 引入字体图标库,Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<header class="main-header">
<div class="container">
<div class="logo">
<i class="fas fa-chart-line"></i>
<h1>舆情监控系统</h1>
</div>
<nav class="main-nav">
<a href="#" class="active">仪表盘</a>
<a href="#">报告</a>
<a href="#">设置</a>
</nav>
</div>
</header>
<main class="main-content">
<div class="container">
<!-- 数据概览卡片 -->
<section class="dashboard-overview">
<div class="card">
<div class="card-icon">
<i class="fas fa-comments"></i>
</div>
<div class="card-content">
<h3>总提及量</h3>
<p class="card-value" id="total-mentions">0</p>
<p class="card-trend">较昨日 <span class="up">+12%</span></p>
</div>
</div>
<div class="card">
<div class="card-icon negative">
<i class="fas fa-frown"></i>
</div>
<div class="card-content">
<h3>负面舆情</h3>
<p class="card-value" id="negative-count">0</p>
<p class="card-trend">需要关注</p>
</div>
</div>
<div class="card">
<div class="card-icon positive">
<i class="fas fa-smile"></i>
</div>
<div class="card-content">
<h3>正面舆情</h3>
<p class="card-value" id="positive-count">0</p>
<p class="card-trend">保持良好</p>
</div>
</div>
<div class="card">
<div class="card-icon neutral">
<i class="fas fa-meh"></i>
</div>
<div class="card-content">
<h3>中性舆情</h3>
<p class="card-value" id="neutral-count">0</p>
<p class="card-trend">正常范围</p>
</div>
</div>
</section>
<!-- 图表和舆情列表区域 -->
<div class="content-grid">
<!-- 左侧:情感分析图表和关键词云 -->
<aside class="sidebar">
<div class="chart-container">
<h2>情感分析</h2>
<canvas id="sentimentChart"></canvas>
</div>
<div class="keyword-cloud-container">
<h2>热门关键词</h2>
<div id="keyword-cloud">
<!-- 关键词将通过 JS 动态生成 -->
</div>
</div>
</aside>
<!-- 右侧:舆情信息列表 -->
<section class="opinion-list">
<div class="list-header">
<h2>最新舆情动态</h2>
<div class="search-and-filter">
<input type="text" id="search-input" placeholder="搜索标题或来源...">
<select id="filter-sentiment">
<option value="all">全部情感</option>
<option value="positive">正面</option>
<option value="neutral">中性</option>
<option value="negative">负面</option>
</select>
</div>
</div>
<div class="table-wrapper">
<table id="opinion-table">
<thead>
<tr>
<th>来源</th>
<th>标题</th>
<th>情感</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="opinion-tbody">
<!-- 舆情数据将通过 JS 动态生成 -->
</tbody>
</table>
</div>
</section>
</div>
</div>
</main>
<footer class="main-footer">
<div class="container">
<p>© 2025 舆情监控系统. All rights reserved.</p>
</div>
</footer>
<!-- 引入 Chart.js 库用于绘制图表 -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- 引入我们自己的 JS 文件 -->
<script src="js/data.js"></script>
<script src="js/main.js"></script>
</body>
</html>
第三步:CSS 样式 (css/style.css)
样式让页面变得美观和专业,我们使用 Flexbox 和 Grid 布局来实现响应式设计。

(图片来源网络,侵删)
/* style.css */
:root {
--primary-color: #3498db;
--secondary-color: #2c3e50;
--success-color: #2ecc71;
--warning-color: #f39c12;
--danger-color: #e74c3c;
--light-bg: #ecf0f1;
--white: #ffffff;
--text-color: #333;
--border-color: #ddd;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--light-bg);
color: var(--text-color);
line-height: 1.6;
}
.container {
width: 90%;
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
}
/* Header */
.main-header {
background-color: var(--secondary-color);
color: var(--white);
padding: 1rem 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.main-header .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo i { font-size: 1.8rem; margin-right: 10px; }
.logo h1 { font-size: 1.5rem; }
.main-nav a {
color: var(--white);
text-decoration: none;
margin-left: 20px;
padding: 5px 10px;
border-radius: 4px;
transition: background-color 0.3s;
}
.main-nav a.active, .main-nav a:hover { background-color: rgba(255,255,255,0.1); }
/* Main Content */
.main-content { padding: 2rem 0; }
/* Dashboard Overview */
.dashboard-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 2rem;
}
.card {
background: var(--white);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
display: flex;
align-items: center;
}
.card-icon { font-size: 2.5rem; margin-right: 20px; }
.card-icon.positive { color: var(--success-color); }
.card-icon.negative { color: var(--danger-color); }
.card-icon.neutral { color: var(--warning-color); }
.card-content h3 { font-size: 0.9rem; color: #777; margin-bottom: 5px; }
.card-value { font-size: 2rem; font-weight: bold; margin-bottom: 5px; }
.card-trend { font-size: 0.8rem; }
.card-trend .up { color: var(--success-color); }
.card-trend .down { color: var(--danger-color); }
/* Content Grid */
.content-grid {
display: grid;
grid-template-columns: 350px 1fr;
gap: 20px;
}
/* Sidebar */
.sidebar .chart-container, .sidebar .keyword-cloud-container {
background: var(--white);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.sidebar h2 { margin-bottom: 15px; font-size: 1.2rem; }
#sentimentChart { max-height: 250px; }
/* Keyword Cloud */
#keyword-cloud {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.keyword {
background: var(--light-bg);
padding: 5px 12px;
border-radius: 20px;
font-size: 0.9rem;
transition: transform 0.2s, background-color 0.2s;
cursor: pointer;
}
.keyword:hover {
transform: scale(1.05);
background-color: var(--primary-color);
color: var(--white);
}
/* Opinion List */
.opinion-list {
background: var(--white);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.search-and-filter {
display: flex;
gap: 10px;
}
.search-and-filter input, .search-and-filter select {
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
outline: none;
}
.search-and-filter input:focus { border-color: var(--primary-color); }
/* Table */
.table-wrapper {
overflow-x: auto;
}
#opinion-table {
width: 100%;
border-collapse: collapse;
}
#opinion-table th, #opinion-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
#opinion-table th {
background-color: var(--light-bg);
font-weight: 600;
}
#opinion-table tr:hover { background-color: #f9f9f9; }
.sentiment-badge {
padding: 3px 10px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
}
.sentiment-badge.positive { background-color: rgba(46, 204, 113, 0.2); color: var(--success-color); }
.sentiment-badge.negative { background-color: rgba(231, 76, 60, 0.2); color: var(--danger-color); }
.sentiment-badge.neutral { background-color: rgba(243, 156, 18, 0.2); color: var(--warning-color); }
.btn-details {
background: none;
border: none;
color: var(--primary-color);
cursor: pointer;
text-decoration: underline;
font-size: 0.9rem;
}
.btn-details:hover { opacity: 0.8; }
/* Footer */
.main-footer {
background-color: var(--secondary-color);
color: var(--white);
text-align: center;
padding: 1rem 0;
margin-top: 2rem;
}
第四步:模拟数据 (js/data.js)
为了让系统可以运行,我们创建一个模拟数据文件,这些数据将代表从后端 API 获取的舆情信息。
// js/data.js
// 模拟舆情数据
const mockOpinionData = [
{ id: 1, source: '新浪微博', title: '某品牌新品发布会,网友反响热烈!', sentiment: 'positive', time: '2025-10-27 10:30:15', url: '#' },
{ id: 2, source: '知乎', title: '如何评价某手机这次的电池续航问题?', sentiment: 'negative', time: '2025-10-27 09:45:22', url: '#' },
{ id: 3, source: '微信公众号', title: '行业分析:未来十年的发展趋势预测', sentiment: 'neutral', time: '2025-10-27 08:20:11', url: '#' },
{ id: 4, source: '抖音', title: '这个产品太好用了,无限回购!', sentiment: 'positive', time: '2025-10-27 11:05:33', url: '#' },
{ id: 5, source: '新闻头条', title: '公司因管理不善被曝出丑闻,股价大跌', sentiment: 'negative', time: '2025-10-27 07:55:44', url: '#' },
{ id: 6, source: 'B站', title: '深度测评:这款耳机到底值不值得买?', sentiment: 'neutral', time: '2025-10-27 12:15:00', url: '#' },
{ id: 7, source: '小红书', title: 'OMG!这个口红颜色绝了,黄皮友好!', sentiment: 'positive', time: '2025-10-27 13:00:05', url: '#' },
{ id: 8, source: '天涯论坛', title: '老员工爆料:公司内部问题重重', sentiment: 'negative', time: '2025-10-27 06:30:10', url: '#' }
];
// 模拟关键词数据
const mockKeywords = [
{ text: '新品发布', weight: 10 },
{ text: '电池续航', weight: 9 },
{ text: '行业分析', weight: 7 },
{ text: '管理不善', weight: 8 },
{ text: '深度测评', weight: 6 },
{ text: '黄皮友好', weight: 5 },
{ text: '内部爆料', weight: 9 },
{ text: '股价大跌', weight: 7 },
{ text: '无限回购', weight: 6 },
{ text: '发展趋势', weight: 5 }
];
// 模拟实时数据更新函数
function addNewOpinion() {
const sources = ['新浪微博', '知乎', '微信公众号', '抖音', '新闻头条', 'B站', '小红书', '天涯论坛'];
const titles = [
'突发新闻!某公司宣布重大战略合作',
'用户体验报告:这个APP真的好用吗?',
'专家解读:新政策将如何影响市场',
'又一个网红产品火了,你买了吗?',
'公司发布财报,业绩超出预期',
'Vlog:我的日常好物分享',
'避雷指南:这些产品千万别买!',
'员工福利提升,大家都很开心'
];
const sentiments = ['positive', 'negative', 'neutral'];
const newOpinion = {
id: mockOpinionData.length + 1,
source: sources[Math.floor(Math.random() * sources.length)],
title: titles[Math.floor(Math.random() * titles.length)],
sentiment: sentiments[Math.floor(Math.random() * sentiments.length)],
time: new Date().toLocaleString('zh-CN'),
url: '#'
};
// 添加到数据开头
mockOpinionData.unshift(newOpinion);
// 保持数据量不超过20条
if (mockOpinionData.length > 20) {
mockOpinionData.pop();
}
// 重新渲染列表和更新统计
renderOpinionTable(mockOpinionData);
updateDashboardStats();
}
第五步:核心 JavaScript 逻辑 (js/main.js)
这是系统的大脑,负责数据渲染、事件处理和图表绘制。
// js/main.js
document.addEventListener('DOMContentLoaded', () => {
// --- 初始化 ---
renderOpinionTable(mockOpinionData);
renderKeywordCloud(mockKeywords);
updateDashboardStats();
initSentimentChart();
// --- 事件监听器 ---
const searchInput = document.getElementById('search-input');
const filterSentiment = document.getElementById('filter-sentiment');
searchInput.addEventListener('input', filterOpinions);
filterSentiment.addEventListener('change', filterOpinions);
// --- 模拟实时更新 ---
// 每5秒添加一条新数据
setInterval(addNewOpinion, 5000);
// 每10秒更新一次关键词(模拟)
setInterval(() => {
// 随机更新一个关键词的权重
const randomIndex = Math.floor(Math.random() * mockKeywords.length);
mockKeywords[randomIndex].weight += Math.floor(Math.random() * 3) - 1;
renderKeywordCloud(mockKeywords);
}, 10000);
// --- 函数定义 ---
/**
* 渲染舆情列表表格
* @param {Array} data - 要渲染的数据
*/
function renderOpinionTable(data) {
const tbody = document.getElementById('opinion-tbody');
tbody.innerHTML = ''; // 清空现有内容
data.forEach(item => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${item.source}</td>
<td>${item.title}</td>
<td><span class="sentiment-badge ${item.sentiment}">${getSentimentText(item.sentiment)}</span></td>
<td>${item.time}</td>
<td><a href="${item.url}" class="btn-details">查看详情</a></td>
`;
tbody.appendChild(row);
});
}
/**
* 渲染关键词云
* @param {Array} keywords - 关键词数组
*/
function renderKeywordCloud(keywords) {
const cloudContainer = document.getElementById('keyword-cloud');
cloudContainer.innerHTML = '';
// 找出最大权重用于缩放
const maxWeight = Math.max(...keywords.map(k => k.weight));
keywords.forEach(keyword => {
const span = document.createElement('span');
span.className = 'keyword';
span.textContent = keyword.text;
// 根据权重设置字体大小
const fontSize = 0.8 + (keyword.weight / maxWeight) * 1.2; // 0.8rem 到 2.0rem
span.style.fontSize = `${fontSize}rem`;
cloudContainer.appendChild(span);
});
}
/**
* 更新仪表盘统计数据
*/
function updateDashboardStats() {
const stats = mockOpinionData.reduce((acc, item) => {
acc.total++;
acc[item.sentiment]++;
return acc;
}, { total: 0, positive: 0, negative: 0, neutral: 0 });
document.getElementById('total-mentions').textContent = stats.total;
document.getElementById('positive-count').textContent = stats.positive;
document.getElementById('negative-count').textContent = stats.negative;
document.getElementById('neutral-count').textContent = stats.neutral;
// 更新图表
if (window.sentimentChart) {
window.sentimentChart.data.datasets[0].data = [stats.positive, stats.neutral, stats.negative];
window.sentimentChart.update();
}
}
/**
* 初始化情感分析图表
*/
function initSentimentChart() {
const ctx = document.getElementById('sentimentChart').getContext('2d');
const stats = mockOpinionData.reduce((acc, item) => {
acc[item.sentiment]++;
return acc;
}, { positive: 0, neutral: 0, negative: 0 });
window.sentimentChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['正面', '中性', '负面'],
datasets: [{
data: [stats.positive, stats.neutral, stats.negative],
backgroundColor: [
'rgba(46, 204, 113, 0.8)', // 绿色 - 正面
'rgba(243, 156, 18, 0.8)', // 橙色 - 中性
'rgba(231, 76, 60, 0.8)' // 红色 - 负面
],
borderColor: [
'rgba(46, 204, 113, 1)',
'rgba(243, 156, 18, 1)',
'rgba(231, 76, 60, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
}
}
}
});
}
/**
* 根据情感类型返回对应的中文文本
* @param {string} sentiment - 情感类型
* @returns {string}
*/
function getSentimentText(sentiment) {
const sentimentMap = {
'positive': '正面',
'neutral': '中性',
'negative': '负面'
};
return sentimentMap[sentiment] || sentiment;
}
/**
* 筛选舆情列表
*/
function filterOpinions() {
const searchTerm = document.getElementById('search-input').value.toLowerCase();
const sentimentFilter = document.getElementById('filter-sentiment').value;
const filteredData = mockOpinionData.filter(item => {
const matchesSearch = item.title.toLowerCase().includes(searchTerm) || item.source.toLowerCase().includes(searchTerm);
const matchesSentiment = sentimentFilter === 'all' || item.sentiment === sentimentFilter;
return matchesSearch && matchesSentiment;
});
renderOpinionTable(filteredData);
}
});
如何运行
- 将以上四个文件(
index.html,style.css,data.js,main.js)按照目录结构放置。 - 直接用浏览器打开
index.html文件即可看到效果。
功能说明
- 实时模拟:系统会每5秒自动添加一条新的模拟舆情,并更新列表和统计数据,模拟实时监控的效果。
- 数据可视化:使用 Chart.js 库绘制了情感分析的饼图,直观展示舆情分布。
- 交互式筛选:可以通过输入关键词和选择情感类型来筛选舆情列表。
- 响应式设计:使用 CSS Grid 和 Flexbox,页面在不同屏幕尺寸下都能良好显示。
- 语义化 HTML5:使用了
<header>,<main>,<section>,<aside>,<footer>等标签,使代码结构清晰。
未来可以扩展的方向
- 后端集成:将
data.js中的模拟数据替换为通过fetch或axios从真实的后端 API 获取数据。 - 更复杂的图表:增加时间序列图表,展示舆情量随时间的变化趋势。
- 详情页:为“查看详情”按钮添加功能,点击后可以跳转到或弹出一个展示该舆情详细信息的页面。
- 数据导出:增加将当前筛选后的数据导出为 Excel 或 CSV 文件的功能。
- 高级筛选:增加按时间范围、来源网站等更复杂的筛选条件。
- 告警系统:当负面舆情数量超过某个阈值时,页面可以高亮显示或发送通知。
这个项目是一个功能完整、设计现代的舆情监控系统前端原型,很好地展示了 HTML5、CSS3 和 JavaScript 的综合运用能力。

(图片来源网络,侵删)
