多步驟多分步的組件StepJump

来源:http://www.cnblogs.com/lyzg/archive/2016/04/01/5342894.html
-Advertisement-
Play Games

最近的工作在做一個多步驟多分步的表單頁面,這個多步驟多分步的意思是說這個頁面的業務是分多個步驟完成的,每個步驟可能又分多個小步驟來處理,大步驟之間,以及小步驟之間都是一種順序發生的業務關係。起初以為這種功能很好做,就跟tab頁的實現原理差不多,真做下來才發現,這裡面的相關邏輯還是挺多的(有可能是我沒... ...


最近的工作在做一個多步驟多分步的表單頁面,這個多步驟多分步的意思是說這個頁面的業務是分多個步驟完成的,每個步驟可能又分多個小步驟來處理,大步驟之間,以及小步驟之間都是一種順序發生的業務關係。起初以為這種功能很好做,就跟tab頁的實現原理差不多,真做下來才發現,這裡面的相關邏輯還是挺多的(有可能是我沒想到更好地辦法~),尤其是當這個功能跟表單,還有業務數據的狀態結合起來的時候。我把這個功能相關的一些邏輯抽象成了一個組件StepJump,這個組件能夠實現純靜態的分步切換和跳轉,以及跟業務相結合的複雜邏輯,有一定的通用性和靈活性,本文主要介紹它的功能要求和實現思路。

實現效果:
image

代碼下載,裡面有兩個效果頁面:demo.html和regist.html,相關js分別是demo.js和regist.js,組件封裝在stepJump.js裡面,採用seajs做的模塊化。demo.html演示的是一個純靜態的多步驟多分步的內容切換,regist.html是一個完整地跟業務結合起來的效果,是我從最近的工作中抽出來的,只不過裡面的業務數據狀態是用一個常量(STEP_STATUS)來模擬的。

image

1. 需求分析

前面給的效果圖不完整,但是設計圖太大,不方便貼出。為了把這個頁面的功能要求描述清楚,我只能儘可能地在文字上多花功夫,儘量把每一個細節都講清楚:
1)這個頁面一共有四個大步驟,其中1,3,4都只對應了一個小步驟,而2對應了三個小步驟,也就說1,3,4分別是1步就能完成的,而2需要3步才能完成;
2)這些步驟是順序發生的關係,必須先完成第1大步,才能進行第2大步;必須先完成第1個小步,才能進行第2個小步;
3)每個大步驟的第一個小步可能有一個按鈕能夠返回到上一大步;
4)每個大步驟位於中間的小步可能有2個按鈕,一個返回上一小步,一個跳轉到下一小步;
5)每個大步驟的最後一個小步可能有一個按鈕能夠跳轉到下一大步;
6)如果一個大步驟只包含一個小步驟,那麼它既是第一個小步,也是最後一個小步;
7)每個大步驟的每個小步驟要顯示的內容都是不一樣的,每次只能顯示一個小步驟;
8)已經完成的大步驟,正在進行的大步驟,後面待執行的大步驟,應該具有不同的UI效果;(不過從實現效果來說,已經完成的跟正在執行做成了一個效果)
9)後面待執行的大步驟必須通過上一個大步驟最後一個小步驟裡面的按鈕點擊才能跳轉;已經完成的和正在執行的大步驟可通過點擊步驟名稱跳轉;
10)點擊大步驟名稱時,跳轉至該大步驟的第一個小步。

