泛微OA的二次开发里,前端JS大概是最"接地气"也最容易踩坑的一块。它不像后端接口那样有完整的文档和调试日志,很多时候就是在一个表单、一个明细表、一个按钮上,凭经验加一段脚本把业务逻辑补齐。我在这套系统里泡了几年,攒下来的常用代码块少说也有一两百个——字段显隐、明细联动、提交校验、弹窗回填、页面跳转、数据格式化,翻来覆去就是那几类。真正让人头疼的不是写不出来,而是写出来不生效、换个环境就报错、三个月后自己看不懂。
这篇东西是我自己的一套代码块整理思路和实操记录,围绕泛微OA常用JS代码块展开。核心解决四件事:代码该写在哪个入口、常用代码块长什么样、为什么这么写、出问题了怎么查。适合两类人:一类是刚接手OA运维和表单配置的同学,照着抄能跑起来;另一类是做了一段时间但代码散落各处、想系统梳理一下的老手。文中所有API写法以常见实践为准,泛微不同版本(E8、E9、E10)的细节有差异,具体请以你自己的环境实测为准。
1. 先把场景摸清楚:泛微OA里JS到底管什么
1.1 三个写JS的地方,位置写错全白干
泛微OA里能写JS的地方远不止一处,但每一处的运行环境和可用对象都不一样,这是新手最容易翻车的地方。我把它们归成三类,你写之前先判断自己属于哪一类。
第一类是流程表单,也就是走审批流的那张单子。这里的JS跑在流程页面上,有完整的WfForm对象可用,字段控制、明细操作、提交校验都靠它。这是使用频率最高的场景,八成以上的需求都落在这里。
第二类是建模应用(有些人叫建模表单、建模模块),表单和列表页面是独立的,字段控制逻辑跟流程表单不完全一样,明细行操作的API也有差别。很多人把流程的代码直接复制到建模里,结果WfForm is not defined报错,就是没分清。
第三类是门户和自定义页面,纯HTML+CSS+JS的舞台,这里没有WfForm,只能用原生DOM和jQuery自己处理。适合做数据看板、入口导航、自定义列表。
判断方法很简单:打开浏览器控制台,敲一下typeof WfForm,返回object就是流程表单环境,返回undefined就说明你不在流程页面上。这个小动作能省掉大量无效调试。
注意:建模和门户的代码别往流程表单里搬,反之也一样。同名的字段ID在不同环境下的DOM结构完全不同。
1.2 为什么多数人第一行代码就写错:jQuery与WfForm的关系
泛微OA前端默认内置了jQuery,流程页面上还挂了一个全局的WfForm对象。理解这两者的分工,代码逻辑就顺了。
WfForm管的是业务层:字段值、字段属性、明细行、提交事件。它操作的是"字段"这个概念,不是DOM节点。你告诉它"把 field110 设成只读",它自己去找对应的DOM去改。这是官方推荐的方式,因为它屏蔽了页面结构的差异,版本升级时相对稳。
jQuery 管的是表现层:位置调整、样式修改、元素显隐、HTML拼接、事件绑定。当你需要做WfForm做不到的事,比如把某个字段整行藏起来、在字段旁边插一个自定义按钮、改一下表格宽度,就得回到jQuery。
我的原则是:能用 WfForm 的绝不用 jQuery。因为直接改DOM有两个副作用——一是页面上隐藏了但值还在,提交时照样带上;二是页面局部刷新(比如明细行增删、字段联动重绘)之后,你改过的DOM会被还原,代码就"失效"了。我见过太多"明明写了隐藏,点一下又出来了"的案例,基本都是直接操作DOM导致的。
// 推荐:走业务层,值和方法都受控 WfForm.changeFieldAttr("field110", {viewAttr: 1}); // 慎用:纯DOM操作,刷新后容易丢 jQuery("#field110").closest("tr").hide();真要用jQuery藏行,也得配合字段值一起处理,或者把它放在页面渲染完成的时机里重复执行。这一点后面第5节会细说。
1.3 我的代码块库是怎么攒出来的
一开始我也是每次遇到需求现写,写完全丢。后来发现80%的需求都在重复——"某个字段选了什么,另一个字段就必填"、"明细里选了物料自动带出规格"、"提交前检查明细不能为空"。于是我开始按"触发条件 + 动作类型"两个维度归档。
归档的好处是复用时只需要改字段ID和条件表达式,主体逻辑一模一样。我给每个代码块都加了统一的注释头:用途、适用环境(流程/建模)、依赖的字段ID、注意事项、最后修改日期。三个月后再翻,一眼就知道能不能直接用。
我建议你也这么干,用最土的办法就行——一个记事本文件、一个内部文档页面、甚至OA里的一个知识文档,只要能搜到就行。代码块的价值不在于写得多花哨,而在于它被复用的时候不需要重新思考。
2. 高频代码块第一梯队:字段控制三件套
2.1 显隐、必填、只读的三个取值与常见坑
字段属性控制是使用频率最高的代码块,没有之一。核心API就一个:WfForm.changeFieldAttr(fieldId, {viewAttr: 值})。取值含义我整理成表,这个表建议你贴在显示器边上。
| viewAttr 取值 | 效果 | 典型场景 |
|---|---|---|
| 1 | 只读,可看见但不可编辑 | 审批节点锁定已确认信息 |
| 2 | 可编辑,非必填 | 默认状态、条件解除时还原 |
| 3 | 必填,可编辑 | 条件触发后的强制填写 |
这三个值是我实际用得最多的。要注意的是,"必填"和"提交校验"是两回事。设成3之后,星号会出现,前端也会有基础的非空提示,但如果你的条件是动态变化的(比如只有金额大于10万时才必填),光靠viewAttr不够稳,还得在提交事件里再校验一次,双保险。
隐藏字段的情况稍微麻烦。如果你想彻底隐藏,常见做法是:
// 兼容写法:先尝试属性控制,再用DOM兜底 var fieldId = "field110"; jQuery("#field" + fieldId).closest("tr").hide();要注意,#field110这个ID的拼接方式是field+ 字段ID(不含 field 前缀的部分要确认清楚,不同版本拼接规则有差异)。稳妥的办法是在控制台里先console.log(WfForm.convertFieldNameToId("你的字段名"))拿到真实ID,再用jQuery("#field" + 真实ID)去试。
另一个坑是:隐藏字段的值不会自动清空。用户先填了内容,你再把它藏起来,提交时这个值照样带上去了。如果业务上要求"藏起来就当没填过",你必须补一句WfForm.changeFieldValue(fieldId, {value: ""})。
提示:字段ID和字段名的转换用
WfForm.convertFieldNameToId("字段名")和WfForm.convertFieldIdToName("field110")互转。写代码时用ID,读代码时看名字,两边都不吃亏。
2.2 按条件控制字段:从"部门经理才显示"讲起
单纯设属性不难,难的是"条件判断"这件事放在哪里、什么时机执行。我把常见触发时机分成三类。
第一类是页面加载时判断一次。适合那些一进页面就能确定的条件,比如"申请人所属部门是财务部时才显示预算科目字段"。执行时机通常是页面初始化完成后,但你要注意数据可能还没回填完,直接取值可能拿到空字符串。稳妥做法是延迟一小会儿再执行,或者监听字段变化后再跑。
// 页面加载后执行一次的条件控制 jQuery(document).ready(function () { setTimeout(function () { var dept = WfForm.getFieldValue(WfForm.convertFieldNameToId("申请部门")); var target = WfForm.convertFieldNameToId("预算科目"); if (dept && dept.indexOf("财务") > -1) { WfForm.changeFieldAttr(target, {viewAttr: 3}); } else { WfForm.changeFieldAttr(target, {viewAttr: 1}); } }, 300); });这里的setTimeout不是偷懒,而是必要的。泛微页面字段值是异步回填的,ready触发的时候数据表格可能还在渲染。300毫秒是经验值,我一般在200到500之间试,太短容易拿不到值,太长用户会看到字段"跳一下"。
第二类是字段值变化时联动。这需要绑定字段的 change 事件,我通常用jQuery绑在DOM上,或者用WfForm提供的注册方式(不同版本支持程度不同,先在控制台确认)。判断字符串包含用indexOf或includes,注意getFieldValue返回的可能是字符串,也可能是"值,值"这种多选拼接格式,用之前先console.log打印一次看真实结构。
第三类是审批节点变化时判断。同一个表单,不同节点看到的字段不一样,这时候用节点相关的判断变量配合条件控制,能做出很清晰的"逐步解锁"效果。
2.3 字段值读写与格式化
取值和赋值看着简单,实际操作里问题不少。
// 主表字段赋值 WfForm.changeFieldValue("field110", {value: "测试值"}); // 主表字段取值 var val = WfForm.getFieldValue("field110"); // 明细字段赋值(必须带 rowIndex) WfForm.changeFieldValue("field120", {value: "100", rowIndex: 1});三个高频坑:一是明细字段忘了带 rowIndex,赋值直接作用到了错误的位置或者没效果;二是日期字段的格式,你给字符串2026-01-01可能页面显示不出来,得给它的标准格式(常见是yyyy-MM-dd);三是选择框类字段,你给显示值不等于给了存储值,有些字段需要给内部ID才能正确显示,这种字段我习惯先手动选一次,然后在控制台打印getFieldValue看它真实存的是什么,再照着格式赋值。
格式化的代码块我也常备一个。比如金额千分位、日期补零、多选值拆数组:
// 金额加千分位 function formatMoney(num) { if (num === null || num === undefined || num === "") return ""; var n = Number(num); if (isNaN(n)) return num; return n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); } // 多选字段值转数组 function splitMulti(val) { if (!val) return []; return String(val).split(",").filter(function (x) { return x !== ""; }); } // 判断字符串是否包含(兼容老环境) function contains(str, key) { return String(str || "").indexOf(key) > -1; }这几个函数我几乎每个项目都用,直接放在代码块库最前面当"基础工具区"。
3. 明细表才是重头戏:行操作与主表联动
3.1 明细行的取数、加行、删行
明细表是泛微OA最有价值也最容易写崩的部分。核心API我列一下,都是实际在用的。
| 用途 | 常见写法 | 说明 |
|---|---|---|
| 取所有行序号 | WfForm.getDetailAllRowIndexStr("detail_1") | 返回类似 "1,2,3" 的字符串 |
| 取明细行字段值 | WfForm.getFieldValue("field120_1") | 或用行序号变量拼接 |
| 明细字段赋值 | WfForm.changeFieldValue("field120", {value: v, rowIndex: i}) | rowIndex 是行序号 |
| 加一行 | WfForm.addDetailRow("detail_1") | 不同版本参数不同 |
| 删一行 | WfForm.delDetailRow("detail_1", "2") | 第二个参数是行序号字符串 |
先说删除。删除行的第二个参数是行序号的字符串,不是数组也不是数字。如果你要删多行,用逗号拼起来;如果要删"当前行",得先拿到当前行的序号。这里有个经典难题:怎么知道用户点的是哪一行?常见做法是给明细行上的某个元素绑定事件,通过closest往上找到行容器,再从行容器的ID里正则提取出行序号。
// 在明细行上绑定自定义按钮事件(示例思路) jQuery(document).on("click", ".my-row-btn", function () { var rowDom = jQuery(this).closest("tr"); var idStr = rowDom.find("[id^='field']").first().attr("id") || ""; var m = idStr.match(/_(\d+)$/); if (m) { var rowIndex = m[1]; // 拿到行序号,可做取值、删除、计算等操作 console.log("当前行序号:" + rowIndex); } });用on做事件委托是关键,因为明细行是动态生成的,直接 bind 到元素上的事件在新增行上不生效。这个坑我踩过好几次,明明代码没错,新加的行就是点不动。
再说加行。addDetailRow执行之后,DOM渲染是异步的,如果你紧接着就getFieldValue去取新行的值,很可能是空的。我的处理方式是在加行之后用setTimeout包一层,或者用明细行操作事件(如WfForm.registerAction配合加行动作)来处理后续逻辑。
3.2 主表→明细、明细→主表的双向联动
联动是明细表最有价值的玩法。典型需求:主表选了"供应商",明细里所有行的"结算方式"自动带出该供应商的默认设置;反过来,明细里所有行金额加起来,回写主表的"总金额"。
主表到明细的写法,核心是遍历所有行再逐行赋值:
function syncToDetail(mainVal) { var rows = WfForm.getDetailAllRowIndexStr("detail_1"); if (!rows) return; rows.split(",").forEach(function (idx) { if (!idx) return; WfForm.changeFieldValue("field130", {value: mainVal, rowIndex: idx}); }); }明细到主表的合计,注意数值转换这一步不能省。明细里的金额取出来是字符串,直接相加会变成字符串拼接,"100" + "200"得到"100200"而不是300。这个错误太常见了,我第一次写合计就中招。
function sumDetail() { var rows = WfForm.getDetailAllRowIndexStr("detail_1"); var total = 0; if (rows) { rows.split(",").forEach(function (idx) { if (!idx) return; var v = WfForm.getFieldValue("field140_" + idx); var n = parseFloat(v); if (!isNaN(n)) total += n; }); } WfForm.changeFieldValue("field150", {value: total.toFixed(2)}); }用parseFloat而不是Number或直接+,是因为它遇到非数字会返回NaN,配合isNaN判断更可控。金额统一toFixed(2)保留两位,避免出现0.30000000000000004这种浮点误差。
联动的执行时机也很讲究。主表字段变化触发时,要把联动函数挂上去;明细行增删之后,还要重新算一次合计。我一般会写一个统一的"重算入口",所有可能影响结果的操作都调它,逻辑集中,不容易漏。
3.3 合计、去重与序号重排
明细表还有几个细节值得单独说。
去重是常见需求,比如明细里的物料不能重复。思路是把所有行的物料编码收集成数组,检查有没有重复项。注意泛微的明细行序号不一定是连续的(删行之后会断号),所以遍历顺序要保持,不能靠序号排序。
function checkDuplicate() { var rows = WfForm.getDetailAllRowIndexStr("detail_1"); var seen = {}; var dup = []; if (!rows) return dup; rows.split(",").forEach(function (idx) { if (!idx) return; var code = WfForm.getFieldValue("field160_" + idx); if (!code) return; if (seen[code]) { dup.push(code); } else { seen[code] = true; } }); return dup; }用对象做哈希表而不是双层循环,行数多的时候性能差别很明显。明细几十行的时候双层循环还看不出来,上百行就卡了。
序号重排是个体验优化。删行之后行号会断,用户可以接受,但如果你在页面上显示了自定义序号列,就得重新刷一遍。做法是遍历现有行,按顺序赋 1、2、3……注意别跟泛微自己的行序号搞混。
空行清理也很实用。用户加了几行又没填内容,提交时校验会报错,体验差。可以在提交前把完全空白的行删掉,或者直接允许提交但后端忽略空行。我倾向于前者,逻辑更清晰。
4. 交互与提交:弹窗、校验、提交拦截
4.1 提交前校验的标准写法
提交校验是最能体现代码块价值的地方。标准的注册方式大致是这样:
WfForm.registerCheckEvent(WfForm.OPER_SAVE + "," + WfForm.OPER_SUBMIT, function (callback) { // 自定义校验 var msg = ""; var amount = parseFloat(WfForm.getFieldValue("field150") || "0"); if (amount > 100000) { var reason = WfForm.getFieldValue("field170"); if (!reason) msg = "金额超过10万,请填写说明"; } var dup = checkDuplicate(); if (!msg && dup.length > 0) { msg = "明细中存在重复物料:" + dup.join("、"); } if (msg) { // 弹出提示,不执行 callback,提交被拦截 alert(msg); return; } callback(); });几个关键点。第一,必须调用 callback 才会继续提交,忘记调用的结果是"点提交没反应",这个bug排查起来很费时间,因为没有任何报错。第二,注册一次就够了,重复注册会导致校验执行多次,弹出多个提示框。第三,alert 不是最佳体验,但胜在稳定可靠,泛微自带的提示组件在不同版本里用法不同,如果你追求体验可以用自带的,但一定要先把逻辑跑通再换。
我还习惯把校验逻辑拆成独立函数,一个函数只干一件事,返回错误信息字符串,没有错误就返回空。这样新增规则的时候只是往列表里加一项,不动主流程。
4.2 弹窗选人/选数据与返回值回填
从其他页面选数据回填,是OA里非常高频的交互。常见有三种做法,各有取舍。
第一种是系统自带的选人组件,最稳,但定制空间小。适合选人员、部门、岗位这类标准数据。
第二种是自定义弹窗页面,做一个独立页面列出可选数据,用户点选后回调父页面赋值。灵活性最高,但要注意跨页面通信的写法,还要处理弹窗被拦截、用户直接关掉弹窗等情况。回调一般挂到window上,弹窗里调用父窗口的函数完成回填。
第三种是iframe嵌入,把另一个系统的页面嵌进来,比如从OA跳转到业务系统选单据。这种场景下会出现"子页面操作完成、父页面需要刷新或回填"的需求,属于下一节的内容。
三种方式的选择标准很简单:标准数据用自带组件,业务数据用自定义弹窗,跨系统数据用iframe。别为了省事把标准数据也做成自定义,维护成本反而高。
4.3 iframe 嵌套页面的通信与父页刷新
iframe 相关的代码块我也囤了不少,因为集成场景太常见了。
子页面回填父页面,常见做法是子页面拿到父窗口对象后直接调用父页面的函数:
// 子页面中执行 if (window.parent && window.parent.fillBackData) { window.parent.fillBackData(rowData); } // 父页面中定义 window.fillBackData = function (data) { WfForm.changeFieldValue("field180", {value: data.name}); };这里的fillBackData要挂到window上,不能是局部函数,否则子页面找不到。同源的情况下这写法最省事;跨域的话就得换消息传递的方式,两边约定好消息格式。
关闭弹窗并刷新父页面,这个组合动作特别常见:用户在弹窗里改完数据,点确认,弹窗关闭,父页面的列表要刷新看到最新结果。
// 关闭自己并刷新父页面(jQuery + 原生混用) function closeAndRefreshParent() { if (window.parent && window.parent !== window) { try { window.parent.location.reload(); } catch (e) { // 跨域时刷新不了,退化为提示用户手动刷新 console.log("无法刷新父页面:" + e.message); } } // 关闭当前弹窗/层(取决于打开方式) if (window.close) { window.close(); } }一个实际经验:先刷新父页面,再关闭自己。顺序反过来,有些浏览器里当前窗口已经关掉了,后面的刷新代码就不会执行。另外跨域时location.reload()会被拒绝,不要在没验证的情况下告诉业务方"会自动刷新",得留个兜底提示。
注意:父页面刷新会导致未保存的数据丢失。如果父页面有已填内容,刷新前最好加个确认提示。
5. 调试、排查与避坑实录
5.1 常见故障速查表
写JS最耗时的不是写,是查。我把高频故障整理成一张表,遇到问题先对照,能省掉大量时间。
| 现象 | 常见原因 | 处理方向 |
|---|---|---|
| WfForm is not defined | 不在流程表单环境,或代码执行过早 | 确认页面类型,把代码放到正确入口 |
| 赋值后页面不显示 | 字段类型格式不匹配,或rowIndex不对 | 打印getFieldValue看真实格式 |
| 隐藏的字段提交时还有值 | 只做了DOM隐藏,没清空值 | 补 changeFieldValue 置空 |
| 明细新加行的事件不生效 | 事件绑定在具体元素上 | 改用事件委托jQuery(document).on |
| 取值拿到空字符串 | 数据异步回填,取早了 | 加 setTimeout 或换触发时机 |
| 点提交没反应 | registerCheckEvent 没调 callback | 检查所有分支都有 callback |
| 校验弹窗弹了两次 | 校验事件重复注册 | 只注册一次,或加重复保护 |
| 明细合计变成字符串拼接 | 没做数值转换 | 统一 parseFloat + isNaN 判断 |
| 修改DOM后样式被还原 | 页面局部重绘 | 走 WfForm 业务层,或监听重绘重执行 |
| 弹窗回填后数据没显示 | 回调函数没挂到 window | 挂到全局,检查作用域 |
这张表我基本是照着踩坑顺序写的,前四条会消耗掉新手大量时间。
5.2 我的调试三板斧
第一板斧是控制台直接验证。不确定API怎么用的时候,我不会去猜,直接在控制台里敲。typeof WfForm、WfForm.convertFieldNameToId("字段名")、WfForm.getFieldValue("field110"),敲一遍结果全出来了。这比翻文档快得多,而且拿到的是你这个环境里的真实结果。
第二板斧是打印日志定范围。代码不生效的时候,第一件事是在关键位置打console.log,看代码到底有没有执行到。如果日志没出来,说明触发时机不对或者代码根本没加载;如果日志出来了但结果不对,说明是逻辑问题。这一步能把问题范围缩小一半。
第三板斧是排除法。把代码注释到只剩一行,看这行有没有效果,然后逐步放开。很多人一上来就盯着整段代码找问题,效率很低。我宁愿花两分钟做二分排查,也不愿意盯着代码猜半小时。
顺带说一句,浏览器缓存是隐形杀手。改了代码刷新页面没变化,先强制刷新(Ctrl+F5)试一下,再看是不是缓存问题。我有一次排查了四十分钟,最后发现是缓存。
5.3 那些文档里不会写的坑
这几个坑,文档里基本不会提,但实际做项目一定会遇到。
坑一:字段ID会变。你以为字段ID是固定的,但在测试环境配的字段复制到正式环境之后,ID可能不一样。所以任何跨环境迁移,都必须重新确认字段ID,我做迁移的时候第一件事就是在正式环境控制台把所有用到的字段ID打印一遍,跟代码里的对照。
坑二:明细行序号不连续。删了几行之后,剩余行的序号是断的,比如只剩 1、3、5。所有遍历逻辑都必须基于getDetailAllRowIndexStr返回的真实序号,不能自己写for (i = 1; i <= 行数; i++)。这个错误在测试时不一定暴露(因为测试时很少删行),上线后用户一删行就出问题。
坑三:多个人同时改同一段代码。OA的代码是配在表单上的,没有版本控制。两个人各自改一段,后保存的覆盖前面的,谁都不知道。我的做法是代码统一抄一份到内部文档,保留修改记录,表单里只放最终版本。看着麻烦,但出问题的时候能救命。
坑四:字段联动会互相触发。你在A字段的变化事件里改B字段的值,如果B字段也绑了变化事件,就会连锁触发。轻则多执行几次,重则死循环把页面卡死。做法是加一个"锁定标志",处理期间不再响应新事件。
var isSyncing = false; function safeSync() { if (isSyncing) return; isSyncing = true; try { // 联动赋值逻辑 } finally { isSyncing = false; } }用try...finally保证标志位一定被释放,即使中间报错也不会导致标志位永久锁死,这是个小细节但很实用。
坑五:移动端和PC端表现不一致。同一段代码在电脑上跑得好好的,在移动端页面上字段结构完全不同,DOM选择器全部失效。如果业务上有移动端审批需求,条件控制的代码要做兼容判断,不能想当然。
6. 让代码块变成资产:命名、复用与升级
6.1 命名与注释:三个月后你还看得懂
代码块最大的成本不在写,在读。我给自己定了几条规矩,执行下来效果很明显。
函数名说清楚干什么,checkDetailDuplicate比check1强一百倍。变量名不要用拼音缩写,deptId可以,bm就别用了。每个代码块头部写三行注释:用途、适用环境、依赖字段。看起来是小事,但下次接手的人(很可能是三个月后的你自己)会感谢你。
/** * 用途:明细物料重复校验 * 环境:流程表单 * 依赖:明细 detail_1,物料编码字段 field160 * 注意:需在提交校验事件中调用,返回重复项数组 */ function checkDetailDuplicate() { // ... }我还习惯在代码里保留"为什么这么写"的注释,比如"这里必须加延迟,因为字段值是异步回填的"。这类注释比"给变量赋值"有用得多,因为它记录的是踩坑经验。
6.2 版本升级时的迁移清单
泛微版本升级是个绕不开的话题,E8升E9、E9升E10,前端API会有变化。我总结了一份迁移检查清单,升级前过一遍,能提前发现大部分问题。
- 逐条确认使用的 API 在新版本是否仍然支持,尤其是明细行相关的方法
- 确认
WfForm的事件注册方式有没有变 - 确认字段ID在新环境是否保持一致
- 确认依赖的字段类型没有变化(比如文本变成了选择框,赋值格式就不同了)
- 在测试环境用真实数据跑一遍全流程,不要只点两下就算通过
- 检查移动端表现
升级前我会把测试环境当作"新版本预演",所有代码先在新环境跑一遍,人工记录每个代码块的状态,兼容的、要改的、废弃的分开标记。这份记录就是升级时的操作手册。
6.3 代码块与后端接口的配合
前端代码块不是孤立的,很多时候要和后端接口配合。常见两种场景。
一种是数据源联动。字段选择之后要远程拉取数据回填,这时候用的是异步请求,要注意请求的时序:用户连续切换选项时,先发的请求可能后返回,导致界面显示的是旧数据。处理方式是给请求加标识,只接受最后一次请求的结果。
另一种是提交前的服务端校验。前端校验只能防手误,真正的业务规则还得后端把关。我的习惯是前端做基础校验提升体验,后端做完整校验保证数据正确,两边各司其职,不要指望前端拦住所有非法数据。前端代码是用户可以绕过的,这点心里要有数。
另外,涉及对接其他业务系统(比如从OA发起单据同步到其他平台)的场景,接口调用要处理好超时和失败提示,不能让用户点了提交之后页面卡在那里没反应。给用户明确的反馈,比代码写得多优雅重要得多。
最后分享一个我个人的习惯:每完成一个需求,就把这次用到的代码块回头整理一遍。新写的存进库,复用的标注一次,过时的删掉。这个过程每次只花几分钟,但半年之后你的代码块库会变得非常好用,绝大多数需求都能在十分钟内找到起点。这个习惯的价值,比任何单个代码块的技巧都大。