区块链3

 拍卖系统

讲义:https://github.com/confucianzuoyuan/blockchain-tutorial/tree/master/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E6%95%99%E7%A8%8B/%E6%8B%8D%E5%8D%96%E5%BA%94%E7%94%A8

和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

 

posted @ 2020-08-10 18:22  Jary霸  阅读(316)  评论(0编辑  收藏  举报