以上部分是頁面的靜態功能分析,下麵要分析的是該頁面實際的業務需求:
1)這個頁面是開放給登錄用戶使用的,用來給某個平臺做用戶入住申請用的,只有完成這個入住流程,才能正式進入平臺使用其它功能;
2)主要的業務數據都是跟用戶相關的,按入住流程來說,用戶的入住流程狀態可以分為:
a. 待填寫資料,如果每次進入這個頁面時是這個狀態值,那麼就顯示【1 入住須知】這個大步驟,表示正在進行該步驟;
b. 待提交資料,如果每次進入這個頁面時是這個狀態值,那麼就顯示【2 公司信息提交】這個大步驟,小步驟預設顯示它的第一個;
c. 審核未通過,如果每次進入這個頁面時是這個狀態值,那麼就顯示【3 等待審核】這個大步驟;
d. 審核已通過,如果每次進入這個頁面時是這個狀態值,那麼就顯示【3 等待審核】這個大步驟;
e. 待確認合同,如果每次進入這個頁面時是這個狀態值,那麼就顯示【4 合同簽訂】這個大步驟;
3)需要註意的是【3 等待審核】和【4 合同簽訂】各包含3個和2個內容,它們各自的這幾個內容是互斥顯示的關係,但是它們不是分步的關係,具體要顯示哪個完全是由業務狀態決定的。比如說【3 等待審核】有下麵3種可能的效果:
image
image
image 
當從【2 公司信息提交】跳轉到【3 等待審核】顯示第一個效果;
如果進入頁面時是審核已通過的狀態顯示第2個效果;
如果進入頁面時是審核未通過的狀態顯示第3個效果,而且這個情況下,步驟名稱還有特殊效果要求:
image
當直接點擊步驟名稱時,比如點擊【2 公司信息提交】,這個效果得還原成預設的效果;當再點擊【3 等待審核】,又得設置成這種特殊的效果;只有通過【2 公司信息提交】的小步跳轉到【3 等待審核】時,才能完全撤銷這種特殊效果。

大體上的需求就是以上這些部分,可能還有一些細節沒有描述,因為用文字不太容易說清楚,所以只能根據實際效果去體會了。 從最終實現來說,前面的需求中,靜態功能需求才是組件實現的核心,後面的業務需求並不具備通用性,我開發這個組件的出發點是根據靜態功能需求寫出組件的基本功能,然後再結合業務需求設計合理的API跟回調,並且儘可能地將js與html分離,組件與業務分離,以便最終的實現能夠將靈活性最大化。

2. 實現思路

首先說html結構:我考慮在前面的需求中,有2個很重要的概念:大步驟,小步驟,並且這些大步驟跟小步驟有包含關係,步驟之間還有順序的約定,所以得設計兩個集合,分別用來存放所有大步驟相關項和所有小步驟相關項,html結構可以設計如下:

<nav class="nav-step">
    <ul id="steps" class="steps">
        <li><a href="javascript:;">1<span class="gap"></span>入住須知</a></li>
        <li><a href="javascript:;">2<span class="gap"></span>公司信息提交</a></li>
        <li><a href="javascript:;">3<span class="gap"></span>等待審核</a></li>
        <li><a href="javascript:;">4<span class="gap"></span>合同簽訂</a></li>
    </ul>
</nav>
<div id="step-content" class="step-content">
    <div class="step-pane" >
    </div>
    <div class="step-pane">
    </div>
    <div class="step-pane">
    </div>
    <div class="step-pane">
    </div>
    <div class="step-pane">
    </div>
    <div class="step-pane"
    </div>
</div>

其中#steps li 就是所有的大步驟項,所有的#step-content .step-pane就是所有的小步驟項。這兩個集合僅僅解決了步驟項的存放和順序問題,對於它們之間的包含關係還沒有解決。在需求當中,大步驟與小步驟是這樣的包含關係:image
這樣的話,我們只要通過一個簡單的配置數組就能把這種關係體現出來,比如以上這個結構就可以用[1,3,1,1]來說明,表示一共有4個大步驟,其中1,3,4都只有一個小步驟,2有3個小步驟。由於大步驟與小步驟是分開存放在兩個集合裡面的,所以我們在對這兩個集合進行存取的時候,都用的是相對集合的索引位置,但是在實際使用過程中:大步驟的位置是比較好識別的,小步驟的絕對位置就不好識別了,而且相對集合的位置都是從0開始,如果每個小步的內容裡面都有定義其它的一些組件,比如表單相關的組件,我們肯定會把這些組件的實例存放到一個配置表裡:

var STEP_CONFIG = {
    0: {
        form: {
            //....
        }
    },
    1: {
        form: {
            //....
        }
    },
    2: {
        form: {
            //....
        }
    },
    3: {
        form: {
            //....
        }
    }
    //...
}

這種配置表是按小步驟的絕對索引來標識的,在實際使用的時候會很不方便,而且當小步的html結構有調整的時候,這個結構就得改,所有引用到它來獲取相關組件的地方都得改。最好是完成下麵這種配置方式:

var STEP_PANES_CONFIG = {
    //2,1表示第二個步驟的第一個小步內容
    //2,2表示第二個步驟的第二個小步內容
    //2,3表示第二個步驟的第三個小步內容
    2: {
        1: {
            //配置小步驟相關的東西
        },
        2: {
            //配置小步驟相關的東西
        },
        3: {
            //配置小步驟相關的東西
        }
        //配置大步驟相關的東西
    }
}

