PWC代码练习

112 阅读5分钟

一. 自定义插件中调用自定义组件

需求描述

在主对象新建/编辑时,从对象操作中要多一个按钮,点击按钮后要能够获取到对应从对象某个字段的内容,并且需要将内容渲染到一个弹窗上

image.png

image.png

实现

思路

  1. 查看文档后发现,要按这个需求把按钮添加上,只能使用插件,而且是只有新建编辑页插件才能将这个按钮添加上

  2. 所以给主对象、从对象都开启了独立的新建/编辑页布局

  3. 查看文档发现,插件可以添加上按钮,并且也可以通过点击事件获取到相应从对象的数据

  4. 由于现在是在插件(原生js)的环境下,那么将数据如何渲染到一个弹窗上呢? 原生js的话,alert()是一个方法,但这个弹窗太随意了。

    想要一个像新建编辑提交按钮前验证的那种弹框样式。如何实现 —> 调用自定义组件

    发现甚至都不需要写组件样式,通过FxUI.userDefine.openCustomComponent("自定义组件的apiname")来调用自定义组件,就会直接弹出一个弹框:

    image.png

    刚好满足需求

  5. 数据传递问题

    在插件(原生js)下获取的从对象的数据,如何传递到我在插件里通过FxUI.userDefine.openCustomComponent("自定义组件的apiname")来调用的自定义组件里面呢?

    首先想到了自定义控制器,然后想到了cache接口(Fx.cache)

    可以在插件里先调用自定义控制器A,将数据保存到cache,然后再阻塞调用自定义组件(这里一定要注意是阻塞调用,不能异步调用,否则可能会出现数据还没通过自定义控制器进入cache,就已经调用自定义组件了),然后再到自定义组件中的mounted()钩子下调用自定义控制器B,从cache中获取数据

image.png

代码

自定义插件:

/**
 * 重置从对象表格的按钮
 */

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表格)

需求描述

image.png

实现

思路

订单主表上添加多行文本类型字段,在审批待办列表中,每一条待办展示明细多行文本字段,以HTML表格形式拼接到多行文本中,拼接的表格代码需要加上 "FXHTML" 为开头的标识

Tip:需灰度申请

在审批流程的配置中挂载函数

image.png

image.png

代码

/**
 * @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);
}

三. 详情页隐藏字段分组

需求描述

在对象的详情页里面,想要隐藏掉某个字段分组

image.png

比如想要隐藏掉这个基本信息字段分组,但是没有位置可以进行配置

隐藏之前的前端界面:

image.png

隐藏之后的前端界面:

image.png

实现

思路

image.png

写一个插件

代码

/**
 * 详情页隐藏字段分组信息
 * @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);
    });
  }
}

四. 自定义实现一个对象列表页展示

需求描述

自定义实现一个对象列表页展示:

  1. 实现一个定制化的列表页卡片
  2. 需要调用APL函数获取对象数据
  3. 点击卡片可以打开查看详情
  4. 提供一个按钮,点击后可以新建对象

实现

思路

  1. 新建一个自定义页面

    image.png

  2. 将我们的组件放入这个自定义页面

    image.png

  3. 在菜单模板管理中添加刚刚的自定义页面

    image.png

    image.png

  4. 组件编写的思路

    1. 样式的话需要用到卡片组件
    2. 对象列表页,首先要获取到对象列表。FxUI有相关接口
    3. 获取到对象列表后,就能将对象数据循环展示在卡片组件中
    4. 卡片组件中需要两个按钮,一个新建按钮,一个详情按钮
    5. 给这两个按钮绑定上相应方法,再到相应方法中调用FxUI相关接口即可

五.自定义插件中调用自定义组件(优化)

在 一. 里面是利用的缓存实现js插件中的数据与组件中的数据互通, 但其实并不需要这样做,vue组件有props节点来接收外来数据

需求描述

与一.相同,不再赘述

实现

思路

  1. 查看文档后发现,要按这个需求把按钮添加上,只能使用插件,而且是只有新建编辑页插件才能将这个按钮添加上

  2. 所以给主对象、从对象都开启了独立的新建/编辑页布局

  3. 查看文档发现,插件可以添加上按钮,并且也可以通过点击事件获取到相应从对象的数据

  4. 由于现在是在插件(原生js)的环境下,那么如何将数据渲染到一个弹窗组件上呢?

代码

关键部分
  1. 在js插件中直接调用组件, 并传递数据给组件

    1. 之前的方案

      在js插件中利用FxUI.userDefine.openCustomComponent()对组件进行调用,但这样有一个问题就是: 即便组件中不写标签、不写样式,也会默认有一个弹窗出现,这是调用FxUI.userDefine.openCustomComponent()的原因

      js插件传递数据给vue组件也只是通过cache实现的

    2. 更好的方案: 在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>
      

六.客户关系层级

需求描述

按照客户对象的上级客户字段, 将客户的层级关系展现在页面上

实现

思路

  1. 调用自定义控制器获取客户对象列表的所有数据

  2. 将线性的客户对象数据按照上级客户字段进行树状化:

    由于直接获取到的客户对象数据中存放的都是上级客户字段,则可抽象为某个节点中只存有其父节点的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>