購物車 (JS物件導向實現)

2020-08-09 12:36:52

效果圖:效果图
程式碼實現:

1.js檔案

#####(1)商品列表模組

import Utils from "./Utils.js";
export default class GoodsItem {

    //設定靜態變數styleBool,用來控制多次回圈,CSS只設置一次
    static styleBool = false;
    data; //將外部傳入的陣列的item數據賦給data屬性
    preIcon;  //miniicon內目標圖片
    constructor() {
        this.elem = this.createElem();

         //首先判斷 回圈建立的div是否新增CSS樣式,當不存在時,執行新增CSS函數
        if (!GoodsItem.styleBool) GoodsItem.setStyle();
         //將新增HTML標籤
        this.renderHTML();
    }

    //建立商品容器
    createElem() {
        if (this.elem) return this.elem;
        var div = Utils.ce("div");
        div.className = "goodsItem";
        return div;
    }
    //新增到傳入的父元素內
    appendTo(parent) {
        if (typeof parent === "string") parent = document.querySelector(parent);
        parent.appendChild(this.elem);
    }

    //給新建立的標籤元素新增CSS屬性
    setData(_data) {
        this.data = _data;
        this.icon = this.elem.querySelector(".icon");
        let presell = this.elem.querySelector(".presell");
        this.miniIconCon = this.elem.querySelector(".miniIconCon");
        var price = this.elem.querySelector(".price");
        var infoCon = this.elem.querySelector(".infoCon");
        var evaluate = this.elem.querySelector(".evaluate");
        var shopCon = this.elem.querySelector(".shopCon");
        var tagCon = this.elem.querySelector(".tagCon");

        //如果item內presell不是空字串
        if (this.data.presell.trim().length > 0) {
            //在presel標籤裡新增內容,內容爲傳入陣列內
            presell.textContent = this.data.presell;
        } else {
            //如果presell是空字串,隱藏presell標籤
            presell.style.display = "none"
        }
        //爲新增miniicon裏面的圖片設定字串
        var miniIconStr = "";
        //遍歷item內miniIcon內的每一項
        this.data.miniIcon.forEach(item => {
            //將miniIcon內每一項圖片路徑轉換爲圖片標籤
            miniIconStr += `<img src=${item} class="miniIcon">`
        })
        //將新增標籤的字串加入到miniIcon內,生成圖片標籤
        this.miniIconCon.innerHTML = miniIconStr;
        //給每一項小標籤新增滑鼠滑出事件
        this.miniIconCon.addEventListener("mouseover", e => this.iconMouseHandler(e))

        //先給icon設定預設值 預設第一張圖片
        this.changeIcon(0);
         
        //價格標籤內填入item物件price內容
        price.textContent = this.data.price.toFixed(2);
        //資訊標籤內填入item物件info內容
        infoCon.textContent = this.data.info
        //評價標籤內填入item物件evaluate內容
        evaluate.textContent = this.data.evaluate;
        //店鋪標籤內填入item物件shopName內容
        shopCon.textContent = this.data.shopName;

        //根據dic物件內key的名稱對應item物件內tag陣列內的字串匹配,匹配成功的給該標籤設定對應的class name,從而擁有CSS樣式
        var dic={
            "自營":"goods_icon_0",
            "放心購":"goods_icon_1",
            "本地倉":"goods_icon_0",
            "贈":"goods_icon_2",
            "京東物流":"goods_icon_2",
            "秒殺":"goods_icon_2",
            "免郵":"goods_icon_2",
            "險":"goods_icon_3",
            "券":"goods_icon_3",
        }
        var tagStr="";

        //匹配item裡tag內容和dic內prop內容,匹配成功的 賦給class
         this.data.tag.forEach(item=>{
             for(let prop in dic){
                 if(item.indexOf(prop)>=0)tagStr+=`<span class='${dic[prop]}'>${item}</span>`;
             }
            
         });
         tagCon.innerHTML=tagStr;
    }

    iconMouseHandler(e) {

        //如果目標事件不是圖片型別,直接返回
        if (e.target.constructor !== HTMLImageElement) return;
        //匹配目標事件爲偵聽事件的哪一個子元素 將找到的下標賦給index。
        var index = Array.from(e.currentTarget.children).indexOf(e.target);
        
        this.changeIcon(index);
    }

    changeIcon(index) {
        //判斷preIcon是否存在 
        if (this.preIcon) {
            //如果存在,設定preIcon 的邊框
            this.preIcon.style.border = "1px solid #CCCCCC";
        }
        //如果不存在 ,設定icon內圖片的路徑爲miniicon對應的大圖片
        this.icon.src = this.data.icon[index];
        //然後給preIcon賦值爲miniIcon內圖片的下標
        this.preIcon = this.miniIconCon.children[index];
        //給miniicon內目標事件圖片設定紅色邊框
        this.preIcon.style.border = "2px solid #e4393c";
    }
    renderHTML() {
        this.elem.innerHTML = `
            <div class="iconCon">
                <img class="icon">
                <div class="presell">
            </div>
            <div class="miniIconCon"></div>
            <div class="priceCon">
                <span class="money">¥</span>
                <span class="price"></span>
            </div>
            <a class="infoCon" href="#"></a>
            <div class="evaluateCon"><span class="evaluate"></span>條評價</div>
            <div class="shopCon"></div>
            <div class="tagCon">
            </div>
        `
    }