相當於把前面的包含結構抽象成:
image[10]
這個結構有兩個好處:一是不考慮集合索引從0開始的問題,STEP_PANES_CONFIG[2]就表示第2個大步驟;二是小步驟的索引也不考慮從0開始的問題,而且是相對大步驟來標識的,比如STEP_PANES_CONFIG[2][1]就表示第2個大步驟的第一個小步,這樣的話,大步驟跟小步驟就都能很好的通過索引來識別,配置表也更穩定一點。也就是說組件在對外提供索引相關的介面或參數的時候,都是按常規思維方式提供的,在組件內部得解決邏輯索引(比如[2][1])跟物理索引的轉化關係,以及物理索引跟邏輯索引的轉換關係。比如外部調用的時候,告訴組件初始化需要顯示第2大步的第1個小步,那麼組件就得根據這個信息找到相應的大小步驟項去顯示;外部已知步驟項的物理索引位置時,組件得提供方法能夠將物理索引位置轉換成邏輯索引。

再來說效果:
1)每個步驟的內容只要控制顯示哪個即可,所以步驟內容如果用css來控制狀態的話就只有2種,預設態和active態,預設態不顯示,active態顯示;
2)每個步驟的邊角可以用css邊框畫三角的原理實現;
3)為了正確控制步驟的效果,每個步驟如果用css來控制狀態的話有3種,預設態,done態和current態,分別表示未執行,已執行和正在執行的步驟。另外第三大步還有一個alerts態,不過這是一個跟業務相關的狀態,跟組件倒是沒有關係。這三個狀態的控制實現,跟網上那種評分組件是類似的。

大概的思路就是這些,另外還有關於API和回調的設計,我會在下一部分的實現細節里去描述。

3. 實現細節

先來看看組件的配置項:

var DEFAULTS = {
    config: [], //必傳參數,步驟項與步驟內容項的配置,如[1,2,3]表示一共有三個(config.length)步驟,第1個步驟有1個(config[0])內容項,第2個步驟有2個(config[1])內容項,第3個步驟有3個(config[2])內容項
    stepPanes: '', //必傳參數,步驟內容項的jq 選擇器
    navSteps: '', //必傳參數,步驟項的jq 選擇器
    initStepIndex: 1, //初始時顯示的步驟位置,如果一共有4個步驟,該參數可選值為:1,2,3,4
    initPaneIndex: 1, //初始時顯示的步驟內容項位置,基於initStepIndex,如果initStepIndex設置成2,且該步驟有3個內容項,則該參數可選值為:1,2,3
    onStepJump: $.noop, //步驟項跳轉時候的回調
    onBeforePaneChange: $.noop, //步驟內容項切換之前的回調
    onPaneChange: $.noop, //步驟內容項切換之後的回調
    onPaneLoad: $.noop //步驟內容項第一次顯示時的回調
};

註釋部分已經說得比較清楚了,前5個在實現思路裡面都有相關內容提及。下麵我把那四個回調作用和調用做一個詳細說明:
1)onStepJump(oldStepIndex, targetStep)
這個回調是在大步驟跳轉的時候觸發的,作用很清楚,就是為了在步驟跳轉的時候做一些邏輯處理,比如業務需求中【3 等待審核】的特殊效果控制就得藉助這個回調,傳遞有兩個參數oldStepIndex表示跳轉前的步驟的物理索引(從0開始), targetStep表示要跳轉到的步驟的物理索引。
2)onBeforePaneChange(currentPane, targetPane, currentStep)
這個回調的作用是在切換小步驟的時候,可能小步驟裡面有的表單校驗未通過,此時就得取消小步驟的切換,通過在這個回調里返回false就能達到這個效果。傳遞的三個參數都是物理索引,分別表示當前小步驟的位置,要切換的小步驟位置和當前大步驟的位置。
3)onPangeChange(currentPane, targetPane, currentStep)
這個跟第二個是差不多的,只不過發生在小步驟切換完成之後調用。
4)onPaneLoad(e,currentStep, currentPane)
這個回調作用很大,小步驟裡面的其它組件,比如表單組件等,都可以在這個回調里定義,目的是為了實現延遲初始化的功能。同時這個回調在執行的時候已經把this指向了當前小步驟對應的DOM元素,以便可以快速地通過該DOM元素找到其它組件初始化需要的子元素,比如form等。這個回調對於每個小步驟來說都只會觸發一次。傳遞三個參數e表示相關的jq事件,後面兩個分別表示當前大步驟和小步驟的物理索引。
回調觸發順序是:onBeforePaneChange,onPangeChange,onPaneLoad,onStepJump。另外onPaneLoad在組件初始化完成的時候也會調用一次。
通過以上這些回調基本上就能解決前面業務需求的那些問題。

