1. 功能介绍

    页面流,就是将使用者在前台界面操作办理的一个复杂多页面交互的业务功能转换成流程向导方式操作执行,将复杂的业务页面拆分成多个页面步骤,通过配置文件配置的逻辑作为流程来流转执行,这样最大程度实现单个页面的重用性,方便不同的客户定制要求,不同业务能够通过配置文件组装的方式实现业务功能,以最大程度的实现产品组装化。

  2. 主要特性

    1. 图形化界面画页面流程并生成页面流程的配置文件,注:配置文件也可以手工编写
    2. 通过配置文件组装页面流程,配置文件支持多种流程节点,如流程步骤,流程判断选项,这样能够最大程度的灵活配置等。
    3. 配置文件的选项支持表达式,支持常用的的语法解析,对于复杂语法调用配置的方法实现。
    4. 页面与页面之间是一种松耦合的关系,页面在脱离页面流照样能够复用。
    5. 页面流参数传递,传递的参数只在页面流这个生命周期中有效,支持传递IData,IDataset,String,String[]等参数,
    6. 页面流字段监控,在操作时输入或选择不同的字段内容,可能会影响流程选项的执行,这样配置监控字段,将会将改变字段之后的全部回退,已保证流程的正确性。
    7. 页面步骤的上一步,下一步、完成按钮的执行绑定,通过配置文件绑定每个步骤页面的执行按钮,这样页面的操作及校验判断由开发者自己的按钮实现。
    8. 页面流步骤页面的兼容性,页面流步骤页面开发和普通页面开发完全一样,包括异常、操作成功的提示,只有当需要用到页面流参数对象传递时才会用到页面流相关功能。
    9. 页面流提供向导式的操作以及图形化方式的业务流程图。
    10. 页面流各步骤页面录入的数据在最后的页面流完成时统一提交处理。
    11. 页面流下一步和完成步骤都能够获取到之前步骤的所有数据,所以一般不需要特意传递数据。
  3. 开发步骤

    1. 生成配置文件。通过图形化配置工具配置页面流并生成配置文件,也可以手工编写页面流配置文件。
    2. 根据配置文件中配置的每个页面流步骤,编写对应的页面类和页面模板。
    3. 若需要在页面流的操作按钮中实现自定义的逻辑(JavaScript、Java逻辑),绑定页面流步骤页面的上一步、下一步按钮。
    4. 调试页面流,通过url或者reidrectTo,redirectToNav方法打开页面流,页面名称:component.flow.pageflow.FlowFrame、方法名称:init、参数:&FLOW_FILE=demo/Demo.xml
  4. 配置文件说明

    1. flow: 流程配置

      name 页面流名称
      desc 页面流描述
    2. step: 步骤配置

      name 步骤名称,每个步骤必须唯一
      desc 步骤描述
      nextstep 接续步骤,即执行完当前步骤后的下一步骤
      page 页面名称,执行该步骤时打开的页面
      listener 页面方法,打开页面触发的方法
      params 页面参数,打开页面调用的参数,多个参数用;号分隔,用:号分隔参数名和参数值,如:params="x:1;y:2;z:3"
      backbutton 点页面步骤上一步绑定的按钮名称,页面绑定按钮同nextbutton,通过backbutton,nextbutton能够让开发者灵活定义页面流操作需要触发的逻辑
      nextbutton 点页面步骤下一步绑定的按钮名称,作为下一步绑定的按钮,存在下一步和完成两种情况,故需要区分下,如果为完成并最终提交时,绑定的按钮必须是type="submit",如:
      <input type="button" jwcid="bsubmit@Submit" value="提交" listener="ognl:listeners.updateStaff" desc="员工资料" onclick="return confirmForm(this);"/>
      若只是下一步操作,按钮用type="button",如:
      <input type="button" id="bnext" value="上一步" onclick="return confirmAll(this)"/>
      monitor 监听页面步骤中字段的改变,若监听的字段改变了,会提示并将该页面的后续步骤全部撤销,这样保证逻辑的一致性,多个监听字段用,号分隔,如:monitor="MON_FIELD1,MON_FIELD2",监听全部字段monitor="ALL"
      syncmethod 同步页面步骤的改变,比如页面流有1:资料,2:业务,3:预览三个页面,当执行到3:预览页面时切换到1:资料页面修改资料,如果配置了同步方法属性,那么会根据方法逻辑将1:资料的修改同步到3:预览页面,保证业务数据的一致性
    3. switch: 选项

      name 选项名称
      expression 选项执行的表达式,兼容ognl表达式写法,表达式中必须为特定变量(param,pageData),其中param表示pd.getData(),pageData表示PageData对象,支持表达式param.USER_ID、pageData.getParameter('USER_ID')、pageData.getData().get('CUST_TYPE')等,如果逻辑比较复杂,无法通过表达式实现,可以指定类名和方法来实现,如:com.TestBean@test, 表示调用TestBean的test的方法,其中方法必须是 public String test(PageData pd, IData param)这种类型,并且不限制为静态方法
      default 默认执行的case,如果表达式中取到的值为空或者没有匹配的case,则直接执行配置的默认case
      desc 选项描述
    4. case: 选项情形

      value case值
      desc case描述
      nextstep 接续的步骤名称
  5. 方法说明

    类路径 com.linkage.component.PageData
    类描述 页面上下文对象,一般可通过Page类的PageData pd = getPageData()方式获取
    方法名称 public boolean isPageFlow() throws Exception
    方法描述 是否为页面流页面
    方法名称 public IPageFlow getPageFlow() throws Exception
    方法描述 获取页面流对象
    类路径 com.linkage.component.PageData
    类描述 页面流对象,一般可通过PageData类的IPageFlow pf = pd.getPageFlow()方式获取
    方法名称 public String getFlowId() throws Exception
    方法描述 获取页面流编号
    方法名称 public String getFlowIdName() throws Exception
    方法描述 获取页面流编号对应的变量名称
    方法名称 public String getTransfer(String name) throws Exception
    方法描述 获取页面流传递的字符串值,参数name表示检索条件
    方法名称 public String[] getTransfers(String name) throws Exception
    方法描述 获取页面流传递的字符串数组值,参数name表示检索条件
    方法名称 public IData getTransferData(String name) throws Exception
    方法描述 获取页面流传递的IData对象,参数name表示检索条件
    方法名称 public IDataset getTransferDataset(String name) throws Exception
    方法描述 获取页面流传递的IDataset对象,参数name表示检索条件
    方法名称 public void setTransfer(String name, String value) throws Exception
    方法描述 设置页面流传递的字符串值,参数name表示key,参数value表示传递的对象
    方法名称 public void setTransfers(String name, String[] value) throws Exception
    方法描述 设置页面流传递的字符串数组值,参数name表示key,参数value表示传递的对象
    方法名称 public void setTransferData(String name, IData data) throws Exception
    方法描述 设置页面流传递的IData对象,参数name表示key,参数data表示传递的对象
    方法名称 public void setTransferDataset(String name, IDataset dataset) throws Exception
    方法描述 设置页面流传递的IDataset对象,参数name表示key,参数dataset表示传递的对象
  6. 注意事项

    1. 绑定按钮的onclick方法,如果需要校验返回,中断后续操作,不需要return false|true,否则都会认为是return true;
    2. 若提交页面存在字段,其他页面同名字段将会过滤掉,若提交页面没有,而其他几个页面都有,会以之前页面的字段为准,后面重复的过滤
    3. 所有页面都必须有form,并且form必须唯一;
    4. 页面流中如果用到redirectToMsg,msg中的按钮将target往上parent一层,保证redirectToMsg不会在页面流框架页面内跳转。
    5. 数据传递如果数据有多个的话,建议用setTransferData,setTransferDataset传递,效率会更高。
    6. 打开流程菜单传递的参数,在每个流程步骤页面中及最终提交时能够直接获取到,不需要手工传递,但如果刷新流程页面刷新后或者子页面中,需要手工传递。
    7. 对于流程步骤页面中的弹出窗口或者子页面,如果需要用到pd.getPageFlow()并获取数据时,会提示pageflow不存在(没有获取到pageflow_id),这个情况下,需要手工将数据传递,如:
      pd.setTransfer(pageflow.getFlowIdName(), pageflow.FlowId())
      然后在iframe或者弹出窗口中设置source="ognl:pageData.transferData"或者其他方式将pageflow_id传递到子页面中。
    8. 参数传递若存在重复字段,以流程步骤页面中的字段的为准。
    9. 绑定按钮不需要设置为不可见,流程运行时会自动隐藏。
    10. 绑定上一步、下一步按钮如果需要用到校验等,和其他页面写法一样,如confirmAll,confirmInfo等,可参看客户端校验章节,但是作为最终提交完成的nextbutton,必须用@Submit组件来实现,而其他情况最好不需要用组件,直接type="button"即可。
    11. 配置文件存放在etc/省代码../pageflow/目录下,如:pageflow/demo/Demo.xml,作为配置文件参数传递时,需要忽略pageflow/,如:&FLOW_FILE=demo/Demo.xml
  7. 代码片段

    1. 配置文件代码(参考etc/../pageflow/adm/StaffEdit.xml)

      <?xml version="1.0" encoding="GB2312"?>
      <flow name="StaffDatumEdit" desc="员工资料编辑">
      	<step
      		name="begin"
      		desc="开始"
      		nextstep="editBaseDatum"
      		/>
      	<step
      		name="editBaseDatum"
      		desc="编辑基本资料"
      		page="adm.staff.StaffBaseDatum"
      		listener="initBaseDatum"
      		nextstep="switchOtherDatum"
      		nextbutton="bnext"
      		monitor="MODIFY_OTHERINFO"
      		syncmethod="syncBaseDatum"
      		/>
      	<switch name="switchOtherDatum" expression="param.MODIFY_OTHERINFO" default="0" desc="选择修改其他资料">
      		<case value="0" nextstep="switchSafeDatum" desc="否"/>
      		<case value="1" nextstep="editOtherDatum" desc="是"/>
      	</switch>
      	<step
      		name="editOtherDatum"
      		desc="编辑其他资料"
      		page="adm.staff.StaffOtherDatum"
      		listener="initOtherDatum"
      		nextstep="switchSafeDatum"
      		nextbutton="bnext"
      		monitor="MODIFY_SAFEINFO"
      		/>
      	<switch name="switchSafeDatum" expression="param.MODIFY_SAFEINFO" default="0" desc="选择修改保护资料">
      		<case value="0" nextstep="editStaffRole" desc="否"/>
      		<case value="1" nextstep="editSafeDatum" desc="是"/>
      	</switch>
      	<step
      		name="editSafeDatum"
      		desc="编辑安全资料"
      		page="adm.staff.StaffSafeDatum"
      		listener="initSafeDatum"
      		nextstep="editStaffRole"
      		nextbutton="bnext"
      		/>
      	<step
      		name="editStaffRole"
      		desc="配置员工角色"
      		page="adm.staff.StaffRoleList"
      		listener="initStaffRole"
      		nextstep="end"
      		backbutton="bback"
      		nextbutton="bsubmit"
      		/>
      	<step
      		name="end"
      		desc="结束"
      		/>
      </flow>
      
    2. JAVA代码(View文件):

      /** 打开员工基本资料修改页面 */
      public void initBaseDatum(IRequestCycle cycle) throws Exception {
      	PageData pd = getPageData();
      	/** 获取员工数据 */
      	String staff_id = pd.getParameter("STAFF_ID");
      	AdmBean bean = new AdmBean();
      	IData info = bean.queryStaff(pd, staff_id);
      	if (info == null) common.error("您要查看的记录已不存在");
      	setInfo(info);
      	/* 将查询出的数据传递,该数据在页面流的整个生命周期都能获取到 */
      	pd.getPageFlow().setTransferData("staffinfo", info);
      }
      /** 打开员工其他资料修改页面 */
      public void initOtherDatum(IRequestCycle cycle) throws Exception {
      	PageData pd = getPageData();
      	/** 获取staffinfo中的数据,该数据在基本资料页面已经设置传递 */
      	IData info = pd.getPageFlow().getTransferData("staffinfo");
      	setInfo(info);
      }
      /** 打开员工保护资料修改页面 */
      public void initSafeDatum(IRequestCycle cycle) throws Exception {
      	PageData pd = getPageData();
      	/** 获取staffinfo中的数据,该数据在基本资料页面已经设置传递 */
      	IData info = pd.getPageFlow().getTransferData("staffinfo");
      	setInfo(info);
      }
      /** 根据员工查询可配置的角色列表 */
      public void initStaffRole(IRequestCycle cycle) throws Exception {
      	PageData pd = getPageData();
      	/** 查询角色列表 */
      	String staff_id = pd.getParameter("STAFF_ID");
      	AdmBean bean = new AdmBean();
      	IDataset roles = bean.queryStaffRolesByMerge(pd, staff_id);
      	setRoles(roles);
      }
      /** 提交员工资料修改 */
      public void updateStaff(IRequestCycle cycle) throws Exception {
      	PageData pd = getPageData();
      	
      	String staff_id = pd.getParameter("STAFF_ID");
      	String staff_status = pd.getParameter("STAFF_STATUS");
      	/** 修改员工资料 */
      	AdmBean bean = new AdmBean();
      	if (!bean.updateStaff(pd, pd.getData(), new String[] { staff_id, staff_status })) common.error("修改失败,信息在此操作前已经被操作过");
      	
      	//页面流执行成功后一定要释放相关资源,否则会占用客户端资源不释放
      	pd.getPageFlow().release();
      
      	pd.setTransfer("STAFF_ID");
      	pd.setTransfer("FLOW_FILE");
      	/** 操作成功提示相关 */
      	redirectToMsg("员工修改成功,选择【继续修改】跳转到[员工编辑]页面,选择【查看详情】跳转到[员工详情]页面", new String[] { "component.flow.pageflow.FlowFrame", "adm.StaffView" }, new String[] { "init", "viewStaff" }, new String[] { "继续修改", "查看详情" });
      }
      
    3. HTML代码(Html文件):

      /** 员工基本资料页面,绑定的下一步按钮 */
      <input type="button" id="bnext" value="下一步" onclick="return confirmAll(this)"/>
      /** 员工其他资料页面,绑定的下一步按钮 */
      <input type="button" id="bnext" value="下一步" onclick="return confirmAll(this)"/>
      /** 员工保护资料页面,绑定的下一步按钮 */
      <input type="button" id="bnext" value="下一步" onclick="return confirmAll(this)"/>
      /** 员工角色列表页面,绑定的完成按钮 */
      <input type="button" jwcid="bsubmit@Submit" value="提 交" listener="ognl:listeners.updateStaff" desc="员工资料" onclick="return confirmForm(this);"/>
      
      JavaScript代码(js文件):
      /** 页面步骤修改后的同步激活方法 */
      function syncBaseDatum() {
      	//获取页面步骤页面
      	var otherframe = getFrame(["editOtherDatum", parent]);
      	//判断页面步骤页面是否存在
      	if (otherframe != null && otherframe.location.href != "about:blank") {
      		//将基本信息页面的修改同步到其他信息页面
      		otherframe.Wade.dom.getElement("REMARK").value = getElementValue("USECUST_NAME");
      	}
      }
      
    4. JavaScript代码(js文件):

      /** 页面步骤修改后的同步激活方法 */
      function syncBaseDatum() {
      	//获取页面步骤页面
      	var otherframe = getFrame(["editOtherDatum", parent]);
      	//判断页面步骤页面是否存在
      	if (otherframe != null && otherframe.location.href != "about:blank") {
      		//将基本信息页面的修改同步到其他信息页面
      		otherframe.Wade.dom.getElement("REMARK").value = getElementValue("USECUST_NAME");
      	}
      }
      
    5. 说明

      1. 页面流所编写的JAVA代码,除了页面流生命周期中参数的传递和获取和普通代码不一样外,其他写法都和普通方式一样。
      2. 页面流生命周期中参数传递和获取必须通过pd.getPageFlow()来实现,如果要判断改页面是否嵌入在页面流中,通过pd.getPageFlow().isPageFlow()实现。
  8. 效果演示

    员工资料修改操作流程

    修改员工基本资料

    修改员工其他资料

    修改员工保护资料

    配置员工角色