一. 自定义插件中调用自定义组件
需求描述
在主对象新建/编辑时,从对象操作中要多一个按钮,点击按钮后要能够获取到对应从对象某个字段的内容,并且需要将内容渲染到一个弹窗上
实现
思路
-
查看文档后发现,要按这个需求把按钮添加上,只能使用插件,而且是只有新建编辑页插件才能将这个按钮添加上
-
所以给主对象、从对象都开启了独立的新建/编辑页布局
-
查看文档发现,插件可以添加上按钮,并且也可以通过点击事件获取到相应从对象的数据
-
由于现在是在插件(原生js)的环境下,那么将数据如何渲染到一个弹窗上呢? 原生js的话,alert()是一个方法,但这个弹窗太随意了。
想要一个像新建编辑提交按钮前验证的那种弹框样式。如何实现 —> 调用自定义组件
发现甚至都不需要写组件样式,通过
FxUI.userDefine.openCustomComponent("自定义组件的apiname")
来调用自定义组件,就会直接弹出一个弹框:刚好满足需求
-
数据传递问题
在插件(原生js)下获取的从对象的数据,如何传递到我在插件里通过
FxUI.userDefine.openCustomComponent("自定义组件的apiname")
来调用的自定义组件里面呢?首先想到了自定义控制器,然后想到了cache接口(Fx.cache)
可以在插件里先调用自定义控制器A,将数据保存到cache,然后再阻塞调用自定义组件(这里一定要注意是阻塞调用,不能异步调用,否则可能会出现数据还没通过自定义控制器进入cache,就已经调用自定义组件了),然后再到自定义组件中的mounted()钩子下调用自定义控制器B,从cache中获取数据
代码
自定义插件:
/**
* 重置从对象表格的按钮
*/
export default {
getMdOperateButtons(context, detailObjectApiName) {
console.log('getMdOperateButtons', arguments);
return detailObjectApiName === 'object_YYizc__c' ? [function() {
return {
add: [{
label: '自定义操作按钮',
action: 'test_handle_3',
callBack(data) {
console.log("当前从对象的排序字段的内容为", data["field_0Tn3x__c"])
// 调用自定义控制器,将数据存入cache
let params = [{
type: 'map',
name: 'args',
value: {
data: data["field_0Tn3x__c"],
}
}]
FxUI.userDefine.call_controller('saveToCache__c', params).then((res) => { // 异步
console.log("缓存的结果:", res)
// 打开自定义组件
FxUI.userDefine.openCustomComponent("component_5tjbd__c")
}).catch(err => {
console.log(err);
})
}
}]
}
}] : []
}
}
// js插件(可以获取到当前操作数据) ---> 数据利用控制器1存入缓存
// 自定义组件(如何拿到js插件获取到的数据?) ---> 数据利用控制器2从缓存中拿出
自定义组件:
<template>
<div> {{ content }} </div>
</template>
<script>
export default {
data() {
return {
content: ''
}
},
mounted() {
this.getFromCache()
},
methods: {
// 调用自定义控制器查询cache中的内容
getFromCache() {
FaciUI.userDefine.call_controller('getFromCache__c').then((res) => { // FxUI在通过FxUI.userDefine.openCustomComponent('')调用自定义组件时出问题了, 研发给了一个FaciUI
console.log('获取缓存的结果:', res)
this.content = res.Value.functionResult
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style scoped>
</style>
二. 审批流程待办明细关键审核字段展示(通过内嵌html表格)
需求描述
实现
思路
订单主表上添加多行文本类型字段,在审批待办列表中,每一条待办展示明细多行文本字段,以HTML表格形式拼接到多行文本中,拼接的表格代码需要加上 "FXHTML" 为开头的标识
Tip:需灰度申请
在审批流程的配置中挂载函数
代码
/**
* @author 杨宇翔Barry
* @codeName 展示明细的关键审核信息
* @description 展示明细的关键审核信息
* @createTime 2023-07-04
*/
String dataId = context.data._id as String;
// 表单
String table = "";
// 表头
String tabHead = "";
// 表体
String tabBody = "";
String tabBodyTr = "";
// 获取订单产品数据
List orderDetails = context.details.object_OjYMi__c as List;
orderDetails.each { item ->
// 产品名称
String productName = (item["field_rn49z__c"] == null) ? ("") : item["field_rn49z__c"] as String;
// 产品单价
BigDecimal productSalePrice = (item["field_Xykq1__c"] == null) ? (0.0) : item["field_Xykq1__c"] as BigDecimal;
// 产品数量
Integer productQuantity = (item["field_wAJcR__c"] == null) ? (0) : item["field_wAJcR__c"] as Integer;
// 折扣
Integer productSales = (item["field_rI6mi__c"] == null) ? (0) : item["field_rI6mi__c"] as Integer;
productSales /= 10
// 总金额
BigDecimal productSumPrice = (item["field_7d31X__c"] == null) ? (0.0) : item["field_7d31X__c"] as BigDecimal;
tabBodyTr += "<tr>" +
"<td>"+ productName +"</td>" +
"<td>"+ productSalePrice +"</td>" +
"<td>"+ productQuantity +"</td>" +
"<td>"+ productSales +"</td>" +
"<td>"+ productSumPrice +"</td>" +
"</tr>";
}
tabHead = "<thead>" +
"<tr style='background-color: #2C334F;'>" +
"<th width=120px>产品名称</th>" +
"<th width=200px>产品单价</th>" +
"<th width=60px>产品数量</th>" +
"<th width=60px>折扣</th>" +
"<th width=60px>总金额(元)</th>" +
"</tr>" +
"</thead>";
tabBody = "<tbody>" + tabBodyTr + "</tbody>";
table = "__FXHTML__" + "<table style='border: 2px solid;'>" + tabHead + tabBody + "</table>";
def updateResult = Fx.object.update("object_8vqYk__c", dataId, ["field_VM8w5__c": table]) // 更新多行文本字段
if (updateResult[0]) {
Fx.message.throwErrorMessage("更新失败:" + updateResult[2]);
} else {
Fx.log.info("更新成功:" + table);
}
三. 详情页隐藏字段分组
需求描述
在对象的详情页里面,想要隐藏掉某个字段分组
比如想要隐藏掉这个基本信息字段分组,但是没有位置可以进行配置
隐藏之前的前端界面:
隐藏之后的前端界面:
实现
思路
写一个插件
代码
/**
* 详情页隐藏字段分组信息
* @author wangbo
*/
export default {
beforeParse(context, data) {
return new Promise(resolve => {
// 打印所有context:
console.log("context: ", context)
// 打印所有data:
console.log("data: ", data)
// 所有组件列表
let comps = data.layout.components;
// 所有字段分组列表
let sections = [];
for (let i = 0; i < comps.length; i++) {
let map = comps[i];
console.log("组件: " , map)
if ("form_component" === map.api_name) { // "详细信息"组件
sections = map.field_section; // "详细信息"组件中的所有字段分组
break;
}
}
for (let j = 0; j < sections.length; j++) {
let map = sections[j];
console.log("字段分组: " , map)
// 去除指定字段分组
if ("基本信息" === map.header) { // "基本信息"字段分组
sections.splice(j, 1); // 从索引j开始删除数组中元素,并且只删除数组中1个元素
break;
}
}
resolve(data);
});
}
}
四. 自定义实现一个对象列表页展示
需求描述
自定义实现一个对象列表页展示:
- 实现一个定制化的列表页卡片
- 需要调用APL函数获取对象数据
- 点击卡片可以打开查看详情
- 提供一个按钮,点击后可以新建对象
实现
思路
-
新建一个自定义页面
-
将我们的组件放入这个自定义页面
-
在菜单模板管理中添加刚刚的自定义页面
-
组件编写的思路
- 样式的话需要用到卡片组件
- 对象列表页,首先要获取到对象列表。FxUI有相关接口
- 获取到对象列表后,就能将对象数据循环展示在卡片组件中
- 卡片组件中需要两个按钮,一个新建按钮,一个详情按钮
- 给这两个按钮绑定上相应方法,再到相应方法中调用FxUI相关接口即可
五.自定义插件中调用自定义组件(优化)
在 一. 里面是利用的缓存实现js插件中的数据与组件中的数据互通, 但其实并不需要这样做,vue组件有props节点来接收外来数据
需求描述
与一.相同,不再赘述
实现
思路
-
查看文档后发现,要按这个需求把按钮添加上,只能使用插件,而且是只有新建编辑页插件才能将这个按钮添加上
-
所以给主对象、从对象都开启了独立的新建/编辑页布局
-
查看文档发现,插件可以添加上按钮,并且也可以通过点击事件获取到相应从对象的数据
-
由于现在是在插件(原生js)的环境下,那么如何将数据渲染到一个弹窗组件上呢?
代码
关键部分
-
在js插件中直接调用组件, 并传递数据给组件
-
之前的方案
在js插件中利用FxUI.userDefine.openCustomComponent()对组件进行调用,但这样有一个问题就是: 即便组件中不写标签、不写样式,也会默认有一个弹窗出现,这是调用FxUI.userDefine.openCustomComponent()的原因
js插件传递数据给vue组件也只是通过cache实现的
-
更好的方案: 在js插件中进行vue组件的实例化
js插件中引入vue组件:
import CustomAlert from './CustomAlert' const CustomAlertVM = Vue.extend(CustomAlert)
js插件中进行vue组件实例化, 将vue组件挂载在html标签上,并传递数据给vue组件:
let div = document.createElement('div'); document.body.appendChild(div); let _widgets = new CustomAlertVM({ el: div, propsData: { transferData: 'testData' } });
vue组件接收数据:
<template> <div> </div> </template> <script> export default { props: { transferData: String // 接收js插件传来的数据 }, data() { } } </script> <style scoped> </style>
-
六.客户关系层级
需求描述
按照客户对象的上级客户字段, 将客户的层级关系展现在页面上
实现
思路
-
调用自定义控制器获取客户对象列表的所有数据
-
将线性的客户对象数据按照上级客户字段进行树状化:
由于直接获取到的客户对象数据中存放的都是上级客户字段,则可抽象为某个节点中只存有其父节点的id。但问题在于我们使用树形控件时,我们必须知道每个节点的子节点id,才能将数据树状渲染出来。所以需要写一个算法来进行客户对象数据的重新组织。
代码
<template>
<div>
<div class="block">
<fx-tree
:data="accountDataShow"
:show-checkbox="false"
node-key="id"
default-expand-all
:expand-on-click-node="false">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<fx-button
type="text"
size="mini"
@click="() => append(data)">
Append
</fx-button>
<fx-button
type="text"
size="mini"
@click="() => remove(node, data)">
Delete
</fx-button>
<fx-button
type="text"
size="mini"
@click="() => detail(data)">
Detail
</fx-button>
</span>
</span>
</fx-tree>
</div>
</div>
</template>
<script>
export default {
data() {
return {
data: [{
id: 1,
label: '一级 1',
children: [{
id: 4,
label: '二级 1-1',
children: [{
id: 9,
label: '三级 1-1-1'
}, {
id: 10,
label: '三级 1-1-2'
}]
}]
}, {
id: 2,
label: '一级 2',
children: [{
id: 5,
label: '二级 2-1'
}, {
id: 6,
label: '二级 2-2'
}]
}, {
id: 3,
label: '一级 3',
children: [{
id: 7,
label: '二级 3-1'
}, {
id: 8,
label: '二级 3-2'
}]
}],
accountDataShow: [
{
id: 1001,
label: '客户yyx',
children:[
{
id: 1002,
label: '客户zara',
children: [
{
id: 1003,
label: '客户cyy'
},
{
id: 1004,
label: '客户xjq'
}
]
}
]
},
{
id: 1005,
label: '客户wy',
children:[
{
id: 1008,
label: '客户cmx',
},
{
id: 1009,
label: '客户dh'
}
]
},
{
id: 1010,
label: '客户tt',
children: [
{
id: 1020,
label: '客户hh'
},
{
id: 1030,
label: '客户ll'
}
]
}
],
accountDataOriginal: [],
id: 1000,
}
},
mounted() {
// 获取客户列表信息 并将 客户列表信息根据上级客户字段树状化
this.fetchData()
},
methods: {
// 获取客户列表信息
fetchData() {
// 异步操作
FxUI.userDefine.call_controller('GetObjectData__c').then((res) => {
if (res.Result.StatusCode === 0) {
this.accountDataOriginal = res.Value.functionResult.dataList
// 原始数据
console.log('原始数据', this.accountDataOriginal) // TODO 测试: 原始数据测试
// 将客户列表信息根据 上级客户 字段树状化
this.solveData()
} else {
alert('调用自定义控制器失败')
console.log(res)
}
})
},
// 将客户列表信息根据 上级客户 字段树状化
solveData() {
// 根节点(可多个)
let root = []
// 非根节点
let subRoot = []
this.accountDataShow = [] // 清空模板数据
// 遍历每个节点,初步对树进行初始化
for (let account of this.accountDataOriginal) {
let id = account['_id']
let parentId = account['parent_account_id'] == null ? '' : account['parent_account_id']
let name = account['name']
if (parentId === '') { // 没有父节点,则统一作为根节点
root.push(id)
this.accountDataShow.push({
id: id,
label: name,
parentId: '',
children: []
})
} else { // 存在父节点,先记录下来
subRoot.push({
id: id,
label: name,
parentId: parentId,
children: []
})
}
}
let subRootBackup = [] // 存放剩余节点的id
// let subRootBackup = {... subRoot} // 深拷贝
for (let item of subRoot) {
subRootBackup.push(item['id'])
}
console.log('subRootBackUp', subRootBackup)
// 测试
console.log('根节点测试', this.accountDataShow) // TODO 根节点测试(没问题
console.log('剩余节点测试', subRoot) // TODO 剩余节点测试(没问题
console.log('剩余节点id测试', subRootBackup) // TODO 剩余节点id测试
// 根据parentId与id连接各个节点(利用数组)
for (let node of subRoot) {
console.log("subRoot各节点测试", node) // TODO(没问题
let id = node['id']
let label = node['label']
let parentId = node['parentId']
let children = (node['children'] == null) ? [] : node['children']
if (root.includes(parentId)) { // 父节点为根节点,由于根节点已存放在this.accountDataShow里面,故直接push
console.log('父节点为根节点,直接push')
for (let parentNode of this.accountDataShow) {
if (parentNode['id'] === parentId) {
parentNode['children'].push({
id: id,
label: label,
parentId: parentId,
children: children
})
// 从subRootBackup中移走该节点
subRootBackup.forEach((item, index, array) => {
if (item === id) {
subRootBackup.splice(index, 1)
}
})
}
}
} else { // 父节点不为根节点,且父节点有两种情况: 1. 已存放在this.accountDataShow里面 2. 还未存入this.accountDataShow里面
if (!subRootBackup.includes(parentId)) { // 1. 父节点已存放在this.accountDataShow里面
console.log('父节点已经在树里面') // TODO 测树1.
// 遍历this.accountDataShow找到父节点添加该子节点
for (let node of this.accountDataShow) {
let parentNode = this.recursionSearchParent(node, parentId)
if (parentNode != null) {
parentNode['children'].push({
id: id,
label: label,
parentId: parentId,
children: children
})
// 从subRoot中移走该节点
subRootBackup.forEach((item, index, array) => {
if (item === id) {
subRootBackup.splice(index, 1)
}
})
break
}
}
} else { // 2. 父节点还未存入this.accountDataShow里面
console.log('父节点还未存入树里面') // TODO 测树2.
for (let node of subRoot) {
if (node['id'] === parentId) {
node['children'].push({
id: id,
label: label,
parentId: parentId,
children: children
})
// 从subRoot中移走该节点
subRootBackup.forEach((item, index, array) => {
if (item === id) {
subRootBackup.splice(index, 1)
}
})
}
}
}
}
}
console.log('展示节点测试', this.accountDataShow)// TODO 展示节点测试
console.log('剩余节点测试', subRootBackup) // TODO 剩余节点测试
// TODO 最终测试点
},
// 递归查找父节点并返回
recursionSearchParent(node, parentId) {
// 递归结束条件
if (node['id'] === parentId) {
return node
}
if (node['children'] == null) {
return null
}
// 递归
for (let childNode of node['children']) {
let result = this.recursionSearchParent(childNode, parentId)
if (result) {
return result
}
}
return null
},
// 增加子节点
append(data) {
const newChild = { id: this.id++, label: 'testtest', children: [] };
if (!data.children) {
this.$set(data, 'children', []);
}
data.children.push(newChild);
},
// 拓展子节点
remove(node, data) {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex(d => d.id === data.id);
children.splice(index, 1);
},
// 查看详情
detail(data) {
console.log(data)
}
}
}
</script>
<style scoped>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
padding-right: 8px;
}
</style>