    static setStyle() {
        GoodsItem.styleBool = true;
        Utils.setStyle({
            ".goodsItem": {
                width: "240px",
                height: "466px",
                float: "left",
                margin: "5px"
            },
            ".goodsItem:hover": {
                boxShadow: "0px 0px 4px #999999"
            },
            ".iconCon": {
                width: "220px",
                height: "220px",
                marginBottom: "5px",
                position: "relative",
                margin: "auto",
                left: 0,
                right: 0,
            },
            ".presell": {
                width: "200px",
                height: "25px",
                position: "absolute",
                bottom: "0px",
                backgroundColor: "rgba(0,0,0,0.4)",
                fontSize: "12px",
                color: "#FFFFFF",
                paddingLeft: "20px",
                lineHeight: "25px"
            },
            ".miniIcon": {
                width: "25px",
                height: "25px",
                // border:"2px solid #e4393c00",
                border: "1px solid #CCCCCC",
                marginRight: "5px"
            },
            ".priceCon": {
                width: "220px",
                height: "22px",
                color: "#e4393c",
                fontSize: "20px",
                marginTop:"5px",
            },
            ".money": {
                fontSize: "16px",
            },
            ".price": {
                marginLeft: "-10px"
            },
            ".infoCon": {
                width: "220px",
                height: "40px",
                wordWrap: "break-word",
                overflow: "hidden",
                display: "block",
                fontSize: "12px",
                color: "#333333",
                lineHeight: "20px",
                marginTop: "10px",
                textDecoration: "none"
            },
            ".evaluateCon": {
                width: "220px",
                height: "18px",
                fontSize: "12px",
                color: "#333333",
                marginTop: "5px"
            },
            ".evaluate": {
                color: "#646FB0",
                fontWeight: "600",
            },
            ".shopCon": {
                fontSize: "12px",
                marginTop: "5px",
                color: "#AAAAAA",
                overflow: "hidden",
                whiteSpace: "nowrap",
                textOverflow: "ellipsis",
                width: "122px",
                height: "18px"
            },
            ".goods_icon_0": {
                float: "left",
                height: "16px",
                lineHeight: "16px",
                padding: "0 3px",
                marginRight: "3px",
                overflow: "hidden",
                textAlign: "center",
                fontStyle: "normal",
                fontSize: "12px",
                fontFamily: '"Helvetica Neue","Hiragino Sans GB",SimSun,serif',
                background: "#e23a3a",
                color: "#FFF",
                cursor: "default",
                borderRadius: "2px",
            },
            ".goods_icon_1": {
                border: "1px solid #e23a3a",
                borderColor: "#4d88ff",
                color: "#4d88ff",
                float: "left",
                height: "14px",
                lineHeight: "14px",
                padding: "0 3px",
                marginRight: "3px",
                overflow: "hidden",
                textAlign: "center",
                fontStyle: "normal",
                fontSize: "12px",
                fontFamily: '"Helvetica Neue","Hiragino Sans GB",SimSun,serif"',
                borderRadius: "2px",
            },
            ".goods_icon_2": {
                float: 'left',
                height: '14px',
                lineHeight: '14px',
                padding: '0 3px',
                border: '1px solid #e23a3a',
                marginRight: '3px',
                overflow: 'hidden',
                textAlign: 'center',
                fontStyle: 'normal',
                fontSize: '12px',
                fontFamily: '"Helvetica Neue","Hiragino Sans GB",SimSun,serif',
                borderRadius: '2px',
                color: '#e23a3a',
            },
            ".goods_icon_3": {
                float: 'left',
                height: '16px',
                lineHeight: '16px',
                padding: '0 3px',
                marginRight: '3px',
                overflow: 'hidden',
                textAlign: 'center',
                fontStyle: 'normal',
                fontSize: '12px',
                fontFamily: '"Helvetica Neue","Hiragino Sans GB",SimSun,serif',
                background: '#e23a3a',
                color: '#FFF',
                cursor: 'default',
                borderRadius: '2px',
                background: "#4b9bfc",
            },
            ".tagCon":{
                marginTop:"10px"
            }
        })
    }
}
(2)選擇框模組
export default class CheckBox extends EventTarget{
    elem;
    label;  //接收外部以參數傳入的lable
    checked=false; //是否選中

