全栈开发一条龙——前端篇
第一篇:框架确定、ide设置与项目创建
第二篇:介绍项目文件意义、组件结构与导入以及setup的引入。
第三篇:setup语法,设置响应式数据。
第四篇:数据绑定、计算属性和watch监视
第五篇 : 组件间通信及知识补充
第六篇:生命周期和自定义hooks
第七篇:路由
第八篇:传参
第九篇:插槽,常用api和全局api。
全栈开发一条龙——全栈篇
第一篇:初识Flask&MySQL实现前后端通信
第二篇: sql操作、发送http请求和邮件发送
第三篇:全栈实现发送验证码注册账号
第四篇:图片验证码及知识补充
全栈开发一条龙——实战篇
第一篇:项目建立与login页面
第二篇:建立管理员后台和添加题目功能实现
第三篇:完善管理员后台
第四篇:用户视图
本篇将完成数据处理页面
文章目录
- 后端
- 数据处理工具
- 蓝图
- 前端
- 模板
- 脚本
- 完整前端代码
- 效果展示
后端
数据处理工具
我们先来写数据处理的工具,我们的思路是从数据库中取出我们之前存储的答案序列,然后分析到达不同阶段的人数和相关系数等等。
我们先从数据库中获取数据
raw_list = db.session.execute( text("select * from data_analysis") ).fetchall()
answer_list = list_row2list_dic(raw_list)
temp = sql_ex()
question_info = temp.search()
接下来进行基础的数据统计,各个题目的答题次数,平均分等
# 初始化每道题的选项计数和分数统计
question_stats = []
for question in question_info:
question_stats.append({
'question': question['questions'],
'choice_counts': {'A': 0, 'B': 0, 'C': 0, 'D': 0},
'total_score': 0,
'response_count': 0,
'points': {
'A': question['point_a'],
'B': question['point_b'],
'C': question['point_c'],
'D': question['point_d']
}
})
# 统计每个选项的选择次数和总分
for answer in answer_list:
answer_str = answer['answer']
for i, char in enumerate(answer_str):
if char in 'ABCD':
question_stats[i]['choice_counts'][char] += 1
question_stats[i]['total_score'] += question_stats[i]['points'][char]
question_stats[i]['response_count'] += 1
# 计算每道题的平均分
for stats in question_stats:
if stats['response_count'] > 0:
stats['average_score'] = stats['total_score'] / stats['response_count']
else:
stats['average_score'] = 0
我们可以使用以下代码来查看处理的情况,当然,最后需要把他们注释掉。
# 输出结果 question_stats
# for stats in question_stats:
# print(f"Question: {stats['question']}")
# print(f"Choice Counts: {stats['choice_counts']}")
# print(f"Average Score: {stats['average_score']:.2f}")
# print('-' + '-' * 50)
#输出格式:[{'question': '惺惺惜惺惺哈哈哈哈和和和和和和和和谢谢谢谢选项下下下下下下下下下下下下下下下下', 'choice_counts': {'A': 4, 'B': 0, 'C': 0, 'D': 1}, 'total_score': 3, 'response_count': 5, 'points': {'A': 0, 'B': 1, 'C': 2, 'D': 3}, 'average_score': 0.6}, {'question': 'csy——beautiful!!', 'choice_counts': {'A': 1, 'B': 1, 'C': 1, 'D': 2}, 'total_score': 9, 'response_count': 5, 'points': {'A': 0, 'B': 1, 'C': 2, 'D': 3}, 'average_score': 1.8}, {'question': '1', 'choice_counts': {'A': 0, 'B': 0, 'C': 1, 'D': 1}, 'total_score': 5, 'response_count': 2, 'points': {'A': 0, 'B': 1, 'C': 2, 'D': 3}, 'average_score': 2.5}, {'question': 'huaidan no', 'choice_counts': {'A': 1, 'B': 0, 'C': 0, 'D': 1}, 'total_score': 3, 'response_count': 2, 'points': {'A': 0, 'B': 1, 'C': 2, 'D': 3}, 'average_score': 1.5}, {'question': '8的合伙人发', 'choice_counts': {'A': 0, 'B': 0, 'C': 0, 'D': 1}, 'total_score': 3, 'response_count': 1, 'points': {'A': 0, 'B': 1, 'C': 2, 'D': 3}, 'average_score': 3.0}, {'question': '9ehwc', 'choice_counts': {'A': 0, 'B': 0, 'C': 0, 'D': 1}, 'total_score': 3, 'response_count': 1, 'points': {'A': 0, 'B': 1, 'C': 2, 'D': 3}, 'average_score': 3.0}, {'question': '接口', 'choice_counts': {'A': 0, 'B': 0, 'C': 0, 'D': 1}, 'total_score': 4, 'response_count': 1, 'points': {'A': 1, 'B': 2, 'C': 3, 'D': 4}, 'average_score': 4.0}]
然后我们统计达到各个stage的人数,逻辑是只要回答了任意一道stage3的题目,这个人就达到了stage3的阶段,counter++
#统计stage人数
# 初始化每个阶段的回答人数统计
stage_answer_counts = {1: 0, 2: 0, 3: 0, 4: 0}
# 统计每个阶段回答了至少一个问题的人数
for answer in answer_list:
answer_str = answer['answer']
answered_stages = set()
for i, char in enumerate(answer_str):
if char in 'ABCD':
stage = question_info[i]['stage']
answered_stages.add(stage)
for stage in answered_stages:
stage_answer_counts[stage] += 1
# stage_answer_counts output:{1: 5, 2: 2, 3: 1, 4: 1}
最后,我们进行相关性分析,这里我们需要使用numpy和pandas
answer_matrix = []
for answer in answer_list:
answer_str = answer['answer']
answer_row = []
for char in answer_str:
if char == 'A':
answer_row.append(1)
elif char == 'B':
answer_row.append(2)
elif char == 'C':
answer_row.append(3)
elif char == 'D':
answer_row.append(4)
else:
answer_row.append(0)
answer_matrix.append(answer_row)
# 转换为DataFrame
df = pd.DataFrame(answer_matrix, columns=[f'Q{i+1}' for i in range(len(answer_matrix[0]))])
# 计算相关性矩阵
correlation_matrix = df.corr()
这里我们需要先把内容转为pandas特定的格式,然后调用库。最后输出的结果是output type <class ‘pandas.core.frame.DataFrame’>格式的,内容是
# Q1 Q2 Q3 Q4 Q5 Q6 Q7
# Q1 1.000000 0.514496 0.745601 0.968246 1.000000 1.000000 1.000000
# Q2 0.514496 1.000000 0.826234 0.664211 0.514496 0.514496 0.514496
# Q3 0.745601 0.826234 1.000000 0.888523 0.745601 0.745601 0.745601
# Q4 0.968246 0.664211 0.888523 1.000000 0.968246 0.968246 0.968246
# Q5 1.000000 0.514496 0.745601 0.968246 1.000000 1.000000 1.000000
# Q6 1.000000 0.514496 0.745601 0.968246 1.000000 1.000000 1.000000
# Q7 1.000000 0.514496 0.745601 0.968246 1.000000 1.000000 1.000000
这样的矩阵,至此,我们的处理工具就做好了,接下来我们需要完善后端节后,使得前端可以访问并获取到处理好的数据。
蓝图
def post(self):
temp = answer_ex()
question,stage,rela = temp.search()
response = {
'question': question,
'stage': stage,
'rela': rela.to_dict()
}
return jsonify({"errcode": 0, "msg": response})
如上就做好了,这里我们将举证转化为dict,方便我们前端展示数据。
前端
前端我们需要做以下几件事,展示基础数据,根据相关性数据绘制图表。这需要用到chart.js 我们可以用npm i一键安装。
我们的展示思路就是把一个个问题做成card,一一展示。
模板
<template>
<div class="container">
<div class="header">
<button class="custom-button" @click="rt">返回编辑页</button>
</div>
<h1>数据统计</h1>
<div class="card">
<h2>数据总览</h2>
<div class="data-row"><strong>总问卷数:</strong> {{ stage[1] }}</div>
<h3>累计人数统计</h3>
<div class="data-row"><strong>二阶段以上:</strong> {{ stage[2] }}</div>
<div class="data-row"><strong>三阶段以上:</strong> {{ stage[3] }}</div>
<div class="data-row"><strong>四阶段以上:</strong> {{ stage[4] }}</div>
<h3>阶段人数统计</h3>
<div class="data-row"><strong>一阶段人数:</strong> {{ stage[1]-stage[2] }}</div>
<div class="data-row"><strong>二阶段人数:</strong> {{ stage[2] - stage[3]}}</div>
<div class="data-row"><strong>三阶段人数:</strong> {{ stage[3] - stage[4] }}</div>
<div class="data-row"><strong>四阶段人数:</strong> {{ stage[4] }}</div>
<h2>人数分布图表</h2>
<div class="chart-container">
<canvas id="stage-chart"></canvas>
</div>
</div>
<h1>题目详情</h1>
<div class="card" v-for="(item, index) in question" :key="index">
<h2>{{ index + 1 }}. {{ item.question }}</h2>
<div class="data-row"><strong>答题统计:</strong></div>
<div class="choice">
<div>A: {{ item.choice_counts.A }}</div>
<div>B: {{ item.choice_counts.B }}</div>
<div>C: {{ item.choice_counts.C }}</div>
<div>D: {{ item.choice_counts.D }}</div>
</div>
<div class="data-row"><strong>回答次数:</strong> {{ item.response_count }}</div>
<div class="data-row"><strong>均分(期望为1.5):</strong> {{ item.average_score }}</div>
<h3>题目相关性
<button @click="toggleRelaVisibility(index)" class="bt">
{{ relaVisible[index] ? '隐藏相关系数' : '显示相关系数' }}
</button>
</h3>
<ul v-if="relaVisible[index]">
<li v-for="(value, key) in getRelaValues(index)" :key="key">与{{ key }}的相关系数为: {{ value }}</li>
</ul>
<canvas :id="'chart-' + index"></canvas>
</div>
</div>
</template>
这里,我们定义了一个标题,枚举了各个问题的card以展示。
脚本
<script setup>
import { ref, onMounted, nextTick } from 'vue';
import axios from 'axios';
import { Chart, CategoryScale, BarController, BarElement, PointElement, LinearScale, Title, PieController, ArcElement, Tooltip, Legend } from 'chart.js';
import { useRouter } from 'vue-router';
import { useUrlStore } from '@/store/urlStore'; // 确保路径正确
const urlStore = useUrlStore(); // 使用URL Store
const router = useRouter();
Chart.register(
CategoryScale,
BarController,
BarElement,
PointElement,
LinearScale,
Title,
PieController,
ArcElement,
Tooltip,
Legend
);
const question = ref([]);
const stage = ref({});
const rela = ref({});
const relaVisible = ref([]);
const fetchData = async () => {
try {
const response = await axios.post(urlStore.urls.data);
if (response.data.errcode === 0) {
question.value = response.data.msg.question;
stage.value = response.data.msg.stage;
rela.value = response.data.msg.rela;
relaVisible.value = new Array(question.value.length).fill(false);
await nextTick();
renderCharts();
renderStageChart();
} else {
console.error('Error fetching data:', response.data.msg);
}
} catch (error) {
console.error('Error fetching data:', error);
}
};
const formatNumber = (number) => number.toFixed(3);
const getRelaValues = (index) => {
const key = `Q${index + 1}`;
const values = rela.value[key] || {};
const formattedValues = {};
Object.keys(values).forEach(k => {
formattedValues[k] = formatNumber(values[k]);
});
return formattedValues;
};
const toggleRelaVisibility = (index) => {
relaVisible.value[index] = !relaVisible.value[index];
};
const renderCharts = () => {
question.value.forEach((item, index) => {
const ctx = document.getElementById('chart-' + index).getContext('2d');
const correlations = getRelaValues(index);
const correlationValues = Object.values(correlations).map(value => parseFloat(value));
const backgroundColors = correlationValues.map(value =>
value < 0 ? 'rgba(255, 99, 132, 0.5)' : 'rgba(75, 192, 192, 0.5)'
);
new Chart(ctx, {
type: 'bar',
data: {
labels: Object.keys(correlations),
datasets: [{
label: `Correlations for Q${index + 1}`,
data: correlationValues,
backgroundColor: backgroundColors,
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
type: 'category',
display: true,
title: {
display: true,
text: 'Questions'
}
},
y: {
type: 'linear',
beginAtZero: true,
display: true,
title: {
display: true,
text: 'Correlation Value'
}
}
}
}
});
});
};
const renderStageChart = () => {
const ctx = document.getElementById('stage-chart').getContext('2d');
const stageData = [
stage.value['1'] - stage.value['2'],
stage.value['2'] - stage.value['3'],
stage.value['3'] - stage.value['4'],
stage.value['4']
];
new Chart(ctx, {
type: 'pie',
data: {
labels: ['stage1', 'stage2', 'stage3', 'stage4'],
datasets: [{
data: stageData,
backgroundColor: [
'rgba(255, 99, 132, 0.6)',
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)',
'rgba(75, 192, 192, 0.6)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right'
},
tooltip: {
enabled: true
}
}
}
});
};
function rt(){
alert("切换成功,一秒后跳转编辑页面");
setTimeout(() => { router.push({ path: "/home" }) }, 1000);
}
onMounted(fetchData);
</script>
我们脚本实现的逻辑就是先访问后端接口获取数据,然后解析数据,绘制图表。我们绘制了一个pie图,用于展示各个阶段人数。也制作了一个柱状图来展示各个题目之间的相关性。
完整前端代码
<template>
<div class="container">
<div class="header">
<button class="custom-button" @click="rt">返回编辑页</button>
</div>
<h1>数据统计</h1>
<div class="card">
<h2>数据总览</h2>
<div class="data-row"><strong>总问卷数:</strong> {{ stage[1] }}</div>
<h3>累计人数统计</h3>
<div class="data-row"><strong>二阶段以上:</strong> {{ stage[2] }}</div>
<div class="data-row"><strong>三阶段以上:</strong> {{ stage[3] }}</div>
<div class="data-row"><strong>四阶段以上:</strong> {{ stage[4] }}</div>
<h3>阶段人数统计</h3>
<div class="data-row"><strong>一阶段人数:</strong> {{ stage[1]-stage[2] }}</div>
<div class="data-row"><strong>二阶段人数:</strong> {{ stage[2] - stage[3]}}</div>
<div class="data-row"><strong>三阶段人数:</strong> {{ stage[3] - stage[4] }}</div>
<div class="data-row"><strong>四阶段人数:</strong> {{ stage[4] }}</div>
<h2>人数分布图表</h2>
<div class="chart-container">
<canvas id="stage-chart"></canvas>
</div>
</div>
<h1>题目详情</h1>
<div class="card" v-for="(item, index) in question" :key="index">
<h2>{{ index + 1 }}. {{ item.question }}</h2>
<div class="data-row"><strong>答题统计:</strong></div>
<div class="choice">
<div>A: {{ item.choice_counts.A }}</div>
<div>B: {{ item.choice_counts.B }}</div>
<div>C: {{ item.choice_counts.C }}</div>
<div>D: {{ item.choice_counts.D }}</div>
</div>
<div class="data-row"><strong>回答次数:</strong> {{ item.response_count }}</div>
<div class="data-row"><strong>均分(期望为1.5):</strong> {{ item.average_score }}</div>
<h3>题目相关性
<button @click="toggleRelaVisibility(index)" class="bt">
{{ relaVisible[index] ? '隐藏相关系数' : '显示相关系数' }}
</button>
</h3>
<ul v-if="relaVisible[index]">
<li v-for="(value, key) in getRelaValues(index)" :key="key">与{{ key }}的相关系数为: {{ value }}</li>
</ul>
<canvas :id="'chart-' + index"></canvas>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue';
import axios from 'axios';
import { Chart, CategoryScale, BarController, BarElement, PointElement, LinearScale, Title, PieController, ArcElement, Tooltip, Legend } from 'chart.js';
import { useRouter } from 'vue-router';
import { useUrlStore } from '@/store/urlStore'; // 确保路径正确
const urlStore = useUrlStore(); // 使用URL Store
const router = useRouter();
Chart.register(
CategoryScale,
BarController,
BarElement,
PointElement,
LinearScale,
Title,
PieController,
ArcElement,
Tooltip,
Legend
);
const question = ref([]);
const stage = ref({});
const rela = ref({});
const relaVisible = ref([]);
const fetchData = async () => {
try {
const response = await axios.post(urlStore.urls.data);
if (response.data.errcode === 0) {
question.value = response.data.msg.question;
stage.value = response.data.msg.stage;
rela.value = response.data.msg.rela;
relaVisible.value = new Array(question.value.length).fill(false);
await nextTick();
renderCharts();
renderStageChart();
} else {
console.error('Error fetching data:', response.data.msg);
}
} catch (error) {
console.error('Error fetching data:', error);
}
};
const formatNumber = (number) => number.toFixed(3);
const getRelaValues = (index) => {
const key = `Q${index + 1}`;
const values = rela.value[key] || {};
const formattedValues = {};
Object.keys(values).forEach(k => {
formattedValues[k] = formatNumber(values[k]);
});
return formattedValues;
};
const toggleRelaVisibility = (index) => {
relaVisible.value[index] = !relaVisible.value[index];
};
const renderCharts = () => {
question.value.forEach((item, index) => {
const ctx = document.getElementById('chart-' + index).getContext('2d');
const correlations = getRelaValues(index);
const correlationValues = Object.values(correlations).map(value => parseFloat(value));
const backgroundColors = correlationValues.map(value =>
value < 0 ? 'rgba(255, 99, 132, 0.5)' : 'rgba(75, 192, 192, 0.5)'
);
new Chart(ctx, {
type: 'bar',
data: {
labels: Object.keys(correlations),
datasets: [{
label: `Correlations for Q${index + 1}`,
data: correlationValues,
backgroundColor: backgroundColors,
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
type: 'category',
display: true,
title: {
display: true,
text: 'Questions'
}
},
y: {
type: 'linear',
beginAtZero: true,
display: true,
title: {
display: true,
text: 'Correlation Value'
}
}
}
}
});
});
};
const renderStageChart = () => {
const ctx = document.getElementById('stage-chart').getContext('2d');
const stageData = [
stage.value['1'] - stage.value['2'],
stage.value['2'] - stage.value['3'],
stage.value['3'] - stage.value['4'],
stage.value['4']
];
new Chart(ctx, {
type: 'pie',
data: {
labels: ['stage1', 'stage2', 'stage3', 'stage4'],
datasets: [{
data: stageData,
backgroundColor: [
'rgba(255, 99, 132, 0.6)',
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)',
'rgba(75, 192, 192, 0.6)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right'
},
tooltip: {
enabled: true
}
}
}
});
};
function rt(){
alert("切换成功,一秒后跳转编辑页面");
setTimeout(() => { router.push({ path: "/home" }) }, 1000);
}
onMounted(fetchData);
</script>
<style scoped>
.chart-container {
width: 60%;
margin: auto;
}
.header {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.custom-button {
position: absolute;
top: 0;
right: 0;
background-color: #f0adcd;
color: white;
border: none;
width: 120px;
height: 30px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
}
.custom-button:hover {
background-color: #e5347d;
}
.bt {
background-color: #77d6dd;
width: 150px;
height: 29%;
}
.container {
padding: 20px;
font-family: 'Arial', sans-serif;
color: #333;
display: flex;
flex-direction: column;
width: 700px;
margin-left: 15vw;
}
h1 {
font-size: 32px;
text-align: center;
margin-bottom: 40px;
color: #2c3e50;
}
.card {
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin-bottom: 40px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: scale(1.005);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.card h2 {
font-size: 24px;
color: #3498db;
margin-bottom: 10px;
}
.data-row {
font-size: 16px;
margin: 8px 0;
}
.choice {
margin-left: 20px;
}
.card h3 {
font-size: 20px;
color: #2c3e50;
margin-top: 20px;
}
.card ul {
list-style-type: none;
padding: 0;
}
.card li {
font-size: 14px;
color: #555;
margin-bottom: 5px;
}
canvas {
margin-top: 20px;
}
</style>
以上是前端的完整代码,你可以自己修改样式。
效果展示
在questionlist里加入一个按钮,来跳转数据处理页面,很简单就不说了。