活动介绍

实现实时投票更新:Vue.js中的WebSocket应用

立即解锁
发布时间: 2024-04-02 11:43:52 阅读量: 90 订阅数: 37
# 1. 介绍 - 1.1 什么是实时投票更新? - 1.2 WebSocket在Web开发中的应用场景 - 1.3 本文要解决的问题 # 2. Vue.js基础 在本章中,我们将深入了解Vue.js的基础知识,包括Vue.js的简介、Vue组件化开发以及Vue数据绑定和响应式原理。让我们一起来探索Vue.js这个流行的JavaScript框架。 # 3. WebSocket技术介绍 WebSocket技术在现代Web开发中扮演着重要的角色,它提供了一种全双工通信机制,允许客户端和服务器之间进行实时、低延迟的数据交换。本章将介绍WebSocket的基本概念以及与HTTP协议的对比,以便更好地理解在Vue.js中集成WebSocket的实现原理。 ### 3.1 WebSocket是什么? WebSocket是一种网络协议,它提供了基于TCP的持久化的全双工通信,允许客户端和服务器之间进行实时数据传输。传统的HTTP协议是无状态的,每次请求都需要建立连接、发送请求、接收响应,然后断开连接,无法实现持续的数据交换。而WebSocket在建立连接后可以保持连接状态,双方可以随时发送消息。 ### 3.2 WebSocket与HTTP协议的对比 - **HTTP协议**: - 基于请求-响应模式,单向通信。 - 每次请求都需要重新建立连接,状态不保存。 - 无法做到实时通信,需要轮询或长连接来模拟实时性。 - **WebSocket协议**: - 基于消息的双向通信。 - 建立连接后保持通信状态,实现实时数据传输。 - 较低的通信开销和延迟,适合实时应用场景。 ### 3.3 WebSocket如何实现实时通信 WebSocket的通信过程主要包括三个阶段: 1. **握手阶段**:客户端发起WebSocket连接请求,服务器响应并建立连接。 2. **数据传输阶段**:双方可以随时发送消息,实现实时通信。 3. **断开连接阶段**:通信结束后,客户端或服务器可以选择关闭连接。 在Vue.js中集成WebSocket,我们可以利用其实时通信的特性,实现诸如实时投票更新、聊天室等功能,为用户提供更加流畅的交互体验。 # 4. 在Vue.js中集成WebSocket 在这一章中,我们将讨论如何在Vue.js应用程序中集成WebSocket,实现实时通信功能。WebSocket是一种在Web浏览器和服务器之间进行全双工通信的协议,它可以让服务器主动向客户端推送数据,实现实时更新的功能。在Vue.js中使用WebSocket可以为我们的应用带来更好的用户体验,让数据更新更加及时。 ### 4.1 选择合适的WebSocket库 在Vue.js中集成WebSocket,首先需要选择合适的WebSocket库。常用的WebSocket库有`Socket.io`、`Vue-native-websocket`等。这里我们以`Vue-native-websocket`为例进行讲解。 ### 4.2 在Vue.js中引入WebSocket 首先,在Vue.js项目中安装`Vue-native-websocket`库: ```bash npm install vue-native-websocket ``` 然后,在Vue.js应用的入口文件(如`main.js`)中引入WebSocket并配置: ```javascript import VueNativeSock from 'vue-native-websocket'; Vue.use(VueNativeSock, 'ws://localhost:8080', { format: 'json', reconnection: true, // 自动重新连接 reconnectionAttempts: 5, // 重新连接尝试次数 reconnectionDelay: 3000, // 重新连接延迟时间 }); ``` ### 4.3 建立WebSocket连接并实现消息传递 现在我们已经在Vue.js项目中引入了WebSocket,并配置了连接信息。接下来就可以在Vue组件中实现WebSocket的连接和消息传递,例如: ```javascript export default { data() { return { messages: [], }; }, mounted() { this.$socket.addEventListener('message', (event) => { this.messages.push(event.data); }); }, }; ``` 在上述示例代码中,我们监听了WebSocket的`message`事件,在接收到消息时将消息添加到`messages`数组中,从而实现前端页面的实时更新。 通过以上步骤,我们成功在Vue.js中集成了WebSocket,并实现了简单的消息传递功能。在接下来的章节中,我们将进一步探讨如何利用WebSocket实现实时投票更新功能。 # 5. 实现实时投票更新功能 在这一章中,我们将介绍如何使用Vue.js和WebSocket技术实现实时投票更新功能。通过构建投票系统前端页面,并使用WebSocket建立实时连接,我们可以实现投票数据的实时同步和更新。让我们开始吧! ### 5.1 构建投票系统前端页面 首先,我们需要构建一个简单的投票系统前端页面,包括投票选项和实时更新的投票结果显示。以下是一个简单的HTML和Vue组件示例: ```html <!-- index.html --> <!DOCTYPE html> <html> <head> <title>实时投票系统</title> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script> </head> <body> <div id="app"> <h1>请选择您的投票选项:</h1> <button @click="vote('Option A')">Option A</button> <button @click="vote('Option B')">Option B</button> <h2>投票结果实时更新:</h2> <p>Option A: {{ votes['Option A'] || 0 }} 票</p> <p>Option B: {{ votes['Option B'] || 0 }} 票</p> </div> <script src="app.js"></script> </body> </html> ``` ```javascript // app.js const app = new Vue({ el: '#app', data: { votes: { 'Option A': 0, 'Option B': 0 } }, methods: { vote(option) { this.votes[option]++; // 在这里发送投票数据到后端服务器 } } }); ``` ### 5.2 使用WebSocket实现实时投票更新功能 接下来,我们将集成WebSocket实现实时投票更新功能。首先,我们需要在后端服务器中实现WebSocket服务端,然后在前端页面中建立WebSocket连接,监听来自服务器的投票更新消息。以下是一个简单的WebSocket实现示例: ```javascript // 在Vue.js中添加WebSocket连接 const ws = new WebSocket('ws://localhost:3000'); // WebSocket连接的地址 ws.onmessage = function(event) { const data = JSON.parse(event.data); app.votes = data.votes; }; ``` ### 5.3 处理投票数据的同步和更新 当用户在前端页面中进行投票操作时,通过WebSocket实时传输投票数据到后端服务器,并进行数据同步更新。后端服务器接收到新的投票数据后,广播给所有连接的客户端,实现投票结果的实时更新。 通过以上步骤,我们成功实现了在Vue.js中使用WebSocket技术实现实时投票更新功能。读者可以根据实际需求对投票系统进行进一步优化和扩展。 # 6. 优化与扩展 在这一章中,我们将讨论如何优化现有的实时投票更新功能,并且考虑如何扩展系统以满足更多需求。 ### 6.1 性能优化:减少消息传输量 为了提高系统性能,我们可以考虑减少消息传输的数据量。一种常见的做法是只传输数据的变化部分,而不是每次都发送完整的数据。这可以通过在前端进行数据计算,只发送变化的部分给后端,再由后端进行广播给所有连接的客户端实现。这种方式可以减少网络传输量,提高系统响应速度。 ```python # Python 示例代码 # 前端计算投票变化部分 old_votes = { "option1": 10, "option2": 15 } new_votes = { "option1": 12, "option2": 15 } vote_changes = {} for key in new_votes: if new_votes[key] != old_votes.get(key): vote_changes[key] = new_votes[key] # 发送 vote_changes 到后端处理 ``` ### 6.2 安全性考虑:防范WebSocket通信漏洞 在实时投票更新中,安全性是至关重要的。为了防范WebSocket通信的漏洞,我们可以考虑以下几点: - 使用安全的WebSocket连接(wss://)而不是普通的ws://连接,确保数据传输加密。 - 对用户输入进行有效性验证,防止恶意用户发送非法数据给服务器。 - 实现安全的身份验证和授权机制,确保只有授权用户可以进行投票操作。 ### 6.3 功能扩展:添加更多实时更新功能 除了实时投票更新功能,我们还可以考虑添加更多的实时更新功能来丰富系统。例如: - 实时聊天功能:添加一个聊天室,让用户可以实时交流。 - 实时通知功能:向用户发送实时通知,比如投票结果更新、活动提醒等。 - 实时数据可视化:通过实时更新展示数据的可视化图表,让用户更直观地了解信息。 通过不断扩展实时更新功能,可以提升用户体验,让系统更加丰富和有趣。 在这一章中,我们探讨了如何优化系统性能、保障系统安全性以及扩展实时更新功能,希朝能为实时投票更新系统的进一步发展提供一些思路和建议。
corwn 最低0.47元/天 解锁专栏
赠100次下载
继续阅读 点击查看下一篇
profit 400次 会员资源下载次数
profit 300万+ 优质博客文章
profit 1000万+ 优质下载资源
profit 1000万+ 优质文库回答
复制全文

