ppt.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. package services
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "eta/eta_api/models"
  6. "eta/eta_api/models/data_manage/excel"
  7. "eta/eta_api/models/ppt_english"
  8. "eta/eta_api/models/system"
  9. "eta/eta_api/services/alarm_msg"
  10. "eta/eta_api/services/ppt2img"
  11. "eta/eta_api/utils"
  12. "fmt"
  13. "sort"
  14. "time"
  15. )
  16. const (
  17. ElementsTypeText = "text"
  18. ElementsTypeImage = "image"
  19. ElementsTypeChart = "chart"
  20. ElementsTypeSheet = "sheet"
  21. )
  22. type PPTContent struct {
  23. //Id int `json:"id" description:"此处因目录改版类型有int也有string且并没有使用到该字段所以注释掉"`
  24. Key int `json:"key"`
  25. ModelId int `json:"modelId"`
  26. Title string `json:"title"`
  27. Elements []PPTContentElements `json:"elements"`
  28. }
  29. type PPTContentElements struct {
  30. Type string `json:"type"`
  31. Position int `json:"position"`
  32. Content string `json:"content"`
  33. RichContent string `json:"richContent"`
  34. ChartId string `json:"chartId"`
  35. SheetId string `json:"sheetId"`
  36. SheetHeight string `json:"sheetHeight"`
  37. Src string `json:"src"`
  38. }
  39. // SavePPTReport 保存PPT报告
  40. func SavePPTReport(pptId, classifyId int, title string, adminInfo *system.Admin) (reportId int, reportCode, errMsg string, err error) {
  41. defer func() {
  42. if err != nil {
  43. utils.FileLog.Info("%s", err.Error())
  44. go alarm_msg.SendAlarmMsg("PPT转报告失败, SavePPTReport Msg: "+errMsg+", Err: "+err.Error(), 3)
  45. }
  46. }()
  47. if pptId == 0 {
  48. errMsg = "参数有误"
  49. err = errors.New("参数有误")
  50. return
  51. }
  52. item, e := models.GetPptV2ById(pptId)
  53. if e != nil {
  54. errMsg = "获取PPT失败"
  55. err = errors.New("获取PPT失败, Err: " + e.Error())
  56. return
  57. }
  58. // PPT内容转HTML
  59. htm, e := pptContent2Html(item.Content, false)
  60. if e != nil {
  61. errMsg = "转换失败"
  62. err = e
  63. return
  64. }
  65. // 2023-02-21 PPT可多次转为报告, 不做关联
  66. if classifyId == 0 {
  67. errMsg = "请选择报告类型"
  68. err = errors.New("请选择报告类型")
  69. return
  70. }
  71. if title == "" {
  72. errMsg = "标题不能为空"
  73. err = errors.New("标题不能为空")
  74. return
  75. }
  76. // 获取分类及父级分类
  77. classifyList, e := models.GetAllClassify()
  78. if e != nil {
  79. errMsg = "转换失败"
  80. err = errors.New("获取分类列表失败, Err: " + e.Error())
  81. return
  82. }
  83. classifyMap := make(map[int]*models.Classify, 0)
  84. for _, v := range classifyList {
  85. classifyMap[v.Id] = v
  86. }
  87. classifyIdFirst := 0
  88. classifyIdSecond := 0
  89. classifyIdThird := 0
  90. classifyNameFirst := ""
  91. classifyNameSecond := ""
  92. classifyNameThird := ""
  93. // 最小单元分类,第二级别的分类 ,最大的分类
  94. var baseClassify, twoClassify, threeClassify *models.Classify
  95. var hasTwo, hasThird bool
  96. baseClassify, ok := classifyMap[classifyId]
  97. if !ok {
  98. errMsg = "分类异常"
  99. err = errors.New("获取分类失败 ")
  100. return
  101. }
  102. twoClassify, hasTwo = classifyMap[baseClassify.ParentId]
  103. if hasTwo {
  104. threeClassify, hasThird = classifyMap[twoClassify.ParentId]
  105. }
  106. if hasThird { // 如果确实是有三级分类
  107. classifyIdFirst = threeClassify.Id
  108. classifyNameFirst = threeClassify.ClassifyName
  109. classifyIdSecond = twoClassify.Id
  110. classifyNameSecond = twoClassify.ClassifyName
  111. classifyIdThird = baseClassify.Id
  112. classifyNameThird = baseClassify.ClassifyName
  113. } else if hasTwo {
  114. classifyIdFirst = twoClassify.Id
  115. classifyNameFirst = twoClassify.ClassifyName
  116. classifyIdSecond = baseClassify.Id
  117. classifyNameSecond = baseClassify.ClassifyName
  118. } else {
  119. classifyIdFirst = baseClassify.Id
  120. classifyNameFirst = baseClassify.ClassifyName
  121. }
  122. // 新增报告
  123. nowTime := time.Now().Local()
  124. reportReq := &models.AddReq{
  125. AddType: 1,
  126. ClassifyIdFirst: classifyIdFirst,
  127. ClassifyNameFirst: classifyNameFirst,
  128. ClassifyIdSecond: classifyIdSecond,
  129. ClassifyNameSecond: classifyNameSecond,
  130. ClassifyIdThird: classifyIdThird,
  131. ClassifyNameThird: classifyNameThird,
  132. Title: title,
  133. Abstract: "",
  134. Author: "",
  135. Frequency: utils.ReportFrequencyDefault,
  136. State: 1,
  137. Content: htm,
  138. CreateTime: nowTime.Format(utils.FormatDateTime),
  139. ReportVersion: 2,
  140. CollaborateType: 1, // 协作方式,1:个人,2:多人协作。默认:1
  141. ReportLayout: 1, // 报告布局,1:常规布局,2:智能布局。默认:1
  142. IsPublicPublish: 1, // 是否公开发布,1:是,2:否
  143. }
  144. // 如果PPT是公开的,则报告也公开发布
  145. if item.IsShare == 1 {
  146. reportReq.IsPublicPublish = 1
  147. }
  148. newReportId, newCode, _, e := CreateNewReport(*reportReq, adminInfo)
  149. if e != nil {
  150. errMsg = "转换失败"
  151. err = errors.New("新增报告失败, Err: " + e.Error())
  152. return
  153. }
  154. reportId = int(newReportId)
  155. reportCode = newCode
  156. // ppt转报告后,将ppt的表格关系做处理
  157. handlerPptToReportTableReferenced(pptId, int(newReportId))
  158. // 更新报告中的ppt图片
  159. go saveReportPptImg(pptId, reportId, item.PptxUrl)
  160. return
  161. }
  162. // PPT2内容转HTML
  163. func pptContent2Html(content string, isEnglish bool) (htm string, err error) {
  164. contents := make([]PPTContent, 0)
  165. if e := json.Unmarshal([]byte(content), &contents); e != nil {
  166. err = errors.New("PPT内容转换失败")
  167. return
  168. }
  169. pageLen := len(contents)
  170. htmlContent := ``
  171. // iframe图表/表格域名
  172. // 获取基础配置, 若未配置则直接返回
  173. // 获取配置好的短信模版
  174. smsCond := ` AND conf_key = ? `
  175. smsPars := make([]interface{}, 0)
  176. smsPars = append(smsPars, "ChartViewUrl")
  177. conf := new(models.BusinessConf)
  178. conf, e := conf.GetItemByCondition(smsCond, smsPars)
  179. if e != nil {
  180. if e.Error() == utils.ErrNoRow() {
  181. err = fmt.Errorf("请先配置公共图库的地址")
  182. return
  183. }
  184. err = fmt.Errorf("获取聚合短信配置信息失败, Err: %s", e.Error())
  185. return
  186. }
  187. if conf.ConfVal == "" {
  188. err = fmt.Errorf("请先配置公共图库的地址")
  189. return
  190. }
  191. chartRoot := conf.ConfVal
  192. if pageLen > 0 {
  193. htmlPrefix := `<p style="text-align: left; margin-top: 10px; font-size: 16px;">`
  194. htmlSuffix := `</p>`
  195. htmlBr := `<br>`
  196. for i := 0; i < pageLen; i++ {
  197. // 每页标题加粗居中
  198. title := contents[i].Title
  199. if title != "" {
  200. htmlContent += `<p style="font-size: 16px; text-align: left;"><strong>`
  201. htmlContent += title
  202. htmlContent += `</strong></p>`
  203. }
  204. ele := contents[i].Elements
  205. // 每页元素按照Position升序排序
  206. sort.Slice(ele, func(k, j int) bool {
  207. return ele[k].Position < ele[j].Position
  208. })
  209. for _, v := range ele {
  210. // 根据不同的Type拼接不同的内容
  211. htmlContent += htmlPrefix
  212. switch v.Type {
  213. case ElementsTypeText:
  214. htmlContent += v.RichContent
  215. case ElementsTypeImage:
  216. htmlContent += fmt.Sprint(`<img src="`, v.Src, `" class="fr-fic fr-dib fr-draggable">`)
  217. case ElementsTypeChart:
  218. if isEnglish {
  219. // 英文研报图表src多加一个fromPage=en, 表格暂时没有区分
  220. htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s&fromPage=en" width="100%%" height="350" style="border-width:0px; min-height:350px;"></iframe>`, chartRoot, v.ChartId)
  221. break
  222. }
  223. htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s" width="100%%" height="350" style="border-width:0px; min-height:350px;"></iframe>`, chartRoot, v.ChartId)
  224. case ElementsTypeSheet:
  225. htmlContent += fmt.Sprintf(`<iframe src="%s/sheetshow?code=%s" class="iframe%s" width="100%%" height="%s" style="border-width:0px;"></iframe>`, chartRoot, v.SheetId, v.SheetId, v.SheetHeight)
  226. }
  227. htmlContent += htmlSuffix
  228. }
  229. // 每页中间插入一个换行符, 最后一页不插入
  230. currentPage := i + 1
  231. if currentPage != pageLen {
  232. htmlContent += htmlPrefix + htmlBr + htmlSuffix
  233. }
  234. }
  235. }
  236. htm = htmlContent
  237. return
  238. }
  239. // ResetPPTReport 重置PPT关联的报告(如删除关联的报告后)
  240. func ResetPPTReport(reportId int, isEnglish bool) (err error) {
  241. defer func() {
  242. if err != nil {
  243. utils.FileLog.Info("%s", err.Error())
  244. go alarm_msg.SendAlarmMsg("重置PPT关联报告失败, ResetPPTReport Err: "+err.Error(), 3)
  245. }
  246. }()
  247. // 英文报告
  248. if isEnglish {
  249. en, e := ppt_english.GetPptEnglishByReportId(reportId)
  250. if e != nil && e.Error() != utils.ErrNoRow() {
  251. err = errors.New("获取英文PPT失败, Err: " + e.Error())
  252. return
  253. }
  254. if en != nil {
  255. updateCols := []string{"ReportId", "ReportCode"}
  256. en.ReportId = 0
  257. en.ReportCode = ""
  258. if e = en.Update(updateCols); e != nil {
  259. err = errors.New("更新英文PPT关联报告失败, Err: " + e.Error())
  260. return
  261. }
  262. }
  263. return
  264. }
  265. // 中文报告
  266. item, e := models.GetPptV2ByReportId(reportId)
  267. if e != nil && e.Error() != utils.ErrNoRow() {
  268. err = errors.New("获取PPT失败, Err: " + e.Error())
  269. return
  270. }
  271. if item != nil {
  272. updateCols := []string{"ReportId", "ReportCode"}
  273. item.ReportId = 0
  274. item.ReportCode = ""
  275. if e = item.Update(updateCols); e != nil {
  276. err = errors.New("更新PPT关联报告失败, Err: " + e.Error())
  277. return
  278. }
  279. }
  280. return
  281. }
  282. // saveReportPptImg ppt转报告后,需要再次将ppt转图片
  283. func saveReportPptImg(pptId, reportId int, pptUrl string) {
  284. var err error
  285. defer func() {
  286. if err != nil {
  287. utils.FileLog.Info(fmt.Sprintf("将ppt转图片失败, saveReportPptImg Err:%s", err.Error()))
  288. go alarm_msg.SendAlarmMsg("将ppt转图片失败, saveReportPptImg Err: "+err.Error(), 3)
  289. }
  290. }()
  291. // 更新报告内容
  292. report, e := models.GetReportByReportId(reportId)
  293. if e != nil && e.Error() != utils.ErrNoRow() {
  294. err = errors.New("获取报告失败, Err: " + e.Error())
  295. return
  296. }
  297. if report == nil {
  298. return
  299. }
  300. // 获取ppt转图片的结果
  301. list, err := ppt2img.Ppt2Img(pptUrl)
  302. if err != nil {
  303. return
  304. }
  305. reportPptImgList := make([]*models.ReportPptImg, 0)
  306. for _, v := range list {
  307. reportPptImg := &models.ReportPptImg{
  308. //ReportPptImgId: 0,
  309. PptId: pptId,
  310. ReportId: reportId,
  311. ReportChapterId: 0,
  312. ImgUrl: v,
  313. CreateTime: time.Now(),
  314. }
  315. reportPptImgList = append(reportPptImgList, reportPptImg)
  316. }
  317. //批量添加Ppt转报告的图片记录
  318. err = models.AddAndEditMultiReportPptImg(pptId, reportPptImgList)
  319. return
  320. }
  321. // SaveEnglishPPTReport 保存英文PPT报告
  322. func SaveEnglishPPTReport(pptId, classifyIdFirst, classifyIdSecond int, title, abstract string, adminInfo *system.Admin) (reportId int, reportCode, errMsg string, err error) {
  323. defer func() {
  324. if err != nil {
  325. utils.FileLog.Info("%s", err.Error())
  326. go alarm_msg.SendAlarmMsg("PPT转报告失败, SavePPTReport Msg: "+errMsg+", Err: "+err.Error(), 3)
  327. }
  328. }()
  329. if pptId == 0 {
  330. errMsg = "参数有误"
  331. err = errors.New("参数有误")
  332. return
  333. }
  334. item, e := ppt_english.GetPptEnglishById(pptId)
  335. if e != nil {
  336. errMsg = "获取PPT失败"
  337. err = errors.New("获取PPT失败, Err: " + e.Error())
  338. return
  339. }
  340. // PPT内容转HTML
  341. htm, e := pptContent2Html(item.Content, true)
  342. if e != nil {
  343. errMsg = "转换失败"
  344. err = e
  345. return
  346. }
  347. // 2023-02-21 PPT可多次转为报告, 不做关联
  348. if title == "" {
  349. errMsg = "标题不能为空"
  350. err = errors.New("标题不能为空")
  351. return
  352. }
  353. if classifyIdFirst <= 0 {
  354. errMsg = "请选择报告分类"
  355. err = errors.New("报告分类不能为空")
  356. return
  357. }
  358. // 分类
  359. classifyList, e := models.GetAllEnglishClassify()
  360. if e != nil {
  361. errMsg = "转换失败"
  362. err = errors.New("获取分类列表失败, Err: " + e.Error())
  363. return
  364. }
  365. classifyMap := make(map[int]string, 0)
  366. for _, v := range classifyList {
  367. classifyMap[v.Id] = v.ClassifyName
  368. }
  369. classifyNameFirst := classifyMap[classifyIdFirst]
  370. classifyNameSecond := classifyMap[classifyIdSecond]
  371. // 新增报告
  372. nowTime := time.Now().Local()
  373. reportReq := &models.AddEnglishReportReq{
  374. AddType: 1,
  375. ClassifyIdFirst: classifyIdFirst,
  376. ClassifyNameFirst: classifyNameFirst,
  377. ClassifyIdSecond: classifyIdSecond,
  378. ClassifyNameSecond: classifyNameSecond,
  379. Title: title,
  380. Abstract: abstract,
  381. Author: "Horizon Insights FICC Team",
  382. Frequency: utils.ReportFrequencyDefault,
  383. State: 1,
  384. Content: htm,
  385. CreateTime: nowTime.Format(utils.FormatDateTime),
  386. }
  387. newReportId, newCode, e := CreateNewEnglishReport(*reportReq, adminInfo)
  388. if e != nil {
  389. errMsg = "转换失败"
  390. err = errors.New("新增报告失败, Err: " + e.Error())
  391. return
  392. }
  393. reportId = int(newReportId)
  394. reportCode = newCode
  395. return
  396. }
  397. // UpdatePptEditing 更新PPT编辑状态
  398. func UpdatePptEditing(pptId, status, userId int, userName string, isEn bool) (ret ppt_english.PPTEditingCache, err error) {
  399. if pptId <= 0 {
  400. return
  401. }
  402. cacheKey := ""
  403. if isEn {
  404. cacheKey = fmt.Sprint(utils.CACHE_EN_PPT_EDITING, pptId)
  405. } else {
  406. cacheKey = fmt.Sprint(utils.CACHE_PPT_EDITING, pptId)
  407. }
  408. // 完成编辑
  409. if status == 2 {
  410. _ = utils.Rc.Delete(cacheKey)
  411. return
  412. }
  413. // 读取缓存中的结果
  414. var editor ppt_english.PPTEditingCache
  415. strCache, _ := utils.Rc.RedisString(cacheKey)
  416. fmt.Println(strCache)
  417. if strCache != "" {
  418. e := json.Unmarshal([]byte(strCache), &editor)
  419. if e != nil {
  420. err = fmt.Errorf("解析缓存内容失败: %s", e.Error())
  421. return
  422. }
  423. }
  424. // 标记编辑中
  425. if status == 1 {
  426. // 无人编辑, 写入缓存
  427. if !editor.IsEditing {
  428. ret.IsEditing = true
  429. ret.AdminId = userId
  430. ret.Editor = userName
  431. ret.Tips = fmt.Sprintf("当前%s正在编辑PPT", userName)
  432. b, _ := json.Marshal(ret)
  433. utils.Rc.SetNX(cacheKey, string(b), utils.ReportPptEditingWait*time.Second)
  434. return
  435. }
  436. // 有人编辑
  437. if editor.IsEditing {
  438. // 编辑用户与当前用户不一致, 返回编辑用户, 一致则更新缓存
  439. if userId == editor.AdminId {
  440. b, _ := json.Marshal(editor)
  441. utils.Rc.Do("SETEX", cacheKey, int64(utils.ReportPptEditingWait), string(b))
  442. }
  443. ret = editor
  444. return
  445. }
  446. } else {
  447. // 默认查询
  448. ret = editor
  449. }
  450. return
  451. }
  452. // handlerPptToReportTableReferenced
  453. // @Description: ppt转报告后,需要同时继承原来ppt中表格的拖动数据逻辑
  454. // @author: Roc
  455. // @datetime 2025-01-09 16:51:11
  456. // @param pptId int
  457. // @param reportId int
  458. func handlerPptToReportTableReferenced(pptId, reportId int) {
  459. var err error
  460. defer func() {
  461. if err != nil {
  462. utils.FileLog.Error("ppt转报告后,报告与动态表格关联处理失败,PPT的ID:%d,新报告ID:%d,Err:%s", pptId, reportId, err.Error())
  463. }
  464. }()
  465. reportInfo, err := models.GetReportByReportId(reportId)
  466. if err != nil {
  467. return
  468. }
  469. addList, err := excel.CopyReferencedExcelConfigByReferencedIdAndFromScene(pptId, utils.TableReferencedByPPT, reportInfo.Id, utils.TableReferencedByReport, reportInfo.AdminId, reportInfo.AdminRealName)
  470. if err != nil {
  471. return
  472. }
  473. if len(addList) > 0 {
  474. // 修改内容
  475. reportInfo.Content = HandleReportContentTable(reportInfo.Id, reportInfo.Content)
  476. reportInfo.ContentStruct = HandleReportContentStructTable(reportInfo.Id, reportInfo.ContentStruct)
  477. err = reportInfo.Update([]string{"Content", "ContentStruct"})
  478. if err != nil {
  479. return
  480. }
  481. if reportInfo.HasChapter == 1 {
  482. chapterList, tmpErr := models.GetChapterListByReportId(reportInfo.Id)
  483. if tmpErr != nil {
  484. err = tmpErr
  485. return
  486. }
  487. for _, v := range chapterList {
  488. v.Content = HandleReportContentTable(reportInfo.Id, v.Content)
  489. v.ContentStruct = HandleReportContentStructTable(reportInfo.Id, v.ContentStruct)
  490. err = v.Update([]string{"Content", "ContentStruct"})
  491. if err != nil {
  492. return
  493. }
  494. }
  495. }
  496. }
  497. }
  498. // HandlerPptToEnPptTableReferenced
  499. // @Description: ppt转英文ppt后,需要同时继承原来ppt中表格的拖动数据逻辑
  500. // @author: Roc
  501. // @datetime 2025-01-09 16:51:03
  502. // @param pptId int
  503. // @param enPptId int
  504. // @param sysUserId int
  505. // @param sysUserName string
  506. func HandlerPptToEnPptTableReferenced(pptId, enPptId, sysUserId int, sysUserName string) {
  507. var err error
  508. defer func() {
  509. if err != nil {
  510. utils.FileLog.Error("ppt转英文PPT后,英文PPT与动态表格关联处理失败,PPT的ID:%d,英文ppt的ID:%d,Err:%s", pptId, enPptId, err.Error())
  511. }
  512. }()
  513. _, err = excel.CopyReferencedExcelConfigByReferencedIdAndFromScene(pptId, utils.TableReferencedByPPT, enPptId, utils.TableReferencedByEnPPT, sysUserId, sysUserName)
  514. if err != nil {
  515. return
  516. }
  517. }
  518. // handlerPptToEnReportTableReferenced
  519. // @Description: 英文ppt转报告后,需要同时继承原来英文ppt中表格的拖动数据逻辑
  520. // @author: Roc
  521. // @datetime 2025-01-09 17:04:49
  522. // @param enPptId int
  523. // @param enReportId int
  524. func handlerPptToEnReportTableReferenced(enPptId, enReportId int) {
  525. var err error
  526. defer func() {
  527. if err != nil {
  528. utils.FileLog.Error("ppt转英文报告后,英文报告与动态表格关联处理失败,PPT的ID:%d,英文报告ID:%d,Err:%s", enPptId, enReportId, err.Error())
  529. }
  530. }()
  531. reportInfo, err := models.GetEnglishReportItemById(enReportId)
  532. if err != nil {
  533. return
  534. }
  535. addList, err := excel.CopyReferencedExcelConfigByReferencedIdAndFromScene(enPptId, utils.TableReferencedByEnPPT, enReportId, utils.TableReferencedByEnReport, reportInfo.AdminId, reportInfo.AdminRealName)
  536. if err != nil {
  537. return
  538. }
  539. if len(addList) > 0 {
  540. // 修改内容
  541. reportInfo.Content = HandleReportContentTable(reportInfo.Id, reportInfo.Content)
  542. err = reportInfo.Update([]string{"Content"})
  543. if err != nil {
  544. return
  545. }
  546. }
  547. }