    //建立多選框
    constructor(_data,_label){
        super();

        //將外部傳入的參數賦給全域性屬性
        this.data=_data;
        this.label=_label;
        this.elem=this.createElem();
    }
    //建立多選框容器
    createElem(){
        if(this.elem) return this.elem;
        let div=document.createElement("div");
        div.style.float="left";
        div.style.marginRight="5px";
        div.style.marginTop="4px";
        div.style.position="relative";
        let icon=document.createElement("span");
        Object.assign(icon.style,{
            width:"13px",
            height:"13px",
            position:"relative",
            display:"inline-block",
            marginRight:"3px",
            border:"1px solid #666666",
            borderRadius:"3px",
         
        });
        //建立多選框選中後對號
        let a=document.createElement("div");
        Object.assign(a.style,{
            width: "3px",
            height: "6px",
            marginLeft:"4px",
            marginTop:"1px",
            borderColor: "#FFFFFF",
            borderStyle: "solid",
            borderWidth: "0 2px 3px 0",
            transform: "rotate(45deg)",
        })
        icon.appendChild(a)
        div.appendChild(icon);

        //建立多選框後面的文字容器
        let labelSpan=document.createElement("span");
        labelSpan.textContent=this.label;
        labelSpan.style.userSelect="none";
        labelSpan.style.position="relative"
        div.appendChild(labelSpan);
        
        //div設定點選事件
        div.addEventListener("click",e=>this.clickHandler(e));
        //div設定滑鼠滑入事件
        div.addEventListener("mouseover",e=>this.mouseHandler(e));
        //div設定滑鼠滑出事件
        div.addEventListener("mouseout",e=>this.mouseHandler(e));
        return div;
    }
    //將建立的元素插入到指定父元素內
    appendTo(parent){
        if(typeof parent==="string") parent=document.querySelector(parent);
        parent.appendChild(this.elem);
    }
    //將元素插入到指定的父元素內,且在指定子元素前面
    insertTo(parent,elem){
        if(typeof parent==="string") parent=document.querySelector(parent);
        if(typeof elem==="string") elem=document.querySelector(elem);
        parent.insertBefore(this.elem,elem);
    }

    //滑鼠點選後執行的函數
    clickHandler(e){
        //給複選框按鈕設定開關
        this.checked=!this.checked;

        //執行setCheck()函數,且將this.checked(即複選框是否選中)作爲參數代入到函數內。
        this.setCheck(this.checked); 

        //建立一個事件名爲:change的事件
        var evt=new Event("change");
        //將this.checked和this.data賦值給事件 e ,這樣就可以把this.checked和this.data拋發出去了。
        evt.checked=this.checked;
        evt.data=this.data;
        //將事件拋發出去
        this.dispatchEvent(evt);
    }
    //點選事件後,具體執行的函數
    setCheck(_check){
        //獲得傳入的參數,並賦值給this.checked
        this.checked=_check;
        //根據checked的值編輯icon的背景顏色和邊框。
        Object.assign(this.elem.firstElementChild.style,{
            backgroundColor:this.checked ? "rgb(54 161 251)" : "#FFFFFF",
            border:this.checked ? "1px solid rgb(54 161 251)" : "1px solid #666666"
        });
        //編輯多選框內小對號是否出現
        this.elem.firstElementChild.firstElementChild.display=this.checked ? "block" : "none"
    }
    //設定滑鼠滑入滑出事件函數
    mouseHandler(e){
        //如果 複選框爲選中,即this.checked爲true
        if(this.checked){
            //當複選框爲選中時,無論滑鼠滑入滑出,邊框始終爲固定值
            this.elem.firstElementChild.style.borderColor="1px solid rgb(54 161 251)";
            return;
        }
        //當滑鼠滑入時,改變邊框顏色
        if(e.type==="mouseover"){
            this.elem.firstElementChild.style.borderColor="#aaaaaa";
        }
        //當滑鼠滑出時,改變邊框顏色
        else{
            this.elem.firstElementChild.style.borderColor="#666666";
        }
    }
}
(3)計數器模組
import Utils from "./Utils.js";
export default class StepNumber extends EventTarget {
  leftBn;
  rightBn;
  input;
  ids;
  step = 1;
  data;
  constructor(_data) {
    super();
    this.data = _data;
    this.elem = this.creatElem();
  }
  creatElem() {
    if (this.elem) return this.elem;
    var div = Utils.ce("div", {
      width: "80px",
      height: "22px",
      position: "relative",
    });
    this.leftBn = Utils.ce("a", {
      width: "15px",
      height: "20px",
      border: "1px solid #cccccc",
      display: "block",
      textDecoration: "none",
      color: "#333333",
      textAlign: "center",
      lineHeight: "20px",
      float: "left",
      backgroundColor: "#FFFFFF",
    });
    this.href = "javaScript:void(0)";
    this.textContent = "-";
    this.input = Utils.ce("input", {
      border: "none",
      borderTop: "1px solid #cccccc",
      borderBottom: "1px solid #cccccc",
      textAlign: "center",
      outline: "none",
      float: "left",
      width: "42px",
      height: "18px",
    });
    this.input.value = "1";
    this.rightBn = this.leftBn.cloneNode(false);
    this.textContent = "+";
    div.appendChlid(this.leftBn);
    div.appendChlid(this.input);
    div.appendChlid(this.righttBn);
    this.leftBn.addEventListener("click", (e) => this.clickHandler(e));
    this.rightBn.addEventListener("click", (e) => this.clickHandler(e));
    this.input.addEventListener("input", (e) => inputHandler(e));
    return div;
  }
  appendTo(parent) {
    if (typeof parent === "string") parent = document.querySelector(parent);
    parent.appendChild(this.elem);
  }
  inputHandler(e) {
    if (this.ids !== undefined) return;
    this.ids = setTimeout(() => {
      clearTimeout(ids);
      this.ids = undefined;
      this.setStep(this.input.value, true);
    });
  }
  clickHandler(e) {
    var bn = e.currentTarget;
    if (bn.bool) return;
    if (bn === this.leftBn) {
      this.step--;
    } else {
      this.step++;
    }
    this.setStep(this.step, true);
  }
  setStep(_step, bool) {
    if (typeof _step === "string") _step = Number(_step.replace(/\D/g, ""));
    if (_step >= 99) {
      _step = 99;
      this.rightBn.style.color = "#CCCCCC";
      this.rightBn.bool = true;
    }
    if (_step <= 1) {
      _step = 1;
      this.leftBn.style.color = "#CCCCCC";
      this.leftBn.bool = true;
    } else {
      this.rightBn.bool = false;
      this.rightBn.style.color = "#000000";
      this.leftBn.bool = false;
      this.leftBn.style.color = "#000000";
    }
    this.step = _step;
    this.input.value = this.step;
    if (bool) {
      var evt = new Event("change");
      evt.data = this.data;
      evt.step = this.step;
      this.dispatchEvent();
    }
  }
}

