11
This commit is contained in:
parent
c4771e2255
commit
749b4bb8e9
BIN
data/满意度调成报告模板.pdf
Normal file
BIN
data/满意度调成报告模板.pdf
Normal file
Binary file not shown.
35
pkg/service/bundle/model/questionnaireSurvey.go
Normal file
35
pkg/service/bundle/model/questionnaireSurvey.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type QuestionnairePDFData struct {
|
||||||
|
// 基本信息
|
||||||
|
CustomerNum string `json:"customerNum"`
|
||||||
|
CustomerName string `json:"customerName"`
|
||||||
|
BundleName string `json:"bundleName"`
|
||||||
|
BundleStartDate string `json:"bundleStartDate"`
|
||||||
|
BundleEndDate string `json:"bundleEndDate"`
|
||||||
|
VideoNum string `json:"videoNum"`
|
||||||
|
AccountNum string `json:"accountNum"`
|
||||||
|
ImagesNum string `json:"imagesNum"`
|
||||||
|
DataAnalysisNum string `json:"dataAnalysisNum"`
|
||||||
|
CompetitiveNum string `json:"competitiveNum"`
|
||||||
|
ValueAddVideoNum string `json:"valueAddVideoNum"`
|
||||||
|
|
||||||
|
// 评分(1-5)
|
||||||
|
Score1 int `json:"score1"`
|
||||||
|
Score2 int `json:"score2"`
|
||||||
|
Score3 int `json:"score3"`
|
||||||
|
Score4 int `json:"score4"`
|
||||||
|
Score5 int `json:"score5"`
|
||||||
|
Score6 int `json:"score6"`
|
||||||
|
Score7 int `json:"score7"`
|
||||||
|
|
||||||
|
// 意见
|
||||||
|
Opinion1 string `json:"opinion1"`
|
||||||
|
Opinion2 string `json:"opinion2"`
|
||||||
|
Opinion3 string `json:"opinion3"`
|
||||||
|
|
||||||
|
// 提交信息
|
||||||
|
Submitter string `json:"submitter"`
|
||||||
|
SubmissionDate string `json:"submissionDate"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
@ -1,8 +1,15 @@
|
|||||||
package bundle
|
package bundle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"fonchain-fiee/api/bundle"
|
"fonchain-fiee/api/bundle"
|
||||||
"fonchain-fiee/pkg/service"
|
"fonchain-fiee/pkg/service"
|
||||||
|
"fonchain-fiee/pkg/service/bundle/model"
|
||||||
|
"fonchain-fiee/pkg/service/upload"
|
||||||
|
"fonchain-fiee/pkg/utils"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -40,5 +47,54 @@ func QuestionnaireSurveyCreate(c *gin.Context) {
|
|||||||
service.Error(c, err)
|
service.Error(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
address, err := utils.ReverseGeo("31.367784", "120.647276", "ZhCN")
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, errors.New("获取地址失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
surveyInfo, err := service.BundleProvider.GetQuestionnaireSurveyInfo(c, &bundle.GetQuestionnaireSurveyInfoRequest{UserTel: req.UserTel})
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
templateDir := "./data/满意度调成报告模板.pdf"
|
||||||
|
outputPath := "./data/" + req.UserTel + time.Now().Format("20060102150405") + ".pdf"
|
||||||
|
err = utils.QuestionnaireSurveyPDF(templateDir, outputPath, &model.QuestionnairePDFData{
|
||||||
|
//CustomerNum: surveyInfo.BundleInfo.,
|
||||||
|
CustomerName: surveyInfo.UserName,
|
||||||
|
BundleName: surveyInfo.BundleInfo.BundleName,
|
||||||
|
BundleStartDate: surveyInfo.BundleInfo.StartAt,
|
||||||
|
BundleEndDate: surveyInfo.BundleInfo.ExpiredAt,
|
||||||
|
VideoNum: string(surveyInfo.BundleInfo.BundleVideoNumber),
|
||||||
|
AccountNum: string(surveyInfo.BundleInfo.BundleAccountNumber),
|
||||||
|
ImagesNum: string(surveyInfo.BundleInfo.BundleImageNumber),
|
||||||
|
DataAnalysisNum: string(surveyInfo.BundleInfo.BundleDataNumber),
|
||||||
|
CompetitiveNum: string(surveyInfo.BundleInfo.BundleCompetitiveNumber),
|
||||||
|
ValueAddVideoNum: string(surveyInfo.BundleInfo.IncreaseVideoNumber),
|
||||||
|
Score1: int(req.SurveyAnswer.BundleAccountScore),
|
||||||
|
Score2: int(req.SurveyAnswer.BundleAccountScore),
|
||||||
|
Score3: int(req.SurveyAnswer.BundleImageScore),
|
||||||
|
Score4: int(req.SurveyAnswer.BundleDataScore),
|
||||||
|
Score5: int(req.SurveyAnswer.IncreaseVideoScore),
|
||||||
|
Score6: int(req.SurveyAnswer.ServiceResponseSpeed),
|
||||||
|
Score7: int(req.SurveyAnswer.ServiceStaffProfessionalism),
|
||||||
|
Opinion1: req.SurveyFeedback.MeritsReview,
|
||||||
|
Opinion2: req.SurveyFeedback.SuggestionsorImprovements,
|
||||||
|
Opinion3: req.SurveyFeedback.AdditionalComments,
|
||||||
|
Submitter: surveyInfo.UserName,
|
||||||
|
SubmissionDate: time.Now().Format(time.DateOnly),
|
||||||
|
Address: address,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Remove(outputPath)
|
||||||
|
outputUrl, ossErr := upload.PutBos(outputPath, upload.PdfType, true)
|
||||||
|
if ossErr != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(outputUrl)
|
||||||
service.Success(c, req)
|
service.Success(c, req)
|
||||||
}
|
}
|
||||||
|
|||||||
88
pkg/utils/map.go
Normal file
88
pkg/utils/map.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReverseGeocodingReq struct {
|
||||||
|
Ak string `json:"ak"`
|
||||||
|
Coordtype string `json:"coordtype"`
|
||||||
|
RetCoordtype string `json:"retCoordtype"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
Output string `json:"output"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReverseGeocodingRes struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Result struct {
|
||||||
|
Location struct {
|
||||||
|
Lng float64 `json:"lng"`
|
||||||
|
Lat float64 `json:"lat"`
|
||||||
|
} `json:"location"`
|
||||||
|
FormattedAddress string `json:"formatted_address"`
|
||||||
|
Edz struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"edz"`
|
||||||
|
Business string `json:"business"`
|
||||||
|
AddressComponent struct {
|
||||||
|
Country string `json:"country"`
|
||||||
|
CountryCodeIso string `json:"country_code_iso"`
|
||||||
|
CountryCodeIso2 string `json:"country_code_iso2"`
|
||||||
|
CountryCode int `json:"country_code"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
City string `json:"city"`
|
||||||
|
CityLevel int `json:"city_level"`
|
||||||
|
District string `json:"district"`
|
||||||
|
Town string `json:"town"`
|
||||||
|
TownCode string `json:"town_code"`
|
||||||
|
Distance string `json:"distance"`
|
||||||
|
Direction string `json:"direction"`
|
||||||
|
Adcode string `json:"adcode"`
|
||||||
|
Street string `json:"street"`
|
||||||
|
StreetNumber string `json:"street_number"`
|
||||||
|
} `json:"addressComponent"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReverseGeo 经纬度逆编码
|
||||||
|
func ReverseGeo(longitude, latitude string, language string) (address string, err error) {
|
||||||
|
var reverseGeocodingReq ReverseGeocodingReq
|
||||||
|
|
||||||
|
reverseGeocodingReq.Ak = "3bAjKGA0pv7qvszGe98RsVZ04Ob5r4ZZ"
|
||||||
|
reverseGeocodingReq.Coordtype = "gcj02ll"
|
||||||
|
reverseGeocodingReq.Output = "json"
|
||||||
|
reverseGeocodingReq.RetCoordtype = "gcj02ll"
|
||||||
|
reverseGeocodingReq.Location = strings.Join([]string{latitude, longitude}, ",")
|
||||||
|
reverseGeocodingReq.Language = language
|
||||||
|
|
||||||
|
url := "https://api.map.baidu.com/reverse_geocoding/v3/?ak=" + reverseGeocodingReq.Ak + "&output=" + reverseGeocodingReq.Output + "&coordtype=" + reverseGeocodingReq.Coordtype + "&location=" + reverseGeocodingReq.Location + "&ret_coordtype=" + reverseGeocodingReq.RetCoordtype + "&language=" + reverseGeocodingReq.Language
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var results ReverseGeocodingRes
|
||||||
|
err = json.Unmarshal(body, &results)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if results.Status != 0 {
|
||||||
|
address = "未知地址"
|
||||||
|
return address, err
|
||||||
|
}
|
||||||
|
|
||||||
|
address = results.Result.FormattedAddress
|
||||||
|
|
||||||
|
return address, nil
|
||||||
|
}
|
||||||
273
pkg/utils/pdf.go
273
pkg/utils/pdf.go
@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"fonchain-fiee/pkg/service/bundle/model"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -10,7 +11,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/phpdave11/gofpdf"
|
"github.com/phpdave11/gofpdf"
|
||||||
"github.com/signintech/gopdf"
|
"github.com/signintech/gopdf"
|
||||||
@ -595,3 +598,273 @@ func ConvertCompetitorReportToText(data CompetitorReportData, isVideo bool) stri
|
|||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//生成问卷调查pdf
|
||||||
|
|
||||||
|
func QuestionnaireSurveyPDF(templatePath, outputPath string, data *model.QuestionnairePDFData) error {
|
||||||
|
pdf := gopdf.GoPdf{}
|
||||||
|
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
|
||||||
|
|
||||||
|
if err := pdf.ImportPagesFromSource(templatePath, "/MediaBox"); err != nil {
|
||||||
|
return fmt.Errorf("导入模板失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pdf.AddTTFFont("simfang", "./data/simfang.ttf"); err != nil {
|
||||||
|
return fmt.Errorf("加载字体失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pdf.SetFont("simfang", "", 12); err != nil {
|
||||||
|
return fmt.Errorf("设置字体失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime, err := time.Parse("2006-01-02", data.BundleStartDate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("BundleStartDate格式错误: %w", err)
|
||||||
|
}
|
||||||
|
endTime, err := time.Parse("2006-01-02", data.BundleEndDate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("BundleEndDate格式错误: %w", err)
|
||||||
|
}
|
||||||
|
submissionDate, err := time.Parse("2006-01-02", data.SubmissionDate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("submissionDate格式错误: %w", err)
|
||||||
|
}
|
||||||
|
nowTime := time.Now().Format(time.DateTime)
|
||||||
|
onePage := 1
|
||||||
|
twoPage := 2
|
||||||
|
threePage := 3
|
||||||
|
fourPage := 4
|
||||||
|
|
||||||
|
// 第1页:客户基本信息
|
||||||
|
pdf.SetPage(onePage)
|
||||||
|
//姓名
|
||||||
|
pdf.SetX(165)
|
||||||
|
pdf.SetY(420)
|
||||||
|
pdf.Cell(nil, data.CustomerName)
|
||||||
|
//套餐名称
|
||||||
|
pdf.SetX(205)
|
||||||
|
pdf.SetY(443)
|
||||||
|
pdf.Cell(nil, data.BundleName)
|
||||||
|
//开始日期
|
||||||
|
pdf.SetX(205)
|
||||||
|
pdf.SetY(467)
|
||||||
|
pdf.Cell(nil, startTime.Format("2006"))
|
||||||
|
pdf.SetX(260)
|
||||||
|
pdf.SetY(467)
|
||||||
|
pdf.Cell(nil, startTime.Format("01"))
|
||||||
|
pdf.SetX(300)
|
||||||
|
pdf.SetY(467)
|
||||||
|
pdf.Cell(nil, startTime.Format("02"))
|
||||||
|
|
||||||
|
//结束日期
|
||||||
|
pdf.SetX(350)
|
||||||
|
pdf.SetY(467)
|
||||||
|
pdf.Cell(nil, endTime.Format("2006"))
|
||||||
|
pdf.SetX(398)
|
||||||
|
pdf.SetY(467)
|
||||||
|
pdf.Cell(nil, endTime.Format("01"))
|
||||||
|
pdf.SetX(437)
|
||||||
|
pdf.SetY(467)
|
||||||
|
pdf.Cell(nil, endTime.Format("02"))
|
||||||
|
//视频数
|
||||||
|
pdf.SetX(220)
|
||||||
|
pdf.SetY(583)
|
||||||
|
pdf.Cell(nil, data.VideoNum)
|
||||||
|
//"账号数: "+
|
||||||
|
pdf.SetX(230)
|
||||||
|
pdf.SetY(625)
|
||||||
|
pdf.Cell(nil, data.AccountNum)
|
||||||
|
// "图文数: "+
|
||||||
|
pdf.SetX(253)
|
||||||
|
pdf.SetY(667)
|
||||||
|
pdf.Cell(nil, data.ImagesNum)
|
||||||
|
//"数据分析数: "+
|
||||||
|
pdf.SetX(280)
|
||||||
|
pdf.SetY(727)
|
||||||
|
pdf.Cell(nil, data.DataAnalysisNum)
|
||||||
|
// 第1页内容写完后
|
||||||
|
if err = addWatermark(&pdf, "确认地址:"+data.Address+"\n确认时间:"+nowTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 第2页:服务数量
|
||||||
|
pdf.SetPage(twoPage)
|
||||||
|
|
||||||
|
//"竞品分析数: "+
|
||||||
|
pdf.SetX(205)
|
||||||
|
pdf.SetY(72)
|
||||||
|
pdf.Cell(nil, data.CompetitiveNum)
|
||||||
|
//"增值视频数: "+
|
||||||
|
pdf.SetX(270)
|
||||||
|
pdf.SetY(156)
|
||||||
|
pdf.Cell(nil, data.ValueAddVideoNum)
|
||||||
|
//"评分1: "+
|
||||||
|
pdf.SetX(123)
|
||||||
|
pdf.SetY(485)
|
||||||
|
pdf.Cell(nil, scoreStars(data.Score1))
|
||||||
|
//"评分2: "+
|
||||||
|
pdf.SetX(343)
|
||||||
|
pdf.SetY(526)
|
||||||
|
pdf.Cell(nil, scoreStars(data.Score2))
|
||||||
|
//"评分3: "+
|
||||||
|
pdf.SetX(230)
|
||||||
|
pdf.SetY(568)
|
||||||
|
pdf.Cell(nil, scoreStars(data.Score3))
|
||||||
|
//"评分4: "+
|
||||||
|
pdf.SetX(362)
|
||||||
|
pdf.SetY(610)
|
||||||
|
pdf.Cell(nil, scoreStars(data.Score4))
|
||||||
|
//"评分5: "+
|
||||||
|
pdf.SetX(220)
|
||||||
|
pdf.SetY(652)
|
||||||
|
pdf.Cell(nil, scoreStars(data.Score5))
|
||||||
|
//"评分6: "+
|
||||||
|
pdf.SetX(164)
|
||||||
|
pdf.SetY(694)
|
||||||
|
pdf.Cell(nil, scoreStars(data.Score6))
|
||||||
|
//"评分7: "+
|
||||||
|
pdf.SetX(197)
|
||||||
|
pdf.SetY(735)
|
||||||
|
pdf.Cell(nil, scoreStars(data.Score7))
|
||||||
|
//水印
|
||||||
|
if err = addWatermark(&pdf, "确认地址:"+data.Address+"\n确认时间:"+nowTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 第3页:评分与意见
|
||||||
|
pdf.SetPage(threePage)
|
||||||
|
// Opinion 超过100字符时自动换行,每行约20个中文字符,行高18pt
|
||||||
|
drawWrappedText(&pdf, data.Opinion1, 90, 145, 24, 34)
|
||||||
|
drawWrappedText(&pdf, data.Opinion2, 90, 377, 24, 34)
|
||||||
|
drawWrappedText(&pdf, data.Opinion3, 90, 574, 24, 34)
|
||||||
|
//水印
|
||||||
|
if err = addWatermark(&pdf, "确认地址:"+data.Address+"\n确认时间:"+nowTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第4页:提交信息
|
||||||
|
pdf.SetPage(fourPage)
|
||||||
|
//"提交人: "+
|
||||||
|
pdf.SetX(135)
|
||||||
|
pdf.SetY(103)
|
||||||
|
pdf.Cell(nil, data.Submitter)
|
||||||
|
//提交时间: "
|
||||||
|
pdf.SetX(148)
|
||||||
|
pdf.SetY(128)
|
||||||
|
pdf.Cell(nil, submissionDate.Format("2006"))
|
||||||
|
pdf.SetX(207)
|
||||||
|
pdf.SetY(128)
|
||||||
|
pdf.Cell(nil, submissionDate.Format("01"))
|
||||||
|
pdf.SetX(260)
|
||||||
|
pdf.SetY(128)
|
||||||
|
pdf.Cell(nil, submissionDate.Format("02"))
|
||||||
|
//水印
|
||||||
|
if err = addWatermark(&pdf, "确认地址:"+data.Address+"\n确认时间:"+nowTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := pdf.WritePdf(outputPath); err != nil {
|
||||||
|
return fmt.Errorf("写入PDF失败: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addWatermark 在当前页叠加浅色斜向双行水印,并在结束后恢复样式
|
||||||
|
func addWatermark(pdf *gopdf.GoPdf, text string) error {
|
||||||
|
const normalFontSize = 12
|
||||||
|
const watermarkFontSize = 22
|
||||||
|
const lineHeight = 32.0 // 水印行间距
|
||||||
|
|
||||||
|
// 设置水印样式
|
||||||
|
pdf.SetGrayFill(0.85)
|
||||||
|
if err := pdf.SetFont("simfang", "", watermarkFontSize); err != nil {
|
||||||
|
return fmt.Errorf("设置水印字体失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按换行拆分,逐行绘制(Cell 不负责自动换行)
|
||||||
|
lines := strings.Split(text, "\n")
|
||||||
|
if len(lines) == 0 {
|
||||||
|
lines = []string{text}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawBlock := func(x, y, cx, cy float64) {
|
||||||
|
pdf.Rotate(35, cx, cy)
|
||||||
|
for i, line := range lines {
|
||||||
|
pdf.SetX(x)
|
||||||
|
pdf.SetY(y + float64(i)*lineHeight)
|
||||||
|
pdf.Cell(nil, line)
|
||||||
|
}
|
||||||
|
pdf.RotateReset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 两处重复水印
|
||||||
|
drawBlock(90, 420, 300, 420)
|
||||||
|
drawBlock(130, 620, 300, 420)
|
||||||
|
|
||||||
|
// 恢复样式,避免影响后续正文
|
||||||
|
pdf.SetGrayFill(0)
|
||||||
|
if err := pdf.SetFont("simfang", "", normalFontSize); err != nil {
|
||||||
|
return fmt.Errorf("恢复字体失败: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawWrappedText 在 PDF 上绘制自动换行的文字
|
||||||
|
// pdf: GoPdf 实例, text: 文字内容, startX/startY: 起始坐标
|
||||||
|
// maxWidth: 最大宽度(pt), lineHeight: 行高(pt), charsPerLine: 每行最多字符数(按中文字符计)
|
||||||
|
func drawWrappedText(pdf *gopdf.GoPdf, text string, startX, startY, lineHeight float64, charsPerLine int) {
|
||||||
|
runes := []rune(text)
|
||||||
|
total := len(runes)
|
||||||
|
if total == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lineStart := 0
|
||||||
|
currentLine := 0
|
||||||
|
|
||||||
|
for lineStart < total {
|
||||||
|
end := lineStart + charsPerLine
|
||||||
|
if end > total {
|
||||||
|
end = total
|
||||||
|
}
|
||||||
|
// 计算实际宽度以决定换行点(按字节估算:ASCII=0.5中文字符宽)
|
||||||
|
charCount := 0
|
||||||
|
splitAt := lineStart
|
||||||
|
for i := lineStart; i < total; i++ {
|
||||||
|
r := runes[i]
|
||||||
|
runeBytes := utf8.RuneLen(r)
|
||||||
|
if runeBytes > 1 {
|
||||||
|
charCount += 2 // 中文等宽字符算2个单位
|
||||||
|
} else {
|
||||||
|
charCount += 1 // ASCII 算1个单位
|
||||||
|
}
|
||||||
|
if charCount > charsPerLine*2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
splitAt = i + 1
|
||||||
|
}
|
||||||
|
if splitAt == lineStart {
|
||||||
|
splitAt = lineStart + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
line := string(runes[lineStart:splitAt])
|
||||||
|
pdf.SetX(startX)
|
||||||
|
pdf.SetY(startY + float64(currentLine)*lineHeight)
|
||||||
|
pdf.Cell(nil, line)
|
||||||
|
|
||||||
|
lineStart = splitAt
|
||||||
|
currentLine++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scoreStars(score int) string {
|
||||||
|
switch {
|
||||||
|
case score <= 1:
|
||||||
|
return "★☆☆☆☆"
|
||||||
|
case score == 2:
|
||||||
|
return "★★☆☆☆"
|
||||||
|
case score == 3:
|
||||||
|
return "★★★☆☆"
|
||||||
|
case score == 4:
|
||||||
|
return "★★★★☆"
|
||||||
|
default:
|
||||||
|
return "★★★★★"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user