Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)

博主还在学习Salesforce的过程中,在学习期间上网了解并记录了很多比较基础的东西,故将笔记写于此篇。本人才疏学浅,文中如果有不对或值得讨论的地方,欢迎大家提出来,一起探讨共同进步。

在Lightning Experience中显示编辑后的页面

第一次尝试在dev cosole中写代码后,进行页面显示时,出现以下画面:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第1张图片
由于需要在Lightning Experience中显示,则需要在控制台中输入 ctrl+shift+j

$A.get("e.force:navigateToURL").setParams(
{"url": "/apex/pageName"}).fire();

//您还可以通过以下方式预览带有记录ID的页面:将记录ID参数添加到JavaScript中URL的末尾:
$A.get("e.force:navigateToURL").setParams(
{"url": "/apex/pageName?&id=00141000004jkU0AAI"}).fire();

Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第2张图片
于是右键打开了控制台,输入上述代码后发现报错,如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第3张图片
谷歌后(https://developer.salesforce.com/forums/?id=9060G0000005MvtQAE)终于在一个评论中找到了答案,如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第4张图片
点击进去后(https://developer.salesforce.com/forums/?id=9060G000000BiGtQAK)看到了这个回答,才知道是控制器打开错了,如下:
https://developers.google.com/web/tools/chrome-devtools/console/
https://trailhead.salesforce.com/fr/modules/visualforce_fundamentals/units/visualforce_creating_pages
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第5张图片
更改你的页面名字,即可跳转到对应页面,如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第6张图片
注意:只能在Lghtning中才可以使用上述代码,在经典版本中照样会报错,如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第7张图片

Account.Owner

Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第8张图片

引用自定义字段

如果您创建了自己的自定义对象(而不是像Account一样使用对象)并且想知道如何引用字段,则必须遵循一个稍微不同的过程。从设置中,在快速查找框中输入对象,然后选择对象,选择您的对象,然后选择该字段。 API名称现在指示您必须在Visualforce页面中使用的字段的名称。例如,如果您的字段名为Foo,则其API名称为Foo__c,并且您将使用该名称引用它 – 如:{! myobject__c.foo__c}。

Apex中的一些字段标签

大多数这些组件的名称都以“apex:input”开头,您可以在组件参考中找到它们。对于选择列表和单选按钮控件,请查找名称以“apex:select”开头的组件。
:是一个结构化的用户界面元素,用于对页面上的相关项进行分组。使用自动建议添加它,并将标题属性设置为“块标题”。
:是另一个将结构和层次结构添加到页面的组件。呈现时,用户可以折叠元素以隐藏除标题以外的所有内容。
:显示特定记录的详细信息,以及相关记录(如联系人,案例,机会等)的列表。快速将记录详细信息添加到使用标准控制器的页面。是一个粗粒度输出组件,它只在一行标记中向页面添加许多字段,部分,按钮和其他用户界面元素。还要注意,它添加到页面的所有内容都使用Salesforce Classic样式。定制的外观有相当多的属性。
:显示与当前记录相关的记录列表。(可根据自己所需来显示)
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第9张图片
:显示记录中的各个字段。当你需要更多的控制你的页面布局时,你可以单独添加字段。显示结果为:Burlington Textiles Corp of AmericaBurlington Textiles Corp of America(336) 222-7000Apparel¥350,000,000(全部显示在一行中,没有标签,也没有其他格式)。如果用组件包装起来,则显示为,如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第10张图片
(当在中使用时,它将采用两列布局,很好地添加字段标签,对齐和样式字段和标签)
:将数据表添加到页面。是一个迭代组件,用于生成一个数据表,并附有平台样式。如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第11张图片
是您可能要用于或代替的其他粗粒度组件。
创建一个页面来编辑数据。是一个Visualforce组件,它将其中的所有内容打包成可以作为页面操作的一部分发送回服务器的东西。
为与其关联的记录数据字段创建屏幕表单字段,可用于捕获任何标准或自定义对象字段的用户输入,并且尊重在字段定义上设置的任何元数据,例如字段是必需的还是唯一的,或者当前用户是否有权查看或编辑它。
:在用户界面上添加一个按钮,如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第12张图片
:显示任何表单处理错误或消息,如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第13张图片
:链接(https://help.salesforce.com/articleView?id=custom_links_constructing.htm&type=5)。如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第14张图片
recordSetVar:该属性表示VF(visual force)页面使用面向集合的标准控制器。属性的值表示传递给页面的记录集的名称表示集合记录。可以在表达式中使用此记录集来返回在页面上显示的值或对记录集执行操作。如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第15张图片


{!listViewOptions}:来获取可用于对象的列表视图过滤器的列表
{!filterId}:设置列表视图过滤器用于标准列表控制器的结果。如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第16张图片
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第17张图片
:组件引用标准列表控制器Previous和Next提供的两个操作方法。结果是执行“上一个”或“下一个”操作的链接(分页)。action属性设置为引用控制器中action方法的表达式。(请注意,与getter方法相比,action方法的名称与引用它们的表达式相同。)如图:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第18张图片
(图上所示方法:该操作方法更改排序顺序专用变量,然后该表被重新渲染?)
:让我们将列标题的内容设置为我们想要的任何内容
:Saleforce之actionFunction和setTimeout 定时调用
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第19张图片

Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第20张图片
操作区域是最重要的标签之一,有助于提高页面性能。因此,我们应该在视觉力页面中尽量利用动作区域。
actionRegion组件仅定义请求期间服务器处理的组件;它不定义在请求完成时重新呈现页面的那一部分。我们仍将在操作组件上使用reRender属性来确定应重新渲染AJAX请求完成的区域。
还有一点需要注意的是,即使使用组件,整个表单仍会提交,但是actionRegion内部的唯一区域是由服务器处理的。
还有一点要注意的是使用操作区域传递验证规则。
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第21张图片
在没有标签时,下拉框的值被发生改变则会直接显示错误信息。这是因为,当通过诸如“ onClick”或“ onChange”等事件生成AJAX请求时,整个都将提交到Force.com服务器,以将Industry value设置为NULL进行处理。因此出现错误。
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第22张图片
在有标签时,若都没有值在点击save后才会显示报错信息。“ ActionRegion”告诉Force.com Server应该处理哪些组件。这里,当在诸如“ KeyPress”或“ onClick”等事件上生成AJAX请求时,服务器将处理ActionRegion内部的任何内容。因此,我们不会收到错误。
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第23张图片

Apex中常用函数

URLENCODE():替换特定字符的符号。由于万维网联盟 (W3C) 制定了 URL 编码标准,因此不能通过 URL 传递某些“不安全”的字符,例如空格和标点符号。自定义按钮和链接会转义这些字段,因此无需经过 URL 编码。如果需要编码 URL,请在合并字段中使用 URLENCODE() 函数,比如:{!URLENCODE(text)},使用您希望编码的合并字段或文本字符串替换 text。例如:如果合并字段 foo__c 包含 Mark’s page, {!URLENCODE(foo_c)} 将导致:%3CB%3EMark%27s%20page%3C%2Fb%3E。
URLFOR(): 链接到 Visualforce 页面。请在 URLFOR() 中使用页面的相对路径,该路径为 /apex/PageName。例如,要链接到称为 MissionList 的未与某个记录相关联的 Visualforce 页面,请使用以下用法。

{! URLFOR(/apex/MissionList” ) }

当您在 URLFOR() 中使用 Visualforce 页面并且想要向页面中传递记录 ID 时,您必须将 ID 作为参数传入。

{! URLFOR(/apex/Mission”, null, [id=Mission__c.Id] ) }

$Action 全局变量和 URLFOR():指向到 Salesforce 页面。
在 Salesforce 中创建指向一个页面的自定义按钮或链接时,请使用 $Action 全局变量来构建链接,而不是粘贴页面的路径。那么,如果贵组织迁移到了另一台服务器或者页面的 URL 发生改变,该链接仍然会指向正确的位置。要构建链接,请使用带有 $Action 变量的 URLFOR() 公式函数。

{!URLFOR( $Action.Case.NewCase, Account.Id )}

“客户”对象上的该自定义链接会打开新建个案表单,将个案创建为客户记录的子级。您可以针对任何可查找“客户”对象的对象使用此过程。要创建不是另一记录子级的记录,您可以使用 $ObjectType.ObjectName 作为第二参数。例如:

{!URLFOR( $Action.Case.NewCase, $ObjectType.Case )}

$Action 全局变量需要一个记录 ID 或 $ObjectType。例如,这些公式可分别创建指向选项卡和客户详细信息页面的的链接。

{!URLFOR( $Action.Account.Tab, $ObjectType.Account )}
{!URLFOR( $Action.Account.View, Some_Account_Lookup__c.Id )}

URLFOR() 函数可获取其他可选参数,作为查询字符串参数传递到目标。您可以在用 Visualforce 页面覆盖标准操作时使用这些参数,以传入 Visualforce 页面或其控制器需要的其他参数。例如,如果在关闭个案时想要将个案上名为“实际交付日期”的自定义字段的值更改为今天,您可以使用:

{!URLFOR($Action.Case.CloseCase, Case.Id, [ actualDeliveryDate=TODAY()] )}

然后,您可以用 Visualforce 页面覆盖“结束个案”操作,并在 Visualforce 页面或其控制器中设置“实际交付日期”字段中的值。有关详细信息,请参阅在 Visualforce 页面中使用查询字符串参数”。
ceiling():数学函数。将数字向上四舍五入为最接近的整数;如果是负数,避免四舍五入为零。

常用方法

getContacts():方被称为getter方法,它是一个通用模式,其中{!您的Visualforce标记中的someExpression}将自动连接到控制器中名为getSomeExpression()的方法。这是让页面访问需要显示的数据的最简单的方法。

分页功能

<apex:page standardController="Contact" recordSetVar="contacts">
    <apex:form>
        <apex:pageBlock title="Contact List" id="contacts_list">
            Filter:
            <apex:selectList value="{!filterId}" size="1">
                <apex:selectOptions value="{!listViewOptions}"/>
                <apex:actionSupport event="onchange" reRender="contacts_list"/>
            apex:selectList>
            
            <apex:pageBlockTable value="{!contacts}" var="ct">
                <apex:column value="{!ct.FirstName}"/>
                <apex:column value="{!ct.LastName}"/>
                <apex:column value="{!ct.Email}"/>
                <apex:column value="{!ct.Account.Name}"/>
            apex:pageBlockTable>
            
            <table style="width:100%">
                <tr>
                	<td>
                        <apex:outputText value="{!PageNumber} of {!ceiling(ResultSize / PageSize)}">
                        apex:outputText>
                    td>
                    <td aligh="center">
                        
                    	<apex:commandLink action="{!Previous}" value="《 Previous" rendered="{!HasPrevious}"/>
                        <apex:outputText style="color:#ccc" value="《 Previous" rendered="{!NOT(HasPrevious)}">apex:outputText>
                        
                        
                        <apex:commandLink action="{!Next}" value="Next 》" rendered="{!HasNext}"/>
                        <apex:outputText style="color:#ccc" value="Next 》" rendered="{!NOT(HasNext)}">apex:outputText>
                    td>
                    <td align="right">
                        Records per page:
                    	<apex:selectList value="{!PageSize}" size="1">
                            <apex:selectOption itemValue="5" itemLabel="5"/>
                            <apex:selectOption itemValue="20" itemLabel="20"/>
                            <apex:actionSupport event="onchange" reRender="contacts_list"/>
                        apex:selectList>
                    td>
                tr>
            table>
        apex:pageBlock>
    apex:form>
apex:page>

效果如下:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第24张图片

静态资源

注意:一定要注意路径问题,图中因多了 ‘jQuery.mobile-1.4.5/’ 导致样式出不来
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第25张图片
salesforce visualforce引用静态资源

  • 引用图片静态资源:
  • 引用附件文件,引用静态资源的路径
  • 页面Css调用静态资源
  • 页面js调用静态资源
  • 引用Url附件

Apex 类(第一次参考别人的代码 总结的一些属性方法等)

public class VATController {
    
    public final List<VAT_Rate__mdt> VATs {get;set;}
    final Map<String, VAT_Rate__mdt> VATsByApiName {get; set;}
    
    public VATController() { 
        VATs = new List<VAT_Rate__mdt>();
        VATsByApiName = new Map<String, Vat_Rate__mdt>();
        for (VAT_Rate__mdt v : [SELECT QualifiedApiName, MasterLabel, Default__c, Rate__c FROM VAT_Rate__mdt]) { 
            VATs.add(v);
            VATsByApiName.put(v.QualifiedApiName, v);
        }
    }
    /**
        VAT_Rate__mdt:为【自定义元数据类型 增值税税率】即可看做 表名
        Default__c、Rate__c:为【自定义字段】即可看做 列名
       for循环语法: for (variable : list_or_set) { code_block }
     */
    
    
    /**
        PageReference:
        自定义控制器或控制器扩展中的任何操作方法都可以作为该方法的结果返回PageReference对象。
        如果将PageReference上的重定向属性设置为true,用户将导航到PageReference指定的URL。
        下面的示例展示了如何使用save方法实现这一点。
        在本例中,save方法返回的PageReference将用户重定向到刚刚保存的帐户记录的详细信息页面:
     */

    public PageReference save() {       
        // 创建一个元数据容器
        //Metadata.DeployContainer:管理用于部署的自定义元数据组件
        Metadata.DeployContainer container = new Metadata.DeployContainer();
        List<String> vatFullNames = new List<String>();
        for (String recordName : VATsByApiName.keySet()) {   //keySet():获取每一个键对应的值
            vatFullNames.add('VAT_Rate.' + recordName);
        }
        
        List<Metadata.Metadata> records = Metadata.Operations.retrieve(Metadata.MetadataType.CUSTOMMETADATA,  vatFullNames);
        /**
             Metadata.Operations.retrieve():从当前组织同步检索元数据的方法。
             检索和部署元数据时,请使用元数据组件的全名。全名可以包括名称空间,元数据类型和组件名称
             Metadata.MetadataType:枚举类型---有两个值
             CustomMetadata:自定义元数据类型的记录
             LAYOUT:布局
         */
        
        for (Metadata.Metadata record : records) {
            Metadata.CustomMetadata vatRecord = (Metadata.CustomMetadata) record;
            String vatRecordName = vatRecord.fullName.substringAfter('.');  //substringAfter('.'):截取'.'之后的字符
            VAT_Rate__mdt vatToCopy = VATsByApiName.get(vatRecordName);
            
            for (Metadata.CustomMetadataValue vatRecValue : vatRecord.values) {
                vatRecValue.value = vatToCopy.get(vatRecValue.field);
            }
            
            // 将记录添加到容器。
            container.addMetadata(vatRecord);

            /**
                DeployContainer方法:
                addMetadata():将自定义元数据组件添加到容器。
                clone():复制Metadata.DeployContainer。
                getMetadata():从容器中检索自定义元数据组件的列表。
                removeMetadata():从容器中删除元数据组件。
                removeMetadataByFullName(fullName):使用组件的全名从容器中删除元数据组件
             */
        }   
        
        // 使用新组件部署容器。
        Id asyncResultId = Metadata.Operations.enqueueDeployment(container, null);
        
        return null;
    }
}

Visualforce Page页面(一边学一边画的一个Page页面)



<apex:page sidebar="false" showHeader="true" tabStyle="Opportunity" standardStylesheets="true" lightningStylesheets="true" standardController="Opportunity" docType="html-5.0">
    <h1>PriceBook:安普浦鸣志价格手册h1><br/>
    <h1>Currency:CNYh1>
    <apex:form >
        <apex:pageBlock title="选择产品"> 
            <apex:pageBlockButtons >
                <apex:commandButton action="{!save}" value="保存"/>
                <apex:commandButton action="{!cancel}" value="取消"/>
            apex:pageBlockButtons>
            
            <apex:pageBlockSection >
                <apex:pageBlockTable value="{!Opportunity}" var="opportunity">
                    <apex:column >
                    	<apex:outputLink >Removeapex:outputLink>
                    apex:column>
                    <apex:column value="{!opportunity.name}"/>
                    <apex:column value="{!opportunity.amount}"/>
                apex:pageBlockTable>
            apex:pageBlockSection>
        apex:pageBlock>
    apex:form>
    
    <apex:form >
        <apex:pageBlock title="搜索产品:">
            <apex:pageBlockButtons location="top">
            	<apex:inputText />
        		<apex:commandButton value="search"/>
            apex:pageBlockButtons>
            
            <apex:pageBlockSection >
                <apex:pageBlockTable value="{!Opportunity}" var="opportunity">
                    <apex:column >
                        <apex:commandButton value="选择"/>
                    apex:column>
                    <apex:column value="{!opportunity.name}"/>
                    <apex:column value="{!opportunity.amount}"/>
                apex:pageBlockTable>
            apex:pageBlockSection>
        apex:pageBlock>
    apex:form>
apex:page>

lightningStylesheets=“true” 有无的区别
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第26张图片
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第27张图片

两种在循环中引用SOQL的方法

// 第一种
List<Account> accounts = [SELECT Id FROM Account WHERE NumberOfEmployees > 300];
for (Account acc : accounts) {
    // 具体逻辑
}

// 第二种:
for (List<Account> accounts : [SELECT Id FROM Account WHERE NumberOfEmployees > 300]) {
    for (Account acc : accounts) {
        // 具体逻辑
    }
}

效果比较
第一种方法是将所有记录先从数据库中读取出来,然后放入循环进行具体的逻辑。它只需要和数据库(服务器)进行一次通信,然后读取大量数据。
第二种方法是将 SOQL 查询放在 for 循环中,for 循环的循环变量是一个列表,接下来对这个列表进行另一个 for 循环,执行具体的逻辑。外部的 for 循环在每一次循环时,会将 Account 记录分块读取出来,放入列表变量。它的优点是每次从数据库中读取的数据量少,而缺点是需要和数据库(服务器)多次通信。
在实际开发的时候,我们需要根据具体的情况,使用不同的方法来读取数据,从而让执行效率最大化。

单元测试类

单元测试类是一种特殊的Apex类,基本语法和普通的Apex类一样。

//单元测试类的结构是:
@isTest
private class MyTestClass {
    @isTest 
    static void myTest() {
        // Test
    }
}

可以看到,“@isTest”是一个关键的注解,带有它的类和方法会被系统认定为单元测试的类和方法。
对于单元测试的方法,可以用“testMethod”关键字代替“@isTest”注解,比如:

static testMethod void myTest() {
    // Test
}

单元测试的方法必须定义在单元测试类中。
单元测试类和方法的访问控制权限并不重要。无论是定义为public还是private,系统都可以进入其中并执行测试代码。

断言函数

系统中提供了一组断言函数来进行结果的比较。它们主要包括:

  1. System.assert(condition, msg):用于判断某条件是否为真,如果不为真则给出错误信息
  2. System.assertEquals(expected, actual, msg):用于判断期望的值是否和实际的值相同,如果不同则给出错误信息
  3. System.assertNotEquals(expected, actual, msg):用于判断期望的值是否和实际的值不同,如果相同则给出错误信息

API名字

每个对象和字段都有一个唯一的名字,这个名字被称为API名字(API Name)。
比如“客户”的API名字是“Account”,“名字”字段的API名字是“Name”,“创建人”字段的API名字是“CreatedBy”。
后缀:
对于自定义对象和字段,它们的API名字必须以“__c”结尾。
比如:有一个“地址”自定义对象,其API名字是“Address__c”而不能是“Address”
当一个字段的类型是自定义关系类型,那么该字段的后缀不用“__c”,而用“__r”
对于外部对象,其API名字以“__x”结尾。

简单的使用vf标准控制器来展示Contact列表,并实现点击名称跳转详细页面。


<apex:page standardController="Contact" recordSetVar="contacts">
	<apex:pageBlock title="Contact List">
		<apex:repeat value="{!contacts}" var="ct">
			<li>
				<apex:outputLink value="/{!ct.Id}">{!ct.Name}apex:outputLink>
				
				
			li>
		apex:repeat> 
	apex:pageBlock>
apex:page>

在Apex中使用静态资源加载jquery

<apex:page>
    
    
    <apex:includeScript value="{! $Resource.jQuery }"/>
    
    
    <script type="text/javascript">
        jQuery.noConflict();
        jQuery(document).ready(function() {
            jQuery("#message").html("Hello from jQuery!");
        });
    script>
    
    
    <h1 id="message">h1>
    
apex:page>

在report中使用The “Power of One” technique来统计不重复的数据

利用公式字段插入图片:IMAGE(path,img_title,height,width)

Apex中在使用类继承时需要使用到的关键字

extends,super,virtual,override.跟Java继承不同的是,超类必须使用virtual修饰,子类使用override和extends修饰,如果需要重写父类的方法,父类中该方法需要用virtual修饰,子类需要使用override。另外如果子类需要使用超类的域或者方法则需要使用super关键字,注意构造方法的复用不需要用成对的virtual和override关键字修饰超类的构造方法和子类的构造方法。

计算两个日期之间相隔的天数

TODAY() - DATEVALUE( CreatedDate )
Implementd__Date__c - DATEVALUE( CreatedDate )
// 注意:只有当日期为标准字段时,才使用DATEVALUE()来转化

标准字段的API Name即为该标准字段的Field Name

使用SOQL语句的注意事项

  1. 字符串类型的只能使用单引号
  2. 尽量使用Order by语句,保证查询结果的稳定性
  3. 使用Limit,防止数据量超过50000而报错
  4. 使用Offset偏移时,一定放在该查询语句的最后,其限制及应用场景见:参考见链接

15位Id与18位Id的区别

当我们在对象中创建记录时,salesforce将创建18位惟一的数字来识别它。这是不区分大小写的
相同的18位数字表示为15位id,区分大小写
Id 001—9000001—1o2Of 在这个15/18位的Id中,前3位表示对象,后5位表示记录

apex:page组件与apex:sectionHeader组件属性的相互影响

<span style="color:#ff0000">如果page中用到了setup="true"属性,那么sectionHeader中就不能显示对象图标。span>

为pageBlockTable中的column组件设置宽度不生效的解决方案

  1. 嵌套关系1: form>pageBlock>pageBlockTable>column,可以直接为column增加width的style或者styleClass,需要的话pageBlockTable设置个width:100%;
  2. 嵌套关系2: form>pageBlock>pageBlockSection>pageBlockTable>column,这个时候添加style和styleClass是不生效的,此时只需要在pageBlockTable中设置columnsWidth=“width1,width2…”;

apex查看当前页面url是否包含某个字符串

ApexPages.currentPage().getUrl().contains('st')

将字符串形式转换成Decimal格式

Decimal.valueOf(ApexPages.currentPage().getParameters().get('st'))

使用apex处理日期

// a. DateTime in Apex
DateTime nowTime = System.now();
String now_date = nowTime.format('yyyy-MM-dd');
String now_dateTime = nowTime.format('yyyy-MM-dd HH:mm:ss');
System.debug(now_date+'<------>'+now_dateTime);

// debug信息
// 14:23:00:002 USER_DEBUG [4]|DEBUG|2017-07-26<------>2017-07-26 14:23:00

// b. Date in Apex - String to Date | Date Class valueOf(stringDate)
String strDate1 = '2018-6-6';
String strDate2 = '2018-06-06';
String strDate3 = '2018/6/6';
Date myDate1 = date.valueOf(strDate1);
Date myDate2 = date.valueOf(strDate2);
//Date myDate3 = date.valueOf(strDate3);//System.TypeException: Invalid date: 2018/6/6
System.debug('myDate1: ' + myDate1);
System.debug('myDate2: ' + myDate2);
//System.debug('myDate3: ' + myDate3);

Apex实例化一个内部类

public class OuterClass {
    public class InnerClass {
        String name = '';
        Blob body = '';
    }
}
// 实例化内部类
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = new OuterClass.InnerClass();
/*接口类实例化*/
global class SyncInterface {    
    // 封装数据结构 - 返回结果  
    global class SyncResults{  
        global List<Response> responses;  
    }  
      
    global class Response{  
        public String TYPE;  
        public String MSG;  
        public String BUSINESS_ID;  
        public String SALESFORCE_ID;  
        public String PROC_TYPE;              
    }  
}
 
// 实例化接口response list
SyncResults syncResponseList = new SyncResults();  
syncResponseList.responses = new List<Response>();

使用Database.query()查询数据集合

String queryIDSQL = 'SELECT Name,ProductCode,Product_Model__c,Product_Category__c,' + 
                    'Family,CharacteristicAttribute__c,Description,ListPrice__c,List_Prices__c ' +
                    'FROM Product2';
if(selectProId.length() > 0){
    queryIDSQL += ' WHERE Name LIKE \'%' + selectProId + '%\'';
}
Products = Database.query(queryIDSQL);
filterStr = 'AND Type__c = \'Text\' ';// 注意写成转义字符形式
query = 'SELECT Name, Type__c, Message__c, Msgpic__c, Mediaid__c, VioceRecognition__c, VoiceFormat__c, VoiceSrc__c ' + 
        'FROM BD_CaseDetail__c ' + 
        'WHERE Case__c = :caseId ' + filterStr + 'ORDER BY CreatedDate ASC LIMIT 500';
caseDetailList = Database.query(query);

caseDetailList = [SELECT Name, Type__c, Message__c, Msgpic__c, Mediaid__c, VioceRecognition__c, VoiceFormat__c, VoiceSrc__c
                  FROM BD_CaseDetail__c
                  WHERE Case__c = :caseId AND Type__c = 'Text' ORDER BY CreatedDate ASC LIMIT 500];

Live Agent配置

详见链接

  1. 先启用live agent - setup -> live agent settings -> enable
  2. My Settings -> Advanced settings -> Live Agent User(勾选上)

创建Task

//任务:
Task task = new Task();
task.Subject = ‘A信息修改申请';
task.status = 'open';
task.priority = 'High';
task.whatId = '0017F00000CfcdsQAB';// 相关项目,这里是供应商Id
task.ownerId = '0057F000000pe6uQAA';// 被分配人,这里是UserId
// 以下两行是设置提醒
task.IsReminderSet = true;
task.ReminderDateTime = System.now();
insert task;

salesforce soql中不能对公式字段使用Group By

Partner User对Quote Tab不可见,Quote无Sharing Rule,它的共享受Opportunity的控制,如果对Partner User的Quote的OLS设置为View且Opportunity页面布局的Quote放出来了,如果共享Opp的Owner没有创建Quote记录,Partner User不可见Quote相关列表,需要创建一条Quote,才可以看见。

使用apex获取IP地址

ApexPages.currentPage().getHeaders().get('X-Salesforce-SIP')

获取某个日期所在月份的天数

Date d = System.today();
Integer numberDays = date.daysInMonth(d.Year(), d.Month());
System.debug(numberDays);

去重Sample

Map<String, String> accIDs = new Map<String,String>();
for(Quote qt: (List<Quote>)Trigger.New){
    if(!accIDs.containsKey(qt.Quote_To_Account__c))
        accIDs.put(qt.Quote_To_Account__c,'');
}
Map<String, Account> accs = new Map<String, Account>([SELECT id,BillingStreet,BillingCity, BillingState, BillingPostalCode, BillingCountry FROM Account WHERE Id IN : accIDs.keySet()]);

启用或禁用内联编辑

有时候我们希望禁用用户默认的行内编辑功能,直接去user interface中将Enable Inline Editing关掉即可

Dynamic Query - String Join的用法:join(iterableObj, separator)

List<String> fNames = new List<String>{'Name', 'Industry', 'Type'};
String objectName = 'Account';
String query = 'SELECT Id'+ (fNames.size() > 0 ? ',' + String.join(fNames,',') : '') +' FROM '+ objectName;
System.debug(query);

// join(iterableObj, separator)
// 将指定的可迭代对象(如列表)的元素连接到指定分隔符分隔的单个字符串中。

获取sandbox name / community站点URL

ConnectApi.CommunityPage cc = ConnectApi.Communities.getCommunities();
System.debug(cc.communities[0].siteUrl);

禁用双重身份验证

方法1:设置IP安全范围0.0.0.0 - 255.255.255.255;
方法2:在Profile里面禁用以下开关:
在这里插入图片描述

Date / DateTime Format公共方法

/*---------Demo--------*/
System.debug(String.valueOf(System.now().format('yyyy/MM/dd hh:mm')));
 
Date dt = Date.today().addDays(15);
Datetime dtDateTime = dt; // Implicit cast
System.debug(dtDateTime.format('yyyy/MM/dd'));
 
 
/*---------公共方法--------*/
public static String dateOrTimeFormat(String type, Object dateOrTime) {
    Datetime dt = (Datetime) dateOrTime;
    return 'Date'.equals(type) ? dt.format('yyyy/MM/dd') : dt.format('yyyy/MM/dd HH:mm');
}
 
/*---------应用--------*/
Test__c t = [SELECT Id, Date__c, DateTime__c FROM Test__c WHERE Id = 'a0F7F00000DdKfe'];
System.debug('before convert...');
System.debug(t.Date__c);
System.debug(t.DateTime__c);
 
System.debug('after convert...');
Datetime dtDateTime = t.Date__c;
System.debug(UtilityClass.dateOrTimeFormat('Date', dtDateTime));
System.debug(UtilityClass.dateOrTimeFormat('DateTime', t.DateTime__c));

效果:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第28张图片

String.leftPad(length, strNo)用法

String countString = '520';
System.debug(countString.leftPad(5, '0'));

效果:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第29张图片

使用Custom Label自定义标间 - 换行


<apex:outputText value="{!$Label.Test_With_Line_Breaks}" escape="false" />

将List转成按分隔符隔开的String:join(iterableObj, separator)

List<String> sl = new List<String> {'a','b','c'};
system.debug(sl);
String s = String.join(sl, ';');
system.debug(s);

Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第30张图片

将Set转成List合并

Set<String> codes = new Set<String>{'001', '002', '003'};
//String mergeSet = String.join(codes, ';');// 语法错误
String mergeStr = String.join(new List<String>(codes), ';');
//System.debug(mergeSet);
System.debug(mergeStr);

将List集合转成Set集合

List<String> sLst = new List<String> {'1', '2', '1'};
System.debug(new Set<String> (sLst));

判断字符串是否以特定值结尾

field.toLowerCase().endsWith('id');

判断字符串是否为Salesforce ID:Salesforce sObject Id Validation in Apex

public static Boolean isValid(String stringValue, Schema.SObjectType sObjectType) {
    Id sObjectId;
    if(isId(stringValue)) sObjectId = (Id)stringValue;
    return isValid(sObjectId, sObjectType);
}
 
public static Boolean isValid(Id sObjectId, Schema.SObjectType sObjectType) {
    return !(sObjectId==null || sObjectId.getSObjectType()!=sObjectType);
}
 
public static Boolean isId(String stringValue) {
    return stringValue InstanceOf Id;
}

将字符串转化成ID原始数据类型

Id myId = Id.valueOf('001xa000003DIlo');

让apex:commandButton仅仅执行js客户端事件,不执行服务端方法

<apex:commandButton value="New" onclick="navigateToUrl('/setup/ui/recordtypeselect.jsp?ent=Lead&retURL=%2F00Q%2Fo&save_new_url=%2F00Q%2Fe%3FretURL%3D%252F00Q%252Fo',null,'new');return false;"/>

获取Label值

{!$ObjectType.ACC_Return_Order_Product__c.LabelPlural}

{!$ObjectType.ACC_Return_Order_Product__c.Fields.ACC_Product__c.Label}

复选框公式中使用基本逻辑(Logical Function)

基本逻辑运算符:NOT() AND() OR() IF()
AND():当且仅当两个参数都为true时,才会返回true,反之返回false
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第31张图片
OR():如果至少一个条件为true,则返回true,反之返回false
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第32张图片
NOT():将正确的结果更改为false,将错误的结果更改为true
IF():IF(test, result, alternate)

文本(Text Function)相关常用函数

BEGINS(text,compare_text):判断某个字符串是否以指定的字符串开始,是返回true,否则返回false;
BR():插入一行,类似HTML中的

CONTAINS(text, compare_text):判断字符串中是否包含指定的字符串,如果包含返回true,否则返回false;
FIND(search_text, text[, start_num]):查询指定的字符串在字符串的位置,类似apex中的indexOf方法的功能,坐标从1开始;
ISPICKVAL(picklist_field, text_literal):判断picklist字段当前的记录是否为某个字符串;
LEN(text):返回字符串的长度;
TEXT(value):将其他类型转换成字符串,包括percent/number/date等;
TRIM(text):将字符串进行去空处理,与apex中String.trim用法相同;
VALUE(text):将字符串转换成number类型。
部份函数代码示例

// TextRelatedFunctionController:定义三种对象,方便page页逻辑处理
public with sharing class TextRelatedFunctionController {
    public String testVariable{get{
        return 'test value';
    }}
      
    public String testTrimVariable{get{
        return ' test value ';
    }}
      
   public String testToNumberVariable {get{
       return '123';
   }}
 }

<apex:page controller="TextRelatedFunctionController">
      testVariable对象的值为:{!testVariable}
      <apex:pageBlock title="BEGINS函数用法">
          testVariable对象值是否以test字符开始 :  {!BEGINS(testVariable,'test')}
      apex:pageBlock>
      <apex:pageBlock title="CONTAINS函数用法">
          testVariable对象值是否包含test字符 :  {!CONTAINS(testVariable,'test')}
      apex:pageBlock>
     
     <apex:pageBlock title="FIND函数用法">
         testVariable对象中lue所在的位置:{!FIND('lue',testVariable)}
     apex:pageBlock>
     <apex:pageBlock title="LEN函数用法">
         testTrimVariable对象值的长度为: {!LEN(testTrimVariable)}
     apex:pageBlock>
     <apex:pageBlock title="TRIM函数用法">
         testTrimVariable对象trim以后长度为:{!LEN(TRIM(testTrimVariable))}
     apex:pageBlock>
     <apex:pageBlock title="VALUE函数用法">
         <apex:outputText value="{!IF(VALUE(testToNumberVariable) > 100,'testToNumberVariable转换成number大于100','testToNumberVariable转换成number小于100')}">
         apex:outputText>
     apex:pageBlock>
 apex:page>

效果:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第33张图片
BLANKVALUE(expression,substitute_expression):当某个变量或者某个值为空字符串情况下设置默认值,形参一为变量,形参二为所替换成的默认值;
ISBLANK(expression):判断某个表达式或者变量是否为空,如果value为空或者为null则返回true,否则返回false;
NULLVALUE(expression,substitute_expression):当某个变量或者某个值为null情况下设置默认值,形参一为变量,形参二为所替换成的默认值;
PRIORVALUE():通常用于validation rule中获取update前一刻的值。
代码示例:

// controller层声明一个变量,设置为null情况
public with sharing class InformationRelatedFunctionController {
    public String testVariable{get;set;}
}

<apex:page controller="InformationRelatedFunctionController">
    testVariable是否为空:<apex:outputText value="{!ISBLANK(testVariable)}">apex:outputText>
    <br/>
    testVariable是否为null:<apex:outputText value="{!ISNULL(testVariable)}">apex:outputText>
    <br/>
    testVariable为空情况下设置一个默认值:<apex:outputText value="{!blankvalue(testVariable,'测试默认值')}">apex:outputText>
    <br/>
apex:page>

效果:
在这里插入图片描述

日期(Date)相关函数

now():此函数可以获取当前时刻的信息,精确到秒,如果需要某种格式的显示,可以进行相关格式的format;
today():此函数可以获取当前日期的信息,包括年月日;
year(date):此函数获取指定date的年的信息,使用此函数需要传递一个date类型的参数;
month(date):此函数获取指定date的月的信息,使用此函数需要传递一个date类型的参数;
day(date):此函数获取指定date的日的信息,使用此函数需要传递一个date类型的参数;
date(year,month,day):此函数通过参数传递年月日三个参数返回一个date类型的结果,包括年月日以及星期等信息;
datevalue(expression):此函数通过expression转换成date值,参数可以是date/time value或者text value或者表达式;
datetimevalue(expression):此函数通过expression转换成date/time值,参数可以是date/time value或者text value或者表达式。
代码示例:

<apex:page >
      <apex:pageBlock title="日期相关函数汇总">
          当前的时间为:
          <apex:outputText value="{0,date,YYYY-MM-DD HH:mm:ss}">
              <apex:param value="{!now()}" />
          apex:outputText>
          <br />
          当前的日期为:
          <apex:outputText value="{!TODAY()}">apex:outputText>
         <br />
         当前的日期(格式转换)为:
         <apex:outputText value="{0,date,YYYY-MM-DD}">
             <apex:param value="{!TODAY()}" />
         apex:outputText>
         <br />
         当前日期的年:
         <apex:outputText value="{!year(today())}">apex:outputText>
         <br />
         当前日期的月:
         <apex:outputText value="{!month(today())}">apex:outputText>
         <br />
         当前日期的日:
         <apex:outputText value="{!day(today())}">apex:outputText>
         <br />
         自定义年月日设置日期:
         <apex:outputText value="{!date(2017,2,13)}">apex:outputText>
         <br />
         使用datevalue函数显示日期(参数为date/time类型):
         <apex:outputText value="{!datevalue(now())}">apex:outputText>
         <br />
         使用datevalue函数显示日期(参数为文本类型):
         <apex:outputText value="{!datevalue('2017-02-13')}">apex:outputText>
         <br />
         使用DATETIMEVALUE函数返回一个日期+GMT时间的值,传递的参数可以为date/time类型也可以为字符串:
         <apex:outputText value="{!datetimevalue('2017-02-13 11:00:00')}">apex:outputText>
     apex:pageBlock>
 apex:page>

效果:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第34张图片

数学(Math)函数

abs():返回参数的绝对值。
ceil():返回大于等于( >= )给定参数的的最小整数。返回类型为double。
floor():返回小于等于(<=)给定参数的最大整数 。返回类型为double。
rint():返回与参数最接近的整数。返回类型为double。
round():它表示四舍五入,算法为 Math.floor(x+0.5),即将原来的数字加上 0.5 后再向下取整,所以,Math.round(11.5) 的结果为12,Math.round(-11.5) 的结果为-11。
min():返回两个参数中的最小值。
max():返回两个参数中的最大值。
exp():返回自然数底数e的参数次方。
log():返回参数的自然数底数的对数值。
pow():返回第一个参数的第二个参数次方。
sqrt():求参数的算术平方根。
sin():求指定double类型参数的正弦值。
cos():求指定double类型参数的余弦值。
tan():求指定double类型参数的正切值。
asin():求指定double类型参数的反正弦值。
acos():求指定double类型参数的反余弦值。
atan():求指定double类型参数的反正切值。
atan2():将笛卡尔坐标转换为极坐标,并返回极坐标的角度值。
toDegrees():将参数转化为角度。
toRadians():将角度转换为弧度。
random():返回一个随机数

计算帐户的累计利息

已经有了一个数学公式来计算一段时间内的帐户利息:A = Pe ^(rt),它使用以下变量
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第35张图片
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第36张图片

在Apex中获取标签

自定义标签的字符数限制为1,000,可以从Apex类访问。Apex类与语法System.Label。MyLabelName用于浏览标签。

在做增删改查时,删除某条数据后刷新报错:SObject row was retrieved via SOQL without querying the requested field: OpportunityLineItem.Product2Id

Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第37张图片

reRender重画

Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第38张图片
参考:
rerender 可以指定页面重画区域,这个其实就是visualforce中的AJAX 技术。
但是,要保证这个有效果,有些注意事项。

  1. rerender可以指定的区域,目前只发现apex:outputpanel 标记好用。 用DIV的话不行。
    通常的写法例子如下:
<apex:page controller="SearchController">
 
<apex:form>
    <apex:inputText value="{! freeWord }" size="60" />
    <apex:commandLink value=" 搜索 " title=" 搜索 " rerender="opSearchResultByFreeWord"
       id="searchBtn" action="{! doFreeWordSearch }" />
 
    
    <apex:outputPanel id="opSearchResultByFreeWord">
        <apex:pageBlock id="resultBlock"  >
            <apex:pageBlockTable value="{! searchRsltByFreeWord }" var="item"
                column headerValue="名称" >
                    <apex:outputField value="{!item.name}" />
                apex:column>
                <apex:column headerValue="说明" >
                    <apex:outputField value="{!item.describe__c}" />
                apex:column>
           apex:pageBlockTable>
        apex:pageBlock>
    apex:outputPanel>
 
apex:form>
apex:page>
  1. 如果outputPanel里面有rendered指定的话,这个区域也是没有办法重画的。
    简单的处理办法是,在这个outputPanel外面再嵌套一层outputPanel,并设置Id,这样就可以用了

可以同时定义两个reRender值,中间用逗号隔开,如下图:
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第39张图片

创建sObject和添加字段

// 创建对象 new  
// API对象名称成为Apex中sObject变量的数据类型。在这个例子中 帐户 是的数据类型 帐户 变量。
Account acct = new Account();

// 有两种添加字段的方法:通过构造函数或使用点表示法。
// 添加字段的最快方法是在构造函数中将它们指定为名称/值对。例如,此语句创建一个新帐户sObject,并使用字符串值填充其“名称”字段
Account acct = new Account(Name='Acme');
// 名称字段是帐户唯一需要的字段,这意味着必须先填充该字段,然后才能插入新记录。但是,您也可以为新记录填充其他字段。此示例还添加了电话号码和员工人数。
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);

// 或者,您可以使用点表示法将字段添加到sObject。以下内容与前面的示例等效,尽管它需要花费更多的代码行。
Account acct = new Account();
acct.Name = 'Acme';
acct.Phone = '(415)555-1212';
acct.NumberOfEmployees = 100;

使用通用sObject数据类型

// 使用通用sObject数据类型声明的变量可以引用任何Salesforce记录,无论它是标准记录还是自定义对象记录。
// 本示例说明了如何将通用sObject变量分配给任何Salesforce对象:一个帐户和一个名为Book__c的自定义对象。
// 相反,使用特定sObject数据类型声明的变量只能引用相同类型的Salesforce记录。

// 将通用sObject强制转换为特定的sObject类型
// 本示例说明如何将通用sObject强制转换为Account
Account acct = (Account)myGenericSObject;
String name = acct.Name;
String phone = acct.Phone;

// 与特定的sObjects类型不同,通用sObjects只能通过newSObject()方法创建。此外,只能通过put()和get()方法访问泛型sObject的字段。

SOQL父子查询

Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第40张图片
详见参考链接

报错 无线循环JS里的方法

Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第41张图片
解决方案:js方法名字不要和apex里的方法名字重名

在主页添加自定义对象选项卡

设置 ⇒ 快速查询中搜索 选项卡 ⇒ 选择 选项卡 ⇒ 点击新建
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第42张图片
选择你要创建选项卡的自定义对象并选择样式,点击保存即可
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第43张图片

Lightning 在对象页面中新建按钮

设置 ⇒ 对象管理器 ⇒ 选择需要创建按钮的对象 ⇒ 选择 按钮、链接和操作 ⇒ 点击 新建操作
在这里插入图片描述
操作类型选择Lightning组件,Lightning 组件中选择相关代码文件,目标对象就是选择你的对象,标准标签类型可以选择无,写好标签和名称点击保存即可
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第44张图片
保存完后,在左边选择页面布局
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第45张图片
将创建的按钮拖动到页面上(不然还是在对象主页中看不见),然后点击保存
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第46张图片
打开对象的页面即可查看到新创建的按钮
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第47张图片

重命名选项卡和标签

如果要修改标注字段的标签名,可以在这里操作
设置 ⇒ 快速搜索中 输入 重命名 ⇒ 选择重命名选项卡和标签 ⇒ 选择你要修改的对象 点击编辑 ⇒
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第48张图片
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第49张图片
在这里我修改客户名为客户公司名,点击保存即可
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第50张图片
修改完后,返回主页点击客户选项卡 选择客户信息 点击详细信息 即可查看刚修改的内容,如下图
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第51张图片

从沙盒环境打包部署到生产环境

在设置中搜索出站更改集
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第52张图片
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第53张图片
写好名称和描述之后
在“更改集组件”相关列表中,单击添加。
将配置文件添加到您要授予访问权限的用户的更改集中。
为要部署的组件选择“组件类型”(例如,Apex类,Apex触发器)。
选择特定的类或触发器名称,然后单击“ 添加到变更集”。
从“更改集详细信息”相关列表中,单击上载,然后将目标组织选择为生产。
点击上传,上传成功后如下图
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第54张图片

工作流

应用目的:用工作流将账套信息更新到放行单主信息上
1.在设置中输入工作流,点击工作流规则,点击新建规则。如下图
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第55张图片
2.选择对象(需要赋值给哪个对象就选择哪个对象【也可以选择该对象的子对象,如本场景所示】)如下图
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第56张图片
3.填写好一下信息。然后点击保存并继续下一步。如下图
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第57张图片
4.在即时工作流中,添加工作流(这里之前有添加过所以有显示)如下图
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第58张图片
5.选择需要更新的字段,这里选择的是主对象的某个字段。在公式中选择赋值的字段点击保存即可。如下图
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第59张图片
6.即可(记得启用!)
Apex & Lightning Salesforce 学习笔记及报错问题(持续更新)_第60张图片
〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰

更多博客内容请查看 可可西里的博客

如果喜欢的话可以点个赞和关注嗷~

你可能感兴趣的:(Apex,Salesforce)