word.go 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  1. package services
  2. import (
  3. "baliance.com/gooxml/color"
  4. "baliance.com/gooxml/document"
  5. "baliance.com/gooxml/measurement"
  6. "baliance.com/gooxml/schema/soo/ofc/sharedTypes"
  7. "baliance.com/gooxml/schema/soo/wml"
  8. "bytes"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. wkhtml "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  13. "github.com/shopspring/decimal"
  14. contractReq "hongze/hongze_mobile_admin/models/request/contract"
  15. "hongze/hongze_mobile_admin/models/tables/contract"
  16. "hongze/hongze_mobile_admin/models/tables/contract_service_detail"
  17. "hongze/hongze_mobile_admin/models/tables/contract_service_template"
  18. "hongze/hongze_mobile_admin/models/tables/contract_template"
  19. "hongze/hongze_mobile_admin/utils"
  20. "html/template"
  21. "reflect"
  22. "strconv"
  23. "strings"
  24. )
  25. type TableData struct {
  26. List []TableRow `json:"table" description:"列数据"`
  27. }
  28. type TableRow struct {
  29. RowList []TableCel `json:"row" description:"列数据"`
  30. }
  31. type TableCel struct {
  32. Value string `json:"value" description:"展示的数据"`
  33. ColumnSpan int `json:"column_span" description:"需要合同的列数量"`
  34. RowSpan int `json:"row_span" description:"需要合同的行数量"`
  35. IsMerged bool `json:"is_merged" description:"是否需要上下行合并"`
  36. IsFirstMerged bool `json:"is_first_merged" description:"是否是第一次合并上下行"`
  37. Background string `json:"background" description:"背景色"`
  38. IsBold bool `json:"is_bold" description:"是否加粗显示"`
  39. TextAlign string `json:"text_align" description:"对齐方式"`
  40. FontSize float64 `json:"font_size" description:"字体大小"`
  41. WidthPercent float64 `json:"width_percent" description:"单元格宽度占整个表格的百分比"`
  42. }
  43. //获取颜色配置
  44. func getColorConf(background string) (foreground color.Color) {
  45. switch background {
  46. case "slate_gray": //石板灰
  47. foreground = color.SlateGray
  48. case "light_slate_gray": //浅石板灰
  49. foreground = color.LightSlateGray
  50. case "light_gray": //浅灰
  51. foreground = color.LightGray
  52. case "gray": //灰色
  53. foreground = color.Gray
  54. case "gray_1": //灰色_1(中浅灰)
  55. foreground = color.RGB(uint8(215), uint8(215), uint8(215))
  56. case "gray_2": //灰色_2(浅灰)
  57. foreground = color.RGB(uint8(241), uint8(241), uint8(241))
  58. case "dim_gray": //暗灰色
  59. foreground = color.DimGray
  60. case "dark_slate_gray": //深灰色
  61. foreground = color.DarkSlateGray
  62. default:
  63. foreground = color.LightGray
  64. }
  65. return
  66. }
  67. func getTextAlignConf(textAlign string) (align wml.ST_Jc) {
  68. switch textAlign {
  69. case "left": //居左
  70. align = wml.ST_JcLeft
  71. case "center": //居中
  72. align = wml.ST_JcCenter
  73. case "right": //居右
  74. align = wml.ST_JcRight
  75. case "both": //
  76. align = wml.ST_JcBoth
  77. default:
  78. align = wml.ST_JcLeft
  79. }
  80. return
  81. }
  82. //生成word
  83. func GenerateWord(contractDetail *contract.ContractDetail) (err error) {
  84. wordTemplatePath := getWordPath(contractDetail.TemplateId)
  85. if wordTemplatePath == "" {
  86. err = errors.New("找不到对应的合同模板")
  87. return
  88. }
  89. doc, err := document.Open(wordTemplatePath)
  90. if err != nil {
  91. fmt.Println("error opening document: %s", err)
  92. return
  93. }
  94. paragraphs := []document.Paragraph{}
  95. for _, p := range doc.Paragraphs() {
  96. paragraphs = append(paragraphs, p)
  97. }
  98. // This sample document uses structured document tags, which are not common
  99. // except for in document templates. Normally you can just iterate over the
  100. // document's paragraphs.
  101. for _, sdt := range doc.StructuredDocumentTags() {
  102. for _, p := range sdt.Paragraphs() {
  103. paragraphs = append(paragraphs, p)
  104. }
  105. }
  106. doc.AddParagraph()
  107. for _, p := range paragraphs {
  108. for _, r := range p.Runs() {
  109. switch r.Text() {
  110. case "{{address}}":
  111. // ClearContent clears both text and line breaks within a run,
  112. // so we need to add the line break back
  113. r.ClearContent()
  114. address := contractDetail.Province + contractDetail.City + contractDetail.Address
  115. r.AddText(address)
  116. //r.AddBreak()
  117. //para := doc.InsertParagraphBefore(p)
  118. //para.AddRun().AddText("Mr.")
  119. //para.SetStyle("Name") // Name is a default style in this template file
  120. //
  121. //para = doc.InsertParagraphAfter(p)
  122. //para.AddRun().AddText("III")
  123. //para.SetStyle("Name")
  124. case "{{postcode}}":
  125. r.ClearContent()
  126. r.AddText(contractDetail.Postcode)
  127. case "{{phone}}":
  128. r.ClearContent()
  129. r.AddText(contractDetail.Phone)
  130. case "{{fax}}":
  131. r.ClearContent()
  132. r.AddText(contractDetail.Fax)
  133. case "{{remark}}":
  134. r.ClearContent()
  135. remark := contractDetail.Remark
  136. if remark == "" {
  137. remark = "无"
  138. }
  139. r.AddText(remark)
  140. case "{{start_date}}":
  141. r.ClearContent()
  142. r.AddText(contractDetail.StartDate.Format("2006 年 01 月 02 日"))
  143. case "{{end_date}}":
  144. r.ClearContent()
  145. r.AddText(contractDetail.EndDate.Format("2006 年 01 月 02 日"))
  146. case "{{num_year}}":
  147. r.ClearContent()
  148. //合同结束日期与合同开始日期的时间差(小时差)
  149. newDecimal := decimal.NewFromFloat(contractDetail.EndDate.Sub(contractDetail.StartDate).Hours())
  150. //分母为365天 * 24 小时
  151. newDecimal2 := decimal.NewFromInt(24 * 365)
  152. //计算出来相差多少年,保留一位小数(四舍五入)
  153. numYearDecimal := newDecimal.Div(newDecimal2).Round(1)
  154. //定义最小年份差,不能小于0.1年
  155. minDecimal := decimal.NewFromFloat(0.1)
  156. //如果计算出来的年份差小于0.1年,那么该年份差就赋值 0.1年
  157. if numYearDecimal.LessThan(minDecimal) {
  158. numYearDecimal = minDecimal
  159. }
  160. //cnYear, cnErr := utils.ConvertNumToCn(numYearDecimal.String())
  161. //if cnErr != nil {
  162. // err = cnErr
  163. // return
  164. //}
  165. r.AddText(numYearDecimal.String())
  166. case "{{price}}":
  167. r.ClearContent()
  168. priceStr := ""
  169. //originalPrice := strconv.FormatFloat(contractDetail.OriginalPrice, 'E', -1, 64)
  170. //优惠前金额(小写)
  171. newDecimal := decimal.NewFromFloat(contractDetail.OriginalPrice)
  172. originalPrice := newDecimal.String()
  173. priceStr += "小写:" + originalPrice + ","
  174. //优惠前金额(大写)
  175. originalCnyPrice, cnyErr := utils.ConvertNumToCny(contractDetail.OriginalPrice)
  176. if cnyErr != nil {
  177. err = cnyErr
  178. return
  179. }
  180. priceStr += "大写:" + originalCnyPrice
  181. //如果实际支付金额与订单原金额不符
  182. if contractDetail.OriginalPrice != contractDetail.Price {
  183. //优惠后的金额(小写)
  184. newDecimal := decimal.NewFromFloat(contractDetail.Price)
  185. price := newDecimal.String()
  186. priceStr += ",经甲乙双方友好协商,优惠至:" + price + "元,"
  187. //优惠后的金额(大写)
  188. cnyPrice, cnyErr := utils.ConvertNumToCny(contractDetail.Price)
  189. if cnyErr != nil {
  190. err = cnyErr
  191. return
  192. }
  193. priceStr += "大写:" + cnyPrice
  194. }
  195. r.AddText(priceStr)
  196. case "{{pay_remark}}":
  197. r.ClearContent()
  198. r.AddText(contractDetail.PayRemark)
  199. case "{{company_name}}":
  200. r.ClearContent()
  201. r.AddText(contractDetail.CompanyName)
  202. //r.AddBreak()
  203. case "{{services}}":
  204. r.ClearContent()
  205. //赋值当前段落
  206. nowParagraph := p
  207. for i := len(contractDetail.Service) - 1; i >= 0; i-- {
  208. //表格数据
  209. var tableDataList TableData
  210. //表头备注信息
  211. tableTitle := ""
  212. item := contractDetail.Service[i]
  213. //表格数据
  214. if item.HasDetail == "是" && len(item.DetailList) > 0 {
  215. //表格每行数据切片
  216. tableRowList := make([]TableRow, 0)
  217. //遍历获取table行数据
  218. for j := 0; j < len(item.DetailList); j++ {
  219. //列数据样式初始化
  220. isBold := false
  221. backgrandColor := ""
  222. fontSize := 10.0
  223. //表头数据样式
  224. if j == 0 {
  225. isBold = true
  226. backgrandColor = "gray_2"
  227. fontSize = 12.0
  228. }
  229. //获取每一列的数据
  230. tmpCellList, colErr := getColList(item.DetailList[j])
  231. if colErr != nil {
  232. err = colErr
  233. return
  234. }
  235. //定义生成table列数据切片
  236. tableCelList := make([]TableCel, 0)
  237. lenCell := len(tmpCellList)
  238. for k := 0; k < len(tmpCellList); k++ {
  239. //计算出来每一列的宽度占比 start
  240. //总宽度
  241. newDecimal := decimal.NewFromFloat(100)
  242. //总列数
  243. newDecimal2 := decimal.NewFromInt(int64(lenCell))
  244. //计算出来每一列的宽度占比(四舍五入)
  245. widthPercent, _ := newDecimal.Div(newDecimal2).Round(3).Float64()
  246. //if !ok {
  247. // err = errors.New("word普通数据表格宽度百分比计算失败")
  248. // return
  249. //}
  250. //计算出来每一列的宽度占比 end
  251. tableCel := TableCel{
  252. Value: tmpCellList[k],
  253. TextAlign: "center",
  254. //ColumnSpan int `json:"column_span" description:"需要合同的列数量"`
  255. //IsMerged bool `json:"is_merged" description:"是否需要上下行合并"`
  256. Background: backgrandColor,
  257. IsBold: isBold,
  258. FontSize: fontSize,
  259. WidthPercent: widthPercent,
  260. }
  261. tableCelList = append(tableCelList, tableCel)
  262. }
  263. //将每行数据插入到table行数据切片之中
  264. tableRow := TableRow{
  265. RowList: tableCelList,
  266. }
  267. tableRowList = append(tableRowList, tableRow)
  268. }
  269. //赋值table表格数据
  270. tableDataList.List = tableRowList
  271. tableTitle = "依照《弘则研究FICC客户服务列表2021》中 小套餐 的服务内容,详细如下:"
  272. } else {
  273. //获取预设的表格数据
  274. contractServiceTemplate, tmpErr := contract_service_template.GetContractServiceTemplateById(item.ServiceTemplateId)
  275. if tmpErr != nil {
  276. err = tmpErr
  277. return
  278. }
  279. //赋值table表格数据
  280. jsonStr := contractServiceTemplate.TableValue
  281. err = json.Unmarshal([]byte(jsonStr), &tableDataList)
  282. if err != nil {
  283. return
  284. }
  285. //表头备注信息
  286. tableTitle = contractServiceTemplate.Remark
  287. }
  288. //往word中添加表格数据
  289. tmpParagraph, tmpErr := addTable(tableTitle, tableDataList, doc, nowParagraph)
  290. if tmpErr != nil {
  291. err = tmpErr
  292. return
  293. }
  294. //fmt.Println("nowParagraph:", nowParagraph, "tmpParagraph:", tmpParagraph)
  295. //fmt.Println("doc:", doc.Paragraphs())
  296. //fmt.Println("==========:")
  297. nowParagraph = tmpParagraph
  298. }
  299. default:
  300. //fmt.Println("not modifying", r.Text())
  301. }
  302. }
  303. }
  304. doc.SaveToFile(fmt.Sprint("./static/word/系统生成合同", contractDetail.ContractId, ".docx"))
  305. return
  306. }
  307. //添加表格数据
  308. func addTable(title string, tableDataList TableData, doc *document.Document, paragraph document.Paragraph) (nowParagraph document.Paragraph, err error) {
  309. //fmt.Println("表头名称:", title)
  310. //插入一个新的段落
  311. nowParagraph = doc.InsertParagraphBefore(paragraph)
  312. nowRun := nowParagraph.AddRun()
  313. nowRun.AddBreak()
  314. //if title != "" {
  315. // fmt.Println("表头名称:", title)
  316. // nowRun.Properties().SetSize(11)
  317. // nowRun.Properties().SetBold(true)
  318. // nowRun.AddText(title)
  319. // nowRun.AddBreak()
  320. //}
  321. //再次插入一个新段落
  322. //_ = doc.InsertParagraphAfter(nowParagraph)
  323. //表格数据
  324. {
  325. table := doc.InsertTableAfter(nowParagraph)
  326. //设置表格宽度
  327. table.Properties().SetWidth(6.5 * measurement.Inch)
  328. //表格宽度设置为自动
  329. //table.Properties().SetWidthAuto()
  330. //边框
  331. borders := table.Properties().Borders()
  332. // thin borders
  333. borders.SetAll(wml.ST_BorderSingle, color.Auto, measurement.Zero)
  334. //表格数据
  335. rowList := tableDataList.List
  336. //每一列合并单元格状态map
  337. rowIsMeged := make(map[int]bool)
  338. //table.Properties().W
  339. for i := 0; i < len(rowList); i++ {
  340. //创建新的一行
  341. row := table.AddRow()
  342. //设置行高,第二个参数是设置固定值还是自动
  343. row.Properties().SetHeight(30*measurement.Point, wml.ST_HeightRuleAtLeast)
  344. //遍历列数据
  345. rowDataList := rowList[i].RowList
  346. if rowDataList != nil {
  347. for j := 0; j < len(rowDataList); j++ {
  348. //当前列是否合并
  349. var isMeged bool
  350. isMeged, ok := rowIsMeged[j]
  351. if !ok {
  352. rowIsMeged[j] = false
  353. isMeged = false
  354. }
  355. cell := row.AddCell()
  356. cellPara := cell.AddParagraph()
  357. run := cellPara.AddRun()
  358. //列数据
  359. cellData := rowDataList[j]
  360. //如果合并列大于0,那么就合并列
  361. if cellData.ColumnSpan > 0 {
  362. // column span / merged cells
  363. cell.Properties().SetColumnSpan(cellData.ColumnSpan)
  364. //_ = row.AddCell()
  365. }
  366. //如果指定了上下单元格合并,那么去合并上下单元格
  367. if cellData.IsMerged {
  368. //将当前合并单元格状态调整为true
  369. rowIsMeged[j] = true
  370. //合并单元格类型
  371. var mergeVal wml.ST_Merge
  372. if isMeged { //如果上一层已经是合并了,那么这一层是继续合并
  373. mergeVal = wml.ST_MergeContinue
  374. } else { //如果上一层不是合并,那么这一层是开始合并
  375. mergeVal = wml.ST_MergeRestart
  376. }
  377. cell.Properties().SetVerticalMerge(mergeVal)
  378. } else {
  379. //将当前合并单元格状态调整为false,这样后续如果再次碰到合并单元格操作,就是重新开始合并了
  380. rowIsMeged[j] = false
  381. }
  382. //背景色
  383. if cellData.Background != "" {
  384. cell.Properties().SetShading(wml.ST_ShdSolid, getColorConf(cellData.Background), color.Auto)
  385. }
  386. //填充内容(文字)垂直对齐方式
  387. cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
  388. //将单元格设置为宽度百分比
  389. if cellData.WidthPercent > 0 {
  390. cell.Properties().SetWidthPercent(cellData.WidthPercent)
  391. }
  392. //文字排版(居中、左、右)
  393. if cellData.TextAlign != "" {
  394. cellPara.Properties().SetAlignment(getTextAlignConf(cellData.TextAlign))
  395. //cellPara.Properties().SetAlignment(wml.ST_JcLeft)
  396. }
  397. //cell.Properties().SetAli
  398. //设置是否加粗
  399. run.Properties().SetBold(cellData.IsBold)
  400. //设置字体大小
  401. fontSize := 10.0
  402. if cellData.FontSize > 0 {
  403. fontSize = cellData.FontSize
  404. }
  405. run.Properties().SetSize(measurement.Distance(fontSize * measurement.Point))
  406. //设置段落间的间距
  407. cellPara.Properties().Spacing().SetLineSpacing(measurement.Distance(1.4*fontSize*measurement.Point), wml.ST_LineSpacingRuleAuto)
  408. //设置段前间距
  409. cellPara.Properties().Spacing().SetBefore(measurement.Distance(0.9 * fontSize * measurement.Point))
  410. //设置段后间距
  411. cellPara.Properties().Spacing().SetAfter(measurement.Distance(0.5 * fontSize * measurement.Point))
  412. //设置字体
  413. run.Properties().SetFontFamily("宋体")
  414. //设置排序
  415. run.Properties().SetVerticalAlignment(sharedTypes.ST_VerticalAlignRunBaseline)
  416. //设置显示的文字
  417. if cellData.Value != "" {
  418. strSlice := strings.Split(cellData.Value, "<br/>")
  419. for s := 0; s < len(strSlice); s++ {
  420. if s > 0 {
  421. run.AddBreak()
  422. }
  423. run.AddText(strSlice[s])
  424. }
  425. } else {
  426. run.AddText("")
  427. }
  428. }
  429. }
  430. }
  431. }
  432. return
  433. }
  434. //获取生成word的docx模板文件
  435. func getWordPath(templateId int) string {
  436. var path string
  437. switch templateId {
  438. case 1:
  439. path = "./static/word/template_1.docx"
  440. case 2:
  441. path = "./static/word/template_2.docx"
  442. }
  443. return path
  444. }
  445. //html转pdf数据样式
  446. type html2pdfData struct {
  447. CompanyName string `description:"甲方名称"`
  448. ContractCode string `description:"合同编号"`
  449. Address string `description:"甲方地址"`
  450. Postcode string `description:"甲方邮编"`
  451. Phone string `description:"甲方电话"`
  452. Fax string `description:"传真"`
  453. Remark string `description:"备注"`
  454. PayRemark string `description:"支付备注"`
  455. StartDate string `description:"合同开始日期"`
  456. EndDate string `description:"合同结束日期"`
  457. NumYear string `description:"合同有效期"`
  458. Price string `description:"支付金额"`
  459. TableHtml string `description:"表格数据"`
  460. }
  461. //获取合同样式预览的html
  462. func GetHtmlByContractDetail(contractDetail *contract.ContractDetail, htmlType string) (contractHtml string, err error) {
  463. contractTemplate, err := contract_template.GetContractTemplateByTemplateId(contractDetail.TemplateId)
  464. if err != nil {
  465. return
  466. }
  467. htmlTpl := contractTemplate.Html
  468. if htmlType == "pdf" {
  469. htmlTpl = contractTemplate.PdfHtml
  470. }
  471. myTpl := template.Must(template.New("contract").Parse(htmlTpl))
  472. //地址
  473. address := contractDetail.Province + contractDetail.City + contractDetail.Address
  474. data := html2pdfData{
  475. CompanyName: contractDetail.CompanyName,
  476. ContractCode: contractDetail.ContractCode,
  477. Address: address,
  478. Postcode: contractDetail.Postcode,
  479. Phone: contractDetail.Phone,
  480. Fax: contractDetail.Fax,
  481. Remark: contractDetail.Remark,
  482. PayRemark: contractDetail.PayRemark,
  483. StartDate: contractDetail.StartDate.Format("2006年01月02日"),
  484. EndDate: contractDetail.EndDate.Format("2006年01月02日"),
  485. }
  486. if data.Postcode == "" {
  487. data.Postcode = "无"
  488. }
  489. if data.Fax == "" {
  490. data.Fax = "无"
  491. }
  492. if data.Phone == "" {
  493. data.Phone = "无"
  494. }
  495. if data.PayRemark == "" {
  496. data.PayRemark = "无"
  497. }
  498. if data.Remark == "" {
  499. data.Remark = "无"
  500. }
  501. //合同有效期
  502. {
  503. //合同结束日期与合同开始日期的时间差(小时差)
  504. newDecimal := decimal.NewFromFloat(contractDetail.EndDate.Sub(contractDetail.StartDate).Hours())
  505. //分母为365天 * 24 小时
  506. newDecimal2 := decimal.NewFromInt(24 * 365)
  507. //计算出来相差多少年,保留一位小数(四舍五入)
  508. numYearDecimal := newDecimal.Div(newDecimal2).Round(1)
  509. //定义最小年份差,不能小于0.1年
  510. minDecimal := decimal.NewFromFloat(0.1)
  511. //如果计算出来的年份差小于0.1年,那么该年份差就赋值 0.1年
  512. if numYearDecimal.LessThan(minDecimal) {
  513. numYearDecimal = minDecimal
  514. }
  515. //合同有效期
  516. data.NumYear = numYearDecimal.String()
  517. }
  518. //合同金额
  519. {
  520. priceStr := ""
  521. //originalPrice := strconv.FormatFloat(contractDetail.OriginalPrice, 'E', -1, 64)
  522. //优惠前金额(小写)
  523. newDecimal := decimal.NewFromFloat(contractDetail.OriginalPrice)
  524. originalPrice := newDecimal.String()
  525. priceStr += "小写:" + originalPrice + ","
  526. //优惠前金额(大写)
  527. originalCnyPrice, cnyErr := utils.ConvertNumToCny(contractDetail.OriginalPrice)
  528. if cnyErr != nil {
  529. err = cnyErr
  530. return
  531. }
  532. priceStr += "大写:" + originalCnyPrice
  533. //如果实际支付金额与订单原金额不符
  534. if contractDetail.OriginalPrice != contractDetail.Price {
  535. //优惠后的金额(小写)
  536. newDecimal := decimal.NewFromFloat(contractDetail.Price)
  537. price := newDecimal.String()
  538. priceStr += ",经甲乙双方友好协商,优惠至:" + price + "元,"
  539. //优惠后的金额(大写)
  540. cnyPrice, cnyErr := utils.ConvertNumToCny(contractDetail.Price)
  541. if cnyErr != nil {
  542. err = cnyErr
  543. return
  544. }
  545. priceStr += "大写:" + cnyPrice
  546. }
  547. data.Price = priceStr
  548. }
  549. buf := new(bytes.Buffer) //实现了读写方法的可变大小的字节缓冲
  550. tplErr := myTpl.Execute(buf, data)
  551. if tplErr != nil {
  552. err = tplErr
  553. return
  554. }
  555. contractHtml = buf.String()
  556. //服务内容
  557. {
  558. tableStr := ""
  559. tableDataSlice := make([]TableData, 0)
  560. tableTitleSlice := make([]string, 0)
  561. title := ""
  562. if contractDetail.ProductId == 1 {
  563. title = "依照《【弘则研究】FICC客户客户服务列表2021》中 "
  564. } else {
  565. title = "依照《【弘则研究】私募客户客户服务列表2021》中 "
  566. }
  567. for i := 0; i < len(contractDetail.Service); i++ {
  568. //表格数据
  569. var tableDataList TableData
  570. item := contractDetail.Service[i]
  571. //表头备注信息
  572. tableTitleSlice = append(tableTitleSlice, item.Title)
  573. //表格数据
  574. if item.HasDetail == "是" && len(item.DetailList) > 0 {
  575. //表格每行数据切片
  576. tableRowList := make([]TableRow, 0)
  577. //遍历获取table行数据
  578. for j := 0; j < len(item.DetailList); j++ {
  579. //列数据样式初始化
  580. isBold := false
  581. backgrandColor := ""
  582. fontSize := 13.0
  583. //表头数据样式
  584. if j == 0 {
  585. isBold = true
  586. backgrandColor = "gray_2"
  587. fontSize = 13.0
  588. }
  589. //获取每一列的数据
  590. tmpCellList, colErr := getColList(item.DetailList[j])
  591. if colErr != nil {
  592. err = colErr
  593. return
  594. }
  595. //定义生成table列数据切片
  596. tableCelList := make([]TableCel, 0)
  597. lenCell := len(tmpCellList)
  598. for k := 0; k < len(tmpCellList); k++ {
  599. //默认30%的宽度,如果不是第一列,那么需要额外计算
  600. widthPercent := 30.0
  601. if k > 0 {
  602. //计算出来每一列的宽度占比 start
  603. //总宽度
  604. newDecimal := decimal.NewFromFloat(70)
  605. //总列数
  606. newDecimal2 := decimal.NewFromInt(int64(lenCell) - 1)
  607. //计算出来每一列的宽度占比(四舍五入)
  608. tmpWidthPercent, _ := newDecimal.Div(newDecimal2).Round(3).Float64()
  609. //if !ok {
  610. // err = errors.New("word普通数据表格宽度百分比计算失败")
  611. // return
  612. //}
  613. widthPercent = tmpWidthPercent
  614. //计算出来每一列的宽度占比 end
  615. }
  616. tableCel := TableCel{
  617. Value: tmpCellList[k],
  618. TextAlign: "center",
  619. //ColumnSpan int `json:"column_span" description:"需要合同的列数量"`
  620. //IsMerged bool `json:"is_merged" description:"是否需要上下行合并"`
  621. Background: backgrandColor,
  622. IsBold: isBold,
  623. FontSize: fontSize,
  624. WidthPercent: widthPercent,
  625. }
  626. tableCelList = append(tableCelList, tableCel)
  627. }
  628. //将每行数据插入到table行数据切片之中
  629. tableRow := TableRow{
  630. RowList: tableCelList,
  631. }
  632. tableRowList = append(tableRowList, tableRow)
  633. }
  634. //赋值table表格数据
  635. tableDataList.List = tableRowList
  636. } else {
  637. //获取预设的表格数据
  638. contractServiceTemplate, tmpErr := contract_service_template.GetContractServiceTemplateById(item.ServiceTemplateId)
  639. if tmpErr != nil {
  640. err = tmpErr
  641. return
  642. }
  643. //赋值table表格数据
  644. jsonStr := contractServiceTemplate.TableValue
  645. tmpEerr := json.Unmarshal([]byte(jsonStr), &tableDataList)
  646. if tmpEerr != nil {
  647. err = tmpEerr
  648. return
  649. }
  650. }
  651. tableDataSlice = append(tableDataSlice, tableDataList)
  652. }
  653. titleStr := strings.Join(tableTitleSlice, "、")
  654. title += titleStr + "的服务内容,详细如下:"
  655. if htmlType == "pdf" {
  656. tableStr += `<p style="">` + title + `</p>`
  657. } else {
  658. tableStr = `<p style="font-size: 13pt; line-height: 40px">` + title + `</p>`
  659. }
  660. for _, tableDataList := range tableDataSlice {
  661. //往word中添加表格数据
  662. if htmlType == "pdf" {
  663. tableStr += getTableStrByPdf(tableDataList)
  664. } else {
  665. tableStr += getTableStr(tableDataList)
  666. }
  667. }
  668. data.TableHtml = tableStr
  669. }
  670. //fmt.Println("TableHtml:", data.TableHtml)
  671. contractHtml = strings.Replace(contractHtml, `\{\{\{TableHtml\}\}\}`, data.TableHtml, -1)
  672. return
  673. //生成pdf
  674. //pdfPath := fmt.Sprint("./static/word/系统生成合同", contractDetail.ContractCode, ".pdf")
  675. //err = Html2Pdf(contractHtml, pdfPath)
  676. //if err != nil {
  677. // return
  678. //}
  679. ////defer func() {
  680. //// //删除对应的Pdf
  681. //// os.Remove(pdfPath)
  682. ////}()
  683. //
  684. //return
  685. }
  686. //生成合同服务的预览表格html代码
  687. func getTableStr(tableDataList TableData) (tableStr string) {
  688. //如果表格需要分页,那么在table的style里面添加该配置:page-break-inside: avoid !important
  689. tableStr += `<table style="width: 100%;border-collapse: collapse;font-size: 13pt;margin-bottom:30px;page-break-inside: avoid !important;"><tbody>`
  690. rowList := tableDataList.List
  691. for i := 0; i < len(rowList); i++ {
  692. //创建新的一行
  693. tableStr += `<tr style="`
  694. tableStr += `page-break-before: always;page-break-after: always;page-break-inside: avoid !important;`
  695. //background-color: #F0F2F5;
  696. tableStr += `">`
  697. //<td style="border-right:1px solid #808181;border-bottom:1px solid #808181;padding: 15px 10px;font-weight: bold;" colspan="5">2.市场跟踪类报告</td>
  698. //row := table.AddRow()
  699. ////设置行高,第二个参数是设置固定值还是自动
  700. //row.Properties().SetHeight(30*measurement.Point, wml.ST_HeightRuleAtLeast)
  701. //
  702. //遍历列数据
  703. rowDataList := rowList[i].RowList
  704. cellStr := ""
  705. if rowDataList != nil {
  706. for j := 0; j < len(rowDataList); j++ {
  707. //当前列是否合并
  708. // <td style="font-weight: bold;" colspan="5">2.市场跟踪类报告</td>
  709. // <td style="" align="center" rowspan="3">市场跟踪</td>
  710. // <td style="" align="center">市场估值</td>
  711. tdStr := `<td `
  712. //单元格样式
  713. styleStr := `style="`
  714. styleStr += `border:1px solid #808181;padding: 15px 10px;line-height: 1.5;`
  715. //其他参数
  716. cellOtherStr := ` valign="middle" `
  717. //列数据
  718. cellData := rowDataList[j]
  719. //如果合并列大于0,那么就合并列
  720. if cellData.ColumnSpan > 0 {
  721. // column span / merged cells
  722. cellOtherStr += ` colspan="` + strconv.Itoa(cellData.ColumnSpan) + `" `
  723. }
  724. //如果指定了上下单元格合并,那么去合并上下单元格
  725. if cellData.IsMerged {
  726. if cellData.IsFirstMerged {
  727. cellOtherStr += ` rowspan="` + strconv.Itoa(cellData.RowSpan) + `" `
  728. } else {
  729. //如果是合并行,且不是第一行,那么就退出当前单元格循环,进入下一个循环
  730. continue
  731. }
  732. }
  733. //背景色
  734. if cellData.Background != "" {
  735. styleStr += `background-color: #F0F2F5;`
  736. }
  737. //将单元格设置为宽度百分比
  738. if cellData.WidthPercent > 0 {
  739. widthDecimal := decimal.NewFromFloat(cellData.WidthPercent)
  740. cellOtherStr += ` width="` + widthDecimal.String() + `%" `
  741. }
  742. //文字排版(居中、左、右)
  743. if cellData.TextAlign != "" {
  744. cellOtherStr += ` align="` + cellData.TextAlign + `" `
  745. }
  746. //cell.Properties().SetAli
  747. //设置是否加粗
  748. if cellData.IsBold {
  749. styleStr += `font-weight:bold;`
  750. }
  751. //设置字体大小
  752. fontSize := 10.0
  753. if cellData.FontSize > 0 {
  754. fontSize = cellData.FontSize
  755. }
  756. fontDecimal := decimal.NewFromFloat(fontSize)
  757. styleStr += `font-size: ` + fontDecimal.String() + `pt;`
  758. bodyStr := cellData.Value
  759. styleStr += `" `
  760. cellStr += tdStr + styleStr + cellOtherStr + `>` + bodyStr + `</td>`
  761. }
  762. }
  763. tableStr += cellStr + `</tr>`
  764. }
  765. tableStr += `</tbody></table>`
  766. return
  767. }
  768. //生成合同服务的pdf表格html代码
  769. func getTableStrByPdf(tableDataList TableData) (tableStr string) {
  770. //如果表格需要分页,那么在table的style里面添加该配置:page-break-inside: avoid !important
  771. tableStr += `<table style="width: 100%;border-collapse: collapse;margin-top:10pt;page-break-inside: avoid !important;"><tbody>`
  772. rowList := tableDataList.List
  773. for i := 0; i < len(rowList); i++ {
  774. //创建新的一行
  775. tableStr += `<tr style="`
  776. tableStr += `page-break-before: always;page-break-after: always;page-break-inside: avoid !important;`
  777. //background-color: #F0F2F5;
  778. tableStr += `">`
  779. //<td style="border-right:1px solid #808181;border-bottom:1px solid #808181;padding: 15px 10px;font-weight: bold;" colspan="5">2.市场跟踪类报告</td>
  780. //row := table.AddRow()
  781. ////设置行高,第二个参数是设置固定值还是自动
  782. //row.Properties().SetHeight(30*measurement.Point, wml.ST_HeightRuleAtLeast)
  783. //
  784. //遍历列数据
  785. rowDataList := rowList[i].RowList
  786. cellStr := ""
  787. if rowDataList != nil {
  788. for j := 0; j < len(rowDataList); j++ {
  789. //当前列是否合并
  790. // <td style="font-weight: bold;" colspan="5">2.市场跟踪类报告</td>
  791. // <td style="" align="center" rowspan="3">市场跟踪</td>
  792. // <td style="" align="center">市场估值</td>
  793. tdStr := `<td `
  794. //单元格样式
  795. styleStr := `style="`
  796. styleStr += `border:1px solid #808181;padding:4pt 10pt;`
  797. //其他参数
  798. cellOtherStr := ` valign="middle" `
  799. //列数据
  800. cellData := rowDataList[j]
  801. //如果合并列大于0,那么就合并列
  802. if cellData.ColumnSpan > 0 {
  803. // column span / merged cells
  804. cellOtherStr += ` colspan="` + strconv.Itoa(cellData.ColumnSpan) + `" `
  805. }
  806. //如果指定了上下单元格合并,那么去合并上下单元格
  807. if cellData.IsMerged {
  808. if cellData.IsFirstMerged {
  809. cellOtherStr += ` rowspan="` + strconv.Itoa(cellData.RowSpan) + `" `
  810. } else {
  811. //如果是合并行,且不是第一行,那么就退出当前单元格循环,进入下一个循环
  812. continue
  813. }
  814. }
  815. //背景色
  816. if cellData.Background != "" {
  817. styleStr += `background-color: #F0F2F5;`
  818. }
  819. //将单元格设置为宽度百分比
  820. if cellData.WidthPercent > 0 {
  821. widthDecimal := decimal.NewFromFloat(cellData.WidthPercent)
  822. cellOtherStr += ` width="` + widthDecimal.String() + `%" `
  823. }
  824. //文字排版(居中、左、右)
  825. if cellData.TextAlign != "" {
  826. cellOtherStr += ` align="` + cellData.TextAlign + `" `
  827. }
  828. //cell.Properties().SetAli
  829. //设置是否加粗
  830. if cellData.IsBold {
  831. styleStr += `font-weight:bold;`
  832. }
  833. //设置字体大小
  834. fontSize := 10.0
  835. if cellData.FontSize > 0 {
  836. fontSize = cellData.FontSize
  837. }
  838. fontDecimal := decimal.NewFromFloat(fontSize)
  839. styleStr += `font-size: ` + fontDecimal.String() + `pt;`
  840. bodyStr := cellData.Value
  841. styleStr += `" `
  842. cellStr += tdStr + styleStr + cellOtherStr + `>` + bodyStr + `</td>`
  843. }
  844. }
  845. tableStr += cellStr + `</tr>`
  846. }
  847. tableStr += `</tbody></table>`
  848. return
  849. }
  850. //根据html生成pdf
  851. func Html2Pdf(htmlStr, pdfPath string) (err error) {
  852. pdfg, err := wkhtml.NewPDFGenerator()
  853. if err != nil {
  854. fmt.Println("err:", err)
  855. return
  856. }
  857. //通过html生成page
  858. page := wkhtml.NewPageReader(strings.NewReader(htmlStr))
  859. //页眉设置
  860. //page.HeaderLeft.Set("弘则弥道(上海)投资咨询有限公司")
  861. //page.HeaderFontName.Set("宋体")
  862. //page.HeaderFontSize.Set(8)
  863. //page.HeaderSpacing.Set(4)
  864. //page.HeaderLine.Set(true)
  865. //page.HeaderSpacing.Set(10)
  866. //页脚设置
  867. page.FooterFontSize.Set(8)
  868. page.FooterRight.Set("[page]")
  869. page.FooterSpacing.Set(4)
  870. //page.FooterLine.Set(true)
  871. //page.EnableForms.Set(false)
  872. //转换HTML表单为PDF表单
  873. //page.EnableForms.Set(true)
  874. //使用打印媒体类型而不是屏幕
  875. //page.PrintMediaType.Set(true)
  876. //允许从标题链接到目录
  877. page.EnableTocBackLinks.Set(true)
  878. //将该page插入到pdf中
  879. pdfg.AddPage(page)
  880. //pdfg.PageSize.Set(wkhtml.PageSizeA4)
  881. //pdfg.MarginTop.Set(20)
  882. //pdfg.MarginBottom.Set(5)
  883. //pdfg.MarginLeft.Set(0)
  884. //pdfg.
  885. err = pdfg.Create()
  886. if err != nil {
  887. return
  888. }
  889. err = pdfg.WriteFile(pdfPath)
  890. return
  891. }
  892. //获取表格列数据
  893. func getColList(item *contract_service_detail.ContractServiceDetail) (cellList []string, err error) {
  894. cellList = make([]string, 0)
  895. var serviceDetailReq contractReq.AddContractServiceDetailReq
  896. tmpItem := *item
  897. t := reflect.TypeOf(tmpItem)
  898. v := reflect.ValueOf(tmpItem)
  899. for k := 0; k < t.NumField(); k++ {
  900. //获取结构体的参数名
  901. tmpName := t.Field(k).Name
  902. if strings.Contains(tmpName, "Col") {
  903. //获取结构体该参数名的值
  904. tmpValue := v.Field(k).String()
  905. //如果值不为空的话,那么做下json转换
  906. if tmpValue != "" {
  907. err = json.Unmarshal([]byte(tmpValue), &serviceDetailReq)
  908. if err != nil {
  909. return
  910. } else {
  911. cellList = append(cellList, serviceDetailReq.Value)
  912. }
  913. }
  914. }
  915. }
  916. return
  917. }