
# 变更记录
| 产品版本 | 更新内容 | 更新日期 |
| --- | --- | --- |
| V7.0.1 | 发布了前端页面脚本功能,可以编写javascript代码,从而快速实现特定的业务控制逻辑等需求 | 2024-10-24 |
## 一、功能概述
通过前端页面脚本扩展功能,用户可以直接编写javascript代码,从而快速实现特定的业务控制逻辑
易于使用,对现有业务逻辑进行扩展操作简单(前提掌握脚本开发)
部署方便,脚本跟随元数据一起导入导出、补丁升级
支持内置API和组件事件等,增强了前端页面定制化开发能力
**如想快速查看相关定制效果,可直接点击"**[案例展示](https://vip.kingdee.com/article/588370901062560768?productLineId=29&isKnowledge=2&lang=zh-CN#9)**"阅读对应章节**
## 二、适用场景
以下场景可以考虑使用页面脚本扩展功能来实现:
1、想在前端页面快速实现一些简单的业务控制逻辑
2、依据页面数据状态等需要对某些UI元素做定制开发(比如修改内容展示格式,样式以及动画等)
3、开发的自定义控件其渲染数据依赖页面上字段等数据的变化,不想通过服务端插件实现
4、其他基于web页面轻量化JS脚本想实现的功能等
**注意**:
1、标准业务与二开项目等都可使用(有做权限控制)
2、**脚本在设计器中可进行编辑或查看,且考虑到web页面脚本的安全性等,不建议将重要或复杂的业务逻辑放在脚本中实现**
3、**如果想开发一个独立控件且需要与服务端插件通信,同时希望在不同单据页面可复用,则推荐使用[自定义控件](https://developer.kingdee.com/knowledge/specialDetail/218022218066869248?category=249447947539332864&id=205421332635100416&productLineId=29)做开发**
## 三、功能入口与界面介绍
该页面脚本功能分设计时与运行时,设计时可以编写/查看、启用/禁用脚本,运行时执行脚本。
### 1、设计时
#### 脚本编辑器

(1)API列表
* 基于这些内置API可快速实现特定功能。双击API名称快速插入代码片段、鼠标悬浮右侧"!"号上展示对应API详细内容
(2)脚本代码编辑区域
* 在此编辑JS脚本。支持录入功能接口或控件key等场景,自动提示并展示下拉选项列表
(3)脚本"保存"按钮
* 点击直接保存脚本**(脚本保存不依赖设计器的"保存"操作,即无需再点击设计器右上角保存按钮)**
(4)脚本语法检查
* 检查脚本内容是否存在语法错误
#### 脚本编辑器入口
脚本编辑和打开脚本编辑器的入口都在表单设计器中。
##### 总入口
点击该入口,会打开脚本编辑器并加载对应脚本内容。
【旧版 - 表单设计器】

【新版 - 表单设计器】

##### 页面/组件事件绑定入口
点击该入口,会打开脚本编辑器并自动生成对应组件/页面的事件绑定代码(如果已绑定过事件则会自动定位到对应代码行位置)。
【旧版表单设计器 - 选中单据根节点 - 动作面板】

【旧版表单设计器 - 选中组件 - 动作面板】

【新版表单设计器 - 选中单据根节点 - 动作面板】

【新版表单设计器 - 选中组件 - 动作面板】

#### 脚本管理界面
脚本管理界面可查看单据自身及其父页面的脚本内容,同时如有编辑权限可启用/禁用当前页面脚本
【旧版表单设计器 - 选中根节点 - 页面脚本属性】

(1)启用/禁用脚本
(2)点击"表单标识"列超链接,可查看对应脚本内容
【新版表单设计器 - 页面脚本管理页签】

#### 权限控制
权限控制,与Java插件管理一致,设计器中相同开发商才允许编辑。
### 2、运行时
执行脚本
* 运行时打开一个单据,单据对应脚本就会执行。
* 当存在多份脚本时(即父页面也有脚本的情况),脚本执行顺序为:自顶向下,优先执行父页面脚本。
## 四、脚本编写规范与语法支持
脚本编写使用javascript,内置了一些脚本对象、API和标准事件等。
**一个单据页面自身只会有一份JS脚本,同时支持通过单据继承/扩展/复制等方式来继承父页面的脚本**。
### 脚本生命周期

脚本编写主要在两个大的生命周期函数中进行,分别为:didMount、willUnmount
#### didMount
* 单据初始化函数
* 执行初始化相关事务,比如注册组件事件监听、设置数据、执行DOM操作等
* 此函数在单据加载服务端插件后自动调用
**注意**:
**1、一般脚本代码的主要实现逻辑都建议在该函数中编写实现**
**2、该函数在页面业务数据加载完成之后执行(即"loaddata"请求)**
#### willUnmount
* 单据卸载函数
* 执行资源释放等事务,比如移除DOM事件监听/定时器、销毁UI元素、删除全局变量等
* 此函数在单据关闭(销毁)时自动调用
**注意:该函数在页面关闭时执行**
### 脚本事件绑定
#### didMount
单据初始化事件,设计器中选中单据根节点,右侧"**动作**"面板可看到对应事件绑定入口(**新增脚本默认自动生成**)
示例:
```
/**
* 单据初始化函数
* 执行初始化相关事务,比如注册组件事件监听、设置数据、执行DOM操作等
* 此函数在单据加载服务端插件后自动调用
*/
function didMount () {
}
```
#### willUnmount
单据卸载事件,设计器中选中单据根节点,右侧"**动作**"面板可看到对应事件绑定入口(**新增脚本会默认自动生成**)
示例:
```
/**
* 单据卸载函数
* 执行资源释放等事务,比如移除DOM事件监听/定时器、销毁UI元素、删除全局变量等
* 此函数在单据关闭(销毁)时自动调用
*/
function willUnmount () {
}
```
#### onValueChange
字段值更改事件,设计器中选中任意字段类型组件,右侧"**动作**"面板可看到对应事件绑定入口
* 字段值更改事件,在内容发生改变且字段输入框失去焦点时触发
* 后续会根据规划与需要开放更多组件的标准事件(比如按钮点击等)
示例:
```
// 事件参数结构说明:data: {key, newValue, oldValue}
this.$('字段标识').onValueChange((data) => {
// todo something
})
```
### 内置对象
"**this**":页面脚本上下文对象,用于访问页面内所有控件实例和获取页面自身相关数据。
* 该对象一般在didMount或willUnmount中使用,使用时注意要保证其指向正确(特别是在嵌套函数中),否则脚本会报错,无法正确访问相关对象或接口
示例:
```
function didMount() {
// this指向页面脚本上下文
this.xxx
//错误写法
this.$('控件标识').on('click',function(e){
//此时this不指向页面脚本上下文,js会报错
this.$('控件标识').set('Name','title')
})
//正确写法(使用箭头函数,自动绑定上下文)
this.$('控件标识').on('click',(e)=>{
//此时this指向页面脚本上下文,js正常执行
this.$('控件标识').set('Name','title')
})
}
function willUnmount() {
// this指向页面脚本上下文
this.xxx
}
// 此时this不指向页面脚本上下文(指向window对象)
this.xxx
```
"**this.$('控件标识')**":用于获取控件实例,使用该实例可调用控件相关功能接口(包括通用控件方法、字段方法等)
示例:
```
// 获取控件实例对象
this.$('控件标识')
//调用控件实例对象功能接口
this.$('控件标识').set('Name', 'title')
```
### export关键字
如果将某些逻辑封装为独立的函数,且函数中用到了脚本内置对象或API等,为了在函数中能正常访问这些对象或API等,则需要为函数增加**export**修饰关键字,同时函数调用方式为: **this.独立函数名称()**。
示例:
```
// 初始化
function didMount() {
this.testMethod() //调用函数
}
// 独立函数写法
export function testMethod(str) {
this.$('控件标识').setValue('111')
}
```
### 与自定义控件通信
标准字段值变化要通知自定义控件,需要调用invoke接口。
具体示例代码见 "**案例展示 - 标准字段与自定控件通信**"部分内容。
## 五、API介绍
页面脚本编写,预制了部分API,提升代码编写效率与使用体验。
具体API及其分组如下:
### 1、通用控件方法
#### set
设置控件属性值(支持所有控件,属性名称可在设计器右侧属性面板查看)
格式:
```
this.$('控件标识').set('属性名', '属性值')
```
示例:
```
this.$('textfield1').set('Name', 'title')
```
#### get
获取控件的属性值(属性名称可在设计器右侧属性面板查看)
格式:
```
this.$('控件标识').get('属性名')
```
示例:
```
this.$('textfield1').get('Name')
```
#### isEditable
获取控件锁定性,即控件是否可编辑
格式:
```
this.$('控件标识').isEditable()
```
示例:
```
this.$('textfield1').isEditable()
```
#### isVisible
获取控件可见性,即是否被隐藏
格式:
```
this.$('控件标识').isVisible()
```
示例:
```
this.$(textfield1').isVisible()
```
### 2、字段方法
#### setValue
设置字段值
格式:
```
this.$('控件标识').setValue('值内容')
```
示例:
```
this.$('textfield1').setValue('123')
```
#### getValue
获取字段值
格式:
```
this.$('控件标识').getValue()
```
示例:
```
this.$('textfield1').getValue()
```
#### isRequired
获取字段必录属性,表明该字段是否是必须录入的
格式:
```
this.$('控件标识').isRequired()
```
示例:
```
this.$('textfield1').isRequired()
```
### 3、表单方法
#### getFormConfig
获取当前表单的配置信息,包含表单相关配置项
示例:
```
this.getFormConfig()
```
#### getFormMeta
获取当表表单的元数据,包含所有组件的布局等描述信息
示例:
```
this.getFormMeta()
```
#### getFormStatus
获取当前表单的单据状态,其返回数据含义:0/新增、1/修改、2/查看、4/提交、5/审核
示例:
```
this.getFormStatus()
```
### 4、DOM操作方法
**注意:DOM相关操作如果依赖控件对应DOM结构上的某个子元素,请谨慎使用,后续版本迭代升级无法保证DOM结构不会变化**。
#### on
用于为DOM元素添加事件监听,接受三个参数:事件名称(HTML原生事件,可同时监听多个,用空格分隔)、子元素选择器 【可选】、事件回调函数
格式:
```
this.$('控件标识').on('html事件名称', 事件回调函数)
```
示例:
```
const handler=event=>{...}
this.$('textfield1').on('mouseenter mouseleave', handler)
```
#### off
用于移除DOM元素的事件监听,可移除通过'on'接口监听的所有或特定事件
格式:
```
this.$('控件标识').off('html事件名称', 事件回调函数)
```
示例:
```
const handler=event=>{...}
this.$('textfield1').off('mouseenter mouseleave',handler)
```
#### getElement
获取控件对应的DOM对象
格式:
```
this.$('控件标识').getElement()
```
示例:
```
let dom = this.$('textfield1').getElement()
dom.style.height = 100
```
#### wait
用于控件懒加载时,异步获取组件对应DOM对象
格式:
```
this.$('控件标识').wait().then(ctrlDomEle => {})
```
示例:
```
this.$('textfield1').wait().then(ctrlDomEle =>
console.log(ctrlDomEle.style.height)
)
```
#### css
设置控件内联样式,支持修改单个或多个html样式属性
格式:
```
this.$('控件标识').css({'css属性名称':'属性值'})
```
示例:
```
this.$('textfield1').css('background','red')
this.$('textfield1').css({'background':'red', 'color':'#666666'})
```
### 5、工具类函数
#### loadFiles
加载资源文件,动态加载第三方资源文件(比如js、css等)
格式:
```
this.utils.loadFiles(['文件路径1','文件路径2']).then(result => {})
```
示例:
```
this.utils.loadFiles(['https://www.xxx.com/xxx/123.js','https://www.xxx.com/xxx/456.css']).then(result => {
console.log(result)
})
```
#### showMessage
显示消息提示
用于弹出消息提示,接受两个参数:消息内容和可选的配置对象。配置对象中包含type(消息类型,可选值为0-成功,1-错误,2-警告)和duration(显示时长/ms)
格式:
```
this.utils.showMessage('消息内容')
```
示例:
```
this.utils.showMessage('content',{type:0,duration:3000})
```
#### createStyle
创建页面样式
动态创建< style >元素并插入到浏览器页面中,当前表单销毁时自动移除
格式:
```
this.utils.createStyle('css样式内容')
```
示例:
```
this.utils.createStyle(".root{width: 10px;} .sel{display: flex}")
```
#### loadArtTemplate
基于模版创建DOM元素
导入art模版库,方便创建dom元素
格式:
```
this.utils.loadArtTemplate().then(template => {})
```
示例:
```
this.utils.loadArtTemplate().then(template => {
const id='testSelect', title='this is test art template'
const str=`
<label for='id'>title</label><select name="pets" id='id'>{{each items}}<option value={{$value.id}}>{{$value.caption}}</option>{{ /each}}</select>
`
const items = [{id:1,caption:'name1'},{id:2,caption:'name2'}]
const htmlString = template.render(str,{items})
document.getElementById('select').innerHTML = htmlString
})
```
### 6、其他方法
#### debugger
调试代码
在js脚本中设置断点,从而支持打开浏览器控制台后调试代码
示例:
```
debugger
```
#### console
输出日志
在浏览器控制台输出日志信息
示例:
```
console.log('日志信息')
```
## 六、DOM相关操作
页面脚本目前支持标准的事件(比如字段值改变事件等,后续也会开放更多),数据获取与赋值等操作也有标准的功能接口(比如getValue、setValue等),通过标准事件与功能接口可以快速实现相关业务逻辑。
**有些页面交互行为或数据是标准组件/接口没有对外提供或暂未规划的,此时通过组件相关DOM对象的相关操作可以变相的快速实现**。
比如:
* 通过DOM对象的'input'事件可以实时监听字段值改变,事件触发时机是实时的(标准字段是失去焦点时才触发)
* 通过DOM对象的'click'事件监听标准按钮的点击事件,在前端页面通过脚本实现相应业务逻辑
**DOM对象事件操作可能对标准组件存在影响,请谨慎使用。**
DOM对象监听的事件为HTML DOM原生事件,具体可参考[DOM原生事件](https://www.w3school.com.cn/jsref/dom_obj_event.asp)。
DOM相关操作内置的API包括:on、off、getElement、wait、css,具体描述见"**API介绍**"章节。
## 七、如何引用第三方JS、CSS等资源
可通过加载第三方资源的API:'**this.utils.loadFiles**' 实现(**请保证自己引用的 JS等资源文件的安全性**)。
示例:
```
// 初始化
function didMount() {
// 加载资源
this.utils.loadFiles(['https://www.xxx.com/xxx/123.js','https://www.xxx.com/xxx/456.css']).then(result=> {
console.log(result)
})
}
```
## 八、如何调试与异常处理
### 脚本代码调试
1、在脚本中使用"**debugger**"关键字
运行时打开浏览器控制台/开发者工具,即可命中该断点,进入脚本调试
脚本发布上线前建议删除调试代码(即"**debugger**"关键字)

2、通过输出日志来反向定位脚本并打断点
在脚本中任意输出一条日志,比如使用'console.log(日志信息)',运行时输出的该日志会有对应文档链接,
点击控制台输出的日志其最右边的链接,浏览器会自动打开对应脚本内容,此时再手动打断点,下次脚本运行到这里就会进入调试。
示例如下:

### 异常处理
1、脚本中调用内置的标准功能接口(比如“this.$('控件标识').接口名称”),有默认做一些异常捕获,比如控件标识不存在,接口不存在等情况,此时浏览器控制台会有异常信息提示,且**后续脚本会继续执行**。
示例如下:

2、当用户自己编写的脚本内容引发了异常(比如访问一个不存在对象的某个属性,例如访问a.b但对象a不存在),同时**没有捕获异常时,后续脚本执行就会终止**。
## 九、案例展示
### 1、大小写字母实时自动转换
【功能场景】:需要将用户录入的小写英文字母自动并实时转为大写英文字母
【实现思路】:在字段中输入框的原生input事件中做转换,再重新赋值
```
// 初始化
function didMount() {
// 'kdtest_textfield':文本字段标识;
this.$('kdtest_textfield').on('input', 'input', (e) => {
// 给字段赋值
setTimeout(()=>{
// setTimeout一下,键盘录入时字母变为大写会有动态过程
this.$('kdtest_textfield').setValue(convertToUpperCase(e.target.value))
},100)
})
}
/**
* 字母转换为大写
*/
function convertToUpperCase(str) {
return str.toUpperCase();
}
```
【效果截图】

### 2、阿拉伯数字转中文数字
【功能场景】:需要将用户录入的阿拉伯数字自动转为中文数字
【实现思路】:在字段值改变的标准事件中做数字转换,再重新赋值
```
// 初始化
function didMount() {
// kdtest_textfield3:文本字段标识;
this.$('kdtest_textfield3').onValueChange((data) => {
let result = convertToChineseNumber(data.newValue)
this.$('kdtest_textfield3').setValue(result)
})
}
/**
* 将字符串中数字转换为大写数字
*/
function convertToChineseNumber(str) {