用js写一个业务复杂的连续多步骤的弹窗,该如何设计呢,集思广益

图片描述

现在的需求是,需要有一系列流程在弹窗中完成。比如:
我点击payment button,会弹出一个弹窗,在“弹窗1”上,选择要结账的产品 和 结账的方式(立即结账还是月结),如果立即结账,就会进入弹窗2,

注意:
1.每一个时刻,页面只显示一个弹窗,即当前步骤的弹窗。
2.日后一定还会在中间加入其他“步骤”和“分支”。
3.不同选择可能走入不同的分支,可能有跳步等动作,例如图中所示的例子。
4.必须有回退功能,即在用户在最后一步sumbit之前,都是可以回退的,而且必须保持之前选择的状态。那么该如何记录之前的选择呢。

技术范围要求:必须js

我的想法是用js的对象方式写。
求一个设计思路,谢谢了,想了很久没有一个完善的方案。

谢谢大家的解答:

我补充下我之前的想法,不知是否可行
1.每一个弹窗都是一个不同的对象,
2.第一个“弹窗对象”是与众不同的,其他所有的“弹窗对象”都是他的属性,而且有一个array属性,可以存放用户轨迹,方便回退。

阅读 9.7k
9 个回答

斗胆回答一下。

感觉题主的最主要的问题不在弹窗,弹窗只是一个简单的需求层面的展现形式。因此实现起来并不存在什么难点。至于是否把弹窗封装成组件,这个就在于你自己的考虑。

如果你的弹窗需要在其他很多地方使用,就封装弹窗。如果只是用在这一个地方,其实也没必要单独做封装。

而个人认为此问题的关键在于如何记录状态与跟踪路径。

因此要解决这个问题就需要涉及到一个状态管理器的问题。

如果楼主有过自己写状态管理器的经验,相信这个问题应该就不会太难。关于状态管理器,比较高级的解法是可以直接使用redux来搞定这个问题,redux作为状态管理器可以脱离react使用。但是redux毕竟功能强大,而题主的需求其实相对简单,因此自己搞一个简单的就行了。

状态管理器,说简单点就是一个全局变量。但是理论上来说我们需要尽量避免使用全局变量,因此就把这个全局变量搞成了一个单独的模块,加一些方法,就变成了一个状态管理器。

简单实现如下

// state.js
// 定义一个对象,用来存储状态
let states = {}


// 获取整个对象内容,常用于开发中查看保存的值变成了多少
function getStates () {
    return states
}

// 根据属性名,获取存储的值
function get (name) {
    if(states[name]) { return states[name] }
    return ''
}


// 保存属性名,用法与react中的setState方法完全一致
function set (options, target) {
    let keys = Object.keys(options)
    let o = target ? target : states
    keys.map( item => {
        if(typeof o[item] == 'undefined') {
            o[item] = options[item]
        } 
        else {
            if(type(o[item]) == 'object') {
                set(options[item], o[item])
            } else {
                o[item] = options[item]
            }
        }
        return item
    })
}

// 判断数据类型
function type(elem) {
    if (elem == null) return elem + '';
    return toString.call(elem).replace(/[\[\]]/g, '').split(' ')[1].toLowerCase();
}

// 这里使用了es6的模块语法对外提供接口,你也可以根据自己的实际开发情况使用其他的模块语法
export { getStates, get, set }

因代码是从老项目中复制出来,需确认无误后使用

那么我们在使用的时候,可以这样做

import * as state from './state.js';

//保存步骤1的选择,可以这样设计数据
state.set({
    payType: {
        step: 1,
        stepName: 'payType',
        preStep: null,
        selectType: 0  // 选择了什么付款方式,可自定义
    }
})

// 保存步骤二
state.set({
    cardInfo: {
        step: 2, 
        stepName: 'cardInfo',
        preStep: 'payType', 
        // 此处的名称与上一步保存的属性名保持一致,
        // 方便直接使用获取上一步的内容,如果有其他具体需求,
        // 也可以与上一步的弹窗的对象名保持同步, 方便直接获取对象。
        // 此处处理相对比较灵活,题主还可以借助一些路由插件来实现返回上一步
        moreInfo: {},
        ... // 更多信用卡信息
    }
})

... ...  // 接下来的步骤都可以使用这个方式保存信息,所有的步骤内容都保存好了。

// 这种方式在以后插入新的步骤也会十分方便,只需要重新保存或者修改一些数据即可

// 如果需要修改保存的数据,比如修改步骤二,直接这样修改就可以了
state.set({
    cardInfo: {
        preStep: 'otherStep'
    }
})

这里简单实现了状态管理器,在实际应用中还可以继续扩展,变成一个订阅-发布模式的高级管理器,但是这里就不做深入讲解了,可以查阅其他资料进行了解。

