<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        守候

        守候 查看完整檔案

        廣州編輯廣東機電職業技術學院  |  軟件技術(網站設計) 編輯廣州找原料  |  web前端 編輯填寫個人主網站
        編輯

        一個web前端新手,正在web前端的路上打拼的小伙子。
        在社區希望能和大家相互學習,相互鼓勵,幫助。在這里我也把在工作中遇到的點滴,學習到的知識分享,希望能幫到大家,我也會不斷的拼搏奮斗。
        以后有所成就的我,一定會感謝現在努力奮斗的自己。

        微信公眾號:守候書閣 ,歡迎大家關注

        在廣州工作也幾年了,什么時候能有個女朋友啊

        個人動態

        守候 發布了文章 · 2019-05-20

        [探索]怎么樣的參數能讓 JS - API 更靈活

        外在決定是否需要了解內在,內在決定是否會一票否決外在。內外結合,好上加好。

        1.前言

        開發 API 的時候,把參數的名字和位置確定下來,函數定義就可以說是完成了。因為 API 使用者來說,只需要知道如何傳遞參數,以及函數將返回什么樣的值就夠了,無需了解內部。所以參數多多少少影響了 API 的一個靈活程度和使用復雜程度。在設計 API 的時候,應該怎么設計參數,下面就簡單的寫下,如果大家有不同的想法,歡迎在評論區留言。

        下面使用的例子,除了原生了 JQuery 的 API。其他的例子都是取自自己封裝的一個常用函數庫 ecDo 。歡迎提建議和 star。

        2.什么時候該設置參數

        其實什么時候設置參數并沒什么什么說法,規范。只要覺得用參數會使得 API 的使用會更加的簡單和靈活就用了。

        設置參數,可能一開始不會有很好的靈感,但是在使用過程中,用得難受了,自然會像辦法優化。

        比如 有 ecDo.count(array|string,item) 這個 API 就是統計一個數組的每個元素的出現次數或者字符串每一個字符的個數。

        這很容易實現,代碼也貼下

        count(arr) {
            let obj = {}, k, arr1 = []
            //記錄每一元素出現的次數
            for (let i = 0, len = arr.length; i < len; i++) {
                k = arr[i];
                if (obj[k]) {
                    obj[k]++;
                } else {
                    obj[k] = 1;
                }
            }
            //保存結果{el-'元素',count-出現次數}
            for (let o in obj) {
                arr1.push({el: o, count: obj[o]});
            }
            return arr1;
        },
        
        
        
        let strTest1='abbccc'
        console.log(ecDo.count(strTest1));
        //result:[{el: "a", count: 1},{el: "b", count: 2},el: "c", count: 3}]

        但是有些時候,開發者并不需要知道所有元素的個數,比如就需要知道 'a' 的出現次數,這個情況直接返回 'a' 的個數就行了,不需要像上面例子一樣,返回一個數組。這樣用起來會舒服一些,改起來也很簡單,只要增加一個參數,一個判斷即可。

        count(arr,item) {
            //重復代碼略
            return item?obj[item]:arr1;
        },
        
        
        
        let strTest1='abbccc'
        console.log(ecDo.count(strTest1),'a');
        //result:1

        還有一個常用的 API 是ecDo.clearKeys(obj)--清除對象中值為‘空’(null, undefined和'')的屬性。

        這個也很容易實現,

        clearKeys(obj) {
            let _newPar = {};
            for (let key in obj) {
                if (obj[key]===0||obj[key]===false||obj[key]) {
                    _newPar[key] = obj[key];
                }
            }
            return _newPar;
        },
        
        
        ecDo.clearKeys({1:0,2:2,3:''})
        //result:{1: 0, 2: 2}

        想必大家也發現這樣寫法太不靈活了,如果下次要把 0false 的屬性也清空呢?如果下次要把值為 '--' 的屬性也清空呢?這樣就做不到了,所以還要改一下,增加一個參數 clearValues - 待清空的值。

        要使用的一個函數

        clearKeys(obj, clearValues = [null, undefined, '']) {
            clearValues.forEach((item, index) => {
                clearValues[index] = Number.isNaN(item) ? 'NaN' : item
            });
            let _newPar = {};
            for (let key in obj) {
                //checkValue 看下面定義
                if (!checkValue(obj[key], clearValues)) {
                    _newPar[key] = obj[key];
                }
            }
            return _newPar;
        },
        
        
        ecDo.clearKeys({a:'',b:0,c:11})
        //result:{b: 0,c: 11}
        ecDo.clearKeys({a:'',b:0,c:'--'},['--',''])
        //result:{b: 0}
        ecDo.clearKeys({a:'',b:0,c:11,d:false},[0,''])
        //result:{c: 11,d: false}
        ecDo.clearKeys({a:NaN,b:2,c:undefined},[NaN,undefined])
        //result:{b: 2}

        checkValue 函數

        function checkValue(val, vals) {
            let _val = val;
            if (Number.isNaN(val)) {
                _val = 'NaN'
            }
            return vals.indexOf(_val) !== -1;
        }

        這樣以來,想清除任何值的屬性,這里都可以實現。

        3.參數數量和前置

        這兩個注意點可以說是平常接觸最頻繁的,也是最無可辯解的。

        首先參數的數量,在不影響 API 使用的情況下肯定是能少就少,越少越好。因為參數的越少,API 的記憶成本越低,調用也更加便利。

        參數能少就少,越少越好,是有前提的--不影響 API 的使用。如果多個參數, API 使用能更方便,靈活,簡單。多個參數就多個參。

        然后參數的前置性,就是參數相關性越高,越不能省略的,就越要放在前面。雖然可以把可省略參數放后面,但是這樣問題可能會很多。

        4.使用對象作為參數

        什么時候該使用對象作為函數的參數,暫時發現是兩種情況。

        1.參數過多的時候

        2.參數不固定的時候

        比如 ajax 的參數,至少至少也有 5 個。url-請求鏈接,method-請求方式,data-請求參數,success-成功回調,fail-失敗回調。如果不使用對象作為參數,完整的寫法,是下面這樣

        ajax(url,method,data,success,fail)

        但這5個參數里面,除了 url ,其他參數都可以省略或者默認。如果只需要傳 url 和 success,需要像下面這樣寫

        ajax(url,'','',success)

        因為參數的順序不能亂,所以就要這樣寫。這樣多難受就不說了,如果參數過多,參數看著會很長,容易搞錯參數順序,就會出現問題了。

        如果使用對象傳參,就舒服很多了。

        ajax({url:url,success:function(){}})

        這樣寫的會多一點,但是好處也有。首先用戶還是需要記住參數名,但不用關心參數順序,不用擔心參數過多。然后就是開發者想增加多少參數都比較方便,也不用關心參數后置的問題。

        是否使用對象作為參數,判斷的指標應該只有一個:是否方便使用,靈活。

        5.參數默認值

        什么時候應該設計默認值?也分幾種情況討論

        首先是,一個參數值出現頻率比其他情況大的時候。比如有一個 ecDo.encrypt(str,regIndex,repText) 的 API,作用很簡單,就是對字符串的特定位置進行加密。比如經常會遇到隱藏用戶的手機號碼的需求。

        電話號碼隨便編造的
        
        ecDo.encrypt('18819233362','3,7')
        //result:188*****362
        
        ecDo.encrypt('18819233362','3,7','+')
        //result:188+++++362
        
        ecDo.encrypt('18819233362','4')
        //result:*****233362
        
        ecDo.encrypt('18819233362','-4')
        //result:188192*****

        在這個 API 里面 ,第三個參數 repText 可能大多數情況都是使用 。如果不對 repText 設置默認值,如果每一次都傳一個 ,不但寫的多,看得也難受。

        還有一種情況,從特定位置執行到結尾結束的時候。比如原生的 substr(start,length) ,第一個參數是開始字符的下標,第二個參數是截取的長度。如果省略,就從開始位置截取到結尾。這樣就算是一種方便。

        5.參數多態

        這里說的參數多態跟繼承方面的多態有點區別

        參數多態這個很常見,目的很簡單,就是通過不同的傳參方式讓一個函數進行不同的操作??吹竭@里,可能大家一下子就想到 splice。因為一個 splice 可以實現數組的增加,刪除,替換

        //刪除
        let arr=[1,2,3,4,5,6]
        arr.splice(0,1)
        //result:arr:[2, 3, 4, 5, 6]
        
        //替換
        arr=[1,2,3,4,5,6]
        arr.splice(2,1,0)
        //result:arr:[1, 2, 0, 4, 5, 6]
        
        //增加
        arr=[1,2,3,4,5,6]
        arr.splice(2,0,0)
        //result:arr:[1, 2, 0, 3, 4, 5, 6]

        但是 splice 應該并不算是參數多態,只能算是一些技巧的一個寫法。

        表現出參數多態的,比如 JQuery 的 attr 。既可以獲取屬性的值,也可以設置屬性的值。

        //獲取 dom 元素 id 的值
        $(dom).attr('id')
        //設置 dom 元素 id 的值
        $(dom).attr('id','domId')

        JQuery 把多態表現得更好的應該是 $() 。JQuery 火的原因,跟這個 $() 有很大的關系,只要是合法的選擇器,頁面存在這個元素,就能找到。讓選擇元素變得非常簡單。

        關于 $() 的強大特性,可參考 jQuery 選擇器

        在自己封裝 API 的時候,也會遇到操作 cookie 的一系列操作(設置,獲取,刪除)。之前是這樣

        ecDo.setCookie(cookieName,value,day)//設置(cookie名稱,cookie值,有效的天數)
        ecDo.getCookie(cookieName)//獲取
        ecDo.removeCookie(cookieName)//刪除

        現在是這樣

        ecDo.cookie(cookieName,value,day)//設置(cookie名稱,cookie值,有效的天數)
        ecDo.cookie(cookieName)//獲取
        ecDo.cookie(cookieName,value,-1)//刪除

        這樣使用起來,就比之前簡單一點,相當于只是記住一個 API 就可以了。

        參數的多態,就是讓 API 的指責會根據參數的情況進行改變。相當于把相似職責的 API 給合并成一個。不需要給用戶提供出太多 API,方便用戶的使用。即使這樣可能違法了單一指責原則,但是呼應了最少知識原則。能讓 API 的使用盡可能簡單化了。

        5-1.什么時候不該設置參數多態

        參數多態就是把相似職責的 API 給合并成一個。但是有時候并不適合使用。更適合把合并的 API 拆分成幾個。

        比如之前封裝常用 API 的時候。有一個去除字符串空格的 API : ecDo.trim(str,type) (str-待處理的字符串,type-去除空格的類型----1-左右空格,2-所有空格,3-左空格,4-右空格) 。

        使用形式

        ecDo 是我封裝的一個常用函數庫。
        let str=' 守 候 ';
        console.log(ecDo.trim(str,1));//'守 候'
        console.log(ecDo.trim(str,2));//'守候'
        console.log(ecDo.trim(str,3));//'守候 '
        console.log(ecDo.trim(str,4));//' 守候'

        這樣使用的話,想必大家都看到問題了。1 2 3 4 是什么鬼?就算知道是什么鬼,也要把這幾個鬼記住。記憶成本大,調用也不方便。后來就拆分成四個函數了。

        let str=' 守 候 ';
        console.log(ecDo.trim(str));//'守 候'
        console.log(ecDo.trimAll(str));//'守候'
        console.log(ecDo.trimLeft(str));//'守候 '
        console.log(ecDo.trimRright(str));//' 守候'

        這樣雖然多了幾個 API 但是使用起來簡單了。記憶成本也比之前少。是否設置參數多態,還是要看調用的情況而言。

        6.小結

        好了,關于 API 參數方面的東西就暫時寫到這里了,這部分的內容不多,所以篇幅不是很長,也容易理解。但是參數這個話題也相對開放,如果大家有什么好的想法。歡迎在評論區留言。

        -------------------------華麗的分割線--------------------

        想了解更多,和我交流,內推職位,請添加我微信?;蛘哧P注我的微信公眾號:守候書閣

        圖片描述圖片描述

        查看原文

        贊 12 收藏 4 評論 0

        守候 發布了文章 · 2019-05-06

        [探索]怎樣讓 JS - API 具有更好的實用性

        程序員的精神,不應不止于實現,更要注重優化。不應止于表面,更要研究內部機制,方能青出于藍而勝于藍。

        1.前言

        在上家公司開發后臺管理系統的時候,頻繁要處理各種數據顯示的問題,一開始是實現就好。后來寫多了,自己看得也難受了。就想著怎么優化代碼和復用了。下面就通過一個簡單的例子,怎么讓 API 更加的實用,更好的復用。

        1.代碼的實用性,只能盡量,盡量再盡量。不會出現完美的API,或者是一次編寫,永不修改的 API 。

        2.關于實用性,API 命名和擴展性也很重要。但之前寫過文章,在這里就不重復了。[[前端開發]--分享個人習慣的命名方式](https://juejin.im/post/5b6ad6...,重構 - 設計API的擴展機制

        2.舉個例子

        比如有一個需求,有這樣的數據

        {
            cashAmount: 236700,//回款金額(分)
            cashDate: "2018-05-26 10:25:28",//回款時間
            cashId: "SM2018022800020692",//回款ID
            cashStatus: 0,//回款狀態
            createTime: "2018-05-23 10:26:25",//創建時間
            custoName: "廣州測試有限公司",//回款公司名稱
            id: "SM2018022800020692",//回款ID
            merchandisers: "守候",//回款公司聯系人
            ordId: "SO2018022800020692",//訂單ID
            payChannel: null,//支付方式
            remark: "",//備注
            userMobile: "18819222363",//回款公司聯系人電話
        }
        

        需要對數據進行以下處理,再渲染到頁面

        1.cashAmount 轉換成元,并保留兩位小數

        2.cashStatus 進行解析(0-未回款 1-已回款)

        3.payChannel 進行解析 ('zfb'-支付寶,'wx'-微信支付,'cash'-現金支付,'bankTransfer'-銀行轉賬)

        4.所有值為 '' , null , undefined 的字段,全部設置為:'--'

        面對這樣的需要,很簡單,順手就來

        let obj = {
            cashAmount: 236700,//回款金額(分)
            cashDate: "2018-05-26 10:25:28",//回款時間
            cashId: "SM2018022800020692",//回款ID
            cashStatus: 0,//回款狀態
            createTime: "2018-05-23 10:26:25",//創建時間
            custoName: "廣州測試有限公司",//回款公司名稱
            id: "SM2018022800020692",//回款ID
            merchandisers: "守候",//回款公司聯系人
            ordId: "SO2018022800020692",//訂單ID
            payChannel: null,//支付方式
            remark: "",//備注
            userMobile: "13226452474",//回款公司聯系人電話
        }
        function setValue(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            //設置金額
            _obj.cashAmount = (_obj.cashAmount / 100).toFixed(2);
            //解析回款狀態
            _obj.cashStatus = _obj.cashStatus === 0 ? '未回款' : '已回款';
            //解析支付方式
            let payChannelLabel = {
                'zfb': '支付寶',
                'wx': '微信支付',
                'cash': '現金支付',
                'bankTransfer': '銀行轉賬'
            }
            _obj.payChannel=payChannelLabel[_obj.payChannel];
            //設置默認值
            for (let key in _obj){
                if(_obj[key]===''||_obj[key]===null||_obj[key]===undefined){
                    _obj[key]='--'
                }
            }
            return _obj;
        }
        obj=setValue(obj);
        console.log(obj)

        結果也正確,如下圖

        圖片描述

        但是如果以后需求變了,比如 userMobile 要改成 xxx xxx xxxx 這種展示方式呢?

        也很簡單,修改下

        function setValue(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            //設置金額
            //解析回款狀態
            //解析支付方式
            /*和上面代碼一樣,不重復粘貼*/
            //設置電話號碼格式
            let _formatType="xxx xxx xxxx",i = 0;
            _obj.userMobile= _formatType.replace(/x/g, function(){
                return _obj.userMobile[i++]
            });
            //設置默認值
            /*和上面代碼一樣,不重復粘貼*/
        }

        代碼寫好了,想必大家也開始難受了。因為每改一次需求,就要改一次 setValue 。改的多了,出現問題的概率就大了。而且,這樣沒復用性。試想,如果別的頁面有一個需求,同樣的數據。但是 cashDate 字段只需要精確到時分秒。這樣的需求,大同小異。但上面的代碼不適用,需要拷貝一個 setValue 方法(就叫 setValue2 吧),然后添加 cashDate 只顯示 時分秒的邏輯。代碼很好寫

        function setValue2(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            //設置金額
            //解析回款狀態
            //解析支付方式
            //設置電話號碼格式
            /*和上面代碼一樣,不重復粘貼*/
            //設置 cashDate 只顯示時分秒
            _obj.cashDate= _obj.cashDate.split(' ')[0];
            //設置默認值
            /*和上面代碼一樣,不重復粘貼*/
        }

        圖片描述

        3.單一職責原則

        想必大家更難受了,因為沒發復用,導致出現了幾乎完全一樣的函數。這個問題解決方式很多,先說下第一個,也是一個 API 設計原則--單一職責原則。

        顧名思義,單一職責原則就是讓每一個函數只做一件事。下面把代碼改造下

        /**
         * @description 設置默認值
         * @param obj 待處理對象
         * @return obj 已處理對象
         */
        function setDefault(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            for (let key in _obj){
                if(_obj[key]===''||_obj[key]===null||_obj[key]===undefined){
                    _obj[key]='--'
                }
            }
            return _obj;
        }
        /**
         * @description 格式化電話號碼
         * @param obj 待處理對象
         * @return obj 已處理對象
         */
        function setFormatMobile(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            let _formatType="xxx xxx xxxx",i = 0;
            _obj.userMobile= _formatType.replace(/x/g, function(){
                return _obj.userMobile[i++]
            });
            return _obj;
        }
        /**
         * @description 解析支付方式
         * @param obj 待處理對象
         * @return obj 已處理對象
         */
        function setPayChannelLabel(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            let payChannelLabel = {
                'zfb': '支付寶',
                'wx': '微信支付',
                'cash': '現金支付',
                'bankTransfer': '銀行轉賬'
            }
            _obj.payChannel = payChannelLabel[_obj.payChannel];
            return _obj;
        }
        /**
         * @description 設置回款金額
         * @param obj 待處理對象
         * @return obj 已處理對象
         */
        function setCashAmount(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            _obj.cashAmount = (_obj.cashAmount / 100).toFixed(2);
            return _obj;
        }
        /**
         * @description 解析回款狀態
         * @param obj 待處理對象
         * @return obj 已處理對象
         */
        function setCashStatus(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            _obj.cashStatus = _obj.cashStatus === 0 ? '未回款' : '已回款';
            return _obj;
        }
        
        obj=setFormatMobile(obj);
        obj=setCashStatus(obj);
        obj=setCashAmount(obj);
        obj=setPayChannelLabel(obj);
        obj=setDefault(obj);

        結果一樣,如果需要加上 cashDate 只顯示 時分秒。加上邏輯就行了

        /**
         * @description 設置匯款時間
         * @param obj 待處理對象
         * @return obj 已處理對象
         */
        function setCashDate(obj) {
            let _obj=JSON.parse(JSON.stringify(obj));
            _obj.cashDate = _obj.cashDate.split(' ')[0];
            return _obj;
        }
        
        obj=setFormatMobile(obj);
        obj=setCashStatus(obj);
        obj=setCashAmount(obj);
        obj=setCashDate(obj);
        obj=setPayChannelLabel(obj);
        obj=setDefault(obj);
        console.log(obj)

        圖片描述

        讓 API 保持單一原則的好處是,復用性比復雜的 API 更好,而且編寫的難度更低。

        4.最少知識原則

        上面的寫法雖然實現了復用,看著比之前好了一點,但是看著也是難受,畢竟賦值了幾次,而且還有那么多的全局函數。

        首先,全局函數這個容易解決,用一個對象包裹起來,全局函數少了,也方便管理。

        重復的代碼和注釋,這里忽略,不重復粘貼
        let handle={
           setDefault(obj) {
                //省略的代碼
            },
            setFormatMobile(obj) {
                //省略的代碼
            },
            setPayChannelLabel(obj) {
                //省略的代碼
            },
            setCashAmount(obj) {
                //省略的代碼
            },
            setCashStatus(obj) {
                //省略的代碼
            }
        }
        
        
        obj=handle.setFormatMobile(obj);
        obj=handle.setCashStatus(obj);
        obj=handle.setCashAmount(obj);
        obj=handle.setPayChannelLabel(obj);
        obj=handle.setDefault(obj);
        console.log(obj)

        第二個讓人難受的地方就是一個步驟,經過了幾次的賦值,這個難免有點難受,寫起來也麻煩,記憶成本高。解決起來也很簡單,就是另寫一個函數,把那些操作步驟封裝在一起就行了。封裝的目的就是為了讓使用的人,只需要記住一個函數的使用方式就可以了,不需要記住多個函數的使用方式。

        let handle={
           /*省略代碼*/
           setCash(obj){
                let _obj=JSON.parse(JSON.stringify(obj));
                _obj=this.setFormatMobile(_obj);
                _obj=this.setCashStatus(_obj);
                _obj=this.setCashAmount(_obj);
                _obj=this.setPayChannelLabel(_obj);
                _obj=this.setDefault(_obj);
                return _obj;
            }
        }
        obj=handle.setCash(obj);
        console.log(obj)

        5.配置數據和業務邏輯分離

        上面的代碼,看著算是比較舒服了,但是問題還是有,就是 setCash 函數寫得太死了。固定了五個方法 :setFormatMobile,setCashStatus,setCashAmount,setPayChannelLabel,setDefault 。如果以后不需要處理電話號碼,又要改 setCash ,把 _obj=this.setFormatMobile(_obj); 這行代碼去掉。雖然改動也很小,但是問題就出來了。如果其中一個地方需要執行 setFormatMobile ,就不能刪除。如果另一個地方, 不需要執行 setFormatMobile ,就要刪除。這樣子就顧此失彼了。

        解決的方案想必大家也知道了,就是需要執行什么函數,就在函數上動態傳入。

        let handle={
           /*省略代碼*/
           setCash(obj,fns='setFormatMobile,setCashStatus,setCashAmount,setPayChannelLabel,setDefault'){
                let _obj=JSON.parse(JSON.stringify(obj));
                let _fns=fns.split(',');
                _fns.forEach(item => {
                    _obj=this[item](_obj);
                });
                return _obj;
            }
        }
        obj=handle.setCash(obj);
        console.log(obj)
        
        //比如另一個地方不需要執行 setFormatMobile
        obj = {
            cashAmount: 236700,//回款金額(分)
            cashDate: "2018-05-26 10:25:28",//回款時間
            cashId: "SM2018022800020692",//回款ID
            cashStatus: 0,//回款狀態
            createTime: "2018-05-23 10:26:25",//創建時間
            custoName: "廣州測試有限公司",//回款公司名稱
            id: "SM2018022800020692",//回款ID
            merchandisers: "守候",//回款公司聯系人
            ordId: "SO2018022800020692",//訂單ID
            payChannel: null,//支付方式
            remark: "",//備注
            userMobile: "13226452474",//回款公司聯系人電話
        }
        obj=handle.setCash(obj,'setCashStatus,setCashAmount,setPayChannelLabel,setDefault');
        console.log('比如另一個地方不需要執行 setFormatMobile',obj)

        圖片描述

        6.批量處理

        看到這里,好像差不多了。但是寫下去,大家才會知道,一般的后臺管理系統的用戶列表,數據一般不會只有一條。一般而言是一個數組對象。如下

        let arr=[
            {
                cashAmount: 236700,//回款金額(分)
                cashDate: "2018-05-26 10:25:28",//回款時間
                cashId: "SM2018022800020692",//回款ID
                cashStatus: 0,//回款狀態
                createTime: "2018-05-23 10:26:25",//創建時間
                custoName: "廣州測試有限公司",//回款公司名稱
                id: "SM2018022800020692",//回款ID
                merchandisers: "守候",//回款公司聯系人
                ordId: "SO2018022800020692",//訂單ID
                payChannel: null,//支付方式
                remark: "",//備注
                userMobile: "13226452474",//回款公司聯系人電話
            },
            {/*省略的代碼*/},
            {/*省略的代碼*/},
            {/*省略的代碼*/},
            //省略的代碼
        ]

        寫起來的時候呢,要這樣寫

        arr.forEach((item,index)=>{
            arr[index]=handle.setCash(item);
        })
        console.log(arr)

        雖然代碼不多,但是有更好的方案,就用更好的方案。比如使用批量處理的方式。就多寫一個函數就行了。

        let handle={
           /*省略代碼*/
           batch(arr,fns,...orther){
                let _arr=JSON.parse(JSON.stringify(arr));
                let _fns=fns.split(',');
                _arr.forEach((item,index)=>{
                    _fns.forEach(fn => {
                        _arr[index]=this[fn](_arr[index],...orther);
                    });
                })
                return _arr
            }
        }

        調用的時候就比之前簡單了一點,結果也正確

        arr=handle.batch(arr,'setCash')
        console.log(arr)

        圖片描述

        要傳其他參數也可以

        arr=handle.batch(arr,'setCash','setCashStatus,setCashAmount,setPayChannelLabel,setDefault')
        console.log(arr)

        圖片描述

        如果要傳入多個操作函數

        arr=handle.batch(arr,'setCashStatus,setCashAmount')
        console.log(arr)

        7.小結

        關于開發上,API 的實用性,暫時就先提這幾個方面,如果以后發現有其他例子,還能從其他方面提高 API 的實用性,就再發文章分享。關于這篇文章,也是我目前嘗試的一種方式,如果大家有更好的一個實現方式,歡迎在評論區留言。

        -------------------------華麗的分割線--------------------

        想了解更多,和我交流,內推職位,請添加我微信?;蛘哧P注我的微信公眾號:守候書閣

        圖片描述圖片描述

        查看原文

        贊 24 收藏 14 評論 2

        守候 評論了文章 · 2019-02-28

        vue快速入門的三個小實例

        1.前言

        用vue做項目也有一段時間了,之前也是寫過關于vue和webpack構建項目的相關文章,大家有興趣可以去看下webpack+vue項目實戰(一,搭建運行環境和相關配置)(這個系列一共有5篇文章,這是第一篇,其它幾篇文章鏈接就不貼了)。但是關于vue入門基礎的文章,我還沒有寫過,那么今天就寫vue入門的三個小實例,這三個小實例是我剛接觸vue的時候的練手作品,難度從很簡單到簡單,都是入門級的。希望能幫到大家更好的學習和了解vue,也是讓自己能夠復習一下vue。如果發現文章寫得有什么不好,寫錯了,或者有什么建議!歡迎大家指點迷津!

        1.本篇文章使用的vue版本是2.4.2,大家要注意版本問題
        2.現在我也是假設您有基礎的html,css,javascript的知識,也已經看過了官網的基本介紹,對vue有了一個大概的認識了,了解了常用的vue指令(v-model,v-show,v-if,v-for,v-on,v-bind等)!如果剛接觸前端的話,你看著文章可能會蒙圈,建議先學習基礎,掌握了基礎知識再來看!
        3.下面的實例,建議大家邊看文章邊動手做!這樣思路會非常清晰,不易混亂!也不會覺得文章長!如果只看文章,你可能未必會看完,因為文章我講得比較細,比較長!
        4.這幾個實例,摘自我自己的平常練習的項目,代碼已經提到github上面了(vue-demos)。歡迎大家star。!

        2.什么是vue

        vue是現在很火的一個前端MVVM框架,它以數據驅動和組件化的思想構建,與angular和react并稱前端三大框架。相比angular和react,vue更加輕巧、高性能、也很容易上手。大家也可以移步,看一下vue的介紹和核心功能官網介紹。簡單粗暴的理解就是:用vue開發的時候,就是操作數據,然后vue就會處理,以數據驅動去改變DOM(不知道有沒有理解錯,理解錯了指點下)。
        下面就是一個最簡單的說明例子

        代碼如下

        html

        <div id="app">
          <p>{{ message }}</p>
          <input v-model="message">
        </div>

        js

        new Vue({
          el: '#app',
          data: {
            message: 'Hello Vue!'
          }
        })
        

        圖片描述

        相信也不難理解,就是input綁定了message這個值,然后在input修改的時候,message就改了,由于雙向綁定,同時頁面的html({{ message }})進行了修改!
        好,下面進入例子學習!

        3.選項卡

        運行效果

        clipboard.png

        原理分析和實現

        這個很簡單,無非就是一個點擊切換顯示而已。但是大家也要實現。如果這個看明白了,再看下面兩個!這個實例應該只是一個熱身和熟悉的作用!

        這個的步驟只有一步,原理也沒什么。我直接在代碼打注釋,看了注釋,大家就明白了!

        完整代碼

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <style>
            body{
                font-family:"Microsoft YaHei";
            }
            #tab{
                width: 600px;
                margin: 0 auto;
            }
            .tab-tit{
                font-size: 0;
                width: 600px;
            }
            .tab-tit a{
                display: inline-block;
                height: 40px;
                line-height: 40px;
                font-size: 16px;
                width: 25%;
                text-align: center;
                background: #ccc;
                color: #333;
                text-decoration: none;
            }
            .tab-tit .cur{
                background: #09f;
                color: #fff;
            }
            .tab-con div{
                border: 1px solid #ccc;
                height: 400px;
                padding-top: 20px;
            }
        </style>
        <body>
        <div id="tab">
            <div class="tab-tit">
                <!--點擊設置curId的值  如果curId等于0,第一個a添加cur類名,如果curId等于1,第二個a添加cur類名,以此類推。添加了cur類名,a就會改變樣式 @click,:class ,v-show這三個是vue常用的指令或添加事件的方式-->
                <a href="javascript:;" @click="curId=0" :class="{'cur':curId===0}">html</a>
                <a href="javascript:;" @click="curId=1" :class="{'cur':curId===1}">css</a>
                <a href="javascript:;" @click="curId=2" :class="{'cur':curId===2}">javascript</a>
                <a href="javascript:;" @click="curId=3" :class="{'cur':curId===3}">vue</a>
            </div>
            <div class="tab-con">
                <!--根據curId的值顯示div,如果curId等于0,第一個div顯示,其它三個div不顯示。如果curId等于1,第二個div顯示,其它三個div不顯示。以此類推-->
                <div v-show="curId===0">
                    html<br/>
                </div>
                <div v-show="curId===1">
                    css
                </div>
                <div v-show="curId===2">
                    javascript
                </div>
                <div v-show="curId===3">
                    vue
                </div>
            </div>
        </div>
        </body>
        <script data-original="vue.min.js"></script>
        <script>
            new Vue({
                el: '#tab',
                data: {
                    curId: 0
                },
                computed: {},
                methods: {},
                mounted: function () {
                }
            })
        </script>
        </html>
        

        4.統計總價

        運行效果

        clipboard.png

        原理分析和實現

        首先,還是先把布局寫好,和引入vue,準備vue實例,這個不多說,代碼如下

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
            <style>
                .fl{
                    float: left;
                }
                .fr{
                    float: right;
                }
               blockquote, body, dd, div, dl, dt, fieldset, form, h1, h2, h3, h4, h5, h6, img, input, li, ol, p, table, td, textarea, th, ul {
                    margin: 0;
                    padding: 0;
                }
               .clearfix{
                  zoom: 1;
               }
                .clearfix:after {
                    clear: both;
                }
                .clearfix:after {
                    content: '.';
                    display: block;
                    overflow: hidden;
                    visibility: hidden;
                    font-size: 0;
                    line-height: 0;
                    width: 0;
                    height: 0;
                }
                a{
                    text-decoration: none;
                    color: #333;
                }
                img{vertical-align: middle;}
                .page-shopping-cart {
                    width: 1200px;
                    margin: 50px auto;
                    font-size: 14px;
                    border: 1px solid #e3e3e3;
                    border-top: 2px solid #317ee7; }
                .page-shopping-cart .cart-title {
                    color: #317ee7;
                    font-size: 16px;
                    text-align: left;
                    padding-left: 20px;
                    line-height: 68px; }
                .page-shopping-cart .red-text {
                    color: #e94826; }
                .page-shopping-cart .check-span {
                    display: block;
                    width: 24px;
                    height: 20px;
                    background: url("shopping_cart.png") no-repeat 0 0; }
                .page-shopping-cart .check-span.check-true {
                    background: url("shopping_cart.png") no-repeat 0 -22px; }
                .page-shopping-cart .td-check {
                    width: 70px; }
                .page-shopping-cart .td-product {
                    width: 460px; }
                .page-shopping-cart .td-num, .page-shopping-cart .td-price, .page-shopping-cart .td-total {
                    width: 160px; }
                .page-shopping-cart .td-do {
                    width: 150px; }
                .page-shopping-cart .cart-product-title {
                    text-align: center;
                    height: 38px;
                    line-height: 38px;
                    padding: 0 20px;
                    background: #f7f7f7;
                    border-top: 1px solid #e3e3e3;
                    border-bottom: 1px solid #e3e3e3; }
                .page-shopping-cart .cart-product-title .td-product {
                    text-align: center;
                    font-size: 14px; }
                .page-shopping-cart .cart-product-title .td-check {
                    text-align: left; }
                .page-shopping-cart .cart-product-title .td-check .check-span {
                    margin: 9px 6px 0 0; }
                .page-shopping-cart .cart-product {
                    padding: 0 20px;
                    text-align: center; }
                .page-shopping-cart .cart-product table {
                    width: 100%;
                    text-align: center;
                    font-size: 14px; }
                .page-shopping-cart .cart-product table td {
                    padding: 20px 0; }
                .page-shopping-cart .cart-product table tr {
                    border-bottom: 1px dashed #e3e3e3; }
                .page-shopping-cart .cart-product table tr:last-child {
                    border-bottom: none; }
                .page-shopping-cart .cart-product table .product-num {
                    border: 1px solid #e3e3e3;
                    display: inline-block;
                    text-align: center; }
                .page-shopping-cart .cart-product table .product-num .num-do {
                    width: 24px;
                    height: 28px;
                    display: block;
                    background: #f7f7f7; }
                .page-shopping-cart .cart-product table .product-num .num-reduce span {
                    background: url("shopping_cart.png") no-repeat -40px -22px;
                    display: block;
                    width: 6px;
                    height: 2px;
                    margin: 13px auto 0 auto; }
                .page-shopping-cart .cart-product table .product-num .num-add span {
                    background: url("shopping_cart.png") no-repeat -60px -22px;
                    display: block;
                    width: 8px;
                    height: 8px;
                    margin: 10px auto 0 auto; }
                .page-shopping-cart .cart-product table .product-num .num-input {
                    width: 42px;
                    height: 28px;
                    line-height: 28px;
                    border: none;
                    text-align: center; }
                .page-shopping-cart .cart-product table .td-product {
                    text-align: left;
                    font-size: 12px;
                    line-height: 20px; }
                .page-shopping-cart .cart-product table .td-product img {
                    border: 1px solid #e3e3e3;
                    margin-right: 10px; }
                .page-shopping-cart .cart-product table .td-product .product-info {
                    display: inline-block;
                    vertical-align: middle; }
                .page-shopping-cart .cart-product table .td-do {
                    font-size: 12px; }
                .page-shopping-cart .cart-product-info {
                    height: 50px;
                    line-height: 50px;
                    background: #f7f7f7;
                    padding-left: 20px; }
                .page-shopping-cart .cart-product-info .delect-product {
                    color: #666; }
                .page-shopping-cart .cart-product-info .delect-product span {
                    display: inline-block;
                    vertical-align: top;
                    margin: 18px 8px 0 0;
                    width: 13px;
                    height: 15px;
                    background: url("shopping_cart.png") no-repeat -60px 0; }
                .page-shopping-cart .cart-product-info .product-total {
                    font-size: 14px;
                    color: #e94826; }
                .page-shopping-cart .cart-product-info .product-total span {
                    font-size: 20px; }
                .page-shopping-cart .cart-product-info .check-num {
                    color: #333; }
                .page-shopping-cart .cart-product-info .check-num span {
                    color: #e94826; }
                .page-shopping-cart .cart-product-info .keep-shopping {
                    color: #666;
                    margin-left: 40px; }
                .page-shopping-cart .cart-product-info .keep-shopping span {
                    display: inline-block;
                    vertical-align: top;
                    margin: 18px 8px 0 0;
                    width: 15px;
                    height: 15px;
                    background: url("shopping_cart.png") no-repeat -40px 0; }
                .page-shopping-cart .cart-product-info .btn-buy {
                    height: 50px;
                    color: #fff;
                    font-size: 20px;
                    display: block;
                    width: 110px;
                    background: #ff7700;
                    text-align: center;
                    margin-left: 30px; }
                .page-shopping-cart .cart-worder {
                    padding: 20px; }
                .page-shopping-cart .cart-worder .choose-worder {
                    color: #fff;
                    display: block;
                    background: #39e;
                    width: 140px;
                    height: 40px;
                    line-height: 40px;
                    border-radius: 4px;
                    text-align: center;
                    margin-right: 20px; }
                .page-shopping-cart .cart-worder .choose-worder span {
                    display: inline-block;
                    vertical-align: top;
                    margin: 9px 10px 0 0;
                    width: 22px;
                    height: 22px;
                    background: url("shopping_cart.png") no-repeat -92px 0; }
                .page-shopping-cart .cart-worder .worker-info {
                    color: #666; }
                .page-shopping-cart .cart-worder .worker-info img {
                    border-radius: 100%;
                    margin-right: 10px; }
                .page-shopping-cart .cart-worder .worker-info span {
                    color: #000; }
        
                .choose-worker-box {
                    width: 620px;
                    background: #fff; }
                .choose-worker-box .box-title {
                    height: 40px;
                    line-height: 40px;
                    background: #F7F7F7;
                    text-align: center;
                    position: relative;
                    font-size: 14px; }
                .choose-worker-box .box-title a {
                    display: block;
                    position: absolute;
                    top: 15px;
                    right: 16px;
                    width: 10px;
                    height: 10px;
                    background: url("shopping_cart.png") no-repeat -80px 0; }
                .choose-worker-box .box-title a:hover {
                    background: url("shopping_cart.png") no-repeat -80px -22px; }
                .choose-worker-box .worker-list {
                    padding-top: 30px;
                    height: 134px;
                    overflow-y: auto; }
                .choose-worker-box .worker-list li {
                    float: left;
                    width: 25%;
                    text-align: center;
                    margin-bottom: 30px; }
                .choose-worker-box .worker-list li p {
                    margin-top: 8px; }
                .choose-worker-box .worker-list li.cur a {
                    color: #f70; }
                .choose-worker-box .worker-list li.cur a img {
                    border: 1px solid #f70; }
                .choose-worker-box .worker-list li a:hover {
                    color: #f70; }
                .choose-worker-box .worker-list li a:hover img {
                    border: 1px solid #f70; }
                .choose-worker-box .worker-list li img {
                    border: 1px solid #fff;
                    border-radius: 100%; }
            </style>
        </head>
        <body>
        <div class="page-shopping-cart" id="shopping-cart">
            <h4 class="cart-title">購物清單</h4>
            <div class="cart-product-title clearfix">
                <div class="td-check fl"><span class="check-span fl check-all"></span>全選</div>
                <div class="td-product fl">商品</div>
                <div class="td-num fl">數量</div>
                <div class="td-price fl">單價(元)</div>
                <div class="td-total fl">金額(元)</div>
                <div class="td-do fl">操作</div>
            </div>
            <div class="cart-product clearfix">
                <table>
                    <tbody><tr>
                        <td class="td-check"><span class="check-span"></span></td>
                        <td class="td-product"><img data-original="testimg.jpg" width="98" height="98">
                            <div class="product-info">
                                <h6>【斯文】甘油&nbsp;|&nbsp;丙三醇</h6>
                                <p>品牌:韓國skc&nbsp;&nbsp;產地:韓國</p>
                                <p>規格/純度:99.7%&nbsp;&nbsp;起定量:215千克</p>
                                <p>配送倉儲:上海倉海倉儲</p>
                            </div>
                            <div class="clearfix"></div>
                        </td>
                        <td class="td-num">
                            <div class="product-num">
                                <a href="javascript:;" class="num-reduce num-do fl"><span></span></a>
                                <input type="text" class="num-input" value="3">
                                <a href="javascript:;" class="num-add num-do fr"><span></span></a>
                            </div>
                        </td>
                        <td class="td-price">
                            <p class="red-text">¥<span class="price-text">800</span>.00</p>
                        </td>
                        <td class="td-total">
                            <p class="red-text">¥<span class="total-text">800</span>.00</p>
                        </td>
                        <td class="td-do"><a href="javascript:;" class="product-delect">刪除</a></td>
                    </tr>
                    <tr>
                        <td class="td-check"><span class="check-span check-true"></span></td>
                        <td class="td-product"><img data-original="testimg.jpg" width="98" height="98">
                            <div class="product-info">
                                <h6>【斯文】甘油&nbsp;|&nbsp;丙三醇</h6>
                                <p>品牌:韓國skc&nbsp;&nbsp;產地:韓國</p>
                                <p>規格/純度:99.7%&nbsp;&nbsp;起定量:215千克</p>
                                <p>配送倉儲:上海倉海倉儲</p>
                            </div>
                            <div class="clearfix"></div>
                        </td>
                        <td class="td-num">
                            <div class="product-num">
                                <a href="javascript:;" class="num-reduce num-do fl"><span></span></a>
                                <input type="text" class="num-input" value="1">
                                <a href="javascript:;" class="num-add num-do fr"><span></span></a>
                            </div>
                        </td>
                        <td class="td-price">
                            <p class="red-text">¥<span class="price-text">800</span>.00</p>
                        </td>
                        <td class="td-total">
                            <p class="red-text">¥<span class="total-text">800</span>.00</p>
                        </td>
                        <td class="td-do"><a href="javascript:;" class="product-delect">刪除</a></td>
                    </tr>
                    </tbody></table>
            </div>
            <div class="cart-product-info">
                <a class="delect-product" href="javascript:;"><span></span>刪除所選商品</a>
                <a class="keep-shopping" href="#"><span></span>繼續購物</a>
                <a class="btn-buy fr" href="javascript:;">去結算</a>
                <p class="fr product-total">¥<span>1600</span></p>
                <p class="fr check-num"><span>2</span>件商品總計(不含運費):</p>
            </div>
            <div class="cart-worder clearfix">
                <a href="javascript:;" class="choose-worder fl"><span></span>綁定跟單員</a>
                <div class="worker-info fl">
                </div>
            </div>
        </div>
        </body>
        <script data-original="vue.min.js"></script>
        <script>
            new Vue({
                el:'#shopping-cart',
                data:{
        
                },
                computed: {},
                methods:{
                    
                }
            })
        </script>
        </html>

        然后準備下列表數據,根據下面表格的箭頭

        clipboard.png

        所以大家就知道嗎,下面的數據大概是漲這樣

        productList:[
            {
                'pro_name': '【斯文】甘油 | 丙三醇',//產品名稱
                'pro_brand': 'skc',//品牌名稱
                'pro_place': '韓國',//產地
                'pro_purity': '99.7%',//規格
                'pro_min': "215千克",//最小起訂量
                'pro_depot': '上海倉海倉儲',//所在倉庫
                'pro_num': 3,//數量
                'pro_img': '../../images/ucenter/testimg.jpg',//圖片鏈接
                'pro_price': 800//單價
            }
        ]

        準備了這么多,大家可能想到,還缺少一個,就是記錄產品是否有選中,但是這個字段,雖然可以在上面那里加,但是意義不大,比如在平常項目那里!后臺的數據不會這樣返回,數據庫也不會有這個字段,這個字段應該是自己添加的。代碼如下

        new Vue({
            el:'#shopping-cart',
            data:{
                productList:[
                    {
                        'pro_name': '【斯文】甘油 | 丙三醇',//產品名稱
                        'pro_brand': 'skc',//品牌名稱
                        'pro_place': '韓國',//產地
                        'pro_purity': '99.7%',//規格
                        'pro_min': "215千克",//最小起訂量
                        'pro_depot': '上海倉海倉儲',//所在倉庫
                        'pro_num': 3,//數量
                        'pro_img': '../../images/ucenter/testimg.jpg',//圖片鏈接
                        'pro_price': 800//單價
                    }
                ]
            },
            computed: {},
            methods:{
        
            },
            mounted:function () {
                //為productList添加select(是否選中)字段,初始值為true
                var _this=this;
                //為productList添加select(是否選中)字段,初始值為true
                this.productList.map(function (item) {
                    _this.$set(item, 'select', true);
                })
                //要像上面這樣寫雙向綁定才能起效,下面的寫法是有問題的,雙向綁定不起效的!
                //this.productList.map(function (item) {item.select=true})
            }
        })
                  

        步驟1

        為了著重表示我修改了什么地方,代碼我現在只貼出修改的部分,大家對著上面的布局,就很容易知道我改的是什么地方了!下面也是這樣操作!

        點擊增加和減少按鈕(箭頭指向地方),所屬列的金額改變(紅框地方)
        clipboard.png

        執行步驟1之前,要先把列表的數據給鋪出來。利用v-for指令。代碼如下

        <tr v-for="item in productList">
            <td class="td-check"><span class="check-span"></span></td>
            <td class="td-product"><img :data-original="item.pro_img" width="98" height="98">
                <div class="product-info">
                    <h6>{{item.pro_name}}</h6>
                    <p>品牌:{{item.pro_brand}}&nbsp;&nbsp;產地:{{item.pro_place}}</p>
                    <p>規格/純度:{{item.pro_purity}}&nbsp;&nbsp;起定量:{{item.pro_min}}</p>
                    <p>配送倉儲:{{item.pro_depot}}</p>
                </div>
                <div class="clearfix"></div>
            </td>
            <td class="td-num">
                <div class="product-num">
                    <a href="javascript:;" class="num-reduce num-do fl" @click="item.pro_num--"><span></span></a>
                    <input type="text" class="num-input" v-model="item.pro_num">
                    <a href="javascript:;" class="num-add num-do fr" @click="item.pro_num++"><span></span></a>
                </div>
            </td>
            <td class="td-price">
                <p class="red-text">¥<span class="price-text">{{item.pro_price.toFixed(2)}}</span></p>
            </td>
            <td class="td-total">
                <p class="red-text">¥<span class="total-text">{{item.pro_price*item.pro_num}}</span>.00</p>
            </td>
            <td class="td-do"><a href="javascript:;" class="product-delect">刪除</a></td>
        </tr>
        

        這樣,列表的數據就有了!

        clipboard.png

        也可以發現,clipboard.png這兩個按鈕的功能已經實現了,后面的金額也會發生變化!是不是感到很驚喜!其實這里沒什么特別的,就是因為輸入框利用v-model綁定了數量(pro_num),然后兩個按鈕分別添加了事件@click="item.pro_num--"和@click="item.pro_num++"。比如剛開始pro_num是3,點擊clipboard.png,pro_num就變成2,點擊clipboard.png
        ,pro_num就變成4,然后后面的金額會改改,是因為{{item.pro_price*item.pro_num}}。只要pro_price或者pro_num的值改變了,整一塊也會改變,視圖就會刷新,我們就能看到變化(這些事情是vue做的,這就是MVVM的魅力,數據驅動視圖改變)。

        步驟2

        點擊所屬列選擇按鈕(箭頭指向地方),總計的金額(紅框地方)和已選產品的列數(藍框地方)和全選(黃框地方)會改變(如果已經全選了,全選按鈕自動變成全選,如果沒有全選,全選按鈕,自動取消全選)!

        clipboard.png

        首先,選擇與取消選擇,在這里只有兩個操作(其實只有一個:改變這條記錄的select字段)。

        clipboard.png

        然后改變clipboard.png,如果這條記錄selectfalse,就顯示clipboard.png,否則就顯示clipboard.png。
        代碼如下

        <td class="td-check"><span class="check-span" @click="item.select=!item.select" :class="{'check-true':item.select}"></span></td>

        其實就是等于添加了@click="item.select=!item.select" :class="{'check-true':item.select}"這里。點擊這個,這條數據的select字段就取反(true->false或者false->true)。然后:class="{'check-true':item.select}",就會根據這條數據的select字段進行判斷,是否添加check-true類名,如果select字段為true,就添加類名,顯示clipboard.png。否則不添加類名,顯示
        clipboard.png。

        然后,clipboard.png全選按鈕,是否變成clipboard.png。這里用一個computed(計算屬性)就好。代碼如下

        html

        <div class="td-check fl"><span class="check-span fl check-all" :class="{'check-true':isSelectAll}"></span>全選</div>
        

        js

        computed: {
            isSelectAll:function(){
                //如果productList中每一條數據的select都為true,返回true,否則返回false;
                return this.productList.every(function (val) { return val.select});
            }
        }

        代碼我解釋下,就是計算屬性中,定義的isSelectAll依賴productList。只要productList改變,isSelectAll的返回值就會改變,然后:class="{'check-true':isSelectAll}"根絕isSelectAll返回值是否添加'check-true'類名,顯示對應的樣式!
        最后,clipboard.png,這里的多少件產品和總價,也是使用計算屬性,有了上一步的基礎,給出代碼,大家一看就明白了!
        html

        <p class="fr product-total">¥<span>{{getTotal.totalPrice}}</span></p>
        <p class="fr check-num"><span>{{getTotal.totalNum}}</span>件商品總計(不含運費):</p>

        js

        computed: {
            //檢測是否全選
            isSelectAll:function(){
                //如果productList中每一條數據的select都為true,返回true,否則返回false;
                return this.productList.every(function (val) { return val.select});
            },
            //獲取總價和產品總件數
            getTotal:function(){
                //獲取productList中select為true的數據。
                var _proList=this.productList.filter(function (val) { return val.select}),totalPrice=0;
                for(var i=0,len=_proList.length;i<len;i++){
                    //總價累加
                    totalPrice+=_proList[i].pro_num*_proList[i].pro_price;
                }
                //選擇產品的件數就是_proList.length,總價就是totalPrice
                return {totalNum:_proList.length,totalPrice:totalPrice}
            }
        },

        代碼很簡單,html根據getTotal返回值顯示數據,getTotal依賴productList的數據,只要productList改變,返回值會改變,視圖也會改變!

        clipboard.png

        步驟3

        點擊全選按鈕(箭頭指向部分),會自動的對產品進行全選或者取消全選,下面的總計也會發生改變

        clipboard.png
        做到這一步,大家應該知道,全選或者取消全選,就是改變記錄的select。但是怎么知道現在的列表有沒有全選呢?這個很賤,不需要在操作函數(全選與取消全選函數)里面遍歷,大家應該還記得第二步的計算屬性isSelectAll(為true就是全選,否則不是全選),把這個傳進操作函數就好,然后操作函數,根據參數,決定執行全選,還是取消全選操作。代碼如下!
        html

        <div class="td-check fl"><span class="check-span fl check-all" :class="{'check-true':isSelectAll}" @click="selectProduct(isSelectAll)"></span>全選</div>

        js

         methods: {
            //全選與取消全選
            selectProduct:function(_isSelect){
                //遍歷productList,全部取反
                for (var i = 0, len = this.productList.length; i < len; i++) {
                    this.productList[i].select = !_isSelect;
                }
            }
        },
        

        clipboard.png

        步驟4

        點擊刪除產品,會刪除已經選中的,全選按鈕和下面的總計,都會變化!點擊每條記錄后面的刪除,會刪除當前的這條記錄。全選按鈕和下面的總計,也都會變化!

        clipboard.png

        首先,點擊刪除產品,刪除已經選中。這個大家知道了怎么做了!就是遍歷productList,如果哪條記錄的select為true,就刪除。
        然后,點擊每條記錄后面的刪除,刪除當前的這條記錄。這個在html遍歷productList的時候。順便帶上索引,然后把索引當成參數,傳進操作函數,然后根據索引參數,刪除productList的哪一條記錄。即可實現!代碼如下!
        html

        <!--遍歷的時候帶上索引-->
        <tr v-for="(item,index) in productList">
            <td class="td-check"><span class="check-span" @click="item.select=!item.select" :class="{'check-true':item.select}"></span></td>
            <td class="td-product"><img :data-original="item.pro_img" width="98" height="98">
                <div class="product-info">
                    <h6>{{item.pro_name}}</h6>
                    <p>品牌:{{item.pro_brand}}&nbsp;&nbsp;產地:{{item.pro_place}}</p>
                    <p>規格/純度:{{item.pro_purity}}&nbsp;&nbsp;起定量:{{item.pro_min}}</p>
                    <p>配送倉儲:{{item.pro_depot}}</p>
                </div>
                <div class="clearfix"></div>
            </td>
            <td class="td-num">
                <div class="product-num">
                    <a href="javascript:;" class="num-reduce num-do fl" @click="item.pro_num--"><span></span></a>
                    <input type="text" class="num-input" v-model="item.pro_num">
                    <a href="javascript:;" class="num-add num-do fr" @click="item.pro_num++"><span></span></a>
                </div>
            </td>
            <td class="td-price">
                <p class="red-text">¥<span class="price-text">{{item.pro_price.toFixed(2)}}</span></p>
            </td>
            <td class="td-total">
                <p class="red-text">¥<span class="total-text">{{item.pro_price*item.pro_num}}</span>.00</p>
            </td>
            <td class="td-do"><a href="javascript:;" class="product-delect" @click="deleteOneProduct(index)">刪除</a></td>
        </tr>
        ...
        <a class="delect-product" href="javascript:;" @click="deleteProduct"><span></span>刪除所選商品</a>

        js

        //刪除已經選中(select=true)的產品
        deleteProduct:function () {
            this.productList=this.productList.filter(function (item) {return !item.select})
        },
        //刪除單條產品
        deleteOneProduct:function (index) {
            //根據索引刪除productList的記錄
            this.productList.splice(index,1);
        },

        完整代碼

        樣式圖片

        clipboard.png

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
            <style>
                .fl {
                    float: left;
                }
        
                .fr {
                    float: right;
                }
        
                blockquote, body, dd, div, dl, dt, fieldset, form, h1, h2, h3, h4, h5, h6, img, input, li, ol, p, table, td, textarea, th, ul {
                    margin: 0;
                    padding: 0;
                }
        
                .clearfix {
                    zoom: 1;
                }
        
                .clearfix:after {
                    clear: both;
                }
        
                .clearfix:after {
                    content: '.';
                    display: block;
                    overflow: hidden;
                    visibility: hidden;
                    font-size: 0;
                    line-height: 0;
                    width: 0;
                    height: 0;
                }
        
                a {
                    text-decoration: none;
                    color: #333;
                }
        
                img {
                    vertical-align: middle;
                }
        
                .page-shopping-cart {
                    width: 1200px;
                    margin: 50px auto;
                    font-size: 14px;
                    border: 1px solid #e3e3e3;
                    border-top: 2px solid #317ee7;
                }
        
                .page-shopping-cart .cart-title {
                    color: #317ee7;
                    font-size: 16px;
                    text-align: left;
                    padding-left: 20px;
                    line-height: 68px;
                }
        
                .page-shopping-cart .red-text {
                    color: #e94826;
                }
        
                .page-shopping-cart .check-span {
                    display: block;
                    width: 24px;
                    height: 20px;
                    background: url("shopping_cart.png") no-repeat 0 0;
                }
        
                .page-shopping-cart .check-span.check-true {
                    background: url("shopping_cart.png") no-repeat 0 -22px;
                }
        
                .page-shopping-cart .td-check {
                    width: 70px;
                }
        
                .page-shopping-cart .td-product {
                    width: 460px;
                }
        
                .page-shopping-cart .td-num, .page-shopping-cart .td-price, .page-shopping-cart .td-total {
                    width: 160px;
                }
        
                .page-shopping-cart .td-do {
                    width: 150px;
                }
        
                .page-shopping-cart .cart-product-title {
                    text-align: center;
                    height: 38px;
                    line-height: 38px;
                    padding: 0 20px;
                    background: #f7f7f7;
                    border-top: 1px solid #e3e3e3;
                    border-bottom: 1px solid #e3e3e3;
                }
        
                .page-shopping-cart .cart-product-title .td-product {
                    text-align: center;
                    font-size: 14px;
                }
        
                .page-shopping-cart .cart-product-title .td-check {
                    text-align: left;
                }
        
                .page-shopping-cart .cart-product-title .td-check .check-span {
                    margin: 9px 6px 0 0;
                }
        
                .page-shopping-cart .cart-product {
                    padding: 0 20px;
                    text-align: center;
                }
        
                .page-shopping-cart .cart-product table {
                    width: 100%;
                    text-align: center;
                    font-size: 14px;
                }
        
                .page-shopping-cart .cart-product table td {
                    padding: 20px 0;
                }
        
                .page-shopping-cart .cart-product table tr {
                    border-bottom: 1px dashed #e3e3e3;
                }
        
                .page-shopping-cart .cart-product table tr:last-child {
                    border-bottom: none;
                }
        
                .page-shopping-cart .cart-product table .product-num {
                    border: 1px solid #e3e3e3;
                    display: inline-block;
                    text-align: center;
                }
        
                .page-shopping-cart .cart-product table .product-num .num-do {
                    width: 24px;
                    height: 28px;
                    display: block;
                    background: #f7f7f7;
                }
        
                .page-shopping-cart .cart-product table .product-num .num-reduce span {
                    background: url("shopping_cart.png") no-repeat -40px -22px;
                    display: block;
                    width: 6px;
                    height: 2px;
                    margin: 13px auto 0 auto;
                }
        
                .page-shopping-cart .cart-product table .product-num .num-add span {
                    background: url("shopping_cart.png") no-repeat -60px -22px;
                    display: block;
                    width: 8px;
                    height: 8px;
                    margin: 10px auto 0 auto;
                }
        
                .page-shopping-cart .cart-product table .product-num .num-input {
                    width: 42px;
                    height: 28px;
                    line-height: 28px;
                    border: none;
                    text-align: center;
                }
        
                .page-shopping-cart .cart-product table .td-product {
                    text-align: left;
                    font-size: 12px;
                    line-height: 20px;
                }
        
                .page-shopping-cart .cart-product table .td-product img {
                    border: 1px solid #e3e3e3;
                    margin-right: 10px;
                }
        
                .page-shopping-cart .cart-product table .td-product .product-info {
                    display: inline-block;
                    vertical-align: middle;
                }
        
                .page-shopping-cart .cart-product table .td-do {
                    font-size: 12px;
                }
        
                .page-shopping-cart .cart-product-info {
                    height: 50px;
                    line-height: 50px;
                    background: #f7f7f7;
                    padding-left: 20px;
                }
        
                .page-shopping-cart .cart-product-info .delect-product {
                    color: #666;
                }
        
                .page-shopping-cart .cart-product-info .delect-product span {
                    display: inline-block;
                    vertical-align: top;
                    margin: 18px 8px 0 0;
                    width: 13px;
                    height: 15px;
                    background: url("shopping_cart.png") no-repeat -60px 0;
                }
        
                .page-shopping-cart .cart-product-info .product-total {
                    font-size: 14px;
                    color: #e94826;
                }
        
                .page-shopping-cart .cart-product-info .product-total span {
                    font-size: 20px;
                }
        
                .page-shopping-cart .cart-product-info .check-num {
                    color: #333;
                }
        
                .page-shopping-cart .cart-product-info .check-num span {
                    color: #e94826;
                }
        
                .page-shopping-cart .cart-product-info .keep-shopping {
                    color: #666;
                    margin-left: 40px;
                }
        
                .page-shopping-cart .cart-product-info .keep-shopping span {
                    display: inline-block;
                    vertical-align: top;
                    margin: 18px 8px 0 0;
                    width: 15px;
                    height: 15px;
                    background: url("shopping_cart.png") no-repeat -40px 0;
                }
        
                .page-shopping-cart .cart-product-info .btn-buy {
                    height: 50px;
                    color: #fff;
                    font-size: 20px;
                    display: block;
                    width: 110px;
                    background: #ff7700;
                    text-align: center;
                    margin-left: 30px;
                }
        
                .page-shopping-cart .cart-worder {
                    padding: 20px;
                }
        
                .page-shopping-cart .cart-worder .choose-worder {
                    color: #fff;
                    display: block;
                    background: #39e;
                    width: 140px;
                    height: 40px;
                    line-height: 40px;
                    border-radius: 4px;
                    text-align: center;
                    margin-right: 20px;
                }
        
                .page-shopping-cart .cart-worder .choose-worder span {
                    display: inline-block;
                    vertical-align: top;
                    margin: 9px 10px 0 0;
                    width: 22px;
                    height: 22px;
                    background: url("shopping_cart.png") no-repeat -92px 0;
                }
        
                .page-shopping-cart .cart-worder .worker-info {
                    color: #666;
                }
        
                .page-shopping-cart .cart-worder .worker-info img {
                    border-radius: 100%;
                    margin-right: 10px;
                }
        
                .page-shopping-cart .cart-worder .worker-info span {
                    color: #000;
                }
        
                .choose-worker-box {
                    width: 620px;
                    background: #fff;
                }
        
                .choose-worker-box .box-title {
                    height: 40px;
                    line-height: 40px;
                    background: #F7F7F7;
                    text-align: center;
                    position: relative;
                    font-size: 14px;
                }
        
                .choose-worker-box .box-title a {
                    display: block;
                    position: absolute;
                    top: 15px;
                    right: 16px;
                    width: 10px;
                    height: 10px;
                    background: url("shopping_cart.png") no-repeat -80px 0;
                }
        
                .choose-worker-box .box-title a:hover {
                    background: url("shopping_cart.png") no-repeat -80px -22px;
                }
        
                .choose-worker-box .worker-list {
                    padding-top: 30px;
                    height: 134px;
                    overflow-y: auto;
                }
        
                .choose-worker-box .worker-list li {
                    float: left;
                    width: 25%;
                    text-align: center;
                    margin-bottom: 30px;
                }
        
                .choose-worker-box .worker-list li p {
                    margin-top: 8px;
                }
        
                .choose-worker-box .worker-list li.cur a {
                    color: #f70;
                }
        
                .choose-worker-box .worker-list li.cur a img {
                    border: 1px solid #f70;
                }
        
                .choose-worker-box .worker-list li a:hover {
                    color: #f70;
                }
        
                .choose-worker-box .worker-list li a:hover img {
                    border: 1px solid #f70;
                }
        
                .choose-worker-box .worker-list li img {
                    border: 1px solid #fff;
                    border-radius: 100%;
                }
            </style>
        </head>
        <body>
        <div class="page-shopping-cart" id="shopping-cart">
            <h4 class="cart-title">購物清單</h4>
            <div class="cart-product-title clearfix">
                <div class="td-check fl"><span class="check-span fl check-all" :class="{'check-true':isSelectAll}" @click="selectProduct(isSelectAll)"></span>全選</div>
                <div class="td-product fl">商品</div>
                <div class="td-num fl">數量</div>
                <div class="td-price fl">單價(元)</div>
                <div class="td-total fl">金額(元)</div>
                <div class="td-do fl">操作</div>
            </div>
            <div class="cart-product clearfix">
                <table>
                    <tbody>
                    <!--遍歷的時候帶上索引-->
                    <tr v-for="(item,index) in productList">
                        <td class="td-check"><span class="check-span" @click="item.select=!item.select" :class="{'check-true':item.select}"></span></td>
                        <td class="td-product"><img :data-original="item.pro_img" width="98" height="98">
                            <div class="product-info">
                                <h6>{{item.pro_name}}</h6>
                                <p>品牌:{{item.pro_brand}}&nbsp;&nbsp;產地:{{item.pro_place}}</p>
                                <p>規格/純度:{{item.pro_purity}}&nbsp;&nbsp;起定量:{{item.pro_min}}</p>
                                <p>配送倉儲:{{item.pro_depot}}</p>
                            </div>
                            <div class="clearfix"></div>
                        </td>
                        <td class="td-num">
                            <div class="product-num">
                                <a href="javascript:;" class="num-reduce num-do fl" @click="item.pro_num>0?item.pro_num--:''"><span></span></a>
                                <input type="text" class="num-input" v-model="item.pro_num">
                                <a href="javascript:;" class="num-add num-do fr" @click="item.pro_num++"><span></span></a>
                            </div>
                        </td>
                        <td class="td-price">
                            <p class="red-text">¥<span class="price-text">{{item.pro_price.toFixed(2)}}</span></p>
                        </td>
                        <td class="td-total">
                            <p class="red-text">¥<span class="total-text">{{item.pro_price*item.pro_num}}</span>.00</p>
                        </td>
                        <td class="td-do"><a href="javascript:;" class="product-delect" @click="deleteOneProduct(index)">刪除</a></td>
                    </tr>
                    </tbody>
                </table>
            </div>
            <div class="cart-product-info">
                <a class="delect-product" href="javascript:;" @click="deleteProduct"><span></span>刪除所選商品</a>
                <a class="keep-shopping" href="#"><span></span>繼續購物</a>
                <a class="btn-buy fr" href="javascript:;">去結算</a>
                <p class="fr product-total">¥<span>{{getTotal.totalPrice}}</span></p>
                <p class="fr check-num"><span>{{getTotal.totalNum}}</span>件商品總計(不含運費):</p>
            </div>
        </div>
        </body>
        <script data-original="vue.min.js"></script>
        <script>
            new Vue({
                el: '#shopping-cart',
                data: {
                    productList: [
                        {
                            'pro_name': '【斯文】甘油 | 丙三醇',//產品名稱
                            'pro_brand': 'skc',//品牌名稱
                            'pro_place': '韓國',//產地
                            'pro_purity': '99.7%',//規格
                            'pro_min': "215千克",//最小起訂量
                            'pro_depot': '上海倉海倉儲',//所在倉庫
                            'pro_num': 3,//數量
                            'pro_img': '../../images/ucenter/testimg.jpg',//圖片鏈接
                            'pro_price': 800//單價
                        },
                        {
                            'pro_name': '【斯文】甘油 | 丙三醇',//產品名稱
                            'pro_brand': 'skc',//品牌名稱
                            'pro_place': '韓國',//產地
                            'pro_purity': '99.7%',//規格
                            'pro_min': "215千克",//最小起訂量
                            'pro_depot': '上海倉海倉儲',//所在倉庫
                            'pro_num': 3,//數量
                            'pro_img': '../../images/ucenter/testimg.jpg',//圖片鏈接
                            'pro_price': 800//單價
                        },
                        {
                            'pro_name': '【斯文】甘油 | 丙三醇',//產品名稱
                            'pro_brand': 'skc',//品牌名稱
                            'pro_place': '韓國',//產地
                            'pro_purity': '99.7%',//規格
                            'pro_min': "215千克",//最小起訂量
                            'pro_depot': '上海倉海倉儲',//所在倉庫
                            'pro_num': 3,//數量
                            'pro_img': '../../images/ucenter/testimg.jpg',//圖片鏈接
                            'pro_price': 800//單價
                        }
                    ]
                },
                computed: {
                    //檢測是否全選
                    isSelectAll:function(){
                        //如果productList中每一條數據的select都為true,返回true,否則返回false;
                        return this.productList.every(function (val) { return val.select});
                    },
                    //獲取總價和產品總件數
                    getTotal:function(){
                        //獲取productList中select為true的數據。
                        var _proList=this.productList.filter(function (val) { return val.select}),totalPrice=0;
                        for(var i=0,len=_proList.length;i<len;i++){
                            //總價累加
                            totalPrice+=_proList[i].pro_num*_proList[i].pro_price;
                        }
                        //選擇產品的件數就是_proList.length,總價就是totalPrice
                        return {totalNum:_proList.length,totalPrice:totalPrice}
                    }
                },
                methods: {
                    //全選與取消全選
                    selectProduct:function(_isSelect){
                        //遍歷productList,全部取反
                        for (var i = 0, len = this.productList.length; i < len; i++) {
                            this.productList[i].select = !_isSelect;
                        }
                    },
                    //刪除已經選中(select=true)的產品
                    deleteProduct:function () {
                        this.productList=this.productList.filter(function (item) {return !item.select})
                    },
                    //刪除單條產品
                    deleteOneProduct:function (index) {
                        //根據索引刪除productList的記錄
                        this.productList.splice(index,1);
                    },
                },
                mounted: function () {
                    var _this=this;
                    //為productList添加select(是否選中)字段,初始值為true
                    this.productList.map(function (item) {
                        _this.$set(item, 'select', true);
                    })
                }
            })
        </script>
        </html> 
                  

        5.todoList

        運行效果

        clipboard.png

        原理分析和實現

        首先,還是先把布局寫好,和引入vue,準備vue實例,這個不多說,代碼如下

        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8">
                <title></title>
                <style>
                    body{font-family: "微軟雅黑";font-size: 14px;}
                    input{font-size: 14px;}
                    body,ul,div,html{padding: 0;margin: 0;}
                    .hidden{display: none;}
                    .main{width: 800px;margin: 0 auto;}
                    li{list-style-type: none;line-height: 40px;position: relative;border: 1px solid transparent;padding: 0 20px;}
                    li .type-span{display: block;width: 10px;height: 10px;background: #ccc;margin: 14px 10px 0 0 ;float: left;}
                    li .close{position: absolute;color: #f00;font-size: 20px;line-height: 40px;height: 40px;right: 20px;cursor: pointer;display: none;top: 0;}
                    li:hover{border: 1px solid #09f;}
                    li:hover .close{display: block;}
                    li .text-keyword{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
                    .text-keyword{box-sizing: border-box;width: 100%;height: 40px;padding-left: 10px;outline: none;}
                </style>
            </head>
            <body>
                <div id="app" class="main">
                    <h2>小目標列表</h2>
                    <div class="list">
                        <h3>添加小目標</h3>
                        <input type="text" class="text-keyword" placeholder="輸入小目標后,按回車確認"/>
                        <p>共有N個目標</p>
                        <p>
                            <input type="radio" name="chooseType" checked="true"/><label>所有目標</label>
                            <input type="radio" name="chooseType"/><label>已完成目標</label>
                            <input type="radio" name="chooseType"/><label>未完成目標</label>
                        </p>
                    </div>
                    <ul>
                        <li class="li1">
                            <div>
                                <span class="type-span"></span>
                                <span>html5</span>
                                <span class="close">X</span>
                            </div>
                        </li>
                        <li class="li1">
                            <div>
                                <span class="type-span"></span>
                                <span>css3</span>
                                <span class="close">X</span>
                            </div>
                        </li>
                    </ul>
                </div>
            </body>
            <script data-original="vue2.4.2.js"></script>
            <script type="text/javascript">
            new Vue({
                el: "#app",
                data: {
                },
                computed:{
                    
                },
                methods:{
                    
                }
            });
            </script>
        </html>

        布局有了,相當于一個骨架就有了,下面實現功能,一個一個來

        步驟1

        輸入并回車,多一條記錄。下面的記錄文字也會改變

        clipboard.png

        首先,大的輸入框回車要添加紀錄,那么輸入框必須綁定一個值和一個添加紀錄的方法。
        代碼如下:
        然后,下面的記錄也要改變,所以,下面的記錄也要幫一個值,因為這個記錄可能會有多個,這個值就是一個數組,也可以看到,記錄除了名稱,還有記錄是否完成的狀態,所以,綁定記錄的這個值肯定是一個對象數組!代碼如下
        最后,記錄文字clipboard.png要改變。這個只是一個當前記錄的長度即可!

        為了著重表示我修改了什么地方,代碼我現在只貼出修改的部分,大家對著上面的布局,就很容易知道我改的是什么地方了!下面也是這樣操作!

        html代碼

        <!--利用v-model把addText綁定到input-->
        <input type="text" class="text-keyword" placeholder="輸入小目標后,按回車確認" @keyup.13='addList' v-model="addText"/>
        <p>共有{{prolist.length}}個目標</p>
        <!--v-for遍歷prolist-->
        <li class="li1" v-for="list in prolist">
            <div>
                <span class="type-span"></span>
                <span>{{list.name}}</span>
                <span class="close">X</span>
            </div>
        </li>
        

        js代碼

        new Vue({
            el: "#app",
            data: {
                addText:'',
                //name-名稱,status-完成狀態
               prolist:[
                       {name:"HTML5",status:false},
                       {name:"CSS3",status:false},
                       {name:"vue",status:false},
                       {name:"react",status:false}
                ]
            },
            computed:{
                
            },
            methods:{
                addList(){
                    //添加進來默認status=false,就是未完成狀態
                    this.prolist.push({
                        name:this.addText,
                        status:false
                    });
                    //添加后,清空addText
                    this.addText="";
                }
            }
        });

        測試一下,沒問題

        clipboard.png

        步驟2

        點擊切換,下面記錄會改變

        clipboard.png

        看到三個選項,也很簡單,無非就是三個選擇,一個是所有的目標,一個是所有已經完成的目標,一個是所有沒完成的目標。
        首先.新建一個新的變量(newList),儲存prolist。遍歷的時候不再遍歷prolist,而是遍歷newList。改變也是改變newList。
        然后.選擇所有目標的時候,顯示全部prolist,把prolist賦值給newList。
        然后.選擇所有已經完成目標的時候,只顯示prolist中,status為true的目標,把prolist中,status為true的項賦值給newList,
        最后.選擇所有未完成目標的時候,只顯示status為false的目標,把prolist中,status為false的項賦值給newList。

        代碼如下

        html

         <ul>
            <li class="li1" v-for="list in newList">
                <div>
                    <span class="status-span"></span>
                    <span>{{list.name}}</span>
                    <span class="close" @click='delectList(index)'>X</span>
                </div>
            </li>
        </ul>

        js

        new Vue({
            el: "#app",
            data: {
                addText:'',
                //name-名稱,status-完成狀態
               prolist:[
                       {name:"HTML5",status:false},
                       {name:"CSS3",status:false},
                       {name:"vue",status:false},
                       {name:"react",status:false}
                ],
                newList:[]
            },
            computed:{
                noend:function(){
                    return this.prolist.filter(function(item){
                        return !item.status
                    }).length;
                }
            },
            methods:{
                addList(){
                    //添加進來默認status=false,就是未完成狀態
                    this.prolist.push({
                        name:this.addText,
                        status:false
                    });
                    //添加后,清空addText
                    this.addText="";
                },
                chooseList(type){
                    //type=1時,選擇所有目標
                    //type=2時,選擇所有已完成目標
                    //type=3時,選擇所有未完成目標
                    switch(type){
                        case 1:this.newList=this.prolist;break;
                        case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                        case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
                    }
                },
                delectList(index){
                    //根據索引,刪除數組某一項
                    this.prolist.splice(index,1);
                    //更新newList  newList可能經過this.prolist.filter()賦值,這樣的話,刪除了prolist不會影響到newList  那么就要手動更新newList
                    this.newList=this.prolist;
                },
            },
            mounted(){
                //初始化,把prolist賦值給newList。默認顯示所有目標
                this.newList=this.prolist;
            }
        });
        

        運行結果

        clipboard.png

        步驟3

        紅色關閉標識,點擊會刪除該記錄。前面按鈕點擊會切換該記錄完成狀態,顏色也改變,記錄文字也跟著改變

        clipboard.png

        首先點擊紅色關閉標識,點擊會刪除該記錄。這個應該沒什么問題,就是刪除prolist的一條記錄!
        然后前面按鈕點擊會切換該記錄完成狀態。這個也沒什么,就是改變prolist的一條記錄的status字段!
        最后記錄文字的改變,就是記錄prolist中status為false的有多少條,prolist中status為true的有多少條而已

        html代碼

        <!--如果noend等于0,就是全部完成了就顯示‘全部完成了’,如果沒有就是顯示已完成多少條(prolist.length-noend)和未完成多少條(noend)-->
        <p>共有{{prolist.length}}個目標,{{noend==0?"全部完成了":'已完成'+(prolist.length-noend)+',還有'+noend+'條未完成'}}</p>
        
        
        <ul>
            <li class="li1" v-for="(list,index) in newList">
                <div>
                    <span class="status-span" @click="list.status=!list.status" :class="{'status-end':list.status}"></span>
                    <span>{{list.name}}</span>
                    <span class="close" @click='delectList(index)'>X</span>
                </div>
            </li>
        </ul>

        js

        new Vue({
            el: "#app",
            data: {
                addText:'',
                //name-名稱,status-完成狀態
               prolist:[
                       {name:"HTML5",status:false},
                       {name:"CSS3",status:false},
                       {name:"vue",status:false},
                       {name:"react",status:false}
                ],
                newList:[]
            },
            computed:{
                //計算屬性,返回未完成目標的條數,就是數組里面status=false的條數
                noend:function(){
                    return this.prolist.filter(function(item){
                        return !item.status
                    }).length;
                }
            },
            methods:{
                addList(){
                    //添加進來默認status=false,就是未完成狀態
                    this.prolist.push({
                        name:this.addText,
                        status:false
                    });
                    //添加后,清空addText
                    this.addText="";
                },
                chooseList(type){
                    switch(type){
                        case 1:this.newList=this.prolist;break;
                        case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                        case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
                    }
                },
                delectList(index){
                    //根據索引,刪除數組某一項
                    this.prolist.splice(index,1);
                    //更新newList  newList可能經過this.prolist.filter()賦值,這樣的話,刪除了prolist不會影響到newList  那么就要手動更新newList
                    this.newList=this.prolist;
                },
            },
            mounted(){
                this.newList=this.prolist;
            }
        });
        

        運行結果

        clipboard.png

        步驟4

        文字雙擊會出現輸入框,可輸入文字,如果回車或者失去焦點,就改變文字,如果按下ESC就恢復原來的文字

        clipboard.png

        首先.雙擊出現輸入框,就是雙擊文字后,給當前的li設置一個類名(‘eidting’),然后寫好樣式。當li出現這個類名的時候,就出現輸入框,并且隱藏其它內容。
        然后.回車或者失去焦點,就改變文字這個只需要操作一個,就是把類名(‘eidting’)清除掉。然后輸入框就會隱藏,其它內容顯示!
        最后.按下ESC就恢復原來的文字,就是出現輸入框的時候,用一個變量(‘beforeEditText’)先保存當前的內容,然后按下了ESC,就把變量(‘beforeEditText’)賦值給當前操作的值!

        代碼如下:

        html

        <ul>
            <li class="li1" v-for="(list,index) in newList" :class="{'eidting':curIndex===index}">
                <div>
                    <span class="status-span" @click="list.status=!list.status" :class="{'status-end':list.status}"></span>
                    <span @dblclick="curIndex=index">{{list.name}}</span>
                    <span class="close" @click='delectList(index)'>X</span>
                </div>
                <input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited'/>
            </li>
        </ul>

        css(加上)

        li div{display: block;}
        li.eidting div{display: none;}
        li .text2{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
        li.eidting .text2{display: block;}

        js

        methods:{
                addList(){
                    //添加進來默認status=false,就是未完成狀態
                    this.prolist.push({
                        name:this.addText,
                        status:false
                    });
                    //添加后,清空addText
                    this.addText="";
                },
                chooseList(type){
                    //type=1時,選擇所有目標
                    //type=2時,選擇所有已完成目標
                    //type=3時,選擇所有未完成目標
                    switch(type){
                        case 1:this.newList=this.prolist;break;
                        case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                        case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
                    }
                },
                delectList(index){
                    //根據索引,刪除數組某一項
                    this.prolist.splice(index,1);
                    //更新newList  newList可能經過this.prolist.filter()賦值,這樣的話,刪除了prolist不會影響到newList  那么就要手動更新newList
                    this.newList=this.prolist;
                },
                //修改前
                editBefore(name){
                    //先記錄當前項(比如這一項,{name:"HTML5",status:false})
                    //beforeEditText="HTML5"
                    this.beforeEditText=name;
                },
                //修改完成后
                edited(){
                    //修改完了,設置curIndex="",這樣輸入框就隱藏,其它元素就會顯示。因為在li元素 寫了::class="{'eidting':curIndex===index}"  當curIndex不等于index時,eidting類名就清除了!
                    //輸入框利用v-model綁定了當前項(比如這一項,{name:"HTML5",status:false})的name,當在輸入框編輯的時候,比如改成‘HTML’,實際上當前項的name已經變成了‘HTML’,所以,這一步只是清除eidting類名,隱藏輸入框而已
                    //還有一個要注意的就是雖然li遍歷的是newList,比如改了newList的這一項({name:"HTML5",status:false}),比如改成這樣({name:"HTML",status:true})。實際上prolist的這一項({name:"HTML5",status:false}),也會被改成({name:"HTML",status:true})。因為這里是一個對象,而且公用一個堆棧!修改其中一個,另一個會被影響到
                    this.curIndex="";
                },
                //取消修改
                cancelEdit(val){
                    //上面說了輸入框利用v-model綁定了當前項(比如這一項,{name:"HTML5",status:false})的name,當在輸入框編輯的時候,比如改成‘HTML’,實際上當前項的name已經變成了‘HTML’,所以,這一步就是把之前保存的beforeEditText賦值給當前項的name屬性,起到一個恢復原來值得作用!
                    val.name=this.beforeEditText;
                    this.curIndex="";
                }
         },

        運行結果

        clipboard.png

        還有一個小細節,大家可能注意到了,就是雙擊文字,出來輸入框的時候,還要自己手動點擊一下,才能獲得焦點,我們想雙擊了,輸入框出來的時候,自動獲取焦點,怎么辦?自定義指令就行了!

        computed:{...},
        methods:{...},
        mounted(){...},
        directives:{
            "focus":{
                update(el){
                    el.focus();
                }
            }
        }
        

        然后html 調用指令

        <input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited' v-focus/>

        完整代碼

        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title></title>
            <style>
                body{font-family: "微軟雅黑";font-size: 14px;}
                input{font-size: 14px;}
                body,ul,div,html{padding: 0;margin: 0;}
                .hidden{display: none;}
                .main{width: 800px;margin: 0 auto;}
                li{list-style-type: none;line-height: 40px;position: relative;border: 1px solid transparent;padding: 0 20px;}
                li .status-span{display: block;width: 10px;height: 10px;background: #ccc;margin: 14px 10px 0 0 ;float: left;}
                li .status-span.status-end{
                    background: #09f;
                }
                li .close{position: absolute;color: #f00;font-size: 20px;line-height: 40px;height: 40px;right: 20px;cursor: pointer;display: none;top: 0;}
                li:hover{border: 1px solid #09f;}
                li:hover .close{display: block;}
                li div{display: block;}
                li.eidting div{display: none;}
                li .text2{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
                li.eidting .text2{display: block;}
                li .text-keyword{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
                .text-keyword{box-sizing: border-box;width: 100%;height: 40px;padding-left: 10px;outline: none;}
            </style>
        </head>
        <body>
        <div id="app" class="main">
            <h2>小目標列表</h2>
            <div class="list">
                <h3>添加小目標</h3>
                <input type="text" class="text-keyword" placeholder="輸入小目標后,按回車確認" @keyup.13='addList' v-model="addText"/>
                <!--如果noend等于0,就是全部完成了就顯示‘全部完成了’,如果沒有就是顯示已完成多少條(prolist.length-noend)和未完成多少條(noend)-->
                <p>共有{{prolist.length}}個目標,{{noend==0?"全部完成了":'已完成'+(prolist.length-noend)+',還有'+noend+'條未完成'}}</p>
                <p>
                    <input type="radio" name="chooseType" checked="true" @click='chooseList(1)'/><label>所有目標</label>
                    <input type="radio" name="chooseType" @click='chooseList(2)'/><label>已完成目標</label>
                    <input type="radio" name="chooseType" @click='chooseList(3)'/><label>未完成目標</label>
                </p>
            </div>
            <ul>
                <li class="li1" v-for="(list,index) in newList" :class="{'eidting':curIndex===index}">
                    <div>
                        <span class="status-span" @click="changeType(index)" :class="{'status-end':list.status}"></span>
                        <span @dblclick="curIndex=index">{{list.name}}</span>
                        <span class="close" @click='delectList(list)'>X</span>
                    </div>
                    <input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited' v-focus/>
                </li>
            </ul>
        </div>
        </body>
        <script data-original="vue.min.js"></script>
        <script type="text/javascript">
            new Vue({
                el: "#app",
                data: {
                    addText:'',
                    //name-名稱,status-完成狀態
                    prolist:[
                        {name:"HTML5",status:false},
                        {name:"CSS3",status:false},
                        {name:"vue",status:false},
                        {name:"react",status:false}
                    ],
                    newList:[],
                    curIndex:'',
                    beforeEditText:"",
                    curType:0
                },
                computed:{
                    //計算屬性,返回未完成目標的條數,就是數組里面status=false的條數
                    noend:function(){
                        return this.prolist.filter(function(item){
                            return !item.status
                        }).length;
                    }
                },
                methods:{
                    addList(){
                        //添加進來默認status=false,就是未完成狀態
                        this.prolist.push({
                            name:this.addText,
                            status:false
                        });
                        //添加后,清空addText
                        this.addText="";
                    },
                    chooseList(type){
                        //type=1時,選擇所有目標
                        //type=2時,選擇所有已完成目標
                        //type=3時,選擇所有未完成目標
                        this.curType=type;
                        switch(type){
                            case 1:this.newList=this.prolist;break;
                            case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                            case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
                        }
                    },
                    /*改變單條數據的完成狀態*/
                    changeType(index){
                        this.newList[index].status=!this.newList[index].status;
                        //更新數據
                        this.chooseList(this.curType);
                    },
                    delectList(list){
                        var index=this.prolist.indexOf(list);
                        //根據索引,刪除數組某一項
                        this.prolist.splice(index,1);
                        //更新newList  newList可能經過this.prolist.filter()賦值,這樣的話,刪除了prolist不會影響到newList  那么就要手動更新newList
                        //this.newList=this.prolist;
                        this.chooseList(this.curType);
                    },
                    //修改前
                    editBefore(name){
                        //先記錄當前項(比如這一項,{name:"HTML5",status:false})
                        //beforeEditText="HTML5"
                        this.beforeEditText=name;
                    },
                    //修改完成后
                    edited(){
                        //修改完了,設置curIndex="",這樣輸入框就隱藏,其它元素就會顯示。因為在li元素 寫了::class="{'eidting':curIndex===index}"  當curIndex不等于index時,eidting類名就清除了!
                        //輸入框利用v-model綁定了當前項(比如這一項,{name:"HTML5",status:false})的name,當在輸入框編輯的時候,比如改成‘HTML’,實際上當前項的name已經變成了‘HTML’,所以,這一步只是清除eidting類名,隱藏輸入框而已
                        //還有一個要注意的就是雖然li遍歷的是newList,比如改了newList的這一項({name:"HTML5",status:false}),比如改成這樣({name:"HTML",status:true})。實際上prolist的這一項({name:"HTML5",status:false}),也會被改成({name:"HTML",status:true})。因為這里是一個對象,而且公用一個堆棧!修改其中一個,另一個會被影響到
                        this.curIndex="";
                    },
                    //取消修改
                    cancelEdit(val){
                        //上面說了輸入框利用v-model綁定了當前項(比如這一項,{name:"HTML5",status:false})的name,當在輸入框編輯的時候,比如改成‘HTML’,實際上當前項的name已經變成了‘HTML’,所以,這一步就是把之前保存的beforeEditText賦值給當前項的name屬性,起到一個恢復原來值得作用!
                        val.name=this.beforeEditText;
                        this.curIndex="";
                    }
                },
                mounted(){
                    //初始化,把prolist賦值給newList。默認顯示所有目標
                    this.newList=this.prolist;
                },
                directives:{
                    "focus":{
                        update(el){
                            el.focus();
                        }
                    }
                }
            });
        </script>
        </html>
        

        6.小結

        好了,三個小實例在這里就說完了!別看文章這么長,其實都是基礎,可能是我比較啰嗦而已!如果大家能熟透這幾個小實例,相信用vue做項目也是信手拈來?;A的語法在這里了,有了基礎,高級的寫法也不會很難學習!如果以后,我有什么要分享的,我會繼續分享。最后一句老話,如果覺得我哪里寫錯了,寫得不好,歡迎指點!

        -------------------------華麗的分割線--------------------
        想了解更多,關注關注我的微信公眾號:守候書閣

        clipboard.png

        查看原文

        守候 提出了問題 · 2019-02-27

        升級webpack4之后,入口文件沒有執行

        生成了webpack4之后,一翻折騰(比如升級了vue-loader,node.js等),項目跑起來了

        圖片描述

        但是不知道為什么,在瀏覽器打開地址之后,發現入口文件并沒有執行

        clipboard.png

        vue組件也沒有渲染到頁面

        下面這個是入口文件的代碼

        clipboard.png

        webpack的代碼

        clipboard.png

        項目目錄

        clipboard.png

        就是這個情況,跑起來之后,入口文件的debugger,并沒有執行。這個不知道哪里出了問題?

        還有一個問題就是打包之后的dist文件,不知道為什么會有兩個 vendor

        clipboard.png

        關注 1 回答 0

        守候 贊了文章 · 2019-02-26

        你知道前端對圖片的處理方式嗎?

        前言

        作為前端工程師 de 我們,日常少不了會跟圖片打交道。在各大電商平臺工作的前端工程師們,感受可能會更加的明顯。

        以下是我之前跟圖片打交道踩到的坑,跟大家分享一下經驗。

        一、情景再現

        用postman請求接口的時候,返回的是這個圖片(二進制)

        postman

        在chrome的network查看的時候,返回的也是這個圖片(二進制)

        network

        可是,在debug打印的時候,返回的卻是亂碼

        debug

        很明顯,數據的類型已經被改動了。思考原因,唯一有可能改變數據類型的地方是在axios。

        我去翻看了一下axios的文檔,里面是這樣描述的

        // `responseType` indicates the type of data that the server will respond with
        // options are 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
        responseType: 'json', // default

        因此,亂碼出現的原因是因為:axios默認返回的是json文本形式,二進制圖片數據被強制轉換成了json文本形式。

        找到了原因,解決方案就好辦了。我們在axios里面,responseType默認返回數據類型是json,將其改為返回數據類型blob。

        export function miniprogramQrcode (params) {
          return axios.post(
            env.MI_URL + '/XXXX/XXX/XXXX',
            params,
            // 將responseType的默認json改為blob
            {
            responseType: 'blob',
            emulateJSON: true
          }).then(res => {
            if (res.data) {
              return Promise.resolve(res.data)
            } else {
              throw res
            }
          }).catch(err => {
            return Promise.reject(err)
          })
        }

        接下來的問題是,如何處理blob對象,將其顯示在前端頁面呢?

        代碼如下:

        createMiniQrcode (blob) {
          let img = document.createElement('img')
          img.onload = function (e) {
            // 元素的onload 事件觸發后將銷毀URL對象, 釋放內存。
            window.URL.revokeObjectURL(img.src)
          }
          // 瀏覽器允許使用URL.createObjectURL()方法,針對 Blob 對象生成一個臨時 URL。
          // 這個 URL 以blob://開頭,表明對應一個 Blob 對象。
          img.src = window.URL.createObjectURL(blob)
          document.querySelector('.imgQrCode').appendChild(img)
        }

        是不是以為就這樣結束了? No, No, No. 了解如何解決問題還不夠,還需要透過表象進行發散思考。

        二、 發散思考

        一般來說,圖片在后端的存儲方式分為兩種:
        其一:可以將圖片以獨立文件的形式存儲在服務器的指定文件夾中,再將路徑存入數據庫字段中;
        其二:將圖片轉換成二進制流,直接存儲到數據庫的 Image 類型字段中.
        
        
        

        對于第一種存儲方式,我們前端直接將存儲路徑賦值給src屬性即可輕松顯示。

        對于第二種存儲方式,我們前端需要將其二進制流交由blob對象處理,然后通過blob的API生成臨時URL賦值給src屬性來顯示。

        兩種存儲方式都有對應的解決方案,似乎已經完美解決了關于圖片顯示的問題。但是,我們的業務場景是多樣且多變的。有時候我們也會遇到這樣的場景,比如圖片拖拽上傳插件后,自動返回給你了Blob對象,但不幸的是,你發現你又用了一個第三方的服務接口只接收 base64 格式的數據,是否有點欲哭無淚?

        那么,圖片的三種表現形式url、base64、blob,三者之間是否可以轉化以滿足需求呢?

        流程圖

        1. url 轉 base64

        url to base64 的方法封裝

        // 原理: 利用canvas.toDataURL的API轉化成base64
        
        urlToBase64(url) {
          return new Promise ((resolve,reject) => {
              let image = new Image();
              image.onload = function() {
                let canvas = document.createElement('canvas');
                canvas.width = this.naturalWidth;
                canvas.height = this.naturalHeight;
                // 將圖片插入畫布并開始繪制
                canvas.getContext('2d').drawImage(image, 0, 0);
                // result
                let result = canvas.toDataURL('image/png')
                resolve(result);
              };
              // CORS 策略,會存在跨域問題https://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
              image.setAttribute("crossOrigin",'Anonymous');
              image.src = url;
              // 圖片加載失敗的錯誤處理
              image.onerror = () => {
                reject(new Error('圖片流異常'));
            };
        }

        你可以這樣調用:

        let imgUrL = `http://XXX.jpg`
        
        this.getDataUri(imgUrL).then(res => {
          // 轉化后的base64圖片地址
          console.log('base64', res)
        })

        2. base64 轉 blob

        base64 to blob 的方法封裝

        // 原理:利用URL.createObjectURL為blob對象創建臨時的URL
        
        base64ToBlob ({b64data = '', contentType = '', sliceSize = 512} = {}) {
            return new Promise((resolve, reject) => {
              // 使用 atob() 方法將數據解碼
              let byteCharacters = atob(b64data);
              let byteArrays = [];
              for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
                let slice = byteCharacters.slice(offset, offset + sliceSize);
                let byteNumbers = [];
                for (let i = 0; i < slice.length; i++) {
                    byteNumbers.push(slice.charCodeAt(i));
                }
                // 8 位無符號整數值的類型化數組。內容將初始化為 0。
                // 如果無法分配請求數目的字節,則將引發異常。
                byteArrays.push(new Uint8Array(byteNumbers));
              }
              let result = new Blob(byteArrays, {
                type: contentType
              })
              result = Object.assign(result,{
                // jartto: 這里一定要處理一下 URL.createObjectURL
                preview: URL.createObjectURL(result),
                name: `圖片示例.png`
              });
              resolve(result)
            })
         }

        你可以這樣調用:

        let base64 = base64.split(',')[1]
        
        this.base64ToBlob({b64data: base64, contentType: 'image/png'}).then(res => {
            // 轉后后的blob對象
            console.log('blob', res)
        })
        
        
        

        3. blob 轉 base64

        blob to base64 的方法封裝

        // 原理:利用fileReader的readAsDataURL,將blob轉為base64
        
        blobToBase64(blob) {
            return new Promise((resolve, reject) => {
              const fileReader = new FileReader();
              fileReader.onload = (e) => {
                resolve(e.target.result);
              };
              // readAsDataURL
              fileReader.readAsDataURL(blob);
              fileReader.onerror = () => {
                reject(new Error('文件流異常'));
              };
            });
        }

        你可以這樣調用:

        this.blobToBase64(blob).then(res => {
            // 轉化后的base64
            console.log('base64', res)
        })

        ps: 以上方法是針對玩轉圖片流的優化,感謝原作者。

        在這里貼出url轉base64, base64與blob的相互轉化的demo,其它的會更新在這里,有興趣可以戳一下這里

        三、圖片處理方式的歸納

        1. 后端的圖片的存儲方式

        在前面我們提到過,圖片在后端的存儲有兩種方式,我們回顧一下:其一:可以將圖片以獨立文件的形式存儲在服務器的指定文件夾中,再將路徑存入數據庫字段中;其二:將圖片轉換成二進制流,直接存儲到數據庫的 Image 類型字段中;

        那么這兩種存儲方式,哪種更優呢?

        據我了解,在互聯網環境中,大訪問量,數據庫速度和性能方面很重要。一般在數據庫存儲圖片的做法比較少,更多的是將圖片路徑存儲在數據庫中,展示圖片的時候只需要連接磁盤路徑把圖片載入進來即可。因為圖片是屬于大字段。一張圖片可能1m到幾m。這樣的大字段數據會加重數據庫的負擔,拖慢數據庫。在大并發訪問的情況下很重要。這是一個經驗。去看看dba對數據庫性能調優方面的分析都能得到這個答案的:就是圖片不要存儲在數據庫中。

        因此,如果你司的后端小哥哥經常將圖片以二進制的形式存儲到數據庫然后返回給你對接,你應該知道如何去dui他了吧(滑稽臉)。

        更多關于圖片或者文件在數據庫的存儲方式的歸納請戳這里

        2. 前端的圖片的顯示方式

        對于前端來說:
        圖片在前端顯示有三種方式:url、base64、blob

        三種顯示方式,哪種更優雅呢?

        url: 一般來說,圖片的顯示還是建議使用url的方式比較好。如果后端傳過來的字段是圖片路徑的話。

        base64:如果圖片較大,圖片的色彩層次比較豐富,則不適合使用這種方式,因為其Base64編碼后的字符串非常大,會明顯增大HTML頁面,影響加載速度。
        如果圖片像loading或者表格線這樣的,大小極小,但又占據了一次HTTP請求,而很多地方都會使用。則非常適用“base64:URL圖片”技術進行優化了!詳細的張鑫旭的Demo演示,請戳這里一下。

        blob: 當后端返回特定的圖片二進制流的時候,就像我第一part里的情景再現說的,前端用blob容器接收。圖片用blob展示會比較好。

        四、感想

        付出,記錄,總結。在項目中遇到的問題我都會一點一滴的記錄整理下來。我相信,這些都是一片一片散落的枝葉,隨著項目經驗的增多,這些枝葉最終一定能夠成長為一棵參天大樹。

        文中的觀點受限于本人當前的技術水平,難免會有講錯的地方,歡迎評論區留言交流指正。

        隨著技術水平的提升,文章會不定期的迭代而優化~你可以通過下面的方式聯系到我。

        關于我

        微信公眾號二維碼

        文章回顧:

        參考資料:

        查看原文

        贊 93 收藏 69 評論 1

        守候 評論了文章 · 2019-01-08

        vue插件開發練習--實用彈窗

        1.前言

        上回說了組件(vue組件開發練習--焦點圖切換)的一個練習項目,這次換下口味,說下vue的插件練手的項目。相對于現在之前的焦點圖切換的組件,這個可能就更簡單了,基本就是熟悉下插件開發的步驟就可以了!這個項目,我更建議大家動手練習了,這個彈窗比之前的焦點圖更加的實用性,也更常用。同時也能讓大家熟悉下vue的插件開發的流程。代碼同樣,我會上傳到github(ec-dialog),需要的可以直接去看代碼!

        建議
        1.下面的步驟,最好在自己本地上跑起來,根據文章的步驟,逐步完成,如果只看代碼,很容易懵逼的。
        2.如果不清楚哪個代碼有什么作用,可能自己調試下,把代碼去掉后,看下有什么影響,就很容易想出代碼有什么作用了!

        2.項目目錄

        clipboard.png

        還是一個很簡單的目錄,各個目錄不知道有什么用的,可以移步去看我上一篇文章。和組件開發的目錄相比,區別就在于src/js/components這個文件夾上。

        3.開發過程

        3-1.把項目跑起來

        首先,先弄src/js/components/alert這個組件。還是一樣,,先在src/js/components/alert/src/main.vue。輸出‘守候’。代碼如下

        <template>
            <transition name="ec">
                <div class="ec">
                    守候
                </div>
            </transition>
        </template>
        <script>
            export default {
                data () {
                    return {
                        name: 'ec-alert',
                    }
                },
                computed: {},
                mounted () {
                },
                methods: {
                }
            }
        </script>
        

        然后來到'alert/index.js'。這個術語叫什么什么文件,我不太清楚,暫時就叫,插件配置文件吧!代碼如下(注意看注釋)

        import Vue from 'vue'
        import AlertComponent from './src/main.vue'
        //合并對象函數,這個方法是會改變,第一個參數的值的
        function merge(target) {
            for (let i = 1, j = arguments.length; i < j; i++) {
                let source = arguments[i] || {};
                for (let prop in source) {
                    if (source.hasOwnProperty(prop)) {
                        let value = source[prop];
                        if (value !== undefined) {
                            target[prop] = value;
                        }
                    }
                }
            }
            return target;
        };
        let instance;
        //extend 是構造一個組件的語法器.傳入參數,返回一個組件
        let AlertConstructor = Vue.extend(AlertComponent);
        
        let initInstance = ()=>{
            //實例化ConfirmConstructor組件
            instance = new AlertConstructor({
                el: document.createElement('div')
            });
            //添加到boby
            document.body.appendChild(instance.$el);
        }
        
        let Alert = (options={}) => {
            //初始化
            initInstance();
            // 將單個 confirm instance 的配置合并到默認值(instance.$data,就是main.vue里面的data)中
            merge(instance.$data, options);
            //返回Promise
            return new Promise((resolve, reject)=>{
                instance.show = true;
                let success = instance.success;
                let cancel = instance.cancel;
                instance.success = () => {
                    //先執行instance.success(main.vue里面的success函數)
                    success();
                    //再執行自定義函數
                    resolve('ok');
                }
            });
        
        }
        export default Alert;

        然后來到components/js/index.js這個文件,配置組件和API,代碼如下

        import alert from './alert/index.js'
        
        const install = function(Vue) {
            //注冊全局組件
            Vue.component(alert.name, alert)
            //添加全局API
            Vue.prototype.$alert = alert
        }
        export default install

        然后在模板文件,index.html里面設置一個div,方便掛載測試

        <!DOCTYPE html>
        <html lang="en">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <body>
        <div id="app">
        </div>
        </body>
        </html>  

        然后在入口文件index.js里面,使用插件

        require("./index.html");
        //引入sass
        require("./src/sass/com.scss");
        import Vue from 'vue'
        import dialog from './src/js/components/index';
        Vue.use(dialog)
        let App = new Vue({
            el: '#app',
            data(){
                return {
                    'name': 'index'
                }
            },
            mounted(){
                this.$alert();
            }
        });

        然后,命令行 $ npm run dev,結果完美

        clipboard.png

        3-2.樣式修改

        完成了上一步,這個插件的一大半就算完成了!剩下的工作主要開發的就是在components/../main.vue這文件開發。
        首先,先別急寫代碼,想一下,一個彈窗大概需要什么字段。

        clipboard.png

        參考上面,發現有一個標題,一個內容,一個按鈕文字。最后還需要一個變量,控制彈窗是否顯示。然后一個點擊按鈕的操作函數。然后還有樣式,大概如下

        clipboard.png

        樣式這個不多說,其他的字段,一個蘿卜一個坑的填進去就好,代碼如下

        <template>
            <transition name="ec">
                <div v-if="show" class="ec">
                    <div class="ec-box">
                        <div class="ec-box-inner">
                            <div class="ec-title" v-if="title">{{title}}</div>
                            <div class="ec-content">{{content}}</div>
                        </div>
                        <div class="ec-box-buttons">
                            <span class="ec-btn-success" @click="success">{{submitText}}</span>
                        </div>
                    </div>
                </div>
            </transition>
        </template>
        <script>
            export default {
                data () {
                    return {
                        name:'ec-alert',
                        show: false,
                        title: '提示',
                        content: '',
                        submitText: '確定',
                        cancelText: '取消'
                    }
                },
                computed: {},
                mounted () {
                },
                methods: {
                    success () {
                        this.show = false;
                    }
                }
            }
        </script>
        <style lang="scss" scoped>
        
            .ec {
                background: rgba(00, 00, 00, .5);
                position: fixed;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                .ec-box {
                    width: 80%;
                    max-width: 400px;
                    top: 200px;
                    position: absolute;
                    left: 0;
                    right: 0;
                    margin: auto;
                    background: #fff;
                    box-sizing: border-box;
                    padding: 20px;
                    border-radius: 6px;
        
                }
                .ec-title {
                    padding-left: 0;
                    margin-bottom: 0;
                    font-size: 16px;
                    font-weight: 700;
                    height: 18px;
                    color: #333;
                }
                .ec-content {
                    padding: 14px 0;
                    line-height: 24px;
                    color: #48576a;
                    font-size: 14px;
                }
                .ec-box-buttons {
                    text-align: right;
                }
                .ec-btn-success {
                    background: #20a0ff;
                    border-color: #20a0ff;
                    display: inline-block;
                    line-height: 1;
                    white-space: nowrap;
                    cursor: pointer;
                    color: #fff;
                    margin: 0;
                    padding: 10px 15px;
                    border-radius: 4px;
                }
                .ec-btn-cancel {
                    display: inline-block;
                    line-height: 1;
                    white-space: nowrap;
                    cursor: pointer;
                    background: #fff;
                    border: 1px solid #c4c4c4;
                    color: #1f2d3d;
                    margin: 0;
                    padding: 10px 15px;
                    border-radius: 4px;
                }
            }
            .ec-enter {
                opacity: 0;
                .ec-box {
                    transform:scale(0);
                }
            }
        
            .ec-enter-active {
                transition: opacity .4s;
                .ec-box {
                    transition: transform .4s;
                }
            }
            .ec-leave-active{
                transition: opacity .2s;
                .ec-box {
                    transition: transform .2s;
                }
            }
            .ec-leave-active {
                opacity: 0;
            }
        </style>
        

        運行效果

        clipboard.png

        3-3.使用插件

        大家知道,在前面步驟,'alert/index.js'這里就已經返回的一個Promise。所以,用法就是像Promise那樣使用!

        clipboard.png

        所以在入口文件,index.js里面直接寫

        mounted(){
            this.$alert({
                title:'提示2',
                content:'這里是提示內容2'
            }).then(()=>{
                this.name='守候'
                alert(this.name)
            })
        }
        

        運行效果

        clipboard.png

        4.其它彈窗

        還是那句話,程序員不會滿足于現狀,只有一種彈窗,怎么夠,下面我再增加一種,和上面那個基本一樣,就是多了一個取消按鈕而已。
        這里我就再講一個簡單的栗子,至于彈窗的樣式,太多了,我在這里就不展開說了,大家需要的可進行拓展。

        首先,創建這個目錄(可以直接把alert那個目錄拷貝過來,然后再修改幾下就完事了)

        clipboard.png

        然后,針對comfirm/src/main.vue文件,添加下面的代碼(下面的代碼基本就是從alert/src/main.vue拷貝過來的,就是增加一個取消按鈕的對應一個字段和操作函數)

        <template>
            <transition name="ec">
                <div v-if="show" class="ec">
                    <div class="ec-box">
                        <div class="ec-box-inner">
                            <div class="ec-title" v-if="title">{{title}}</div>
                            <div class="ec-content">{{content}}</div>
                        </div>
                        <div class="ec-box-buttons">
                            <span class="ec-btn-success" @click="success">{{submitText}}</span>
                            <span class="ec-btn-cancel" @click="cancel">{{cancelText}}</span>
                        </div>
                    </div>
                </div>
            </transition>
        </template>
        <script>
            export default {
                data () {
                    return {
                        name:'ec-comfirm',
                        show: false,
                        title: '提示',
                        content: '',
                        submitText: '確定',
                        cancelText: '取消'
                    }
                },
                computed: {},
                mounted () {
                },
                methods: {
                    success () {
                        this.show = false;
                    },
                    cancel () {
                        this.show = false;
                    }
                }
            }
        </script>
        <style lang="scss" scoped>
            .ec {
                background: rgba(00, 00, 00, .5);
                position: fixed;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                z-index: 9999;
                .ec-box {
                    width: 80%;
                    max-width: 400px;
                    top: 200px;
                    position: absolute;
                    left: 0;
                    right: 0;
                    margin: auto;
                    background: #fff;
                    box-sizing: border-box;
                    padding: 20px;
                    border-radius: 6px;
        
                }
                .ec-title {
                    padding-left: 0;
                    margin-bottom: 0;
                    font-size: 16px;
                    font-weight: 700;
                    height: 18px;
                    color: #333;
                }
                .ec-content {
                    padding: 14px 0;
                    line-height: 24px;
                    color: #48576a;
                    font-size: 14px;
                }
                .ec-box-buttons {
                    text-align: right;
                }
                .ec-btn-success {
                    background: #20a0ff;
                    border-color: #20a0ff;
                    display: inline-block;
                    line-height: 1;
                    white-space: nowrap;
                    cursor: pointer;
                    color: #fff;
                    margin: 0;
                    padding: 10px 15px;
                    border-radius: 4px;
                }
                .ec-btn-cancel {
                    display: inline-block;
                    line-height: 1;
                    white-space: nowrap;
                    cursor: pointer;
                    background: #fff;
                    border: 1px solid #c4c4c4;
                    color: #1f2d3d;
                    margin: 0;
                    padding: 10px 15px;
                    border-radius: 4px;
                }
            }
            .ec-enter {
                opacity: 0;
                .ec-box {
                    transform:scale(0);
                }
            }
        
            .ec-enter-active {
                transition: opacity .4s;
                .ec-box {
                    transition: transform .4s;
                }
            }
            .ec-leave-active{
                transition: opacity .2s;
                .ec-box {
                    transition: transform .2s;
                }
            }
            .ec-leave-active {
                opacity: 0;
            }
        </style>
        

        然后就是comfirm/index.js(也是基本拷貝的,我就截圖,告訴大家改哪里吧,這個得稍微細看才知道改哪里)

        clipboard.png

        clipboard.png

        然后components/index.js

        import comfirm from './comfirm/index.js'
        import alert from './alert/index.js'
        
        const install = function(Vue) {
            //注冊全局組件
            Vue.component(comfirm.name, comfirm)
            Vue.component(alert.name, alert)
            //添加全局API
            Vue.prototype.$confirm = comfirm
            Vue.prototype.$alert = alert
        }
        export default install

        最后在入口文件,index.js使用

        require("./index.html");
        //引入sass
        require("./src/sass/com.scss");
        import Vue from 'vue'
        import dialog from './src/js/components/index';
        Vue.use(dialog)
        let App = new Vue({
            el: '#app',
            data(){
                return {
                    'name': 'index'
                }
            },
            mounted(){
                this.$confirm({
                    title:'提示',
                    content:'這里是提示內容',
                    submitText:'提交',
                    cancelText:'返回'
                }).then(()=>{
                    this.$alert({
                        title:'提示2',
                        content:'這里是提示內容2'
                    }).then(()=>{
                        this.name='守候'
                        alert(this.name)
                    })
                }).catch((err)=>{
                    alert(err)
                })
            }
        }); 
        

        運行結果,就是這樣

        clipboard.png

        5.小結

        一個簡單的彈窗就到這里了,很簡單,但是在我開發那里還是能用,能暫時滿足。但是這個肯定是需要維護的,畢竟很多的項目都需要彈窗。大家也根據自己的需要進行拓展!以上的案例也很簡單,容易懂?;径际怯浟鞒?。但是這個我很建議大家邊動手,邊看文章。這個可以讓自己練習下基于vue開發插件,是一個不錯的練習,希望能幫到大家學習到新的知識!最后,如果覺得文章那里寫的不好或者寫錯了,歡迎指出!

        -------------------------華麗的分割線--------------------
        想了解更多,關注關注我的微信公眾號:守候書閣

        clipboard.png

        查看原文

        守候 評論了文章 · 2019-01-06

        培訓機構讓Github的含金量降低了?

        所有人都想成功,都想有高薪的工作,但是太多的人想一步登天,只有少數人愿意腳踏實地。

        1.前言

        這篇文章完全是有感而發,看了掘金的沸點了,又看到了知乎的一篇文章,就打算寫下,說下自己的個人看法。

        Github ,在程序員這個行業, 即使自己不活躍,都會有聽說過的一個開源社區。近年來,越來越多的人擁抱開源,使得 Github 又火了一把。也有很多人把自己的得意之作放到上面去,畢竟自己有作品放在上面,相當于告訴別人自己有能力做什么樣的項目,技術水平到了什么地步,有沒有跟進技術的趨勢等,可以說是程序員簡歷里面,含金量最高的一個‘附件’。但是在最近就感覺,這個附件也有點失效了。

        2.現狀

        先來引用掘金沸點的幾張圖片

        圖片描述

        圖片描述

        (圖片出自掘金的沸點:https://juejin.im/pin/5b57dea...

        看到上圖,沒猜錯就是全班同學做的班級作業,然后每一個人都上傳到 Github 上面 。給人的感覺要么就是集體刷 star ,以打造自己簡歷的含金量;要么就是把 Github 當網盤用了,做好的東西就直接放里面保存。

        再來一篇文章

        中國內地 GitHub 造假呈指數級增長,其背后是……

        文章的其中一張圖片

        圖片描述

        這個還沒有得到證實,當時看到挺震驚的,居然有人把 Github 造假職業化了。但后來也就習慣了,畢竟利益的驅動不小??!

        現在的狀況,除了簡歷,還會在 Github 上面造假,不知道再過一段時間,是不是輪到了 Stackoverflow , npm ,yarn 也有造假了!

        3.關于培訓機構

        出現這個的原因,應該會有很多人都覺得是培訓機構在搞事情了,包括我自己。因為自己了解的,除了培訓班,很少會出現這樣的情況。

        不得不說,培訓機構挺與時俱進的,在不同的時代,都懂得從各個方面增加企業的篩選簡歷,挑選人才的溝通成本,時間成本。

        在簡歷上面,剛開始時候,教學員在簡歷上寫各種精通。達到脫穎而出的目的。后來挑簡歷的人反感‘精通’了,就沒有人敢寫了,就自帶經驗:工作經驗的公司是假的,項目經驗是學習的demo,有些甚至連學歷也造假?,F在,很多公司看重博客,Github,培訓機構也開始搞這個,這很有可能使得 Github 的作品質量大大的下降,影響整個社區。

        在技術上面,剛開始 IOS 火的時候,就各種培訓 IOS,導致 IOS 爛大街。然后前端出現了模塊化,工程化,開發大轉變的時候,又搞前端,導致前端爛大街?,F在聽公司的同事說,Java 也有爛大街的趨勢。至于安卓,php,python等就不太了解情況了。

        現在比較活的人工智能,大數據,區塊鏈,在搜索引擎一搜,前幾條基本都是培訓的廣告。

        如果是是真材實料,恐怕沒這么多人反對。但是現在就是很多培訓機構過分夸張的宣傳,甚至是一些觸及道德底線的欺騙性的宣傳,忽悠別人就去報名,這個就很反感了。

        培訓機構的反思

        1.培訓機構應該注重教學員技術,而不是教學員怎么撒謊,造假,包裝簡歷。技術好才受人待見,一味的造假只會遭人唾棄。

        2.培訓機構的宣傳應該注意,不能過分的夸張,零基礎,沒接觸過計算機的人,有幾個能通過短短幾個月的學習就達到月薪上萬,擁有兩三年經驗的實力?

        培訓機構在業界口碑如何?

        首先最明了一個,就是絕大部分的培訓機構教學員寫簡歷的時候,不會讓學員把培訓機構的經歷寫到簡歷上面去。這個想象就說明了業界不認可培訓機構。什么時候認可培訓機構?可能就是要等到培訓機構敢讓學員把培訓經歷寫到簡歷上的時候吧。

        然后再看一下知乎的問答:

        為什么很多IT公司不喜歡進過培訓機構的人呢?

        為什么一些公司招前端不想要培訓班出來的人?

        為什么公司不愿意招接受過培訓的程序員?本人是某智培訓機構出來的………?

        為什么有些公司會封殺 IT 培訓機構出來的?

        很多培訓出來的人會認為大家帶著有眼眼鏡看他,但是為什么不想下為什么別人會戴有色眼鏡看他?嫉妒他比自己多了一個培訓經歷嗎?顯示不是。而是現在很多人都已經有以下的等價關系了:培訓的人=水貨=造假。雖然這有點極端,但是也可以理解。從我身邊的HR朋友來說,遇到的簡歷造假,技術與工作經驗不匹配的,都是培訓機構出來的,一切就是這么巧。久而久之就成了一個‘培訓’就成了一個負面的詞語。

        之前我也發過一篇文章,下面的評論,有人說造假是為了找工作,不得已而為之。這個我實在很難想通,因為很多人像我一樣,都是沒造假,沒工作經驗,從實習做起,但是能找到工作。從低薪做起,也是面試了十幾家,投了上百份簡歷才找到工作,為什么就會有不得已而為之的造假?是自己學得不好,還是社會復雜,要造假才能有工作?是找不到工作,還是找不到自己滿意的高薪工作,要造假才能找到?

        業界對培訓機構,沒猜錯的話大多數是不認可的。但值得慶幸的就是,還沒有到絕望的地步,仍然有培訓機構口碑比較好,仍有培訓出來的人是靠譜的,只是比例有點小而已。業界普遍的不認可,也導致了少數好的培訓機構,培訓出來但靠譜的人,也被貼上不靠譜的標簽,這有點不公平。業界本不反感培訓機構,反感教學員造假的培訓機構。也不是不想要培訓機構出來的人,而是不想要培訓機構出來的造假的人。

        4.關于 Github

        對于我自己而言,Github 是一個學習地方,因為有很多的可以作為學習的 demo ,優秀項目的源碼在里面都可以看到。最近一年,我也自己提了一些常用的函數庫,一些小組件提交上去。如果是 demo 我也是放一個集合,一些學習的實例源碼。雖然我自己的項目也是很爛,但也不會像沸點那張圖一般,把自己的仿站項目,作為班級作業的小游戲等一些沒什么參考性的項目也放上去,把 Github 當網盤用。

        對于企業而言,Github 是面試官進一步了解面試者的一個渠道之一。也有不少的企業會在這個社區上面找項目,找人才等。如果繼續就這樣的搞下去,在 Github 那里造假,面試官就會覺得這個開源社區的參考性就越低,企業也覺得這個開源社區上面的作品質量和作者的水平有一定質疑性,然后大家就逐漸的喪失了看 Github 的耐心,這樣很有可能會使得這個社區上面優秀的作者和作品都會被埋沒,甚至可能影響整個社區。

        在 Github 上面上傳什么項目目前沒什么強制要求,把自己的仿站項目,班級練習這個,也沒有所謂的錯誤,也能理解其中的做法。但是個人比較反感的就是在 Github 上面造假,為了提高含金量,在社區上面串通刷 star ,fork,代刷博客,惡意合代碼等行為。這樣的行為和簡歷造假同樣惡劣。

        活躍 Github 的開發者相信經常聽到 ‘求star’,‘刷star’這兩個字眼,但是這兩個不是一回事。求 star :是自己開發好了一個項目,希望得到大家的支持,使用從而在各個地方宣傳,希望別人 star 。刷 star :是一般是有利益關系的,比如找幾個人,或者一些專門團隊對特定的項目進行 star ,就像淘寶刷單一般。

        5.小結

        我自己算是 Github 的一個中度使用者吧。偶然看到知乎上面的一篇文章有感而發。 關于Github,希望還是以前那個 Github 。練手的 demo 和優秀的作品,還能在里面找到,而不是在里面找到一堆仿站項目,班級作業等。關于培訓機構,希望教學員技術就好,不要教他們包裝簡歷,造假等。機構真實,學員務實。一切還是少一點套路,多一點真誠。

        -----------華麗的分割線------------

        想進一步交流,請加我微信,或者關注公眾號:守候書閣

        圖片描述

        我的博客即將搬運同步至騰訊云+社區,邀請大家一同入駐:https://cloud.tencent.com/dev...

        查看原文

        守候 關注了用戶 · 2018-12-29

        SHERlocked93 @sherlocked93

        來自南京的前端打字員,掘金優秀作者,慕課暢銷專欄 <JavaScript 設計模式精講> 作者,原創同步更新于 Github 個人博客 (求 star?? )

        公眾號 前端下午茶,歡迎關注 ?? ,分享前端相關的技術博客、精選文章,期待在這里和大家一起進步 ~

        關注 401

        守候 發布了文章 · 2018-11-26

        [盤點]項目中可以怎么優化圖片

        看似平常的事物,往往會蘊含的巨大的智慧。把看似平常的事物簡單做好,可能很正常。如果能把平常的事物做精,做細,這個不平常。

        1.前言

        每一個開發者在開發項目中,不可避免要和圖片打交道,優化圖片似乎也成了一個必修課。圖片優化也不僅僅是性能上的優化,還要進行體驗上的優化。至于怎么優化圖片,沒有固定的方式,只能具體場景,具體分析,選擇合適的方案。不多說,下面也簡單介紹下自己處理過,了解過的一些方式。如果大家有補充,建議。歡迎在評論區留言,交流學習下。

        2.概念用法

        ‘概念用法’這個詞是自己亂起的,可能不太準確,是因為詞窮了,不知道怎樣形容??偟脕碚f,這部分介紹的處理方式,就是講一下就知道怎么用的方式,不需要怎么放代碼,運行圖等。只需要籠統的介紹一下,大家都會懂的一些方式。

        2-1.圖片壓縮

        這個沒有隱含的意思,就是把圖片的大小進行壓縮。目前自己用的比較多的兩個壓縮網站是TinyPng智圖。使用比較方便,品質也基本保持一致。

        2-2.base64代替小圖標

        一些比較小的圖標,使用 base64 編碼代替可以減少 http 請求。但是有一個缺點就是轉成 base64 后,編碼會比原圖更大,圖片越大,差別就越大。1K左右的圖標,轉碼出來的 base64 大概是 1.1K-2K。如果是 8K 的圖片,轉碼出來的 base64 可能超過10K。就自己項目開發而言,只有小于 4K 的圖標,才會進行轉碼。

        2-3.icon-font代替圖標

        由于 icon-font 看著是圖片,實際上是字體。

        優點:就是在于可以矢量縮放,大小圖標都可以使用,也可以改變顏色,使用也不麻煩。

        缺點:需要引入的文件不少(.svg,.ttf,.woff,.eot )。文件大小也比較大。建議是項目的圖標要達到一定量才使用 icon-font,如果是幾個圖標,還是用圖片吧。如果需要引入的圖標多,就建議使用 icon-font。

        上面說的 icon-font 由于是字體,所以不支持多色圖標。有了解到,現在 icon-font 可以支持多色圖標了(symbol引用)。只是兼容性不好。

        2-4.雪碧圖

        雪碧圖就是把很多小的圖整合到一起,制作成一張比較大的圖,然后作為元素的背景圖片使用,定位到相應的圖片即可。

        優點:減少了大量的 http 請求。

        缺點:背景定位和在移動端適配大小有點麻煩。

        除此之外,使用雪碧圖,有兩個個注意地方

        1.不要把頁面所有的圖片都合并,比如把 logo 整合會破壞 html 的語義結構。圖像復雜的 banner 也不要合并

        2.盡量只把顏色相近的圖標整合在一張圖片上,如果圖片顏色相差太大,合并出來的圖片可能會很大。

        2-5.響應式圖片

        比如頁面上有一張尺寸是 100*100 的圖片,但是圖片的實際尺寸是 1000*1000 的。這樣的情況建議在多準備一張 100*100 的圖片。不然可能會造成資源浪費。

        2-6.混合模式代替變色的圖標

        如下例子,比如頁面有這個圖標

        圖片描述

        在特定情況下會是下面這個顏色。

        圖片描述

        同一個圖標,在不同的時候是不同的顏色。icon-font 可以通過改變 color 實現?;蛘哂脙蓮垐D片。除了這兩個方法,用 CSS3 的混合模式,一樣可以實現。兩行代碼搞定。

        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8">
                <title></title>
                <style>
                    div{
                        /*容器必須有背景*/
                        background: #09f;
                        display: inline-block;
                    }
                    img{
                        width: 100px;
                        vertical-align: top;
                    }
                    img:hover{
                        /*設置混合模式*/
                        mix-blend-mode: lighten;
                    }
                </style>
            </head>
            <body>
                <div><img data-original="images/icon-good.jpg" class="u-icon"/></div>
                <div><img data-original="images/icon-good.png" class="u-icon"/></div>
            </body>
        </html>

        運行效果

        圖片描述

        展示完 mix-blend-mode,順便提下 background-blend-mode 。用法基本一致,只是 mix-blend-mode 作用于 html 元素的混合模式,background-blend-mode 作用于元素背景的混合模式。

        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8">
                <title></title>
                <style>
                    div{
                        display: inline-block;
                        width: 100px;
                        height: 100px;
                        /*設置背景*/
                        background: url(images/icon-good.jpg) no-repeat center,#09f;
                        background-size:100%;
                        /*設置背景混合模式*/
                        background-blend-mode: lighten;
                    }
                    
                </style>
            </head>
            <body>
                <div></div>
            </body>
        </html>

        圖片描述

        注意事項

        1.圖片必須是白底純色圖標

        2.現代的瀏覽器,支持這個屬性的瀏覽器

        如果圖片是透明純色背景,得到的結果會是這樣

        圖片描述

        受限篇幅影響,混合模式暫時就介紹這么多,以后發現好玩的再寫文章。有興趣可以看下面的參考資料。

        兩行 CSS 代碼實現圖片任意顏色賦色技術

        不可思議的顏色混合模式 mix-blend-mode

        不可思議的混合模式 background-blend-mode

        2-7.簡單圖標使用 CSS 畫

        有一些簡單的圖標,可以使用 CSS 代替。比如下面這些

        自己而言,項目上畫的最多的就是各種箭頭

        圖片描述

        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8">
                <title></title>
                <style>
                    .icon-arrow-bottom { 
                        width: 0; 
                        height: 0; 
                        border: 100px solid #000; 
                        border-color: #000 transparent transparent transparent; 
                    }
                    .icon-arrow-top { 
                        width: 0; 
                        height: 0; 
                        border: 100px solid #000; 
                        border-color: transparent transparent #000 transparent; 
                    }
                </style>
            </head>
            <body>
                <div class="icon-arrow-bottom"></div>
                <div class="icon-arrow-top"</div>
            </body>
        </html>

        優點:矢量縮放,顏色可變,不需要發送請求

        缺點:只適合用簡單圖形,1-5行 CSS 代碼可以搞定的才建議用,超過的不建議。想得痛苦,寫也麻煩,花時間也多,效果未必比其它方案好。建議還是圖片 base64,或者 icon-font。

        這里就簡單舉個例子,需要知道 css3 還可以畫什么圖形??磪⒖假Y料。

        純CSS制作的圖形效果

        奇妙的 CSS shapes(CSS圖形)

        【CSS】用CSS繪制圖標(圖標大全)

        3.隱式預加載

        1.從這里開始。下面的demo,有些會用到 ecDo 這個庫(自己寫的一個常用函數庫,歡迎star)。之前的文章有介紹過,這里就不再重復。大家不知道的時候點開看下相應的 API ,運行下,調試下就好。

        2.為方便展示,下面的demo,除了懶加載,都有在 network 把網速調至了慢速的3G。

        有些項目圖片比較多,如果一次性加載,用戶等待時間會過久,可能會造成體驗效果很差,甚至導致用戶流失,很多網站用到的一個體驗優化方式是隱式預加載。

        等待首屏加載,在用戶看首屏(第一張大圖)的時候,悄悄的加載其它圖片(這里為了展示效果,在項目上其他的小圖片不應在第一屏)。

        圖片描述

        <body>
            <p><img data-original="lawyerOtherImg.jpg"/></p>
            <p>這是預加載的圖片</p>
            <div>
                <img data-data-original="https://materialdb.zhaoyl.com/201809/106796.jpg" class="load-img" width="100" height="100"/><img
                    data-data-original="https://materialdb.zhaoyl.com/201809/105567.jpg" class="load-img" width="100" height="100"/><img
                    data-data-original="https://materialdb.zhaoyl.com/201809/103097.jpg" class="load-img" width="100" height="100"/><img
                    data-data-original="https://materialdb.zhaoyl.com/201809/10205.jpg" class="load-img" width="100" height="100"/><img
                    data-data-original="https://materialdb.zhaoyl.com/201809/001.jpg" class="load-img" width="100" height="100"/>
            </div>
        </body>
        //測試請先清空緩存
        window.onload = function () {
            ecDo.loadImg('load-img', function () {
                console.log('加載完畢')
            });
        }

        注意事項:

        1.大概預測,用戶看首屏的時候,很大概率會往下面看。

        2.該方式,用戶等待的時間比較短。但是圖片超大,要慎重考慮。因為該方式無法保證用戶在瀏覽的時候,能把下一屏(比如瀏覽第一屏的時候,要加載第二屏)的圖片加載完畢,讓用戶無感知。如果切換的下一屏還沒加載完畢,也可能會影響體驗。

        demo:https://github.com/chenhuiYj/...

        4.顯式預加載

        告訴用戶正在加載,等到加載完了再一次性渲染在頁面上。

        圖片描述

        <style>
            div{
                display: none;
            }
        </style>
        <body>
            <p id="p">顯示預加載進行中</p>
            <div id="div">
                <img data-data-original="https://materialdb.zhaoyl.com/201809/106796.jpg" class="load-img" width="100" height="100"/><img
                    data-data-original="https://materialdb.zhaoyl.com/201809/105567.jpg" class="load-img" width="100" height="100"/><img
                    data-data-original="https://materialdb.zhaoyl.com/201809/103097.jpg" class="load-img" width="100" height="100"/><img
                    data-data-original="https://materialdb.zhaoyl.com/201809/10205.jpg" class="load-img" width="100" height="100"/><img
                    data-data-original="https://materialdb.zhaoyl.com/201809/001.jpg" class="load-img" width="100" height="100"/>
            </div>
        </body>
        let oP1=document.getElementById('p');
        let oDiv=document.getElementById('div');
        //測試請先清空緩存
        window.onload = function () {
            ecDo.loadImg('load-img', function () {
                oDiv.style.display='block';
                oP1.style.display='none';
            });
        }

        注意事項:

        1.大概預測,用戶看首屏的時候,很大概率會往下面看。

        2.該方式好處在于加載完畢之后,就所有圖片都加載完畢了,體驗比較好。如果圖片全部過大,加載時間會比較長,loading 的時間也會很長,會影響體驗。

        demo地址:https://github.com/chenhuiYj/...

        5.懶加載

        這個大家應該很熟悉了,簡單點說就是圖片一開始不加載,當用戶瀏覽到什么位置的時候,相應位置得圖片就加載出來。

        圖片描述

        <body>
            <p><img data-data-original="lawyerOtherImg.jpg" class="load-img" width='528' height='304'/></p>
            <p><img data-data-original="lawyerOtherImg.jpg" class="load-img" width='528' height='304'/></p>
            <p><img data-data-original="lawyerOtherImg.jpg" class="load-img" width='528' height='304'/></p>
            <p><img data-data-original="https://materialdb.zhaoyl.com/201809/105567.jpg" class="load-img" width='528' height='304'/></p>
            <p><img data-data-original="https://materialdb.zhaoyl.com/201809/106796.jpg" class="load-img" width='528' height='304'/></p>
            <p><img data-data-original="https://materialdb.zhaoyl.com/201809/103097.jpg" class="load-img" width='528' height='304'/></p>
            <p><img data-data-original="https://materialdb.zhaoyl.com/201809/10205.jpg" class="load-img" width='528' height='304'/></p>
            <p><img data-data-original="https://materialdb.zhaoyl.com/201809/001.jpg" class="load-img" width='528' height='304'/></p>
        </body>
        window.onload = function () {
            //根據load-img 這個 class 遍歷,元素距離頁面底部 100像素的時候就開始加載,加載錯誤就顯示error.jpg
            ecDo.delayFn(ecDo.lazyLoadImg('load-img', 100, 'error.jpg'),100,200);
            window.onscroll = function () {
                ecDo.delayFn(ecDo.lazyLoadImg('load-img', 100, 'error.jpg'),100,200);
            }
        }

        demo:https://github.com/chenhuiYj/...

        6.圖片沒加載出來顯示默認圖片

        這個例子,當網速比較慢的時候,想要加載的圖片沒有馬上出來?;蛘邎D片路徑錯誤,這個時候頁面可能會出現一部分空白的地方,或者頁面布局會出現錯亂,比較常用的做法是先顯示一張 loading 圖或者是 logo 圖。告訴用戶,這里是圖片,正在加載,體驗上會好很多,比如下面這個例子。

        圖片描述

        下面也簡單的實現一下。

        比如網站上有這樣的圖片

        <p><img data-original="error.jpg" data-data-original="https://materialdb.zhaoyl.com/201809/105567.jpg" width="264"/></p>
        <p><img data-original="error.jpg" data-data-original="https://materialdb.zhaoyl.com/201809/106796.jpg" width='264'/></p>
        <p><img data-original="error.jpg" data-data-original="https://materialdb.zhaoyl.com/201809/1067961.jpg" width='264'/></p>
        在 network 把網速調至了慢速的3G,以方便調試。
        //測試前請先清空緩存
        window.onload = function () {
            let oImg=document.getElementsByTagName('img');
            for(let i=0;i<oImg.length;i++){
                ecDo.aftLoadImg({
                    dom:oImg[i],
                    url:oImg[i].dataset.src,
                    errorUrl:oImg[i].src
                })
            }
        }

        ![](https://user-gold-cdn.xitu.io/2018/11/19/1672b97cbc84f222?w=621&h=764&f=gif&s=107185)

        可以看到,一開始顯示的是一張默認圖片,等需要加載的圖片,加載完了之后,再加載需要加載的圖片。(最后一張圖片,是故意把路徑寫錯,所以出來的圖片是之前的圖片)

        demo:https://github.com/chenhuiYj/...

        7.小結

        關于項目上,優化圖片的各種方式,自己用過的,聽過的,大概就在這里了。實現方案,也不敢說是最好。如果大家有更好的想法,建議,歡迎在評論區留言。

        -------------------------華麗的分割線--------------------

        想了解更多,和我交流,內推職位,請添加我微信?;蛘哧P注我的微信公眾號:守候書閣

        圖片描述圖片描述

        查看原文

        贊 30 收藏 24 評論 0

        守候 贊了文章 · 2018-11-23

        使用API自動生成工具優化前端工作流

        在工作中,我們的前端工作流一般開始于前后端協商好Api文檔之后,再針對這個Api文檔做mock模擬數據,然后用做好的mock進行開發,后端開發完畢之后再改一下API數據的BaseURL切換到正式API進行聯調;如下

        本文介紹的一個工具(或者說方法),來將這個工作流優化一下,也是我平時工作正在用的方法,當做自己的筆記,也跟大家一起分享一下~

        這個方法的主要思路就是開發人員在某個api工具中按要求填好文檔,然后導出swagger.json配置文件,再把這個配置文件導入到easy-mock中,再用工具自動生成前端api的js文件以供調用。

        本文中所使用的工具:sosoApi、Easy-mock、Swagger、Easy-mock-api-template、axios

        1. 使用Api管理平臺導出swagger.json文件

        一般我們前后端通過各種平臺或者工具來管理Api,比如免費的可視化Api管理平臺 sosoApi、Yapi等,一般來說這些工具都可以生成swagger.json的Api,我們可以用它來直接生成一個漂亮的可視化Api文檔,也可以用它來作為配置文件導入其他工具中,比如Easy-mock;

        比如在sosoApi中就可以導出為swagger文檔(swagger.json):

        我們先導出一個swagger.json備用;

        2. 使用swagger.json導入easy-mock

        Mock平臺我們可以使用Easy-mock,輕量又簡潔,雖然沒有Api的分組功能,但平時應付應付不太大的應用、個人應用等場景足夠了;Easy-mock官網的服務被不少人直接拿到開發環境用,經常被擠爆,這個情況可以用本地部署來解決這個問題,參考 windows本地安裝部署 Easy Mock 。

        我們將Api管理平臺中導出的swagger.json文件在新建project的時候導入:

        這樣剛剛Api平臺中配置的Api就被同步到我們的Easy-mock配置中了,比如sosoApi的示例項目導出的結果就是:

        這時我們就可以用它來進行數據mock了,怎么樣,是不是很輕松~

        easy-mock項目面板上面會有個 Project ID,這個記下來后面要用;

        3. 使用easy-mock-cli生成js格式Api

        有了easy-mock之后一般情況下我們要寫前端的api文件了,一般api工具用axios,這里提供一個封裝:

        // utils/fetch.js
        import axios from 'axios'
         
        const service = axios.create({
          baseURL: 'https://easy-mock.com/project/5bf6a23c92b5d9334494e884',
          timeout: 5000
        })
         
        // request攔截器
        service.interceptors.request.use( config => {...},  err => {...})
         
        // respone攔截器
        service.interceptors.response.use( res => {...},  err => {...})
         
        export default service

        我們可以用easy-mock-cli來生成api,模板文件如果不想用原來的模板的話,可以使用我fork之后改寫的一個模板easy-mock-api-template,生成的Api文件是這樣的:

        // api/index.js
        import fetch from 'utils/fetch';
         
        /* 活動查詢 */
        const activityQuery = ({ activityDate }) => fetch({
          method: 'get',
          url: '/activity/query',
          params: { activityDate }
        });
         
        /** 活動保存 */
        const activitySave = () => fetch({
          method: 'post',
          url: '/activity/save'
        });
         
        /** 活動提交 */
        const activitySubmit = ({ activityId, content }) => fetch({
          method: 'post',
          url: '/activity/submit',
          data: { activityId, content }
        });
         
        export {
          activityQuery,   // 活動查詢
          activitySave,    // 活動保存
          activitySubmit   // 活動提交
        };

        然后在文件中就可以:

        import * as Api from 'api/index.js';
         
        // 調用
        Api.activitySubmit({ activityId: 2 })
            .then(...)

        簡單介紹一下配置文件,更復雜的配置要參考原來的文檔;

        // .easy-mock.js 配置文件
         
        {
          host: 'http://localhost:8080/',        // easy-mock的源,沒有本地部署的話不用寫,本地部署則填本地服務地址
          output: "../",                       // 生成 API 的基礎目錄
          template: "../",                       // 指定模板,這里用本地寫的模板
          projects: [                           // 可以有多個模板來源
            {
              "id": "你要創建的 Easy Mock 項目的 id",    // 剛剛記下來的 Project ID
              "name": "api"                         // 生成的output目錄下的文件名
            }
          ]
        }

        然后

        npm run create-api

        就可以在根目錄下生成一個api/index.js文件了~


        網上的帖子大多深淺不一,甚至有些前后矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~

        參考:

        1. 用swagger.json自動生成axios api訪問代碼 - 簡書
        2. Easy-mock-cli/README.md

        推介閱讀:

        1. windows本地安裝部署 Easy Mock - 掘金

        PS:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~

        另外可以加入「前端下午茶交流群」微信群,長按識別下面二維碼即可加我好友,備注加群,我拉你入群~

        查看原文

        贊 69 收藏 46 評論 4

        認證與成就

        • 獲得 3668 次點贊
        • 獲得 38 枚徽章 獲得 1 枚金徽章, 獲得 8 枚銀徽章, 獲得 29 枚銅徽章

        擅長技能
        編輯

        開源項目 & 著作
        編輯

        • ec-slider

          個人練習的一個項目,基于vue2開發的一個彈窗插件。簡單易用,滿足一般開發

        • ec-do

          封裝實例為日常開發常用的小實例,包括數組去重,打亂數組,字母大小寫轉換,cookie操作的封裝等。

        • ec-css

          基于css3開發的代碼庫,包括日常使用的hover過渡效果,動畫效果,以及預設動畫

        • ec-dialog

          個人練習項目,基于vue的簡單彈窗

        注冊于 2017-06-13
        個人主頁被 36.8k 人瀏覽

        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