再來看看API,我根據前面的需求只考慮3個API實例方法:

return {
    goStep: function(step) {
        goStep(step - 1);
    },
    goNext: function() {
        go(currentPane + 1);
    },
    goPrev: function() {
        go(currentPane - 1);
    }
}

goStep可以跳轉到指定步驟的第一個小步,goNext跳轉到下一個小步,goPrev跳轉到上一個小步。另外還有一個靜態方法:

//根據步驟內容項的絕對索引位置,獲取相對於步驟項的位置
//step從0開始,pane表示絕對索引位置,比如stepPanes一共有6個,那麼pane可能的值就是0-5
//舉例:config: [1,3,1,1], step: 2, pane: 4,就會返回1,表示第三個步驟的第1個步驟內容項的位置
StepJump.getRelativePaneIndex = function(config, step, pane) {
    return pane - getPaneCountBeforeStep(config, step) + 1;
};

因為前面那些回調傳遞的參數都是物理索引,外部如果需要把物理索引轉換成邏輯索引的話,就得使用這個方法。

其它細節說明:
1)maxStepIndex
這個變數也很關鍵,通過它來控制哪些大步驟不能通過直接點擊的方式來跳轉。
2)大步驟項的UI控制

//步驟項UI控制
function showStep(targetStep) {
    $navSteps.each(function(i) {
        var cname = this.className;
        cname = $.trim(cname.replace(/current|done/g, ''));

        if (i < targetStep) {
            //當前步驟之前的狀態全部設置為done
            cname += ' done';
        } else if (i == targetStep) {
            //當前步驟項狀態設置為current
            cname += ' current';
        }
        this.className = cname;
    });
}

整體實現如下,代碼優化程度受水平限制,但是邏輯還是很清楚的:

