You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过扩展控制器将Visualforce生成的PDF保存至商机页面文件

问题分析与解决方案

核心问题

报错“QuotePDFSaveExt(ApexPages.StandardController ctrl)存在空引用”的原因是:生成PDF的Visualforce页面使用了自定义控制器QuoteOppProductCtrl,但同时指定了扩展QuotePDFSaveExt,而该扩展的构造函数仅支持StandardController类型。当访问PDF页面时,系统无法提供StandardController实例,导致ctrl为空,触发空引用错误。

另外,现有保存逻辑中直接调用pdfPage.getContentAsPDF()会再次触发PDF页面的action="{!generateAndStoreQuoteNumber}",可能导致重复生成报价编号,需要优化。


修复步骤

1. 调整生成PDF的Visualforce页面

移除不必要的扩展,因为该页面依赖自定义控制器,不需要QuotePDFSaveExt扩展:

<apex:page controller="QuoteOppProductCtrl" applyHtmlTag="false" showHeader="false" renderAs="PDF" action="{!generateAndStoreQuoteNumber}">  
----------------the body of the content of the PDF is here ------------
</apex:page>

2. 修正扩展控制器QuotePDFSaveExt

  • 确保构造函数仅在保存页面(使用StandardController)时生效
  • 增加空值校验,防止商机ID为空
  • 优化PDF存储的命名规则,更易识别
public with sharing class QuotePDFSaveExt{
    private final Opportunity opp;

    // 仅支持StandardController构造,对应保存用的VF页面
    public QuotePDFSaveExt(ApexPages.StandardController ctrl) {
        // 直接查询商机记录,同时校验ID是否有效
        if(ctrl.getId() != null){
            opp = [SELECT Id, Name FROM Opportunity WHERE Id = :ctrl.Id LIMIT 1];
        } else {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, '无法获取商机ID,请从商机页面操作'));
        }
    }
        
    public PageReference savePDF() {
        PageReference ret = null;
        // 校验商机是否存在
        if(opp == null){
            return ret;
        }

        try {
            // 构造PDF页面引用,传递商机ID
            PageReference pdfPage = Page.QuoteOppProductVfp;
            pdfPage.getParameters().put('Id', opp.Id);
            
            // 获取PDF内容,触发PDF页面的action方法生成报价编号
            Blob pdfBlob = Test.isRunningTest()
                ? Blob.valueOf('Test PDF Content')
                : pdfPage.getContentAsPDF();

            // 创建ContentVersion存储PDF
            ContentVersion cv = new ContentVersion(
                Title = '报价单_' + opp.Name + '_' + opp.Id,
                PathOnClient = '报价单_' + opp.Name + '_' + opp.Id + '.pdf',
                VersionData = pdfBlob,
                FirstPublishLocationId = opp.Id
            );
            insert cv;

            // 查询对应的ContentDocument,跳转到文件详情页
            ContentDocument doc = [SELECT Id FROM ContentDocument WHERE LatestPublishedVersionId = :cv.Id LIMIT 1];
            ret = new ApexPages.StandardController(doc).view();
            
        } catch (Exception e) {
            ApexPages.addMessages(e);
        }
        return ret;
    }
}

3. 保留保存用的Visualforce页面(仅优化样式)

给iframe添加宽高样式,提升预览体验:

<apex:page standardController="Opportunity" extensions="QuotePDFSaveExt">
    <apex:pageMessages />
    <apex:form >
        <apex:pageBlock title="预览并保存报价单">
            <apex:pageBlockButtons >
                <apex:commandButton value="保存报价单" action="{!savePDF}" />
            </apex:pageBlockButtons>
            <apex:iframe src="{!URLFOR($Page.QuoteOppProductVfp, null, [id=Opportunity.Id])}" scrolling="true" style="width:100%;height:800px;" />
        </apex:pageBlock>
    </apex:form>
</apex:page>

4. 优化自定义控制器QuoteOppProductCtrl(必填)

确保控制器能正确从URL参数获取商机ID,避免空引用:

public with sharing class QuoteOppProductCtrl {
    public Date Today { get { return Date.today(); } }
    public Opportunity opportunity { get; set; }
    public String shipToAccountName { get; set; }
    public String prosthetistName { get; set; }
    public String fittingProsthetistId { get; set; }
    public String prosthetistEmail { get; set; }
    public Decimal subtotal { get; set; }
    public Decimal quoteTotal { get; set; }
    public String quoteNumber { get; set; }

    public QuoteOppProductCtrl() {
        // 从URL参数获取商机ID
        String oppId = ApexPages.currentPage().getParameters().get('Id');
        if(oppId != null){
            opportunity = [SELECT Id, Name, Account.Name FROM Opportunity WHERE Id = :oppId LIMIT 1];
            // 其他初始化逻辑(比如汇总产品、生成报价编号)...
        } else {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, '未传入有效商机ID'));
        }
    }

    // generateAndStoreQuoteNumber方法实现...
}

操作流程

  1. 从商机页面点击自定义按钮,跳转到保存用的Visualforce页面
  2. 页面通过iframe自动加载并预览PDF内容
  3. 点击「保存报价单」按钮,系统将PDF存储为商机关联的文件,并自动跳转到文件详情页

内容的提问来源于stack exchange,提问作者amyverghese

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 01:30:38