
简介面向具备一定 Axure 基础的产品经理、UI/UX 设计师与前端原型开发人员的变量与函数进阶手册聚焦原型交互中跨页面传值、动态列表、响应式布局等难点帮助读者把静态线框推进到可演示的高保真交互。压缩包仅含 1 个 PDF 文档体积约 556KB轻量便于随身查阅与打印批注内容按全局变量、中继器数据集、元件、页面、窗口、鼠标指针及数值、字符串、日期处理等模块组织并穿插布尔逻辑运算符与表达式示例。已有 218 人学习说明其在原型进阶人群中具备一定参考价值。读者可从中获得各类变量与函数的用途对照、常见表达式写法、分页与条件分支的实现思路以及命名规范、作用域管理、数据类型匹配等避坑提示适合边操作软件边查阅用来提升原型逻辑的严谨性与可维护性。1. 表达式原样显示成方括号问题往往不在语法很多人第一次在 Axure 里做动态交互都会撞上同一个画面明明在文本里写了[[userName]]预览时页面上却老老实实印着两个方括号和一串英文。反直觉的地方在于这跟“变量没定义”和“语法写错”基本无关——Axure 的[[ ]]从来不是编辑器里的字符串替换而是一条只在特定执行时机被触发的求值通道。搞懂这条通道什么时候开、谁在往里塞值比背下几十行函数表有用得多。变量与函数系统正是 Axure 原型设计从“画页面”跨到“跑逻辑”的分水岭交互设计里那些跨页面传参、动态列表、响应式布局、前端开发式的条件判断底层全压在这套求值模型上。2. 求值模型[[ ]]里的内容在什么时候被真正算出来2.1 编辑时是文本运行时才是表达式Axure 把“你写的东西”和“跑起来的东西”当成两码事。你在元件文本属性里敲下的[[userName]]在编辑器里只是一段普通字符运行时才会被解析成一次求值。求值点大致分三类页面加载OnPageLoad、动作执行设置文本、设置值、显示/隐藏、移动等、中继器每行加载OnItemLoad。写在求值点之外的地方比如元件名称、交互备注运行时不会求值于是你就看到了方括号。// 写在矩形的「文本」属性里页面加载时求值一次属于模板式 当前登录用户[[userName]] // 写在「设置文本」动作的值里只有动作被触发时才求值属于表达式式 [[ 当前登录用户 userName ]]这两段看着像机制完全不同。第一段里[[ ]]外面是纯文本括号内是变量名运行时做的是“把括号段替换成值”第二段整个值就是一个表达式括号内是完整的 JS 风格运算字符串常量必须带引号拼接靠变量名不带引号。参数上要记住一条括号内写的是变量名本身不是[[userName]]套[[userName]]嵌套写会让解析器把内层当成不存在的标识符结果是空字符串——空值不会报错只会安静地消失这也是它最坑的地方。2.2 全局变量的生命周期它活在同一个运行时里全局变量定义在项目级有一个默认值。原型加载的那一刻运行时把默认值灌进去之后每次“设置变量值”动作都会覆盖它。它的存活范围是同一个浏览器标签页里的运行时内存页面跳转不会销毁刷新才会回到默认值。跨页面传参之所以成立靠的就是这一点页面只是渲染单元变量挂在运行时上不在页面上。// 登录按钮的「设置变量值」动作两条 userName [[ LVAR1.text ]] // LVAR1 绑定输入框 txtUser isLoggedIn true // 布尔也用字符串存别指望有类型 // 首页 OnPageLoad用条件表达式决定文案 [[ isLoggedIn true ? 欢迎, userName : 请先登录 ]]这里有两个细节值得说透。第一全局变量没有类型系统你存进去的true是字符串写isLoggedIn true可能拿到 false稳妥做法是两头都用字符串比较。第二也是血泪经验新手容易把变量名和元件名混着用[[txtUser]]拿到的是元件对象在文本里会被转成它的文本内容但一旦进了数值运算就变成 NaN所以绑定元件时显式写.text更保险。命名规范不是形式主义。全局变量的作用域是整个原型两个页面各定义一个name后加载的会覆盖先加载的数据污染来得毫无征兆。我一般要求变量名带业务前缀比如login_userName、filter_keyword一眼能看出归属也能避开page、window、this这类内置标识符。2.3 作用域优先级局部变量为什么能压过全局变量局部变量LVAR1、LVAR2…是在动作编辑器里临时定义的只在这个动作或这条条件判断内有效。它的价值不在于“省事”而在于给动作一个稳定的输入快照——全局变量随时可能被别的交互改掉局部变量在动作开始时绑定好中途不会变。// 动作级局部变量LVAR1 绑定元件 txtKeywordLVAR2 绑定全局变量 filter_keyword // 全局变量里也有一个叫 keyword 的变量故意同名测试遮蔽 [[ 输入 LVAR1.text / 全局 keyword ]]常见的实现思路是这样一个名字解析顺序局部变量 → 当前中继器 Item 上下文 → 全局变量 → 内置对象This、Target、Page、Window、Now。也就是说局部变量名和全局变量名撞车时局部赢。这不是 bug而是刻意的设计取舍——动作内部应该优先使用自己声明的输入避免被外部状态偷偷劫持。理解了这一层你就能解释另一种常见现象同一个表达式放在中继器模板里能出数据挪到页面上的文本框里就变成空因为Item这个上下文只在模板行内存在。3. 中继器原型里的「内存表 渲染循环」3.1 Item 与 Repeater行对象和容器对象不能混用中继器的数据集本质是一张二维表列名就是表头行是记录。Item指向“当前这一行”Repeater指向“整个中继器容器”两者是不同层级的对象混用是最高频的错误来源。模板行内部只能用Item因为渲染循环执行到哪一行Item就临时指向哪一行容器级的信息总条数、当前页码才用Repeater。// 中继器模板行内的文本元件每加载一行就求值一次 [[Item.name]] —— [[Item.price]] 元 // 行序号与斑马纹判断index 从 1 开始 第 [[Item.index]] 行 / 偶数行[[Item.isEven]]Item下挂的行函数里index从 1 开始而不是 0isFirst和isLast用来做首尾特殊样式isEven、isOdd做隔行底色isMarked配合“标记行”动作实现多选isVisible反映当前行是否被隐藏。这些属性不是静态字段而是每次渲染时按行的实际状态算出来的所以你在模板里写[[Item.isLast ? 最后一页 : ]]每一行拿到的结果都不同——这正是渲染循环的意义。// 放在中继器外部的统计文本里用容器对象不要用 Item 共 [[Repeater.dataCount]] 条当前第 [[Repeater.pageIndex]] / [[Repeater.pageCount]] 页参数说明Item.ColumnName里的列名必须和数据集表头完全一致大小写敏感多一个空格就取不到值且不会报错只返回空。这一点和后端模板引擎的习惯不同后者通常有位子占位符做约束而 Axure 是静默失败。3.2 dataCount、itemCount、pageCount 三个数字为什么会对不上这三个计数看着都像“条数”实际量的是三件不同的事。dataCount是数据集里的总行数是数据的真身itemCount是当前已加载、参与渲染的项数筛过、分过页之后它会缩水visibleItemCount是当前页里真正可见的项数再叠一层隐藏状态。pageCount和pageIndex则是分页窗口的坐标。// 筛选后对比dataCount 不变itemCount 变小 数据集总量[[Repeater.dataCount]] / 当前加载[[Repeater.itemCount]] // 自己算页数时除法必须向上取整否则 11 条 5 条一页会算成 2 页 [[ Math.ceil(Repeater.dataCount / 5) ]]为什么要把“数据量”和“渲染量”分开因为中继器不是一次性把所有行都画到画布上它只维持一个渲染窗口。分页、筛选、动态增删都会改变窗口内容但不改变底层的表。这个区分带来的直接好处是做“共 N 条当前显示 M 条”这种交互时不用额外存状态两个属性一减就知道有没有被筛掉。踩坑点在于很多人用itemCount去做分页总页数计算结果加了筛选之后页数乱跳——正确做法永远用dataCount算总页用itemCount显示当前。3.3 OnItemLoad 与分页渲染窗口决定了你看到哪几行OnItemLoad是每一行渲染时触发一次的循环体。它执行的次数等于当前渲染的项数不是数据集行数。这一点决定了中继器的能力边界你可以在每行加载时读取该行数据、做条件样式、拼装文本但不能在行内修改整个数据集——那是“设置数据集”动作的活。下面这段是很多动态列表的骨架行内按Item的值做条件分支再用Item.isFirst做首行加粗。// OnItemLoad 中给行内标签设置文本 [[ Item.price 100 ? 高价 Item.price : 常规 Item.price ]] // 首行加个标记用于视觉引导 [[ Item.isFirst ? ★ 推荐位 : ]]逻辑说明表达式在行加载时求值Item临时绑定当前行所以每一行算出的字符串都不同Item.price 100走的是数值比较前提是数据集里该列存的是数字而不是带单位的字符串否则会掉进字典序比较的陷阱第 5 章会展开。分页控制的本质是移动渲染窗口pageIndex加一窗口后移一页OnItemLoad重新对新的那批行执行一遍所以你写在行内的初始化逻辑不会“记住”上一页的状态——需要跨页保持的东西得存在全局变量里。4. 取值与运算四类上下文对象和一类函数库怎么配合4.1 This 与 Target同一段表达式绑定对象不同结果就不同This指触发当前事件的元件Target指这个动作作用的目标元件。多数时候两者相同因为元件自己触发、自己变化但在“多个按钮共用一个动作”的复用场景里它们必然分叉触发的是某个按钮目标是页面上那个统一的结果标签。// 五个分类按钮共用一段「设置文本」写进同一个 lblResult // This 是被点击的那个按钮Target 是 lblResult [[ 你选择了 This.text ]] // 反过来读目标当前的文本常用于切换式交互 [[ Target.text 展开 ? 收起 : 展开 ]]参数说明This.text取的是触发元件当前的文本This.x、This.y、This.width、This.height分别对应坐标和尺寸This.rotation是旋转角度This.opacity是透明度This.isVisible判断可见性This.isSelected判断选中态This.scrollX、This.scrollY是元件内部滚动位置。读属性不难难在写表达式里做赋值比如[[This.width]] 200是很多教程里的写法实际执行靠的是“设置尺寸”这类动作表达式本身只负责算值。把“算”和“改”分开是理解这套系统的关键一步。4.2 Page 与 Window两个“宽度”差在哪什么时候该用哪个Page.width是原型页面的设计画布宽度是一个固定值你在页面设置里定多少就是多少。Window.width是预览时浏览器视口的宽度随窗口缩放实时变化。做响应式判断必须用Window用Page判断等于在问“我的画布多宽”永远得不到你想要的断点效果。// 根据视口宽度打布局标记 [[ Window.width 1200 ? lg : (Window.width 768 ? md : sm) ]] // 滚动超过 100px 时显示回顶按钮 [[ Window.scrollY 100 ? show : hide ]]逻辑说明这段嵌套三元表达式从左往右短路求值先判大屏再判中屏都不满足才落到sm。Window.scrollY是相对文档顶部的滚动距离配合“窗口滚动时”事件可以做出滚动吸顶、进度条这类效果。Page.url和Page.title在预览环境下拿到的是预览地址不要指望它和最终发布环境一致做参数传递时这点要留神。Page.scrollX、Page.scrollY与Window的同名属性容易混前者偏向页面容器自身的滚动后者是视口滚动实际使用中以预览效果为准别死记。4.3 Cursor 与拖动累计量为什么 totalDragX 才可靠Cursor.x、Cursor.y是鼠标相对页面的坐标Cursor.dragX、Cursor.dragY是本次拖动过程中的增量Cursor.totalDragX、Cursor.totalDragY是从按下到现在的累计距离Cursor.downTime是按住时长。区别在于“增量”会被清掉累计不会——判断“用户到底拖没拖够”必须用累计量。// 拖动超过 50px 才算有效拖拽横向纵向任一方向满足即可 [[ Math.abs(Cursor.totalDragX) 50 || Math.abs(Cursor.totalDragY) 50 ]] // 长按判定按住超过 800ms [[ Cursor.downTime 800 ]]这里用Math.abs是因为向左拖totalDragX是负数直接比大小会把左滑判成无效。参数上Cursor系列只在鼠标相关事件点击、拖动、移动的上下文里有意义写在页面加载里拿到的就是初始值别指望它实时刷新。拖动交互最常见的翻车是把dragX当成累计量用结果一松手就归零判定永远不成立。4.4 Number、String、Date、Boolean函数库其实是 JS 的影子Axure 的运算函数基本是 JavaScript 同名能力的子集语义也一致。理解这一点很多行为就不用背了按 JS 的规则推就行。数值侧toFixed(n)返回的是字符串Math.round()返回的是数字两者不能互相替代。做金额展示用toFixed做后续计算用Math.round。参与运算前先确认是数值字符串会走隐式转换转不动就是 NaN而 NaN 参与比较永远为 false。// 求和并保留两位小数用 *1 强制走数值运算避免字符串拼接 [[ (LVAR1 * 1 LVAR2 * 1).toFixed(2) ]] // 随机数取整到 1-100 [[ Math.floor(Math.random() * 100) 1 ]]字符串侧substr(start, length)是“从哪开始、取几个”substring(start, end)是“从哪到哪”slice支持负数下标。三个都能截串混用的后果是截出来多一个字或少一个字而且不报错。split返回数组indexOf返回首次出现位置、找不到返回 -1判断存在性时别写成 0位置 0 也是存在的。// 从 2024-06-01 这种格式里拆年月 [[ LVAR1.substr(0, 4) ]] 年 [[ LVAR1.substr(5, 2) ]] 月 // 去空格后再比较避免用户多打一个空格导致匹配失败 [[ LVAR1.trim() admin ]]日期侧Now是内置的当前时间对象getMonth()返回 0 到 11所以展示时要 1getDay()返回 0 到 60 是周日Date.parse(2024-06-01)返回毫秒时间戳比较两个日期先后用它最稳。getTimezoneOffset()返回的是分钟数不是小时。// 拼一个 yyyy-MM-dd HH:mm注意月和分不会自动补零 [[ Now.getFullYear() - (Now.getMonth() 1) - Now.getDate() Now.getHours() : Now.getMinutes() ]]布尔侧是宽松相等1 1为真在 Axure 的表达式里支持度不稳定稳妥做法是两端统一类型再比。和||有短路特性If(cond, a, b)只算命中的那一支把重计算放在b里条件不成立时就不会执行。5. 避坑与排查原型跑不通时先看这五处5.1 现象页面上原样显示[[userName]]原因通常是写在了不会被求值的位置。元件名称、部分说明性属性、以及某些没有绑定动作的静态文本运行时都不会解析方括号。另一种情况是表达式本身写错解析器放弃求值原样回显。解决办法分三步先确认这段文字是不是写在元件的“文本”属性或动作的“值”里再确认所在动作确实被触发过加一个临时的点击提示验证最后检查括号是否成对、变量名是否带多余空格。排查时养成一个习惯——在页面角落放一个隐藏的调试文本框专门显示中间值比盯着成品页猜快得多。5.2 现象中继器十行数据渲染出来全是第一行原因基本是把Item写成了固定值或者在OnItemLoad里用“设置文本”把模板文本覆盖成了常量。还有一种隐蔽情况模板行里的表达式引用了一个全局变量而这个全局变量在每行加载时恰好没被更新于是十行拿到同一个值。解决办法是先分清层级——行内用Item.Column行外用Repeater.xxx。然后检查OnItemLoad里有没有写死的赋值动作它会覆盖掉模板里的表达式。如果是全局变量的问题把值在行加载时绑定到局部变量再使用快照能保证每行拿的是自己的那份。5.3 现象数值比较结果和肉眼看到的相反9 10返回 true原因是没有做类型统一字符串比较走的是字典序逐位比字符编码9比1大所以9 10成立。这在中继器排序、价格筛选里非常常见因为数据集里的数字列很容易被当成文本存。解决办法是在比较前强制转成数值用* 1或Math系列函数把字符串拖进数值域。如果数据来源不可控先用trim()清空格再转换最后用isNaN之类的判定拦住脏数据。我一般会在筛选动作里先算一次转换后的值存进局部变量后续比较全用那个变量避免每处都写一遍转换逻辑。5.4 现象日期少一个月、差一天或者跨时区对不上原因是getMonth()从 0 开始这是最经典的一个偏移其次是时区getTimezoneOffset()返回分钟不同环境下的显示可能不一致再就是手写拼接时没补零2024-6-1这种格式在字符串比较时会出问题。解决办法展示月份统一1拼接时对月和日做补零处理比较日期先后不用字符串比统一转Date.parse()的时间戳比大小跨时区场景下只做展示的用本地时间做业务判断的统一走时间戳。5.5 现象跳转页面后全局变量变回默认值原因多半是新开了标签页或窗口。全局变量活在单个运行时实例的内存里新标签页就是新实例变量自然重置预览刷新也一样。同一个标签页内的页面跳转不受影响这是很多人误判的地方。解决办法是先确认跳转方式同标签内跳转就能保住值确实需要跨标签页的用 URL 参数把关键值带过去接收页在加载时读出来写回全局变量相当于手动做一次序列化。这个思路和前端开发里的会话状态管理是一个道理只是原型阶段不需要做得那么重。6. 设计取舍为什么这套表达式语言故意做得很薄Axure 完全可以把表达式引擎做得更强——上完整 JS 引擎、加类型检查、给控制台报错。它没有。这不是能力问题是取舍原型的第一诉求是快速试错和沟通不是交付级代码。强类型意味着每写一个表达式都要先声明报错意味着每次改动都要停下来处理异常这两件事都会把“画一版给评审看”的节奏拖垮。所以它选择了宽松、静默、能跑就行的路线。代价也很明确错误不报只表现为空值、0 或原样显示。这正是这套系统最难的地方——它不给你黑匣子只给你结果。于是调试只能靠“打点”我一般用三栏法把一条链路拆开验证。调试位置写什么用来确认源值[[userName]]变量到底有没有被赋值中间值[[userName.length]]值是不是空串、有没有被转成字符串结果值[[ 欢迎, userName ]]拼接逻辑是否成立三栏并排放哪一栏开始空问题就出在它前面那一步。这套方法听着笨但比反复改表达式猜原因快得多尤其在中继器里行内表达式的执行上下文和外面不一样光看成品页根本看不出是哪一行出错。再往深一层看这套设计其实在教一件事分清“数据在哪”和“数据怎么算”。全局变量是跨页面的共享状态局部变量是动作内的输入快照中继器数据集是结构化数据的容器三类东西的生命周期完全不同混用就是各种玄学问题的根源。把生命周期想清楚Item为什么在页面文本框里失效、全局变量为什么刷新后重置、dataCount为什么和itemCount对不上全都能自己推出来不用背。从那以后我每次搭带交互的原型都强制先画一张极简的数据流草稿哪些值要跨页面活、哪些只在一个动作里活、哪些是表里的行。写完再动手返工少一半。希望帮到你。本文还有配套的精品资源点击获取