h_exam.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. package handler
  2. import (
  3. webServerEntity "cicv-data-closedloop/amd64/web_server/entity"
  4. "cicv-data-closedloop/common/config/c_db"
  5. "cicv-data-closedloop/common/config/c_log"
  6. commonEntity "cicv-data-closedloop/common/entity"
  7. "cicv-data-closedloop/common/util"
  8. "github.com/gin-gonic/gin"
  9. "github.com/signintech/gopdf"
  10. "io"
  11. "log"
  12. "math"
  13. "net/http"
  14. "os"
  15. "strconv"
  16. "sync"
  17. "time"
  18. )
  19. var (
  20. defaultTime = time.Date(2006, time.January, 2, 15, 4, 5, 0, time.Local)
  21. cacheMutex sync.Mutex
  22. cacheTeamName = make(map[string]time.Time)
  23. heartBeatTimeThreshold = 5 * time.Second // 心跳时间
  24. InitialPositionX = 456193.00 // todo 需要比赛确认起点
  25. InitialPositionY = 4397731.00 // todo 需要比赛确认起点
  26. // todo 比赛阶段
  27. trialBegin = time.Date(2024, time.June, 16, 13, 00, 00, 0, time.Local)
  28. trialEnd = time.Date(2024, time.June, 19, 23, 59, 59, 0, time.Local)
  29. )
  30. // 考试心跳
  31. func Tick(c *gin.Context) {
  32. param := new(webServerEntity.ExamPao)
  33. // 映射到结构体
  34. if err := c.ShouldBindJSON(&param); err != nil {
  35. c_log.GlobalLogger.Error("请求体解析失败,错误信息为:", err)
  36. c.JSON(http.StatusBadRequest, commonEntity.Response{
  37. Code: 500,
  38. Msg: "请求体解析失败。",
  39. })
  40. return
  41. }
  42. teamName := param.TeamName
  43. positionX, _ := strconv.ParseFloat(param.PositionX, 64)
  44. positionY, _ := strconv.ParseFloat(param.PositionY, 64)
  45. if !util.ContainsKey(cacheTeamName, teamName) && math.Abs(positionX-InitialPositionX) < 5.00 && math.Abs(positionY-InitialPositionY) < 5.00 { // (在起点开始)
  46. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-insert-begin_time-and-topic-by-team_name.sql"])
  47. stage := "表演赛"
  48. if time.Now().After(trialBegin) && time.Now().Before(trialEnd) {
  49. stage = "预赛"
  50. } else {
  51. stage = "决赛"
  52. }
  53. competitionBegin := time.Now()
  54. c_log.GlobalLogger.Infof("当前比赛阶段为 %v ,队伍 %v 在起始点范围内第一次启动,保存新一条比赛记录的开始时间 %v", stage, teamName, competitionBegin)
  55. if err := c_db.DoTx(sqlTemplate, []any{param.TeamName, stage, competitionBegin}); err != nil {
  56. c_log.GlobalLogger.Error("保存比赛开始时间报错:", err)
  57. c.JSON(http.StatusBadRequest, commonEntity.Response{Code: 500, Msg: "保存比赛开始时间报错。"})
  58. return
  59. }
  60. } else if !util.ContainsKey(cacheTeamName, teamName) && math.Abs(positionX-InitialPositionX) < 5.00 && math.Abs(positionY-InitialPositionY) < 5.00 { // 不在起点(开始)
  61. // 车辆不是在起点启动的自动驾驶模式
  62. selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"])
  63. if err != nil {
  64. c_log.GlobalLogger.Error("读取sql文件报错:", err)
  65. return
  66. }
  67. // 可以传参数
  68. var result []webServerEntity.ExamPo
  69. if err = c_db.MysqlDb.Select(&result, selectSql, teamName); err != nil {
  70. c_log.GlobalLogger.Error("数据库查询报错:", err)
  71. return
  72. }
  73. if len(result) == 1 {
  74. c_log.GlobalLogger.Info("上一条数据记录为:", result[0])
  75. }
  76. // 添加队伍名到缓存中
  77. cacheTeamName[teamName] = time.Now()
  78. // 更新记录结束时间为默认时间
  79. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"])
  80. if err = c_db.DoTx(sqlTemplate, []any{
  81. defaultTime,
  82. teamName,
  83. }); err != nil {
  84. c_log.GlobalLogger.Error("插入数据报错:", err)
  85. return
  86. }
  87. c_log.GlobalLogger.Infof("队伍 %v 的考试在中途中断后重新开始。", teamName)
  88. } else if util.ContainsKey(cacheTeamName, teamName) { // 进行中
  89. cacheTeamName[teamName] = time.Now()
  90. } else {
  91. c_log.GlobalLogger.Errorf("接收到心跳信息但考试并未开始,队伍名字为 %v,x坐标为 %v,y坐标为 %v ", teamName, positionX, positionY)
  92. }
  93. c.JSON(http.StatusOK, commonEntity.Response{
  94. Code: 200,
  95. Msg: "心跳接收成功。",
  96. })
  97. }
  98. func ExamEndTicker() {
  99. // 创建一个定时器,每隔一秒触发一次
  100. ticker := time.NewTicker(1 * time.Second)
  101. for {
  102. select {
  103. // 定时器触发时执行的代码
  104. case <-ticker.C:
  105. cacheMutex.Lock()
  106. {
  107. var keysToDelete []string
  108. for teamName, heartBeatTime := range cacheTeamName {
  109. if time.Since(heartBeatTime) > heartBeatTimeThreshold { // 检查缓存中的队名,如果超过心跳时间,则代表考试结束,删除缓存中的队名
  110. keysToDelete = append(keysToDelete, teamName)
  111. }
  112. }
  113. for _, teamName := range keysToDelete { // 检查缓存中的队名,如果超过心跳时间,则代表考试结束,删除缓存中的队名
  114. delete(cacheTeamName, teamName)
  115. // 1 查询指定队伍的开始时间最新的考试是否有结束时间,如果有则不在处理,如果没有则更新
  116. var result []webServerEntity.ExamPo
  117. selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"])
  118. if err != nil {
  119. c_log.GlobalLogger.Error("读取sql文件报错:", err)
  120. return
  121. }
  122. // 可以传参数
  123. if err = c_db.MysqlDb.Select(&result, selectSql, teamName); err != nil {
  124. c_log.GlobalLogger.Error("数据库查询报错:", err)
  125. return
  126. }
  127. c_log.GlobalLogger.Info("数据库查询成功:", result)
  128. if !result[0].EndTime.Equal(defaultTime) {
  129. c_log.GlobalLogger.Error("赛队", teamName, "考试已结束!")
  130. return
  131. }
  132. // 更新到数据库
  133. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"])
  134. if err := c_db.DoTx(sqlTemplate, []any{
  135. time.Now(),
  136. teamName,
  137. }); err != nil {
  138. c_log.GlobalLogger.Error("插入数据报错:", err)
  139. return
  140. }
  141. c_log.GlobalLogger.Infof("队伍 %v 的考试结束。", teamName)
  142. }
  143. }
  144. cacheMutex.Unlock()
  145. }
  146. }
  147. }
  148. // 考试开始时间
  149. func Begin(c *gin.Context) {
  150. param := new(webServerEntity.ExamPao)
  151. // 映射到结构体
  152. if err := c.ShouldBindJSON(&param); err != nil {
  153. c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err)
  154. c.JSON(http.StatusBadRequest, commonEntity.Response{
  155. Code: 500,
  156. Msg: "请求体解析失败。",
  157. })
  158. return
  159. }
  160. // 插入到数据库
  161. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-insert-begin_time-by-team_name.sql"])
  162. c_log.GlobalLogger.Info("插入比赛开始时间", sqlTemplate)
  163. if err := c_db.DoTx(sqlTemplate, []any{
  164. param.TeamName,
  165. time.Now(),
  166. }); err != nil {
  167. c_log.GlobalLogger.Error("插入数据报错:", err)
  168. c.JSON(http.StatusBadRequest, commonEntity.Response{
  169. Code: 500,
  170. Msg: "插入数据报错。",
  171. })
  172. return
  173. }
  174. c.JSON(http.StatusOK, commonEntity.Response{
  175. Code: 200,
  176. Msg: "插入数据成功。",
  177. })
  178. }
  179. // 考试结束时间
  180. func End(c *gin.Context) {
  181. param := new(webServerEntity.ExamPao)
  182. // 映射到结构体
  183. if err := c.ShouldBindJSON(&param); err != nil {
  184. c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err)
  185. c.JSON(http.StatusBadRequest, commonEntity.Response{
  186. Code: 500,
  187. Msg: "请求体解析失败。",
  188. })
  189. return
  190. }
  191. // 1 查询指定队伍的开始时间最新的考试是否有结束时间,如果有则不在处理,如果没有则更新
  192. var result []webServerEntity.ExamPo
  193. selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"])
  194. if err != nil {
  195. c_log.GlobalLogger.Error("读取sql文件报错:", err)
  196. c.JSON(http.StatusBadRequest, commonEntity.Response{
  197. Code: 500,
  198. Msg: "读取sql文件报错。",
  199. })
  200. return
  201. }
  202. // 可以传参数
  203. if err = c_db.MysqlDb.Select(&result, selectSql, param.TeamName); err != nil {
  204. c_log.GlobalLogger.Error("数据库查询报错:", err)
  205. c.JSON(http.StatusBadRequest, commonEntity.Response{
  206. Code: 500,
  207. Msg: "数据库查询报错。",
  208. })
  209. return
  210. }
  211. c_log.GlobalLogger.Info("数据库查询成功:", result)
  212. if !result[0].EndTime.Equal(defaultTime) {
  213. c_log.GlobalLogger.Error("赛队", param.TeamName, "重复请求考试结束接口!")
  214. c.JSON(http.StatusBadRequest, commonEntity.Response{
  215. Code: 500,
  216. Msg: "重复请求。",
  217. })
  218. return
  219. }
  220. // 更新到数据库
  221. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"])
  222. if err := c_db.DoTx(sqlTemplate, []any{
  223. time.Now(),
  224. param.TeamName,
  225. }); err != nil {
  226. c_log.GlobalLogger.Error("插入数据报错:", err)
  227. c.JSON(http.StatusBadRequest, commonEntity.Response{
  228. Code: 500,
  229. Msg: "插入数据报错。",
  230. })
  231. return
  232. }
  233. c.JSON(http.StatusOK, commonEntity.Response{
  234. Code: 200,
  235. Msg: "插入数据成功。",
  236. })
  237. }
  238. // 分页查询
  239. // todo 如果日期为默认值,则返回空""
  240. func Page(c *gin.Context) {
  241. param := new(webServerEntity.ExamPagePao)
  242. _ = c.ShouldBindJSON(&param)
  243. var resultPos []webServerEntity.ExamPo
  244. var resultPosTotal []int
  245. var pageSql string
  246. var totalSql string
  247. offset := (param.CurrentPage - 1) * param.PageSize
  248. size := param.PageSize
  249. if param.TeamName == "" && param.Topic == "" {
  250. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page.sql"])
  251. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total.sql"])
  252. err := c_db.MysqlDb.Select(&resultPos, pageSql, offset, size)
  253. if err != nil {
  254. c_log.GlobalLogger.Error(err)
  255. }
  256. err = c_db.MysqlDb.Select(&resultPosTotal, totalSql)
  257. if err != nil {
  258. c_log.GlobalLogger.Error(err)
  259. }
  260. }
  261. if param.TeamName != "" && param.Topic == "" {
  262. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-team_name.sql"])
  263. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-team_name.sql"])
  264. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.TeamName+"%", offset, size)
  265. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.TeamName+"%")
  266. }
  267. if param.TeamName == "" && param.Topic != "" {
  268. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-topic.sql"])
  269. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-topic.sql"])
  270. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.Topic+"%", offset, size)
  271. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.Topic+"%")
  272. }
  273. if param.TeamName != "" && param.Topic != "" {
  274. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-team_name-and-topic.sql"])
  275. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-team_name-and-topic.sql"])
  276. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.TeamName+"%", "%"+param.Topic+"%", offset, size)
  277. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.TeamName+"%", "%"+param.Topic+"%")
  278. }
  279. var resultVos []webServerEntity.ExamVo
  280. for _, po := range resultPos {
  281. resultVos = append(resultVos, webServerEntity.ExamVo{
  282. Id: po.Id,
  283. TeamName: po.TeamName,
  284. Topic: po.Topic,
  285. BeginTime: util.GetTimeString(po.BeginTime),
  286. EndTime: util.GetTimeString(po.EndTime),
  287. ScoreOnline: po.ScoreOnline,
  288. ScoreOffline: po.ScoreOffline,
  289. ScoreFinal: po.ScoreFinal,
  290. Details: po.Details,
  291. ScoreReportPath: po.ScoreReportPath,
  292. })
  293. }
  294. c.JSON(http.StatusOK, commonEntity.Response{
  295. Code: 200,
  296. Msg: "分页查询成功!",
  297. Data: resultVos,
  298. Total: resultPosTotal[0],
  299. })
  300. }
  301. // 评分报告pdf下载
  302. func Report(c *gin.Context) {
  303. param := new(webServerEntity.ExamReportPao)
  304. // 映射到结构体
  305. if err := c.ShouldBindJSON(&param); err != nil {
  306. c_log.GlobalLogger.Error("接收请求参数报错:", err)
  307. c.JSON(http.StatusBadRequest, commonEntity.Response{
  308. Code: 500,
  309. Msg: "请求体解析失败。",
  310. })
  311. return
  312. }
  313. layout := "2006-01-02 15:04:05"
  314. beginTime, _ := time.Parse(layout, param.BeginTime)
  315. endTime, _ := time.Parse(layout, param.EndTime)
  316. // 1 根据ID查询数据
  317. // 2 根据数据生成pdf
  318. // 3 创建pdf文件
  319. {
  320. // 初始化 pdf 对象
  321. pdf := gopdf.GoPdf{}
  322. pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
  323. // 添加字体文件到pdf
  324. err := pdf.AddTTFFont("simfang", "D:\\code\\cicv-data-closedloop\\amd64\\web_server\\simfang.ttf")
  325. if err != nil {
  326. log.Print(err.Error())
  327. return
  328. }
  329. leftMargin := 70.0
  330. topMargin := 100.0
  331. lineHeight := 25.0
  332. tableWidth := 450.0
  333. pdf.SetLeftMargin(leftMargin) // 设置左边距
  334. alignOptionText := gopdf.CellOption{Align: gopdf.Left | gopdf.Middle}
  335. //alignOptionText2 := gopdf.CellOption{Align: gopdf.Center | gopdf.Middle}
  336. alignOptionTable := gopdf.CellOption{Align: gopdf.Center | gopdf.Middle, Border: gopdf.Left | gopdf.Right | gopdf.Bottom | gopdf.Top}
  337. alignOptionTable2 := gopdf.CellOption{Align: gopdf.Left | gopdf.Middle}
  338. // ------- 封面页 -------
  339. pdf.AddPage()
  340. {
  341. err = pdf.SetFont("simfang", "", 36)
  342. err = pdf.Image("D:\\code\\cicv-data-closedloop\\amd64\\web_server\\background.png", 0, 0, nil) // 背景图片
  343. err = pdf.Image("D:\\code\\cicv-data-closedloop\\amd64\\web_server\\logo.png", 20, 20, &gopdf.Rect{W: 320, H: 100})
  344. {
  345. pdf.SetXY(100, 250)
  346. _ = pdf.Text("车路云一体化算法挑战赛")
  347. }
  348. {
  349. pdf.SetXY(220, 315) // 200
  350. _ = pdf.Text("评分报告")
  351. }
  352. _ = pdf.SetFont("simfang", "", 14)
  353. currentLine := 0.0
  354. tableCellWidth := 100.0
  355. tableLeftMartin := 190.0
  356. tableTopMartin := 500.0
  357. {
  358. {
  359. pdf.SetXY(tableLeftMartin, tableTopMartin+currentLine*lineHeight)
  360. _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "赛队编号:", alignOptionTable2)
  361. }
  362. {
  363. pdf.SetXY(tableLeftMartin+1.0*tableCellWidth, tableTopMartin+currentLine*lineHeight)
  364. _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, param.TeamName, alignOptionTable2)
  365. }
  366. currentLine++
  367. }
  368. {
  369. {
  370. pdf.SetXY(tableLeftMartin, tableTopMartin+currentLine*lineHeight)
  371. _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "评测车型:", alignOptionTable2)
  372. }
  373. {
  374. pdf.SetXY(tableLeftMartin+1.0*tableCellWidth, tableTopMartin+currentLine*lineHeight)
  375. _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "多功能车平台", alignOptionTable2)
  376. }
  377. currentLine++
  378. }
  379. {
  380. {
  381. pdf.SetXY(tableLeftMartin, tableTopMartin+currentLine*lineHeight)
  382. _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "评测地点:", alignOptionTable2)
  383. }
  384. {
  385. pdf.SetXY(tableLeftMartin+1.0*tableCellWidth, tableTopMartin+currentLine*lineHeight)
  386. _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "天津生态城", alignOptionTable2)
  387. }
  388. currentLine++
  389. }
  390. {
  391. {
  392. pdf.SetXY(tableLeftMartin, tableTopMartin+currentLine*lineHeight)
  393. _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "报告时间:", alignOptionTable2)
  394. }
  395. {
  396. pdf.SetXY(tableLeftMartin+1.0*tableCellWidth, tableTopMartin+currentLine*lineHeight)
  397. _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, time.Now().Format("2006年01月02日"), alignOptionTable2)
  398. }
  399. currentLine++
  400. }
  401. {
  402. pdf.SetXY(165, 775)
  403. _ = pdf.Cell(nil, "国汽(北京)智能网联汽车研究院有限公司")
  404. }
  405. }
  406. // todo 先不要目录页
  407. //// ------- 目录页 -------
  408. //{
  409. // pdf.AddPage()
  410. // currentLine := 0.0
  411. // {
  412. // err = pdf.SetFont("simfang", "", 18)
  413. // pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  414. // _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "目录", alignOptionText2)
  415. // currentLine++
  416. // currentLine++
  417. // }
  418. // {
  419. // err = pdf.SetFont("simfang", "", 14)
  420. // pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  421. // currentLine++
  422. // _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "1. 总体情况 ............................... 1", alignOptionText)
  423. // }
  424. //}
  425. // ------- 详情页 第一页 -------
  426. {
  427. pdf.AddPage()
  428. currentLine := 0.0
  429. // 1. 总体分数情况
  430. {
  431. {
  432. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  433. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "1. 总体分数情况", alignOptionText)
  434. currentLine++
  435. }
  436. {
  437. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  438. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "整体得分:"+util.ToString(param.ScoreFinal), alignOptionText)
  439. currentLine++
  440. }
  441. {
  442. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  443. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "线上得分:"+util.ToString(param.ScoreFinal), alignOptionText)
  444. currentLine++
  445. }
  446. {
  447. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  448. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "线下得分:", alignOptionText)
  449. currentLine++
  450. }
  451. {
  452. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  453. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "整体耗时:"+endTime.Sub(beginTime).String(), alignOptionText)
  454. currentLine++
  455. }
  456. }
  457. // 2. 分数详情
  458. {
  459. columnNumber := 4.0 // 列数
  460. tableCellWidth := tableWidth / columnNumber // 单元格宽度
  461. {
  462. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  463. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "2. 分数详情", alignOptionText)
  464. currentLine++
  465. }
  466. // 分数详情表格
  467. {
  468. {
  469. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  470. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "序号", alignOptionTable)
  471. }
  472. {
  473. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  474. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "场景名称", alignOptionTable)
  475. }
  476. {
  477. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  478. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "线上得分", alignOptionTable)
  479. }
  480. {
  481. pdf.SetXY(leftMargin+3.0*tableCellWidth, topMargin+currentLine*lineHeight)
  482. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "线下得分", alignOptionTable)
  483. }
  484. currentLine++
  485. }
  486. for i := 0; i < 10; i++ {
  487. {
  488. {
  489. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  490. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, util.ToString(i+1), alignOptionTable)
  491. }
  492. {
  493. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  494. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable)
  495. }
  496. {
  497. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  498. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable)
  499. }
  500. {
  501. pdf.SetXY(leftMargin+3.0*tableCellWidth, topMargin+currentLine*lineHeight)
  502. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable)
  503. }
  504. currentLine++
  505. }
  506. }
  507. }
  508. }
  509. // ------- 详情页 第二页 -------
  510. {
  511. pdf.AddPage()
  512. currentLine := 0.0
  513. // 3. 线上扣分详情
  514. {
  515. columnNumber := 3.0 // 列数
  516. tableCellWidth := tableWidth / columnNumber // 单元格宽度
  517. {
  518. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  519. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "3. 线上扣分详情", alignOptionText)
  520. currentLine++
  521. }
  522. for i := 0; i < 5; i++ {
  523. // 场景1
  524. {
  525. {
  526. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  527. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "("+util.ToString(i+1)+")xxxx场景", alignOptionText)
  528. currentLine++
  529. }
  530. // 场景1 表格
  531. {
  532. {
  533. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  534. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "序号", alignOptionTable)
  535. }
  536. {
  537. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  538. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "扣分点", alignOptionTable)
  539. }
  540. {
  541. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  542. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "扣分说明", alignOptionTable)
  543. }
  544. currentLine++
  545. }
  546. for j := 0; j < 2; j++ {
  547. {
  548. {
  549. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  550. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, util.ToString(j+1), alignOptionTable)
  551. }
  552. {
  553. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  554. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable)
  555. }
  556. {
  557. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  558. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable)
  559. }
  560. currentLine++
  561. }
  562. }
  563. }
  564. }
  565. }
  566. }
  567. // ------- 详情页 第三页 -------
  568. size := 10
  569. if size > 5 {
  570. pdf.AddPage()
  571. currentLine := 0.0
  572. // 3. 线上扣分详情
  573. {
  574. columnNumber := 3.0 // 列数
  575. tableCellWidth := tableWidth / columnNumber // 单元格宽度
  576. for i := 5; i < 10; i++ {
  577. // 场景1
  578. {
  579. {
  580. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  581. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "("+util.ToString(i+1)+")xxxx场景", alignOptionText)
  582. currentLine++
  583. }
  584. // 场景1 表格
  585. {
  586. {
  587. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  588. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "序号", alignOptionTable)
  589. }
  590. {
  591. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  592. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "扣分点", alignOptionTable)
  593. }
  594. {
  595. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  596. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "扣分说明", alignOptionTable)
  597. }
  598. currentLine++
  599. }
  600. for j := 0; j < 2; j++ {
  601. {
  602. {
  603. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  604. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, util.ToString(j+1), alignOptionTable)
  605. }
  606. {
  607. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  608. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable)
  609. }
  610. {
  611. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  612. _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable)
  613. }
  614. currentLine++
  615. }
  616. }
  617. }
  618. }
  619. }
  620. // 4 作品优化建议
  621. {
  622. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  623. _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "4. 作品优化建议", alignOptionText)
  624. currentLine++
  625. }
  626. }
  627. // ------- 附录页 -------
  628. pdf.AddPage()
  629. {
  630. pdf.SetXY(leftMargin, topMargin)
  631. _ = pdf.Text("附件:评分规则")
  632. pdf.AddExternalLink("https://www.baidu.com/", leftMargin, topMargin-lineHeight, 6*14, lineHeight)
  633. }
  634. // 写入本地
  635. pdf.WritePdf("D:\\hello.pdf")
  636. }
  637. // 打开要发送的文件
  638. file, err := os.Open("D:\\hello.pdf")
  639. if err != nil {
  640. c.String(http.StatusNotFound, "文件未找到")
  641. return
  642. }
  643. defer file.Close()
  644. // 将文件复制到响应主体
  645. _, err = io.Copy(c.Writer, file)
  646. if err != nil {
  647. c.String(http.StatusInternalServerError, "无法复制文件到响应体")
  648. return
  649. }
  650. }