(4)購物車模組
import Utils from "./Utils.js";
import CheckBox from "./CheckBox註釋.js";
import StepNumber from "./StepNumber註釋.js";

export default class Shopping extends EventTarget{

    //table存放購物車放在的表格
    table;

    //設定靜態屬性styleBoll 存放是否已經建立CSS樣式
    static styleBool=false;

    //存放表頭內容的陣列
    headList=["全選","","商品","","單價","數量","小計","操作"];

    //建立三個靜態屬性用於拋發事件名
    static CHECK_CHANGE="check_change_event";
    static STEP_CHANGE="step_change_event";
    static DELETE_CHANGE="delete_change_event";

    //建立購物車
    constructor(){
        super();

        //如果沒有建立CSS樣式 ,新增CSS樣式
        if(!Shopping.styleBool) Shopping.setStyle();
        this.elem=this.createElem();
    }

    //建立購物車外部容器
    createElem(){
        if(this.elem) return this.elem;
        return Utils.ce("div");
    }
    appendTo(parent){
        if(typeof parent==="string") parent=document.querySelector(parent);
        parent.appendChild(this.elem);
    }

    //根據 參數帶入的列表 設定數據,根據數據生成表格
    setData(list){
        //如果表格存在,刪除表格,用於更新表格
        if(this.table) this.table.remove();
        
        //建立表格,並且給表格元素賦予class名
        this.table=Utils.ce("table");
        this.table.className="tableClass";

        //根據list給表格建立表頭
        this.createHead(list);
        //根據list給表格建立每一行
        this.createListTr(list);
        //將表格 新增進購物車div裡
        this.elem.appendChild(this.table);
    }

    //建立表頭
    createHead(list){

        //建立第一行,並且賦給clssName
        var thr=Utils.ce("tr");
        thr.className="thr";
        //根據表頭內容長度,建立單元格
        for(var j=0;j<this.headList.length;j++){

            //根據需求,將第一列跟第二列合併,此時不建立第二列。
           if(j===1) continue;
           //建立單元格
           var th=Utils.ce("th");
           //單元格內文字爲陣列對應的內容
           th.textContent=this.headList[j];
           //單元格內元素左對齊
           th.style.textAlign="left";
           //設定單元格左填充
           th.style.paddingLeft="15px";

           //如果爲第一個單元格時
           if(j===0){
               //合併第一,第二列
              th.setAttribute("colspan","2");

              //第一個單元格設定全選按鈕
              let ck=new CheckBox();

              //當list內每一項元素內所有的checked 對應的值爲true時,bool才爲true
              var bool=list.every(item=>{
                  return item.checked
              })
              //設定全選按鈕點選事件函數
                ck.setCheck(bool);
                //將ck插入到th內,且在th子元素前面
                ck.insertTo(th,th.firstChild);
                //觸發change事件時,執行checkHandler函數
                ck.addEventListener("change",e=>this.checkHandler(e))
           }
           //第三個單元格內容居中,且左填充爲0
           if(j===2){
             th.style.textAlign="center"
             th.style.paddingLeft="0";
           }
           //將單元格插入到表頭內
           thr.appendChild(th);
        }
        //將表頭插入到表格裡
        this.table.appendChild(thr)
    }

    //建立每一行
    createListTr(list){
        //根據列表裏的數據建立每一行
        for(var i=0;i<list.length;i++){
            var tr=Utils.ce("tr");
            //給tr賦予class名
            tr.className="trs";
            //如果是第一行,改變邊框
            if(i===0) tr.style.borderTop="2px solid #999999";
            //遍歷陣列內每一個物件,根據資訊建立單元格
            for(var prop in list[i]){
                if(prop==="id") continue;
                var td=Utils.ce("td");
                td.style.padding="15px 0 10px";
                td.style.wordWrap="break-word";
                //執行createTdContent函數,給單元格賦上屬性和新增CSS樣式
                this.createTdContent(td,list[i],prop);
                tr.appendChild(td);
            }
            this.table.appendChild(tr);
        }   
    }