define(function(require, exports, module) {
    
    var $ = require('jquery');

    //step: 表示步驟項
    //pane: 表示步驟內容項

    var DEFAULTS = {
        config: [], //必傳參數,步驟項與步驟內容項的配置,如[1,2,3]表示一共有三個(config.length)步驟,第1個步驟有1個(config[0])內容項,第2個步驟有2個(config[1])內容項,第3個步驟有3個(config[2])內容項
        stepPanes: '', //必傳參數,步驟內容項的jq 選擇器
        navSteps: '', //必傳參數,步驟項的jq 選擇器
        initStepIndex: 1, //初始時顯示的步驟位置,如果一共有4個步驟,該參數可選值為:1,2,3,4
        initPaneIndex: 1, //初始時顯示的步驟內容項位置,基於initStepIndex,如果initStepIndex設置成2,且該步驟有3個內容項,則該參數可選值為:1,2,3
        onStepJump: $.noop, //步驟項跳轉時候的回調
        onBeforePaneChange: $.noop, //步驟內容項切換之前的回調
        onPaneChange: $.noop, //步驟內容項切換之後的回調
        onPaneLoad: $.noop //步驟內容項第一次顯示時的回調
    };

    function StepJump(options) {
        var opts = $.extend({}, DEFAULTS, options),
            $stepPanes = $(opts.stepPanes),
            $navSteps = $(opts.navSteps),
            config = opts.config,
            stepPaneCount = sum.apply(null, config), //步驟內容項的總數
            currentStep = opts.initStepIndex - 1, //當前步驟項的索引
            currentPane = sum.apply(null, config.slice(0, currentStep)) + (opts.initPaneIndex - 1), //當前內容項的索引
            maxStepIndex = currentStep, //允許通過直接點擊步驟項跳轉的最大步驟項位置
            $activePane = $stepPanes.eq(currentPane);

        //註冊僅觸發一次的stepLoad事件
        $stepPanes.each(function() {
            $(this).one('stepLoad', $.proxy(function() {
                opts.onPaneLoad.apply(this, [].slice.apply(arguments).concat([currentStep, currentPane]));
            }, this));
        });
        //初始化UI
        showStep(currentStep);
        $activePane.addClass('active').trigger('stepLoad');

        //註冊點擊步驟項的回調
        $navSteps.on('click.step.jump', function() {
            var $this = $(this),
                step = $this.index(opts.navSteps); //找到當前點擊步驟項在所有步驟項中的位置

            if (step > maxStepIndex || $this.hasClass('current')) return;
            //跳轉到該步驟項的第一個步驟內容項
            goStep(step);
        });

        //步驟項UI控制
        function showStep(targetStep) {
            $navSteps.each(function(i) {
                var cname = this.className;
                cname = $.trim(cname.replace(/current|done/g, ''));

                if (i < targetStep) {
                    //當前步驟之前的狀態全部設置為done
                    cname += ' done';
                } else if (i == targetStep) {
                    //當前步驟項狀態設置為current
                    cname += ' current';
                }
                this.className = cname;
            });
        }

        function goStep(step) {
            go(getPaneCountBeforeStep(config, step));
        }

        //通過步驟內容項查找步驟項的位置
        function getStepByPaneIndex(targetPane) {
            var r = 0,
                targetStep = 0;
            for (var i = 0; i < stepPaneCount; i++) {
                r = r + config[i];
                if (targetPane < r) {
                    targetStep = i;
                    break;
                }
            }
            return targetStep;
        }

        function go(targetPane) {
            if (targetPane < 0 || targetPane >= stepPaneCount) {
                return;
            }

            //在切換步驟內容項之前提供給外部的回調,以便外部可以對當前步驟內容項做一些校驗之類的工作
            //如果回調返回false則取消切換
            var ret = opts.onBeforePaneChange(currentPane, targetPane, currentStep);
            if (ret === false) return;

            var $targetPane = $stepPanes.eq(targetPane),
                targetStep = getStepByPaneIndex(targetPane);

            $activePane.removeClass('active');
            $targetPane.addClass('active');

            opts.onPaneChange(currentPane, targetPane, currentStep);

            $activePane = $targetPane;
            currentPane = targetPane;

            var oldStepIndex = currentStep;

            currentStep = targetStep;
            currentStep > maxStepIndex && (maxStepIndex = currentStep);

            $targetPane.trigger('stepLoad');

            if (targetStep !== oldStepIndex) {
                showStep(targetStep);
                opts.onStepJump(oldStepIndex, targetStep);
            }
        }

        return {
            goStep: function(step) {
                goStep(step - 1);
            },
            goNext: function() {
                go(currentPane + 1);
            },
            goPrev: function() {
                go(currentPane - 1);
            }
        }
    }

    //根據步驟內容項的絕對索引位置,獲取相對於步驟項的位置
    //step從0開始,pane表示絕對索引位置,比如stepPanes一共有6個,那麼pane可能的值就是0-5
    //舉例:config: [1,3,1,1], step: 2, pane: 4,就會返回1,表示第三個步驟的第1個步驟內容項的位置
    StepJump.getRelativePaneIndex = function(config, step, pane) {
        return pane - getPaneCountBeforeStep(config, step) + 1;
    };

    //求和
    //註:slice(start,end)返回的數據不包含end索引對應的元素
    function sum() {
        var a = [].slice.apply(arguments),
            r = 0;
        a.forEach(function(n) {
            r = r + n;
        });
        return r;
    }

    //統計在指定的步驟項之前一共有多少個步驟內容項,step從0開始,比如config: [1,3,1,1], 當step=2,就會返回4
    function getPaneCountBeforeStep(config, step) {
        return sum.apply(null, config.slice(0, step));
    }

    return StepJump;
});

4. 調用舉例

demo.html里的使用方式:

define(function(require, exports, module) {

    var $ = require('jquery');
    var StepJump = require('components/stepJump'),
        stepJump = new StepJump({
            config: [1,3,1,1],
            stepPanes: '#step-content .step-pane',
            navSteps: '#steps > li',
            initStepIndex: 1
        });

    $(document).on('click.stepPane.switch', '.btn-step', function(e) {
        var $this = $(this);
        if ($this.hasClass('next')) {
            stepJump.goNext();
        } else {
            stepJump.goPrev();
        }
    });
});

