|
|
|
|
@ -1,235 +1,282 @@
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
<head>
|
|
|
|
|
<!-- 定义页面的字符编码为 UTF-8,确保能正确显示各种字符 -->
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<!-- 设置页面视口,使其在移动设备上能自适应宽度,初始缩放比例为 1.0,方便页面在不同设备上的展示效果优化 -->
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<!-- 设置页面标题为“创建新的投票项目”,该标题会显示在浏览器标签页上 -->
|
|
|
|
|
<title>创建新的投票项目</title>
|
|
|
|
|
<!-- 页面内部样式定义,用于设置页面元素的外观样式 -->
|
|
|
|
|
<style>
|
|
|
|
|
body {
|
|
|
|
|
/* 设置页面默认字体为 Arial 或无衬线字体(sans-serif),增强字体的通用性和兼容性 */
|
|
|
|
|
font-family: Arial, sans-serif;
|
|
|
|
|
/* 设置页面背景颜色为浅灰色(#f4f4f4),营造柔和的视觉效果 */
|
|
|
|
|
background-color: #f4f4f4;
|
|
|
|
|
/* 清除页面默认的外边距,便于后续进行精确的页面布局 */
|
|
|
|
|
margin: 0;
|
|
|
|
|
/* 清除页面默认的内边距,同样利于布局控制 */
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
.container {
|
|
|
|
|
/* 设置容器的最大宽度为 600px,避免在大屏幕上内容过度拉伸,保持合适的布局宽度 */
|
|
|
|
|
max-width: 600px;
|
|
|
|
|
/* 使容器在水平方向上居中显示,通过设置左右外边距为 auto 实现 */
|
|
|
|
|
margin: 50px auto;
|
|
|
|
|
/* 设置容器的背景颜色为白色(#fff),与页面背景区分开来 */
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
/* 设置容器内部的内边距为 20px,增加内容与容器边框的间距 */
|
|
|
|
|
padding: 20px;
|
|
|
|
|
/* 设置容器的边框圆角为 8px,使容器外观更圆润美观 */
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
/* 为容器添加一个淡淡的阴影效果,增加立体感,参数表示水平和垂直方向阴影偏移量为 0,模糊半径为 10px,阴影颜色为半透明黑色(rgba(0, 0, 0, 0.1)) */
|
|
|
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
h1 {
|
|
|
|
|
/* 使标题文本在水平方向上居中对齐 */
|
|
|
|
|
text-align: center;
|
|
|
|
|
/* 设置标题文本颜色为深灰色(#333),增强可读性和视觉对比度 */
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
label {
|
|
|
|
|
/* 将 label 标签设置为块级元素,使其独占一行,方便布局和样式控制 */
|
|
|
|
|
display: block;
|
|
|
|
|
/* 设置 label 标签下方的外边距为 5px,增加与下方元素的间距 */
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
/* 设置 label 标签文本颜色为深灰色(#333),保持整体文本颜色一致性 */
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
input[type="text"],
|
|
|
|
|
select,
|
|
|
|
|
input[type="date"],
|
|
|
|
|
button {
|
|
|
|
|
/* 设置这些表单元素的宽度为 100%,使其占满父容器宽度,便于在不同屏幕尺寸下保持布局一致性 */
|
|
|
|
|
width: 100%;
|
|
|
|
|
/* 设置表单元素内部的内边距为 10px,增加输入内容与边框的间距 */
|
|
|
|
|
padding: 10px;
|
|
|
|
|
/* 设置表单元素下方的外边距为 20px,增加元素之间的垂直间距 */
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
/* 设置表单元素的边框为 1px 实线,颜色为浅灰色(#ccc),统一外观样式 */
|
|
|
|
|
border: 1px solid #ccc;
|
|
|
|
|
/* 设置边框圆角为 5px,使元素外观更圆润 */
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
/* 设置盒模型为 border-box,这样元素设置的内边距和边框不会增加元素的实际宽度,方便布局计算 */
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
button {
|
|
|
|
|
/* 设置按钮的背景颜色为蓝色(#007bff),通常用于表示可操作的主要按钮颜色 */
|
|
|
|
|
background-color: #007bff;
|
|
|
|
|
/* 设置按钮文本颜色为白色(#fff),与背景颜色形成鲜明对比,增强可读性 */
|
|
|
|
|
color: #fff;
|
|
|
|
|
/* 将鼠标指针样式设置为手型,提示用户该元素可点击操作 */
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
/* 设置按钮背景颜色在鼠标悬停时的过渡效果,过渡时间为 0.3 秒,过渡动画为缓动效果(ease),使交互体验更平滑 */
|
|
|
|
|
transition: background-color 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
button:hover {
|
|
|
|
|
/* 定义鼠标悬停在按钮上时的背景颜色变化,变为更深的蓝色(#0056b3),增强交互反馈效果 */
|
|
|
|
|
background-color: #0056b3;
|
|
|
|
|
}
|
|
|
|
|
.message {
|
|
|
|
|
/* 使消息文本在水平方向上居中对齐 */
|
|
|
|
|
text-align: center;
|
|
|
|
|
/* 设置消息文本上方的外边距为 20px,增加与上方元素的间距 */
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
.success {
|
|
|
|
|
/* 设置表示成功的消息文本颜色为绿色,直观地传达操作成功的信息 */
|
|
|
|
|
color: green;
|
|
|
|
|
}
|
|
|
|
|
.error {
|
|
|
|
|
/* 设置表示错误的消息文本颜色为红色,醒目地提示出现错误情况 */
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<div class="container">
|
|
|
|
|
<h1>创建新的投票项目</h1>
|
|
|
|
|
<form id="createVoteForm">
|
|
|
|
|
<label for="voteTitle">投票标题:</label>
|
|
|
|
|
<input type="text" id="voteTitle" name="voteTitle" required>
|
|
|
|
|
|
|
|
|
|
<label for="numOptions">候选项数量:</label>
|
|
|
|
|
<select id="numOptions" name="numOptions" onchange="updateOptions()" required>
|
|
|
|
|
<option value="">选择候选项数量</option>
|
|
|
|
|
<option value="2">2</option>
|
|
|
|
|
<option value="3">3</option>
|
|
|
|
|
<option value="4">4</option>
|
|
|
|
|
<option value="5">5</option>
|
|
|
|
|
<option value="6">6</option>
|
|
|
|
|
<option value="7">7</option>
|
|
|
|
|
<option value="8">8</option>
|
|
|
|
|
<option value="9">9</option>
|
|
|
|
|
<option value="10">10</option>
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
<div id="optionsContainer"></div>
|
|
|
|
|
|
|
|
|
|
<label for="deadline">截止日期:</label>
|
|
|
|
|
<input type="date" id="deadline" name="deadline" required>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<button type="submit">创建投票</button>
|
|
|
|
|
</form>
|
|
|
|
|
<div id="message" class="message"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/web3@1.5.3/dist/web3.min.js"></script>
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@metamask/detect-provider@1.2.0/dist/detect-provider.min.js"></script>
|
|
|
|
|
<script>
|
|
|
|
|
function updateOptions() {
|
|
|
|
|
const numOptions = document.getElementById('numOptions').value;
|
|
|
|
|
const optionsContainer = document.getElementById('optionsContainer');
|
|
|
|
|
optionsContainer.innerHTML = ''; // 清空之前的选项
|
|
|
|
|
|
|
|
|
|
if (numOptions !== '') {
|
|
|
|
|
const optionValues = new Set(); // 用于存储已添加的候选项值
|
|
|
|
|
for (let i = 1; i <= numOptions; i++) {
|
|
|
|
|
const label = document.createElement('label');
|
|
|
|
|
label.textContent = `候选项 ${i}:`;
|
|
|
|
|
optionsContainer.appendChild(label);
|
|
|
|
|
|
|
|
|
|
const input = document.createElement('input');
|
|
|
|
|
input.type = 'text';
|
|
|
|
|
input.name = `option${i}`;
|
|
|
|
|
input.required = true;
|
|
|
|
|
input.addEventListener('input', function(event) {
|
|
|
|
|
const value = event.target.value.trim(); // 移除首尾空格
|
|
|
|
|
if (value !== '' && optionValues.has(value)) {
|
|
|
|
|
event.target.setCustomValidity('候选项的值不能重复');
|
|
|
|
|
} else {
|
|
|
|
|
event.target.setCustomValidity('');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
optionsContainer.appendChild(input);
|
|
|
|
|
optionValues.add(''); // 添加初始值,避免首次输入为空时的重复检查
|
|
|
|
|
<!-- 创建一个类名为 container 的 div 容器,用于包裹整个投票项目创建的表单内容,实现布局和样式的统一管理 -->
|
|
|
|
|
<div class="container">
|
|
|
|
|
<!-- 页面标题,显示“创建新的投票项目”,通过 h1 标签强调其重要性,并且在样式中设置了居中对齐 -->
|
|
|
|
|
<h1>创建新的投票项目</h1>
|
|
|
|
|
<!-- 创建一个表单,id 为 createVoteForm,用于收集创建投票项目所需的各种信息,后续通过 JavaScript 来处理表单提交等操作 -->
|
|
|
|
|
<form id="createVoteForm">
|
|
|
|
|
<!-- 创建一个 label 标签,用于提示输入投票标题,通过 for 属性与对应的 input 元素关联,增强可访问性 -->
|
|
|
|
|
<label for="voteTitle">投票标题:</label>
|
|
|
|
|
<!-- 创建一个文本输入框,id 为 voteTitle,name 也为 voteTitle,并且设置为必填项(required),用于用户输入投票项目的标题 -->
|
|
|
|
|
<input type="text" id="voteTitle" name="voteTitle" required>
|
|
|
|
|
|
|
|
|
|
optionsContainer.appendChild(document.createElement('br'));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
<!-- 创建一个 label 标签,用于提示选择候选项数量 -->
|
|
|
|
|
<label for="numOptions">候选项数量:</label>
|
|
|
|
|
<!-- 创建一个下拉选择框,id 为 numOptions,name 为 numOptions,并且添加了 onchange 事件监听器,当选项改变时会调用 updateOptions 函数来动态更新候选项的输入框,同时设置为必填项 -->
|
|
|
|
|
<select id="numOptions" name="numOptions" onchange="updateOptions()" required>
|
|
|
|
|
<!-- 下拉框的默认提示选项,显示“选择候选项数量”,其 value 值为空 -->
|
|
|
|
|
<option value="">选择候选项数量</option>
|
|
|
|
|
<!-- 可供选择的候选项数量选项,分别提供了从 2 到 10 的数值选项 -->
|
|
|
|
|
<option value="2">2</option>
|
|
|
|
|
<option value="3">3</option>
|
|
|
|
|
<option value="4">4</option>
|
|
|
|
|
<option value="5">5</option>
|
|
|
|
|
<option value="6">6</option>
|
|
|
|
|
<option value="7">7</option>
|
|
|
|
|
<option value="8">8</option>
|
|
|
|
|
<option value="9">9</option>
|
|
|
|
|
<option value="10">10</option>
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
// 设置截止日期的最小值为明天的日期
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const tomorrow = new Date(today);
|
|
|
|
|
tomorrow.setDate(tomorrow.getDate() + 1); // 设置日期为明天
|
|
|
|
|
const tomorrowISO = tomorrow.toISOString().split('T')[0];
|
|
|
|
|
document.getElementById('deadline').setAttribute('min', tomorrowISO);
|
|
|
|
|
|
|
|
|
|
window.addEventListener('DOMContentLoaded', async () => {
|
|
|
|
|
// 检查 MetaMask 是否安装
|
|
|
|
|
const provider = await detectEthereumProvider();
|
|
|
|
|
if (!provider) {
|
|
|
|
|
console.error('请安装 MetaMask 扩展程序');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
<!-- 创建一个空的 div 容器,id 为 optionsContainer,后续通过 JavaScript 动态生成候选项的输入框等元素会添加到这个容器内 -->
|
|
|
|
|
<div id="optionsContainer"></div>
|
|
|
|
|
|
|
|
|
|
// 创建 Web3 实例
|
|
|
|
|
const web3 = new Web3(provider);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 请求 MetaMask 授权
|
|
|
|
|
await ethereum.request({ method: 'eth_requestAccounts' });
|
|
|
|
|
|
|
|
|
|
// 获取 MetaMask 用户地址
|
|
|
|
|
const accounts = await web3.eth.getAccounts();
|
|
|
|
|
const metaMaskUser = accounts[0];
|
|
|
|
|
|
|
|
|
|
// 将 MetaMask 用户地址添加到表单数据中
|
|
|
|
|
document.getElementById('createVoteForm').addEventListener('submit', function(event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
const formData = new FormData(this); // 获取表单数据
|
|
|
|
|
const data = {};
|
|
|
|
|
formData.forEach(function(value, key) {
|
|
|
|
|
data[key] = value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 添加 MetaMask 用户地址到数据中
|
|
|
|
|
data.metaMaskUser = metaMaskUser;
|
|
|
|
|
|
|
|
|
|
// 检查候选项是否重复
|
|
|
|
|
const optionValues = new Set();
|
|
|
|
|
let hasDuplicate = false;
|
|
|
|
|
for (let i = 1; i <= data.numOptions; i++) {
|
|
|
|
|
const option = data[`option${i}`].trim(); // 移除首尾空格
|
|
|
|
|
if (option !== '') {
|
|
|
|
|
if (optionValues.has(option)) {
|
|
|
|
|
hasDuplicate = true;
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
optionValues.add(option);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
<!-- 创建一个 label 标签,用于提示输入投票截止日期 -->
|
|
|
|
|
<label for="deadline">截止日期:</label>
|
|
|
|
|
<!-- 创建一个日期选择输入框,id 为 deadline,name 为 deadline,并且设置为必填项,用于用户选择投票的截止时间 -->
|
|
|
|
|
<input type="date" id="deadline" name="deadline" required>
|
|
|
|
|
|
|
|
|
|
if (hasDuplicate) {
|
|
|
|
|
alert('候选项的值不能重复');
|
|
|
|
|
<!-- 创建一个按钮,类型为 submit,文本为“创建投票”,用于提交表单数据,触发创建投票的相关操作,按钮的样式在前面的 CSS 中已定义,有鼠标悬停等交互效果 -->
|
|
|
|
|
<button type="submit">创建投票</button>
|
|
|
|
|
</form>
|
|
|
|
|
<!-- 创建一个空的 div 容器,id 为 message,类名为 message,用于后续通过 JavaScript 根据操作结果显示相应的提示消息,如投票创建成功或失败的提示 -->
|
|
|
|
|
<div id="message" class="message"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 通过 CDN 引入 Web3.js 库的最小化版本,版本号为 1.5.3,Web3.js 常用于与以太坊区块链进行交互,在本页面中可能用于处理投票相关的区块链操作 -->
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/web3@1.5.3/dist/web3.min.js"></script>
|
|
|
|
|
<!-- 通过 CDN 引入 MetaMask 检测提供器的最小化版本,版本号为 1.2.0,用于检测用户浏览器是否安装了 MetaMask 扩展程序,这在基于区块链的应用中很常用,因为 MetaMask 常用于管理以太坊账户等操作 -->
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@metamask/detect-provider@1.2.0/dist/detect-provider.min.js"></script>
|
|
|
|
|
<script>
|
|
|
|
|
// 以下是 JavaScript 脚本部分的注释,用于实现页面的交互逻辑和功能
|
|
|
|
|
|
|
|
|
|
// 定义 updateOptions 函数,用于根据用户在下拉框中选择的候选项数量动态更新候选项输入框的显示情况
|
|
|
|
|
function updateOptions() {
|
|
|
|
|
// 获取下拉框(id 为 numOptions)中用户选择的值,即候选项的数量
|
|
|
|
|
const numOptions = document.getElementById('numOptions').value;
|
|
|
|
|
// 获取用于存放候选项输入框的容器元素(id 为 optionsContainer)
|
|
|
|
|
const optionsContainer = document.getElementById('optionsContainer');
|
|
|
|
|
// 清空 optionsContainer 内之前可能存在的候选项输入框等元素,避免重复添加造成混乱
|
|
|
|
|
optionsContainer.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
// 如果用户选择了具体的候选项数量(即 numOptions 不为空字符串)
|
|
|
|
|
if (numOptions!== '') {
|
|
|
|
|
// 创建一个 Set 数据结构,用于存储已添加的候选项值,利用 Set 的特性可以方便地检查值是否重复
|
|
|
|
|
const optionValues = new Set();
|
|
|
|
|
// 循环生成对应数量的候选项输入框及相关标签元素
|
|
|
|
|
for (let i = 1; i <= numOptions; i++) {
|
|
|
|
|
// 创建一个 label 标签,用于显示候选项的序号提示,如“候选项 1:”
|
|
|
|
|
const label = document.createElement('label');
|
|
|
|
|
label.textContent = `候选项 ${i}:`;
|
|
|
|
|
// 将创建好的 label 标签添加到 optionsContainer 容器内
|
|
|
|
|
optionsContainer.appendChild(label);
|
|
|
|
|
|
|
|
|
|
// 创建一个文本输入框,用于用户输入具体的候选项内容
|
|
|
|
|
const input = document.createElement('input');
|
|
|
|
|
input.type = 'text';
|
|
|
|
|
input.name = `option${i}`;
|
|
|
|
|
input.required = true;
|
|
|
|
|
// 为输入框添加 input 事件监听器,用于实时检查输入的值是否重复
|
|
|
|
|
input.addEventListener('input', function(event) {
|
|
|
|
|
// 获取输入框输入的值,并移除首尾空格,确保比较时的准确性
|
|
|
|
|
const value = event.target.value.trim();
|
|
|
|
|
// 如果输入的值不为空且在 optionValues 集合中已存在(表示重复)
|
|
|
|
|
if (value!== '' && optionValues.has(value)) {
|
|
|
|
|
// 设置输入框的自定义验证提示信息,提示用户候选项的值不能重复
|
|
|
|
|
event.target.setCustomValidity('候选项的值不能重复');
|
|
|
|
|
} else {
|
|
|
|
|
// 候选项没有重复,提交表单
|
|
|
|
|
fetch('/createVote', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
})
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('网络响应错误');
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(data => {
|
|
|
|
|
const messageElement = document.getElementById('message');
|
|
|
|
|
if (data.success) {
|
|
|
|
|
messageElement.textContent = `投票项目创建成功!合约地址:${data.contractAddress}`;
|
|
|
|
|
messageElement.classList.add('success');
|
|
|
|
|
|
|
|
|
|
// 将合约地址复制到剪贴板
|
|
|
|
|
navigator.clipboard.writeText(data.contractAddress)
|
|
|
|
|
.then(() => console.log('合约地址已复制到剪贴板'))
|
|
|
|
|
.catch(err => console.error('复制到剪贴板失败:', err));
|
|
|
|
|
|
|
|
|
|
// Redirect to index.html on successful creation
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
window.location.href = '/index.html';
|
|
|
|
|
}, 1000); // Redirect after 2 seconds
|
|
|
|
|
} else {
|
|
|
|
|
messageElement.textContent = '投票项目创建失败,请稍后重试。';
|
|
|
|
|
messageElement.classList.add('error');
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('发生错误:', error);
|
|
|
|
|
const messageElement = document.getElementById('message');
|
|
|
|
|
messageElement.textContent = '发生错误,请稍后重试。';
|
|
|
|
|
messageElement.classList.add('error');
|
|
|
|
|
});
|
|
|
|
|
// 如果值不重复或为空,则清除自定义验证提示信息,允许输入框正常提交
|
|
|
|
|
event.target.setCustomValidity('');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('授权失败:', error);
|
|
|
|
|
// 将创建好的输入框添加到 optionsContainer 容器内
|
|
|
|
|
optionsContainer.appendChild(input);
|
|
|
|
|
// 将一个空字符串添加到 optionValues 集合中,避免首次输入为空值时误判为重复(因为后续会检查是否已存在该值)
|
|
|
|
|
optionValues.add('');
|
|
|
|
|
|
|
|
|
|
// 在每个候选项输入框后添加一个换行元素(br),使页面布局更清晰
|
|
|
|
|
optionsContainer.appendChild(document.createElement('br'));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置截止日期输入框(id 为 deadline)的最小值为明天的日期,确保用户不能选择早于明天的截止日期
|
|
|
|
|
|
|
|
|
|
// 获取当前日期对象
|
|
|
|
|
const today = new Date();
|
|
|
|
|
// 创建一个新的日期对象,并赋值为今天的日期,用于后续修改为明天的日期
|
|
|
|
|
const tomorrow = new Date(today);
|
|
|
|
|
// 通过设置日期对象的日期值为当前日期加 1,将其修改为明天的日期
|
|
|
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
|
// 将明天的日期对象转换为 ISO 8601 格式的字符串,并截取日期部分(去除时间部分),得到适合设置为 input 类型为 date 的 min 属性的值
|
|
|
|
|
const tomorrowISO = tomorrow.toISOString().split('T')[0];
|
|
|
|
|
// 将计算得到的明天日期字符串设置为截止日期输入框的 min 属性值
|
|
|
|
|
document.getElementById('deadline').setAttribute('min', tomorrowISO);
|
|
|
|
|
|
|
|
|
|
// 当页面的 DOM 内容加载完成后(即页面结构和元素都已加载完毕),执行以下异步函数
|
|
|
|
|
window.addEventListener('DOMContentLoaded', async () => {
|
|
|
|
|
// 检查 MetaMask 是否安装,调用 detectEthereumProvider 函数(由引入的 @metamask/detect-provider 库提供)进行检测
|
|
|
|
|
const provider = await detectEthereumProvider();
|
|
|
|
|
// 如果没有检测到 MetaMask 提供器(即未安装 MetaMask 扩展程序)
|
|
|
|
|
if (!provider) {
|
|
|
|
|
// 在控制台输出错误信息,提示用户安装 MetaMask 扩展程序
|
|
|
|
|
console.error('请安装 MetaMask 扩展程序');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建一个 Web3 实例,传入检测到的以太坊提供器(provider),用于后续与以太坊区块链进行交互
|
|
|
|
|
const web3 = new Web3(provider);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 请求 MetaMask 授权,通过调用 ethereum.request 方法并传入指定的授权方法名(eth_requestAccounts)来向 MetaMask 申请使用用户账户权限
|
|
|
|
|
await ethereum.request({ method: 'eth_requestAccounts' });
|
|
|
|
|
|
|
|
|
|
// 获取 MetaMask 用户的账户地址,通过调用 web3.eth.getAccounts 方法,该方法返回一个包含用户账户地址的数组,通常第一个地址就是常用的主地址
|
|
|
|
|
const accounts = await web3.eth.getAccounts();
|
|
|
|
|
const metaMaskUser = accounts[0];
|
|
|
|
|
|
|
|
|
|
// 为表单(id 为 createVoteForm)添加 submit 事件监听器,用于处理表单提交操作,阻止表单默认的提交行为(避免页面刷新等默认操作),然后进行后续的数据处理和提交逻辑
|
|
|
|
|
document.getElementById('createVoteForm').addEventListener('submit', function(event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
// 创建一个 FormData 对象,用于获取表单中用户输入的所有数据,方便后续进行处理和发送
|
|
|
|
|
const formData = new FormData(this);
|
|
|
|
|
const data = {};
|
|
|
|
|
// 遍历 FormData 对象中的每一项数据,将键值对添加到 data 对象中,方便后续以 JSON 格式发送数据
|
|
|
|
|
formData.forEach(function(value, key) {
|
|
|
|
|
data[key] = value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 将获取到的 MetaMask 用户地址添加到 data 对象中,以便后续将该信息一起发送到服务器,可能用于标识投票创建者等用途
|
|
|
|
|
data.metaMaskUser = metaMaskUser;
|
|
|
|
|
|
|
|
|
|
// 检查候选项是否存在重复值,创建一个新的 Set 数据结构用于存储已检查的候选项值
|
|
|
|
|
const optionValues = new Set();
|
|
|
|
|
let hasDuplicate = false;
|
|
|
|
|
for (let i = 1; i <= data.numOptions; i++) {
|
|
|
|
|
// 获取每个候选项的值,并移除首尾空格,确保准确比较
|
|
|
|
|
const option = data[`option${i}`].trim();
|
|
|
|
|
// 如果候选项值不为空
|
|
|
|
|
if (option!== '') {
|
|
|
|
|
// 如果该值已经在 optionValues 集合中存在(表示重复)
|
|
|
|
|
if (optionValues.has(option)) {
|
|
|
|
|
hasDuplicate = true;
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
// 如果值不重复,则将其添加到 optionValues 集合中
|
|
|
|
|
optionValues.add(option);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果候选项存在重复值
|
|
|
|
|
if (hasDuplicate) {
|
|
|
|
|
// 弹出警告框,提示用户候选项的值不能重复
|
|
|
|
|
alert('候选项的值不能重复');
|
|
|
|
|
} else {
|
|
|
|
|
// 如果候选项没有重复,进行表单数据的提交操作
|
|
|
|
|
|
|
|
|
|
// 使用 fetch API 发送一个 POST 请求到服务器的 /createVote 路径,用于创建投票项目
|
|
|
|
|
fetch('/createVote', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
// 设置请求头的 Content-Type 为 application/json,表示发送的数据是 JSON 格式
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
// 将 data 对象转换为 JSON 字符串
|