具体如何保存状态,数据,路径,你也可以自己设计得更加合理一点,我这里只是简单的提供一个思路,并不一定是最好的方式。

这种状态记录只是js层面上的记录,如果用户刷新或者关掉网页状态肯定就没了,因此如果有必要,题主需要在每一步保存的时候,向服务器提交状态与数据,将所有的信息保存在服务器。下次刷新再从服务端重新获取。

很好解决,是你没捋清头绪。我们来修改一下这张图,用前端路由或后端路由来实现

clipboard.png

请求1和路由1就不说了,就是你的初始页面。

每个路由,接受将要到来的请求, 每个请求中包含页面传过来的数据(就是用户在页面上填写的)

每个路由验证请求是否合法(用户是否填写了符合规则的数据)

路由2发送请求3时,将请求2的数据也通过请求3传递给路由3

请求3请求4的请求数据里有一个结账方式字段

路由3验证数据后存到数据库

首先,谢邀!来,哥哥来给你整面向对象的设计

function PopupWindowManager(){
    this.popupWindowArray = [];
    this.popupNewWindow = function(windowInstance){
        //实现弹窗de逻辑
        xxxxxxxx
        this.popupWindowArray.push(windowInstance);
    };
    this.closeWindow = function(windowInstance){
        //实现关闭窗口的逻辑,判断能不能关windowInstance等等
        this.popupWindowArray.pop();        
    };
}

function PopupWindowBase(title){
    this.title = title;
    this.message = "xxxx";
    this.form = xxxx;
    this.width = "xxxx";
    this.hight = "xxxx";
    
    this.view = render();
    
    this.close = function(){
        //发送消息给popupWindowManager告诉它谁被用户点了X
        xxxx
    };
    this.addButton = function(buttonLabel,callback){
        var btn = new Button(buttonLable);
        btn.onclick = callback;
        this.view.appendChild(new Button(buttonLable));
    };
    
    function render(){
        //根据参数绘制div或者其它html元素,并返回该element
        return element;
    }
}
var mgr = new PopupWindowManager();
var win1 = new PopupWindowBase('窗口1');
win1.addButton('立即结账', function(){
    mgr.popupNewWindow(win2);
});
win1.addButton('月账', function(){
    mgr.popupNewWindow(win3);
});
var win2 = new PopupWindowBase('窗口2');
win2.addButton('ok', function(){
    mgr.popupNewWindow(win3);
});
var win3 = new PopupWindowBase('窗口3');

其实不知道你们为啥这么纠结多个弹窗的问题,这个问题其实不用考虑多个弹窗,弹窗只有一个,只是显示内容不同而已

给你大概一个思路:

  div.modal
    div.content1.active
    div.content2
    div.content3

你只要在点击按钮的时候,切换隐藏/显示就好。而且由于只是隐藏/显示,所以内容切换也并不会让之前填入的值消失。

至于content之间的数据同步,你可以在页面切换的时候,进行当前页面的数据的同步。也可以监听输入域,输入的时候直接进行其他页面的数据同步。

用模式弹窗啊,很好解决。 很多前端框架都带漂亮的模式弹窗。自行百度。

数据存在全局变量,反正也没页面跳转和刷新,如果有就用COOKIE。把需求当做一个简单的问卷处理。

我来写写 用promise解决,3个弹框都可以用promise封装一下

showDialog1.then(()=>{
    //立即结账
    showDialog2.then(()=>{
        showDialog3.then(()=>{
            
        })
    })
}).catch(()=>{
    //月结
    showDialog3.then(()=>{
        
    })
})

一般这种情况我都是用链表(单/双向根据需求来定)来关联弹框。。。

使用模态窗口啊:
AngualrJS Example:

Website:
HTML page:

<button class="btn btn-primary" ng-click="open('lg','app/editapp.html','appController')" ng-show="mode == 'create'">Create Application</button>

当单击“Create Application” button的时候, 调用“open” 这个方法,传入三个参数:
'lg' 表示弹出窗口大小为 Large,
'app/editapp.html' 表示弹出窗口的内容页面,
'appController' 表示控制弹出窗口的JS代码

JS: 创建模态窗口实例。

    $scope.open = function (size, templateUrl, controller) {
        Task.modalInstance = $uibModal.open({
            templateUrl: templateUrl,
            controller: controller,
            size: size,
            backdrop: "static"
        });
        Task.modalInstance.opened.then(function () {
            //When the model is opened, it will be used!
        });
        Task.modalInstance.result.then(function (result) {
            //When the modalInstance is closed, it will be used
            if(controller.toString().indexOf('app') > -1) {
                $scope.loadApps(true, function (err, loaded) {

                });
            }
            else {
                $scope.loadScripts(true, function (err, loaded) {

                });

            }
        }, function (reason) {
            $log.info('Modal dismissed at: ' + new Date());
        });
    };
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题