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
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' %}
{% block title %}
文章分析
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">文章分析页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block card-stretch card-height">
<div class="card-body">
<div class="form-group">
<label>类型选择</label>
<select onchange="typeChange(event)" class="form-control mb-3">
{% for i in typeList %}
{% if defaultType == i %}
<option selected value="{{ i }}">{{ i }}</option>
{% else %}
<option value="{{ i }}">{{ i }}</option>
{% endif %}
{% endfor %}
</select>
<script>
function typeChange(e){
window.location.href = 'http://127.0.0.1:5000/page/articleChar?type=' + e.target.value
}
</script>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章点赞量分析 👍</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章评论量分析 🔥</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章转发量分析 🥇</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var colors = ['#66cc99','#ffcc66','#ff6666','#6699cc']
option = {
title: {
text: '点赞区间统计'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['区间个数']
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
xAxis: [
{
type: 'category',
// prettier-ignore
data:{{ xData |tojson }}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '区间个数',
type: 'bar',
data: {{ yData }},
itemStyle:{
color:function(params){
return colors[params.dataIndex % colors.length];
}
},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
title: {
text: '文章评论区间统计'
},
tooltip: {
trigger: 'axis'
},
legend: {
},
xAxis: {
type: 'category',
data: {{ x1Data | tojson }}
},
yAxis: {
type: 'value'
},
series: [
{
name:"区间个数",
data: {{ y1Data }},
type: 'line',
symbol: 'triangle',
symbolSize: 20,
lineStyle: {
color: '#5470C6',
width: 4,
type: 'dashed'
},
itemStyle: {
borderWidth: 3,
borderColor: '#EE6666',
color: 'yellow'
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '转发量分析区间图'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{ x2Data | tojson }}
},
series: [
{
name: '区间个数',
type: 'bar',
data: {{ y2Data }}
}
]
};
option && myChart.setOption(option);
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="articleChar">文章分析</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="articleCharPage">文章分析页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block card-stretch card-height">
<div class="card-body">
<div class="form-group">
<label data-i18n="typeSelection">类型选择</label>
<select onchange="typeChange(event)" class="form-control mb-3">
{% for i in typeList %}
{% if defaultType == i %}
<option selected value="{{ i }}">{{ i }}</option>
{% else %}
<option value="{{ i }}">{{ i }}</option>
{% endif %}
{% endfor %}
</select>
<script>
function typeChange(e){
window.location.href = 'http://127.0.0.1:5000/page/articleChar?type=' + e.target.value
}
</script>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleLikeAnalysis">文章点赞量分析 👍</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleCommentAnalysis">文章评论量分析 🔥</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleForwardAnalysis">文章转发量分析 🥇</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var colors = ['#66cc99','#ffcc66','#ff6666','#6699cc']
option = {
title: {
text: t('likeRangeStatistics')
},
tooltip: {
trigger: 'axis'
},
legend: {
data: [t('rangeCount')]
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
xAxis: [
{
type: 'category',
// prettier-ignore
data:{{ xData |tojson }}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '区间个数',
type: 'bar',
data: {{ yData }},
itemStyle:{
color:function(params){
return colors[params.dataIndex % colors.length];
}
},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
title: {
text: '文章评论区间统计'
},
tooltip: {
trigger: 'axis'
},
legend: {
},
xAxis: {
type: 'category',
data: {{ x1Data | tojson }}
},
yAxis: {
type: 'value'
},
series: [
{
name:"区间个数",
data: {{ y1Data }},
type: 'line',
symbol: 'triangle',
symbolSize: 20,
lineStyle: {
color: '#5470C6',
width: 4,
type: 'dashed'
},
itemStyle: {
borderWidth: 3,
borderColor: '#EE6666',
color: 'yellow'
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '转发量分析区间图'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{ x2Data | tojson }}
},
series: [
{
name: '区间个数',
type: 'bar',
data: {{ y2Data }}
}
]
};
option && myChart.setOption(option);
</script>
{% endblock %}
+143 -143
View File
@@ -1,144 +1,144 @@
{% extends 'base_page.html' %}
{% block title %}
文章内容词云图
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">文章分析页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章内容词云图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 750px;text-align: center">
<img style="width:60%" src="/static/contentCloud.jpg" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="articleCloud">文章内容词云图</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="articleCharPage">文章分析页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleCloud">文章内容词云图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 750px;text-align: center">
<img style="width:60%" src="/static/contentCloud.jpg" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
</script>
{% endblock %}
+504 -477
View File
@@ -1,478 +1,505 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}首页{% endblock %}</title>
<link rel="stylesheet" href="/static/css/backend-plugin.min.css">
<link rel="stylesheet" href="/static/css/backend.css">
<meta name="referrer" content="no-referrer" />
</head>
<body class=" ">
<!-- loader Start -->
<div id="loading">
<div id="loading-center">
</div>
</div>
<!-- loader END -->
<!-- Wrapper Start -->
<div class="wrapper">
<div class="iq-sidebar sidebar-default ">
<div class="iq-sidebar-logo d-flex align-items-end justify-content-between">
<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">
<span>微博舆情分析系统</span>
</a>
<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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
</div>
<div class="data-scrollbar" data-scroll="1">
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
<div class="pt-5 pb-5"></div>
</div>
</div>
<div class="content-page">
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">首页</h4>
</div>
</div>
<div class="col-lg-8 col-md-12">
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary">文章个数</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">{{ articleLenMax }}个</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary">文章爬取规则</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">每 5 小时更新一次爬取内容</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary">下次爬取时间</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">7-5-18:00</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center flex-wrap">
<h4 class="font-weight-bold">文章发布时间个数</h4>
</div>
<div id="main" style="width:100%;height: 350px" class="custom-chart"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-8">
<div class="card card-block card-stretch card-height">
<div class="card-header card-header-border d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">评论点赞量 Top Fore</h4>
</div>
</div>
<div class="card-body-list">
<ul class="list-style-3 mb-0">
{% for i in commentsLikeCountTopFore %}
<li class="p-3 list-item d-flex justify-content-start align-items-center">
<div class="avatar">
<p>
🧑‍ {{ i[5] }}
</p>
<p class="mb-0" style="color:#ccc;width:320px;overflow: hidden;text-overflow: ellipsis;white-space:nowrap">
{{ i[4] }}
</p>
</div>
<div class="list-style-action d-flex justify-content-end ml-auto">
<h6 class="font-weight-bold text-danger">👍 {{ i[2] }}</h6>
</div>
</li>
{% endfor %}
<div class="d-flex justify-content-end align-items-center border-top-table p-3">
<a href="/page/tableData" class="btn btn-secondary btn-sm">查看全部</a>
</div>
</ul>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">文章类型占比</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainTwo" style="width:100%;height:350px">
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">评论用户名词云图</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainTwo" style="width:100%;height:350px;text-align: center">
<img style="width:85%" src="/static/authorNameCloud.jpg" alt="">
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">评论用户时间占比</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainThree" style="width:100%;height:350px">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
</div>
</div>
<!-- Wrapper End-->
<footer class="iq-footer">
<div class="container-fluid">
<div class="row">
<div class="col-lg-6">
</div>
<div class="col-lg-6 text-right">
<span class="mr-1">网安小学期 &copy; 2024.<a target="_blank" href="#">郭航江</a></span>
</div>
</div>
</div>
</footer>
<script src="/static/echarts.min.js"></script>
<script src="/static/china.js"></script>
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myCharts = echarts.init(chartDom);
var option;
var xData = {{ xData | tojson }};
var yData = {{ yData }};
var xRes = []
var yRes = []
for(var i = 0;i < 8;i++){
xRes.push(xData[i])
yRes.push(yData[i])
}
option = {
tooltip:{
trigger:"axis"
},
legend:{},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: { readOnly: false },
magicType: { type: ['line', 'bar'] },
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xRes
},
yAxis: {
type: 'value'
},
series: [
{
name:"日期个数",
data: yRes,
type: 'bar',
areaStyle: {
color:"rgba(0,128,255,0.2)"
},
smooth:true,
lineStyle:{
width:5
},
emphasis:{
focus:'series'
}
}
]
};
let count = 8;
setInterval(()=>{
if(count >= xData.length) count=0
xRes.shift()
xRes.push(xData[count])
yRes.shift()
yRes.push(yData[count])
count++
myCharts.setOption({
xAxis:[{
data:xRes
}],
series:[{
data:yRes
}]
})
},2000)
option && myCharts.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '各微博类型占比饼状图',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left',
padding:[10,20,30,20],
},
series: [
{
name: '博客类型占比',
type: 'pie',
radius: '50%',
data: {{ typeChart |tojson }},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left:10,
right:10,
type: 'scroll',
},
series: [
{
name: '评论时间发布个数',
type: 'pie',
radius: ['60%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 20,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: {{ createAtChart | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
{% 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;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}首页{% endblock %}</title>
<link rel="stylesheet" href="/static/css/backend-plugin.min.css">
<link rel="stylesheet" href="/static/css/backend.css">
<meta name="referrer" content="no-referrer" />
<script src="/static/js/i18n.js"></script>
</head>
<body class=" ">
<!-- loader Start -->
<div id="loading">
<div id="loading-center">
</div>
</div>
<!-- loader END -->
<!-- Wrapper Start -->
<div class="wrapper">
<div class="iq-sidebar sidebar-default ">
<div class="iq-sidebar-logo d-flex align-items-end justify-content-between">
<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">
<span data-i18n="weiboSystem">微博舆情分析系统</span>
</a>
<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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
</div>
<div class="data-scrollbar" data-scroll="1">
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2" data-i18n="ipChar">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2" data-i18n="commentChar">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="yuqingChar">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="yuqingpredict">舆情预测</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
<li class="sidebar-layout">
<a href="javascript:void(0)" id="language-switcher" class="svg-icon">
<i class="">
<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 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>
</svg>
</i>
<span class="ml-2" id="language-text">切换语言</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
<div class="pt-5 pb-5"></div>
</div>
</div>
<div class="content-page">
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="home">首页</h4>
</div>
</div>
<div class="col-lg-8 col-md-12">
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary" data-i18n="articleCount">文章个数</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">{{ articleLenMax }}个</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary" data-i18n="articleCrawlRule">文章爬取规则</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">每 5 小时更新一次爬取内容</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary" data-i18n="nextCrawlTime">下次爬取时间</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">7-5-18:00</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center flex-wrap">
<h4 class="font-weight-bold" data-i18n="articlePublishTimeCount">文章发布时间个数</h4>
</div>
<div id="main" style="width:100%;height: 350px" class="custom-chart"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-8">
<div class="card card-block card-stretch card-height">
<div class="card-header card-header-border d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="commentLikeCountTopFore">评论点赞量 Top Fore</h4>
</div>
</div>
<div class="card-body-list">
<ul class="list-style-3 mb-0">
{% for i in commentsLikeCountTopFore %}
<li class="p-3 list-item d-flex justify-content-start align-items-center">
<div class="avatar">
<p>
🧑‍ {{ i[5] }}
</p>
<p class="mb-0" style="color:#ccc;width:320px;overflow: hidden;text-overflow: ellipsis;white-space:nowrap">
{{ i[4] }}
</p>
</div>
<div class="list-style-action d-flex justify-content-end ml-auto">
<h6 class="font-weight-bold text-danger">👍 {{ i[2] }}</h6>
</div>
</li>
{% endfor %}
<div class="d-flex justify-content-end align-items-center border-top-table p-3">
<a href="/page/tableData" class="btn btn-secondary btn-sm" data-i18n="viewAll">查看全部</a>
</div>
</ul>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="articleTypeRatio">文章类型占比</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainTwo" style="width:100%;height:350px">
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="commentUserWordCloud">评论用户名词云图</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainTwo" style="width:100%;height:350px;text-align: center">
<img style="width:85%" src="/static/authorNameCloud.jpg" alt="">
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="commentUserTimeRatio">评论用户时间占比</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainThree" style="width:100%;height:350px">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
</div>
</div>
<!-- Wrapper End-->
<footer class="iq-footer">
<div class="container-fluid">
<div class="row">
<div class="col-lg-6">
</div>
<div class="col-lg-6 text-right">
<span class="mr-1"><span data-i18n="semester">网安小学期</span> &copy; 2024.<a target="_blank" href="#">郭航江</a></span>
</div>
</div>
</div>
</footer>
<script src="/static/echarts.min.js"></script>
<script src="/static/china.js"></script>
<!-- 添加语言切换脚本 -->
<script>
// 页面加载完成后初始化语言切换功能
document.addEventListener('DOMContentLoaded', function() {
// 更新语言切换按钮文本
updateLanguageSwitcherText();
});
// 更新语言切换按钮文本
function updateLanguageSwitcherText() {
const currentLang = getCurrentLanguage();
const langText = document.getElementById('language-text');
if (langText) {
langText.textContent = currentLang === 'zh' ? '切换到英文' : 'Switch to Chinese';
}
}
</script>
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myCharts = echarts.init(chartDom);
var option;
var xData = {{ xData | tojson }};
var yData = {{ yData }};
var xRes = []
var yRes = []
for(var i = 0;i < 8;i++){
xRes.push(xData[i])
yRes.push(yData[i])
}
option = {
tooltip:{
trigger:"axis"
},
legend:{},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: { readOnly: false },
magicType: { type: ['line', 'bar'] },
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xRes
},
yAxis: {
type: 'value'
},
series: [
{
name:"日期个数",
data: yRes,
type: 'bar',
areaStyle: {
color:"rgba(0,128,255,0.2)"
},
smooth:true,
lineStyle:{
width:5
},
emphasis:{
focus:'series'
}
}
]
};
let count = 8;
setInterval(()=>{
if(count >= xData.length) count=0
xRes.shift()
xRes.push(xData[count])
yRes.shift()
yRes.push(yData[count])
count++
myCharts.setOption({
xAxis:[{
data:xRes
}],
series:[{
data:yRes
}]
})
},2000)
option && myCharts.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '各微博类型占比饼状图',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left',
padding:[10,20,30,20],
},
series: [
{
name: '博客类型占比',
type: 'pie',
radius: '50%',
data: {{ typeChart |tojson }},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left:10,
right:10,
type: 'scroll',
},
series: [
{
name: '评论时间发布个数',
type: 'pie',
radius: ['60%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 20,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: {{ createAtChart | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
{% 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>
+316 -316
View File
@@ -1,317 +1,317 @@
{% extends 'base_page.html' %}
{% block title %}
评论分析
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">评论分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">评论点赞次数区间图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">评论用户性别占比</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">用户评论词云图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px;text-align: center">
<img style="width:84%" src="/static/commentCloud.jpg" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '评论点赞量区间折线图',
left: '1%'
},
tooltip: {
trigger: 'axis'
},
grid: {
left: '5%',
right: '15%',
bottom: '10%'
},
legend:{},
xAxis: {
data: {{ xData | tojson }}
},
yAxis: {},
toolbox: {
right: 10,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [
{
show: true,
start: 80,
end: 100
},
{
type: 'inside',
start: 80,
end: 100
}
],
visualMap: {
top: 50,
right: 10,
pieces: [
{
gt: 0,
lte: 50,
color: '#93CE07'
},
{
gt: 50,
lte: 100,
color: '#FBDB0F'
},
{
gt: 100,
lte: 150,
color: '#FC7D02'
},
{
gt: 150,
lte: 200,
color: '#FD0100'
},
{
gt: 200,
lte: 300,
color: '#AA069F'
},
{
gt: 300,
color: '#AC3B2A'
}
],
outOfRange: {
color: '#999'
}
},
series: {
name: '点赞个数',
type: 'line',
data: {{ yData }},
markLine: {
silent: true,
lineStyle: {
color: '#333'
},
data: [
{
yAxis: 50
},
{
yAxis: 100
},
{
yAxis: 150
},
{
yAxis: 200
},
{
yAxis: 300
}
]
}
}
}
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '评论性别占比饼图',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '性别个数',
type: 'pie',
radius: '50%',
data: {{ genderPieData | tojson }},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && myChart.setOption(option);
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="commentChar">评论分析</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="commentChar">评论分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="commentLikeRangeChart">评论点赞次数区间图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="commentUserGenderRatio">评论用户性别占比</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="userCommentWordCloud">用户评论词云图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px;text-align: center">
<img style="width:84%" src="/static/commentCloud.jpg" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '评论点赞量区间折线图',
left: '1%'
},
tooltip: {
trigger: 'axis'
},
grid: {
left: '5%',
right: '15%',
bottom: '10%'
},
legend:{},
xAxis: {
data: {{ xData | tojson }}
},
yAxis: {},
toolbox: {
right: 10,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [
{
show: true,
start: 80,
end: 100
},
{
type: 'inside',
start: 80,
end: 100
}
],
visualMap: {
top: 50,
right: 10,
pieces: [
{
gt: 0,
lte: 50,
color: '#93CE07'
},
{
gt: 50,
lte: 100,
color: '#FBDB0F'
},
{
gt: 100,
lte: 150,
color: '#FC7D02'
},
{
gt: 150,
lte: 200,
color: '#FD0100'
},
{
gt: 200,
lte: 300,
color: '#AA069F'
},
{
gt: 300,
color: '#AC3B2A'
}
],
outOfRange: {
color: '#999'
}
},
series: {
name: '点赞个数',
type: 'line',
data: {{ yData }},
markLine: {
silent: true,
lineStyle: {
color: '#333'
},
data: [
{
yAxis: 50
},
{
yAxis: 100
},
{
yAxis: 150
},
{
yAxis: 200
},
{
yAxis: 300
}
]
}
}
}
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '评论性别占比饼图',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '性别个数',
type: 'pie',
radius: '50%',
data: {{ genderPieData | tojson }},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && myChart.setOption(option);
</script>
{% endblock %}
File diff suppressed because it is too large Load Diff
+30 -27
View File
@@ -1,27 +1,30 @@
<!--
_oo0oo_
o8888888o
88" . "88
(| -_- |)
0\ = /0
___/`---'\___
.' \\| |// '.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' |_/ |
\ .-\__ '-' ___/-. /
___'. .' /--.--\ `. .'___
."" '< `.___\_<|>_/___.' >' "".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `_. \_ __\ /__ _/ .-` / /
=====`-.____`.___ \_____/___.-`___.-'=====
`=---='
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
佛祖保佑 代码永无BUG
-->
{% extends 'base_page.html' %}
<!--
_oo0oo_
o8888888o
88" . "88
(| -_- |)
0\ = /0
___/`---'\___
.' \\| |// '.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' |_/ |
\ .-\__ '-' ___/-. /
___'. .' /--.--\ `. .'___
."" '< `.___\_<|>_/___.' >' "".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `_. \_ __\ /__ _/ .-` / /
=====`-.____`.___ \_____/___.-`___.-'=====
`=---='
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
佛祖保佑 代码永无BUG
-->
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="home">首页</span>
{% endblock %}
+274 -274
View File
@@ -1,275 +1,275 @@
{% extends 'base_page.html' %}
{% block title %}
IP分析
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">IP分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章IP位置分析图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 750px"></div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">评论IP位置分析图</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 750px"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
title:{
text:'文章IP发布地址地图',
left:'center',
textStyle:{
color:"#333",
fontWeight:"bold"
}
},
tooltip:{
trigger:'item',
formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个'
}
},
visualMap:{
min:0,
max:50,
text:['高','低'],
realtime:true,
calulable:true,
inRange:{
color:['orange','green']
}
},
series:[{
type:'map',
map:"china",
label:{
normal:{
show:true,
color:"white",
fontSize:'12'
}
},
emphasis:{
label:{
show:true
},
},
data:{{ articleRegionData | tojson }},
itemStyle:{
normal: {
areaColor:"skyblue",
borderColor:"#fff"
},
emphasis:{
areaColor: '#2b91b7'
}
},
zoom:1.4,
roam:true
}]
}
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title:{
text:'文章IP发布地址地图',
left:'center',
textStyle:{
color:"#333",
fontWeight:"bold"
}
},
tooltip:{
trigger:'item',
formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个'
}
},
visualMap:{
min:0,
max:200,
text:['高','低'],
realtime:true,
calulable:true,
inRange:{
color:['orange','green']
}
},
series:[{
type:'map',
map:"china",
label:{
normal:{
show:true,
color:"white",
fontSize:'12'
}
},
emphasis:{
label:{
show:true
},
},
data:{{ commentRegionData | tojson }},
itemStyle:{
normal: {
areaColor:"skyblue",
borderColor:"#fff"
},
emphasis:{
areaColor: '#2b91b7'
}
},
zoom:1.4,
roam:true
}]
}
option && myChart.setOption(option);
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="ipChar">IP分析</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="ipChar">IP分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleIpLocationAnalysis">文章IP位置分析图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 750px"></div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="commentIpLocationAnalysis">评论IP位置分析图</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 750px"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
title:{
text:'文章IP发布地址地图',
left:'center',
textStyle:{
color:"#333",
fontWeight:"bold"
}
},
tooltip:{
trigger:'item',
formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个'
}
},
visualMap:{
min:0,
max:50,
text:['高','低'],
realtime:true,
calulable:true,
inRange:{
color:['orange','green']
}
},
series:[{
type:'map',
map:"china",
label:{
normal:{
show:true,
color:"white",
fontSize:'12'
}
},
emphasis:{
label:{
show:true
},
},
data:{{ articleRegionData | tojson }},
itemStyle:{
normal: {
areaColor:"skyblue",
borderColor:"#fff"
},
emphasis:{
areaColor: '#2b91b7'
}
},
zoom:1.4,
roam:true
}]
}
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title:{
text:'文章IP发布地址地图',
left:'center',
textStyle:{
color:"#333",
fontWeight:"bold"
}
},
tooltip:{
trigger:'item',
formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个'
}
},
visualMap:{
min:0,
max:200,
text:['高','低'],
realtime:true,
calulable:true,
inRange:{
color:['orange','green']
}
},
series:[{
type:'map',
map:"china",
label:{
normal:{
show:true,
color:"white",
fontSize:'12'
}
},
emphasis:{
label:{
show:true
},
},
data:{{ commentRegionData | tojson }},
itemStyle:{
normal: {
areaColor:"skyblue",
borderColor:"#fff"
},
emphasis:{
areaColor: '#2b91b7'
}
},
zoom:1.4,
roam:true
}]
}
option && myChart.setOption(option);
</script>
{% endblock %}
+227 -224
View File
@@ -1,225 +1,228 @@
{% extends 'base_page.html' %}
{% block title %}
微博舆情统计页
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">微博舆情统计页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">微博文章统计表格 - 舆情 情感分类
</h4>
</div>
<div class="header-action">
<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">
<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>
</i>
</div>
</div>
<div class="card-body">
<p><a href="/page/tableData?flag=True" class="btn btn-primary btn-sm">情感分类</a></p>
<div class="table-responsive">
<div id="datatable-1_wrapper" class="dataTables_wrapper dt-bootstrap4">
<div class="row">
<table id="datatable-1" class="table data-table table-striped table-bordered dataTable" role="grid" aria-describedby="datatable-1_info">
<thead>
<tr>
<th style="font-weight: bold">
文章ID
</th>
<th style="font-weight: bold">
文章IP
</th>
<th style="font-weight: bold">
点赞量
</th>
<th style="font-weight: bold">
转发量
</th>
<th style="font-weight: bold">
评论量
</th>
<th style="font-weight: bold">
类型
</th>
<th style="font-weight: bold">
内容
</th>
<th style="font-weight: bold">
发布时间
</th>
{% if defaultFlag %}
<th style="font-weight: bold">
情感分类
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for article in tableData %}
<tr class="even">
<td style="width:330px" class="sorting_1">
<a href="{{ article[9] }}">
{{ article[0] }}
</a>
</td>
<td style="width:90px">{{ article[4] }}</td>
<td style="width:90px">👍{{ article[1] }}</td>
<td style="width:90px">🥇{{ article[2] }}</td>
<td style="width:90px">🔥{{ article[3] }}</td>
<td style="width:90px" class="text-right">{{ article[8] }}</td>
<td style="width:155px" class="text-right">{{ article[5] }}</td>
<td style="width:90px" class="text-right">{{ article[7] }}</td>
{% if defaultFlag %}
<td style="width:90px">
{% if article[-1] == '正面' %}
<span class="text-success">
{{ article[-1] }}
</span>
{% else %}
<span class="text-danger">
{{ article[-1] }}
</span>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="tableData">微博舆情统计页</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="tableData">微博舆情统计页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="weiboArticleStatTable">微博文章统计表格 - 舆情 情感分类</h4>
</div>
<div class="header-action">
<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">
<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>
</i>
</div>
</div>
<div class="card-body">
<p><a href="/page/tableData?flag=True" class="btn btn-primary btn-sm" data-i18n="sentimentClassification">情感分类</a></p>
<div class="table-responsive">
<div id="datatable-1_wrapper" class="dataTables_wrapper dt-bootstrap4">
<div class="row">
<table id="datatable-1" class="table data-table table-striped table-bordered dataTable" role="grid" aria-describedby="datatable-1_info">
<thead>
<tr>
<th style="font-weight: bold" data-i18n="articleId">
文章ID
</th>
<th style="font-weight: bold" data-i18n="articleIp">
文章IP
</th>
<th style="font-weight: bold" data-i18n="articleTitle">
文章标题
</th>
<th style="font-weight: bold" data-i18n="articleLike">
点赞量
</th>
<th style="font-weight: bold" data-i18n="articleForward">
转发量
</th>
<th style="font-weight: bold" data-i18n="articleComment">
评论量
</th>
<th style="font-weight: bold" data-i18n="articleType">
类型
</th>
<th style="font-weight: bold" data-i18n="articleContent">
内容
</th>
<th style="font-weight: bold" data-i18n="articleTime">
发布时间
</th>
{% if defaultFlag %}
<th style="font-weight: bold" data-i18n="sentimentClassification">
情感分类
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for article in tableData %}
<tr class="even">
<td style="width:330px" class="sorting_1">
<a href="{{ article[9] }}">
{{ article[0] }}
</a>
</td>
<td style="width:90px">{{ article[4] }}</td>
<td style="width:90px" class="text-right">{{ article[5] }}</td>
<td style="width:90px">👍{{ article[1] }}</td>
<td style="width:90px">🥇{{ article[2] }}</td>
<td style="width:90px">🔥{{ article[3] }}</td>
<td style="width:90px" class="text-right">{{ article[8] }}</td>
<td style="width:155px" class="text-right">{{ article[5] }}</td>
<td style="width:90px" class="text-right">{{ article[7] }}</td>
{% if defaultFlag %}
<td style="width:90px">
{% if article[-1] == '正面' %}
<span class="text-success">
{{ article[-1] }}
</span>
{% else %}
<span class="text-danger">
{{ article[-1] }}
</span>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
</script>
{% endblock %}
+400 -400
View File
@@ -1,401 +1,401 @@
{% extends 'base_page.html' %}
{% block title %}
舆情分析
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">舆情分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">热词情感趋势柱状图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">热词情感趋势树形图</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章内容与评论内容舆情趋势饼状图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">热词TOP10</h4>
</div>
</div>
<div class="card-body">
<div id="mainFore" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-12 mb-3">
<div class="form-group">
<label for="modelSelect">选择分析模型:</label>
<select class="form-control" id="modelSelect" onchange="updateModel(this.value)">
<optgroup label="基础模型">
<option value="basic" {% if model_type == 'basic' %}selected{% endif %}>SnowNLP</option>
</optgroup>
<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-4" {% if model_type == 'gpt-4' %}selected{% endif %}>GPT-4</option>
</optgroup>
<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-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>
</optgroup>
<optgroup label="DeepSeek 模型">
<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>
</optgroup>
</select>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var colors = ['#66CC99', '#FFCC66', '#FF6666'];
option = {
title: {
text: '热词情感分析柱状图'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['Rainfall']
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
xAxis: [
{
type: 'category',
// prettier-ignore
data: {{ xData | tojson }}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '舆情个数',
type: 'bar',
data: {{ yData }},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
},
itemStyle: {
color: function (parmas) {
return colors[parmas.dataIndex % colors.length];
}
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
series: [
{
type: 'treemap',
data: {{ biedata | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
data: [
'正面',
'负面',
'中性'
]
},
series: [
{
name: '评论舆情结果',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner',
fontSize: 14
},
labelLine: {
show: false
},
data: {{ biedata1 | tojson }}
},
{
name: '文章舆情结果',
type: 'pie',
radius: ['45%', '60%'],
labelLine: {
length: 30
},
label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}}{c} {per|{d}%} ',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
a: {
color: '#6E7079',
lineHeight: 22,
align: 'center'
},
hr: {
borderColor: '#8C8D8E',
width: '100%',
borderWidth: 1,
height: 0
},
b: {
color: '#4C5058',
fontSize: 14,
fontWeight: 'bold',
lineHeight: 33
},
per: {
color: '#fff',
backgroundColor: '#4C5058',
padding: [3, 4],
borderRadius: 4
}
}
},
data: {{ biedata2 | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainFore');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{ x1Data | tojson }}
},
series: [
{
name: '热词出现个数',
type: 'bar',
data:{{ y1Data }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
function updateModel(value) {
window.location.href = '/page/yuqingChar?model=' + value;
}
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="yuqingChar">舆情分析</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<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>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<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>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<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>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="yuqingChar">舆情分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="hotWordSentimentTrendBar">热词情感趋势柱状图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="hotWordSentimentTrendTree">热词情感趋势树形图</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleCommentSentimentTrendPie">文章内容与评论内容舆情趋势饼状图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">热词TOP10</h4>
</div>
</div>
<div class="card-body">
<div id="mainFore" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-12 mb-3">
<div class="form-group">
<label for="modelSelect">选择分析模型:</label>
<select class="form-control" id="modelSelect" onchange="updateModel(this.value)">
<optgroup label="基础模型">
<option value="basic" {% if model_type == 'basic' %}selected{% endif %}>SnowNLP</option>
</optgroup>
<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-4" {% if model_type == 'gpt-4' %}selected{% endif %}>GPT-4</option>
</optgroup>
<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-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>
</optgroup>
<optgroup label="DeepSeek 模型">
<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>
</optgroup>
</select>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var colors = ['#66CC99', '#FFCC66', '#FF6666'];
option = {
title: {
text: '热词情感分析柱状图'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['Rainfall']
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
xAxis: [
{
type: 'category',
// prettier-ignore
data: {{ xData | tojson }}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '舆情个数',
type: 'bar',
data: {{ yData }},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
},
itemStyle: {
color: function (parmas) {
return colors[parmas.dataIndex % colors.length];
}
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
series: [
{
type: 'treemap',
data: {{ biedata | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
data: [
'正面',
'负面',
'中性'
]
},
series: [
{
name: '评论舆情结果',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner',
fontSize: 14
},
labelLine: {
show: false
},
data: {{ biedata1 | tojson }}
},
{
name: '文章舆情结果',
type: 'pie',
radius: ['45%', '60%'],
labelLine: {
length: 30
},
label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}}{c} {per|{d}%} ',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
a: {
color: '#6E7079',
lineHeight: 22,
align: 'center'
},
hr: {
borderColor: '#8C8D8E',
width: '100%',
borderWidth: 1,
height: 0
},
b: {
color: '#4C5058',
fontSize: 14,
fontWeight: 'bold',
lineHeight: 33
},
per: {
color: '#fff',
backgroundColor: '#4C5058',
padding: [3, 4],
borderRadius: 4
}
}
},
data: {{ biedata2 | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainFore');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{ x1Data | tojson }}
},
series: [
{
name: '热词出现个数',
type: 'bar',
data:{{ y1Data }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
function updateModel(value) {
window.location.href = '/page/yuqingChar?model=' + value;
}
</script>
{% 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
import json
import os
from datetime import datetime
import threading
from queue import Queue
import asyncio
import websockets
import logging
from spider.spiderData import SpiderData
from openai import OpenAI
from anthropic import Anthropic
import aiohttp
from concurrent.futures import ThreadPoolExecutor
from ratelimit import limits, sleep_and_retry
from tenacity import retry, stop_after_attempt, wait_exponential
# 创建蓝图
spider_bp = Blueprint('spider', __name__)
# 创建日志记录器
logger = logging.getLogger('spider_control')
logger.setLevel(logging.INFO)
# 存储WebSocket连接的集合
websocket_connections = set()
# 创建消息队列
message_queue = Queue()
# 创建线程池
thread_pool = ThreadPoolExecutor(max_workers=3)
# 创建异步事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# 默认配置
DEFAULT_CONFIG = {
'crawlDepth': 3,
'interval': 5,
'maxRetries': 3,
'timeout': 30,
'maxConcurrent': 2
}
# 限流装饰器
@sleep_and_retry
@limits(calls=100, period=60) # 每分钟最多100个请求
def rate_limited_request():
pass
class SpiderWorker:
def __init__(self, topics, parameters):
self.topics = topics
self.parameters = parameters
self.total_topics = len(topics)
self.completed_topics = 0
self.spider = SpiderData()
self.message_buffer = []
self.message_buffer_size = 10
self.semaphore = asyncio.Semaphore(parameters.get('maxConcurrent', DEFAULT_CONFIG['maxConcurrent']))
async def send_message(self, message):
"""异步发送消息,使用缓冲区优化"""
self.message_buffer.append(message)
if len(self.message_buffer) >= self.message_buffer_size:
await self.flush_messages()
async def flush_messages(self):
"""刷新消息缓冲区"""
if not self.message_buffer:
return
try:
await broadcast_message(self.message_buffer)
self.message_buffer.clear()
except Exception as e:
logger.error(f"发送消息失败: {e}")
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
async def crawl_single_topic(self, topic):
"""爬取单个话题"""
try:
rate_limited_request()
await self.send_message({
'type': 'log',
'message': f'开始爬取话题: {topic}'
})
async with self.semaphore:
await asyncio.get_event_loop().run_in_executor(
thread_pool,
self.spider.crawl_topic,
topic,
self.parameters['crawlDepth'],
self.parameters['interval'],
self.parameters['maxRetries'],
self.parameters['timeout']
)
self.completed_topics += 1
progress = int((self.completed_topics / self.total_topics) * 100)
await self.send_message({
'type': 'progress',
'value': progress
})
await self.send_message({
'type': 'log',
'message': f'话题 {topic} 爬取完成'
})
except Exception as e:
logger.error(f"爬取话题 {topic} 失败: {e}")
await self.send_message({
'type': 'log',
'message': f'爬取话题 {topic} 时出错: {str(e)}'
})
raise
async def run(self):
"""运行爬虫任务"""
try:
tasks = [self.crawl_single_topic(topic) for topic in self.topics]
await asyncio.gather(*tasks)
await self.flush_messages()
await self.send_message({
'type': 'log',
'message': '所有话题爬取完成'
})
except Exception as e:
logger.error(f"爬虫任务执行出错: {e}")
await self.send_message({
'type': 'log',
'message': f'爬虫任务执行出错: {str(e)}'
})
finally:
await self.flush_messages()
async def broadcast_message(messages):
"""广播消息到所有WebSocket连接"""
if not websocket_connections:
return
for websocket in websocket_connections.copy():
try:
if isinstance(messages, list):
for message in messages:
await websocket.send(json.dumps(message))
else:
await websocket.send(json.dumps(messages))
except websockets.exceptions.ConnectionClosed:
websocket_connections.remove(websocket)
except Exception as e:
logger.error(f"发送WebSocket消息失败: {e}")
websocket_connections.remove(websocket)
@spider_bp.route('/spider/control')
def spider_control():
"""渲染爬虫控制页面"""
return render_template('spider_control.html')
@spider_bp.route('/api/spider/start', methods=['POST'])
async def start_spider():
"""启动爬虫任务"""
try:
data = request.get_json()
topics = data.get('topics', [])
parameters = {**DEFAULT_CONFIG, **data.get('parameters', {})}
if not topics:
return jsonify({
'success': False,
'message': '请选择至少一个话题'
})
# 创建爬虫工作器
worker = SpiderWorker(topics, parameters)
# 在事件循环中运行爬虫任务
asyncio.create_task(worker.run())
return jsonify({
'success': True,
'message': '爬虫任务已启动'
})
except Exception as e:
logger.error(f"启动爬虫任务失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
@spider_bp.route('/api/spider/save-config', methods=['POST'])
def save_spider_config():
"""保存爬虫配置"""
try:
config = request.get_json()
if save_config(config):
return jsonify({
'success': True,
'message': '配置保存成功'
})
else:
return jsonify({
'success': False,
'message': '配置保存失败'
})
except Exception as e:
logger.error(f"保存配置失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
@spider_bp.websocket('/ws/spider-status')
async def spider_status_socket(websocket):
"""WebSocket连接处理"""
try:
websocket_connections.add(websocket)
logging.info("新的WebSocket连接已建立")
try:
while True:
# 等待消息,保持连接活跃
message = await websocket.receive()
if message is None:
break
except websockets.exceptions.ConnectionClosed:
logging.info("WebSocket连接已关闭")
finally:
websocket_connections.remove(websocket)
logging.info("WebSocket连接已移除")
except Exception as e:
logger.error(f"WebSocket连接处理失败: {e}")
if websocket in websocket_connections:
websocket_connections.remove(websocket)
def get_ai_client():
"""获取可用的AI客户端"""
# 按优先级尝试不同的AI服务
if os.getenv('ANTHROPIC_API_KEY'):
return {
'type': 'anthropic',
'client': Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
}
elif os.getenv('OPENAI_API_KEY'):
return {
'type': 'openai',
'client': OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
}
else:
raise ValueError("未找到可用的AI API密钥")
def parse_ai_response(response_text):
"""解析AI响应中的JSON配置"""
try:
# 查找JSON内容
start = response_text.find('{')
end = response_text.rfind('}') + 1
if start == -1 or end == 0:
raise ValueError("未找到有效的JSON配置")
json_str = response_text[start:end]
config = json.loads(json_str)
# 验证配置格式
if not isinstance(config.get('topics'), list):
raise ValueError("配置必须包含话题列表")
parameters = config.get('parameters', {})
if not all(key in parameters for key in ['crawlDepth', 'interval', 'maxRetries', 'timeout']):
raise ValueError("配置缺少必要的参数")
# 提取建议文本(JSON之前的部分)
suggestion = response_text[:start].strip()
return config, suggestion
except Exception as e:
raise ValueError(f"解析AI响应失败: {str(e)}")
@spider_bp.route('/api/spider/ai-config', methods=['POST'])
def generate_ai_config():
"""使用AI生成爬虫配置"""
try:
prompt = request.json.get('prompt', '')
if not prompt:
return jsonify({
'success': False,
'message': '请提供爬虫需求描述'
})
# 构建AI提示
system_prompt = """你是一个专业的爬虫配置助手。请根据用户的自然语言描述,生成合适的微博爬虫配置。
配置应包含以下内容:
1. 要爬取的话题列表
2. 爬虫参数(爬取深度、间隔时间、重试次数、超时时间)
请先用通俗易懂的语言解释你的配置建议,然后在最后提供一个JSON格式的具体配置。
注意:
- 爬取深度(crawlDepth)范围:1-10页
- 间隔时间(interval)范围:3-30秒
- 重试次数(maxRetries)范围:1-5次
- 超时时间(timeout)范围:10-60秒
- 所有参数都必须是整数
示例输出格式:
根据您的需求,我建议...
{
"topics": ["话题1", "话题2"],
"parameters": {
"crawlDepth": 5,
"interval": 5,
"maxRetries": 3,
"timeout": 30
}
}"""
# 获取AI客户端
ai = get_ai_client()
try:
if ai['type'] == 'anthropic':
response = ai['client'].messages.create(
model="claude-3-sonnet-20240229",
max_tokens=1000,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
response_text = response.content[0].text
else: # OpenAI
response = ai['client'].chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
response_text = response.choices[0].message.content
# 解析AI响应
config, suggestion = parse_ai_response(response_text)
return jsonify({
'success': True,
'config': config,
'suggestion': suggestion
})
except Exception as e:
logger.error(f"AI服务调用失败: {e}")
return jsonify({
'success': False,
'message': f"AI配置生成失败: {str(e)}"
})
except Exception as e:
logger.error(f"生成配置失败: {e}")
return jsonify({
'success': False,
'message': str(e)
from flask import Blueprint, jsonify, request, render_template
import json
import os
from datetime import datetime
import threading
from queue import Queue
import asyncio
import websockets
import logging
from spider.spiderData import SpiderData
from openai import OpenAI
from anthropic import Anthropic
import aiohttp
from concurrent.futures import ThreadPoolExecutor
from ratelimit import limits, sleep_and_retry
from tenacity import retry, stop_after_attempt, wait_exponential
# 创建蓝图
spider_bp = Blueprint('spider', __name__)
# 创建日志记录器
logger = logging.getLogger('spider_control')
logger.setLevel(logging.INFO)
# 存储WebSocket连接的集合
websocket_connections = set()
# 创建消息队列
message_queue = Queue()
# 创建线程池
thread_pool = ThreadPoolExecutor(max_workers=3)
# 创建异步事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# 默认配置
DEFAULT_CONFIG = {
'crawlDepth': 3,
'interval': 5,
'maxRetries': 3,
'timeout': 30,
'maxConcurrent': 2
}
# 限流装饰器
@sleep_and_retry
@limits(calls=100, period=60) # 每分钟最多100个请求
def rate_limited_request():
pass
class SpiderWorker:
def __init__(self, topics, parameters):
self.topics = topics
self.parameters = parameters
self.total_topics = len(topics)
self.completed_topics = 0
self.spider = SpiderData()
self.message_buffer = []
self.message_buffer_size = 10
self.semaphore = asyncio.Semaphore(parameters.get('maxConcurrent', DEFAULT_CONFIG['maxConcurrent']))
async def send_message(self, message):
"""异步发送消息,使用缓冲区优化"""
self.message_buffer.append(message)
if len(self.message_buffer) >= self.message_buffer_size:
await self.flush_messages()
async def flush_messages(self):
"""刷新消息缓冲区"""
if not self.message_buffer:
return
try:
await broadcast_message(self.message_buffer)
self.message_buffer.clear()
except Exception as e:
logger.error(f"发送消息失败: {e}")
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
async def crawl_single_topic(self, topic):
"""爬取单个话题"""
try:
rate_limited_request()
await self.send_message({
'type': 'log',
'message': f'开始爬取话题: {topic}'
})
async with self.semaphore:
await asyncio.get_event_loop().run_in_executor(
thread_pool,
self.spider.crawl_topic,
topic,
self.parameters['crawlDepth'],
self.parameters['interval'],
self.parameters['maxRetries'],
self.parameters['timeout']
)
self.completed_topics += 1
progress = int((self.completed_topics / self.total_topics) * 100)
await self.send_message({
'type': 'progress',
'value': progress
})
await self.send_message({
'type': 'log',
'message': f'话题 {topic} 爬取完成'
})
except Exception as e:
logger.error(f"爬取话题 {topic} 失败: {e}")
await self.send_message({
'type': 'log',
'message': f'爬取话题 {topic} 时出错: {str(e)}'
})
raise
async def run(self):
"""运行爬虫任务"""
try:
tasks = [self.crawl_single_topic(topic) for topic in self.topics]
await asyncio.gather(*tasks)
await self.flush_messages()
await self.send_message({
'type': 'log',
'message': '所有话题爬取完成'
})
except Exception as e:
logger.error(f"爬虫任务执行出错: {e}")
await self.send_message({
'type': 'log',
'message': f'爬虫任务执行出错: {str(e)}'
})
finally:
await self.flush_messages()
async def broadcast_message(messages):
"""广播消息到所有WebSocket连接"""
if not websocket_connections:
return
for websocket in websocket_connections.copy():
try:
if isinstance(messages, list):
for message in messages:
await websocket.send(json.dumps(message))
else:
await websocket.send(json.dumps(messages))
except websockets.exceptions.ConnectionClosed:
websocket_connections.remove(websocket)
except Exception as e:
logger.error(f"发送WebSocket消息失败: {e}")
websocket_connections.remove(websocket)
@spider_bp.route('/spider/control')
def spider_control():
"""渲染爬虫控制页面"""
return render_template('spider_control.html')
@spider_bp.route('/api/spider/start', methods=['POST'])
async def start_spider():
"""启动爬虫任务"""
try:
data = request.get_json()
topics = data.get('topics', [])
parameters = {**DEFAULT_CONFIG, **data.get('parameters', {})}
if not topics:
return jsonify({
'success': False,
'message': '请选择至少一个话题'
})
# 创建爬虫工作器
worker = SpiderWorker(topics, parameters)
# 在事件循环中运行爬虫任务
asyncio.create_task(worker.run())
return jsonify({
'success': True,
'message': '爬虫任务已启动'
})
except Exception as e:
logger.error(f"启动爬虫任务失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
@spider_bp.route('/api/spider/save-config', methods=['POST'])
def save_spider_config():
"""保存爬虫配置"""
try:
config = request.get_json()
if save_config(config):
return jsonify({
'success': True,
'message': '配置保存成功'
})
else:
return jsonify({
'success': False,
'message': '配置保存失败'
})
except Exception as e:
logger.error(f"保存配置失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
@spider_bp.websocket('/ws/spider-status')
async def spider_status_socket(websocket):
"""WebSocket连接处理"""
try:
websocket_connections.add(websocket)
logging.info("新的WebSocket连接已建立")
try:
while True:
# 等待消息,保持连接活跃
message = await websocket.receive()
if message is None:
break
except websockets.exceptions.ConnectionClosed:
logging.info("WebSocket连接已关闭")
finally:
websocket_connections.remove(websocket)
logging.info("WebSocket连接已移除")
except Exception as e:
logger.error(f"WebSocket连接处理失败: {e}")
if websocket in websocket_connections:
websocket_connections.remove(websocket)
def get_ai_client():
"""获取可用的AI客户端"""
# 按优先级尝试不同的AI服务
if os.getenv('ANTHROPIC_API_KEY'):
return {
'type': 'anthropic',
'client': Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
}
elif os.getenv('OPENAI_API_KEY'):
return {
'type': 'openai',
'client': OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
}
else:
raise ValueError("未找到可用的AI API密钥")
def parse_ai_response(response_text):
"""解析AI响应中的JSON配置"""
try:
# 查找JSON内容
start = response_text.find('{')
end = response_text.rfind('}') + 1
if start == -1 or end == 0:
raise ValueError("未找到有效的JSON配置")
json_str = response_text[start:end]
config = json.loads(json_str)
# 验证配置格式
if not isinstance(config.get('topics'), list):
raise ValueError("配置必须包含话题列表")
parameters = config.get('parameters', {})
if not all(key in parameters for key in ['crawlDepth', 'interval', 'maxRetries', 'timeout']):
raise ValueError("配置缺少必要的参数")
# 提取建议文本(JSON之前的部分)
suggestion = response_text[:start].strip()
return config, suggestion
except Exception as e:
raise ValueError(f"解析AI响应失败: {str(e)}")
@spider_bp.route('/api/spider/ai-config', methods=['POST'])
def generate_ai_config():
"""使用AI生成爬虫配置"""
try:
prompt = request.json.get('prompt', '')
if not prompt:
return jsonify({
'success': False,
'message': '请提供爬虫需求描述'
})
# 构建AI提示
system_prompt = """你是一个专业的爬虫配置助手。请根据用户的自然语言描述,生成合适的微博爬虫配置。
配置应包含以下内容:
1. 要爬取的话题列表
2. 爬虫参数(爬取深度、间隔时间、重试次数、超时时间)
请先用通俗易懂的语言解释你的配置建议,然后在最后提供一个JSON格式的具体配置。
注意:
- 爬取深度(crawlDepth)范围:1-10页
- 间隔时间(interval)范围:3-30秒
- 重试次数(maxRetries)范围:1-5次
- 超时时间(timeout)范围:10-60秒
- 所有参数都必须是整数
示例输出格式:
根据您的需求,我建议...
{
"topics": ["话题1", "话题2"],
"parameters": {
"crawlDepth": 5,
"interval": 5,
"maxRetries": 3,
"timeout": 30
}
}"""
# 获取AI客户端
ai = get_ai_client()
try:
if ai['type'] == 'anthropic':
response = ai['client'].messages.create(
model="claude-3-sonnet-20240229",
max_tokens=1000,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
response_text = response.content[0].text
else: # OpenAI
response = ai['client'].chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
response_text = response.choices[0].message.content
# 解析AI响应
config, suggestion = parse_ai_response(response_text)
return jsonify({
'success': True,
'config': config,
'suggestion': suggestion
})
except Exception as e:
logger.error(f"AI服务调用失败: {e}")
return jsonify({
'success': False,
'message': f"AI配置生成失败: {str(e)}"
})
except Exception as e:
logger.error(f"生成配置失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
Binary file not shown.
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>微博舆情分析系统 | 登录</title>
<link rel="icon" href="../../../static/原神启动/favicon.ico" />
<script src="/static/js/i18n.js"></script>
</head>
<style>
@@ -287,9 +288,31 @@
.yanzhengma:hover {
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>
<body onclick="playAudio()">
<div class="language-switcher" id="language-switcher">
<span id="language-text">切换语言</span>
</div>
<audio id="audio-player" autoplay class="hide-player">
<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"
+372 -372
View File
@@ -1,372 +1,372 @@
import time
import hashlib
from flask import Blueprint, redirect, render_template, request, Flask, session, current_app, make_response
from datetime import datetime, timedelta
import re
from utils.query import query
from utils.errorResponse import errorResponse
from utils.logger import app_logger as logging
from functools import wraps
import secrets
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import redis
import json
import bleach
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import html
# 创建Argon2密码哈希器
ph = PasswordHasher()
# Redis连接
redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# 创建限流器
limiter = Limiter(
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
ub = Blueprint('user',
__name__,
url_prefix='/user',
template_folder='templates')
def sanitize_input(text):
"""清理用户输入,防止XSS攻击"""
if text is None:
return None
return bleach.clean(str(text), strip=True)
def validate_csrf_token():
"""验证CSRF令牌"""
token = request.form.get('csrf_token')
stored_token = session.get('csrf_token')
if not token or not stored_token or token != stored_token:
return False
return True
def get_client_info():
"""获取客户端信息"""
return {
'ip': request.remote_addr,
'user_agent': str(request.user_agent.string),
'platform': str(request.user_agent.platform),
'browser': str(request.user_agent.browser),
}
def is_suspicious_ip(ip):
"""检查IP是否可疑"""
key = f"login_attempts:{ip}"
attempts = redis_client.get(key)
if attempts and int(attempts) >= 5: # 5次失败尝试
return True
return False
def record_failed_attempt(ip):
"""记录失败的登录尝试"""
key = f"login_attempts:{ip}"
pipe = redis_client.pipeline()
pipe.incr(key)
pipe.expire(key, 1800) # 30分钟后重置
pipe.execute()
def clear_login_attempts(ip):
"""清除登录尝试记录"""
redis_client.delete(f"login_attempts:{ip}")
def set_secure_headers(response):
"""设置安全响应头"""
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Content-Security-Policy'] = "default-src 'self'"
return response
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'username' not in session:
return redirect('/user/login')
# 验证会话完整性
if 'client_info' not in session or 'session_id' not in session:
session.clear()
return redirect('/user/login')
# 验证客户端信息
current_client = get_client_info()
stored_client = session['client_info']
if (current_client['ip'] != stored_client['ip'] or
current_client['user_agent'] != stored_client['user_agent']):
session.clear()
return redirect('/user/login')
# 验证会话ID
stored_session_id = redis_client.get(f"session:{session['username']}")
if not stored_session_id or stored_session_id != session['session_id']:
session.clear()
return redirect('/user/login')
return f(*args, **kwargs)
return decorated_function
def hash_password(password: str) -> str:
"""
使用Argon2id算法哈希密码
:param password: 用户输入的密码
:return: 哈希后的密码
"""
return ph.hash(password)
def verify_password(stored_hash: str, password: str) -> bool:
"""
验证密码
:param stored_hash: 存储的密码哈希
:param password: 用户输入的密码
:return: 是否匹配
"""
try:
return ph.verify(stored_hash, password)
except VerifyMismatchError:
return False
def validate_password(password: str) -> bool:
"""
验证密码强度
"""
if len(password) < 12: # 增加最小长度要求
return False
if not re.search(r"[A-Z]", password):
return False
if not re.search(r"[a-z]", password):
return False
if not re.search(r"\d", password):
return False
if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
return False
# 检查常见密码模式
common_patterns = ['password', '123456', 'qwerty']
if any(pattern in password.lower() for pattern in common_patterns):
return False
return True
@ub.route('/login', methods=['GET', 'POST'])
@limiter.limit("5 per minute")
def login():
"""处理用户登录请求"""
if request.method == 'GET':
response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response)
try:
if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败")
return errorResponse('无效的请求')
client_ip = request.remote_addr
if is_suspicious_ip(client_ip):
logging.warning(f"可疑IP尝试登录: {client_ip}")
return errorResponse('由于多次失败尝试,请30分钟后再试')
username = sanitize_input(request.form.get('username'))
password = request.form.get('password') # 密码不需要sanitize
if not username or not password:
logging.warning("登录失败:用户名或密码为空")
return errorResponse('用户名和密码不能为空')
# 查询用户信息
sql = "SELECT password, status FROM user WHERE username = %s"
result = query(sql, [username], "select")
if result:
stored_password = result[0]['password']
status = result[0]['status']
if status != 'active':
logging.warning(f"已禁用的账户尝试登录: {username}")
return errorResponse('账户已被禁用')
if verify_password(stored_password, password):
session.clear()
session.regenerate()
# 生成唯一会话ID
session_id = secrets.token_hex(32)
client_info = get_client_info()
# 存储会话信息
session['username'] = username
session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
session['csrf_token'] = secrets.token_hex(32)
session['client_info'] = client_info
session['session_id'] = session_id
session.permanent = True
current_app.permanent_session_lifetime = timedelta(hours=2)
# 在Redis中存储会话ID
redis_client.setex(
f"session:{username}",
int(current_app.permanent_session_lifetime.total_seconds()),
session_id
)
clear_login_attempts(client_ip)
# 记录登录历史
login_history_sql = '''
INSERT INTO login_history
(username, login_time, ip_address, user_agent, success, attempt_count)
VALUES (%s, %s, %s, %s, %s, %s)
'''
query(login_history_sql, [
username,
datetime.now(),
client_info['ip'],
client_info['user_agent'],
True,
redis_client.get(f"login_attempts:{client_ip}") or 0
])
logging.info(f"用户 {username} 登录成功")
response = make_response(redirect('/page/home'))
return set_secure_headers(response)
record_failed_attempt(client_ip)
logging.warning(f"登录失败:用户名或密码错误")
return errorResponse('用户名或密码错误')
except Exception as e:
logging.error(f"登录过程发生错误: {e}")
return errorResponse('登录失败,请稍后重试')
@ub.route('/register', methods=['GET', 'POST'])
@limiter.limit("3 per hour")
def register():
if request.method == 'GET':
response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response)
try:
if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败")
return errorResponse('无效的请求')
username = sanitize_input(request.form.get('username'))
password = request.form.get('password')
email = sanitize_input(request.form.get('email'))
if not username or not password or not email:
return errorResponse('用户名、密码和邮箱不能为空')
# 验证用户名格式
if not re.match(r'^[a-zA-Z0-9_]{4,20}$', username):
return errorResponse('用户名只能包含字母、数字和下划线,长度4-20位')
# 验证邮箱格式
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
return errorResponse('邮箱格式不正确')
# 验证密码强度
if not validate_password(password):
return errorResponse('密码必须包含大小写字母、数字和特殊字符,且长度至少12位')
try:
# 检查用户名和邮箱是否存在
check_sql = """
SELECT
(SELECT COUNT(*) FROM user WHERE LOWER(username) = LOWER(%s)) as username_count,
(SELECT COUNT(*) FROM user WHERE LOWER(email) = LOWER(%s)) as email_count
"""
result = query(check_sql, [username.lower(), email.lower()], "select")
if result[0]['username_count'] > 0:
return errorResponse('该用户名已被注册')
if result[0]['email_count'] > 0:
return errorResponse('该邮箱已被注册')
# 哈希密码
hashed_password = hash_password(password)
# 插入新用户
insert_sql = '''
INSERT INTO user(username, password, email, status, createTime, last_password_change)
VALUES(%s, %s, %s, %s, %s, %s)
'''
current_time = datetime.now()
query(insert_sql, [
username,
hashed_password,
email,
'active',
current_time,
current_time
])
# 记录注册信息
client_info = get_client_info()
register_history_sql = '''
INSERT INTO register_history
(username, register_time, ip_address, user_agent, email)
VALUES (%s, %s, %s, %s, %s)
'''
query(register_history_sql, [
username,
current_time,
client_info['ip'],
client_info['user_agent'],
email
])
logging.info(f"新用户注册成功: {username}")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
except Exception as e:
logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试')
except Exception as e:
logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试')
@ub.route('/logout')
@login_required
def logout():
"""用户登出"""
try:
username = session.get('username')
client_info = session.get('client_info', {})
# 记录登出历史
logout_history_sql = '''
INSERT INTO logout_history
(username, logout_time, ip_address, user_agent, session_id)
VALUES (%s, %s, %s, %s, %s)
'''
query(logout_history_sql, [
username,
datetime.now(),
client_info.get('ip'),
client_info.get('user_agent'),
session.get('session_id')
])
# 删除Redis中的会话
redis_client.delete(f"session:{username}")
session.clear()
logging.info(f"用户 {username} 成功登出")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
except Exception as e:
logging.error(f"登出过程发生错误: {e}")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
import time
import hashlib
from flask import Blueprint, redirect, render_template, request, Flask, session, current_app, make_response
from datetime import datetime, timedelta
import re
from utils.query import query
from utils.errorResponse import errorResponse
from utils.logger import app_logger as logging
from functools import wraps
import secrets
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import redis
import json
import bleach
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import html
# 创建Argon2密码哈希器
ph = PasswordHasher()
# Redis连接
redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# 创建限流器
limiter = Limiter(
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
ub = Blueprint('user',
__name__,
url_prefix='/user',
template_folder='templates')
def sanitize_input(text):
"""清理用户输入,防止XSS攻击"""
if text is None:
return None
return bleach.clean(str(text), strip=True)
def validate_csrf_token():
"""验证CSRF令牌"""
token = request.form.get('csrf_token')
stored_token = session.get('csrf_token')
if not token or not stored_token or token != stored_token:
return False
return True
def get_client_info():
"""获取客户端信息"""
return {
'ip': request.remote_addr,
'user_agent': str(request.user_agent.string),
'platform': str(request.user_agent.platform),
'browser': str(request.user_agent.browser),
}
def is_suspicious_ip(ip):
"""检查IP是否可疑"""
key = f"login_attempts:{ip}"
attempts = redis_client.get(key)
if attempts and int(attempts) >= 5: # 5次失败尝试
return True
return False
def record_failed_attempt(ip):
"""记录失败的登录尝试"""
key = f"login_attempts:{ip}"
pipe = redis_client.pipeline()
pipe.incr(key)
pipe.expire(key, 1800) # 30分钟后重置
pipe.execute()
def clear_login_attempts(ip):
"""清除登录尝试记录"""
redis_client.delete(f"login_attempts:{ip}")
def set_secure_headers(response):
"""设置安全响应头"""
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Content-Security-Policy'] = "default-src 'self'"
return response
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'username' not in session:
return redirect('/user/login')
# 验证会话完整性
if 'client_info' not in session or 'session_id' not in session:
session.clear()
return redirect('/user/login')
# 验证客户端信息
current_client = get_client_info()
stored_client = session['client_info']
if (current_client['ip'] != stored_client['ip'] or
current_client['user_agent'] != stored_client['user_agent']):
session.clear()
return redirect('/user/login')
# 验证会话ID
stored_session_id = redis_client.get(f"session:{session['username']}")
if not stored_session_id or stored_session_id != session['session_id']:
session.clear()
return redirect('/user/login')
return f(*args, **kwargs)
return decorated_function
def hash_password(password: str) -> str:
"""
使用Argon2id算法哈希密码
:param password: 用户输入的密码
:return: 哈希后的密码
"""
return ph.hash(password)
def verify_password(stored_hash: str, password: str) -> bool:
"""
验证密码
:param stored_hash: 存储的密码哈希
:param password: 用户输入的密码
:return: 是否匹配
"""
try:
return ph.verify(stored_hash, password)
except VerifyMismatchError:
return False
def validate_password(password: str) -> bool:
"""
验证密码强度
"""
if len(password) < 12: # 增加最小长度要求
return False
if not re.search(r"[A-Z]", password):
return False
if not re.search(r"[a-z]", password):
return False
if not re.search(r"\d", password):
return False
if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
return False
# 检查常见密码模式
common_patterns = ['password', '123456', 'qwerty']
if any(pattern in password.lower() for pattern in common_patterns):
return False
return True
@ub.route('/login', methods=['GET', 'POST'])
@limiter.limit("5 per minute")
def login():
"""处理用户登录请求"""
if request.method == 'GET':
response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response)
try:
if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败")
return errorResponse('无效的请求')
client_ip = request.remote_addr
if is_suspicious_ip(client_ip):
logging.warning(f"可疑IP尝试登录: {client_ip}")
return errorResponse('由于多次失败尝试,请30分钟后再试')
username = sanitize_input(request.form.get('username'))
password = request.form.get('password') # 密码不需要sanitize
if not username or not password:
logging.warning("登录失败:用户名或密码为空")
return errorResponse('用户名和密码不能为空')
# 查询用户信息
sql = "SELECT password, status FROM user WHERE username = %s"
result = query(sql, [username], "select")
if result:
stored_password = result[0]['password']
status = result[0]['status']
if status != 'active':
logging.warning(f"已禁用的账户尝试登录: {username}")
return errorResponse('账户已被禁用')
if verify_password(stored_password, password):
session.clear()
session.regenerate()
# 生成唯一会话ID
session_id = secrets.token_hex(32)
client_info = get_client_info()
# 存储会话信息
session['username'] = username
session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
session['csrf_token'] = secrets.token_hex(32)
session['client_info'] = client_info
session['session_id'] = session_id
session.permanent = True
current_app.permanent_session_lifetime = timedelta(hours=2)
# 在Redis中存储会话ID
redis_client.setex(
f"session:{username}",
int(current_app.permanent_session_lifetime.total_seconds()),
session_id
)
clear_login_attempts(client_ip)
# 记录登录历史
login_history_sql = '''
INSERT INTO login_history
(username, login_time, ip_address, user_agent, success, attempt_count)
VALUES (%s, %s, %s, %s, %s, %s)
'''
query(login_history_sql, [
username,
datetime.now(),
client_info['ip'],
client_info['user_agent'],
True,
redis_client.get(f"login_attempts:{client_ip}") or 0
])
logging.info(f"用户 {username} 登录成功")
response = make_response(redirect('/page/home'))
return set_secure_headers(response)
record_failed_attempt(client_ip)
logging.warning(f"登录失败:用户名或密码错误")
return errorResponse('用户名或密码错误')
except Exception as e:
logging.error(f"登录过程发生错误: {e}")
return errorResponse('登录失败,请稍后重试')
@ub.route('/register', methods=['GET', 'POST'])
@limiter.limit("3 per hour")
def register():
if request.method == 'GET':
response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response)
try:
if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败")
return errorResponse('无效的请求')
username = sanitize_input(request.form.get('username'))
password = request.form.get('password')
email = sanitize_input(request.form.get('email'))
if not username or not password or not email:
return errorResponse('用户名、密码和邮箱不能为空')
# 验证用户名格式
if not re.match(r'^[a-zA-Z0-9_]{4,20}$', username):
return errorResponse('用户名只能包含字母、数字和下划线,长度4-20位')
# 验证邮箱格式
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
return errorResponse('邮箱格式不正确')
# 验证密码强度
if not validate_password(password):
return errorResponse('密码必须包含大小写字母、数字和特殊字符,且长度至少12位')
try:
# 检查用户名和邮箱是否存在
check_sql = """
SELECT
(SELECT COUNT(*) FROM user WHERE LOWER(username) = LOWER(%s)) as username_count,
(SELECT COUNT(*) FROM user WHERE LOWER(email) = LOWER(%s)) as email_count
"""
result = query(check_sql, [username.lower(), email.lower()], "select")
if result[0]['username_count'] > 0:
return errorResponse('该用户名已被注册')
if result[0]['email_count'] > 0:
return errorResponse('该邮箱已被注册')
# 哈希密码
hashed_password = hash_password(password)
# 插入新用户
insert_sql = '''
INSERT INTO user(username, password, email, status, createTime, last_password_change)
VALUES(%s, %s, %s, %s, %s, %s)
'''
current_time = datetime.now()
query(insert_sql, [
username,
hashed_password,
email,
'active',
current_time,
current_time
])
# 记录注册信息
client_info = get_client_info()
register_history_sql = '''
INSERT INTO register_history
(username, register_time, ip_address, user_agent, email)
VALUES (%s, %s, %s, %s, %s)
'''
query(register_history_sql, [
username,
current_time,
client_info['ip'],
client_info['user_agent'],
email
])
logging.info(f"新用户注册成功: {username}")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
except Exception as e:
logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试')
except Exception as e:
logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试')
@ub.route('/logout')
@login_required
def logout():
"""用户登出"""
try:
username = session.get('username')
client_info = session.get('client_info', {})
# 记录登出历史
logout_history_sql = '''
INSERT INTO logout_history
(username, logout_time, ip_address, user_agent, session_id)
VALUES (%s, %s, %s, %s, %s)
'''
query(logout_history_sql, [
username,
datetime.now(),
client_info.get('ip'),
client_info.get('user_agent'),
session.get('session_id')
])
# 删除Redis中的会话
redis_client.delete(f"session:{username}")
session.clear()
logging.info(f"用户 {username} 成功登出")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
except Exception as e:
logging.error(f"登出过程发生错误: {e}")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)