由於這是個靜態的功能,所以不用加任何回調。

regist.html里的使用方式:

//STEP_STATUS取值:
//0 待填寫資料,如果每次進入這個頁面時是這個狀態值,那麼就顯示【1 入住須知】這個大步驟,表示正在進行該步驟;
//1 待提交資料,如果每次進入這個頁面時是這個狀態值,那麼就顯示【2 公司信息提交】這個大步驟,小步驟預設顯示它的第一個;
//2 審核未通過,如果每次進入這個頁面時是這個狀態值,那麼就顯示【3 等待審核】這個大步驟;
//3 審核已通過,如果每次進入這個頁面時是這個狀態值,那麼就顯示【3 等待審核】這個大步驟;
//4 待確認合同,如果每次進入這個頁面時是這個狀態值,那麼就顯示【4 合同簽訂】這個大步驟;

var STEP_STATUS = 3,
    MODE = STEP_STATUS == 2 || STEP_STATUS == 4 ? 3 : 2, //3表示只讀,在公司信息提交步驟只能看不能改
    STEP_AUDIT_ALERTS = STEP_STATUS == 3, //這個變數用來控制在等待審核步驟的時候是否給步驟項添加alerts樣式
    STEP_STATUS_MAP = {
        0: 1,
        1: 2,
        2: 3,
        3: 3,
        4: 4
    },
    initStepIndex = STEP_STATUS_MAP[STEP_STATUS],
    STEP_PANES_DATA = [1, 3, 1, 1],
    STEP_PANES_CONFIG = {
        //3,1表示第三個步驟的第一個步驟內容
        3: {
            1: {
                onPaneLoad: function(e, currentStep, currentPane, conf) {
                    var $stepPane = $(this);
                    conf.vc = new VisibleController($stepPane.children('div'));
                    if (STEP_AUDIT_ALERTS) {
                        $auditStep = $('#audit-step');
                        $auditStep.addClass('alerts');
                        conf.vc.show('#audit-no');
                    } else if (STEP_STATUS == 2 || STEP_STATUS == 4) {
                        conf.vc.show('#audit-yes');
                    } else {
                        conf.vc.show('#audit-wait');
                    }
                }
            },
            onLeaveStep: function() {
                STEP_AUDIT_ALERTS && $auditStep.removeClass('alerts');
            },
            onEnterStep: function(step, conf) {
                if (STEP_AUDIT_ALERTS) {
                    $auditStep.addClass('alerts');
                } else {
                    conf[1].vc.show('#audit-wait');
                }
            }
        },
        4: {
            1: {
                onPaneLoad: function(e, currentStep, currentPane, conf) {
                    var $stepPane = $(this);
                    conf.vc = new VisibleController($stepPane.children('div'));
                    conf.vc.show('#contract-confirm');
                }
            }
        }
    },
    GET_STEP_PANES_CONFIG = function(step, pane) {
        if (pane == undefined) return STEP_PANES_CONFIG[step + 1];
        return STEP_PANES_CONFIG[step + 1] && STEP_PANES_CONFIG[step + 1][StepJump.getRelativePaneIndex(STEP_PANES_DATA, step, pane)];
    };

var $auditStep,
    stepJump = new StepJump({
        config: STEP_PANES_DATA,
        stepPanes: '#step-content .step-pane',
        navSteps: '#steps > li',
        initStepIndex: initStepIndex,
        onBeforePaneChange: function(currentPane, targetPane, currentStep) {
            var conf = GET_STEP_PANES_CONFIG(currentStep, currentPane);
            return conf && conf.onBeforePaneChange && conf.onBeforePaneChange.apply(this, [].slice.apply(arguments).concat[conf]);
        },
        onPaneChange: function() {
            window.scrollTo(0, 0);
        },
        onPaneLoad: function(e, currentStep, currentPane) {
            var conf = GET_STEP_PANES_CONFIG(currentStep, currentPane);
            conf && conf.onPaneLoad && conf.onPaneLoad.apply(this, [].slice.apply(arguments).concat([conf]));
        },
        onStepJump: function(currentStep, targetStep) {
            var conf = GET_STEP_PANES_CONFIG(currentStep);
            conf && conf.onLeaveStep && conf.onLeaveStep(currentStep, conf);

            conf = GET_STEP_PANES_CONFIG(targetStep);
            conf && conf.onEnterStep && conf.onEnterStep(targetStep, conf);
        }
    });

