ppt.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. package services
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "eta_gn/eta_api/models"
  6. "eta_gn/eta_api/models/data_manage/excel"
  7. "eta_gn/eta_api/models/system"
  8. "eta_gn/eta_api/services/alarm_msg"
  9. "eta_gn/eta_api/services/ppt2img"
  10. "eta_gn/eta_api/utils"
  11. "fmt"
  12. "html"
  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. Uid string `json:"uid"`
  39. }
  40. // SavePPTReport 保存PPT报告
  41. func SavePPTReport(pptId, classifyId int, title string, adminInfo *system.Admin) (reportId int, reportCode, errMsg string, err error) {
  42. defer func() {
  43. if err != nil {
  44. utils.FileLog.Info("%s", err.Error())
  45. go alarm_msg.SendAlarmMsg("PPT转报告失败, SavePPTReport Msg: "+errMsg+", Err: "+err.Error(), 3)
  46. }
  47. }()
  48. if pptId == 0 {
  49. errMsg = "参数有误"
  50. err = errors.New("参数有误")
  51. return
  52. }
  53. item, e := models.GetPptV2ById(pptId)
  54. if e != nil {
  55. errMsg = "获取PPT失败"
  56. err = errors.New("获取PPT失败, Err: " + e.Error())
  57. return
  58. }
  59. // PPT内容转HTML
  60. htm, e := pptContent2Html(item.Content, false)
  61. if e != nil {
  62. errMsg = "转换失败"
  63. err = e
  64. return
  65. }
  66. // 2023-02-21 PPT可多次转为报告, 不做关联
  67. if classifyId == 0 {
  68. errMsg = "请选择报告类型"
  69. err = errors.New("请选择报告类型")
  70. return
  71. }
  72. if title == "" {
  73. errMsg = "标题不能为空"
  74. err = errors.New("标题不能为空")
  75. return
  76. }
  77. // 获取分类及父级分类
  78. classifyList, e := models.GetAllClassify()
  79. if e != nil {
  80. errMsg = "转换失败"
  81. err = errors.New("获取分类列表失败, Err: " + e.Error())
  82. return
  83. }
  84. classifyMap := make(map[int]*models.Classify, 0)
  85. for _, v := range classifyList {
  86. classifyMap[v.Id] = v
  87. }
  88. classifyIdFirst := 0
  89. classifyIdSecond := 0
  90. classifyIdThird := 0
  91. classifyNameFirst := ""
  92. classifyNameSecond := ""
  93. classifyNameThird := ""
  94. // 最小单元分类,第二级别的分类 ,最大的分类
  95. var baseClassify, twoClassify, threeClassify *models.Classify
  96. var hasTwo, hasThird bool
  97. baseClassify, ok := classifyMap[classifyId]
  98. if !ok {
  99. errMsg = "分类异常"
  100. err = errors.New("获取分类失败 ")
  101. return
  102. }
  103. twoClassify, hasTwo = classifyMap[baseClassify.ParentId]
  104. if hasTwo {
  105. threeClassify, hasThird = classifyMap[twoClassify.ParentId]
  106. }
  107. if hasThird { // 如果确实是有三级分类
  108. classifyIdFirst = threeClassify.Id
  109. classifyNameFirst = threeClassify.ClassifyName
  110. classifyIdSecond = twoClassify.Id
  111. classifyNameSecond = twoClassify.ClassifyName
  112. classifyIdThird = baseClassify.Id
  113. classifyNameThird = baseClassify.ClassifyName
  114. } else if hasTwo {
  115. classifyIdFirst = twoClassify.Id
  116. classifyNameFirst = twoClassify.ClassifyName
  117. classifyIdSecond = baseClassify.Id
  118. classifyNameSecond = baseClassify.ClassifyName
  119. } else {
  120. classifyIdFirst = baseClassify.Id
  121. classifyNameFirst = baseClassify.ClassifyName
  122. }
  123. // 新增报告
  124. nowTime := time.Now().Local()
  125. reportReq := &models.AddReq{
  126. AddType: 1,
  127. ClassifyIdFirst: classifyIdFirst,
  128. ClassifyNameFirst: classifyNameFirst,
  129. ClassifyIdSecond: classifyIdSecond,
  130. ClassifyNameSecond: classifyNameSecond,
  131. ClassifyIdThird: classifyIdThird,
  132. ClassifyNameThird: classifyNameThird,
  133. Title: title,
  134. Abstract: "",
  135. Author: "",
  136. Frequency: utils.ReportFrequencyDefault,
  137. State: 1,
  138. Content: htm,
  139. CreateTime: nowTime.Format(utils.FormatDateTime),
  140. ReportVersion: 2,
  141. CollaborateType: 1, // 协作方式,1:个人,2:多人协作。默认:1
  142. ReportLayout: 1, // 报告布局,1:常规布局,2:智能布局。默认:1
  143. IsPublicPublish: 2, // 是否公开发布,1:是,2:否
  144. }
  145. // 如果PPT是公开的,则报告也公开发布
  146. if item.IsShare == 1 {
  147. reportReq.IsPublicPublish = 1
  148. }
  149. newReportInfo, _, e := CreateNewReport(*reportReq, adminInfo)
  150. if e != nil {
  151. errMsg = "转换失败"
  152. err = errors.New("新增报告失败, Err: " + e.Error())
  153. return
  154. }
  155. reportId = newReportInfo.Id
  156. reportCode = newReportInfo.ReportCode
  157. // ppt转报告后,将ppt的表格关系做处理
  158. handlerPptToReportTableReferenced(pptId, reportId)
  159. // 更新报告中的ppt图片
  160. go saveReportPptImg(pptId, reportId, item.PptxUrl)
  161. return
  162. }
  163. // PPT2内容转HTML
  164. func pptContent2Html(content string, isEnglish bool) (htm string, err error) {
  165. contents := make([]PPTContent, 0)
  166. if e := json.Unmarshal([]byte(content), &contents); e != nil {
  167. err = errors.New("PPT内容转换失败")
  168. return
  169. }
  170. pageLen := len(contents)
  171. htmlContent := ``
  172. // iframe图表/表格域名
  173. // 获取基础配置, 若未配置则直接返回
  174. // 获取配置好的短信模版
  175. smsCond := ` AND conf_key = ? `
  176. smsPars := make([]interface{}, 0)
  177. smsPars = append(smsPars, "ChartViewUrl")
  178. conf := new(models.BusinessConf)
  179. conf, e := conf.GetItemByCondition(smsCond, smsPars)
  180. if e != nil {
  181. if utils.IsErrNoRow(e) {
  182. err = fmt.Errorf("请先配置公共图库的地址")
  183. return
  184. }
  185. err = fmt.Errorf("获取聚合短信配置信息失败, Err: %s", e.Error())
  186. return
  187. }
  188. if conf.ConfVal == "" {
  189. err = fmt.Errorf("请先配置公共图库的地址")
  190. return
  191. }
  192. chartRoot := conf.ConfVal
  193. if pageLen > 0 {
  194. htmlPrefix := `<p style="text-align: left; margin-top: 10px; font-size: 16px;">`
  195. htmlSuffix := `</p>`
  196. htmlBr := `<br>`
  197. for i := 0; i < pageLen; i++ {
  198. // 每页标题加粗居中
  199. title := contents[i].Title
  200. if title != "" {
  201. htmlContent += `<p style="font-size: 16px; text-align: left;"><strong>`
  202. htmlContent += title
  203. htmlContent += `</strong></p>`
  204. }
  205. ele := contents[i].Elements
  206. // 每页元素按照Position升序排序
  207. sort.Slice(ele, func(k, j int) bool {
  208. return ele[k].Position < ele[j].Position
  209. })
  210. for _, v := range ele {
  211. // 根据不同的Type拼接不同的内容
  212. htmlContent += htmlPrefix
  213. switch v.Type {
  214. case ElementsTypeText:
  215. htmlContent += v.RichContent
  216. case ElementsTypeImage:
  217. htmlContent += fmt.Sprint(`<img src="`, v.Src, `" class="fr-fic fr-dib fr-draggable">`)
  218. case ElementsTypeChart:
  219. if isEnglish {
  220. // 英文研报图表src多加一个fromPage=en, 表格暂时没有区分
  221. htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s&fromPage=en&uid=%s" width="100%%" height="350" style="border-width:0px; min-height:350px;"></iframe>`, chartRoot, v.ChartId, v.Uid)
  222. break
  223. }
  224. htmlContent += fmt.Sprintf(`<iframe src="%s/chartshow?code=%s&uid=%s" width="100%%" height="350" style="border-width:0px; min-height:350px;" class="iframe%s"></iframe>`, chartRoot, v.ChartId, v.Uid, v.Uid)
  225. case ElementsTypeSheet:
  226. htmlContent += fmt.Sprintf(`<iframe src="%s/sheetshow?code=%s&uid=%s" class="iframe%s" width="100%%" height="%s" style="border-width:0px;"></iframe>`, chartRoot, v.SheetId, v.Uid, v.Uid, v.SheetHeight)
  227. }
  228. htmlContent += htmlSuffix
  229. }
  230. // 每页中间插入一个换行符, 最后一页不插入
  231. currentPage := i + 1
  232. if currentPage != pageLen {
  233. htmlContent += htmlPrefix + htmlBr + htmlSuffix
  234. }
  235. }
  236. }
  237. htm = htmlContent
  238. return
  239. }
  240. // ResetPPTReport 重置PPT关联的报告(如删除关联的报告后)
  241. func ResetPPTReport(reportId int, isEnglish bool) (err error) {
  242. defer func() {
  243. if err != nil {
  244. utils.FileLog.Info("%s", err.Error())
  245. go alarm_msg.SendAlarmMsg("重置PPT关联报告失败, ResetPPTReport Err: "+err.Error(), 3)
  246. }
  247. }()
  248. // 中文报告
  249. item, e := models.GetPptV2ByReportId(reportId)
  250. if e != nil && !utils.IsErrNoRow(e) {
  251. err = errors.New("获取PPT失败, Err: " + e.Error())
  252. return
  253. }
  254. if item != nil {
  255. updateCols := []string{"ReportId", "ReportCode"}
  256. item.ReportId = 0
  257. item.ReportCode = ""
  258. if e = item.Update(updateCols); e != nil {
  259. err = errors.New("更新PPT关联报告失败, Err: " + e.Error())
  260. return
  261. }
  262. }
  263. return
  264. }
  265. // saveReportPptImg ppt转报告后,需要再次将ppt转图片
  266. func saveReportPptImg(pptId, reportId int, pptUrl string) {
  267. var err error
  268. defer func() {
  269. if err != nil {
  270. utils.FileLog.Info(fmt.Sprintf("将ppt转图片失败, saveReportPptImg Err:%s", err.Error()))
  271. go alarm_msg.SendAlarmMsg("将ppt转图片失败, saveReportPptImg Err: "+err.Error(), 3)
  272. }
  273. }()
  274. // 更新报告内容
  275. report, e := models.GetReportByReportId(reportId)
  276. if e != nil && !utils.IsErrNoRow(e) {
  277. err = errors.New("获取报告失败, Err: " + e.Error())
  278. return
  279. }
  280. if report == nil {
  281. return
  282. }
  283. // 获取ppt转图片的结果
  284. list, err := ppt2img.Ppt2Img(pptUrl)
  285. if err != nil {
  286. return
  287. }
  288. reportPptImgList := make([]*models.ReportPptImg, 0)
  289. for _, v := range list {
  290. reportPptImg := &models.ReportPptImg{
  291. //ReportPptImgId: 0,
  292. PptId: pptId,
  293. ReportId: reportId,
  294. ReportChapterId: 0,
  295. ImgUrl: v,
  296. CreateTime: time.Now(),
  297. }
  298. reportPptImgList = append(reportPptImgList, reportPptImg)
  299. }
  300. //批量添加Ppt转报告的图片记录
  301. err = models.AddAndEditMultiReportPptImg(pptId, reportPptImgList)
  302. return
  303. }
  304. // UpdatePptEditing 更新PPT编辑状态
  305. func UpdatePptEditing(pptId, status, userId int, userName string, isEn bool) (ret models.PPTEditingCache, err error) {
  306. if pptId <= 0 {
  307. return
  308. }
  309. cacheKey := ""
  310. if isEn {
  311. cacheKey = fmt.Sprint(utils.CACHE_EN_PPT_EDITING, pptId)
  312. } else {
  313. cacheKey = fmt.Sprint(utils.CACHE_PPT_EDITING, pptId)
  314. }
  315. // 完成编辑
  316. if status == 2 {
  317. _ = utils.Rc.Delete(cacheKey)
  318. return
  319. }
  320. // 读取缓存中的结果
  321. var editor models.PPTEditingCache
  322. strCache, _ := utils.Rc.RedisString(cacheKey)
  323. fmt.Println(strCache)
  324. if strCache != "" {
  325. e := json.Unmarshal([]byte(strCache), &editor)
  326. if e != nil {
  327. err = fmt.Errorf("解析缓存内容失败: %s", e.Error())
  328. return
  329. }
  330. }
  331. // 标记编辑中
  332. if status == 1 {
  333. // 无人编辑, 写入缓存
  334. if !editor.IsEditing {
  335. ret.IsEditing = true
  336. ret.AdminId = userId
  337. ret.Editor = userName
  338. ret.Tips = fmt.Sprintf("当前%s正在编辑PPT", userName)
  339. b, _ := json.Marshal(ret)
  340. utils.Rc.SetNX(cacheKey, string(b), utils.ReportPptEditingWait*time.Second)
  341. return
  342. }
  343. // 有人编辑
  344. if editor.IsEditing {
  345. // 编辑用户与当前用户不一致, 返回编辑用户, 一致则更新缓存
  346. if userId == editor.AdminId {
  347. b, _ := json.Marshal(editor)
  348. utils.Rc.Do("SETEX", cacheKey, int64(utils.ReportPptEditingWait), string(b))
  349. }
  350. ret = editor
  351. return
  352. }
  353. } else {
  354. // 默认查询
  355. ret = editor
  356. }
  357. return
  358. }
  359. // handlerPptToReportTableReferenced
  360. // @Description: ppt转报告后,需要同时继承原来ppt中表格的拖动数据逻辑
  361. // @author: Roc
  362. // @datetime 2025-01-09 16:51:11
  363. // @param pptId int
  364. // @param reportId int
  365. func handlerPptToReportTableReferenced(pptId, reportId int) {
  366. var err error
  367. defer func() {
  368. if err != nil {
  369. utils.FileLog.Error("ppt转报告后,报告与动态表格关联处理失败,PPT的ID:%d,新报告ID:%d,Err:%s", pptId, reportId, err.Error())
  370. }
  371. }()
  372. reportInfo, err := models.GetReportByReportId(reportId)
  373. if err != nil {
  374. return
  375. }
  376. newFromScene := utils.TableReferencedByReport
  377. addList, err := excel.CopyReferencedExcelConfigByReferencedIdAndFromScene(pptId, utils.TableReferencedByPPT, reportInfo.Id, newFromScene, reportInfo.AdminId, reportInfo.AdminRealName)
  378. if err != nil {
  379. return
  380. }
  381. if len(addList) > 0 {
  382. // 修改内容
  383. content := HandleReportContentTableAndScene(reportInfo.Id, newFromScene, html.UnescapeString(reportInfo.Content))
  384. reportInfo.Content = html.EscapeString(content)
  385. reportInfo.ContentStruct = HandleReportContentStructTableAndScene(reportInfo.Id, newFromScene, reportInfo.ContentStruct)
  386. err = reportInfo.Update([]string{"Content", "ContentStruct"})
  387. if err != nil {
  388. return
  389. }
  390. if reportInfo.HasChapter == 1 {
  391. chapterList, tmpErr := models.GetChapterListByReportId(reportInfo.Id)
  392. if tmpErr != nil {
  393. err = tmpErr
  394. return
  395. }
  396. for _, v := range chapterList {
  397. chapterContent := HandleReportContentTableAndScene(reportInfo.Id, newFromScene, html.UnescapeString(v.Content))
  398. v.Content = html.EscapeString(chapterContent)
  399. v.ContentStruct = HandleReportContentStructTableAndScene(reportInfo.Id, newFromScene, v.ContentStruct)
  400. err = v.Update([]string{"Content", "ContentStruct"})
  401. if err != nil {
  402. return
  403. }
  404. }
  405. }
  406. }
  407. }
  408. // HandlerMergePptTableReferenced
  409. // @Description: 多ppt合并生成新的ppt,需要同时继承原来所有ppt中表格的拖动数据逻辑
  410. // @author: Roc
  411. // @datetime 2025-01-09 16:51:03
  412. // @param pptId int
  413. // @param enPptId int
  414. // @param sysUserId int
  415. // @param sysUserName string
  416. // @param fromScene int
  417. func HandlerMergePptTableReferenced(oldPptIdList []int, pptId, sysUserId int, sysUserName string, fromScene int) {
  418. var err error
  419. defer func() {
  420. if err != nil {
  421. utils.FileLog.Error("ppt转英文PPT后,英文PPT与动态表格关联处理失败,待合并的PPT的ID:%v,英文ppt的ID:%d,Err:%s", oldPptIdList, pptId, err.Error())
  422. }
  423. }()
  424. _, err = excel.CopyReferencedExcelConfigByReferencedIdListAndFromScene(oldPptIdList, fromScene, pptId, fromScene, sysUserId, sysUserName)
  425. if err != nil {
  426. return
  427. }
  428. }