# PgcFormViewer **Repository Path**: risun/pgc-form-viewer ## Basic Information - **Project Name**: PgcFormViewer - **Description**: 通过脚本语言对复杂表单进行重新编排展示,方便业务视图理解和使用。 - **Primary Language**: JavaScript - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-03-10 - **Last Updated**: 2026-03-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 需求 目前存在一套已有的营销活动后台管理配置界面,但是为了通用和支持业务动态组合,按技术性编排整个配置流程,导致业务运营人员使用时需要根据当前业务和技术说明文档去作一个映射理解,才能正常配置使用,不够直观;且为了兼容更多业务场景,大量配置可能与当前需求场景不一致,出现大量冗余,进一步增加理解难度; ### 核心设计思路 基于以上背景,提出做一个纯前端视图组件工具,基于在已有的后管界面相应的配置项中打标记,通过全局查询标记定位收集HTML的DOM元素,临时挪到视图组件中,并按一个规则脚本进行展示,使用重新设计且符合业务语言的界面元素重新展示配置界面(具体交互细节保留原始组件效果),使得信息被业务贴源化和切块细化,解决信息量庞大及难以理解的问题; 对此,针对收集标记的HTML DOM元素和展示规则脚本解析这两个过程,会产生两颗树,一个解析当前HTML模块DOM结构根据相关节点标记组装得到,另一个通过根据规则脚本解析得到; 对于HTML模块DOM结构对应的树,整体根据视图归属有一个根节点PgcSection,该元素下的被标记的元素按data-pgc-section标记为一个组,都对应一个PgcSection,每个组下由data-pgc-anchor标记节点元素,相应根据节点功能和相应标记分别解析出当前节点的字段名称(data-pgc-title)、动态增减(data-pgc-dynamic)、输入能力(data-pgc-input)、错误处理(data-pgc-error)等,组装到节点PgcAnchor对象中,所有的PgcSection和PgcAnchor组成这棵节点树;每次树刷新都会重新查询并DOM树并更新PgcSection内的数据结构;树中内容后加可后补,根据id或name去重; 对于展示规则脚本的树,需要按语法进行解析,从上到下,按行处理,每行对应生成一个PgcTreeNode。 用‘-’表示层级,默认无符号表示最高层级,每多一个则表示向下递增一级,对应PgcTreeNode也建立父子关系,在名称中用多级路径体现(parent.child),同一级为同一组; 用‘#’表示全局唯一声明,生成的对象只被处理一次; 用‘##’表示引用对象,前面的数据内容会被传递到后面的对象里; 用‘###’表示声明一个内容,可以重复声明,后出现的会覆盖前面的同名的声明内容,不会合并; 用‘@’表示引用一份数据,此数据会在当前上下文中被直接使用; 每行脚本,如果出现‘[’开头,则是声明一个导航标题,如果其中没有+、-开头,说明是静态标题;否则,就是声明可动态新增、减少的动态节点,根据第一个‘:’后的内容取实际展示的标题,根据第二个‘:’后的内容取动态操作表达式处理; 每行脚本,如果出现‘(’开头表示需要进行标记,对不同标记的情况会进行不同的操作: - Slient:此脚本行会在当前声明内容块被选中或聚焦时直接执行代码处理; - ?提示内容/*详细描述*/:此脚本行对应的原始节点默认不展示,作为思维泡的可选项展示提示内容,鼠标悬浮上去后展示详细描述,点击思维泡后,才会出现相应的节点; - ^:同组的原始节点都不展示,只展示当前标记对应的节点; 每行脚本,‘(’开头‘)’结尾后或者无任何其他脚本标记后声明的名称,为目标节点名称,表示需要在当前组对应的一屏界面中展示此节点,可用在后面再使用操作符‘=’进行赋值操作,包括静态值和引用值,其中如果静态值赋值为‘@’说明进行该节点的点击操作;目标节点名称后通过一对‘`’重新声明展示节点名称,可以在名称声明中使用‘@’引用变量进行字符串拼接; 对于被‘@’声明引用的节点对象,会根据结果创建新的子树所有节点实例,并且继承当前层级关系,即:当前是几级,则引用后所有内容的级别都会递增对应级数,同步路径也更新为对应层级。 对HTML模块DOM打标记通过Vue指令进行实现,如: v-pgc-root="moduleName" v-pgc-section="sectionName" v-pgc-anchor="" v-pgc-dynamic="@/+/- section/anchor" v-pgc-title=".selector" v-pgc-input="['text/option/checkbox', (value) => this.xxx = value]" // 可以参考活动配置里的类型 v-pgc-error 指令会在被标记的DOM节点中留下data-对应标记,方便HTMLElement.querySelectorAll按层级查询父子级节点实例,构建对应的PgcNode节点树。 ### 类设计 PgcView + engine:PgcScriptEngine + viewElement:HTMLElement + tree:PgcTree + context:PgcContext - getElement():HTMLElement - getEngine():PgcScriptEngine - focusing(anchor):PgcTreeNode - refresh():void - nextStep():PgcTreeNode - previousStep():PgcTreeNode - gotoStep(path):PgcTreeNode - currentMinds(path):Map - currentContext(path):PgcContext PgcScriptEngine - parse(tree, script):PgcTree PgcTree + name:string + refers:Map + root:PgcTreeNode - getName():string - getRoot():PgcTreeNode - seek(name):PgcTreeNode - getRefer(name):PgcTreeNode PgcTreeNode + id:string + title:string + line:string + belongTree:PgcTree + source:PgcSpot + children:Map - getId():string - getTitle():string - getSource():PgcSpot - getBelongTree():PgcTree - getChildren():Map - add(treeNode):void - remove(treeNode):void - commands():Map - toDataJson():any - activeView():void PgcContext + parent:PgcContext + view:PgcView + values:Object + modules:Map + declarations:Map + subContexts:Map - getView():PgcView - getParent():PgcContext - getValue(key):any - setValue(key, value):boolean - arouseSubContext(path):PgcContext - parseReferExpr(expr):string - addInputWatcher(spot, callback):void PgcCommand + line:string - getLine():string - optSequence():Array - toDataJson(context):any PgcOptExpr + expr:string - getExpr():string - getTag():string - toOpt(context):PgcOpt PgcOpt + expr:PgcOptExpr - getExpr():PgcOptExpr - doOpt(context, node, anchor):any - doCancel(context, node, anchor):any PgcSpot + name:string + path:string + element:HTMLElement + parentElement:HTMLElement + srcParentElement:HTMLElement - getName():string - getPath():string - getElement():HTMLElement - getParentElement():HTMLElement - getSrcParentElemnt():HTMLElement - emit(eventType):void - destroy():boolean PgcSection->PgcSpot + view:PgcView + root:PgcSection + parent:PgcSection + children:Map + title:PgcTitle - getView():PgcView - getRoot():PgcSection - getTitle():PgcTitle - inputReset():void - errorReset():void - errorValidate():void - getParent():PgcSection - getChildren():Map - getAllAnchors():Map - seekSection(name):PgcSection - seekAnchor(name):PgcAnchor PgcAnchor->PgcSpot + root:PgcSection + parent:PgcSection + title:PgcTitle + input:PgcInput + error:PgcError - getRoot():PgcSection - getParent():PgcSection - getTitle():PgcTitle - getInput():PgcInput - getError():PgcError PgcAnchorDynamic->PgcAnchor + optType:string + optExpr:string - getOptType():string - getOptExpr():PgcOptExpr PgcTitle->PgcSpot + content:string - getContent():string PgcInput->PgcSpot - getValue():any - set(value):any - reset():any PgcError->PgcSpot + type:string + message:string - getType():string - getMessage():string - validate():boolean - reset():boolean ### 类核心流程 1. 创建一个PgcView绑定一个DOM元素,根据指定的DOM元素作为根节点进行扫描子DOM元素,查找所有带有标记的DOM节点,创建所有PgcSection和PgcAnchor实例,组到PgcView中。 2. 创建一个PgcScriptEngine实例解析脚本,生成PgcTree,每行都是一个PgcTreeNode,并记录对应的脚本行内容(line);并把全局静态声明的内容执行生成数据记录到全局PgcContext中。 3. 遍历PgcTree,取PgcTreeNode对应脚本行line根据语法解析为指令,创建PgcCommand实例,每个PgcCommand中再根据语法拆分为各个PgcOptExpr以及相应的PgcOpt示例。 4. 初始化PgcContext,设置当前视图为根视图,每个PgcSection对应一个子级PgcContext实例。 5. 根据PgcTree中的第二级节点,每个节点为一个PgcView步骤视图,每个视图需要执行的脚本信息来自二级节点的子节点的内容。 6. 展示视图时,将视图关联的所有节点的source执行元素挪动,挪动到当前容器元素下,按脚本声明顺序从上到下展示。 7. 根据用户对当前视图的操作或者包含的静默执行脚本,触发当前视图相关的PgcCommand,并执行对应解析得到的PgcOptExpr对应的PgcOpt操作。 8. 每次操作更新后调用PgcView的refresh方法, 重新执行第1步扫描操作,更新信息到PgcSection和PgcAnchor中。 9. 重复步骤7-8,直到用户在所有步骤视图中完成所有操作。 ### 脚本案例 ``` // {预置配置} # 主界面 - 概要: @概要#概要视图 - 玩法: @业务玩法#配置视图 - 权益: @权益#权益视图 // 内容块 ## 概要 [基本信息] - 活动编号`营销活动编号` - 活动名称 - (?是否为营销中心活动/*营销平台中创建的活动编号,用于推送报名以及达标判定*/) 营销中心活动编号 - (?) 所属机构 - (?) 活动时间 [活动规则] - 活动规则=@活动规则 [参与条件] - 报名时间 - 不在报名时间提示文案 - (?) 高级报名设置 -- 登录是否需要证件号 -- 是否校验建行客户参与 -- 是否自动登录活动 -- 是否限制活动期间总报名人数 -- 是否限制活动每日总报名人数 -- 是否限制每日报名时间段 ## 权益 [达标奖励池] - 达标奖励池 [奖励池设置] - 奖励设置 // 定义块 ## 活动规则 - =可选规则模板1 - =可选规则模板2 - =可选规则模板3 ## 表单操作 - [@提交表单:提交:提交表单=@] - [@重置表单:重置:重置表单=@] ## 专题广告位配置 [+楼层配置`@楼层名称`:添加楼层:(Silent) +楼层配置$=@] - [-楼层配置:删除楼层:(Silent) -楼层配置$=@] - [楼层展示] -- 楼层名称 -- 楼层类型预览 -- 楼层背景图 -- [+资源位配置`@资源位名称`:设置栏位:(Silent) +资源位配置$=@] --- [-资源位配置:删除栏位:(Silent) -资源位配置$=@] --- [资源位展示] ---- 资源位名称 ---- 资源位位置 ---- 资源位图片 ---- 资源位标识 ---- 展示优先级 --- [投放链接] ---- 微信端链接 ---- 手机银行端链接 ---- 建行生活端链接 ---- 建行生活小程序端链接 ---- 裕农通端链接 ---- 其他渠道端链接 ---- (?) 是否全链活动 --- [投放管控] ---- 是否展示 ---- 限制地区访问 ---- 投放客群 ---- (?) 关联产品素材 - [楼层管控] -- 是否展示 -- 投放客群 // {业务配置} // ========== ## 业务玩法 [遇“建”专区] - (Silent) 配置专区类玩法=Y - (Silent) 玩法名称=遇“建”专区 - [首页设置] -- (Silent) 首页=@||(Silent) +页面设置$=@|页面名称=首页 -- @专题广告位配置.首页 - [联盟页设置] -- (Silent) 联盟页=@||(Silent) +页面设置$=@|页面名称=联盟页 -- @专题广告位配置.联盟页 [一重礼:升金有礼] - (Silent) 配置指标达标类玩法=Y - (Silent) 玩法1=@ - [玩法周期] -- 玩法时间 - [指标设置] -- 规则设置`升金有礼指标` --- (?) 任务跳转链接 - [奖励设置] -- (^) 发放奖品 [二重礼:分享有礼] - (Silent) 配置指标达标类玩法=Y - (Silent) 玩法2=@ - [玩法周期] -- 玩法时间 - [指标设置] -- 规则设置 --- (?) 任务跳转链接 - [奖励设置] -- (^) 发放奖品 ``` ### 交互设计要求 突出视觉重心 突出焦点区域 思维泡设计 多级导航设计 ### 关键节点 框架界面搭建 1 树数据解析 1 指令执行 1 动态表单 1 引用依赖 1 直接赋值 思维泡 错误处理 ``` ### 基础信息 [活动基本信息] - 活动名称\`营销活动名称\` - 活动类型 - 活动状态 - 活动描述 ### 时间设置 [活动时间] - 开始时间 - 结束时间 [高级时间设置] - 预热时间 - 报名截止时间 ### 奖励设置 [奖励基本设置] - 奖励类型 - 奖励发放方式 [奖励详情] - [+奖励项\`好礼$\`:新增奖励:(Silent) +奖励项$=@] -- [-奖励项:删除奖励:(Silent) -奖励项$=@] -- [奖励项基本信息] --- 奖励金额 --- 中奖概率 --- 发放上限 --- 是否必中 -- [+奖励条件:新增条件:(Silent) +奖励条件$=@] --- [-奖励条件:删除条件:(Silent) -奖励条件$=@] --- 条件类型 --- 条件值 --- 单位 ### 首页 - (Silent) 页面配置.首页=@||(Silent) 页面配置.页面0.页面名称=首页|(Reload) 页面配置 - (Silent) 页面配置.首页=@||(Silent) 页面配置.+页面$=@|(Silent) 页面配置.页面$.页面名称=首页|(Reload) 页面配置 - @页面配置.首页 ```