相关推荐

根据以上代码,整理优化下面代码使之能够和前端代码运行 from fastapi import FastAPI, WebSocket, HTTPException from pydantic import BaseModel from typing import Dict import databases import sqlalchemy from contextlib import asynccontextmanager import uvicorn import logging # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 数据库配置 DATABASE_URL = "mysql://root:123456@localhost/dbname" database = databases.Database(DATABASE_URL) metadata = sqlalchemy.MetaData() # 问卷表结构 polls = sqlalchemy.Table( "polls", metadata, sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True), sqlalchemy.Column("question", sqlalchemy.String(255)), sqlalchemy.Column("options", sqlalchemy.JSON), ) class PollManager: def __init__(self): self.connections = set() async def broadcast(self, data: dict): for connection in self.connections: await connection.send_json(data) manager = PollManager() @asynccontextmanager async def lifespan(app: FastAPI): # 数据库连接 try: await database.connect() logger.info("✅ 成功连接到数据库") await init_poll() except Exception as e: logger.error(f"❌ 数据库连接失败: {str(e)}") raise yield # 数据库断开 try: await database.disconnect() logger.info("⏏️ 已断开数据库连接") except Exception as e: logger.error(f"❌ 数据库断开异常: {str(e)}") app = FastAPI(lifespan=lifespan) # 数据模型 class VoteRequest(BaseModel): option_id: int # REST API @app.get("/api/poll") async def get_poll(): query = polls.select() return await database.fetch_one(query) @app.post("/api/poll/vote") async def submit_vote(vote: VoteRequest): try: # 原子更新操作 query = polls.update().values( options=sqlalchemy.func.json_set( polls.c.options, f'$[{"*" if vote.option_id == 2 else vote.option_id}].votes', polls.c.options[vote.option_id]['votes'] + 1 ) ) await database.execute(query) current = await get_poll() await manager.broadcast(current) return {"status": "success"} except Exception as e: logger.error(f"投票失败: {str(e)}") raise HTTPException(status_code=500, detail="投票处理失败") # WebSocket @app.websocket("/ws/poll") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() manager.connections.add(websocket) try: while True: await websocket.receive_text() except: manager.connections.remove(websocket) await websocket.close() async def init_poll(): try: existing = await database.fetch_one(polls.select()) if not existing: init_data = { "question": "您最喜欢的编程语言是?", "options": [ {"id": 0, "text": "Python", "votes": 0}, {"id": 1, "text": "JavaScript", "votes": 0}, {"id": 2, "text": "Java", "votes": 0} ] } await database.execute(polls.insert().values(**init_data)) logger.info("✅ 初始化问卷数据成功") else: logger.info("ℹ️ 问卷数据已存在,跳过初始化") except Exception as e: logger.error(f"初始化问卷失败: {str(e)}") raise if __name__ == "__main__": uvicorn.run( "main:app", host="0.0.0.0", port=8000, reload=True, log_level="info", access_log=True )