StepJump組件的初始化在最後面,前面都是一些配置相關的內容。更換STEP_STAUS這個變數的值,就能模擬實際業務中的不同業務狀態,就能看到不同狀態進入頁面時這個組件的顯示的效果。

5. 小結

本文把最近工作的一部分成果總結了一下,提供了一個StepJump組件,也許在你的工作中也有用得著的地方,當然每個人的思路跟做法都不一定相同,我也僅僅是分享的目的。其實這幾天的工作思考的東西還是挺多的,除了這個組件之外,更多的想法都集中在樣式分離,CSS命名跟表單組件的分離這一塊,只不過現在這些思想還不夠系統,還不到總結分享的水平,這些工作方法層面的理論,很少人去總結跟分享,我目前只見到張鑫旭的博客上有較完整的一套思路,學習下來,確實有不少收穫跟體會,但是這畢竟是別人的,有一些只可意會不可言傳的精華,還是掌握不到,只能一步步去積累才行,等將來我自己的思路成形了,我會把我的想法全部分享出來,相信這件事情會成為我今年分享的最有價值的內容。

謝謝閱讀:)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文轉自:http://www.zccode.com/thread-724-1-1.html 該資源說明:看起來不錯的一套一元雲購CMS源碼,源碼包裡面帶了安卓和ios手機客戶端,手機客戶端需要自己反編譯。 這裡不做功能和其它更多的介紹,可以自己下載後慢慢測試瞭解。安裝說明:伺服器空間需要支持PHP ...
  • 問題的提出整個WR的ISE工程比較大,編譯時間很長,導致開發效率低。通過分析發現,ISE在綜合的時候大量的時間都花在了初始化DPRAM上。調研發現Xilinx提供了BMM文件和DATA2MEM工具,可以將軟核CPU的運行代碼在HDL綜合完後再與bit文件合併,這樣可以節約大量的編譯時間。但是在wr工 ...
  • Atitit.android jsbridge v1新特性 1. Java代碼調用js並傳參其實是通過WebView的loadUrl方法去調用的、只是參數url的寫法不一樣而已1 2. 三、JAVA和JS交互註意事項1 3. Js調用android java通過jsbridge2 4. JsBrid ...
  • 介紹 智能企業(intelligent enterprise)就是全面運用智能管理體系,實現企業管理中“機要素智能化高效整合併實現人機協調”的企業。信息管理中經常談到“人機協調”的問題.這裡所說的“人機協調含義有所不同。信息管理中的人機協調更多的是研究技術上的問題,這裡的人機協調強調的是管理問題,必... ...
  • 序言 這個AOP要從我們公司的一個事故說起,前段時間公司的系統突然在烏雲中出現,數據被泄露的一覽無餘,烏雲上顯示是SQL註入攻擊。呵,多麼貼近生活的一個露洞,可謂是人盡皆知啊。然而卻華麗麗的給拉我們一記耳光。 那麼問題既然來啦,我們.net組有40-50個項目之多吧,怎麼去一一補救這一過失呢?什麼又 ...
  • 需求:根據年級下拉框的變化使得科目下拉框綁定次年級下對應有的值 我們用三層架構的模式來實現 1.我們想和資料庫交互,我們首先得來先解決DAL資料庫交互層 01.獲得年級下拉框的數據 在GradeDAL類中 02.在業務邏輯層 03.在窗體UI層 在Load事件中載入年級下拉框 其中在使用 獲得年級下 ...
  • 本地文件系統如ext3,reiserfs等 (這裡不討論基於記憶體的文件系統),它們管理本地的磁碟存儲資源、提供文件到存儲位置的映射,並抽象出一套文件訪問介面供用戶使用。但隨著互聯網企業的高 速發展,這些企業對數據存儲的要求越來越高,而且模式各異,如淘寶主站的大量商品圖片,其特點是文件較小,但數量巨大 ...
  • HTML5已經成為最流行的編程語言在web開發者。強大的編程語言有很大的能力,生產更好的萬維網內容。HTML5的興起已經在過去三年增長迅速。介紹了HTML5的新技術是更好的。HTML5技術是由像Chrome的瀏覽器,支持Firefox、IE和更多。今天,我們列出了十個HTML5動畫製作工具,可以幫助 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...