    //給單元格新增內容和CSS屬性
    createTdContent(td,data,prop){
        //根據物件內容,判斷單元格內容
        switch(prop){
            case "checked":
                //建立單選框
                let ck=new CheckBox(data);
                //傳入checked此時的值,判斷是否選中
                ck.setCheck(data[prop]);
                ck.appendTo(td);
                //設定change事件偵聽,獲取CheckBox類內拋發的數據
                ck.addEventListener("change",e=>this.checkHandler(e))
                td.style.padding="0 15px 0 11px";
                break;
            case "icon":
                //建立圖片
                var img=new Image();
                //從列表中獲取圖片src
                img.src=data[prop];
                //給圖片賦予CSS樣式
                Object.assign(img.style,{
                    width:"80px",
                    height:"80px"
                })
                td.appendChild(img);
                break;
            case "total":
                td.style.fontWeight="600";
            case "price":
                td.textContent="¥"+data[prop].toFixed(2);
                break
            case "deleted":
                //建立刪除標籤
                var span=Utils.ce("span");
                span.style.marginLeft="10px";
                span.textContent="刪除";
                //將data賦值爲span的屬性
                span.data=data;
                td.appendChild(span);
                //設定點選事件偵聽
                span.addEventListener("click",e=>this.deleteHandler(e));
                break;
            case "num":
                //建立數據累加器
                var step=new StepNumber(data);
                //將列表內數據以參數的形式傳入setStep函數
                step.setStep(data[prop]);
                step.appendTo(td);
                //設定change事件偵聽,獲取StepNumber類內拋發的數據
                step.addEventListener("change",e=>this.stepChangeHandler(e))
                break;
            default :
            //其他單元格內填入列表內對應數據
               td.textContent=data[prop];
               td.style.paddingLeft="15px"
               break
        }
    }

    //設定事件拋發,呼叫該類的該方法時,偵聽獲取數據
    stepChangeHandler(e){
       var evt=new Event(Shopping.STEP_CHANGE);
       evt.data=e.data;
       evt.step=e.step;
       this.dispatchEvent(evt);
    }
    checkHandler(e){
        var evt=new Event(Shopping.CHECK_CHANGE);
        evt.data=e.data;
        evt.checked=e.checked;
        this.dispatchEvent(evt);
    }
    deleteHandler(e){
        var evt=new Event(Shopping.DELETE_CHANGE);
        evt.data=e.currentTarget.data;
        this.dispatchEvent(evt);
    }
    //靜態函數設定CSS樣式
    static setStyle(){
        //首先把靜態變數styleBool設定爲true,即已經設定CSS屬性。
        Shopping.styleBool=true;
        Utils.setStyle({
            ".tableClass":{
                width:"990px",
                position:"relative",
                margin:"auto",
                left:0,
                right:0,
                borderCollapse:"collapse"
            },
            ".thr":{
                width:"100%",
                height:"32px",
                lineHeight:"32px",
                padding:"5px 0px",
                backgroundColor:"#F3F3F3",
                color:"#666666",
                fontSize:"12px",
                margin:"0px 0px 10px"
            },
            ".trs":{
                width:"100%",
                backgroundColor:"#fff4e8",
                color:"#666666",
                fontSize:"12px",
                borderTop:"1px solid #CCCCCC",
            },
            ".thr>th:nth-child(1)":{
                width:"122px",
                paddingLeft:"11px"
            },
            ".thr>th:nth-child(2)":{
                width:"208px",
             
            },
            ".thr>th:nth-child(3)":{
                width:"150px",
                padding:"0 10px 0 20px"
            },
            ".thr>th:nth-child(4)":{
                width:"100px",
                paddingRight:"40px"
            },
            ".thr>th:nth-child(5)":{
                width:"120px",
               
            },
            ".thr>th:nth-child(6)":{
                width:"100px",
                paddingRight:"40px"
            },
            ".thr>th:nth-child(7)":{
                width:"75px",
                paddingLeft:"11px"
            }
        })
    }
}
(5)合成頁面模組
import GoodsItem from "./GoodsItem註釋.js";
import Shopping from "./Shopping註釋.js";
import Utils from "./Utils.js";

