区块链3
拍卖系统:
和web3.js1.0文档
原理:
合约的状态变量充当数据库的作用,web页面要拿到数据则通过web3来调用合约函数,进而拿到合约里面的东西。
当部署交易,改变合约的状态变量时,才会产生对应交易,打包到区块链中去,区块链记录着这些动作。
truffle:

步骤:
安装web3@1.以上的,ganache-cli,solc@0.4.23,ipfs-api。其中npm install:若无-g,则把包安装到当前目录,若有-g,则全局安装,装到其他位置。最后都装到了文件夹node_modules
进入一个空文件夹开始用npm管理:npm init
编写智能合约:
pragma solidity ^0.4.17;
contract EcommerceStore{
enum ProductStatus{Open,Sold,Unsold} // Open:竞价状态,Sold:已卖,Unsold:未卖
enum ProductCondition{New,Used} // 产品是新的还是旧的
uint public productIndex; // 产品id
mapping (uint=>address) productIdInStore; // 产品id=>产品创建者
mapping (address=>mapping(uint=>Product)) stores; // 人=>属于他的所有产品
struct Product{
uint id;
string name;
string category; // 所属分类
string imageLink; // 图片上传给ipfs的哈希值,而非真实图片
string descLink; // 图片描述上传给ipfs的哈希值
uint auctionStartTime; // 那个时间点对应的秒数
uint auctionEndTime;
uint startPrice; // 初始价格
address highestBidder;
uint highestBid;
uint secondHighest;
uint totalBids; // 被竞拍了多少次
ProductStatus status;
ProductCondition condition;
mapping (address=>mapping(bytes32=>Bid)) bids; // 有多少人竞拍了该产品,bids的每个元素:小明:{第一次的竞拍,第二次的竞拍}
}
// 单次竞拍的数据结构
struct Bid{
address bidder; // 竞拍人
uint productId; // 所竞拍的产品
uint value; // 当竞拍人报价时,所交的押金
bool revealed; // 该竞拍是否已经被揭示出来,初始是false
}
constructor()public {
productIndex = 0;
}
string t="bbb";
// 测试
function test(){
t="aaa";
}
function getT()view public returns(string){
return t;
}
function testStr()returns(string){
return "HI";
}
function addProductToStore(string _name,string _category,string _imageLink,string _descLink,uint _auctionStartTime,uint _auctionEndTime,uint _startPrice,uint _productCondition)public returns(address){
require(_auctionStartTime<_auctionEndTime,"auctionStartTime<auctionEndTime!!!!");
productIndex+=1;
Product memory product = Product(productIndex,_name,_category,_imageLink,_descLink,_auctionStartTime,_auctionEndTime,_startPrice,0,0,0,0,ProductStatus.Open,ProductCondition(_productCondition));
stores[msg.sender][productIndex] = product;
productIdInStore[productIndex]=msg.sender;
return msg.sender;
}
function getProduct(uint _productId)public view returns(uint,string,string,string,string,uint,uint,uint,ProductStatus,ProductCondition){
Product memory product = stores[productIdInStore[_productId]][_productId];
return (product.id,product.name,product.category,product.imageLink,product.descLink,product.auctionStartTime,product.auctionEndTime,product.startPrice,product.status,product.condition);
}
// 发生了一次竞拍
function bid(uint _productId,bytes32 _bid)public payable returns (bool){
Product storage product = stores[productIdInStore[_productId]][_productId]; // 得到_productId所指向的产品
require(now>=product.auctionStartTime,"错误:时间小于该商品拍卖起始时间");
require(now<=product.auctionEndTime,"错误:时间大于该商品拍卖终止时间");
require(msg.value>product.startPrice); // 报价得大于起拍价
require(product.bids[msg.sender][_bid].bidder==0); //不能一个人重复报相同的价
product.bids[msg.sender][_bid]=Bid(msg.sender,_productId,msg.value,false);// 新建单次的竞拍
product.totalBids+=1;
return true;
}
// 揭示报价,_amount为报价,约定只有产品的最高报价者交付这个报价,其他竞价人退还他的押金
function revealBid(uint _productId,string _amount,string _secret){
Product storage product = stores[productIdInStore[_productId]][_productId]; // 得到_productId所指向的产品
require(now>=product.auctionEndTime);// 因为此时要揭示竞价
bytes32 sealedBid =sha3(_amount,_secret); // 根据报价和密码确定唯一竞拍:获得小明第n次竞拍所对应的哈希值
Bid memory bidInfo = product.bids[msg.sender][sealedBid]; // 拿到小明的第n次竞拍
require(bidInfo.bidder>0,"该竞拍要存在");
require(bidInfo.revealed==false,"该竞拍要未揭示过");
uint refund; // 要退还给竞拍所对应的竞拍者的钱
uint amount = stringToUint(_amount);
if(bidInfo.value<amount){ // 该次竞拍不合理,直接退还他的押金
refund=bidInfo.value;
}else{// 竞拍合理的话
// 如果之前没有人报价,则初始价格自动降到第二高价,本次竞拍价作为最高价,并计算该退还他剩余的钱,他上交报价的钱
if(address(product.highestBidder)==0){
product.highestBidder = msg.sender; // msg.sender就是当前竞拍者
product.highestBid = amount;
product.secondHighest = product.startPrice;
refund = bidInfo.value - amount;
}else{
// 如果该竞拍的报价大于该产品最大的报价,则最高报价将为第二高,退还给最高竞价人钱(他的报价),该竞价设置为产品的最高报价,退还多余的钱
if(amount>product.highestBid){
product.secondHighest = product.highestBid;
product.highestBidder.transfer(product.highestBid);
product.highestBid = amount;
product.highestBidder = msg.sender;
refund = bidInfo.value - amount;
// 如果大于第二报价但小于最高报价,则降为第二高价,退还押金
}else if(amount>product.secondHighest){
product.secondHighest = amount;
refund = bidInfo.value;
// 如果当前报价比第二高价要低,则退还押金
}else{
refund = bidInfo.value;
}
}
}
// 设置该竞拍为已经揭示过
product.bids[msg.sender][sealedBid].revealed = true;
// 写到最后面是因为防止转账失败的话会回滚前面代码
if(refund>0){
msg.sender.transfer(refund);
}
}
// 字符串到数字:"123"=>123
function stringToUint(string s)private pure returns(uint){
bytes memory b = bytes(s);
uint result = 0;
for(uint i=0;i<b.length;i++){
if(b[i]>=48 && b[i]<=57){
result = result *10+(uint(b[i])-48);
}
}
return result;
}
function highestBidderInfo(uint _productId)public view returns(address,uint,uint){
Product memory product = stores[productIdInStore[_productId]][_productId]; // 得到_productId所指向的产品
return (product.highestBidder,product.highestBid,product.secondHighest);
}
function totalBids(uint _productId)public view returns(uint){
Product memory product = stores[productIdInStore[_productId]][_productId]; // 得到_productId所指向的产品
return product.totalBids;
}
}
执行ganache-cli打开区块链环境,随后web3.isConnected()就=true了
编译合约的脚本,node compile.js:
// 将编译后的abi和字节码存储到C:\Users\Jary\Desktop\project\truffle_project\compiled\EcommerceStore.json中去
var fs = require('fs-extra');
var solc = require('solc');
var path = require('path');
var compilePath = path.resolve(__dirname,'compiled'); // resolve的作用是拼接,compilePath : 当期目录/complied
fs.removeSync(compilePath) // 删掉已有的compilePath文件夹
fs.ensureDirSync(compilePath) // 若无compilePath文件夹,则新建,用于存放EcommerceStore.json
var sourceCode=fs.readFileSync('EcommerceStore.sol').toString() //拿到sol文件
var compiledCode=solc.compile(sourceCode) //用编译器编译它
Object.keys(compiledCode.contracts).forEach(name=>{
let contractName = name.replace(/^:/,''); // EcommerceStore
let filePath = path.resolve(compilePath,`${contractName}.json`); // C:\Users\Jary\Desktop\project\truffle_project\compiled\EcommerceStore.json
fs.outputJSONSync(filePath,compiledCode.contracts[name]);
})
编译后运行ganache-cli,开启以太坊环境:
部署合约到区块链的脚本,node deploy.js:
// 重新部署合约到区块链,并新建文件EcommerceStore.abi来存储abi
var Web3=require('web3') // 获得模块
var web3=new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) // 连接到以太坊节点上(本地),让该节点提供服务
//sudo chmod 666 aaa.sol// linux要写这句:修改文件权限使能读
var path = require('path');
var filePath = path.resolve(__dirname,'compiled/EcommerceStore.json'); // resolve的作用是拼接,compilePath : 当期目录/complied
var {interface,bytecode} = require(filePath); // 读取EcommerceStore.json
var abi = JSON.parse(interface) //将字符串反序列化转成对象,获得其中编译的abi
var MyContract = new web3.eth.Contract(abi,{data: bytecode});
web3.eth.getAccounts().then(
res=>{
MyContract.deploy().send({from:res[0],gas:3000000}); // deploy的参数是合约构造函数的参数
}
)
var fs=require('fs');
fs.writeFile('./compiled/EcommerceStore.abi', JSON.stringify(abi),error=>{
if (error) {
console.log('abi写入失败')
} else {
console.log('abi写入成功')
}
})
此时ganache-cli里面就有了合约地址
为了把文件上传到ipfs,需要开启ipfs:ipfs int ,然后ipfs daemon
上传几个文件并得到对应的哈希:ipfs add 文件地址
注意,当关闭ipfs时,上传的文件也没了,但是同样一个文件对应的哈希是一致的。
给合约变量赋值:node seed.js,其中seed.js:
// 添加东西(给合约变量赋值)
var fs = require('fs')
var solc = require('solc')
var Web3=require('web3') // 获得模块
var web3=new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
//sudo chmod 666 aaa.sol// linux要写这句:修改文件权限使能读
var sourceCode=fs.readFileSync('EcommerceStore.sol').toString() //拿到sol文件
var compiledCode=solc.compile(sourceCode) //用编译器编译它
var abi = JSON.parse(compiledCode.contracts[':EcommerceStore'].interface) //获得其中编译的abi
var contractAddr = "0xcc2e899e5bcd2b23468016cb536960ccb53194e3";
var MyContract = new web3.eth.Contract(abi, contractAddr); // 拿到区块链上已有的合约
// 开始添加产品
current_time = Math.round(new Date() / 1000);
var amt_1 = web3.utils.toWei('1', 'ether');
web3.eth.getAccounts().then(accounts=>{
// call是调用不会改变状态的合约函数,send是调用会花费交易的函数
// gas是交易的手续费,注意都把gas设定为3000000,因为gas有上限,也有下限
web3.eth.getAccounts().then(accounts=>
{
// on:看不同的结果执行不同的代码
MyContract.methods.addProductToStore('iphone 5', 'Cell Phones & Accessories', 'QmekZaRsMQXHwwDT7qu41khcPspwFLANnv1QEv8xdAR7Cz', 'QmbLRFj5U6UGTy3o9Zt8jEnVDuAw2GKzvrrv3RED9wyGRk', current_time, current_time + 200, amt_1, 0).send({from:accounts[0],gas:3000000})
// .on('receipt', receipt=>console.log('该交易入块了,receipt:',receipt))
// .on('error', error=>console.log('交易出错了,error:',error))
MyContract.methods.addProductToStore('iphone 6', 'Cell Phones & Accessories', 'QmPyt3L7VYM31KndYoEpAv23HSDSdhx7UYhpSYrwpUhpCt', 'QmbLRFj5U6UGTy3o9Zt8jEnVDuAw2GKzvrrv3RED9wyGRk', current_time, current_time + 200, amt_1, 0).call({from:accounts[0]}).then(console.log);
}
)
})
建立index.js渲染页面:
// 从合约里面拿出商品来,从而渲染页面
var ipfsAPI = require('ipfs-api'); // webpack漏洞,它找不到ipfs-api!
var ipfs = ipfsAPI({host: 'localhost', port: '5001', protocol: 'http'});
Window = new BrowserWindow({show: false,width: 1041, height: 650, minWidth: 1041, minHeight: 650,title:'新脸谱', center:true, resizable: true,webPreferences:{nodeIntegration:true}})
window.addEventListener('load', function() {
var Web3=require('web3');
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
window.web3 = web3;
var abi = JSON.parse('[{"constant":true,"inputs":[],"name":"getT","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_productId","type":"uint256"}],"name":"totalBids","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"testStr","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_category","type":"string"},{"name":"_imageLink","type":"string"},{"name":"_descLink","type":"string"},{"name":"_auctionStartTime","type":"uint256"},{"name":"_auctionEndTime","type":"uint256"},{"name":"_startPrice","type":"uint256"},{"name":"_productCondition","type":"uint256"}],"name":"addProductToStore","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_productId","type":"uint256"}],"name":"getProduct","outputs":[{"name":"","type":"uint256"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint8"},{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"productIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_productId","type":"uint256"}],"name":"highestBidderInfo","outputs":[{"name":"","type":"address"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_productId","type":"uint256"},{"name":"_bid","type":"bytes32"}],"name":"bid","outputs":[{"name":"","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_productId","type":"uint256"},{"name":"_amount","type":"string"},{"name":"_secret","type":"string"}],"name":"revealBid","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"test","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]');
var contractAddr = "0xcc2e899e5bcd2b23468016cb536960ccb53194e3";
var MyContract = new web3.eth.Contract(abi, contractAddr); // 拿到区块链上已有的合约
window.MyContract = MyContract;
// 展示合约保存的所有产品
renderStore();
// 当用户点击 html 中的 file 字段并选择一个文件上传时,触发change() 事件,将页面的图片读取到一个缓冲区。
var reader;
$("#product-image").change(function(event) {
const file = event.target.files[0]
reader = new window.FileReader()
reader.readAsArrayBuffer(file)
});
// 当用户点击提交时,把页面的数据整理成对象的格式
// 然后先将图片上传到ipfs上,再将图片描述上传到ipfs,然后账户0将产品信息+产品图片哈希+图片描述哈希保存到合约上,该保存动作被打包成一个交易存储到区块链上
$("#add-item-to-store").submit(function(event) {
const req = $("#add-item-to-store").serialize();
let params = JSON.parse('{"' + req.replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}');
let decodedParams = {}
Object.keys(params).forEach(function(v) {
decodedParams[v] = decodeURIComponent(decodeURI(params[v]));
});
saveProduct(reader, decodedParams);
event.preventDefault();
});
// 如果当前处于产品详情页,从合约获得相应产品,然后显示到页面上
if($("#product-details").length > 0) {
// 通过解析网址获得产品id
let productId = new URLSearchParams(window.location.search).get('id');
renderProductDetails(productId);
}
});
// 根据产品id把产品所有信息显示到页面上
function renderProductDetails(productId) {
MyContract.methods.getProduct(productId).call().then(
p=>{
console.log(p);
let content = "";
// 产品描述信息就是个哈希,还得从ipfs处拿下来,并显示到web标签当中
ipfs.cat(p[4]).then(function(file) {
content = file.toString();
$("#product-desc").append("<div>" + content+ "</div>");
});
$("#product-image").append("<img src='https://ipfs.io/ipfs/" + p[3] + "' width='250px' />");
$("#product-price").html(displayPrice(p[7]));
$("#product-name").html(p[1]);
$("#product-auction-end").html(displayEndHours(p[6]));
$("#product-id").val(p[0]);
$("#revealing, #bidding").hide();
let currentTime = getCurrentTimeInSeconds();
// 如果当前时间在产品的竞拍时间结束前,显示竞价;如果时间在竞价结束1分钟内,显示揭示报价;如果时间全超过了,都不显示
if(currentTime < p[6]) {
$("#bidding").show();
} else if (currentTime - (60) < p[6]) {
$("#revealing").show();
}
}
);
}
// 返回当前时间的秒数
function getCurrentTimeInSeconds(){
return Math.round(new Date() / 1000);
}
// 返回以wei为单位的数字
function displayPrice(amt) {
amt = amt +"";
return 'Ξ' + web3.utils.fromWei(amt, 'ether');
}
// 返回距离截止竞拍时间还有多少天,多少小时,多少分钟,多少秒,参数是截止竞拍时间的时间戳
function displayEndHours(seconds) {
let current_time = getCurrentTimeInSeconds()
let remaining_seconds = seconds - current_time;
if (remaining_seconds <= 0) {
return "Auction has ended";
}
let days = Math.trunc(remaining_seconds / (24*60*60));
remaining_seconds -= days*24*60*60;
let hours = Math.trunc(remaining_seconds / (60*60));
remaining_seconds -= hours*60*60;
let minutes = Math.trunc(remaining_seconds / 60);
remaining_seconds -= minutes * 60;
if (days > 0) {
return "Auction ends in " + days + " days, " + hours + ", hours, " + minutes + " minutes";
} else if (hours > 0) {
return "Auction ends in " + hours + " hours, " + minutes + " minutes ";
} else if (minutes > 0) {
return "Auction ends in " + minutes + " minutes ";
} else {
return "Auction ends in " + remaining_seconds + " seconds";
}
}
// 先将图片上传到ipfs上,再将图片描述上传到ipfs
// 然后账户0将产品信息+产品图片哈希+图片描述哈希保存到合约上,该保存动作被打包成一个交易存储到区块链上
function saveProduct(reader, decodedParams) {
let imageId, descId;
saveImageOnIpfs(reader).then(function(id) {
imageId = id;
saveTextBlobOnIpfs(decodedParams["product-description"]).then(function(id) {
descId = id;
saveProductToBlockchain(decodedParams, imageId, descId);
})
})
}
// 账户0将产品信息+产品图片哈希+图片描述哈希保存到合约上,该保存动作被打包成一个交易存储到区块链上
function saveProductToBlockchain(params, imageId, descId) {
console.log(params);
let auctionStartTime = Date.parse(params["product-auction-start"]) / 1000;
let auctionEndTime = auctionStartTime + parseInt(params["product-auction-end"]) * 24 * 60 * 60
web3.eth.getAccounts().then(accounts=>
{
var product_price = params["product-price"]+"";
MyContract.methods.addProductToStore(params["product-name"], params["product-category"], imageId, descId, auctionStartTime,
auctionEndTime, web3.utils.toWei(product_price, 'ether'), parseInt(params["product-condition"]))
.send({from:accounts[0],gas:3000000})
.on('receipt', receipt=>{
console.log(receipt);
$("#msg").show();
$("#msg").html("Your product was successfully added to your store!");
})
}
)
}
// 将图片上传到 IPFS,上传完毕然后返回上传后的哈希
function saveImageOnIpfs(reader) {
return new Promise(function(resolve, reject) {
const buffer = Buffer.from(reader.result);
ipfs.add(buffer)
.then((response) => {
console.log(response)
resolve(response[0].hash);
}).catch((err) => {
console.error(err)
reject(err);
})
})
}
// 将产品介绍上传到 IPFS
function saveTextBlobOnIpfs(blob) {
return new Promise(function(resolve, reject) {
const descBuffer = Buffer.from(blob, 'utf-8');
ipfs.add(descBuffer)
.then((response) => {
console.log(response)
resolve(response[0].hash);
}).catch((err) => {
console.error(err)
reject(err);
})
})
}
// 展示合约保存的所有产品
function renderStore() {
// 得到产品:调用合约里面的getProduct方法,然后异步进入then
MyContract.methods.getProduct(1).call().then(p=>$("#product-list").append(buildProduct(p)));
MyContract.methods.getProduct(2).call().then(p=>$("#product-list").append(buildProduct(p)));
}
/* 根据产品对应的数组生成一个div,展示出产品来
<div class="col-sm-3 text-center col-margin-bottom-1">
<img src="http://localhost:8080/ipfs/QmekZaRsMQXHwwDT7qu41khcPspwFLANnv1QEv8xdAR7Cz" width="150px">
<div>iphone 5</div>
<div>Cell Phones & Accessories</div>
<div>1597407993</div><div>1597408193</div>
<div>Ether 1000000000000000000</div>
</div>
*/
function buildProduct(product) {
let node = $("<div/>");
node.addClass("col-sm-3 text-center col-margin-bottom-1");
node.append("<a href='product.html?id=" + product[0] + "'><img src='http://localhost:8080/ipfs/" + product[3] + "' width='150px' /></a>");
node.append("<div>" + product[1]+ "</div>");
node.append("<div>" + product[2]+ "</div>");
node.append("<div>" + product[5]+ "</div>");
node.append("<div>" + product[6]+ "</div>");
node.append("<div>Ether " + product[7] + "</div>");
return node;
}
为了解释require,webpack index.js,它将处理结果放到了dist/main.js中
html页面引入js文件:
<!DOCTYPE html>
<html>
<head>
<title>去中心化</title>
<link href='https://fonts.proxy.ustclug.org/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="./dist/main.js"></script>
</head>
<body>
<div class="container-fluid">
<h1>Ecommerce Store</h1>
<div>Total Products: <span id="total-products"></span></div>
<a href="list-item.html" class="btn btn-primary">List Item</a>
<div class="row">
<div class="col-sm-2">
<h2>Categories</h2>
<div id="categories">
</div>
</div>
<div class="col-sm-10">
<div class="row">
<h2 class="text-center">Products To Buy</h2>
<div class="row">
<div class="row" id="product-list">
</div>
</div>
</div>
<div class="row">
<h2 class="text-center">Products In Reveal Stage</h2>
<div class="row">
<div class="row" id="product-reveal-list">
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
产品详情页product.html:
<!DOCTYPE html>
<html>
<head>
<title>Decentralized Ecommerce Store</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="./dist/main.js"></script>
<!-- 除了仅仅显示产品细节,还有两个表单,一个用于出价,另一个用于揭示出价。 -->
</head>
<body>
<div class="container">
<h1 class="text-center">Product Details</h1>
<div class="container">
<div class="row" id="product-details">
<div style="display: none;" class="alert alert-success" id="msg"></div>
<div class="col-sm-12">
<div class="col-sm-4">
<div id="product-image"></div>
<div id="product-name"></div>
<div id="product-auction-end"></div>
</div>
<div class="col-sm-8">
<h3>Start Price: <span id="product-price"></span></h3>
<form id="bidding" class="col-sm-4">
<h4>Your Bid</h4>
<div class="form-group">
<label for="bid-amount">Enter Bid Amount</label>
<input type="text" class="form-control" name="bid-amount" id="bid-amount" placeholder="Amount > Start Price" required="required">
</div>
<div class="form-group">
<label for="bid-send-amount">Enter Amount To Send</label>
<input type="text" class="form-control" name="bid-send-amount" id="bid-send-amount" placeholder="Amount >= Bid Amount" required="required">
</div>
<div class="form-group">
<label for="secret-text">Enter Secret Text</label>
<input type="text" class="form-control" name="secret-text" id="secret-text" placeholder="Any random text" required="required">
</div>
<input type="hidden" name="product-id" id="product-id" />
<button type="submit" class="btn btn-primary">Submit Bid</button>
</form>
<form id="revealing" class="col-sm-4">
<h4>Reveal Bid</h4>
<div class="form-group">
<label for="actual-amount">Amount You Bid</label>
<input type="text" class="form-control" name="actual-amount" id="actual-amount" placeholder="Amount > Start Price" required="required">
</div>
<div class="form-group">
<label for="reveal-secret-text">Enter Secret Text</label>
<input type="text" class="form-control" name="reveal-secret-text" id="reveal-secret-text" placeholder="Any random text" required="required">
</div>
<input type="hidden" name="product-id" id="product-id" />
<button type="submit" class="btn btn-primary">Reveal Bid</button>
</form>
</div>
</div>
<div id="product-desc" class="col-sm-12">
<h2>Product Description</h2>
</div>
</div>
</div>
</div>
</body>
</html>
这两个html都引用了webpack index.js,后生成的main.js

浙公网安备 33010602011771号