<template> {{ alertMessage }} 投票人:{{ voterName }} 身份证:{{ formattedIdCard }} 经理投票 {{ votes[17] }} / 5 厂长投票 {{ votes[18] }} / 5 副厂长投票 {{ votes[19] }} / 15 总票数 {{ totalVotes }} / 25 {{ voter.name }} ID: {{ voter.id }} <button @click="castVote(voter, 17)" :disabled="!canVote(voter, 17)" :class="{ 'selected': voter.vote === 17, 'disabled': !canVote(voter, 17) }" > 经理 </button> <button @click="castVote(voter, 18)" :disabled="!canVote(voter, 18)" :class="{ 'selected': voter.vote === 18, 'disabled': !canVote(voter, 18) }" > 厂长 </button> <button @click="castVote(voter, 19)" :disabled="!canVote(voter, 19)" :class="{ 'selected': voter.vote === 19, 'disabled': !canVote(voter, 19) }" > 副厂长 </button> <button @click="submitVotes" :disabled="!hasSelectedCandidates || isSubmitting">提交投票</button> <button @click="resetVotes" :disabled="isSubmitting">重置投票</button> </template> <script setup> import { ref, reactive, computed } from 'vue'; import { useRoute, useRouter} from 'vue-router'; import { onMounted } from 'vue'; const route = useRoute(); const router = useRouter() const voters = ref([]); //候选人 // 使用props接收参数 const props = defineProps({ queryParams: { type: Object, default: () => ({}) } }) // 使用计算属性 const activeSurvey = ref({ surveyId: route.params.surveyId,//调查令 qydcl: route.params.qydcl, dclx: route.query.dclx, tffs: route.query.tffs, bt: route.query.bt }); onMounted(async() => { // 确保路由完全解析完成 await router.isReady() // console.log('当前路由对象:', route) // console.log('路径参数:', route.params) // 获取params参数 // console.log('查询参数:', route.query) // 获取query参数 }); // 添加消息提示状态 const alertMessage = ref(''); const showAlert = ref(false); const alertType = ref(''); // 'success' 或 'error' // 安全序列化函数 function safeStringify(obj) { const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { // 检测到循环引用,返回占位符或跳过 return "[Circular Reference Removed]"; } seen.add(value); } return value; }); } // onMounted生命周期钩子 onMounted(async () => { // 从sessionStorage获取投票人信息并立即清除 const voterInfo = sessionStorage.getItem('voterInfo'); if (voterInfo) { const { name, idCard } = JSON.parse(voterInfo); voterName.value = name; voterIdCard.value = idCard; sessionStorage.removeItem('voterInfo'); } // 获取投票详情 const route = useRoute(); // 加载候选人数据 voters.value = await fetchCandidates(); }); // 添加用于存储投票人信息的变量 const voterName = ref(''); const voterIdCard = ref(''); // 格式化身份证显示(安全脱敏) const formattedIdCard = computed(() => { if (!voterIdCard.value) return ''; // 显示前6位和后4位,中间用*代替 return voterIdCard.value.substring(0, 6) + '******' + voterIdCard.value.substring(voterIdCard.value.length - 4); }); onMounted(() => { // 从sessionStorage获取数据并立即清除 const voterInfo = sessionStorage.getItem('voterInfo'); if (voterInfo) { const { name, idCard } = JSON.parse(voterInfo); voterName.value = name; voterIdCard.value = idCard; // 关键:立即清除存储防止数据残留 sessionStorage.removeItem('voterInfo'); } }); //获取候选人明细 const fetchCandidates = async () => { try { const response = await fetch('/api/wechat/getInvestigate', { method: 'POST', body: JSON.stringify({ id: '9', dcl: '123' }) }); // console.log('API响应:', response); const result = await response.json(); if (!result || !result.root) throw new Error('无效API响应'); // 提取候选人数据 const candidateArray = []; let idCounter = 1; // 自增计数器,名称序号 result.root.forEach(rootItem => { if (!rootItem.childEntList) return; rootItem.childEntList.forEach(candidate => { if (!candidate.dcbt || !candidate.dcxbt) return; candidateArray.push({ name: candidate.dcxbt, vote: null, id:idCounter++, tmlx: candidate.tmlx, // 投票类型 mainId: candidate.mainId, // 主ID dcbt: candidate.dcbt, // 投票标题 }); }); }); // console.log('候选人数据:', candidateArray); return candidateArray; } catch (error) { console.error('获取候选人失败:', error); return []; // 返回空数组保持安全 } }; // 新增计算属性 - 获取已选择的候选人 const selectedCandidates = computed(() => { return voters.value .filter(v => v.vote) // 只过滤已投票的 .map(v => ({ mainId: v.mainId, // 候选人原始ID voteType: v.vote, // 投票类型 voteValue: v.vote, name: v.name, // 候选人姓名 tmlx: v.tmlx, // 原始类型 dcbt: v.dcbt // 投票标题 })); }); // 检查是否有选择的候选人 const hasSelectedCandidates = computed(() => { return selectedCandidates.value.length > 0; }); //获取并保存第一个候选人的mainId const mainIdToSubmit = ref(null); const firstCandidateMainId = computed(() => { return selectedCandidates.value[0]?.mainId || null; }); // 投票统计 const votes = reactive({ 17: 0, 18: 0, 19: 0 }); // 计算总票数 const totalVotes = computed(() => { return votes[17] + votes[18] + votes[19]; }); // 检查投票资格 const canVote = (voter, type) => { // 当前投票类型限额配置 const limits = { 17: 5, 18: 5, 19: 15 }; // 情况1:用户取消当前选择的类型(总是允许) if (voter.vote === type) return true; // 情况2:用户切换投票类型 if (voter.vote) { // 检查新类型是否达到限额 if (votes[type] >= limits[type]) return false; } // 情况3:用户首次投票 else { // 检查新类型是否达到限额和总票数 if (votes[type] >= limits[type] || totalVotes.value >= 25) return false; } return true; }; // 投票方法 const castVote = (voter, voteValue) => { // 如果已投票且点击相同类型,取消投票 if (voter.vote === voteValue) { voter.vote = null; votes[voteValue]--; return; } // 如果之前有投票,先取消 if (voter.vote !== null) { votes[voter.vote]--; } // 投新票 voter.vote = voteValue; votes[voteValue]++; }; //投票人信息 // 添加投票人信息数据模型 const voterInfo = reactive({ name: '', idNumber: '' }); // 提交投票 // 防止重复提交 const isSubmitting = ref(false); // 提交投票到API const submitVotes = async () => { // console.log("dclx", activeSurvey.value.dclx); // 添加防御性检查 if (!hasSelectedCandidates.value) { showMessage('请先选择候选人', 'error'); return; } // 防止重复提交 if (isSubmitting.value) return; isSubmitting.value = true; // 设置mainId值 mainIdToSubmit.value = firstCandidateMainId.value; try { const mainData = [ { fieldName: "bt", fieldValue: activeSurvey.value.bt }, { fieldName: "tprxm", fieldValue: voterName.value }, { fieldName: "tprsfz", fieldValue: voterIdCard.value }, { fieldName: "mb", fieldValue: mainIdToSubmit.value }, { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, { fieldName: "tffs", fieldValue: activeSurvey.value.tffs } ]; const workflowRequestTableRecords = selectedCandidates.value.map(candidate => ({ workflowRequestTableFields: [ { fieldName: "dcbt", fieldValue: candidate.dcbt }, { fieldName: "tmlx", fieldValue: candidate.tmlx }, { fieldName: "dcxx", fieldValue: candidate.vote === 'A' ? 17 : candidate.vote === 'B' ? 18 : 19 } ] })); // console.log('提交数据:', { // mainData, // workflowRequestTableRecords // }); const requestBody = { requestName: activeSurvey.value.bt, // 投票标题 workflowId: 118, // 固定工作流ID mainData, workflowRequestTableRecords }; // 发送POST请求 const response = await fetch('/api/wechat/addInvestigateWorkflow1', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: safeStringify(requestBody)// 使用安全序列化,避免重复引用 }); const result = await response.json(); // 根据 api_status 判断 if (result.api_status === true) { // 成功处理 const successMsg = result.msg || '投票提交成功!'; showMessage('投票提交成功!', 'success'); // 存储已投票标识 localStorage.setItem('voted_' + voterIdCard.value, 'true'); } else { // 处理错误情况 if (result.msg === '你已提交或不满足提交条件') { showMessage(result.msg, 'error'); // 特殊处理:用户已投票,存储标识防止重复提交 localStorage.setItem('voted_' + voterIdCard.value, 'true'); } else { // 其他错误情况 const errorMsg = result.msg ? 提交失败: ${result.msg} : '未知错误,请稍后重试'; showMessage(errorMsg, 'error'); } } } catch (error) { console.error('网络请求失败:', error); showMessage('网络错误,请检查连接后重试', 'error'); } finally { isSubmitting.value = false; } }; // 重置投票 const resetVotes = () => { if (confirm('确定要重置所有投票吗?')) { voters.value.forEach(voter => { voter.vote = null; }); votes.A = 0; votes.B = 0; votes.C = 0; voterInfo.name = ''; voterInfo.idNumber = ''; } }; //后台响应信息showMessage const showMessage = (message, type = 'error') => { // 更新消息提示状态 alertMessage.value = message; showAlert.value = true; alertType.value = type; // 错误提示停留3秒,成功提示停留2秒 const timeout = type === 'error' ? 3000 : 2000; setTimeout(() => { showAlert.value = false; }, timeout); }; </script> <style scoped> /* 移动端垂直布局 */ @media (max-width: 480px) { .input-group { flex-direction: column; } } /* 平板/桌面端水平布局 */ @media (min-width: 768px) { .input-group { flex-direction: row; } } /* 消息提示样式 */ .alert { padding: 15px; margin-bottom: 20px; border-radius: 4px; text-align: center; position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 1000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); min-width: 300px; opacity: 0.95; } .alert.error { background-color: #ffebee; color: #b71c1c; border: 1px solid #ffcdd2; } .alert.success { background-color: #e8f5e9; color: #1b5e20; border: 1px solid #c8e6c9; } .vote-container { max-width: 1200px; margin: 0 auto; padding: 20px; } .stats { display: flex; justify-content: space-between; margin-bottom: 30px; background: #f5f7fa; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .stat { flex: 1; text-align: center; padding: 0 15px; } .progress { height: 20px; background: #e0e0e0; border-radius: 10px; margin: 10px 0; overflow: hidden; } .progress-bar { height: 100%; background: #3498db; transition: width 0.3s; } .voters-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 20px; } .voter-card { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s; } .voter-card:hover { transform: translateY(-5px); } .voter-id { color: #777; font-size: 0.9rem; margin-bottom: 15px; } .vote-options { display: flex; justify-content: space-between; } .vote-options button { flex: 1; margin: 0 5px; padding: 8px 0; border: none; border-radius: 4px; cursor: pointer; transition: all 0.2s; } .vote-options button:not(.selected):hover { opacity: 0.9; transform: scale(1.05); } .vote-options button:first-child { background: #ff6b6b; color: white; } .vote-options button:nth-child(2) { background: #4ecdc4; color: white; } .vote-options button:last-child { background: #ffd166; color: white; } .selected { border: 2px solid #2c3e50 !important; font-weight: bold; box-shadow: 0 0 2 rgba(61, 60, 60, 0.5); } .disabled { opacity: 0.5 !important; cursor: not-allowed !important; } .action-buttons { margin-top: 30px; display: flex; justify-content: center; gap: 20px; } .action-buttons button { padding: 12px 30px; border: none; border-radius: 6px; cursor: pointer; font-size: 1rem; font-weight: 600; transition: all 0.2s; } .action-buttons button:first-child { background: #3498db; color: white; } .action-buttons button:first-child:disabled { background: #bdc3c7; cursor: not-allowed; } .action-buttons button:last-child { background: #e74c3c; color: white; } .action-buttons button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } </style>浏览器报错:Uncaught (in promise) TypeError: _ctx.canVote is not a function at eval (VM10454 Vote.vue:88:23) at renderList (runtime-core.esm-bundler.js:2904:1) at Proxy.render (VM10454 Vote.vue:82:552) at renderComponentRoot (runtime-core.esm-bundler.js:6501:1) at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:5397:1) at ReactiveEffect.run (reactivity.esm-bundler.js:225:1) at ReactiveEffect.runIfDirty (reactivity.esm-bundler.js:263:1) at callWithErrorHandling (runtime-core.esm-bundler.js:199:1) at flushJobs (runtime-core.esm-bundler.js:408:1) eval @ VM10454 Vote.vue:88 renderList @ runtime-core.esm-bundler.js:2904 render @ VM10454 Vote.vue:82 renderComponentRoot @ runtime-core.esm-bundler.js:6501 componentUpdateFn @ runtime-core.esm-bundler.js:5397 run @ reactivity.esm-bundler.js:225 runIfDirty @ reactivity.esm-bundler.js:263 callWithErrorHandling @ runtime-core.esm-bundler.js:199 flushJobs @ runtime-core.esm-bundler.js:408 Promise.then queueFlush @ runtime-core.esm-bundler.js:322 queueJob @ runtime-core.esm-bundler.js:317 effect.scheduler @ runtime-core.esm-bundler.js:5448 trigger @ reactivity.esm-bundler.js:253 endBatch @ reactivity.esm-bundler.js:311 notify @ reactivity.esm-bundler.js:597 trigger @ reactivity.esm-bundler.js:571 set value @ reactivity.esm-bundler.js:1448 eval @ VM10456 Vote.vue:94 await in eval eval @ runtime-core.esm-bundler.js:2815 callWithErrorHandling @ runtime-core.esm-bundler.js:199 callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206 hook.__weh.hook.__weh @ runtime-core.esm-bundler.js:2795 flushPostFlushCbs @ runtime-core.esm-bundler.js:385 flushJobs @ runtime-core.esm-bundler.js:427 Promise.then queueFlush @ runtime-core.esm-bundler.js:322 queueJob @ runtime-core.esm-bundler.js:317 reload @ runtime-core.esm-bundler.js:527 eval @ runtime-core.esm-bundler.js:561 eval @ Vote.vue:15 ./src/views/Vote.vue @ about.js:1355 __webpack_require__ @ app.js:308 _requireSelf @ app.js:599 apply @ app.js:1335 (匿名) @ app.js:839 internalApply @ app.js:837 (匿名) @ app.js:775 waitForBlockingPromises @ app.js:730 (匿名) @ app.js:773 Promise.then (匿名) @ app.js:772 Promise.then (匿名) @ app.js:753 Promise.then hotCheck @ app.js:744 check @ dev-server.js:15 eval @ dev-server.js:69 emit @ events.js:153 reloadApp @ reloadApp.js:38 ok @ index.js:227 eval @ socket.js:62 client.onmessage @ WebSocketClient.js:45