export default class Main {
    list;  //接收以參數傳入的列表
    goodsList = [];
    shoppingList = []; //建立一個空陣列,然後存放點選商品後加入的商品數據
    shopping;  //shopping範例化
    constructor(_list) {
        this.list = _list;
        //建立div容器存放商品列表
        var div = Utils.ce("div");
        for (var i = 0; i < _list.length; i++) {
            //根據列表長度建立商品顯示內容
            var goods = new GoodsItem();
            //將商品顯示內容放入div中
            goods.appendTo(div);
            //將列表內對應數據加入商品顯示內容
            goods.setData(_list[i]);
            //給商品顯示內容新增點選偵聽事件
            goods.addEventListener("click", e => this.clickHandler(e));
        }
        document.body.appendChild(div);

        //生成購物車範例化物件
        this.shopping=new Shopping();
        //將購物車放入body裡
        this.shopping.appendTo("body");
        
        //購物車偵聽多選框是否被選中
        this.shopping.addEventListener(Shopping.CHECK_CHANGE,e=>this.checkChangeHandler(e));
        //偵聽累加器是否被修改
        this.shopping.addEventListener(Shopping.STEP_CHANGE,e=>this.stepChangeHandler(e));
        //偵聽是否刪除數據
        this.shopping.addEventListener(Shopping.DELETE_CHANGE,e=>this.deleteChangeHandler(e));

        //建立總價容器
        this.totalDiv=Utils.ce("div",{
            fontSize:"30px",
            textAlign:"right",
            paddingRight:"150px",
            color:"red"
        },"body");
    }

    //點選商品顯示區後,執行的點選事件,將點選的商品加入購物車
    clickHandler(e) {
        //將點選商品的資訊賦值給data
        var data = e.currentTarget.data;
        //當數據重複時,使用reduce將數據篩除,只留一條數據。
       var item=this.shoppingList.reduce((value,item)=>{
            if(item.id===data.id) value=item;
            return value;
        },null);
        //如果
        if (item) {
            item.num++;
            item.total=item.price*item.num;
        } else {
            var obj = {
                id: data.id,
                checked: false,
                icon: e.icon,
                name: data.info,
                info: "",
                price: data.price,
                num: 1,
                total: data.price,
                deleted: false
            }
            this.shoppingList.push(obj);
        }
        this.shopping.setData(this.shoppingList)
    }
    checkChangeHandler(e){
       if(!e.data){
          this.shoppingList.forEach(item=>{
              item.checked=e.checked;
          })
       }else{
        this.shoppingList.forEach(item=>{
            if(item.id===e.data.id) item.checked=e.checked;
        })
       }
       this.shopping.setData(this.shoppingList);
       this.totalPrice();
    }
    stepChangeHandler(e){
        this.shoppingList.forEach(item=>{
            if(item.id===e.data.id){
                item.num=e.step;
                item.total=e.step*item.price;
            }
        });
        this.shopping.setData(this.shoppingList);
        this.totalPrice();
    }
    deleteChangeHandler(e){
        this.shoppingList=this.shoppingList.filter(item=>{
            return item.id!==e.data.id;
        });
        this.shopping.setData(this.shoppingList)
        this.totalPrice();
    }
    totalPrice(){
        this.totalDiv.textContent=this.shoppingList.reduce((value,item)=>{
            if(item.checked) value+=item.total;
            return value;
        },0)
    }
}
(5)建立元素板塊
export default class Utils{
 static ce(type,style,parent){
        var elem=document.createElement(type);
        if(style){
            for(var prop in style){
                elem.style[prop]=style[prop];
            }
        }
        if(typeof parent==="string") parent=document.querySelector(parent);
        if(parent) parent.appendChild(elem);
        return elem;
    }}

