Bilingual support, with full system support for Chinese and English switching.

This commit is contained in:
阿彭Baileys
2025-03-09 19:09:00 +08:00
committed by GitHub
parent f81a71e970
commit b4f14ae3e7
21 changed files with 5725 additions and 5364 deletions
+15 -3
View File
@@ -142,15 +142,27 @@ def not_found_error(error):
@app.errorhandler(500) @app.errorhandler(500)
def internal_error(error): def internal_error(error):
return render_template('500.html'), 500 return render_template('error.html',
error_code=500,
error_title='服务器错误',
error_message='服务器遇到了一个问题,请稍后再试。',
error_i18n_key='serverError'), 500
@app.errorhandler(403) @app.errorhandler(403)
def forbidden_error(error): def forbidden_error(error):
return render_template('403.html'), 403 return render_template('error.html',
error_code=403,
error_title='禁止访问',
error_message='您没有权限访问此页面。',
error_i18n_key='forbidden'), 403
@app.errorhandler(400) @app.errorhandler(400)
def bad_request_error(error): def bad_request_error(error):
return render_template('400.html'), 400 return render_template('error.html',
error_code=400,
error_title='错误请求',
error_message='服务器无法理解您的请求。',
error_i18n_key='badRequest'), 400
# 数据库配置 # 数据库配置
DB_CONFIG = { DB_CONFIG = {
+239
View File
@@ -0,0 +1,239 @@
// 多语言支持文件
const translations = {
'zh': {
// 导航菜单
'home': '首页',
'hotWord': '热词统计',
'tableData': '微博舆情统计',
'articleChar': '文章分析',
'ipChar': 'IP分析',
'commentChar': '评论分析',
'yuqingChar': '舆情分析',
'yuqingpredict': '舆情预测',
'articleCloud': '文章内容词云图',
'dataVisualization': '数据可视化',
'weiboSystem': '微博舆情分析系统',
'wordCloud': '词云图',
// 首页
'articleCount': '文章个数',
'articleCrawlRule': '文章爬取规则',
'nextCrawlTime': '下次爬取时间',
'articlePublishTimeCount': '文章发布时间个数',
'commentLikeCountTopFore': '评论点赞量 Top Fore',
'viewAll': '查看全部',
'articleTypeRatio': '文章类型占比',
'commentUserWordCloud': '评论用户名词云图',
'commentUserTimeRatio': '评论用户时间占比',
// 热词页面
'hotWordStatistics': '热词统计页',
'hotWordCloud': '热词词云图',
'hotWordRanking': '热词查询表格',
'wordFrequency': '词频',
'hotWordSelection': '热词选择',
'hotWordName': '热词名称',
'occurrenceCount': '出现次数',
'hotWordSentiment': '热词情感',
'hotWordYearTrend': '热词年份变化趋势',
'queryCommentsByHotWord': '根据选择的热词从而查询出评论数据',
'hotWordTimeDistribution': '热词出现时间分布个数',
// 舆情分析页面
'hotWordSentimentTrendBar': '热词情感趋势柱状图',
'hotWordSentimentTrendTree': '热词情感趋势树形图',
'articleCommentSentimentTrendPie': '文章内容与评论内容舆情趋势饼状图',
// 舆情预测页面
'topicStatisticsPage': '话题统计页',
// 文章分析页面
'articleCharPage': '文章分析页',
'typeSelection': '类型选择',
'articleLikeAnalysis': '文章点赞量分析 👍',
'articleCommentAnalysis': '文章评论量分析 🔥',
'articleForwardAnalysis': '文章转发量分析 🥇',
'likeRangeStatistics': '点赞区间统计',
'rangeCount': '区间个数',
// 评论分析页面
'commentLikeRangeChart': '评论点赞次数区间图',
'commentUserGenderRatio': '评论用户性别占比',
'userCommentWordCloud': '用户评论词云图',
// IP分析页面
'articleIpLocationAnalysis': '文章IP位置分析图',
'commentIpLocationAnalysis': '评论IP位置分析图',
// 评论相关
'commentUser': '评论用户',
'commentGender': '评论性别',
'commentAddress': '评论地址',
'commentContent': '评论内容',
'likeCount': '点赞量',
// 微博舆情统计页面
'weiboArticleStatTable': '微博文章统计表格 - 舆情 情感分类',
'sentimentClassification': '情感分类',
'articleId': '文章ID',
'articleIp': '文章IP',
'articleTitle': '文章标题',
'articleLike': '点赞量',
'articleForward': '转发量',
'articleComment': '评论量',
'articleType': '类型',
'articleContent': '内容',
'articleTime': '发布时间',
// 通用
'switchToEnglish': '切换到英文',
'switchToChinese': '切换到中文',
'semester': '网安小学期',
// 错误页面
'pageNotFound': '页面未找到',
'backToHome': '返回首页',
'serverError': '服务器错误',
'forbidden': '禁止访问',
'badRequest': '错误请求'
},
'en': {
// Navigation menu
'home': 'Home',
'hotWord': 'Hot Words',
'tableData': 'Weibo Public Opinion Stats',
'articleChar': 'Article Analysis',
'ipChar': 'IP Analysis',
'commentChar': 'Comment Analysis',
'yuqingChar': 'Public Opinion Analysis',
'yuqingpredict': 'Opinion Prediction',
'articleCloud': 'Article Content Word Cloud',
'dataVisualization': 'Data Visualization',
'weiboSystem': 'Weibo Public Opinion Analysis System',
'wordCloud': 'Word Cloud',
// Home page
'articleCount': 'Article Count',
'articleCrawlRule': 'Article Crawl Rule',
'nextCrawlTime': 'Next Crawl Time',
'articlePublishTimeCount': 'Article Publish Time Count',
'commentLikeCountTopFore': 'Comment Like Count Top Four',
'viewAll': 'View All',
'articleTypeRatio': 'Article Type Ratio',
'commentUserWordCloud': 'Comment User Word Cloud',
'commentUserTimeRatio': 'Comment User Time Ratio',
// Hot word page
'hotWordStatistics': 'Hot Word Statistics',
'hotWordCloud': 'Hot Word Cloud',
'hotWordRanking': 'Hot Word Ranking',
'wordFrequency': 'Word Frequency',
'hotWordSelection': 'Hot Word Selection',
'hotWordName': 'Hot Word Name',
'occurrenceCount': 'Occurrence Count',
'hotWordSentiment': 'Hot Word Sentiment',
'hotWordYearTrend': 'Hot Word Year Trend',
'queryCommentsByHotWord': 'Query comments based on selected hot word',
'hotWordTimeDistribution': 'Hot Word Time Distribution Count',
// Public opinion analysis page
'hotWordSentimentTrendBar': 'Hot Word Sentiment Trend Bar Chart',
'hotWordSentimentTrendTree': 'Hot Word Sentiment Trend Tree Chart',
'articleCommentSentimentTrendPie': 'Article and Comment Sentiment Trend Pie Chart',
// Opinion prediction page
'topicStatisticsPage': 'Topic Statistics Page',
// Article analysis page
'articleCharPage': 'Article Analysis Page',
'typeSelection': 'Type Selection',
'articleLikeAnalysis': 'Article Like Analysis 👍',
'articleCommentAnalysis': 'Article Comment Analysis 🔥',
'articleForwardAnalysis': 'Article Forward Analysis 🥇',
'likeRangeStatistics': 'Like Range Statistics',
'rangeCount': 'Range Count',
// Comment analysis page
'commentLikeRangeChart': 'Comment Like Range Chart',
'commentUserGenderRatio': 'Comment User Gender Ratio',
'userCommentWordCloud': 'User Comment Word Cloud',
// IP analysis page
'articleIpLocationAnalysis': 'Article IP Location Analysis',
'commentIpLocationAnalysis': 'Comment IP Location Analysis',
// Comment related
'commentUser': 'Comment User',
'commentGender': 'Gender',
'commentAddress': 'Address',
'commentContent': 'Content',
'likeCount': 'Likes',
// Weibo public opinion stats page
'weiboArticleStatTable': 'Weibo Article Statistics Table - Sentiment Classification',
'sentimentClassification': 'Sentiment Classification',
'articleId': 'Article ID',
'articleIp': 'Article IP',
'articleTitle': 'Article Title',
'articleLike': 'Likes',
'articleForward': 'Forwards',
'articleComment': 'Comments',
'articleType': 'Type',
'articleContent': 'Content',
'articleTime': 'Publish Time',
// Common
'switchToEnglish': 'Switch to English',
'switchToChinese': 'Switch to Chinese',
'semester': 'Network Security Semester',
// Error pages
'pageNotFound': 'Page Not Found',
'backToHome': 'Back to Home',
'serverError': 'Server Error',
'forbidden': 'Forbidden',
'badRequest': 'Bad Request'
}
};
// 获取当前语言
function getCurrentLanguage() {
return localStorage.getItem('language') || 'zh';
}
// 设置语言
function setLanguage(lang) {
localStorage.setItem('language', lang);
location.reload();
}
// 翻译函数
function t(key) {
const lang = getCurrentLanguage();
return translations[lang][key] || key;
}
// 页面加载时应用翻译
document.addEventListener('DOMContentLoaded', function() {
// 应用当前语言
applyTranslations();
// 添加语言切换按钮事件
const langSwitcher = document.getElementById('language-switcher');
if (langSwitcher) {
langSwitcher.addEventListener('click', function() {
const currentLang = getCurrentLanguage();
const newLang = currentLang === 'zh' ? 'en' : 'zh';
setLanguage(newLang);
});
}
});
// 应用翻译到页面元素
function applyTranslations() {
const elements = document.querySelectorAll('[data-i18n]');
elements.forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = t(key);
});
}
+57 -28
View File
@@ -1,29 +1,58 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404页面</title> <title>404 - 页面未找到</title>
<link rel="stylesheet" href="/static/css/backend-plugin.min.css"> <link rel="stylesheet" href="/static/css/backend.css">
<link rel="stylesheet" href="/static/css/backend.css"> </head> <!-- 添加语言支持脚本 -->
<body class=" "> <script src="/static/js/i18n.js"></script>
<style>
body {
<div class="wrapper"> display: flex;
<div class="container"> justify-content: center;
<div class="row no-gutters height-self-center"> align-items: center;
<div class="col-sm-12 text-center align-self-center"> height: 100vh;
<div class="iq-error position-relative"> margin: 0;
<img src="/static/picture/Datum_404.png" class="img-fluid iq-error-img iq-error-img-dark mx-auto" alt=""> background-color: #f8f9fa;
<img src="/static/picture/Datum_404.png" class="img-fluid iq-error-img mb-0" alt=""> }
<h2 class="mb-0">噢!该页面没有找到..</h2> .error-container {
<p>本次请求没有任何反应.</p> text-align: center;
<a class="btn btn-primary d-inline-flex align-items-center mt-3" href="/user/login"><i class="ri-home-4-line mr-2"></i>回到登录页</a> padding: 2rem;
</div> }
</div> .error-code {
</div> font-size: 6rem;
</div> font-weight: bold;
</div> color: #dc3545;
/ }
</body> .error-message {
font-size: 1.5rem;
margin: 1rem 0;
}
.back-link {
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 0.25rem;
}
</style>
</head>
<body>
<div class="error-container">
<div class="error-code">404</div>
<div class="error-message" data-i18n="pageNotFound">页面未找到</div>
<p>您请求的页面不存在或已被移除。</p>
<a href="/" class="back-link" data-i18n="backToHome">返回首页</a>
</div>
<!-- 初始化语言支持 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
applyTranslations();
});
</script>
</body>
</html> </html>
+57 -32
View File
@@ -1,33 +1,58 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>错误页面</title> <title>{{ error_code }} - {{ error_title }}</title>
<link rel="stylesheet" href="/static/css/backend.css">
<link rel="stylesheet" href="/static/css/backend-plugin.min.css"> <!-- 添加语言支持脚本 -->
<link rel="stylesheet" href="/static/css/backend.css"> </head> <script src="/static/js/i18n.js"></script>
<body class=" "> <style>
body {
display: flex;
<div class="wrapper"> justify-content: center;
<div class="mt-5 iq-maintenance"> align-items: center;
<div class="container-fluid p-0"> height: 100vh;
<div class="row no-gutters"> margin: 0;
<div class="col-sm-12 text-center"> background-color: #f8f9fa;
<div class="iq-maintenance"> }
<img src="/static/picture/maintenance.png" class="img-fluid" alt=""> .error-container {
<h3 class="mt-4 mb-2">{{ errorMsg }}</h3> text-align: center;
<p class="mb-2">请回去再次检查问题并且修改问题.</p> padding: 2rem;
<p><a href="/user/login" class="btn btn-primary">回到登录页</a></p> }
</div> .error-code {
</div> font-size: 6rem;
</div> font-weight: bold;
</div> color: #dc3545;
</div> }
</div> .error-message {
font-size: 1.5rem;
<!-- Backend Bundle JavaScript --> margin: 1rem 0;
}
</body> .back-link {
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 0.25rem;
}
</style>
</head>
<body>
<div class="error-container">
<div class="error-code">{{ error_code }}</div>
<div class="error-message" data-i18n="{{ error_i18n_key }}">{{ error_title }}</div>
<p>{{ error_message }}</p>
<a href="/" class="back-link" data-i18n="backToHome">返回首页</a>
</div>
<!-- 初始化语言支持 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
applyTranslations();
});
</script>
</body>
</html> </html>
+380 -380
View File
@@ -1,381 +1,381 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>爬虫控制面板</title> <title>爬虫控制面板</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet"> <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
<style> <style>
.topic-item { .topic-item {
margin: 5px; margin: 5px;
padding: 8px 15px; padding: 8px 15px;
border-radius: 20px; border-radius: 20px;
background-color: #f8f9fa; background-color: #f8f9fa;
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
} }
.topic-item.selected { .topic-item.selected {
background-color: #0d6efd; background-color: #0d6efd;
color: white; color: white;
} }
.custom-topic-input { .custom-topic-input {
margin: 10px 0; margin: 10px 0;
} }
.parameter-section { .parameter-section {
margin: 20px 0; margin: 20px 0;
padding: 20px; padding: 20px;
border-radius: 10px; border-radius: 10px;
background-color: #f8f9fa; background-color: #f8f9fa;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container mt-5"> <div class="container mt-5">
<h2 class="mb-4">爬虫控制面板</h2> <h2 class="mb-4">爬虫控制面板</h2>
<!-- 话题选择区域 --> <!-- 话题选择区域 -->
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0">选择话题类型</h5> <h5 class="mb-0">选择话题类型</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="predefinedTopics" class="mb-3"> <div id="predefinedTopics" class="mb-3">
<!-- 预定义话题将通过JavaScript动态加载 --> <!-- 预定义话题将通过JavaScript动态加载 -->
</div> </div>
<div class="custom-topic-input"> <div class="custom-topic-input">
<h6>添加自定义话题</h6> <h6>添加自定义话题</h6>
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" id="customTopic" placeholder="输入自定义话题"> <input type="text" class="form-control" id="customTopic" placeholder="输入自定义话题">
<button class="btn btn-primary" onclick="addCustomTopic()"> <button class="btn btn-primary" onclick="addCustomTopic()">
<i class="fas fa-plus"></i> 添加 <i class="fas fa-plus"></i> 添加
</button> </button>
</div> </div>
</div> </div>
<div id="selectedTopics" class="mt-3"> <div id="selectedTopics" class="mt-3">
<h6>已选择的话题:</h6> <h6>已选择的话题:</h6>
<div id="selectedTopicsList" class="mt-2"> <div id="selectedTopicsList" class="mt-2">
<!-- 已选择的话题将在这里显示 --> <!-- 已选择的话题将在这里显示 -->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 爬虫参数配置 --> <!-- 爬虫参数配置 -->
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0">爬虫参数配置</h5> <h5 class="mb-0">爬虫参数配置</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="mb-3"> <div class="mb-3">
<label for="crawlDepth" class="form-label">爬取深度</label> <label for="crawlDepth" class="form-label">爬取深度</label>
<input type="number" class="form-control" id="crawlDepth" value="3" min="1" max="10"> <input type="number" class="form-control" id="crawlDepth" value="3" min="1" max="10">
<small class="text-muted">每个话题爬取的页数(1-10</small> <small class="text-muted">每个话题爬取的页数(1-10</small>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="mb-3"> <div class="mb-3">
<label for="interval" class="form-label">爬取间隔(秒)</label> <label for="interval" class="form-label">爬取间隔(秒)</label>
<input type="number" class="form-control" id="interval" value="5" min="1"> <input type="number" class="form-control" id="interval" value="5" min="1">
<small class="text-muted">每次请求之间的间隔时间</small> <small class="text-muted">每次请求之间的间隔时间</small>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="mb-3"> <div class="mb-3">
<label for="maxRetries" class="form-label">最大重试次数</label> <label for="maxRetries" class="form-label">最大重试次数</label>
<input type="number" class="form-control" id="maxRetries" value="3" min="1"> <input type="number" class="form-control" id="maxRetries" value="3" min="1">
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="mb-3"> <div class="mb-3">
<label for="timeout" class="form-label">请求超时时间(秒)</label> <label for="timeout" class="form-label">请求超时时间(秒)</label>
<input type="number" class="form-control" id="timeout" value="30" min="1"> <input type="number" class="form-control" id="timeout" value="30" min="1">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- AI配置助手 --> <!-- AI配置助手 -->
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0"> <h5 class="mb-0">
<i class="fas fa-robot"></i> AI配置助手 <i class="fas fa-robot"></i> AI配置助手
</h5> </h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="mb-3"> <div class="mb-3">
<label for="aiPrompt" class="form-label">用自然语言描述您的爬虫需求</label> <label for="aiPrompt" class="form-label">用自然语言描述您的爬虫需求</label>
<textarea class="form-control" id="aiPrompt" rows="3" <textarea class="form-control" id="aiPrompt" rows="3"
placeholder="例如:我想爬取最近一周关于人工智能的热门微博,重点关注转发量超过1000的内容,每个话题爬取前5页内容。"></textarea> placeholder="例如:我想爬取最近一周关于人工智能的热门微博,重点关注转发量超过1000的内容,每个话题爬取前5页内容。"></textarea>
</div> </div>
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<button class="btn btn-primary" onclick="generateConfig()"> <button class="btn btn-primary" onclick="generateConfig()">
<i class="fas fa-magic"></i> 生成配置 <i class="fas fa-magic"></i> 生成配置
</button> </button>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="autoApply" checked> <input class="form-check-input" type="checkbox" id="autoApply" checked>
<label class="form-check-label" for="autoApply"> <label class="form-check-label" for="autoApply">
自动应用生成的配置 自动应用生成的配置
</label> </label>
</div> </div>
</div> </div>
<div id="aiResponse" class="mt-3" style="display: none;"> <div id="aiResponse" class="mt-3" style="display: none;">
<div class="alert alert-info"> <div class="alert alert-info">
<h6 class="alert-heading">AI助手建议:</h6> <h6 class="alert-heading">AI助手建议:</h6>
<p id="aiSuggestion" class="mb-0"></p> <p id="aiSuggestion" class="mb-0"></p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<div class="d-flex justify-content-between mb-5"> <div class="d-flex justify-content-between mb-5">
<button class="btn btn-primary" onclick="startCrawling()"> <button class="btn btn-primary" onclick="startCrawling()">
<i class="fas fa-play"></i> 开始爬取 <i class="fas fa-play"></i> 开始爬取
</button> </button>
<button class="btn btn-secondary" onclick="saveConfig()"> <button class="btn btn-secondary" onclick="saveConfig()">
<i class="fas fa-save"></i> 保存配置 <i class="fas fa-save"></i> 保存配置
</button> </button>
</div> </div>
<!-- 爬虫状态和日志 --> <!-- 爬虫状态和日志 -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0">爬虫状态</h5> <h5 class="mb-0">爬虫状态</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="progress mb-3"> <div class="progress mb-3">
<div id="crawlProgress" class="progress-bar" role="progressbar" style="width: 0%"></div> <div id="crawlProgress" class="progress-bar" role="progressbar" style="width: 0%"></div>
</div> </div>
<div class="border p-3 bg-light" style="height: 200px; overflow-y: auto;"> <div class="border p-3 bg-light" style="height: 200px; overflow-y: auto;">
<pre id="crawlLog" class="mb-0"></pre> <pre id="crawlLog" class="mb-0"></pre>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/js/bootstrap.bundle.min.js"></script>
<script> <script>
// 预定义话题列表 // 预定义话题列表
const predefinedTopics = [ const predefinedTopics = [
'热门', '社会', '科技', '娱乐', '体育', '财经', '热门', '社会', '科技', '娱乐', '体育', '财经',
'教育', '健康', '军事', '文化', '汽车', '美食' '教育', '健康', '军事', '文化', '汽车', '美食'
]; ];
// 已选择的话题 // 已选择的话题
let selectedTopics = new Set(); let selectedTopics = new Set();
// 初始化页面 // 初始化页面
window.onload = function() { window.onload = function() {
loadPredefinedTopics(); loadPredefinedTopics();
}; };
// 加载预定义话题 // 加载预定义话题
function loadPredefinedTopics() { function loadPredefinedTopics() {
const topicsDiv = document.getElementById('predefinedTopics'); const topicsDiv = document.getElementById('predefinedTopics');
predefinedTopics.forEach(topic => { predefinedTopics.forEach(topic => {
const topicElement = document.createElement('span'); const topicElement = document.createElement('span');
topicElement.className = 'topic-item'; topicElement.className = 'topic-item';
topicElement.textContent = topic; topicElement.textContent = topic;
topicElement.onclick = () => toggleTopic(topic, topicElement); topicElement.onclick = () => toggleTopic(topic, topicElement);
topicsDiv.appendChild(topicElement); topicsDiv.appendChild(topicElement);
}); });
} }
// 切换话题选择状态 // 切换话题选择状态
function toggleTopic(topic, element) { function toggleTopic(topic, element) {
if (selectedTopics.has(topic)) { if (selectedTopics.has(topic)) {
selectedTopics.delete(topic); selectedTopics.delete(topic);
element.classList.remove('selected'); element.classList.remove('selected');
} else { } else {
selectedTopics.add(topic); selectedTopics.add(topic);
element.classList.add('selected'); element.classList.add('selected');
} }
updateSelectedTopicsList(); updateSelectedTopicsList();
} }
// 添加自定义话题 // 添加自定义话题
function addCustomTopic() { function addCustomTopic() {
const input = document.getElementById('customTopic'); const input = document.getElementById('customTopic');
const topic = input.value.trim(); const topic = input.value.trim();
if (topic) { if (topic) {
selectedTopics.add(topic); selectedTopics.add(topic);
input.value = ''; input.value = '';
updateSelectedTopicsList(); updateSelectedTopicsList();
} }
} }
// 更新已选择的话题列表 // 更新已选择的话题列表
function updateSelectedTopicsList() { function updateSelectedTopicsList() {
const listDiv = document.getElementById('selectedTopicsList'); const listDiv = document.getElementById('selectedTopicsList');
listDiv.innerHTML = ''; listDiv.innerHTML = '';
selectedTopics.forEach(topic => { selectedTopics.forEach(topic => {
const topicElement = document.createElement('span'); const topicElement = document.createElement('span');
topicElement.className = 'topic-item selected'; topicElement.className = 'topic-item selected';
topicElement.textContent = topic; topicElement.textContent = topic;
topicElement.onclick = () => { topicElement.onclick = () => {
selectedTopics.delete(topic); selectedTopics.delete(topic);
updateSelectedTopicsList(); updateSelectedTopicsList();
}; };
listDiv.appendChild(topicElement); listDiv.appendChild(topicElement);
}); });
} }
// 开始爬取 // 开始爬取
function startCrawling() { function startCrawling() {
if (selectedTopics.size === 0) { if (selectedTopics.size === 0) {
alert('请至少选择一个话题!'); alert('请至少选择一个话题!');
return; return;
} }
const config = { const config = {
topics: Array.from(selectedTopics), topics: Array.from(selectedTopics),
parameters: { parameters: {
crawlDepth: parseInt(document.getElementById('crawlDepth').value), crawlDepth: parseInt(document.getElementById('crawlDepth').value),
interval: parseInt(document.getElementById('interval').value), interval: parseInt(document.getElementById('interval').value),
maxRetries: parseInt(document.getElementById('maxRetries').value), maxRetries: parseInt(document.getElementById('maxRetries').value),
timeout: parseInt(document.getElementById('timeout').value) timeout: parseInt(document.getElementById('timeout').value)
} }
}; };
// 发送爬虫配置到后端 // 发送爬虫配置到后端
fetch('/api/spider/start', { fetch('/api/spider/start', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify(config) body: JSON.stringify(config)
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
updateCrawlLog('爬虫任务已启动...'); updateCrawlLog('爬虫任务已启动...');
} else { } else {
updateCrawlLog('启动失败:' + data.message); updateCrawlLog('启动失败:' + data.message);
} }
}) })
.catch(error => { .catch(error => {
updateCrawlLog('错误:' + error.message); updateCrawlLog('错误:' + error.message);
}); });
} }
// 保存配置 // 保存配置
function saveConfig() { function saveConfig() {
const config = { const config = {
topics: Array.from(selectedTopics), topics: Array.from(selectedTopics),
parameters: { parameters: {
crawlDepth: parseInt(document.getElementById('crawlDepth').value), crawlDepth: parseInt(document.getElementById('crawlDepth').value),
interval: parseInt(document.getElementById('interval').value), interval: parseInt(document.getElementById('interval').value),
maxRetries: parseInt(document.getElementById('maxRetries').value), maxRetries: parseInt(document.getElementById('maxRetries').value),
timeout: parseInt(document.getElementById('timeout').value) timeout: parseInt(document.getElementById('timeout').value)
} }
}; };
fetch('/api/spider/save-config', { fetch('/api/spider/save-config', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify(config) body: JSON.stringify(config)
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
alert('配置已保存!'); alert('配置已保存!');
} else { } else {
alert('保存失败:' + data.message); alert('保存失败:' + data.message);
} }
}) })
.catch(error => { .catch(error => {
alert('保存出错:' + error.message); alert('保存出错:' + error.message);
}); });
} }
// 更新爬虫日志 // 更新爬虫日志
function updateCrawlLog(message) { function updateCrawlLog(message) {
const log = document.getElementById('crawlLog'); const log = document.getElementById('crawlLog');
const timestamp = new Date().toLocaleTimeString(); const timestamp = new Date().toLocaleTimeString();
log.innerHTML += `[${timestamp}] ${message}\n`; log.innerHTML += `[${timestamp}] ${message}\n`;
log.scrollTop = log.scrollHeight; log.scrollTop = log.scrollHeight;
} }
// WebSocket连接用于实时更新爬虫状态 // WebSocket连接用于实时更新爬虫状态
const ws = new WebSocket(`ws://${window.location.host}/ws/spider-status`); const ws = new WebSocket(`ws://${window.location.host}/ws/spider-status`);
ws.onmessage = function(event) { ws.onmessage = function(event) {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.type === 'progress') { if (data.type === 'progress') {
document.getElementById('crawlProgress').style.width = data.value + '%'; document.getElementById('crawlProgress').style.width = data.value + '%';
} else if (data.type === 'log') { } else if (data.type === 'log') {
updateCrawlLog(data.message); updateCrawlLog(data.message);
} }
}; };
// AI配置生成 // AI配置生成
async function generateConfig() { async function generateConfig() {
const prompt = document.getElementById('aiPrompt').value.trim(); const prompt = document.getElementById('aiPrompt').value.trim();
if (!prompt) { if (!prompt) {
alert('请输入您的爬虫需求描述!'); alert('请输入您的爬虫需求描述!');
return; return;
} }
const aiResponse = document.getElementById('aiResponse'); const aiResponse = document.getElementById('aiResponse');
const aiSuggestion = document.getElementById('aiSuggestion'); const aiSuggestion = document.getElementById('aiSuggestion');
try { try {
const response = await fetch('/api/spider/ai-config', { const response = await fetch('/api/spider/ai-config', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ prompt }) body: JSON.stringify({ prompt })
}); });
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
// 显示AI建议 // 显示AI建议
aiSuggestion.textContent = data.suggestion; aiSuggestion.textContent = data.suggestion;
aiResponse.style.display = 'block'; aiResponse.style.display = 'block';
// 如果选择自动应用配置 // 如果选择自动应用配置
if (document.getElementById('autoApply').checked) { if (document.getElementById('autoApply').checked) {
// 清除现有选择 // 清除现有选择
selectedTopics.clear(); selectedTopics.clear();
// 应用新的话题 // 应用新的话题
data.config.topics.forEach(topic => { data.config.topics.forEach(topic => {
selectedTopics.add(topic); selectedTopics.add(topic);
}); });
// 更新参数 // 更新参数
document.getElementById('crawlDepth').value = data.config.parameters.crawlDepth; document.getElementById('crawlDepth').value = data.config.parameters.crawlDepth;
document.getElementById('interval').value = data.config.parameters.interval; document.getElementById('interval').value = data.config.parameters.interval;
document.getElementById('maxRetries').value = data.config.parameters.maxRetries; document.getElementById('maxRetries').value = data.config.parameters.maxRetries;
document.getElementById('timeout').value = data.config.parameters.timeout; document.getElementById('timeout').value = data.config.parameters.timeout;
// 更新UI // 更新UI
updateSelectedTopicsList(); updateSelectedTopicsList();
// 添加提示 // 添加提示
updateCrawlLog('AI配置已自动应用'); updateCrawlLog('AI配置已自动应用');
} }
} else { } else {
throw new Error(data.message); throw new Error(data.message);
} }
} catch (error) { } catch (error) {
aiSuggestion.textContent = '生成配置时出错:' + error.message; aiSuggestion.textContent = '生成配置时出错:' + error.message;
aiResponse.style.display = 'block'; aiResponse.style.display = 'block';
} }
} }
</script> </script>
</body> </body>
</html> </html>
Binary file not shown.
+548 -548
View File
File diff suppressed because it is too large Load Diff
+344 -344
View File
@@ -1,345 +1,345 @@
{% extends 'base_page.html' %} {% extends 'base_page.html' %}
{% block title %} {% block title %}
文章分析 <span data-i18n="articleChar">文章分析</span>
{% endblock %} {% endblock %}
{% block nav %} {% block nav %}
<nav class="iq-sidebar-menu"> <nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu"> <ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span> <span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/home" class="svg-icon"> <a href="/page/home" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">首页</span> <span class="ml-2" data-i18n="home">首页</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon "> <a href="/page/hotWord" class="svg-icon ">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">热词统计</span> <span class="ml-2">热词统计</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon"> <a href="/page/tableData" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">微博舆情统计</span> <span class="ml-2">微博舆情统计</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span> <span class="text-uppercase small font-weight-bold">数据可视化</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon"> <a href="/page/articleChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">文章分析</span> <span class="ml-2">文章分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon"> <a href="/page/ipChar" class="svg-icon">
<i class=""> <i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor"> <svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path> <path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line> <line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg> </svg>
</i> </i>
<span class="ml-2">IP分析</span> <span class="ml-2">IP分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon"> <a href="/page/commentChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg> </svg>
</i><span class="ml-2">评论分析</span> </i><span class="ml-2">评论分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon"> <a href="/page/yuqingChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情分析</span> <span class="ml-2">舆情分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon"> <a href="/page/yuqingpredict" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情预测</span> <span class="ml-2">舆情预测</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2"> <li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span> <span class="text-uppercase small font-weight-bold">词云图</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon"> <a href="/page/articleCloud" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg> </svg>
</i><span class="ml-2">文章内容词云图</span> </i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12 mb-4 mt-1"> <div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center"> <div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">文章分析页</h4> <h4 class="font-weight-bold" data-i18n="articleCharPage">文章分析页</h4>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card card-block card-stretch card-height"> <div class="card card-block card-stretch card-height">
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label>类型选择</label> <label data-i18n="typeSelection">类型选择</label>
<select onchange="typeChange(event)" class="form-control mb-3"> <select onchange="typeChange(event)" class="form-control mb-3">
{% for i in typeList %} {% for i in typeList %}
{% if defaultType == i %} {% if defaultType == i %}
<option selected value="{{ i }}">{{ i }}</option> <option selected value="{{ i }}">{{ i }}</option>
{% else %} {% else %}
<option value="{{ i }}">{{ i }}</option> <option value="{{ i }}">{{ i }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select> </select>
<script> <script>
function typeChange(e){ function typeChange(e){
window.location.href = 'http://127.0.0.1:5000/page/articleChar?type=' + e.target.value window.location.href = 'http://127.0.0.1:5000/page/articleChar?type=' + e.target.value
} }
</script> </script>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">文章点赞量分析 👍</h4> <h4 class="card-title mb-0" data-i18n="articleLikeAnalysis">文章点赞量分析 👍</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div> <div id="main" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">文章评论量分析 🔥</h4> <h4 class="card-title mb-0" data-i18n="articleCommentAnalysis">文章评论量分析 🔥</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div> <div id="mainTwo" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">文章转发量分析 🥇</h4> <h4 class="card-title mb-0" data-i18n="articleForwardAnalysis">文章转发量分析 🥇</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div> <div id="mainThree" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block echarts %} {% block echarts %}
<script> <script>
var chartDom = document.getElementById('main'); var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
var colors = ['#66cc99','#ffcc66','#ff6666','#6699cc'] var colors = ['#66cc99','#ffcc66','#ff6666','#6699cc']
option = { option = {
title: { title: {
text: '点赞区间统计' text: t('likeRangeStatistics')
}, },
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis'
}, },
legend: { legend: {
data: ['区间个数'] data: [t('rangeCount')]
}, },
toolbox: { toolbox: {
show: true, show: true,
feature: { feature: {
dataView: { show: true, readOnly: false }, dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] }, magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true }, restore: { show: true },
saveAsImage: { show: true } saveAsImage: { show: true }
} }
}, },
calculable: true, calculable: true,
xAxis: [ xAxis: [
{ {
type: 'category', type: 'category',
// prettier-ignore // prettier-ignore
data:{{ xData |tojson }} data:{{ xData |tojson }}
} }
], ],
yAxis: [ yAxis: [
{ {
type: 'value' type: 'value'
} }
], ],
series: [ series: [
{ {
name: '区间个数', name: '区间个数',
type: 'bar', type: 'bar',
data: {{ yData }}, data: {{ yData }},
itemStyle:{ itemStyle:{
color:function(params){ color:function(params){
return colors[params.dataIndex % colors.length]; return colors[params.dataIndex % colors.length];
} }
}, },
markPoint: { markPoint: {
data: [ data: [
{ type: 'max', name: 'Max' }, { type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' } { type: 'min', name: 'Min' }
] ]
}, },
markLine: { markLine: {
data: [{ type: 'average', name: 'Avg' }] data: [{ type: 'average', name: 'Avg' }]
} }
} }
] ]
}; };
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
<script> <script>
var chartDom = document.getElementById('mainTwo'); var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
toolbox: { toolbox: {
show: true, show: true,
feature: { feature: {
dataView: { show: true, readOnly: false }, dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] }, magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true }, restore: { show: true },
saveAsImage: { show: true } saveAsImage: { show: true }
} }
}, },
title: { title: {
text: '文章评论区间统计' text: '文章评论区间统计'
}, },
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis'
}, },
legend: { legend: {
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: {{ x1Data | tojson }} data: {{ x1Data | tojson }}
}, },
yAxis: { yAxis: {
type: 'value' type: 'value'
}, },
series: [ series: [
{ {
name:"区间个数", name:"区间个数",
data: {{ y1Data }}, data: {{ y1Data }},
type: 'line', type: 'line',
symbol: 'triangle', symbol: 'triangle',
symbolSize: 20, symbolSize: 20,
lineStyle: { lineStyle: {
color: '#5470C6', color: '#5470C6',
width: 4, width: 4,
type: 'dashed' type: 'dashed'
}, },
itemStyle: { itemStyle: {
borderWidth: 3, borderWidth: 3,
borderColor: '#EE6666', borderColor: '#EE6666',
color: 'yellow' color: 'yellow'
} }
} }
] ]
}; };
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
<script> <script>
var chartDom = document.getElementById('mainThree'); var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
title: { title: {
text: '转发量分析区间图' text: '转发量分析区间图'
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'shadow' type: 'shadow'
} }
}, },
legend: {}, legend: {},
grid: { grid: {
left: '3%', left: '3%',
right: '4%', right: '4%',
bottom: '3%', bottom: '3%',
containLabel: true containLabel: true
}, },
xAxis: { xAxis: {
type: 'value', type: 'value',
boundaryGap: [0, 0.01] boundaryGap: [0, 0.01]
}, },
yAxis: { yAxis: {
type: 'category', type: 'category',
data: {{ x2Data | tojson }} data: {{ x2Data | tojson }}
}, },
series: [ series: [
{ {
name: '区间个数', name: '区间个数',
type: 'bar', type: 'bar',
data: {{ y2Data }} data: {{ y2Data }}
} }
] ]
}; };
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
{% endblock %} {% endblock %}
+143 -143
View File
@@ -1,144 +1,144 @@
{% extends 'base_page.html' %} {% extends 'base_page.html' %}
{% block title %} {% block title %}
文章内容词云图 <span data-i18n="articleCloud">文章内容词云图</span>
{% endblock %} {% endblock %}
{% block nav %} {% block nav %}
<nav class="iq-sidebar-menu"> <nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu"> <ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span> <span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/home" class="svg-icon"> <a href="/page/home" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">首页</span> <span class="ml-2" data-i18n="home">首页</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon "> <a href="/page/hotWord" class="svg-icon ">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">热词统计</span> <span class="ml-2" data-i18n="hotWord">热词统计</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon"> <a href="/page/tableData" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">微博舆情统计</span> <span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span> <span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon"> <a href="/page/articleChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">文章分析</span> <span class="ml-2" data-i18n="articleChar">文章分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon"> <a href="/page/ipChar" class="svg-icon">
<i class=""> <i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor"> <svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path> <path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line> <line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg> </svg>
</i> </i>
<span class="ml-2">IP分析</span> <span class="ml-2">IP分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon"> <a href="/page/commentChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg> </svg>
</i><span class="ml-2">评论分析</span> </i><span class="ml-2">评论分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon"> <a href="/page/yuqingChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情分析</span> <span class="ml-2">舆情分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon"> <a href="/page/yuqingpredict" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情预测</span> <span class="ml-2">舆情预测</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2"> <li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span> <span class="text-uppercase small font-weight-bold">词云图</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon"> <a href="/page/articleCloud" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg> </svg>
</i><span class="ml-2">文章内容词云图</span> </i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12 mb-4 mt-1"> <div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center"> <div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">文章分析页</h4> <h4 class="font-weight-bold" data-i18n="articleCharPage">文章分析页</h4>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">文章内容词云图</h4> <h4 class="card-title mb-0" data-i18n="articleCloud">文章内容词云图</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainThree" style="width: 100%;height: 750px;text-align: center"> <div id="mainThree" style="width: 100%;height: 750px;text-align: center">
<img style="width:60%" src="/static/contentCloud.jpg" alt=""> <img style="width:60%" src="/static/contentCloud.jpg" alt="">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block echarts %} {% block echarts %}
<script> <script>
</script> </script>
{% endblock %} {% endblock %}
+504 -477
View File
@@ -1,478 +1,505 @@
<!doctype html>
<!doctype html> <html lang="en">
<html lang="en"> <head>
<head> <meta charset="utf-8">
<meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>{% block title %}首页{% endblock %}</title>
<title>{% block title %}首页{% endblock %}</title>
<link rel="stylesheet" href="/static/css/backend-plugin.min.css">
<link rel="stylesheet" href="/static/css/backend-plugin.min.css"> <link rel="stylesheet" href="/static/css/backend.css">
<link rel="stylesheet" href="/static/css/backend.css"> <meta name="referrer" content="no-referrer" />
<meta name="referrer" content="no-referrer" /> <script src="/static/js/i18n.js"></script>
</head> </head>
<body class=" "> <body class=" ">
<!-- loader Start --> <!-- loader Start -->
<div id="loading"> <div id="loading">
<div id="loading-center"> <div id="loading-center">
</div> </div>
</div> </div>
<!-- loader END --> <!-- loader END -->
<!-- Wrapper Start --> <!-- Wrapper Start -->
<div class="wrapper"> <div class="wrapper">
<div class="iq-sidebar sidebar-default "> <div class="iq-sidebar sidebar-default ">
<div class="iq-sidebar-logo d-flex align-items-end justify-content-between"> <div class="iq-sidebar-logo d-flex align-items-end justify-content-between">
<a href="" class="header-logo"> <a href="" class="header-logo">
<img src="https://lovexl-oss.oss-cn-beijing.aliyuncs.com/bed/202407051027268.png" class="img-fluid rounded-normal light-logo" alt="logo"> <img src="https://lovexl-oss.oss-cn-beijing.aliyuncs.com/bed/202407051027268.png" class="img-fluid rounded-normal light-logo" alt="logo">
<span>微博舆情分析系统</span> <span data-i18n="weiboSystem">微博舆情分析系统</span>
</a> </a>
<div class="side-menu-bt-sidebar-1"> <div class="side-menu-bt-sidebar-1">
<svg xmlns="http://www.w3.org/2000/svg" class="text-light wrapper-menu" width="30" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="text-light wrapper-menu" width="30" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg> </svg>
</div> </div>
</div> </div>
<div class="data-scrollbar" data-scroll="1"> <div class="data-scrollbar" data-scroll="1">
{% block nav %} {% block nav %}
<nav class="iq-sidebar-menu"> <nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu"> <ul id="iq-sidebar-toggle" class="side-menu">
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/home" class="svg-icon"> <a href="/page/home" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">首页</span> <span class="ml-2" data-i18n="home">首页</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon "> <a href="/page/hotWord" class="svg-icon ">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">热词统计</span> <span class="ml-2" data-i18n="hotWord">热词统计</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon"> <a href="/page/tableData" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">微博舆情统计</span> <span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon"> <a href="/page/articleChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">文章分析</span> <span class="ml-2" data-i18n="articleChar">文章分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon"> <a href="/page/ipChar" class="svg-icon">
<i class=""> <i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor"> <svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path> <path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line> <line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg> </svg>
</i> </i>
<span class="ml-2">IP分析</span> <span class="ml-2" data-i18n="ipChar">IP分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon"> <a href="/page/commentChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg> </svg>
</i><span class="ml-2">评论分析</span> </i><span class="ml-2" data-i18n="commentChar">评论分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon"> <a href="/page/yuqingChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情分析</span> <span class="ml-2" data-i18n="yuqingChar">舆情分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon"> <a href="/page/yuqingpredict" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情预测</span> <span class="ml-2" data-i18n="yuqingpredict">舆情预测</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon"> <a href="/page/articleCloud" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg> </svg>
</i><span class="ml-2">文章内容词云图</span> </i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a> </a>
</li> </li>
</ul> <li class="sidebar-layout">
</nav> <a href="javascript:void(0)" id="language-switcher" class="svg-icon">
{% endblock %} <i class="">
<div class="pt-5 pb-5"></div> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
</div> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"></path>
</div> </svg>
<div class="content-page"> </i>
{% block content %} <span class="ml-2" id="language-text">切换语言</span>
<div class="container-fluid"> </a>
<div class="row"> </li>
<div class="col-md-12 mb-4 mt-1"> </ul>
<div class="d-flex flex-wrap justify-content-between align-items-center"> </nav>
<h4 class="font-weight-bold">首页</h4> {% endblock %}
</div> <div class="pt-5 pb-5"></div>
</div> </div>
<div class="col-lg-8 col-md-12"> </div>
<div class="row"> <div class="content-page">
<div class="col-md-4"> {% block content %}
<div class="card"> <div class="container-fluid">
<div class="card-body"> <div class="row">
<div class="d-flex align-items-center"> <div class="col-md-12 mb-4 mt-1">
<div class=""> <div class="d-flex flex-wrap justify-content-between align-items-center">
<p class="mb-2 text-secondary">文章个数</p> <h4 class="font-weight-bold" data-i18n="home">首页</h4>
<div class="d-flex flex-wrap justify-content-start align-items-center"> </div>
<h5 class="mb-0 font-weight-bold">{{ articleLenMax }}个</h5> </div>
</div> <div class="col-lg-8 col-md-12">
</div> <div class="row">
</div> <div class="col-md-4">
</div> <div class="card">
</div> <div class="card-body">
</div> <div class="d-flex align-items-center">
<div class="col-md-4"> <div class="">
<div class="card"> <p class="mb-2 text-secondary" data-i18n="articleCount">文章个数</p>
<div class="card-body"> <div class="d-flex flex-wrap justify-content-start align-items-center">
<div class="d-flex align-items-center"> <h5 class="mb-0 font-weight-bold">{{ articleLenMax }}个</h5>
<div class=""> </div>
<p class="mb-2 text-secondary">文章爬取规则</p> </div>
<div class="d-flex flex-wrap justify-content-start align-items-center"> </div>
<h5 class="mb-0 font-weight-bold">每 5 小时更新一次爬取内容</h5> </div>
</div> </div>
</div> </div>
</div> <div class="col-md-4">
</div> <div class="card">
</div> <div class="card-body">
</div> <div class="d-flex align-items-center">
<div class="col-md-4"> <div class="">
<div class="card"> <p class="mb-2 text-secondary" data-i18n="articleCrawlRule">文章爬取规则</p>
<div class="card-body"> <div class="d-flex flex-wrap justify-content-start align-items-center">
<div class="d-flex align-items-center"> <h5 class="mb-0 font-weight-bold">每 5 小时更新一次爬取内容</h5>
<div class=""> </div>
<p class="mb-2 text-secondary">下次爬取时间</p> </div>
<div class="d-flex flex-wrap justify-content-start align-items-center"> </div>
<h5 class="mb-0 font-weight-bold">7-5-18:00</h5> </div>
</div> </div>
</div> </div>
</div> <div class="col-md-4">
</div> <div class="card">
</div> <div class="card-body">
</div> <div class="d-flex align-items-center">
<div class="col-md-12"> <div class="">
<div class="card"> <p class="mb-2 text-secondary" data-i18n="nextCrawlTime">下次爬取时间</p>
<div class="card-body"> <div class="d-flex flex-wrap justify-content-start align-items-center">
<div class="d-flex justify-content-between align-items-center flex-wrap"> <h5 class="mb-0 font-weight-bold">7-5-18:00</h5>
<h4 class="font-weight-bold">文章发布时间个数</h4> </div>
</div> </div>
<div id="main" style="width:100%;height: 350px" class="custom-chart"></div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="col-md-12">
</div> <div class="card">
<div class="col-lg-4 col-md-8"> <div class="card-body">
<div class="card card-block card-stretch card-height"> <div class="d-flex justify-content-between align-items-center flex-wrap">
<div class="card-header card-header-border d-flex justify-content-between"> <h4 class="font-weight-bold" data-i18n="articlePublishTimeCount">文章发布时间个数</h4>
<div class="header-title"> </div>
<h4 class="card-title">评论点赞量 Top Fore</h4> <div id="main" style="width:100%;height: 350px" class="custom-chart"></div>
</div> </div>
</div> </div>
<div class="card-body-list"> </div>
<ul class="list-style-3 mb-0"> </div>
{% for i in commentsLikeCountTopFore %} </div>
<li class="p-3 list-item d-flex justify-content-start align-items-center"> <div class="col-lg-4 col-md-8">
<div class="avatar"> <div class="card card-block card-stretch card-height">
<p> <div class="card-header card-header-border d-flex justify-content-between">
🧑‍ {{ i[5] }} <div class="header-title">
</p> <h4 class="card-title" data-i18n="commentLikeCountTopFore">评论点赞量 Top Fore</h4>
<p class="mb-0" style="color:#ccc;width:320px;overflow: hidden;text-overflow: ellipsis;white-space:nowrap"> </div>
{{ i[4] }} </div>
</p> <div class="card-body-list">
</div> <ul class="list-style-3 mb-0">
<div class="list-style-action d-flex justify-content-end ml-auto"> {% for i in commentsLikeCountTopFore %}
<h6 class="font-weight-bold text-danger">👍 {{ i[2] }}</h6> <li class="p-3 list-item d-flex justify-content-start align-items-center">
</div> <div class="avatar">
</li> <p>
{% endfor %} 🧑‍ {{ i[5] }}
<div class="d-flex justify-content-end align-items-center border-top-table p-3"> </p>
<a href="/page/tableData" class="btn btn-secondary btn-sm">查看全部</a> <p class="mb-0" style="color:#ccc;width:320px;overflow: hidden;text-overflow: ellipsis;white-space:nowrap">
</div> {{ i[4] }}
</ul> </p>
</div> </div>
</div> <div class="list-style-action d-flex justify-content-end ml-auto">
<h6 class="font-weight-bold text-danger">👍 {{ i[2] }}</h6>
</div> </div>
<div class="col-lg-4 col-md-6"> </li>
<div class="card card-block card-stretch card-height"> {% endfor %}
<div class="card-header d-flex justify-content-between"> <div class="d-flex justify-content-end align-items-center border-top-table p-3">
<div class="header-title"> <a href="/page/tableData" class="btn btn-secondary btn-sm" data-i18n="viewAll">查看全部</a>
<h4 class="card-title">文章类型占比</h4> </div>
</div> </ul>
</div> </div>
<div class="card-body p-0"> </div>
<div id="mainTwo" style="width:100%;height:350px">
</div>
</div> <div class="col-lg-4 col-md-6">
</div> <div class="card card-block card-stretch card-height">
</div> <div class="card-header d-flex justify-content-between">
</div> <div class="header-title">
<div class="col-lg-4 col-md-6"> <h4 class="card-title" data-i18n="articleTypeRatio">文章类型占比</h4>
<div class="card card-block card-stretch card-height"> </div>
<div class="card-header d-flex justify-content-between"> </div>
<div class="header-title"> <div class="card-body p-0">
<h4 class="card-title">评论用户名词云图</h4> <div id="mainTwo" style="width:100%;height:350px">
</div>
</div> </div>
<div class="card-body p-0"> </div>
<div id="mainTwo" style="width:100%;height:350px;text-align: center"> </div>
<img style="width:85%" src="/static/authorNameCloud.jpg" alt=""> </div>
</div> <div class="col-lg-4 col-md-6">
</div> <div class="card card-block card-stretch card-height">
</div> <div class="card-header d-flex justify-content-between">
</div> <div class="header-title">
<div class="col-lg-4 col-md-6"> <h4 class="card-title" data-i18n="commentUserWordCloud">评论用户名词云图</h4>
<div class="card card-block card-stretch card-height"> </div>
<div class="card-header d-flex justify-content-between"> </div>
<div class="header-title"> <div class="card-body p-0">
<h4 class="card-title">评论用户时间占比</h4> <div id="mainTwo" style="width:100%;height:350px;text-align: center">
</div> <img style="width:85%" src="/static/authorNameCloud.jpg" alt="">
</div> </div>
<div class="card-body p-0"> </div>
<div id="mainThree" style="width:100%;height:350px"> </div>
</div>
</div> <div class="col-lg-4 col-md-6">
</div> <div class="card card-block card-stretch card-height">
</div> <div class="card-header d-flex justify-content-between">
</div> <div class="header-title">
</div> <h4 class="card-title" data-i18n="commentUserTimeRatio">评论用户时间占比</h4>
</div> </div>
{% endblock %} </div>
</div> <div class="card-body p-0">
</div> <div id="mainThree" style="width:100%;height:350px">
<!-- Wrapper End-->
<footer class="iq-footer"> </div>
<div class="container-fluid"> </div>
<div class="row"> </div>
<div class="col-lg-6"> </div>
</div> </div>
<div class="col-lg-6 text-right"> </div>
<span class="mr-1">网安小学期 &copy; 2024.<a target="_blank" href="#">郭航江</a></span> {% endblock %}
</div> </div>
</div> </div>
</div> <!-- Wrapper End-->
</footer> <footer class="iq-footer">
<script src="/static/echarts.min.js"></script> <div class="container-fluid">
<script src="/static/china.js"></script> <div class="row">
{% block echarts %} <div class="col-lg-6">
<script> </div>
var chartDom = document.getElementById('main'); <div class="col-lg-6 text-right">
var myCharts = echarts.init(chartDom); <span class="mr-1"><span data-i18n="semester">网安小学期</span> &copy; 2024.<a target="_blank" href="#">郭航江</a></span>
var option; </div>
var xData = {{ xData | tojson }}; </div>
var yData = {{ yData }}; </div>
var xRes = [] </footer>
var yRes = [] <script src="/static/echarts.min.js"></script>
for(var i = 0;i < 8;i++){ <script src="/static/china.js"></script>
xRes.push(xData[i]) <!-- 添加语言切换脚本 -->
yRes.push(yData[i]) <script>
} // 页面加载完成后初始化语言切换功能
option = { document.addEventListener('DOMContentLoaded', function() {
tooltip:{ // 更新语言切换按钮文本
trigger:"axis" updateLanguageSwitcherText();
}, });
legend:{},
toolbox: { // 更新语言切换按钮文本
show: true, function updateLanguageSwitcherText() {
feature: { const currentLang = getCurrentLanguage();
dataZoom: { const langText = document.getElementById('language-text');
yAxisIndex: 'none' if (langText) {
}, langText.textContent = currentLang === 'zh' ? '切换到英文' : 'Switch to Chinese';
dataView: { readOnly: false }, }
magicType: { type: ['line', 'bar'] }, }
restore: {}, </script>
saveAsImage: {} {% block echarts %}
} <script>
}, var chartDom = document.getElementById('main');
xAxis: { var myCharts = echarts.init(chartDom);
type: 'category', var option;
boundaryGap: false, var xData = {{ xData | tojson }};
data: xRes var yData = {{ yData }};
}, var xRes = []
yAxis: { var yRes = []
type: 'value' for(var i = 0;i < 8;i++){
}, xRes.push(xData[i])
series: [ yRes.push(yData[i])
{ }
name:"日期个数", option = {
data: yRes, tooltip:{
type: 'bar', trigger:"axis"
areaStyle: { },
color:"rgba(0,128,255,0.2)" legend:{},
}, toolbox: {
smooth:true, show: true,
lineStyle:{ feature: {
width:5 dataZoom: {
}, yAxisIndex: 'none'
emphasis:{ },
focus:'series' dataView: { readOnly: false },
} magicType: { type: ['line', 'bar'] },
} restore: {},
] saveAsImage: {}
}; }
let count = 8; },
setInterval(()=>{ xAxis: {
if(count >= xData.length) count=0 type: 'category',
xRes.shift() boundaryGap: false,
xRes.push(xData[count]) data: xRes
yRes.shift() },
yRes.push(yData[count]) yAxis: {
count++ type: 'value'
myCharts.setOption({ },
xAxis:[{ series: [
data:xRes {
}], name:"日期个数",
series:[{ data: yRes,
data:yRes type: 'bar',
}] areaStyle: {
}) color:"rgba(0,128,255,0.2)"
},2000) },
smooth:true,
option && myCharts.setOption(option); lineStyle:{
width:5
</script> },
<script> emphasis:{
var chartDom = document.getElementById('mainTwo'); focus:'series'
var myChart = echarts.init(chartDom); }
var option; }
]
option = { };
title: { let count = 8;
text: '各微博类型占比饼状图', setInterval(()=>{
left: 'center' if(count >= xData.length) count=0
}, xRes.shift()
tooltip: { xRes.push(xData[count])
trigger: 'item' yRes.shift()
}, yRes.push(yData[count])
legend: { count++
orient: 'vertical', myCharts.setOption({
left: 'left', xAxis:[{
padding:[10,20,30,20], data:xRes
}, }],
series: [ series:[{
{ data:yRes
name: '博客类型占比', }]
type: 'pie', })
radius: '50%', },2000)
data: {{ typeChart |tojson }},
emphasis: { option && myCharts.setOption(option);
itemStyle: {
shadowBlur: 10, </script>
shadowOffsetX: 0, <script>
shadowColor: 'rgba(0, 0, 0, 0.5)' var chartDom = document.getElementById('mainTwo');
} var myChart = echarts.init(chartDom);
} var option;
}
] option = {
}; title: {
text: '各微博类型占比饼状图',
option && myChart.setOption(option); left: 'center'
},
</script> tooltip: {
<script> trigger: 'item'
var chartDom = document.getElementById('mainThree'); },
var myChart = echarts.init(chartDom); legend: {
var option; orient: 'vertical',
left: 'left',
option = { padding:[10,20,30,20],
tooltip: { },
trigger: 'item' series: [
}, {
legend: { name: '博客类型占比',
top: '5%', type: 'pie',
left:10, radius: '50%',
right:10, data: {{ typeChart |tojson }},
type: 'scroll', emphasis: {
}, itemStyle: {
series: [ shadowBlur: 10,
{ shadowOffsetX: 0,
name: '评论时间发布个数', shadowColor: 'rgba(0, 0, 0, 0.5)'
type: 'pie', }
radius: ['60%', '50%'], }
avoidLabelOverlap: false, }
label: { ]
show: false, };
position: 'center'
}, option && myChart.setOption(option);
emphasis: {
label: { </script>
show: true, <script>
fontSize: 20, var chartDom = document.getElementById('mainThree');
fontWeight: 'bold' var myChart = echarts.init(chartDom);
} var option;
},
labelLine: { option = {
show: false tooltip: {
}, trigger: 'item'
data: {{ createAtChart | tojson }} },
} legend: {
] top: '5%',
}; left:10,
right:10,
option && myChart.setOption(option); type: 'scroll',
},
</script> series: [
{% endblock %} {
<script src="/static/js/backend-bundle.min.js"></script> name: '评论时间发布个数',
<script src="/static/js/customizer.js"></script> type: 'pie',
<script src="/static/js/sidebar.js"></script> radius: ['60%', '50%'],
<script src="/static/js/flex-tree.min.js"></script> avoidLabelOverlap: false,
<script src="/static/js/tree.js"></script> label: {
<script src="/static/js/table-treeview.js"></script> show: false,
<script src="/static/js/sweetalert.js"></script> position: 'center'
<script src="/static/js/vector-map-custom.js"></script> },
<script src="/static/js/chart-custom.js"></script> emphasis: {
<script src="/static/js/01.js"></script> label: {
<script src="/static/js/02.js"></script> show: true,
<script src="/static/js/slider.js"></script> fontSize: 20,
<script src="/static/js/index.js" type="module"></script> fontWeight: 'bold'
<script src="/static/js/app.js"> }
},
</script> labelLine: {
</body> show: false
</html> },
data: {{ createAtChart | tojson }}
<style> }
body { ]
background-color: #add8e6; /* 浅蓝色 */ };
}
option && myChart.setOption(option);
.dark .card {
background-color: #3a4d76; </script>
border-radius: 0px; {% endblock %}
} <script src="/static/js/backend-bundle.min.js"></script>
<script src="/static/js/customizer.js"></script>
<script src="/static/js/sidebar.js"></script>
<script src="/static/js/flex-tree.min.js"></script>
<script src="/static/js/tree.js"></script>
<script src="/static/js/table-treeview.js"></script>
<script src="/static/js/sweetalert.js"></script>
<script src="/static/js/vector-map-custom.js"></script>
<script src="/static/js/chart-custom.js"></script>
<script src="/static/js/01.js"></script>
<script src="/static/js/02.js"></script>
<script src="/static/js/slider.js"></script>
<script src="/static/js/index.js" type="module"></script>
<script src="/static/js/app.js">
</script>
</body>
</html>
<style>
body {
background-color: #add8e6; /* 浅蓝色 */
}
.dark .card {
background-color: #3a4d76;
border-radius: 0px;
}
</style> </style>
+316 -316
View File
@@ -1,317 +1,317 @@
{% extends 'base_page.html' %} {% extends 'base_page.html' %}
{% block title %} {% block title %}
评论分析 <span data-i18n="commentChar">评论分析</span>
{% endblock %} {% endblock %}
{% block nav %} {% block nav %}
<nav class="iq-sidebar-menu"> <nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu"> <ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span> <span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/home" class="svg-icon"> <a href="/page/home" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">首页</span> <span class="ml-2" data-i18n="home">首页</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon "> <a href="/page/hotWord" class="svg-icon ">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">热词统计</span> <span class="ml-2" data-i18n="hotWord">热词统计</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon"> <a href="/page/tableData" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">微博舆情统计</span> <span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span> <span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon"> <a href="/page/articleChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">文章分析</span> <span class="ml-2" data-i18n="articleChar">文章分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon"> <a href="/page/ipChar" class="svg-icon">
<i class=""> <i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor"> <svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path> <path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line> <line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg> </svg>
</i> </i>
<span class="ml-2">IP分析</span> <span class="ml-2">IP分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon"> <a href="/page/commentChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg> </svg>
</i><span class="ml-2">评论分析</span> </i><span class="ml-2">评论分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon"> <a href="/page/yuqingChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情分析</span> <span class="ml-2">舆情分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon"> <a href="/page/yuqingpredict" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情预测</span> <span class="ml-2">舆情预测</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2"> <li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span> <span class="text-uppercase small font-weight-bold">词云图</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon"> <a href="/page/articleCloud" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg> </svg>
</i><span class="ml-2">文章内容词云图</span> </i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12 mb-4 mt-1"> <div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center"> <div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">评论分析</h4> <h4 class="font-weight-bold" data-i18n="commentChar">评论分析</h4>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">评论点赞次数区间图</h4> <h4 class="card-title mb-0" data-i18n="commentLikeRangeChart">评论点赞次数区间图</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div> <div id="main" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">评论用户性别占比</h4> <h4 class="card-title mb-0" data-i18n="commentUserGenderRatio">评论用户性别占比</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div> <div id="mainTwo" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">用户评论词云图</h4> <h4 class="card-title mb-0" data-i18n="userCommentWordCloud">用户评论词云图</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px;text-align: center"> <div id="mainThree" style="width: 100%;height: 450px;text-align: center">
<img style="width:84%" src="/static/commentCloud.jpg" alt=""> <img style="width:84%" src="/static/commentCloud.jpg" alt="">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block echarts %} {% block echarts %}
<script> <script>
var chartDom = document.getElementById('main'); var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
title: { title: {
text: '评论点赞量区间折线图', text: '评论点赞量区间折线图',
left: '1%' left: '1%'
}, },
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis'
}, },
grid: { grid: {
left: '5%', left: '5%',
right: '15%', right: '15%',
bottom: '10%' bottom: '10%'
}, },
legend:{}, legend:{},
xAxis: { xAxis: {
data: {{ xData | tojson }} data: {{ xData | tojson }}
}, },
yAxis: {}, yAxis: {},
toolbox: { toolbox: {
right: 10, right: 10,
feature: { feature: {
dataZoom: { dataZoom: {
yAxisIndex: 'none' yAxisIndex: 'none'
}, },
restore: {}, restore: {},
saveAsImage: {} saveAsImage: {}
} }
}, },
dataZoom: [ dataZoom: [
{ {
show: true, show: true,
start: 80, start: 80,
end: 100 end: 100
}, },
{ {
type: 'inside', type: 'inside',
start: 80, start: 80,
end: 100 end: 100
} }
], ],
visualMap: { visualMap: {
top: 50, top: 50,
right: 10, right: 10,
pieces: [ pieces: [
{ {
gt: 0, gt: 0,
lte: 50, lte: 50,
color: '#93CE07' color: '#93CE07'
}, },
{ {
gt: 50, gt: 50,
lte: 100, lte: 100,
color: '#FBDB0F' color: '#FBDB0F'
}, },
{ {
gt: 100, gt: 100,
lte: 150, lte: 150,
color: '#FC7D02' color: '#FC7D02'
}, },
{ {
gt: 150, gt: 150,
lte: 200, lte: 200,
color: '#FD0100' color: '#FD0100'
}, },
{ {
gt: 200, gt: 200,
lte: 300, lte: 300,
color: '#AA069F' color: '#AA069F'
}, },
{ {
gt: 300, gt: 300,
color: '#AC3B2A' color: '#AC3B2A'
} }
], ],
outOfRange: { outOfRange: {
color: '#999' color: '#999'
} }
}, },
series: { series: {
name: '点赞个数', name: '点赞个数',
type: 'line', type: 'line',
data: {{ yData }}, data: {{ yData }},
markLine: { markLine: {
silent: true, silent: true,
lineStyle: { lineStyle: {
color: '#333' color: '#333'
}, },
data: [ data: [
{ {
yAxis: 50 yAxis: 50
}, },
{ {
yAxis: 100 yAxis: 100
}, },
{ {
yAxis: 150 yAxis: 150
}, },
{ {
yAxis: 200 yAxis: 200
}, },
{ {
yAxis: 300 yAxis: 300
} }
] ]
} }
} }
} }
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
<script> <script>
var chartDom = document.getElementById('mainTwo'); var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
title: { title: {
text: '评论性别占比饼图', text: '评论性别占比饼图',
left: 'center' left: 'center'
}, },
tooltip: { tooltip: {
trigger: 'item' trigger: 'item'
}, },
legend: { legend: {
orient: 'vertical', orient: 'vertical',
left: 'left' left: 'left'
}, },
series: [ series: [
{ {
name: '性别个数', name: '性别个数',
type: 'pie', type: 'pie',
radius: '50%', radius: '50%',
data: {{ genderPieData | tojson }}, data: {{ genderPieData | tojson }},
emphasis: { emphasis: {
itemStyle: { itemStyle: {
shadowBlur: 10, shadowBlur: 10,
shadowOffsetX: 0, shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)' shadowColor: 'rgba(0, 0, 0, 0.5)'
} }
} }
} }
] ]
}; };
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
{% endblock %} {% endblock %}
File diff suppressed because it is too large Load Diff
+30 -27
View File
@@ -1,27 +1,30 @@
<!-- <!--
_oo0oo_ _oo0oo_
o8888888o o8888888o
88" . "88 88" . "88
(| -_- |) (| -_- |)
0\ = /0 0\ = /0
___/`---'\___ ___/`---'\___
.' \\| |// '. .' \\| |// '.
/ \\||| : |||// \ / \\||| : |||// \
/ _||||| -:- |||||- \ / _||||| -:- |||||- \
| | \\\ - /// | | | | \\\ - /// | |
| \_| ''\---/'' |_/ | | \_| ''\---/'' |_/ |
\ .-\__ '-' ___/-. / \ .-\__ '-' ___/-. /
___'. .' /--.--\ `. .'___ ___'. .' /--.--\ `. .'___
."" '< `.___\_<|>_/___.' >' "". ."" '< `.___\_<|>_/___.' >' "".
| | : `- \`.;`\ _ /`;.`/ - ` : | | | | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `_. \_ __\ /__ _/ .-` / / \ \ `_. \_ __\ /__ _/ .-` / /
=====`-.____`.___ \_____/___.-`___.-'===== =====`-.____`.___ \_____/___.-`___.-'=====
`=---=' `=---='
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
佛祖保佑 代码永无BUG 佛祖保佑 代码永无BUG
--> -->
{% extends 'base_page.html' %} {% extends 'base_page.html' %}
{% block title %}
<span data-i18n="home">首页</span>
{% endblock %}
+274 -274
View File
@@ -1,275 +1,275 @@
{% extends 'base_page.html' %} {% extends 'base_page.html' %}
{% block title %} {% block title %}
IP分析 <span data-i18n="ipChar">IP分析</span>
{% endblock %} {% endblock %}
{% block nav %} {% block nav %}
<nav class="iq-sidebar-menu"> <nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu"> <ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span> <span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/home" class="svg-icon"> <a href="/page/home" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">首页</span> <span class="ml-2" data-i18n="home">首页</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon "> <a href="/page/hotWord" class="svg-icon ">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">热词统计</span> <span class="ml-2" data-i18n="hotWord">热词统计</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon"> <a href="/page/tableData" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">微博舆情统计</span> <span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span> <span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon"> <a href="/page/articleChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">文章分析</span> <span class="ml-2" data-i18n="articleChar">文章分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon"> <a href="/page/ipChar" class="svg-icon">
<i class=""> <i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor"> <svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path> <path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line> <line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg> </svg>
</i> </i>
<span class="ml-2">IP分析</span> <span class="ml-2">IP分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon"> <a href="/page/commentChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg> </svg>
</i><span class="ml-2">评论分析</span> </i><span class="ml-2">评论分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon"> <a href="/page/yuqingpredict" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情分析</span> <span class="ml-2">舆情分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon"> <a href="/page/yuqingChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情预测</span> <span class="ml-2">舆情预测</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2"> <li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span> <span class="text-uppercase small font-weight-bold">词云图</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon"> <a href="/page/articleCloud" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg> </svg>
</i><span class="ml-2">文章内容词云图</span> </i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12 mb-4 mt-1"> <div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center"> <div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">IP分析</h4> <h4 class="font-weight-bold" data-i18n="ipChar">IP分析</h4>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">文章IP位置分析图</h4> <h4 class="card-title mb-0" data-i18n="articleIpLocationAnalysis">文章IP位置分析图</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="main" style="width: 100%;height: 750px"></div> <div id="main" style="width: 100%;height: 750px"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">评论IP位置分析图</h4> <h4 class="card-title mb-0" data-i18n="commentIpLocationAnalysis">评论IP位置分析图</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainTwo" style="width: 100%;height: 750px"></div> <div id="mainTwo" style="width: 100%;height: 750px"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block echarts %} {% block echarts %}
<script> <script>
var chartDom = document.getElementById('main'); var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
title:{ title:{
text:'文章IP发布地址地图', text:'文章IP发布地址地图',
left:'center', left:'center',
textStyle:{ textStyle:{
color:"#333", color:"#333",
fontWeight:"bold" fontWeight:"bold"
} }
}, },
tooltip:{ tooltip:{
trigger:'item', trigger:'item',
formatter:function(params){ formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个' return params.name + '<br>微博发布个数: ' + params.value + ' 个'
} }
}, },
visualMap:{ visualMap:{
min:0, min:0,
max:50, max:50,
text:['高','低'], text:['高','低'],
realtime:true, realtime:true,
calulable:true, calulable:true,
inRange:{ inRange:{
color:['orange','green'] color:['orange','green']
} }
}, },
series:[{ series:[{
type:'map', type:'map',
map:"china", map:"china",
label:{ label:{
normal:{ normal:{
show:true, show:true,
color:"white", color:"white",
fontSize:'12' fontSize:'12'
} }
}, },
emphasis:{ emphasis:{
label:{ label:{
show:true show:true
}, },
}, },
data:{{ articleRegionData | tojson }}, data:{{ articleRegionData | tojson }},
itemStyle:{ itemStyle:{
normal: { normal: {
areaColor:"skyblue", areaColor:"skyblue",
borderColor:"#fff" borderColor:"#fff"
}, },
emphasis:{ emphasis:{
areaColor: '#2b91b7' areaColor: '#2b91b7'
} }
}, },
zoom:1.4, zoom:1.4,
roam:true roam:true
}] }]
} }
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
<script> <script>
var chartDom = document.getElementById('mainTwo'); var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
title:{ title:{
text:'文章IP发布地址地图', text:'文章IP发布地址地图',
left:'center', left:'center',
textStyle:{ textStyle:{
color:"#333", color:"#333",
fontWeight:"bold" fontWeight:"bold"
} }
}, },
tooltip:{ tooltip:{
trigger:'item', trigger:'item',
formatter:function(params){ formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个' return params.name + '<br>微博发布个数: ' + params.value + ' 个'
} }
}, },
visualMap:{ visualMap:{
min:0, min:0,
max:200, max:200,
text:['高','低'], text:['高','低'],
realtime:true, realtime:true,
calulable:true, calulable:true,
inRange:{ inRange:{
color:['orange','green'] color:['orange','green']
} }
}, },
series:[{ series:[{
type:'map', type:'map',
map:"china", map:"china",
label:{ label:{
normal:{ normal:{
show:true, show:true,
color:"white", color:"white",
fontSize:'12' fontSize:'12'
} }
}, },
emphasis:{ emphasis:{
label:{ label:{
show:true show:true
}, },
}, },
data:{{ commentRegionData | tojson }}, data:{{ commentRegionData | tojson }},
itemStyle:{ itemStyle:{
normal: { normal: {
areaColor:"skyblue", areaColor:"skyblue",
borderColor:"#fff" borderColor:"#fff"
}, },
emphasis:{ emphasis:{
areaColor: '#2b91b7' areaColor: '#2b91b7'
} }
}, },
zoom:1.4, zoom:1.4,
roam:true roam:true
}] }]
} }
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
{% endblock %} {% endblock %}
+227 -224
View File
@@ -1,225 +1,228 @@
{% extends 'base_page.html' %} {% extends 'base_page.html' %}
{% block title %} {% block title %}
微博舆情统计页 <span data-i18n="tableData">微博舆情统计页</span>
{% endblock %} {% endblock %}
{% block nav %} {% block nav %}
<nav class="iq-sidebar-menu"> <nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu"> <ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span> <span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/home" class="svg-icon"> <a href="/page/home" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">首页</span> <span class="ml-2" data-i18n="home">首页</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon "> <a href="/page/hotWord" class="svg-icon ">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">热词统计</span> <span class="ml-2" data-i18n="hotWord">热词统计</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon"> <a href="/page/tableData" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">微博舆情统计</span> <span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span> <span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon"> <a href="/page/articleChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">文章分析</span> <span class="ml-2" data-i18n="articleChar">文章分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon"> <a href="/page/ipChar" class="svg-icon">
<i class=""> <i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor"> <svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path> <path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line> <line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg> </svg>
</i> </i>
<span class="ml-2">IP分析</span> <span class="ml-2">IP分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon"> <a href="/page/commentChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg> </svg>
</i><span class="ml-2">评论分析</span> </i><span class="ml-2">评论分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon"> <a href="/page/yuqingChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情分析</span> <span class="ml-2">舆情分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon"> <a href="/page/yuqingpredict" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情预测</span> <span class="ml-2">舆情预测</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2"> <li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span> <span class="text-uppercase small font-weight-bold">词云图</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon"> <a href="/page/articleCloud" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg> </svg>
</i><span class="ml-2">文章内容词云图</span> </i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12 mb-4 mt-1"> <div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center"> <div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">微博舆情统计页</h4> <h4 class="font-weight-bold" data-i18n="tableData">微博舆情统计页</h4>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<div class="card"> <div class="card">
<div class="card-header d-flex justify-content-between"> <div class="card-header d-flex justify-content-between">
<div class="header-title"> <div class="header-title">
<h4 class="card-title">微博文章统计表格 - 舆情 情感分类 <h4 class="card-title" data-i18n="weiboArticleStatTable">微博文章统计表格 - 舆情 情感分类</h4>
</h4> </div>
</div> <div class="header-action">
<div class="header-action"> <i data-toggle="collapse" data-target="#datatable-1" aria-expanded="false">
<i data-toggle="collapse" data-target="#datatable-1" aria-expanded="false"> <svg width="20" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg width="20" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path> </svg>
</svg> </i>
</i> </div>
</div> </div>
</div> <div class="card-body">
<div class="card-body"> <p><a href="/page/tableData?flag=True" class="btn btn-primary btn-sm" data-i18n="sentimentClassification">情感分类</a></p>
<p><a href="/page/tableData?flag=True" class="btn btn-primary btn-sm">情感分类</a></p> <div class="table-responsive">
<div class="table-responsive"> <div id="datatable-1_wrapper" class="dataTables_wrapper dt-bootstrap4">
<div id="datatable-1_wrapper" class="dataTables_wrapper dt-bootstrap4"> <div class="row">
<div class="row"> <table id="datatable-1" class="table data-table table-striped table-bordered dataTable" role="grid" aria-describedby="datatable-1_info">
<table id="datatable-1" class="table data-table table-striped table-bordered dataTable" role="grid" aria-describedby="datatable-1_info"> <thead>
<thead> <tr>
<tr> <th style="font-weight: bold" data-i18n="articleId">
<th style="font-weight: bold"> 文章ID
文章ID </th>
</th> <th style="font-weight: bold" data-i18n="articleIp">
<th style="font-weight: bold"> 文章IP
文章IP </th>
</th> <th style="font-weight: bold" data-i18n="articleTitle">
<th style="font-weight: bold"> 文章标题
点赞量 </th>
</th> <th style="font-weight: bold" data-i18n="articleLike">
<th style="font-weight: bold"> 点赞量
转发量 </th>
</th> <th style="font-weight: bold" data-i18n="articleForward">
<th style="font-weight: bold"> 转发量
评论量 </th>
</th> <th style="font-weight: bold" data-i18n="articleComment">
<th style="font-weight: bold"> 评论量
类型 </th>
</th> <th style="font-weight: bold" data-i18n="articleType">
<th style="font-weight: bold"> 类型
内容 </th>
</th> <th style="font-weight: bold" data-i18n="articleContent">
<th style="font-weight: bold"> 内容
发布时间 </th>
</th> <th style="font-weight: bold" data-i18n="articleTime">
{% if defaultFlag %} 发布时间
<th style="font-weight: bold"> </th>
情感分类 {% if defaultFlag %}
</th> <th style="font-weight: bold" data-i18n="sentimentClassification">
{% endif %} 情感分类
</tr> </th>
</thead> {% endif %}
<tbody> </tr>
{% for article in tableData %} </thead>
<tr class="even"> <tbody>
<td style="width:330px" class="sorting_1"> {% for article in tableData %}
<a href="{{ article[9] }}"> <tr class="even">
{{ article[0] }} <td style="width:330px" class="sorting_1">
</a> <a href="{{ article[9] }}">
</td> {{ article[0] }}
<td style="width:90px">{{ article[4] }}</td> </a>
<td style="width:90px">👍{{ article[1] }}</td> </td>
<td style="width:90px">🥇{{ article[2] }}</td> <td style="width:90px">{{ article[4] }}</td>
<td style="width:90px">🔥{{ article[3] }}</td> <td style="width:90px" class="text-right">{{ article[5] }}</td>
<td style="width:90px" class="text-right">{{ article[8] }}</td> <td style="width:90px">👍{{ article[1] }}</td>
<td style="width:155px" class="text-right">{{ article[5] }}</td> <td style="width:90px">🥇{{ article[2] }}</td>
<td style="width:90px" class="text-right">{{ article[7] }}</td> <td style="width:90px">🔥{{ article[3] }}</td>
{% if defaultFlag %} <td style="width:90px" class="text-right">{{ article[8] }}</td>
<td style="width:90px"> <td style="width:155px" class="text-right">{{ article[5] }}</td>
{% if article[-1] == '正面' %} <td style="width:90px" class="text-right">{{ article[7] }}</td>
<span class="text-success"> {% if defaultFlag %}
{{ article[-1] }} <td style="width:90px">
</span> {% if article[-1] == '正面' %}
<span class="text-success">
{% else %} {{ article[-1] }}
<span class="text-danger"> </span>
{{ article[-1] }}
</span> {% else %}
<span class="text-danger">
{% endif %} {{ article[-1] }}
</td> </span>
{% endif %}
</tr> {% endif %}
{% endfor %} </td>
</tbody> {% endif %}
</table> </tr>
</div> {% endfor %}
</div> </tbody>
</div> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
{% endblock %} </div>
</div>
{% block echarts %} {% endblock %}
<script>
</script> {% block echarts %}
<script>
</script>
{% endblock %} {% endblock %}
+400 -400
View File
@@ -1,401 +1,401 @@
{% extends 'base_page.html' %} {% extends 'base_page.html' %}
{% block title %} {% block title %}
舆情分析 <span data-i18n="yuqingChar">舆情分析</span>
{% endblock %} {% endblock %}
{% block nav %} {% block nav %}
<nav class="iq-sidebar-menu"> <nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu"> <ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span> <span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/home" class="svg-icon"> <a href="/page/home" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">首页</span> <span class="ml-2" data-i18n="home">首页</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon "> <a href="/page/hotWord" class="svg-icon ">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">热词统计</span> <span class="ml-2" data-i18n="hotWord">热词统计</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon"> <a href="/page/tableData" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">微博舆情统计</span> <span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2 "> <li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span> <span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon"> <a href="/page/articleChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">文章分析</span> <span class="ml-2" data-i18n="articleChar">文章分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon"> <a href="/page/ipChar" class="svg-icon">
<i class=""> <i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor"> <svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path> <path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line> <line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg> </svg>
</i> </i>
<span class="ml-2">IP分析</span> <span class="ml-2">IP分析</span>
</a> </a>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon"> <a href="/page/commentChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg> </svg>
</i><span class="ml-2">评论分析</span> </i><span class="ml-2">评论分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon"> <a href="/page/yuqingChar" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情分析</span> <span class="ml-2">舆情分析</span>
</a> </a>
</li> </li>
<li class="active sidebar-layout"> <li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon"> <a href="/page/yuqingpredict" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg> </svg>
</i> </i>
<span class="ml-2">舆情预测</span> <span class="ml-2">舆情预测</span>
</a> </a>
</li> </li>
<li class="px-3 pt-3 pb-2"> <li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span> <span class="text-uppercase small font-weight-bold">词云图</span>
</li> </li>
<li class=" sidebar-layout"> <li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon"> <a href="/page/articleCloud" class="svg-icon">
<i class=""> <i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg> </svg>
</i><span class="ml-2">文章内容词云图</span> </i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12 mb-4 mt-1"> <div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center"> <div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">舆情分析</h4> <h4 class="font-weight-bold" data-i18n="yuqingChar">舆情分析</h4>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">热词情感趋势柱状图</h4> <h4 class="card-title mb-0" data-i18n="hotWordSentimentTrendBar">热词情感趋势柱状图</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div> <div id="main" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">热词情感趋势树形图</h4> <h4 class="card-title mb-0" data-i18n="hotWordSentimentTrendTree">热词情感趋势树形图</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div> <div id="mainTwo" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">文章内容与评论内容舆情趋势饼状图</h4> <h4 class="card-title mb-0" data-i18n="articleCommentSentimentTrendPie">文章内容与评论内容舆情趋势饼状图</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div> <div id="mainThree" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card card-block"> <div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0"> <div class="card-header d-flex justify-content-between pb-0">
<div class="header-title"> <div class="header-title">
<h4 class="card-title mb-0">热词TOP10</h4> <h4 class="card-title mb-0">热词TOP10</h4>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="mainFore" style="width: 100%;height: 450px"></div> <div id="mainFore" style="width: 100%;height: 450px"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-12 mb-3"> <div class="col-lg-12 mb-3">
<div class="form-group"> <div class="form-group">
<label for="modelSelect">选择分析模型:</label> <label for="modelSelect">选择分析模型:</label>
<select class="form-control" id="modelSelect" onchange="updateModel(this.value)"> <select class="form-control" id="modelSelect" onchange="updateModel(this.value)">
<optgroup label="基础模型"> <optgroup label="基础模型">
<option value="basic" {% if model_type == 'basic' %}selected{% endif %}>SnowNLP</option> <option value="basic" {% if model_type == 'basic' %}selected{% endif %}>SnowNLP</option>
</optgroup> </optgroup>
<optgroup label="OpenAI 模型"> <optgroup label="OpenAI 模型">
<option value="gpt-3.5-turbo" {% if model_type == 'gpt-3.5-turbo' %}selected{% endif %}>GPT-3.5-Turbo</option> <option value="gpt-3.5-turbo" {% if model_type == 'gpt-3.5-turbo' %}selected{% endif %}>GPT-3.5-Turbo</option>
<option value="gpt-4" {% if model_type == 'gpt-4' %}selected{% endif %}>GPT-4</option> <option value="gpt-4" {% if model_type == 'gpt-4' %}selected{% endif %}>GPT-4</option>
</optgroup> </optgroup>
<optgroup label="Claude 模型"> <optgroup label="Claude 模型">
<option value="claude-3-opus-20240229" {% if model_type == 'claude-3-opus-20240229' %}selected{% endif %}>Claude-3 Opus</option> <option value="claude-3-opus-20240229" {% if model_type == 'claude-3-opus-20240229' %}selected{% endif %}>Claude-3 Opus</option>
<option value="claude-3-sonnet-20240229" {% if model_type == 'claude-3-sonnet-20240229' %}selected{% endif %}>Claude-3 Sonnet</option> <option value="claude-3-sonnet-20240229" {% if model_type == 'claude-3-sonnet-20240229' %}selected{% endif %}>Claude-3 Sonnet</option>
<option value="claude-3-haiku-20240307" {% if model_type == 'claude-3-haiku-20240307' %}selected{% endif %}>Claude-3 Haiku</option> <option value="claude-3-haiku-20240307" {% if model_type == 'claude-3-haiku-20240307' %}selected{% endif %}>Claude-3 Haiku</option>
</optgroup> </optgroup>
<optgroup label="DeepSeek 模型"> <optgroup label="DeepSeek 模型">
<option value="deepseek-chat" {% if model_type == 'deepseek-chat' %}selected{% endif %}>DeepSeek-V3</option> <option value="deepseek-chat" {% if model_type == 'deepseek-chat' %}selected{% endif %}>DeepSeek-V3</option>
<option value="deepseek-reasoner" {% if model_type == 'deepseek-reasoner' %}selected{% endif %}>DeepSeek-R1</option> <option value="deepseek-reasoner" {% if model_type == 'deepseek-reasoner' %}selected{% endif %}>DeepSeek-R1</option>
</optgroup> </optgroup>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block echarts %} {% block echarts %}
<script> <script>
var chartDom = document.getElementById('main'); var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
var colors = ['#66CC99', '#FFCC66', '#FF6666']; var colors = ['#66CC99', '#FFCC66', '#FF6666'];
option = { option = {
title: { title: {
text: '热词情感分析柱状图' text: '热词情感分析柱状图'
}, },
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis'
}, },
legend: { legend: {
data: ['Rainfall'] data: ['Rainfall']
}, },
toolbox: { toolbox: {
show: true, show: true,
feature: { feature: {
dataView: { show: true, readOnly: false }, dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] }, magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true }, restore: { show: true },
saveAsImage: { show: true } saveAsImage: { show: true }
} }
}, },
calculable: true, calculable: true,
xAxis: [ xAxis: [
{ {
type: 'category', type: 'category',
// prettier-ignore // prettier-ignore
data: {{ xData | tojson }} data: {{ xData | tojson }}
} }
], ],
yAxis: [ yAxis: [
{ {
type: 'value' type: 'value'
} }
], ],
series: [ series: [
{ {
name: '舆情个数', name: '舆情个数',
type: 'bar', type: 'bar',
data: {{ yData }}, data: {{ yData }},
markPoint: { markPoint: {
data: [ data: [
{ type: 'max', name: 'Max' }, { type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' } { type: 'min', name: 'Min' }
] ]
}, },
markLine: { markLine: {
data: [{ type: 'average', name: 'Avg' }] data: [{ type: 'average', name: 'Avg' }]
}, },
itemStyle: { itemStyle: {
color: function (parmas) { color: function (parmas) {
return colors[parmas.dataIndex % colors.length]; return colors[parmas.dataIndex % colors.length];
} }
} }
} }
] ]
}; };
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
<script> <script>
var chartDom = document.getElementById('mainTwo'); var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
series: [ series: [
{ {
type: 'treemap', type: 'treemap',
data: {{ biedata | tojson }} data: {{ biedata | tojson }}
} }
] ]
}; };
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
<script> <script>
var chartDom = document.getElementById('mainThree'); var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)' formatter: '{a} <br/>{b}: {c} ({d}%)'
}, },
legend: { legend: {
data: [ data: [
'正面', '正面',
'负面', '负面',
'中性' '中性'
] ]
}, },
series: [ series: [
{ {
name: '评论舆情结果', name: '评论舆情结果',
type: 'pie', type: 'pie',
selectedMode: 'single', selectedMode: 'single',
radius: [0, '30%'], radius: [0, '30%'],
label: { label: {
position: 'inner', position: 'inner',
fontSize: 14 fontSize: 14
}, },
labelLine: { labelLine: {
show: false show: false
}, },
data: {{ biedata1 | tojson }} data: {{ biedata1 | tojson }}
}, },
{ {
name: '文章舆情结果', name: '文章舆情结果',
type: 'pie', type: 'pie',
radius: ['45%', '60%'], radius: ['45%', '60%'],
labelLine: { labelLine: {
length: 30 length: 30
}, },
label: { label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}}{c} {per|{d}%} ', formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}}{c} {per|{d}%} ',
backgroundColor: '#F6F8FC', backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E', borderColor: '#8C8D8E',
borderWidth: 1, borderWidth: 1,
borderRadius: 4, borderRadius: 4,
rich: { rich: {
a: { a: {
color: '#6E7079', color: '#6E7079',
lineHeight: 22, lineHeight: 22,
align: 'center' align: 'center'
}, },
hr: { hr: {
borderColor: '#8C8D8E', borderColor: '#8C8D8E',
width: '100%', width: '100%',
borderWidth: 1, borderWidth: 1,
height: 0 height: 0
}, },
b: { b: {
color: '#4C5058', color: '#4C5058',
fontSize: 14, fontSize: 14,
fontWeight: 'bold', fontWeight: 'bold',
lineHeight: 33 lineHeight: 33
}, },
per: { per: {
color: '#fff', color: '#fff',
backgroundColor: '#4C5058', backgroundColor: '#4C5058',
padding: [3, 4], padding: [3, 4],
borderRadius: 4 borderRadius: 4
} }
} }
}, },
data: {{ biedata2 | tojson }} data: {{ biedata2 | tojson }}
} }
] ]
}; };
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
<script> <script>
var chartDom = document.getElementById('mainFore'); var chartDom = document.getElementById('mainFore');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { option = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'shadow' type: 'shadow'
} }
}, },
legend: {}, legend: {},
grid: { grid: {
left: '3%', left: '3%',
right: '4%', right: '4%',
bottom: '3%', bottom: '3%',
containLabel: true containLabel: true
}, },
xAxis: { xAxis: {
type: 'value', type: 'value',
boundaryGap: [0, 0.01] boundaryGap: [0, 0.01]
}, },
yAxis: { yAxis: {
type: 'category', type: 'category',
data: {{ x1Data | tojson }} data: {{ x1Data | tojson }}
}, },
series: [ series: [
{ {
name: '热词出现个数', name: '热词出现个数',
type: 'bar', type: 'bar',
data:{{ y1Data }} data:{{ y1Data }}
} }
] ]
}; };
option && myChart.setOption(option); option && myChart.setOption(option);
</script> </script>
<script> <script>
function updateModel(value) { function updateModel(value) {
window.location.href = '/page/yuqingChar?model=' + value; window.location.href = '/page/yuqingChar?model=' + value;
} }
</script> </script>
{% endblock %} {% endblock %}
File diff suppressed because it is too large Load Diff
+370 -370
View File
@@ -1,371 +1,371 @@
from flask import Blueprint, jsonify, request, render_template from flask import Blueprint, jsonify, request, render_template
import json import json
import os import os
from datetime import datetime from datetime import datetime
import threading import threading
from queue import Queue from queue import Queue
import asyncio import asyncio
import websockets import websockets
import logging import logging
from spider.spiderData import SpiderData from spider.spiderData import SpiderData
from openai import OpenAI from openai import OpenAI
from anthropic import Anthropic from anthropic import Anthropic
import aiohttp import aiohttp
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from ratelimit import limits, sleep_and_retry from ratelimit import limits, sleep_and_retry
from tenacity import retry, stop_after_attempt, wait_exponential from tenacity import retry, stop_after_attempt, wait_exponential
# 创建蓝图 # 创建蓝图
spider_bp = Blueprint('spider', __name__) spider_bp = Blueprint('spider', __name__)
# 创建日志记录器 # 创建日志记录器
logger = logging.getLogger('spider_control') logger = logging.getLogger('spider_control')
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
# 存储WebSocket连接的集合 # 存储WebSocket连接的集合
websocket_connections = set() websocket_connections = set()
# 创建消息队列 # 创建消息队列
message_queue = Queue() message_queue = Queue()
# 创建线程池 # 创建线程池
thread_pool = ThreadPoolExecutor(max_workers=3) thread_pool = ThreadPoolExecutor(max_workers=3)
# 创建异步事件循环 # 创建异步事件循环
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
# 默认配置 # 默认配置
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
'crawlDepth': 3, 'crawlDepth': 3,
'interval': 5, 'interval': 5,
'maxRetries': 3, 'maxRetries': 3,
'timeout': 30, 'timeout': 30,
'maxConcurrent': 2 'maxConcurrent': 2
} }
# 限流装饰器 # 限流装饰器
@sleep_and_retry @sleep_and_retry
@limits(calls=100, period=60) # 每分钟最多100个请求 @limits(calls=100, period=60) # 每分钟最多100个请求
def rate_limited_request(): def rate_limited_request():
pass pass
class SpiderWorker: class SpiderWorker:
def __init__(self, topics, parameters): def __init__(self, topics, parameters):
self.topics = topics self.topics = topics
self.parameters = parameters self.parameters = parameters
self.total_topics = len(topics) self.total_topics = len(topics)
self.completed_topics = 0 self.completed_topics = 0
self.spider = SpiderData() self.spider = SpiderData()
self.message_buffer = [] self.message_buffer = []
self.message_buffer_size = 10 self.message_buffer_size = 10
self.semaphore = asyncio.Semaphore(parameters.get('maxConcurrent', DEFAULT_CONFIG['maxConcurrent'])) self.semaphore = asyncio.Semaphore(parameters.get('maxConcurrent', DEFAULT_CONFIG['maxConcurrent']))
async def send_message(self, message): async def send_message(self, message):
"""异步发送消息,使用缓冲区优化""" """异步发送消息,使用缓冲区优化"""
self.message_buffer.append(message) self.message_buffer.append(message)
if len(self.message_buffer) >= self.message_buffer_size: if len(self.message_buffer) >= self.message_buffer_size:
await self.flush_messages() await self.flush_messages()
async def flush_messages(self): async def flush_messages(self):
"""刷新消息缓冲区""" """刷新消息缓冲区"""
if not self.message_buffer: if not self.message_buffer:
return return
try: try:
await broadcast_message(self.message_buffer) await broadcast_message(self.message_buffer)
self.message_buffer.clear() self.message_buffer.clear()
except Exception as e: except Exception as e:
logger.error(f"发送消息失败: {e}") logger.error(f"发送消息失败: {e}")
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10)) @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
async def crawl_single_topic(self, topic): async def crawl_single_topic(self, topic):
"""爬取单个话题""" """爬取单个话题"""
try: try:
rate_limited_request() rate_limited_request()
await self.send_message({ await self.send_message({
'type': 'log', 'type': 'log',
'message': f'开始爬取话题: {topic}' 'message': f'开始爬取话题: {topic}'
}) })
async with self.semaphore: async with self.semaphore:
await asyncio.get_event_loop().run_in_executor( await asyncio.get_event_loop().run_in_executor(
thread_pool, thread_pool,
self.spider.crawl_topic, self.spider.crawl_topic,
topic, topic,
self.parameters['crawlDepth'], self.parameters['crawlDepth'],
self.parameters['interval'], self.parameters['interval'],
self.parameters['maxRetries'], self.parameters['maxRetries'],
self.parameters['timeout'] self.parameters['timeout']
) )
self.completed_topics += 1 self.completed_topics += 1
progress = int((self.completed_topics / self.total_topics) * 100) progress = int((self.completed_topics / self.total_topics) * 100)
await self.send_message({ await self.send_message({
'type': 'progress', 'type': 'progress',
'value': progress 'value': progress
}) })
await self.send_message({ await self.send_message({
'type': 'log', 'type': 'log',
'message': f'话题 {topic} 爬取完成' 'message': f'话题 {topic} 爬取完成'
}) })
except Exception as e: except Exception as e:
logger.error(f"爬取话题 {topic} 失败: {e}") logger.error(f"爬取话题 {topic} 失败: {e}")
await self.send_message({ await self.send_message({
'type': 'log', 'type': 'log',
'message': f'爬取话题 {topic} 时出错: {str(e)}' 'message': f'爬取话题 {topic} 时出错: {str(e)}'
}) })
raise raise
async def run(self): async def run(self):
"""运行爬虫任务""" """运行爬虫任务"""
try: try:
tasks = [self.crawl_single_topic(topic) for topic in self.topics] tasks = [self.crawl_single_topic(topic) for topic in self.topics]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
await self.flush_messages() await self.flush_messages()
await self.send_message({ await self.send_message({
'type': 'log', 'type': 'log',
'message': '所有话题爬取完成' 'message': '所有话题爬取完成'
}) })
except Exception as e: except Exception as e:
logger.error(f"爬虫任务执行出错: {e}") logger.error(f"爬虫任务执行出错: {e}")
await self.send_message({ await self.send_message({
'type': 'log', 'type': 'log',
'message': f'爬虫任务执行出错: {str(e)}' 'message': f'爬虫任务执行出错: {str(e)}'
}) })
finally: finally:
await self.flush_messages() await self.flush_messages()
async def broadcast_message(messages): async def broadcast_message(messages):
"""广播消息到所有WebSocket连接""" """广播消息到所有WebSocket连接"""
if not websocket_connections: if not websocket_connections:
return return
for websocket in websocket_connections.copy(): for websocket in websocket_connections.copy():
try: try:
if isinstance(messages, list): if isinstance(messages, list):
for message in messages: for message in messages:
await websocket.send(json.dumps(message)) await websocket.send(json.dumps(message))
else: else:
await websocket.send(json.dumps(messages)) await websocket.send(json.dumps(messages))
except websockets.exceptions.ConnectionClosed: except websockets.exceptions.ConnectionClosed:
websocket_connections.remove(websocket) websocket_connections.remove(websocket)
except Exception as e: except Exception as e:
logger.error(f"发送WebSocket消息失败: {e}") logger.error(f"发送WebSocket消息失败: {e}")
websocket_connections.remove(websocket) websocket_connections.remove(websocket)
@spider_bp.route('/spider/control') @spider_bp.route('/spider/control')
def spider_control(): def spider_control():
"""渲染爬虫控制页面""" """渲染爬虫控制页面"""
return render_template('spider_control.html') return render_template('spider_control.html')
@spider_bp.route('/api/spider/start', methods=['POST']) @spider_bp.route('/api/spider/start', methods=['POST'])
async def start_spider(): async def start_spider():
"""启动爬虫任务""" """启动爬虫任务"""
try: try:
data = request.get_json() data = request.get_json()
topics = data.get('topics', []) topics = data.get('topics', [])
parameters = {**DEFAULT_CONFIG, **data.get('parameters', {})} parameters = {**DEFAULT_CONFIG, **data.get('parameters', {})}
if not topics: if not topics:
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': '请选择至少一个话题' 'message': '请选择至少一个话题'
}) })
# 创建爬虫工作器 # 创建爬虫工作器
worker = SpiderWorker(topics, parameters) worker = SpiderWorker(topics, parameters)
# 在事件循环中运行爬虫任务 # 在事件循环中运行爬虫任务
asyncio.create_task(worker.run()) asyncio.create_task(worker.run())
return jsonify({ return jsonify({
'success': True, 'success': True,
'message': '爬虫任务已启动' 'message': '爬虫任务已启动'
}) })
except Exception as e: except Exception as e:
logger.error(f"启动爬虫任务失败: {e}") logger.error(f"启动爬虫任务失败: {e}")
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': str(e) 'message': str(e)
}) })
@spider_bp.route('/api/spider/save-config', methods=['POST']) @spider_bp.route('/api/spider/save-config', methods=['POST'])
def save_spider_config(): def save_spider_config():
"""保存爬虫配置""" """保存爬虫配置"""
try: try:
config = request.get_json() config = request.get_json()
if save_config(config): if save_config(config):
return jsonify({ return jsonify({
'success': True, 'success': True,
'message': '配置保存成功' 'message': '配置保存成功'
}) })
else: else:
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': '配置保存失败' 'message': '配置保存失败'
}) })
except Exception as e: except Exception as e:
logger.error(f"保存配置失败: {e}") logger.error(f"保存配置失败: {e}")
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': str(e) 'message': str(e)
}) })
@spider_bp.websocket('/ws/spider-status') @spider_bp.websocket('/ws/spider-status')
async def spider_status_socket(websocket): async def spider_status_socket(websocket):
"""WebSocket连接处理""" """WebSocket连接处理"""
try: try:
websocket_connections.add(websocket) websocket_connections.add(websocket)
logging.info("新的WebSocket连接已建立") logging.info("新的WebSocket连接已建立")
try: try:
while True: while True:
# 等待消息,保持连接活跃 # 等待消息,保持连接活跃
message = await websocket.receive() message = await websocket.receive()
if message is None: if message is None:
break break
except websockets.exceptions.ConnectionClosed: except websockets.exceptions.ConnectionClosed:
logging.info("WebSocket连接已关闭") logging.info("WebSocket连接已关闭")
finally: finally:
websocket_connections.remove(websocket) websocket_connections.remove(websocket)
logging.info("WebSocket连接已移除") logging.info("WebSocket连接已移除")
except Exception as e: except Exception as e:
logger.error(f"WebSocket连接处理失败: {e}") logger.error(f"WebSocket连接处理失败: {e}")
if websocket in websocket_connections: if websocket in websocket_connections:
websocket_connections.remove(websocket) websocket_connections.remove(websocket)
def get_ai_client(): def get_ai_client():
"""获取可用的AI客户端""" """获取可用的AI客户端"""
# 按优先级尝试不同的AI服务 # 按优先级尝试不同的AI服务
if os.getenv('ANTHROPIC_API_KEY'): if os.getenv('ANTHROPIC_API_KEY'):
return { return {
'type': 'anthropic', 'type': 'anthropic',
'client': Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY')) 'client': Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
} }
elif os.getenv('OPENAI_API_KEY'): elif os.getenv('OPENAI_API_KEY'):
return { return {
'type': 'openai', 'type': 'openai',
'client': OpenAI(api_key=os.getenv('OPENAI_API_KEY')) 'client': OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
} }
else: else:
raise ValueError("未找到可用的AI API密钥") raise ValueError("未找到可用的AI API密钥")
def parse_ai_response(response_text): def parse_ai_response(response_text):
"""解析AI响应中的JSON配置""" """解析AI响应中的JSON配置"""
try: try:
# 查找JSON内容 # 查找JSON内容
start = response_text.find('{') start = response_text.find('{')
end = response_text.rfind('}') + 1 end = response_text.rfind('}') + 1
if start == -1 or end == 0: if start == -1 or end == 0:
raise ValueError("未找到有效的JSON配置") raise ValueError("未找到有效的JSON配置")
json_str = response_text[start:end] json_str = response_text[start:end]
config = json.loads(json_str) config = json.loads(json_str)
# 验证配置格式 # 验证配置格式
if not isinstance(config.get('topics'), list): if not isinstance(config.get('topics'), list):
raise ValueError("配置必须包含话题列表") raise ValueError("配置必须包含话题列表")
parameters = config.get('parameters', {}) parameters = config.get('parameters', {})
if not all(key in parameters for key in ['crawlDepth', 'interval', 'maxRetries', 'timeout']): if not all(key in parameters for key in ['crawlDepth', 'interval', 'maxRetries', 'timeout']):
raise ValueError("配置缺少必要的参数") raise ValueError("配置缺少必要的参数")
# 提取建议文本(JSON之前的部分) # 提取建议文本(JSON之前的部分)
suggestion = response_text[:start].strip() suggestion = response_text[:start].strip()
return config, suggestion return config, suggestion
except Exception as e: except Exception as e:
raise ValueError(f"解析AI响应失败: {str(e)}") raise ValueError(f"解析AI响应失败: {str(e)}")
@spider_bp.route('/api/spider/ai-config', methods=['POST']) @spider_bp.route('/api/spider/ai-config', methods=['POST'])
def generate_ai_config(): def generate_ai_config():
"""使用AI生成爬虫配置""" """使用AI生成爬虫配置"""
try: try:
prompt = request.json.get('prompt', '') prompt = request.json.get('prompt', '')
if not prompt: if not prompt:
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': '请提供爬虫需求描述' 'message': '请提供爬虫需求描述'
}) })
# 构建AI提示 # 构建AI提示
system_prompt = """你是一个专业的爬虫配置助手。请根据用户的自然语言描述,生成合适的微博爬虫配置。 system_prompt = """你是一个专业的爬虫配置助手。请根据用户的自然语言描述,生成合适的微博爬虫配置。
配置应包含以下内容: 配置应包含以下内容:
1. 要爬取的话题列表 1. 要爬取的话题列表
2. 爬虫参数(爬取深度、间隔时间、重试次数、超时时间) 2. 爬虫参数(爬取深度、间隔时间、重试次数、超时时间)
请先用通俗易懂的语言解释你的配置建议,然后在最后提供一个JSON格式的具体配置。 请先用通俗易懂的语言解释你的配置建议,然后在最后提供一个JSON格式的具体配置。
注意: 注意:
- 爬取深度(crawlDepth)范围:1-10页 - 爬取深度(crawlDepth)范围:1-10页
- 间隔时间(interval)范围:3-30秒 - 间隔时间(interval)范围:3-30秒
- 重试次数(maxRetries)范围:1-5次 - 重试次数(maxRetries)范围:1-5次
- 超时时间(timeout)范围:10-60秒 - 超时时间(timeout)范围:10-60秒
- 所有参数都必须是整数 - 所有参数都必须是整数
示例输出格式: 示例输出格式:
根据您的需求,我建议... 根据您的需求,我建议...
{ {
"topics": ["话题1", "话题2"], "topics": ["话题1", "话题2"],
"parameters": { "parameters": {
"crawlDepth": 5, "crawlDepth": 5,
"interval": 5, "interval": 5,
"maxRetries": 3, "maxRetries": 3,
"timeout": 30 "timeout": 30
} }
}""" }"""
# 获取AI客户端 # 获取AI客户端
ai = get_ai_client() ai = get_ai_client()
try: try:
if ai['type'] == 'anthropic': if ai['type'] == 'anthropic':
response = ai['client'].messages.create( response = ai['client'].messages.create(
model="claude-3-sonnet-20240229", model="claude-3-sonnet-20240229",
max_tokens=1000, max_tokens=1000,
messages=[ messages=[
{"role": "system", "content": system_prompt}, {"role": "system", "content": system_prompt},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
] ]
) )
response_text = response.content[0].text response_text = response.content[0].text
else: # OpenAI else: # OpenAI
response = ai['client'].chat.completions.create( response = ai['client'].chat.completions.create(
model="gpt-3.5-turbo", model="gpt-3.5-turbo",
messages=[ messages=[
{"role": "system", "content": system_prompt}, {"role": "system", "content": system_prompt},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
] ]
) )
response_text = response.choices[0].message.content response_text = response.choices[0].message.content
# 解析AI响应 # 解析AI响应
config, suggestion = parse_ai_response(response_text) config, suggestion = parse_ai_response(response_text)
return jsonify({ return jsonify({
'success': True, 'success': True,
'config': config, 'config': config,
'suggestion': suggestion 'suggestion': suggestion
}) })
except Exception as e: except Exception as e:
logger.error(f"AI服务调用失败: {e}") logger.error(f"AI服务调用失败: {e}")
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': f"AI配置生成失败: {str(e)}" 'message': f"AI配置生成失败: {str(e)}"
}) })
except Exception as e: except Exception as e:
logger.error(f"生成配置失败: {e}") logger.error(f"生成配置失败: {e}")
return jsonify({ return jsonify({
'success': False, 'success': False,
'message': str(e) 'message': str(e)
}) })
Binary file not shown.
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>微博舆情分析系统 | 登录</title> <title>微博舆情分析系统 | 登录</title>
<link rel="icon" href="../../../static/原神启动/favicon.ico" /> <link rel="icon" href="../../../static/原神启动/favicon.ico" />
<script src="/static/js/i18n.js"></script>
</head> </head>
<style> <style>
@@ -287,9 +288,31 @@
.yanzhengma:hover { .yanzhengma:hover {
color: #aa863e; color: #aa863e;
} }
/* 添加语言切换按钮样式 */
.language-switcher {
position: absolute;
top: 20px;
right: 20px;
background-color: rgba(255, 255, 255, 0.8);
padding: 8px 15px;
border-radius: 20px;
cursor: pointer;
z-index: 1000;
font-size: 14px;
transition: all 0.3s ease;
}
.language-switcher:hover {
background-color: rgba(255, 255, 255, 1);
}
</style> </style>
<body onclick="playAudio()"> <body onclick="playAudio()">
<div class="language-switcher" id="language-switcher">
<span id="language-text">切换语言</span>
</div>
<audio id="audio-player" autoplay class="hide-player"> <audio id="audio-player" autoplay class="hide-player">
<source <source
src="https://lovexl-oss.oss-cn-beijing.aliyuncs.com/bed/%E5%8E%9F%E7%A5%9E%E9%A6%96%E9%A1%B5%E8%83%8C%E6%99%AF%E9%9F%B3.mp4" src="https://lovexl-oss.oss-cn-beijing.aliyuncs.com/bed/%E5%8E%9F%E7%A5%9E%E9%A6%96%E9%A1%B5%E8%83%8C%E6%99%AF%E9%9F%B3.mp4"
+372 -372
View File
@@ -1,372 +1,372 @@
import time import time
import hashlib import hashlib
from flask import Blueprint, redirect, render_template, request, Flask, session, current_app, make_response from flask import Blueprint, redirect, render_template, request, Flask, session, current_app, make_response
from datetime import datetime, timedelta from datetime import datetime, timedelta
import re import re
from utils.query import query from utils.query import query
from utils.errorResponse import errorResponse from utils.errorResponse import errorResponse
from utils.logger import app_logger as logging from utils.logger import app_logger as logging
from functools import wraps from functools import wraps
import secrets import secrets
from flask_limiter import Limiter from flask_limiter import Limiter
from flask_limiter.util import get_remote_address from flask_limiter.util import get_remote_address
import redis import redis
import json import json
import bleach import bleach
from argon2 import PasswordHasher from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError from argon2.exceptions import VerifyMismatchError
import html import html
# 创建Argon2密码哈希器 # 创建Argon2密码哈希器
ph = PasswordHasher() ph = PasswordHasher()
# Redis连接 # Redis连接
redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# 创建限流器 # 创建限流器
limiter = Limiter( limiter = Limiter(
key_func=get_remote_address, key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"] default_limits=["200 per day", "50 per hour"]
) )
ub = Blueprint('user', ub = Blueprint('user',
__name__, __name__,
url_prefix='/user', url_prefix='/user',
template_folder='templates') template_folder='templates')
def sanitize_input(text): def sanitize_input(text):
"""清理用户输入,防止XSS攻击""" """清理用户输入,防止XSS攻击"""
if text is None: if text is None:
return None return None
return bleach.clean(str(text), strip=True) return bleach.clean(str(text), strip=True)
def validate_csrf_token(): def validate_csrf_token():
"""验证CSRF令牌""" """验证CSRF令牌"""
token = request.form.get('csrf_token') token = request.form.get('csrf_token')
stored_token = session.get('csrf_token') stored_token = session.get('csrf_token')
if not token or not stored_token or token != stored_token: if not token or not stored_token or token != stored_token:
return False return False
return True return True
def get_client_info(): def get_client_info():
"""获取客户端信息""" """获取客户端信息"""
return { return {
'ip': request.remote_addr, 'ip': request.remote_addr,
'user_agent': str(request.user_agent.string), 'user_agent': str(request.user_agent.string),
'platform': str(request.user_agent.platform), 'platform': str(request.user_agent.platform),
'browser': str(request.user_agent.browser), 'browser': str(request.user_agent.browser),
} }
def is_suspicious_ip(ip): def is_suspicious_ip(ip):
"""检查IP是否可疑""" """检查IP是否可疑"""
key = f"login_attempts:{ip}" key = f"login_attempts:{ip}"
attempts = redis_client.get(key) attempts = redis_client.get(key)
if attempts and int(attempts) >= 5: # 5次失败尝试 if attempts and int(attempts) >= 5: # 5次失败尝试
return True return True
return False return False
def record_failed_attempt(ip): def record_failed_attempt(ip):
"""记录失败的登录尝试""" """记录失败的登录尝试"""
key = f"login_attempts:{ip}" key = f"login_attempts:{ip}"
pipe = redis_client.pipeline() pipe = redis_client.pipeline()
pipe.incr(key) pipe.incr(key)
pipe.expire(key, 1800) # 30分钟后重置 pipe.expire(key, 1800) # 30分钟后重置
pipe.execute() pipe.execute()
def clear_login_attempts(ip): def clear_login_attempts(ip):
"""清除登录尝试记录""" """清除登录尝试记录"""
redis_client.delete(f"login_attempts:{ip}") redis_client.delete(f"login_attempts:{ip}")
def set_secure_headers(response): def set_secure_headers(response):
"""设置安全响应头""" """设置安全响应头"""
response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN' response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block' response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Content-Security-Policy'] = "default-src 'self'" response.headers['Content-Security-Policy'] = "default-src 'self'"
return response return response
def login_required(f): def login_required(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if 'username' not in session: if 'username' not in session:
return redirect('/user/login') return redirect('/user/login')
# 验证会话完整性 # 验证会话完整性
if 'client_info' not in session or 'session_id' not in session: if 'client_info' not in session or 'session_id' not in session:
session.clear() session.clear()
return redirect('/user/login') return redirect('/user/login')
# 验证客户端信息 # 验证客户端信息
current_client = get_client_info() current_client = get_client_info()
stored_client = session['client_info'] stored_client = session['client_info']
if (current_client['ip'] != stored_client['ip'] or if (current_client['ip'] != stored_client['ip'] or
current_client['user_agent'] != stored_client['user_agent']): current_client['user_agent'] != stored_client['user_agent']):
session.clear() session.clear()
return redirect('/user/login') return redirect('/user/login')
# 验证会话ID # 验证会话ID
stored_session_id = redis_client.get(f"session:{session['username']}") stored_session_id = redis_client.get(f"session:{session['username']}")
if not stored_session_id or stored_session_id != session['session_id']: if not stored_session_id or stored_session_id != session['session_id']:
session.clear() session.clear()
return redirect('/user/login') return redirect('/user/login')
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated_function return decorated_function
def hash_password(password: str) -> str: def hash_password(password: str) -> str:
""" """
使用Argon2id算法哈希密码 使用Argon2id算法哈希密码
:param password: 用户输入的密码 :param password: 用户输入的密码
:return: 哈希后的密码 :return: 哈希后的密码
""" """
return ph.hash(password) return ph.hash(password)
def verify_password(stored_hash: str, password: str) -> bool: def verify_password(stored_hash: str, password: str) -> bool:
""" """
验证密码 验证密码
:param stored_hash: 存储的密码哈希 :param stored_hash: 存储的密码哈希
:param password: 用户输入的密码 :param password: 用户输入的密码
:return: 是否匹配 :return: 是否匹配
""" """
try: try:
return ph.verify(stored_hash, password) return ph.verify(stored_hash, password)
except VerifyMismatchError: except VerifyMismatchError:
return False return False
def validate_password(password: str) -> bool: def validate_password(password: str) -> bool:
""" """
验证密码强度 验证密码强度
""" """
if len(password) < 12: # 增加最小长度要求 if len(password) < 12: # 增加最小长度要求
return False return False
if not re.search(r"[A-Z]", password): if not re.search(r"[A-Z]", password):
return False return False
if not re.search(r"[a-z]", password): if not re.search(r"[a-z]", password):
return False return False
if not re.search(r"\d", password): if not re.search(r"\d", password):
return False return False
if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password): if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
return False return False
# 检查常见密码模式 # 检查常见密码模式
common_patterns = ['password', '123456', 'qwerty'] common_patterns = ['password', '123456', 'qwerty']
if any(pattern in password.lower() for pattern in common_patterns): if any(pattern in password.lower() for pattern in common_patterns):
return False return False
return True return True
@ub.route('/login', methods=['GET', 'POST']) @ub.route('/login', methods=['GET', 'POST'])
@limiter.limit("5 per minute") @limiter.limit("5 per minute")
def login(): def login():
"""处理用户登录请求""" """处理用户登录请求"""
if request.method == 'GET': if request.method == 'GET':
response = make_response(render_template('login_and_register.html')) response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response) return set_secure_headers(response)
try: try:
if request.method == 'POST' and not validate_csrf_token(): if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败") logging.warning("CSRF验证失败")
return errorResponse('无效的请求') return errorResponse('无效的请求')
client_ip = request.remote_addr client_ip = request.remote_addr
if is_suspicious_ip(client_ip): if is_suspicious_ip(client_ip):
logging.warning(f"可疑IP尝试登录: {client_ip}") logging.warning(f"可疑IP尝试登录: {client_ip}")
return errorResponse('由于多次失败尝试,请30分钟后再试') return errorResponse('由于多次失败尝试,请30分钟后再试')
username = sanitize_input(request.form.get('username')) username = sanitize_input(request.form.get('username'))
password = request.form.get('password') # 密码不需要sanitize password = request.form.get('password') # 密码不需要sanitize
if not username or not password: if not username or not password:
logging.warning("登录失败:用户名或密码为空") logging.warning("登录失败:用户名或密码为空")
return errorResponse('用户名和密码不能为空') return errorResponse('用户名和密码不能为空')
# 查询用户信息 # 查询用户信息
sql = "SELECT password, status FROM user WHERE username = %s" sql = "SELECT password, status FROM user WHERE username = %s"
result = query(sql, [username], "select") result = query(sql, [username], "select")
if result: if result:
stored_password = result[0]['password'] stored_password = result[0]['password']
status = result[0]['status'] status = result[0]['status']
if status != 'active': if status != 'active':
logging.warning(f"已禁用的账户尝试登录: {username}") logging.warning(f"已禁用的账户尝试登录: {username}")
return errorResponse('账户已被禁用') return errorResponse('账户已被禁用')
if verify_password(stored_password, password): if verify_password(stored_password, password):
session.clear() session.clear()
session.regenerate() session.regenerate()
# 生成唯一会话ID # 生成唯一会话ID
session_id = secrets.token_hex(32) session_id = secrets.token_hex(32)
client_info = get_client_info() client_info = get_client_info()
# 存储会话信息 # 存储会话信息
session['username'] = username session['username'] = username
session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
session['csrf_token'] = secrets.token_hex(32) session['csrf_token'] = secrets.token_hex(32)
session['client_info'] = client_info session['client_info'] = client_info
session['session_id'] = session_id session['session_id'] = session_id
session.permanent = True session.permanent = True
current_app.permanent_session_lifetime = timedelta(hours=2) current_app.permanent_session_lifetime = timedelta(hours=2)
# 在Redis中存储会话ID # 在Redis中存储会话ID
redis_client.setex( redis_client.setex(
f"session:{username}", f"session:{username}",
int(current_app.permanent_session_lifetime.total_seconds()), int(current_app.permanent_session_lifetime.total_seconds()),
session_id session_id
) )
clear_login_attempts(client_ip) clear_login_attempts(client_ip)
# 记录登录历史 # 记录登录历史
login_history_sql = ''' login_history_sql = '''
INSERT INTO login_history INSERT INTO login_history
(username, login_time, ip_address, user_agent, success, attempt_count) (username, login_time, ip_address, user_agent, success, attempt_count)
VALUES (%s, %s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s, %s)
''' '''
query(login_history_sql, [ query(login_history_sql, [
username, username,
datetime.now(), datetime.now(),
client_info['ip'], client_info['ip'],
client_info['user_agent'], client_info['user_agent'],
True, True,
redis_client.get(f"login_attempts:{client_ip}") or 0 redis_client.get(f"login_attempts:{client_ip}") or 0
]) ])
logging.info(f"用户 {username} 登录成功") logging.info(f"用户 {username} 登录成功")
response = make_response(redirect('/page/home')) response = make_response(redirect('/page/home'))
return set_secure_headers(response) return set_secure_headers(response)
record_failed_attempt(client_ip) record_failed_attempt(client_ip)
logging.warning(f"登录失败:用户名或密码错误") logging.warning(f"登录失败:用户名或密码错误")
return errorResponse('用户名或密码错误') return errorResponse('用户名或密码错误')
except Exception as e: except Exception as e:
logging.error(f"登录过程发生错误: {e}") logging.error(f"登录过程发生错误: {e}")
return errorResponse('登录失败,请稍后重试') return errorResponse('登录失败,请稍后重试')
@ub.route('/register', methods=['GET', 'POST']) @ub.route('/register', methods=['GET', 'POST'])
@limiter.limit("3 per hour") @limiter.limit("3 per hour")
def register(): def register():
if request.method == 'GET': if request.method == 'GET':
response = make_response(render_template('login_and_register.html')) response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response) return set_secure_headers(response)
try: try:
if request.method == 'POST' and not validate_csrf_token(): if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败") logging.warning("CSRF验证失败")
return errorResponse('无效的请求') return errorResponse('无效的请求')
username = sanitize_input(request.form.get('username')) username = sanitize_input(request.form.get('username'))
password = request.form.get('password') password = request.form.get('password')
email = sanitize_input(request.form.get('email')) email = sanitize_input(request.form.get('email'))
if not username or not password or not email: if not username or not password or not email:
return errorResponse('用户名、密码和邮箱不能为空') return errorResponse('用户名、密码和邮箱不能为空')
# 验证用户名格式 # 验证用户名格式
if not re.match(r'^[a-zA-Z0-9_]{4,20}$', username): if not re.match(r'^[a-zA-Z0-9_]{4,20}$', username):
return errorResponse('用户名只能包含字母、数字和下划线,长度4-20位') return errorResponse('用户名只能包含字母、数字和下划线,长度4-20位')
# 验证邮箱格式 # 验证邮箱格式
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email): if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
return errorResponse('邮箱格式不正确') return errorResponse('邮箱格式不正确')
# 验证密码强度 # 验证密码强度
if not validate_password(password): if not validate_password(password):
return errorResponse('密码必须包含大小写字母、数字和特殊字符,且长度至少12位') return errorResponse('密码必须包含大小写字母、数字和特殊字符,且长度至少12位')
try: try:
# 检查用户名和邮箱是否存在 # 检查用户名和邮箱是否存在
check_sql = """ check_sql = """
SELECT SELECT
(SELECT COUNT(*) FROM user WHERE LOWER(username) = LOWER(%s)) as username_count, (SELECT COUNT(*) FROM user WHERE LOWER(username) = LOWER(%s)) as username_count,
(SELECT COUNT(*) FROM user WHERE LOWER(email) = LOWER(%s)) as email_count (SELECT COUNT(*) FROM user WHERE LOWER(email) = LOWER(%s)) as email_count
""" """
result = query(check_sql, [username.lower(), email.lower()], "select") result = query(check_sql, [username.lower(), email.lower()], "select")
if result[0]['username_count'] > 0: if result[0]['username_count'] > 0:
return errorResponse('该用户名已被注册') return errorResponse('该用户名已被注册')
if result[0]['email_count'] > 0: if result[0]['email_count'] > 0:
return errorResponse('该邮箱已被注册') return errorResponse('该邮箱已被注册')
# 哈希密码 # 哈希密码
hashed_password = hash_password(password) hashed_password = hash_password(password)
# 插入新用户 # 插入新用户
insert_sql = ''' insert_sql = '''
INSERT INTO user(username, password, email, status, createTime, last_password_change) INSERT INTO user(username, password, email, status, createTime, last_password_change)
VALUES(%s, %s, %s, %s, %s, %s) VALUES(%s, %s, %s, %s, %s, %s)
''' '''
current_time = datetime.now() current_time = datetime.now()
query(insert_sql, [ query(insert_sql, [
username, username,
hashed_password, hashed_password,
email, email,
'active', 'active',
current_time, current_time,
current_time current_time
]) ])
# 记录注册信息 # 记录注册信息
client_info = get_client_info() client_info = get_client_info()
register_history_sql = ''' register_history_sql = '''
INSERT INTO register_history INSERT INTO register_history
(username, register_time, ip_address, user_agent, email) (username, register_time, ip_address, user_agent, email)
VALUES (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)
''' '''
query(register_history_sql, [ query(register_history_sql, [
username, username,
current_time, current_time,
client_info['ip'], client_info['ip'],
client_info['user_agent'], client_info['user_agent'],
email email
]) ])
logging.info(f"新用户注册成功: {username}") logging.info(f"新用户注册成功: {username}")
response = make_response(redirect('/user/login')) response = make_response(redirect('/user/login'))
return set_secure_headers(response) return set_secure_headers(response)
except Exception as e: except Exception as e:
logging.error(f"注册过程发生错误: {e}") logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试') return errorResponse('注册失败,请稍后重试')
except Exception as e: except Exception as e:
logging.error(f"注册过程发生错误: {e}") logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试') return errorResponse('注册失败,请稍后重试')
@ub.route('/logout') @ub.route('/logout')
@login_required @login_required
def logout(): def logout():
"""用户登出""" """用户登出"""
try: try:
username = session.get('username') username = session.get('username')
client_info = session.get('client_info', {}) client_info = session.get('client_info', {})
# 记录登出历史 # 记录登出历史
logout_history_sql = ''' logout_history_sql = '''
INSERT INTO logout_history INSERT INTO logout_history
(username, logout_time, ip_address, user_agent, session_id) (username, logout_time, ip_address, user_agent, session_id)
VALUES (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)
''' '''
query(logout_history_sql, [ query(logout_history_sql, [
username, username,
datetime.now(), datetime.now(),
client_info.get('ip'), client_info.get('ip'),
client_info.get('user_agent'), client_info.get('user_agent'),
session.get('session_id') session.get('session_id')
]) ])
# 删除Redis中的会话 # 删除Redis中的会话
redis_client.delete(f"session:{username}") redis_client.delete(f"session:{username}")
session.clear() session.clear()
logging.info(f"用户 {username} 成功登出") logging.info(f"用户 {username} 成功登出")
response = make_response(redirect('/user/login')) response = make_response(redirect('/user/login'))
return set_secure_headers(response) return set_secure_headers(response)
except Exception as e: except Exception as e:
logging.error(f"登出过程发生错误: {e}") logging.error(f"登出过程发生错误: {e}")
response = make_response(redirect('/user/login')) response = make_response(redirect('/user/login'))
return set_secure_headers(response) return set_secure_headers(response)