2.【Vibe Coding】Trae-网页投票工具
本帖最后由 糖逗 于 2026-4-14 10:35 编辑1. 目标
创建一个网页投票小程序(下面是我生成的效果)
2. 操作步骤
①本地创建02_Project_Vote文件夹,用Trae打开
②打开NewChat(`Ctrl+U'(Windows)、``**U`(Mac)),使用内置智能体Builder,输入提示词
③Trae会根据提示词生成程序/代码
提示词如下
你现在是一名资深网页前端开发工程师,请帮我使用原生 HTML + CSS + JavaScript
编写一个可直接在浏览器中打开使用的「在线投票程序」,要求如下:
-功能:
创建投票:输入投票标题,至少含2个选项:支持添加/删除选项
-开始投票:显示选项列表,点击为某一选项投票,实时更新票数与百分比
-防重复投票:同一设备对同一投票仅能投一次(使用lcocalstorage记录)
-结果展示:显示每个选项票数和百分比,提供简单进度条或条形图效果
-数据持久化:投票标题、选项、票数、是否已投状态均使用localStorage保存,刷新后保留
重置提供"重置投票"按钮,清空数据并重新创建新的投票
-界面与技术:
-风格简洁淡雅(浅灰/浅蓝),居中卡片布局,按钮圆角阴影
-使用CSSFlex或Grid布局;当前日期不需要显示
-不使用任何框架或库;全部代码在同一个HTML文件中
-使用原生JavaScript操作DOM,逻辑清晰,适量注释
-输出格式:
-直接保存为`vote.html`文件,
-代码可直接运行,无需任何依赖
学习视频:快速体验Trea
以下是完整的「在线投票程序」代码,保存为 `vote.html` 后可直接在浏览器中打开使用。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在线投票程序</title>
<style>
/* 重置默认样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa; /* 浅灰色背景 */
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.container {
width: 100%;
max-width: 800px;
background-color: #ffffff; /* 白色卡片 */
border-radius: 16px; /* 圆角 */
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); /* 阴影 */
padding: 30px;
margin: 20px;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 20px;
font-size: 2.2rem;
}
h2 {
color: #3498db; /* 浅蓝色 */
margin-top: 30px;
margin-bottom: 15px;
font-size: 1.6rem;
border-bottom: 2px solid #ecf0f1;
padding-bottom: 8px;
}
.section {
margin-bottom: 25px;
}
.input-group {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
}
input {
padding: 12px 16px;
border: 2px solid #dfe6e9;
border-radius: 10px;
font-size: 1rem;
transition: border-color 0.3s;
}
input:focus {
outline: none;
border-color: #3498db;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 10px;
}
button {
padding: 12px 24px;
background-color: #3498db; /* 浅蓝色按钮 */
color: white;
border: none;
border-radius: 10px; /* 按钮圆角 */
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
box-shadow: 0 4px 8px rgba(52, 152, 219, 0.2); /* 按钮阴影 */
}
button:hover {
background-color: #2980b9;
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
button.delete-btn {
background-color: #e74c3c;
box-shadow: 0 4px 8px rgba(231, 76, 60, 0.2);
}
button.delete-btn:hover {
background-color: #c0392b;
}
button.reset-btn {
background-color: #95a5a6;
box-shadow: 0 4px 8px rgba(149, 165, 166, 0.2);
}
button.reset-btn:hover {
background-color: #7f8c8d;
}
button:disabled {
background-color: #bdc3c7;
cursor: not-allowed;
transform: none;
}
.options-list {
list-style-type: none;
margin-top: 15px;
}
.options-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: #f8f9fa;
border: 1px solid #dfe6e9;
border-radius: 8px;
margin-bottom: 10px;
transition: background-color 0.3s;
}
.options-list li:hover {
background-color: #ecf0f1;
}
.vote-options {
display: grid;
gap: 15px;
margin-top: 15px;
}
.vote-option {
display: flex;
flex-direction: column;
padding: 18px;
background-color: #f8f9fa;
border: 2px solid #dfe6e9;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s;
}
.vote-option:hover {
border-color: #3498db;
background-color: #e3f2fd;
}
.vote-option.voted {
border-color: #27ae60;
background-color: #d5f4e6;
}
.vote-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.option-text {
font-size: 1.1rem;
font-weight: 600;
color: #2c3e50;
}
.vote-count {
font-size: 1rem;
color: #7f8c8d;
}
.progress-bar {
width: 100%;
height: 12px;
background-color: #dfe6e9;
border-radius: 6px;
overflow: hidden;
margin-top: 8px;
}
.progress-fill {
height: 100%;
background-color: #3498db;
border-radius: 6px;
width: 0%;
transition: width 0.5s ease;
}
.results {
margin-top: 30px;
}
.result-item {
margin-bottom: 20px;
}
.result-info {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
}
.percentage {
font-weight: 600;
color: #2c3e50;
}
.message {
padding: 15px;
border-radius: 10px;
text-align: center;
font-weight: 600;
margin-top: 20px;
}
.message.success {
background-color: #d5f4e6;
color: #27ae60;
}
.message.error {
background-color: #fadbd8;
color: #e74c3c;
}
.message.info {
background-color: #d6eaf8;
color: #3498db;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>在线投票程序</h1>
<!-- 创建投票部分 -->
<div class="section">
<h2>创建投票</h2>
<div class="input-group">
<input type="text" id="voteTitle" placeholder="输入投票标题(例如:你最喜欢的编程语言?)" value="你最喜欢的编程语言?">
<input type="text" id="newOption" placeholder="输入新选项(例如:Python)">
<div class="button-group">
<button id="addOptionBtn">添加选项</button>
<button id="startVoteBtn" class="hidden">开始投票</button>
</div>
</div>
<ul id="optionsList" class="options-list">
<!-- 选项列表将动态生成 -->
</ul>
</div>
<!-- 投票部分 -->
<div id="voteSection" class="section hidden">
<h2 id="voteTitleDisplay">投票标题</h2>
<div id="voteOptions" class="vote-options">
<!-- 投票选项将动态生成 -->
</div>
<p id="voteMessage" class="message hidden"></p>
</div>
<!-- 结果展示部分 -->
<div id="resultsSection" class="section hidden">
<h2>投票结果</h2>
<div id="results" class="results">
<!-- 结果将动态生成 -->
</div>
</div>
<!-- 重置按钮 -->
<div class="button-group">
<button id="resetBtn" class="reset-btn">重置投票</button>
</div>
</div>
<script>
// 全局变量:投票数据键名
const STORAGE_KEY = 'voteData';
// 初始化投票数据
let voteData = {
title: "你最喜欢的编程语言?",
options: [
{ text: "Python", votes: 0 },
{ text: "JavaScript", votes: 0 },
{ text: "Java", votes: 0 }
],
hasVoted: false
};
// DOM 元素
const voteTitleInput = document.getElementById('voteTitle');
const newOptionInput = document.getElementById('newOption');
const addOptionBtn = document.getElementById('addOptionBtn');
const startVoteBtn = document.getElementById('startVoteBtn');
const optionsList = document.getElementById('optionsList');
const voteSection = document.getElementById('voteSection');
const voteTitleDisplay = document.getElementById('voteTitleDisplay');
const voteOptions = document.getElementById('voteOptions');
const voteMessage = document.getElementById('voteMessage');
const resultsSection = document.getElementById('resultsSection');
const results = document.getElementById('results');
const resetBtn = document.getElementById('resetBtn');
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', function() {
loadVoteData();
renderCreateSection();
if (voteData.options.length >= 2) {
startVoteBtn.classList.remove('hidden');
}
});
// 从 localStorage 加载投票数据
function loadVoteData() {
const savedData = localStorage.getItem(STORAGE_KEY);
if (savedData) {
try {
voteData = JSON.parse(savedData);
} catch (e) {
console.error('加载投票数据失败,使用默认数据');
}
}
}
// 保存投票数据到 localStorage
function saveVoteData() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(voteData));
}
// 渲染创建投票部分
function renderCreateSection() {
voteTitleInput.value = voteData.title;
optionsList.innerHTML = '';
voteData.options.forEach((option, index) => {
const li = document.createElement('li');
li.innerHTML = `
<span>${option.text}</span>
<button class="delete-btn" data-index="${index}">删除</button>
`;
optionsList.appendChild(li);
});
// 更新开始投票按钮状态
if (voteData.options.length >= 2) {
startVoteBtn.classList.remove('hidden');
} else {
startVoteBtn.classList.add('hidden');
}
}
// 渲染投票部分
function renderVoteSection() {
voteTitleDisplay.textContent = voteData.title;
voteOptions.innerHTML = '';
voteData.options.forEach((option, index) => {
const totalVotes = getTotalVotes();
const percentage = totalVotes > 0 ? Math.round((option.votes / totalVotes) * 100) : 0;
const optionDiv = document.createElement('div');
optionDiv.className = 'vote-option';
optionDiv.dataset.index = index;
optionDiv.innerHTML = `
<div class="vote-header">
<span class="option-text">${option.text}</span>
<span class="vote-count">${option.votes} 票</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${percentage}%"></div>
</div>
`;
voteOptions.appendChild(optionDiv);
});
// 如果已投票,禁用投票选项
if (voteData.hasVoted) {
document.querySelectorAll('.vote-option').forEach(opt => {
opt.classList.add('voted');
opt.style.cursor = 'not-allowed';
});
showMessage('您已经投过票了!', 'info');
} else {
showMessage('请点击选项进行投票', 'info');
}
voteSection.classList.remove('hidden');
renderResults();
}
// 渲染结果部分
function renderResults() {
results.innerHTML = '';
const totalVotes = getTotalVotes();
voteData.options.forEach(option => {
const percentage = totalVotes > 0 ? Math.round((option.votes / totalVotes) * 100) : 0;
const resultItem = document.createElement('div');
resultItem.className = 'result-item';
resultItem.innerHTML = `
<div class="result-info">
<span>${option.text}</span>
<span>${option.votes} 票 (<span class="percentage">${percentage}%</span>)</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${percentage}%"></div>
</div>
`;
results.appendChild(resultItem);
});
resultsSection.classList.remove('hidden');
}
// 计算总票数
function getTotalVotes() {
return voteData.options.reduce((sum, option) => sum + option.votes, 0);
}
// 显示消息
function showMessage(text, type) {
voteMessage.textContent = text;
voteMessage.className = `message ${type}`;
voteMessage.classList.remove('hidden');
}
// 隐藏消息
function hideMessage() {
voteMessage.classList.add('hidden');
}
// 事件监听:添加选项
addOptionBtn.addEventListener('click', function() {
const text = newOptionInput.value.trim();
if (text === '') {
alert('请输入选项内容!');
return;
}
if (voteData.options.some(opt => opt.text === text)) {
alert('该选项已存在!');
return;
}
voteData.options.push({ text: text, votes: 0 });
newOptionInput.value = '';
saveVoteData();
renderCreateSection();
});
// 事件监听:删除选项(使用事件委托)
optionsList.addEventListener('click', function(e) {
if (e.target.classList.contains('delete-btn')) {
const index = parseInt(e.target.dataset.index);
voteData.options.splice(index, 1);
saveVoteData();
renderCreateSection();
}
});
// 事件监听:开始投票
startVoteBtn.addEventListener('click', function() {
voteData.title = voteTitleInput.value.trim();
if (voteData.title === '') {
alert('请输入投票标题!');
return;
}
if (voteData.options.length < 2) {
alert('至少需要两个选项才能开始投票!');
return;
}
saveVoteData();
renderVoteSection();
});
// 事件监听:投票(使用事件委托)
voteOptions.addEventListener('click', function(e) {
if (voteData.hasVoted) {
showMessage('您已经投过票了,不能重复投票!', 'error');
return;
}
const optionDiv = e.target.closest('.vote-option');
if (!optionDiv) return;
const index = parseInt(optionDiv.dataset.index);
voteData.options.votes++;
voteData.hasVoted = true;
saveVoteData();
renderVoteSection();
showMessage('投票成功!感谢您的参与。', 'success');
});
// 事件监听:重置投票
resetBtn.addEventListener('click', function() {
if (confirm('确定要重置投票吗?所有数据将被清空。')) {
voteData = {
title: "你最喜欢的编程语言?",
options: [
{ text: "Python", votes: 0 },
{ text: "JavaScript", votes: 0 },
{ text: "Java", votes: 0 }
],
hasVoted: false
};
localStorage.removeItem(STORAGE_KEY);
voteSection.classList.add('hidden');
resultsSection.classList.add('hidden');
hideMessage();
renderCreateSection();
}
});
// 输入框回车键快捷添加选项
newOptionInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
addOptionBtn.click();
}
});
voteTitleInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
startVoteBtn.click();
}
});
</script>
</body>
</html>
使用说明:
1. 将以上代码复制并保存为 vote.html 文件。
2. 在浏览器中双击打开该文件,即可使用在线投票程序。
功能说明:
- 创建投票:输入投票标题和选项,点击“添加选项”按钮增加选项,选项列表会显示并可删除。
- 开始投票:至少添加两个选项后,点击“开始投票”按钮进入投票界面。
- 投票:点击选项进行投票,系统会实时更新票数和百分比,并用进度条可视化。
- 防重复投票:同一设备只能投票一次,使用 localStorage 记录状态。
- 结果展示:投票后显示每个选项的票数和百分比进度条。
- 数据持久化:所有数据保存在 localStorage 中,刷新页面后数据保留。
- 重置投票:点击“重置投票”按钮可清空所有数据并恢复到初始状态。
界面特点:
- 采用浅灰色背景和白色卡片布局,按钮为浅蓝色圆角设计。
- 使用 Flexbox 进行响应式布局,适应不同屏幕尺寸。
- 代码完全原生,无需任何外部依赖。
如有问题,欢迎在论坛继续讨论!
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
页:
[1]