2.html頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    
</head>
<body>
    <script type="module">
         import Main from "./js/Main註釋.js";
         var arr = [
            {
                id: 1001,
                icon: ["./img/a1.jpg"],
                miniIcon: ["./img/mini_a1.jpg"],
                price: 1199,
                info: "榮耀Play4T 全網通6GB+128GB大記憶體 藍水翡翠 4000mAh大電池 4800萬AI攝影 6.39英寸魅眼屏",
                info1: "",
                used: false,
                evaluate: "31萬+",
                shopName: "榮耀京東自營旗艦店",
                presell: "",
                tag: ["自營", "放心購"]
            },

            {
                id: 1002,
                icon: ["./img/a2.jpg"],
                miniIcon: ["./img/mini_a2.jpg"],
                price: 1389,
                info: "榮耀Play4T Pro 麒麟810晶片 OLED螢幕指紋 4800萬高感光夜拍三攝 22.5W超級快充 全網通6GB+128GB 幻夜黑",
                info1: "",
                used: false,
                evaluate: "10萬+",
                shopName: "榮耀京東自營旗艦店",
                presell: "預售中",
                tag: ["自營", "放心購", "秒殺", "贈"]
            },

            {
                id: 1003,
                icon: ["./img/b1_1.jpg", "./img/b1_2.jpg", "./img/b1_3.jpg"],
                miniIcon: ["./img/mini_b1_1.jpg", "./img/mini_b1_2.jpg", "./img/mini_b1_3.jpg"],
                price: 1389,
                info: "榮耀Play4T Pro 麒麟810晶片 OLED螢幕指紋 4800萬高感光夜拍三攝 22.5W超級快充 全網通6GB+128GB 藍水翡翠",
                info1: "",
                used: false,
                evaluate: "37萬+",
                shopName: "榮耀京東自營旗艦店",
                presell: "",
                tag: ["自營", "放心購", "本地倉", "秒殺", "贈"]
            },

            {
                id: 1004,
                icon: ["./img/b2_1.jpg", "./img/b2_2.jpg", "./img/b2_3.jpg"],
                miniIcon: ["./img/mini_b2_1.jpg", "./img/mini_b2_2.jpg", "./img/mini_b2_3.jpg"],
                price: 1199,
                info: "榮耀Play4T 全網通6GB+128GB大記憶體 藍水翡翠 4000mAh大電池 4800萬AI攝影 6.39英寸魅眼屏",
                info1: "",
                used: false,
                evaluate: "31萬+",
                shopName: "榮耀京東自營旗艦店",
                presell: "",
                tag: ["自營", "放心購", "本地倉", "贈"]
            },

            {
                id: 1005,
                icon: ["./img/c1.jpg"],
                miniIcon: ["./img/mini_c1.jpg"],
                price: 1389,
                info: "榮耀Play4T Pro 麒麟810晶片 OLED螢幕指紋 4800萬高感光夜拍三攝 22.5W超級快充 全網通6GB+128GB 幻夜黑",
                info1: "",
                evaluate: "10萬+",
                used: false,
                shopName: "榮耀京東自營旗艦店",
                presell: "",
                tag: ["自營", "放心購", "秒殺", "贈"],
            },

            {
                id: 1006,
                icon: ["./img/c2_1.jpg", "./img/c2_2.jpg", "./img/c2_3.jpg", "./img/c2_4.jpg"],
                miniIcon: ["./img/mini_c2_1.jpg", "./img/mini_c2_2.jpg", "./img/mini_c2_3.jpg", "./img/mini_c2_4.jpg"],
                price: 1389,
                info: "麒麟980晶片;超感光徠卡四攝10倍混合變焦;店鋪首頁領白條免息券",
                info1: "",
                evaluate: "81萬+",
                used: true,
                shopName: "華爲京東自營官方旗艦店",
                presell: "",
                tag: ["自營", "放心購", "秒殺", "贈"],
            },

            {
                id: 1007,
                icon: ["./img/d1_1.jpg", "./img/d1_2.jpg", "./img/d1_3.jpg", "./img/d1_4.jpg", "./img/d1_5.jpg"],
                miniIcon: ["./img/mini_d1_1.jpg", "./img/mini_d1_2.jpg", "./img/mini_d1_3.jpg", "./img/mini_d1_4.jpg", "./img/mini_d1_5.jpg"],
                price: 2489,
                info: "榮耀Play4 Pro 5G雙模 麒麟990 4000萬超感光暗拍 40W超級快充 8GB+128GB幻夜黑",
                info1: "",
                used: false,
                evaluate: "21萬+",
                shopName: "榮耀京東自營官方旗艦店",
                presell: "",
                tag: ["自營", "放心購", "秒殺", "贈"]
            },

            {
                id: 1008,
                icon: ["./img/d2_1.jpg", "./img/d2_2.jpg", "./img/d2_3.jpg", "./img/d2_4.jpg"],
                miniIcon: ["./img/mini_d2_1.jpg", "./img/mini_d2_2.jpg", "./img/mini_d2_3.jpg", "./img/mini_d2_4.jpg"],
                price: 2399,
                info: " HUAWEI nova 5 Pro 前置3200萬人像超級夜景4800萬AI四攝麒麟980晶片8GB+128GB亮黑色全網通雙4G",
                info1: "",
                used: true,
                evaluate: "57萬+",
                shopName: "華爲京東自營官方旗艦店",
                presell: "",
                tag: ["自營", "放心購", "秒殺"]
            },

            {
                id: 1009,
                icon: ["./img/e1_1.jpg", "./img/e1_2.jpg", "./img/e1_3.jpg", "./img/e1_4.jpg", "./img/e1_5.jpg"],
                miniIcon: ["./img/mini_e1_1.jpg", "./img/mini_e1_2.jpg", "./img/mini_e1_3.jpg", "./img/mini_e1_4.jpg", "./img/mini_e1_5.jpg"],
                price: 2489,
                info: "榮耀V30 5G 雙模 麒麟990 突破性相機矩陣 遊戲手機 8GB+128GB 幻夜星河 移動聯通電信5G 雙卡雙待",
                info1: "",
                used: false,
                evaluate: "21萬+",
                shopName: "榮耀京東自營旗艦店",
                presell: "",
                tag: ["自營", "放心購", "秒殺", "贈"],

            },

            {
                id: 1010,
                icon: ["./img/e2_1.jpg", "./img/e2_2.jpg", "./img/e2_3.jpg", "./img/e2_4.jpg", "./img/e2_5.jpg"],
                miniIcon: ["./img/mini_e2_1.jpg", "./img/mini_e2_2.jpg", "./img/mini_e2_3.jpg", "./img/mini_e2_4.jpg", "./img/mini_e2_5.jpg"],
                price: 1889,
                info: "榮耀Play4 5G雙模 6400萬銳力四攝 4300mAh大電池 VC液冷散熱 8GB+128GB 幻夜黑TNNH-AN00",
                info1: "",
                used: false,
                evaluate: "170萬+",
                shopName: "榮耀京東自營旗艦店",
                presell: "",
                tag: ["自營", "放心購", "秒殺", "贈"],

            },

            {
                id: 1011,
                icon: ["./img/f1_1.jpg", "./img/f1_2.jpg", "./img/f1_3.jpg"],
                miniIcon: ["./img/mini_f1_1.jpg", "./img/mini_f1_2.jpg", "./img/mini_f1_3.jpg"],
                price: 1889,
                info: "榮耀Play4 5G雙模 6400萬銳力四攝 4300mAh大電池 VC液冷散熱",
                info1: "",
                used: true,
                evaluate: "1.3萬+",
                shopName: "榮耀京東自營旗艦店",
                presell:"",
                tag: ["自營", "放心購", "秒殺", "贈"],
            },

            {
                id: 1012,
                icon: ["./img/f2_1.jpg", "./img/f2_2.jpg", "./img/f2_3.jpg"],
                miniIcon: ["./img/mini_f2_1.jpg", "./img/mini_f2_2.jpg", "./img/mini_f2_3.jpg"],
                price: 1889,
                info: "華爲暢享10e 手機 翡冷翠 移動全網通(4G+64G)",
                info1: "【4 + 64移動綠秒殺低至828元!直降170元!】現貨速發,5000mAh超長續航,支援反向充電~!《暢享20pro諮詢減錢》戳~",
                used: true,
                evaluate: "1.3萬+",
                shopName: "榮耀京東自營旗艦店",
                presell:"",
                tag: ["京東物流", "放心購", "秒殺", "免郵", "險"],
            }
        ]

    
        new Main(arr)
    </script>
