package pdf import ( "bytes" "context" "fmt" "strings" "github.com/jung-kurt/gofpdf/v2" "go.uber.org/zap" ) // GenerateQYGLReportPDF 使用 gofpdf 根据 QYGLJ1U9 聚合结果生成企业全景报告 PDF, // 尽量复刻 qiye.html 中的板块顺序和展示逻辑。 // report 参数通常是从 JSON 反序列化得到的 map[string]interface{}。 func GenerateQYGLReportPDF(_ context.Context, logger *zap.Logger, report map[string]interface{}) ([]byte, error) { if logger == nil { logger = zap.NewNop() } pdf := gofpdf.New("P", "mm", "A4", "") // 整体页边距和自动分页底部预留稍微加大,整体更「松」一点 pdf.SetMargins(15, 25, 15) pdf.SetAutoPageBreak(true, 22) // 加载中文字体 fontManager := NewFontManager(logger) chineseFontLoaded := fontManager.LoadChineseFont(pdf) fontName := "ChineseFont" if !chineseFontLoaded { // 回退:使用内置核心字体(英文),中文可能会显示为方块 fontName = "Arial" } // 整体默认正文字体稍微放大 pdf.SetFont(fontName, "", 14) // 头部信息,对齐 qiye.html entName := getString(report, "entName") if entName == "" { entName = "未知企业" } creditCode := getString(report, "creditCode") if creditCode == "" { creditCode = getStringFromMap(report, "basic", "creditCode") } basic := getMap(report, "basic") entStatus := getString(basic, "status") if entStatus == "" { entStatus = getString(basic, "entStatus") } entType := getString(basic, "entType") reportTime := getString(report, "reportTime") riskOverview := getMap(report, "riskOverview") riskScore := getString(riskOverview, "riskScore") riskLevel := getString(riskOverview, "riskLevel") // 首页头部 pdf.AddPage() // 预留更多顶部空白,让标题整体再下移一些 pdf.Ln(20) // 顶部大标题:居中、加粗、蓝色,更大字号 pdf.SetFont(fontName, "B", 34) pdf.SetTextColor(37, 99, 235) pdf.CellFormat(0, 18, "企业全景报告", "", 1, "C", false, 0, "") pdf.Ln(10) // 恢复正文颜色 pdf.SetTextColor(0, 0, 0) pdf.SetFont(fontName, "", 18) pdf.MultiCell(0, 9, fmt.Sprintf("企业名称:%s", entName), "", "L", false) pdf.SetFont(fontName, "", 14) pdf.MultiCell(0, 7, fmt.Sprintf("统一社会信用代码:%s", safePlaceholder(creditCode)), "", "L", false) pdf.MultiCell(0, 7, fmt.Sprintf("经营状态:%s", safePlaceholder(entStatus)), "", "L", false) pdf.MultiCell(0, 7, fmt.Sprintf("企业类型:%s", safePlaceholder(entType)), "", "L", false) if reportTime != "" { pdf.MultiCell(0, 7, fmt.Sprintf("报告生成时间:%s", reportTime), "", "L", false) } // 综合风险评分(使用 riskOverview) pdf.Ln(6) pdfSubTitle(pdf, fontName, "综合风险评分") pdf.SetFont(fontName, "", 13) if riskScore != "" { pdf.MultiCell(0, 7, fmt.Sprintf("风险得分:%s", riskScore), "", "L", false) } if riskLevel != "" { pdf.MultiCell(0, 7, fmt.Sprintf("风险等级:%s", riskLevel), "", "L", false) } // 头部风险标签 if tags, ok := riskOverview["tags"].([]interface{}); ok && len(tags) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "", 11) pdf.MultiCell(0, 5, "风险标签:", "", "L", false) for _, t := range tags { pdf.MultiCell(0, 5, fmt.Sprintf("· %v", t), "", "L", false) } } // 对齐 qiye.html 的板块顺序 sections := []struct { key string title string }{ {"riskOverview", "风险情况(综合分析)"}, {"basic", "一、主体概览(企业基础信息)"}, {"branches", "二、分支机构"}, {"shareholding", "三、股权与控制"}, {"controller", "四、实际控制人"}, {"beneficiaries", "五、最终受益人"}, {"investments", "六、对外投资"}, {"guarantees", "七、对外担保"}, {"management", "八、人员与组织"}, {"assets", "九、资产与经营(年报)"}, {"licenses", "十、行政许可与资质"}, {"activities", "十一、经营活动"}, {"inspections", "十二、抽查检查"}, {"risks", "十三、风险与合规"}, {"timeline", "十四、发展时间线"}, {"listed", "十五、上市信息"}, } for _, s := range sections { switch s.key { case "riskOverview": renderPDFRiskOverview(pdf, fontName, s.title, riskOverview) case "basic": renderPDFBasic(pdf, fontName, s.title, basic, entName, creditCode) case "branches": renderPDFBranches(pdf, fontName, s.title, getSlice(report, "branches")) case "shareholding": renderPDFShareholding(pdf, fontName, s.title, getMap(report, "shareholding")) case "controller": renderPDFController(pdf, fontName, s.title, getMap(report, "controller")) case "beneficiaries": renderPDFBeneficiaries(pdf, fontName, s.title, getSlice(report, "beneficiaries")) case "investments": renderPDFInvestments(pdf, fontName, s.title, getMap(report, "investments")) case "guarantees": renderPDFGuarantees(pdf, fontName, s.title, getSlice(report, "guarantees")) case "management": renderPDFManagement(pdf, fontName, s.title, getMap(report, "management")) case "assets": renderPDFAssets(pdf, fontName, s.title, getMap(report, "assets")) case "licenses": renderPDFLicenses(pdf, fontName, s.title, getMap(report, "licenses")) case "activities": renderPDFActivities(pdf, fontName, s.title, getMap(report, "activities")) case "inspections": renderPDFInspections(pdf, fontName, s.title, getSlice(report, "inspections")) case "risks": renderPDFRisks(pdf, fontName, s.title, getMap(report, "risks")) case "timeline": renderPDFTimeline(pdf, fontName, s.title, getSlice(report, "timeline")) case "listed": renderPDFListed(pdf, fontName, s.title, getMap(report, "listed")) } } // 输出为字节 var buf bytes.Buffer if err := pdf.Output(&buf); err != nil { return nil, fmt.Errorf("输出企业全景报告 PDF 失败: %w", err) } return buf.Bytes(), nil } // 辅助方法 func getString(m map[string]interface{}, key string) string { if m == nil { return "" } if v, ok := m[key]; ok && v != nil { if s, ok := v.(string); ok { return s } return fmt.Sprint(v) } return "" } func getStringFromMap(root map[string]interface{}, field string, key string) string { child := getMap(root, field) return getString(child, key) } func getMap(m map[string]interface{}, key string) map[string]interface{} { if m == nil { return map[string]interface{}{} } if v, ok := m[key]; ok && v != nil { if mm, ok := v.(map[string]interface{}); ok { return mm } } return map[string]interface{}{} } func getSlice(m map[string]interface{}, key string) []interface{} { if m == nil { return nil } if v, ok := m[key]; ok && v != nil { if arr, ok := v.([]interface{}); ok { return arr } } return nil } func writeKeyValue(pdf *gofpdf.Fpdf, fontName, label, value string) { if value == "" { return } // 计算表格布局:左侧标签列 + 右侧内容列(模块内数据统一用表格框展示) pageW, pageH := pdf.GetPageSize() lMargin, _, rMargin, bMargin := pdf.GetMargins() labelW := 40.0 valueW := pageW - lMargin - rMargin - labelW // 行高整体拉宽一点 lineH := 10.0 // 当前起始坐标(统一从左边距起,栅格化对齐) x := lMargin y := pdf.GetY() // 预计算内容高度:使用 SplitLines 获取行数,避免长内容导致单元格高度不够 lines := pdf.SplitLines([]byte(value), valueW) rowH := float64(len(lines)) * lineH if rowH < lineH { rowH = lineH } // 轻微增加上下留白,避免文字贴边 rowH += 2 // 如剩余空间不足,先换到新页再画整行,避免表格跨页导致内容重叠 if y+rowH > pageH-bMargin { pdf.AddPage() y = pdf.GetY() pdf.SetXY(lMargin, y) } // 画出整行的两个单元格边框 // 使用黑色描边,左侧标签单元格填充标题蓝色背景 pdf.SetDrawColor(0, 0, 0) pdf.SetFillColor(91, 155, 213) pdf.Rect(x, y, labelW, rowH, "FD") // 标签:填充+描边 pdf.Rect(x+labelW, y, valueW, rowH, "D") // 值:仅描边 // 写入标签单元格 pdf.SetXY(x, y) pdf.SetFont(fontName, "B", 13) pdf.SetTextColor(255, 255, 255) pdf.CellFormat(labelW, rowH, fmt.Sprintf("%s:", label), "", 0, "L", false, 0, "") // 写入值单元格(自动换行) pdf.SetXY(x+labelW, y) pdf.SetFont(fontName, "", 13) pdf.SetTextColor(0, 0, 0) pdf.MultiCell(valueW, lineH, value, "", "L", false) // 将光标移动到下一行开头,恢复默认颜色 pdf.SetDrawColor(0, 0, 0) pdf.SetFillColor(255, 255, 255) pdf.SetXY(lMargin, y+rowH) } func safePlaceholder(s string) string { if s == "" { return "-" } return s } // ------------------------- // 各板块渲染(与 qiye.html 对齐) // ------------------------- // 风险综合分析(只做风险点一览,与 qiye.html 中 renderRiskOverview 对齐的精简版) func renderPDFRiskOverview(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) itemsRaw, ok := v["items"].([]interface{}) if !ok || len(itemsRaw) == 0 { pdf.MultiCell(0, 6, "暂无风险分析数据", "", "L", false) return } for _, it := range itemsRaw { m, ok := it.(map[string]interface{}) if !ok { continue } name := getString(m, "name") if name == "" { continue } hit := false if hv, ok := m["hit"]; ok { if b, ok := hv.(bool); ok { hit = b } else { hit = fmt.Sprint(hv) == "true" } } status := "未命中" if hit { status = "命中" } pdf.MultiCell(0, 6, fmt.Sprintf("· %s:%s", name, status), "", "L", false) } } // 主体概览,对照 qiye.html renderBasic 中的字段顺序,做精简单列展示 func renderPDFBasic(pdf *gofpdf.Fpdf, fontName, title string, basic map[string]interface{}, entName, creditCode string) { if len(basic) == 0 && entName == "" && creditCode == "" { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 13) // 基本信息采用条目式行展示,隔行背景色,不使用表格框 rowIndex := 0 writeBasicRow := func(label, val string) { if val == "" { return } alt := rowIndex%2 == 0 pdfWriteBasicRow(pdf, fontName, label, val, alt) rowIndex++ } writeBasicRow("企业名称", entName) writeBasicRow("统一社会信用代码", safePlaceholder(creditCode)) keys := []string{ "regNo", "orgCode", "entType", "entTypeCode", "entityTypeCode", "establishDate", "registeredCapital", "regCap", "regCapCurrency", "regCapCurrencyCode", "regOrg", "regOrgCode", "regProvince", "provinceCode", "regCity", "regCityCode", "regDistrict", "districtCode", "address", "postalCode", "legalRepresentative", "compositionForm", "approvedBusinessItem", "status", "statusCode", "operationPeriodFrom", "operationPeriodTo", "approveDate", "cancelDate", "revokeDate", "cancelReason", "revokeReason", "oldNames", "businessScope", "lastAnnuReportYear", } for _, k := range keys { val := getString(basic, k) if k == "oldNames" && val == "" { continue } if val == "" { continue } writeBasicRow(mapBasicLabel(k), val) } } func mapBasicLabel(k string) string { switch k { case "regNo": return "注册号" case "orgCode": return "组织机构代码" case "entType": return "企业类型" case "entTypeCode": return "企业类型编码" case "entityTypeCode": return "实体类型编码" case "establishDate": return "成立日期" case "registeredCapital", "regCap": return "注册资本" case "regCapCurrency": return "注册资本币种" case "regCapCurrencyCode": return "注册资本币种代码" case "regOrg": return "登记机关" case "regOrgCode": return "注册地址行政编号" case "regProvince": return "所在省份" case "provinceCode": return "所在省份编码" case "regCity": return "所在城市" case "regCityCode": return "所在城市编码" case "regDistrict": return "所在区/县" case "districtCode": return "所在区/县编码" case "address": return "住址" case "postalCode": return "邮编" case "legalRepresentative": return "法定代表人" case "compositionForm": return "组成形式" case "approvedBusinessItem": return "许可经营项目" case "status": return "经营状态" case "statusCode": return "经营状态编码" case "operationPeriodFrom": return "经营期限自" case "operationPeriodTo": return "经营期限至" case "approveDate": return "核准日期" case "cancelDate": return "注销日期" case "revokeDate": return "吊销日期" case "cancelReason": return "注销原因" case "revokeReason": return "吊销原因" case "oldNames": return "曾用名" case "businessScope": return "经营业务范围" case "lastAnnuReportYear": return "最后年检年度" default: return k } } func renderPDFBranches(pdf *gofpdf.Fpdf, fontName, title string, branches []interface{}) { if len(branches) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) for i, b := range branches { m, ok := b.(map[string]interface{}) if !ok { continue } name := getString(m, "name") if name == "" { name = getString(m, "entName") } if name == "" { continue } regNo := getString(m, "regNo") credit := getString(m, "creditCode") regOrg := getString(m, "regOrg") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, name), "", "L", false) meta := fmt.Sprintf(" 注册号:%s;信用代码:%s;登记机关:%s", safePlaceholder(regNo), safePlaceholder(credit), safePlaceholder(regOrg), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } func renderPDFShareholding(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { if len(v) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) // 汇总(对齐前端 renderShareholding 的 summary 部分) shareholderCount := getString(v, "shareholderCount") registeredCapital := getString(v, "registeredCapital") currency := getString(v, "currency") topHolderName := getString(v, "topHolderName") topHolderPercent := getString(v, "topHolderPercent") top5TotalPercent := getString(v, "top5TotalPercent") hasEquityPledges := boolToCN(getString(v, "hasEquityPledges")) if shareholderCount != "" || registeredCapital != "" || currency != "" || topHolderName != "" || topHolderPercent != "" || top5TotalPercent != "" || hasEquityPledges != "" { pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "股权汇总", "", "L", false) pdf.SetFont(fontName, "", 11) if shareholderCount != "" { writeKeyValue(pdf, fontName, "股东总数", shareholderCount) } if registeredCapital != "" { writeKeyValue(pdf, fontName, "注册资本", registeredCapital+" 元") } if currency != "" { writeKeyValue(pdf, fontName, "币种", currency) } if topHolderName != "" { writeKeyValue(pdf, fontName, "第一大股东", topHolderName) } if topHolderPercent != "" { writeKeyValue(pdf, fontName, "第一大股东持股", topHolderPercent) } if top5TotalPercent != "" { writeKeyValue(pdf, fontName, "前五大合计", top5TotalPercent) } if hasEquityPledges != "" { writeKeyValue(pdf, fontName, "存在股权出质", hasEquityPledges) } } // 股东明细(股东全量字段的精简版) if shRaw, ok := v["shareholders"].([]interface{}); ok && len(shRaw) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "股东明细", "", "L", false) pdf.SetFont(fontName, "", 11) for i, sh := range shRaw { m, ok := sh.(map[string]interface{}) if !ok { continue } name := getString(m, "name") if name == "" { continue } pct := getString(m, "ownershipPercent") if pct == "" { pct = getString(m, "sharePercent") } titleLine := fmt.Sprintf("%d. %s", i+1, name) if pct != "" { titleLine = fmt.Sprintf("%s(持股:%s)", titleLine, pct) } pdf.MultiCell(0, 6, titleLine, "", "L", false) // 认缴 / 实缴等关键信息 subscribedAmount := getString(m, "subscribedAmount") subscribedCurrency := getString(m, "subscribedCurrency") subscribedCurrencyCode := getString(m, "subscribedCurrencyCode") subscribedDate := getString(m, "subscribedDate") subscribedMethod := getString(m, "subscribedMethod") subscribedMethodCode := getString(m, "subscribedMethodCode") paidAmount := getString(m, "paidAmount") paidDate := getString(m, "paidDate") paidMethod := getString(m, "paidMethod") creditCode := getString(m, "creditCode") regNo := getString(m, "regNo") isHistory := boolToCN(getString(m, "isHistory")) metaParts := []string{} if subscribedAmount != "" { sub := "认缴:" + subscribedAmount if subscribedCurrency != "" || subscribedCurrencyCode != "" { sub += " " + subscribedCurrency if subscribedCurrencyCode != "" { sub += "(" + subscribedCurrencyCode + ")" } } metaParts = append(metaParts, sub) } if subscribedDate != "" { metaParts = append(metaParts, "认缴日:"+subscribedDate) } if subscribedMethod != "" || subscribedMethodCode != "" { subm := "认缴方式:" + subscribedMethod if subscribedMethodCode != "" { subm += "(" + subscribedMethodCode + ")" } metaParts = append(metaParts, subm) } if paidAmount != "" { metaParts = append(metaParts, "实缴:"+paidAmount) } if paidDate != "" { metaParts = append(metaParts, "实缴日:"+paidDate) } if paidMethod != "" { metaParts = append(metaParts, "实缴方式:"+paidMethod) } if creditCode != "" { metaParts = append(metaParts, "股东信用代码:"+creditCode) } if regNo != "" { metaParts = append(metaParts, "股东注册号:"+regNo) } if isHistory != "" { metaParts = append(metaParts, "是否历史:"+isHistory) } if len(metaParts) > 0 { pdf.MultiCell(0, 5, " "+joinWithChineseSemicolon(metaParts), "", "L", false) } pdf.Ln(1) } } // 股权变更记录 if changes, ok := v["equityChanges"].([]interface{}); ok && len(changes) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "股权变更记录", "", "L", false) pdf.SetFont(fontName, "", 11) for i, e := range changes { m, ok := e.(map[string]interface{}) if !ok { continue } changeDate := getString(m, "changeDate") shareholderName := getString(m, "shareholderName") titleLine := fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(changeDate), shareholderName) pdf.MultiCell(0, 6, titleLine, "", "L", false) before := getString(m, "percentBefore") after := getString(m, "percentAfter") if before != "" || after != "" { pdf.MultiCell(0, 5, fmt.Sprintf(" 变更前:%s → 变更后:%s", safePlaceholder(before), safePlaceholder(after)), "", "L", false) } pdf.Ln(1) } } // 股权出质 if pledges, ok := v["equityPledges"].([]interface{}); ok && len(pledges) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "股权出质", "", "L", false) pdf.SetFont(fontName, "", 11) for i, e := range pledges { m, ok := e.(map[string]interface{}) if !ok { continue } regNo := getString(m, "regNo") pledgor := getString(m, "pledgor") pledgee := getString(m, "pledgee") titleLine := fmt.Sprintf("%d. %s %s → %s", i+1, safePlaceholder(regNo), safePlaceholder(pledgor), safePlaceholder(pledgee)) pdf.MultiCell(0, 6, titleLine, "", "L", false) amount := getString(m, "pledgedAmount") regDate := getString(m, "regDate") status := getString(m, "status") meta := fmt.Sprintf(" 出质数额:%s 元;登记日:%s;状态:%s", safePlaceholder(amount), safePlaceholder(regDate), safePlaceholder(status), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } // 实缴出资明细 if paidDetails, ok := v["paidInDetails"].([]interface{}); ok && len(paidDetails) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "实缴出资明细", "", "L", false) pdf.SetFont(fontName, "", 11) for i, e := range paidDetails { m, ok := e.(map[string]interface{}) if !ok { continue } investor := getString(m, "investor") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(investor)), "", "L", false) paidDate := getString(m, "paidDate") paidMethod := getString(m, "paidMethod") accumulatedPaid := getString(m, "accumulatedPaid") meta := fmt.Sprintf(" 日期:%s;方式:%s;累计实缴:%s 万元", safePlaceholder(paidDate), safePlaceholder(paidMethod), safePlaceholder(accumulatedPaid), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } // 认缴出资明细 if subDetails, ok := v["subscribedCapitalDetails"].([]interface{}); ok && len(subDetails) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "认缴出资明细", "", "L", false) pdf.SetFont(fontName, "", 11) for i, e := range subDetails { m, ok := e.(map[string]interface{}) if !ok { continue } investor := getString(m, "investor") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(investor)), "", "L", false) subscribedDate := getString(m, "subscribedDate") subscribedMethod := getString(m, "subscribedMethod") accSubscribed := getString(m, "accumulatedSubscribed") meta := fmt.Sprintf(" 认缴日:%s;方式:%s;累计认缴:%s 元", safePlaceholder(subscribedDate), safePlaceholder(subscribedMethod), safePlaceholder(accSubscribed), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } } func renderPDFController(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { if len(v) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) writeKeyValue(pdf, fontName, "标识", getString(v, "id")) writeKeyValue(pdf, fontName, "姓名/名称", getString(v, "name")) writeKeyValue(pdf, fontName, "类型", getString(v, "type")) writeKeyValue(pdf, fontName, "持股比例", getString(v, "percent")) writeKeyValue(pdf, fontName, "判定依据", getString(v, "reason")) writeKeyValue(pdf, fontName, "来源", getString(v, "source")) } func renderPDFBeneficiaries(pdf *gofpdf.Fpdf, fontName, title string, arr []interface{}) { if len(arr) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) for i, b := range arr { m, ok := b.(map[string]interface{}) if !ok { continue } name := getString(m, "name") if name == "" { continue } pct := getString(m, "percent") line := fmt.Sprintf("%d. %s", i+1, name) if pct != "" { line = fmt.Sprintf("%s(持股:%s)", line, pct) } pdf.MultiCell(0, 6, line, "", "L", false) reason := getString(m, "reason") if reason != "" { pdf.MultiCell(0, 5, fmt.Sprintf(" 原因:%s", reason), "", "L", false) } pdf.Ln(1) } } func renderPDFInvestments(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { if len(v) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) // 汇总 totalCount := getString(v, "totalCount") totalAmount := getString(v, "totalAmount") if totalCount != "" || totalAmount != "" { pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "汇总", "", "L", false) pdf.SetFont(fontName, "", 11) if totalCount != "" { writeKeyValue(pdf, fontName, "对外投资数量", totalCount) } if totalAmount != "" { writeKeyValue(pdf, fontName, "投资总额(万)", totalAmount) } } // 对外投资列表 list := []interface{}{} if arr, ok := v["list"].([]interface{}); ok { list = arr } else if arr, ok := v["entities"].([]interface{}); ok { list = arr } if len(list) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "对外投资列表", "", "L", false) pdf.SetFont(fontName, "", 11) for i, inv := range list { m, ok := inv.(map[string]interface{}) if !ok { continue } name := getString(m, "entName") if name == "" { name = getString(m, "name") } if name == "" { continue } pct := getString(m, "investPercent") titleLine := fmt.Sprintf("%d. %s", i+1, name) if pct != "" { titleLine = fmt.Sprintf("%s(持股:%s)", titleLine, pct) } pdf.MultiCell(0, 6, titleLine, "", "L", false) } } } func renderPDFGuarantees(pdf *gofpdf.Fpdf, fontName, title string, arr []interface{}) { if len(arr) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) for i, it := range arr { m, ok := it.(map[string]interface{}) if !ok { continue } mortgagor := getString(m, "mortgagor") creditor := getString(m, "creditor") if mortgagor == "" && creditor == "" { continue } titleLine := fmt.Sprintf("%d. %s → %s", i+1, safePlaceholder(mortgagor), safePlaceholder(creditor)) pdf.MultiCell(0, 6, titleLine, "", "L", false) amount := getString(m, "principalAmount") kind := getString(m, "principalKind") guaranteeType := getString(m, "guaranteeType") periodFrom := getString(m, "periodFrom") periodTo := getString(m, "periodTo") meta := fmt.Sprintf(" 主债权数额:%s;种类:%s;保证方式:%s;期限:%s 至 %s", safePlaceholder(amount), safePlaceholder(kind), safePlaceholder(guaranteeType), safePlaceholder(periodFrom), safePlaceholder(periodTo), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } func renderPDFManagement(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { if len(v) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) // 高管列表 if execs, ok := v["executives"].([]interface{}); ok && len(execs) > 0 { pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "高管列表", "", "L", false) pdf.SetFont(fontName, "", 11) for i, e := range execs { m, ok := e.(map[string]interface{}) if !ok { continue } name := getString(m, "name") if name == "" { continue } position := getString(m, "position") line := fmt.Sprintf("%d. %s", i+1, name) if position != "" { line = fmt.Sprintf("%s(%s)", line, position) } pdf.MultiCell(0, 6, line, "", "L", false) } } // 从业与社保 employeeCount := getString(v, "employeeCount") femaleEmployeeCount := getString(v, "femaleEmployeeCount") if employeeCount != "" || femaleEmployeeCount != "" { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "从业与社保", "", "L", false) pdf.SetFont(fontName, "", 11) if employeeCount != "" { writeKeyValue(pdf, fontName, "从业人数", employeeCount) } if femaleEmployeeCount != "" { writeKeyValue(pdf, fontName, "女性从业人数", femaleEmployeeCount) } } // 法定代表人其他任职 var others []interface{} if arr, ok := v["legalRepresentativeOtherPositions"].([]interface{}); ok { others = arr } else if arr, ok := v["legalPersonOtherPositions"].([]interface{}); ok { others = arr } if len(others) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "法定代表人其他任职", "", "L", false) pdf.SetFont(fontName, "", 11) for i, f := range others { m, ok := f.(map[string]interface{}) if !ok { continue } entName := getString(m, "entName") if entName == "" { continue } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, entName), "", "L", false) parts := []string{} if pos := getString(m, "position"); pos != "" { parts = append(parts, "职务:"+pos) } if name := getString(m, "name"); name != "" { parts = append(parts, "姓名:"+name) } if st := getString(m, "entStatus"); st != "" { parts = append(parts, "企业状态:"+st) } if cc := getString(m, "creditCode"); cc != "" { parts = append(parts, "信用代码:"+cc) } if rn := getString(m, "regNo"); rn != "" { parts = append(parts, "注册号:"+rn) } if len(parts) > 0 { pdf.MultiCell(0, 5, " "+joinWithChineseSemicolon(parts), "", "L", false) } pdf.Ln(1) } } } func renderPDFAssets(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { yearsRaw, ok := v["years"].([]interface{}) if !ok || len(yearsRaw) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) for _, y := range yearsRaw { m, ok := y.(map[string]interface{}) if !ok { continue } year := getString(m, "year") reportDate := getString(m, "reportDate") titleLine := year if reportDate != "" { titleLine = fmt.Sprintf("%s(%s)", year, reportDate) } pdf.MultiCell(0, 6, titleLine, "", "L", false) meta := fmt.Sprintf(" 资产总额:%s;营收:%s;净利润:%s;负债:%s", safePlaceholder(getString(m, "assetTotal")), safePlaceholder(getString(m, "revenueTotal")), safePlaceholder(getString(m, "netProfit")), safePlaceholder(getString(m, "liabilityTotal")), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } func renderPDFLicenses(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { if len(v) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) // 行政许可列表 if permits, ok := v["permits"].([]interface{}); ok && len(permits) > 0 { pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "行政许可列表", "", "L", false) pdf.SetFont(fontName, "", 11) for i, p := range permits { m, ok := p.(map[string]interface{}) if !ok { continue } name := getString(m, "name") if name == "" { continue } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, name), "", "L", false) meta := fmt.Sprintf(" 有效期:%s ~ %s;许可机关:%s;%s", safePlaceholder(getString(m, "valFrom")), safePlaceholder(getString(m, "valTo")), safePlaceholder(getString(m, "licAnth")), safePlaceholder(getString(m, "licItem")), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } // 许可变更记录 if changes, ok := v["permitChanges"].([]interface{}); ok && len(changes) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "许可变更记录", "", "L", false) pdf.SetFont(fontName, "", 11) for i, p := range changes { m, ok := p.(map[string]interface{}) if !ok { continue } changeDate := getString(m, "changeDate") changeType := getString(m, "changeType") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(changeDate), safePlaceholder(changeType)), "", "L", false) before := getString(m, "detailBefore") after := getString(m, "detailAfter") if before != "" || after != "" { pdf.MultiCell(0, 5, " 变更前:"+before, "", "L", false) pdf.MultiCell(0, 5, " 变更后:"+after, "", "L", false) } pdf.Ln(1) } } // 知识产权出质(如有则原样列出) if ipPledges, ok := v["ipPledges"].([]interface{}); ok && len(ipPledges) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "知识产权出质", "", "L", false) pdf.SetFont(fontName, "", 11) for i, it := range ipPledges { pdf.MultiCell(0, 5, fmt.Sprintf("%d. %v", i+1, it), "", "L", false) } } } func renderPDFActivities(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { if len(v) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) // 招投标 if bids, ok := v["bids"].([]interface{}); ok && len(bids) > 0 { pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "招投标", "", "L", false) pdf.SetFont(fontName, "", 11) for i, b := range bids { m, ok := b.(map[string]interface{}) if !ok { continue } titleText := getString(m, "announcetitle") if titleText == "" { titleText = getString(m, "ANNOUNCETITLE") } if titleText == "" { titleText = getString(m, "title") } if titleText == "" { continue } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, titleText), "", "L", false) } } // 网站 / 网店 if websites, ok := v["websites"].([]interface{}); ok && len(websites) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "网站 / 网店", "", "L", false) pdf.SetFont(fontName, "", 11) for i, w := range websites { m, ok := w.(map[string]interface{}) if !ok { continue } name := getString(m, "websitname") if name == "" { name = getString(m, "WEBSITNAME") } if name == "" { name = getString(m, "name") } if name == "" { continue } webType := getString(m, "webtype") if webType == "" { webType = getString(m, "WEBTYPE") } domain := getString(m, "domain") if domain == "" { domain = getString(m, "DOMAIN") } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, name), "", "L", false) meta := fmt.Sprintf(" 类型:%s;域名:%s", safePlaceholder(webType), safePlaceholder(domain)) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } } func renderPDFInspections(pdf *gofpdf.Fpdf, fontName, title string, arr []interface{}) { if len(arr) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) for i, it := range arr { m, ok := it.(map[string]interface{}) if !ok { continue } date := getString(m, "inspectDate") result := getString(m, "result") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(date), safePlaceholder(result)), "", "L", false) } } func renderPDFRisks(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { if len(v) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) // has* 风险项一览(有 / 无) type item struct { Key string Label string } items := []item{ {"hasCourtJudgments", "裁判文书"}, {"hasJudicialAssists", "司法协助"}, {"hasDishonestDebtors", "失信被执行人"}, {"hasLimitHighDebtors", "限高被执行人"}, {"hasAdminPenalty", "行政处罚"}, {"hasException", "经营异常"}, {"hasSeriousIllegal", "严重违法"}, {"hasTaxOwing", "欠税"}, {"hasSeriousTaxIllegal", "重大税收违法"}, {"hasMortgage", "动产抵押"}, {"hasEquityPledges", "股权出质"}, {"hasQuickCancel", "简易注销"}, } for _, it := range items { val := getString(v, it.Key) status := "未发现" if val == "true" || val == "1" { status = "存在" } pdf.MultiCell(0, 6, fmt.Sprintf("· %s:%s", it.Label, status), "", "L", false) } // 裁判文书 if arr, ok := v["courtJudgments"].([]interface{}); !ok || len(arr) == 0 { // 兼容 judicialCases 字段 if alt, ok2 := v["judicialCases"].([]interface{}); ok2 { arr = alt ok = len(arr) > 0 } } if arr, ok := v["courtJudgments"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "裁判文书", "", "L", false) pdf.SetFont(fontName, "", 11) for i, c := range arr { m, ok := c.(map[string]interface{}) if !ok { continue } caseNo := getString(m, "caseNumber") if caseNo == "" { caseNo = getString(m, "CASENUMBER") } if caseNo == "" { caseNo = getString(m, "judicialDocumentId") } pdf.MultiCell(0, 6, fmt.Sprintf("%d. 案号:%s", i+1, safePlaceholder(caseNo)), "", "L", false) } } // 司法协助 if arr, ok := v["judicialAssists"].([]interface{}); !ok || len(arr) == 0 { if alt, ok2 := v["judicialAids"].([]interface{}); ok2 { arr = alt ok = len(arr) > 0 } if ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "司法协助", "", "L", false) pdf.SetFont(fontName, "", 11) for i, a := range arr { m, ok := a.(map[string]interface{}) if !ok { continue } name := getString(m, "iname") if name == "" { name = getString(m, "INAME") } if name == "" { name = getString(m, "marketName") } court := getString(m, "courtName") if court == "" { court = getString(m, "COURTNAME") } share := getString(m, "shaream") if share == "" { share = getString(m, "SHAREAM") } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(name)), "", "L", false) meta := fmt.Sprintf(" 法院:%s;股权数额:%s 元", safePlaceholder(court), safePlaceholder(share)) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } } // 失信被执行人 if arr, ok := v["dishonestDebtors"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "失信被执行人", "", "L", false) pdf.SetFont(fontName, "", 11) for i, d := range arr { m, ok := d.(map[string]interface{}) if !ok { continue } caseNo := getString(m, "caseNo") execCourt := getString(m, "execCourt") performance := getString(m, "performanceStatus") publishDate := getString(m, "publishDate") pdf.MultiCell(0, 6, fmt.Sprintf("%d. 案号 %s", i+1, safePlaceholder(caseNo)), "", "L", false) meta := fmt.Sprintf(" 执行法院:%s;履行情况:%s;发布时间:%s", safePlaceholder(execCourt), safePlaceholder(performance), safePlaceholder(publishDate), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } // 限高被执行人 if arr, ok := v["limitHighDebtors"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "限高被执行人", "", "L", false) pdf.SetFont(fontName, "", 11) for i, x := range arr { m, ok := x.(map[string]interface{}) if !ok { continue } ah := getString(m, "ah") if ah == "" { ah = getString(m, "caseNo") } zxfy := getString(m, "zxfy") if zxfy == "" { zxfy = getString(m, "execCourt") } fbrq := getString(m, "fbrq") if fbrq == "" { fbrq = getString(m, "publishDate") } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(ah)), "", "L", false) meta := fmt.Sprintf(" 执行法院:%s;发布日期:%s", safePlaceholder(zxfy), safePlaceholder(fbrq)) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } // 行政处罚 if arr, ok := v["adminPenalties"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "行政处罚", "", "L", false) pdf.SetFont(fontName, "", 11) for i, p := range arr { m, ok := p.(map[string]interface{}) if !ok { continue } pendecno := getString(m, "pendecno") if pendecno == "" { pendecno = getString(m, "PENDECNO") } illeg := getString(m, "illegacttype") if illeg == "" { illeg = getString(m, "ILLEGACTTYPE") } auth := getString(m, "penauth") if auth == "" { auth = getString(m, "PENAUTH") } date := getString(m, "pendecissdate") if date == "" { date = getString(m, "PENDECISSDATE") } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(pendecno)), "", "L", false) meta := fmt.Sprintf(" 违法类型:%s;机关:%s;日期:%s", safePlaceholder(illeg), safePlaceholder(auth), safePlaceholder(date)) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } // 行政处罚变更 if arr, ok := v["adminPenaltyUpdates"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "行政处罚变更记录", "", "L", false) pdf.SetFont(fontName, "", 11) for i, p := range arr { m, ok := p.(map[string]interface{}) if !ok { continue } date := getString(m, "updateDate") content := getString(m, "updateContent") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(date)), "", "L", false) if content != "" { pdf.MultiCell(0, 5, " "+content, "", "L", false) } pdf.Ln(1) } } // 经营异常 if arr, ok := v["exceptions"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "经营异常", "", "L", false) pdf.SetFont(fontName, "", 11) for i, e := range arr { m, ok := e.(map[string]interface{}) if !ok { continue } indate := getString(m, "indate") if indate == "" { indate = getString(m, "INDATE") } inreason := getString(m, "inreason") if inreason == "" { inreason = getString(m, "INREASON") } outdate := getString(m, "outdate") if outdate == "" { outdate = getString(m, "OUTDATE") } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(indate)), "", "L", false) meta := fmt.Sprintf(" 原因:%s;移出:%s", safePlaceholder(inreason), safePlaceholder(outdate)) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } // 动产抵押 if arr, ok := v["mortgages"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "动产抵押", "", "L", false) pdf.SetFont(fontName, "", 11) for i, m := range arr { mm, ok := m.(map[string]interface{}) if !ok { continue } regNo := getString(mm, "regNo") amount := getString(mm, "guaranteedAmount") regDate := getString(mm, "regDate") regOrg := getString(mm, "regOrg") status := getString(mm, "status") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(regNo), safePlaceholder(amount)), "", "L", false) meta := fmt.Sprintf(" 登记日:%s;机关:%s;状态:%s", safePlaceholder(regDate), safePlaceholder(regOrg), safePlaceholder(status)) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } // 简易注销 if qc, ok := v["quickCancel"].(map[string]interface{}); ok && len(qc) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "简易注销", "", "L", false) pdf.SetFont(fontName, "", 11) writeKeyValue(pdf, fontName, "企业名称", getString(qc, "entName")) writeKeyValue(pdf, fontName, "统一社会信用代码", getString(qc, "creditCode")) writeKeyValue(pdf, fontName, "注册号", getString(qc, "regNo")) writeKeyValue(pdf, fontName, "登记机关", getString(qc, "regOrg")) writeKeyValue(pdf, fontName, "公告起始日期", getString(qc, "noticeFromDate")) writeKeyValue(pdf, fontName, "公告结束日期", getString(qc, "noticeToDate")) writeKeyValue(pdf, fontName, "注销结果", getString(qc, "cancelResult")) } // 清算信息 if liq, ok := v["liquidation"].(map[string]interface{}); ok && len(liq) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "清算信息", "", "L", false) pdf.SetFont(fontName, "", 11) writeKeyValue(pdf, fontName, "负责人", getString(liq, "principal")) writeKeyValue(pdf, fontName, "成员", getString(liq, "members")) } // 纳税与欠税(taxRecords) if tax, ok := v["taxRecords"].(map[string]interface{}); ok && len(tax) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "纳税与欠税", "", "L", false) pdf.SetFont(fontName, "", 11) ayears := getSlice(tax, "taxLevelAYears") owings := getSlice(tax, "taxOwings") hint := fmt.Sprintf("纳税A级年度:%s;欠税:%s", formatCountHint(len(ayears)), formatCountHint(len(owings)), ) pdf.MultiCell(0, 5, hint, "", "L", false) if len(owings) > 0 { pdf.Ln(1) for i, t := range owings { m, ok := t.(map[string]interface{}) if !ok { continue } taxType := getString(m, "taxOwedType") if taxType == "" { taxType = getString(m, "taxowedtype") } total := getString(m, "totalOwedAmount") if total == "" { total = getString(m, "totalowedamount") } pub := getString(m, "publishDate") if pub == "" { pub = getString(m, "publishdate") } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(taxType)), "", "L", false) meta := fmt.Sprintf(" 合计:%s;公示日:%s", safePlaceholder(total), safePlaceholder(pub)) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } } else { // 完全无 taxRecords 时,与 HTML 行为一致给一个“无”的提示 pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "纳税与欠税", "", "L", false) pdf.SetFont(fontName, "", 11) pdf.MultiCell(0, 5, "无", "", "L", false) } // 涉诉案件(litigation)—— 按案件类型分类 if lit, ok := v["litigation"].(map[string]interface{}); ok && len(lit) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "涉诉案件汇总", "", "L", false) pdf.SetFont(fontName, "", 11) typeLabels := map[string]string{ "administrative": "行政案件", "implement": "执行案件", "preservation": "非诉保全审查", "civil": "民事案件", "criminal": "刑事案件", "bankrupt": "强制清算与破产案件", "jurisdict": "管辖案件", "compensate": "赔偿案件", } // 汇总表 for key, label := range typeLabels { if sec, ok := lit[key].(map[string]interface{}); ok { count := getString(sec, "count") if count != "" && count != "0" { pdf.MultiCell(0, 5, fmt.Sprintf("· %s:%s 件", label, count), "", "L", false) } } } // 各类案件明细 for key, label := range typeLabels { sec, ok := lit[key].(map[string]interface{}) if !ok { continue } cases, ok := sec["cases"].([]interface{}) if !ok || len(cases) == 0 { continue } pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, label, "", "L", false) pdf.SetFont(fontName, "", 11) for i, c := range cases { m, ok := c.(map[string]interface{}) if !ok { continue } caseNo := getString(m, "caseNo") court := getString(m, "court") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s(%s)", i+1, safePlaceholder(caseNo), safePlaceholder(court)), "", "L", false) region := getString(m, "region") trialLevel := getString(m, "trialLevel") caseType := getString(m, "caseType") cause := getString(m, "cause") amount := getString(m, "amount") filing := getString(m, "filingDate") judgment := getString(m, "judgmentDate") victory := getString(m, "victoryResult") meta := fmt.Sprintf(" 地区:%s;审级:%s;案件类型:%s;案由:%s;标的金额:%s;立案日期:%s;裁判日期:%s;胜败结果:%s", safePlaceholder(region), safePlaceholder(trialLevel), safePlaceholder(caseType), safePlaceholder(cause), safePlaceholder(amount), safePlaceholder(filing), safePlaceholder(judgment), safePlaceholder(victory), ) pdf.MultiCell(0, 5, meta, "", "L", false) pdf.Ln(1) } } } } func renderPDFTimeline(pdf *gofpdf.Fpdf, fontName, title string, arr []interface{}) { if len(arr) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) for i, it := range arr { m, ok := it.(map[string]interface{}) if !ok { continue } date := getString(m, "date") tp := getString(m, "type") desc := getString(m, "title") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(date), safePlaceholder(tp)), "", "L", false) if desc != "" { pdf.MultiCell(0, 5, " "+desc, "", "L", false) } pdf.Ln(1) } } func renderPDFListed(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) { if len(v) == 0 { return } pdf.AddPage() pdfSectionTitle(pdf, fontName, title) pdf.Ln(1) pdf.SetFont(fontName, "", 11) isListed := boolToCN(getString(v, "isListed")) if isListed != "" { writeKeyValue(pdf, fontName, "是否上市", isListed) } // 上市公司信息 if company, ok := v["company"].(map[string]interface{}); ok && len(company) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "上市公司信息", "", "L", false) pdf.SetFont(fontName, "", 11) writeKeyValue(pdf, fontName, "经营范围", getString(company, "bizScope")) writeKeyValue(pdf, fontName, "信用代码", getString(company, "creditCode")) writeKeyValue(pdf, fontName, "注册地址", getString(company, "regAddr")) writeKeyValue(pdf, fontName, "注册资本", getString(company, "regCapital")) writeKeyValue(pdf, fontName, "组织机构代码", getString(company, "orgCode")) writeKeyValue(pdf, fontName, "币种", getString(company, "cur")) writeKeyValue(pdf, fontName, "币种名称", getString(company, "curName")) } // 股票信息 if stock, ok := v["stock"]; ok && stock != nil { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "股票信息", "", "L", false) pdf.SetFont(fontName, "", 11) switch s := stock.(type) { case string: if s != "" { pdf.MultiCell(0, 5, s, "", "L", false) } case map[string]interface{}: // 简单将几个常见字段以表格形式展示 code := getString(s, "code") name := getString(s, "name") market := getString(s, "market") if code != "" { writeKeyValue(pdf, fontName, "代码", code) } if name != "" { writeKeyValue(pdf, fontName, "名称", name) } if market != "" { writeKeyValue(pdf, fontName, "市场", market) } default: // 其他类型非空时,用字符串方式输出 txt := fmt.Sprint(stock) if txt != "" && txt != "" { pdf.MultiCell(0, 5, txt, "", "L", false) } } } // 十大股东 if arr, ok := v["topShareholders"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "十大股东", "", "L", false) pdf.SetFont(fontName, "", 11) for i, s := range arr { m, ok := s.(map[string]interface{}) if !ok { continue } name := getString(m, "name") if name == "" { name = getString(m, "shaname") } pct := getString(m, "percent") if pct == "" { pct = getString(m, "fundedratio") } pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(name)), "", "L", false) if pct != "" { pdf.MultiCell(0, 5, " 持股比例:"+pct, "", "L", false) } pdf.Ln(1) } } // 上市高管 if arr, ok := v["listedManagers"].([]interface{}); ok && len(arr) > 0 { pdf.Ln(2) pdf.SetFont(fontName, "B", 12) pdf.MultiCell(0, 6, "上市高管", "", "L", false) pdf.SetFont(fontName, "", 11) for i, mng := range arr { m, ok := mng.(map[string]interface{}) if !ok { continue } name := getString(m, "name") if name == "" { name = getString(m, "perName") } position := getString(m, "position") pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(name)), "", "L", false) if position != "" { pdf.MultiCell(0, 5, " 职务:"+position, "", "L", false) } pdf.Ln(1) } } } // joinWithChineseSemicolon 使用中文分号连接字符串 func joinWithChineseSemicolon(parts []string) string { if len(parts) == 0 { return "" } res := parts[0] for i := 1; i < len(parts); i++ { res += ";" + parts[i] } return res } // pdfSectionTitle 渲染模块主标题(对应 HTML h2) func pdfSectionTitle(pdf *gofpdf.Fpdf, fontName, title string) { pdf.SetFont(fontName, "B", 17) // 模块标题前加蓝色点缀方块 pdf.SetTextColor(37, 99, 235) pdf.CellFormat(0, 10, "■ "+title, "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) } // pdfSubTitle 渲染模块内小节标题(对应 HTML h3) func pdfSubTitle(pdf *gofpdf.Fpdf, fontName, title string) { pdf.SetFont(fontName, "B", 14) // 小节标题用圆点前缀,略微缩进 pdf.SetTextColor(37, 99, 235) pdf.CellFormat(0, 8, "● "+title, "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) } // pdfWriteBasicRow 渲染基础信息的一行,使用隔行背景色而不加表格框 func pdfWriteBasicRow(pdf *gofpdf.Fpdf, fontName, label, value string, alt bool) { if value == "" { return } pageW, pageH := pdf.GetPageSize() lMargin, _, rMargin, bMargin := pdf.GetMargins() x := lMargin y := pdf.GetY() w := pageW - lMargin - rMargin labelW := 40.0 valueW := w - labelW // 行高整体拉宽一点 lineH := 10.0 // 预先根据内容拆行,计算本行整体高度(用于背景块) lines := pdf.SplitLines([]byte(value), valueW) rowH := float64(len(lines)) * lineH if rowH < lineH { rowH = lineH } rowH += 2 // 如当前页剩余空间不足,先分页再画整行,避免内容与下一页重叠 if y+rowH > pageH-bMargin { pdf.AddPage() y = pdf.GetY() pdf.SetXY(lMargin, y) } // 隔行底色 if alt { pdf.SetFillColor(242, 242, 242) } else { pdf.SetFillColor(255, 255, 255) } pdf.Rect(x, y, w, rowH, "F") // 标签列 pdf.SetXY(x, y) pdf.SetFont(fontName, "B", 14) pdf.CellFormat(labelW, rowH, fmt.Sprintf("%s:", label), "", 0, "L", false, 0, "") // 值列自动换行 pdf.SetXY(x+labelW, y) pdf.SetFont(fontName, "", 14) pdf.MultiCell(valueW, lineH, value, "", "L", false) // 移到下一行起始位置 pdf.SetXY(lMargin, y+rowH) } // formatCountHint 将条数格式化为“X 条”或“无” func formatCountHint(n int) string { if n <= 0 { return "无" } return fmt.Sprintf("%d 条", n) } // boolToCN 将 true/false/1/0/yes/no 映射成 “是/否” func boolToCN(s string) string { if s == "" { return s } low := strings.ToLower(strings.TrimSpace(s)) switch low { case "true", "1", "yes", "y": return "是" case "false", "0", "no", "n": return "否" default: return s } }