请将投票问题,选项以及投票次数都计入数据库,投票结果也从数据库拿出数据。给出所有代码 from fastapi import FastAPI, WebSocket, HTTPException from fastapi.middleware.cors import CORSMiddleware # 新增CORS支持 from pydantic import BaseModel from typing import Dict import databases import sqlalchemy from contextlib import asynccontextmanager import uvicorn import logging # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 数据库配置(请确认MySQL服务已启动) DATABASE_URL = "mysql+aiomysql://root:123456@localhost/dbname" database = databases.Database(DATABASE_URL) metadata = sqlalchemy.MetaData() # 问卷表结构 polls = sqlalchemy.Table( "polls", metadata, sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True), sqlalchemy.Column("question", sqlalchemy.String(255)), sqlalchemy.Column("options", sqlalchemy.JSON), ) class PollManager: def __init__(self): self.connections = set() async def broadcast(self, data: dict): for connection in self.connections: try: await connection.send_json(data) except Exception as e: logger.error(f"广播失败: {str(e)}") self.connections.remove(connection) manager = PollManager() @asynccontextmanager async def lifespan(app: FastAPI): # 数据库连接 try: await database.connect() logger.info("✅ 成功连接到数据库") await init_poll() except Exception as e: logger.error(f"❌ 数据库连接失败: {str(e)}") raise yield # 数据库断开 try: await database.disconnect() logger.info("⏏️ 已断开数据库连接") except Exception as e: logger.error(f"❌ 数据库断开异常: {str(e)}") app = FastAPI(lifespan=lifespan) # 添加CORS中间件 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class VoteRequest(BaseModel): option_id: int @app.get("/api/poll") async def get_poll(): query = polls.select() result = await database.fetch_one(query) return dict(result) if result else {} @app.post("/api/poll/vote") async def submit_vote(vote: VoteRequest): try: # 使用JSON_SET更新MySQL的JSON字段 update_query = polls.update().values( options=sqlalchemy.func.json_set( polls.c.options, f'$[{vote.option_id}].votes', polls.c.options[vote.option_id]['votes'] + 1 ) ) await database.execute(update_query) current = await database.fetch_one(polls.select()) await manager.broadcast(dict(current)) return {"status": "success"} except Exception as e: logger.error(f"投票失败: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) # 添加获取结果的端点(在原有代码基础上新增) @app.get("/api/poll/results") async def get_results(): try: query = polls.select() result = await database.fetch_one(query) if not result: raise HTTPException(status_code=404, detail="Poll not found") # 转换数据结构为前端需要的格式 return { "options": { opt["text"]: opt["votes"] for opt in result["options"] } } except Exception as e: logger.error(f"获取结果失败: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) # 修改WebSocket端点路径(原代码修改) @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() manager.connections.add(websocket) try: while True: await websocket.receive_text() except Exception as e: logger.info(f"客户端断开: {str(e)}") finally: manager.connections.remove(websocket) async def init_poll(): try: if not await database.fetch_one(polls.select()): init_data = { "question": "您最喜欢的编程语言是?", "options": [ {"id": 0, "text": "Python", "votes": 0}, {"id": 1, "text": "JavaScript", "votes": 0}, {"id": 2, "text": "Java", "votes": 0} ] } await database.execute(polls.insert().values(**init_data)) logger.info("✅ 初始化问卷数据成功") except Exception as e: logger.error(f"初始化失败: {str(e)}") raise if __name__ == "__main__": # 开发环境启动命令:uvicorn main:app --reload --host 0.0.0.0 --port 8000 uvicorn.run( app="main:app", host="0.0.0.0", port=8000, reload=True )