</body>
</html>

3.購物車思路

(1)Utils 建立元素 設定樣式

CheckBox 多選框
constructor 將多選框關聯的物件存入
createElem 建立多選框
appendTo 將多選框插入在父容器尾部
insertTo 將多選框插入在父容器中某個元素前面
clickHandler 點選多選框切換 並且拋發事件 現在多選框是否選中,將帶入的物件也攜帶拋出
setCheck 設定多選框的內容
mouseHandler 改變多選框經過時的樣式

(2)GoodsItem
constructor 建立整個顯示容器
createElem 建立容器
appendTo  插入在父容器
setData   設定數據,根據數據生成商品顯示內容
iconMouseHandler 滑鼠經過小圖示時事件
changeIcon 修改大圖示
renderHTML 渲染所有HTML標籤內容
setStyle  設定樣式
clickHandler 當點選當前商品時,拋發事件將當前商品的物件數據拋出
(3) StepNumber 數據累加器
constructor  建立數據累加器,並且將當前數據的物件存入
createElem 建立數據累加器內容
appendTo   將當前元素放入父容器中
inputHandler 當輸入內容處理值
clickHandler 當點選+-按鈕時處理內容
setStep 輸入和點選按鈕,以及可以外部設定值,根據設定值改變input的顯示內容,並且判斷是否拋發事件,事件中帶有當前數據的物件和累加的結果(當輸入和點選按鈕時拋發,如果外部設定不拋發)
(4)Shopping
constructor 建立購物車
createElem  建立購物車容器
appendTo 將購物車新增在父容器中
setData  設定數據,根據數據生成表格
createHead 建立表頭, 表頭使用到CheckBox,並且設定偵聽         CheckBox事件內容,設定了根據數據判斷是否需要全選
createListTr 建立表格各行
createTdContent 建立表格每行當中的內容,分別使用到           CheckBox,   stepNumber,CheckBox設定了多選框的data數據,設定多選框根據數據內容選擇是否被選中,偵聽多選事件,stepNumber放入時設定當前物件儲存,偵聽累加器是否改變,設定累加器初始值
stepChangeHandler 偵聽累加器收到修改事件,並且重新拋發新的事件通知外層類執行
checkHandler 偵聽多選框選中事件,並且重新拋發事件通知外層類執行
deleteHandler 偵聽刪除事件,並且重新拋發事件通知外層類執行
setStyle  設定樣式
(5)Main

constructor 建立購物車頁面內容 根據給入的數據建立了商品列表,建立商品列表的偵聽事件,建立了購物車物件,建立總價容器。購物車偵聽多選框是否被選中,偵聽累加器是否被修改,偵聽是否刪除數據
clickHandler 當點選商品列表中商品時,判斷再購物車shoppingList陣列中是否有當前點選的商品,如果有,就給數據做累加,如果沒有就新增新數據到shoppingList,並且根據當前shoppingList灌入到購物車中,重新建立購物車表格
checkChangeHandler 如果多選框被選中,判斷有沒有數據,如果沒有數據就是表頭的多選框,有數據就是每個商品數據,根據這個內容修改shoppingList數據中對應的內容,並且重新灌入到購物車中,重新生成購物車表格
stepChangeHandler 累加器被修改時,修改對應的shoppingList中對應的數據,並且重新灌入到購物車中,重新生成購物車表格
deleteChangeHandler 從shoppingList中刪除需要刪除的數據商品,並且重新灌入到購物車中,重新生成購物車表格
totalPrice 修改商品數量,刪除商品,選中商品,都執行這個方法,並且根據數據是否選中,將商品總價累積並且顯示