张诚01

知名公司技术专家
09级浙大计算机硕士,曾在多个知名公司担任技术专家和团队领导,有超过10年的前端和移动开发经验,主导过多个大型项目的开发和优化,精通React、Vue等主流前端框架。
最低0.47元/天 解锁专栏
赠100次下载
百万级 高质量VIP文章无限畅学
千万级 优质资源任意下载
千万级 优质文库回答免费看
专栏简介
本专栏旨在教授如何利用Vue.js技术构建强大的投票系统。从初学者指南到高级技巧,涵盖了如何优化UI设计、管理状态、实现实时更新、提高性能等方面的内容。通过深入探讨Vue的计算属性、Mixins技术、虚拟列表技术等,读者将学会构建响应式设计、实现数据动态加载以及提升用户交互体验的方法。同时,重点介绍了Vuex中的持久化状态管理和Vue.js单元测试实践,确保投票系统功能稳定性和数据不丢失。最后,还将分享如何构建Vue SSR应用、封装可复用的组件库来优化开发效率。通过本专栏,读者将全面掌握构建高效投票系统的技巧和策略。

最新推荐

ESP8266小电视性能测试与调优秘籍:稳定运行的关键步骤(专家版)

![ESP8266小电视性能测试与调优秘籍:稳定运行的关键步骤(专家版)](https://www.espboards.dev/img/lFyodylsbP-900.png) # 摘要 本文全面探讨了ESP8266小电视的基本概念、原理、性能测试、问题诊断与解决以及性能调优技巧。首先,介绍了ESP8266小电视的基本概念和工作原理,随后阐述了性能测试的理论基础和实际测试方法,包括测试环境的搭建和性能测试结果的分析。文章第三章重点描述了性能问题的诊断方法和常见问题的解决策略,包括内存泄漏和网络延迟的优化。在第四章中,详细讨论了性能调优的理论和实践,包括软件和硬件优化技巧。最后,第五章着重探讨了

【STM32f107vc串口通信】:精通串口通信及其与FreeRTOS的完美集成

![【STM32f107vc串口通信】:精通串口通信及其与FreeRTOS的完美集成](https://bkimg.cdn.bcebos.com/pic/4bed2e738bd4b31c8701ac6c6b99307f9e2f0608529e?x-bce-process=image/format,f_auto) # 摘要 本论文详细探讨了STM32F107VC微控制器与串口通信的基础知识、机制、软件实现、在FreeRTOS环境下的编程实践、综合应用案例以及性能调优与故障诊断。首先介绍串口通信的基础理论和STM32F107VC的串口硬件特性,随后阐述在FreeRTOS操作系统中如何进行串口编程

【智能调度系统的构建】:基于矢量数据的地铁调度优化方案,效率提升50%

# 摘要 随着城市地铁系统的迅速发展,智能调度系统成为提升地铁运营效率与安全的关键技术。本文首先概述了智能调度系统的概念及其在地铁调度中的重要性。随后,文章深入探讨了矢量数据在地铁调度中的应用及其挑战,并回顾了传统调度算法,同时提出矢量数据驱动下的调度算法创新。在方法论章节中,本文讨论了数据收集、处理、调度算法设计与实现以及模拟测试与验证的方法。在实践应用部分,文章分析了智能调度系统的部署、运行和优化案例,并探讨了系统面临的挑战与应对策略。最后,本文展望了人工智能、大数据技术与边缘计算在智能调度系统中的应用前景,并对未来研究方向进行了展望。 # 关键字 智能调度系统;矢量数据;调度算法;数据

Shopee上架工具扩展性升级:掌握多店铺同步的终极方案

![Shopee上架工具扩展性升级:掌握多店铺同步的终极方案](https://ask.qcloudimg.com/http-save/yehe-1475574/696453895d391e6b0f0e27455ef79c8b.jpeg) # 摘要 Shopee店铺同步工具是一个为电商平台多店铺管理提供的解决方案,本文详细介绍了该工具的基本概念、多店铺同步的技术基础、实践应用以及进阶功能开发。通过探讨同步技术架构、数据库同步理论与实践、以及高级同步技术,本文旨在提供一个全面的同步工具实践框架。随后,文章深入分析了如何实现同步流程优化、增强工具扩展性,并开发了API集成、异常处理、用户权限管理

【管理策略探讨】:掌握ISO 8608标准在路面不平度控制中的关键

![【管理策略探讨】:掌握ISO 8608标准在路面不平度控制中的关键](https://assets.isu.pub/document-structure/221120190714-fc57240e57aae44b8ba910280e02df35/v1/a6d0e4888ce5e1ea00b7cdc2d1b3d5bf.jpeg) # 摘要 本文全面概述了ISO 8608标准及其在路面不平度测量与管理中的重要性。通过深入讨论路面不平度的定义、分类、测量技术以及数据处理方法,本文强调了该标准在确保路面质量控制和提高车辆行驶安全性方面的作用。文章还分析了ISO 8608标准在路面设计、养护和管理

英语学习工具开发总结:C#实现功能与性能的平衡

# 摘要 本文探讨了C#在英语学习工具中的应用,首先介绍了C#的基本概念及在英语学习工具中的作用。随后,详细分析了C#的核心特性,包括面向对象编程和基础类型系统,并探讨了开发环境的搭建,如Visual Studio的配置和.NET框架的安装。在关键技术部分,本文着重论述了用户界面设计、语言学习模块的开发以及多媒体交互设计。性能优化方面,文章分析了性能瓶颈并提出了相应的解决策略,同时分享了实际案例分析。最后,对英语学习工具市场进行了未来展望,包括市场趋势、云计算和人工智能技术在英语学习工具中的应用和创新方向。 # 关键字 C#;英语学习工具;面向对象编程;用户界面设计;性能优化;人工智能技术

SSD加密技术:确保数据安全的关键实现

![固态硬盘SSD原理详细介绍,固态硬盘原理详解,C,C++源码.zip](https://pansci.asia/wp-content/uploads/2022/11/%E5%9C%96%E8%A7%A3%E5%8D%8A%E5%B0%8E%E9%AB%94%EF%BC%9A%E5%BE%9E%E8%A8%AD%E8%A8%88%E3%80%81%E8%A3%BD%E7%A8%8B%E3%80%81%E6%87%89%E7%94%A8%E4%B8%80%E7%AA%BA%E7%94%A2%E6%A5%AD%E7%8F%BE%E6%B3%81%E8%88%87%E5%B1%95%E6%9C%9

FRET实验的高通量分析:自动化处理与高精度数据解读的十个技巧

![FRET实验的高通量分析:自动化处理与高精度数据解读的十个技巧](https://www.bmglabtech.com/hubfs/1_Webseite/5_Resources/Blogs/kinase-assays-fig4.webp) # 摘要 FRET( Förster共振能量转移)实验是生物物理和生物化学研究中一种广泛应用的技术,尤其在高通量分析中具有重要地位。本文从FRET实验的背景讲起,详细探讨了高通量自动化处理技巧、高精度数据解读的理论与实践,以及高级自动化与数据分析方法。文中分析了高通量实验设计、自动化工具的应用、数据采集和管理,以及解读数据分析的关键技术。进阶内容包括机

【Swing资源管理】:避免内存泄漏的实用技巧

![【Swing资源管理】:避免内存泄漏的实用技巧](https://opengraph.githubassets.com/a6710ff2c86c331c13363554d00aab3dd898536c00e1344fa99ef3cd2923e717/daggerok/findbugs-example) # 摘要 Swing资源管理对于提高Java桌面应用程序的性能和稳定性至关重要。本文首先阐述了Swing资源管理的重要性,紧接着深入探讨了内存泄漏的成因和原理,包括组件和事件模型以及不恰当的事件监听器和长期引用所导致的问题。本文还对JVM的垃圾回收机制进行了概述,介绍了Swing内存泄漏检

【OGG跨平台数据同步】:Oracle 11g环境下的跨平台同步绝技

# 摘要 本文详细介绍了跨平台数据同步技术,并以Oracle GoldenGate(OGG)为例进行深入探讨。首先,概述了Oracle 11g下的数据同步基础,包括数据同步的定义、重要性以及Oracle 11g支持的数据同步类型。随后,介绍了Oracle 11g的数据复制技术,并详细分析了OGG的软件架构和核心组件。在实战演练章节,文章指导读者完成单向和双向数据同步的配置与实施,并提供了常见问题的故障排除方法。最后,重点讨论了OGG同步性能优化策略、日常管理与监控,以及在不同平台应用的案例研究,旨在提升数据同步效率,确保数据一致性及系统的稳定性。 # 关键字 数据同步;Oracle Gold