AddReport.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. <script setup>
  2. import {ref,onMounted,onUnmounted} from 'vue'
  3. import {useInitFroalaEditor} from '@/hooks/useFroalaEditor'
  4. import EditReportBaseInfo from './components/EditReportBaseInfo.vue'
  5. import ReportInsertContent from './components/reportInsert/Index.vue'
  6. import ReportPublishTimeSet from './components/ReportPublishTimeSet.vue'
  7. import apiReport from '@/api/report'
  8. import apiChart from '@/api/chart'
  9. import {getSystemInfo} from '@/api/common'
  10. import moment from 'moment'
  11. import { showToast,showDialog } from 'vant'
  12. import { useRouter } from 'vue-router'
  13. import {useCachedViewsStore} from '@/store/modules/cachedViews'
  14. import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
  15. import {usePublicSettingStore} from '@/store/modules/publicSetting'
  16. import {useReportApprove} from '@/hooks/useReportApprove'
  17. import {Base64} from 'js-base64'
  18. const cachedViewsStore=useCachedViewsStore()
  19. const publicSettingStore = usePublicSettingStore()
  20. const {isApprove,hasApproveFlow,getEtaConfig,checkClassifyNameArr} = useReportApprove()
  21. const router=useRouter()
  22. const {checkAuthBtn} = useAuthBtn()
  23. const {lastFocusPosition,initFroalaEditor}=useInitFroalaEditor()
  24. let reportContentEditorIns=null//报告内容编辑器实例
  25. let reportId=0//报告id
  26. let reportCode=0//报告id
  27. // 水印
  28. const waterMarkStr=ref('')
  29. onMounted(() => {
  30. const el=document.getElementById('editor')
  31. reportContentEditorIns=initFroalaEditor('#editor',{height:el.offsetHeight-150})
  32. getSystemInfoFun()
  33. })
  34. // 报告基本内容
  35. const showReportBaseInfo=ref(false)
  36. let reportBaseInfoData={
  37. addType:1,
  38. classifyName:[],
  39. author:['FICC团队'],
  40. frequency: ['日度'],
  41. createtime:moment().format('YYYY-MM-DD'),
  42. title:'',
  43. abstract:''
  44. }
  45. async function handleReportBaseInfoChange(e){
  46. reportBaseInfoData=e
  47. // 继承报告 覆盖一次
  48. if(e.addType===2&&e.classifyName.length===2){
  49. const res=await apiReport.reportDetailByClassifyId({
  50. ClassifyIdFirst:e.classifyName[0].id,
  51. ClassifyIdSecond:e.classifyName[1].id
  52. })
  53. if(res.Ret===200){
  54. if(res.Data===null){
  55. showToast('此分类暂无报告')
  56. }else{
  57. reportBaseInfoData.author=res.Data.Author ? res.Data.Author.split(',') : ['FICC团队']
  58. reportBaseInfoData.frequency=[res.Data.Frequency]
  59. reportBaseInfoData.createtime=moment().format('YYYY-MM-DD')
  60. reportBaseInfoData.title=res.Data.Title
  61. reportBaseInfoData.abstract=res.Data.Abstract
  62. reportContentEditorIns.html.set(res.Data.Content);
  63. }
  64. }
  65. }
  66. const classify = e.classifyName.map(i=>i.id)
  67. checkClassifyNameArr(1,classify)
  68. showReportBaseInfo.value=false
  69. }
  70. // 报告插入数据弹窗
  71. const showReportInsertPop=ref(false)
  72. /**
  73. * list:[UniqueCode] 图表code 如果是以 isETAForumChart_ 开头则说明是社区图表
  74. * type:iframe/img 插入的为iframe或者图片
  75. * chartType: chart-图表,sheet-表格
  76. */
  77. function handleInsert({list,type,chartType}){
  78. reportContentEditorIns.events.focus()
  79. if(lastFocusPosition.value){
  80. reportContentEditorIns.selection.get().removeAllRanges()
  81. reportContentEditorIns.selection.get().addRange(lastFocusPosition.value)
  82. }
  83. if(type==='iframe'){
  84. let link=publicSettingStore.publicSetting.ChartViewUrl;
  85. if(chartType==='chart'){
  86. // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
  87. link=link+'/chartshow'
  88. list.forEach(item => {
  89. const isETAForumChart=item.startsWith('isETAForumChart_')?true:false
  90. reportContentEditorIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
  91. <iframe src='${link}?code=${isETAForumChart?item.replace(/^isETAForumChart_/, ''):item}&fromPage=&isETAForumChart=${isETAForumChart}' width='100%' height='350' style='border-width:0px; min-height:350px;'></iframe>
  92. </p>`)
  93. });
  94. }else if(chartType==='sheet'){
  95. // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
  96. link=link+'/sheetshow'
  97. list.forEach(item => {
  98. reportContentEditorIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
  99. <iframe src='${link}?code=${item}' class='iframe${item}' width='100%' style='border-width:0px;'></iframe>
  100. </p>`)
  101. });
  102. }
  103. }else if(type==='img'){
  104. list.forEach(item=>{
  105. reportContentEditorIns.html.insert(`<img style='width:100%' src='${item}' />`)
  106. })
  107. }
  108. showReportInsertPop.value=false
  109. }
  110. // 更新sheet表格高度
  111. function reInitSheetIframe(e){
  112. const { height,code } = e.data;
  113. let iframeDom = document.getElementsByClassName(`iframe${code}`)
  114. Array.prototype.forEach.call(iframeDom, function (ele) {
  115. ele.height = `${height+45}px`;
  116. });
  117. }
  118. onMounted(()=>{
  119. getEtaConfig()
  120. window.addEventListener('message',reInitSheetIframe)
  121. })
  122. onUnmounted(()=>{
  123. window.removeEventListener('message',reInitSheetIframe)
  124. })
  125. // 刷新所有图表
  126. async function handleRefreshAllChart(){
  127. let code_arr = [];
  128. $('iframe').each((k,i) => {
  129. try {
  130. let href = $(i).attr('src');
  131. code_arr.push(href.slice(href.indexOf('code=') + 5));
  132. } catch (err) {
  133. }
  134. });
  135. if(!code_arr.length) return showToast('请插入图表');
  136. const res=await apiChart.refreshChartMultiple({ChartInfoCode:code_arr})
  137. if(res.Ret===200){
  138. $('iframe').each((k,i) => {
  139. $(i).attr('src',$(i).attr('src'))
  140. });
  141. showToast('刷新成功')
  142. }
  143. }
  144. // 发布报告-fb;保存-cg;预览-yl
  145. const showPublishPop=ref(false)
  146. async function handleReportOpt(type){
  147. if(reportBaseInfoData.classifyName.length===0){
  148. showToast('请选择报告分类')
  149. return
  150. }
  151. if(!reportBaseInfoData.title){
  152. showToast('请填写报告标题')
  153. return
  154. }
  155. //如果富文本中有未上传完成的图片,去除这个dom
  156. $('.fr-element').find('img.fr-uploading').length&&$('.fr-element').find('img.fr-uploading').remove()
  157. const params={
  158. AddType: reportBaseInfoData.addType,
  159. ClassifyIdFirst: reportBaseInfoData.classifyName[0].id,
  160. ClassifyNameFirst: reportBaseInfoData.classifyName[0].text,
  161. ClassifyIdSecond: reportBaseInfoData.classifyName[1].id,
  162. ClassifyNameSecond: reportBaseInfoData.classifyName[1].text,
  163. Title: reportBaseInfoData.title,
  164. Abstract: reportBaseInfoData.abstract,
  165. Author:reportBaseInfoData.author.join(','),
  166. Frequency: reportBaseInfoData.frequency[0],
  167. Content: $('.fr-element').html(),
  168. CreateTime: reportBaseInfoData.createtime,
  169. ReportVersion: 2,
  170. State:1
  171. }
  172. console.log(params);
  173. if(type==='yl'){
  174. sessionStorage.setItem('reportPreData',JSON.stringify(params))
  175. const routerEl=router.resolve({
  176. path:'/report/preview',
  177. query:{
  178. id:-1
  179. }
  180. })
  181. window.open(routerEl.href,'_blank')
  182. return
  183. }
  184. cachedViewsStore.removeCaches('ReportList')
  185. if(type==='cg'){
  186. // 存草稿
  187. const res=await apiReport.reportAdd(params)
  188. if(res.Ret===200){
  189. showToast('保存成功')
  190. router.replace({
  191. path:'/report/edit',
  192. query:{
  193. id:res.Data.ReportId
  194. }
  195. })
  196. }
  197. }
  198. if(type==='fb'){
  199. // 发布
  200. const hasTel=reportBaseInfoData.classifyName[1].HasTeleconference
  201. //有电话会的不提示推送客群
  202. if(hasTel==1){
  203. const res=await apiReport.reportAdd(params)
  204. if(res.Ret===200){
  205. reportPublish(res.Data.ReportId,res.Data.ReportCode)
  206. }
  207. }else{
  208. // 显示发布提示弹窗,提示推送客群
  209. //判断是否有推送权限
  210. if(!checkAuthBtn(reportManageBtn.reportManage_sendMsg)){
  211. handleConfirmPublish(1)
  212. return
  213. }
  214. showPublishPop.value=true
  215. }
  216. }
  217. if(type==='submit'){
  218. //提交
  219. handleReportSubmit(params)
  220. }
  221. if(type==='dsfb'){
  222. //定时发布
  223. const res=await apiReport.reportAdd(params)
  224. if(res.Ret===200){
  225. reportId=res.Data.ReportId
  226. reportCode=res.Data.ReportCode
  227. showDSFBTime.value=true
  228. }
  229. }
  230. }
  231. // 点击发布提示弹窗中的操作按钮
  232. async function handleConfirmPublish(e){
  233. const params={
  234. AddType: reportBaseInfoData.addType,
  235. ClassifyIdFirst: reportBaseInfoData.classifyName[0].id,
  236. ClassifyNameFirst: reportBaseInfoData.classifyName[0].text,
  237. ClassifyIdSecond: reportBaseInfoData.classifyName[1].id,
  238. ClassifyNameSecond: reportBaseInfoData.classifyName[1].text,
  239. Title: reportBaseInfoData.title,
  240. Abstract: reportBaseInfoData.abstract,
  241. Author:reportBaseInfoData.author.join(','),
  242. Frequency: reportBaseInfoData.frequency[0],
  243. Content: $('.fr-element').html(),
  244. CreateTime: reportBaseInfoData.createtime,
  245. ReportVersion: 2,
  246. State:1
  247. }
  248. const saveRes=await apiReport.reportAdd(params)
  249. if(e===1){//仅发布
  250. reportPublish(saveRes.Data.ReportId,saveRes.Data.ReportCode)
  251. }else if(e===2){
  252. const pubRes=await apiReport.reportPublish({ReportIds:saveRes.Data.ReportId.toString(),ReportUrl:generatePdfLinks(saveRes.Data.ReportCode)})
  253. if(pubRes.Ret!==200) return
  254. const msgRes=await apiReport.reportMessageSend({ReportId:saveRes.Data.ReportId})
  255. if(msgRes.Ret!==200) return
  256. router.back()
  257. }
  258. }
  259. function generatePdfLinks(Code){
  260. return `${publicSettingStore.publicSetting.ReportViewUrl}/reportshare_pdf?code=${Code}&flag=${waterMarkStr.value}`
  261. }
  262. async function reportPublish(id,code){
  263. const res=await apiReport.reportPublish({ReportIds:id.toString(),ReportUrl:generatePdfLinks(code)})
  264. if(res.Ret===200){
  265. console.log('back');
  266. router.back()
  267. }
  268. }
  269. // 定时发布报告选择时间
  270. const showDSFBTime=ref(false)
  271. function onConfirmDSFBTime(time){
  272. console.log(time);
  273. const isAuthPushMsg=checkAuthBtn(reportManageBtn.reportManage_sendMsg)
  274. console.log(isAuthPushMsg);
  275. showDialog({
  276. title: '提示',
  277. message:isAuthPushMsg?'是否发布定时报告,并推送模板消息?':'是否发布定时报告',
  278. confirmButtonText:isAuthPushMsg?'推送':'取消',
  279. cancelButtonText:isAuthPushMsg?'不推送':'确定',
  280. showCancelButton:true
  281. }).then(()=>{
  282. if(!isAuthPushMsg){
  283. apiReport.reportPublishTimeSet({
  284. ReportId:reportId,
  285. PrePublishTime:time,
  286. PreMsgSend:0,
  287. ReportUrl:generatePdfLinks(reportCode)
  288. }).then(res=>{
  289. if(res.Ret===200){
  290. showToast('定时发布成功!')
  291. setTimeout(() => {
  292. router.back()
  293. }, 1000);
  294. }
  295. })
  296. return
  297. }
  298. //推送
  299. apiReport.reportPublishTimeSet({
  300. ReportId:reportId,
  301. PrePublishTime:time,
  302. PreMsgSend:1,
  303. ReportUrl:generatePdfLinks(reportCode)
  304. }).then(res=>{
  305. if(res.Ret===200){
  306. showToast('定时发布成功!')
  307. setTimeout(() => {
  308. router.back()
  309. }, 1000);
  310. }
  311. })
  312. }).catch(()=>{
  313. if(!isAuthPushMsg) return
  314. //不推送
  315. apiReport.reportPublishTimeSet({
  316. ReportId:reportId,
  317. PrePublishTime:time,
  318. PreMsgSend:0,
  319. ReportUrl:generatePdfLinks(reportCode)
  320. }).then(res=>{
  321. if(res.Ret===200){
  322. showToast('定时发布成功!')
  323. setTimeout(() => {
  324. router.back()
  325. }, 1000);
  326. }
  327. })
  328. })
  329. }
  330. //提交
  331. async function handleReportSubmit(params){
  332. const res=await apiReport.reportAdd(params)
  333. if(res.Ret!==200) return
  334. showDialog({
  335. title: '提示',
  336. message: '是否确认提交该报告进入审批流程?',
  337. showCancelButton:true
  338. }).then(()=>{
  339. apiReport.reportCnSubmit({
  340. ReportId:Number(res.Data.ReportId)
  341. }).then(res=>{
  342. if(res.Ret!==200) return
  343. showToast('提交成功')
  344. router.back()
  345. }).catch(()=>{ //如果选择取消就和存草稿一样,转到编辑页
  346. router.replace({
  347. path:'/report/edit',
  348. query:{
  349. id:res.Data.ReportId
  350. }
  351. })
  352. })
  353. })
  354. }
  355. const getSystemInfoFun=()=>{
  356. getSystemInfo().then(res=>{
  357. if(res.Ret===200){
  358. const systemUserInfo=res.Data
  359. // 设置水印文案
  360. let waterMarkString=''
  361. if(systemUserInfo){
  362. waterMarkString=`${systemUserInfo.RealName}${systemUserInfo.Mobile?systemUserInfo.Mobile:systemUserInfo.Email}`
  363. waterMarkString=encodeURIComponent(waterMarkString)
  364. waterMarkStr.value=Base64.encode(waterMarkString)
  365. }
  366. }
  367. })
  368. }
  369. </script>
  370. <template>
  371. <div class="add-report-page">
  372. <van-cell title="基础信息" is-link @click="showReportBaseInfo=true"/>
  373. <div class="main-wrap">
  374. <div class="editor-box" id="editor"></div>
  375. </div>
  376. <!-- 底部操作 -->
  377. <div class="bot-action-box">
  378. <div class="left-box">
  379. <div class="item" @click="handleRefreshAllChart">
  380. <img src="@/assets/imgs/report/icon_refresh.png" alt="">
  381. <span>刷新</span>
  382. </div>
  383. <div class="item" @click="handleReportOpt('yl')" v-permission="reportManageBtn.reportManage_reportView">
  384. <img src="@/assets/imgs/report/icon_preview.png" alt="">
  385. <span>预览</span>
  386. </div>
  387. <div class="item" @click="handleReportOpt('cg')">
  388. <img src="@/assets/imgs/report/icon_save2.png" alt="">
  389. <span>保存</span>
  390. </div>
  391. <template v-if="!isApprove||!hasApproveFlow">
  392. <div class="item" @click="handleReportOpt('dsfb')" v-permission="reportManageBtn.reportManage_publish">
  393. <img src="@/assets/imgs/report/icon_time.png" alt="">
  394. <span>定时发布</span>
  395. </div>
  396. <div class="item" @click="handleReportOpt('fb')" v-permission="reportManageBtn.reportManage_publish">
  397. <img src="@/assets/imgs/report/icon_publish3.png" alt="">
  398. <span>发布</span>
  399. </div>
  400. </template>
  401. <template v-if="isApprove&&hasApproveFlow">
  402. <div class="item" @click="handleReportOpt('submit')" v-permission="reportManageBtn.reportManage_publish">
  403. <img src="@/assets/imgs/report/icon_publish3.png" alt="">
  404. <span>提交</span>
  405. </div>
  406. </template>
  407. </div>
  408. <div class="right-btn" @click="showReportInsertPop=true">
  409. <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
  410. <path d="M12.0499 15.9499V27.5H15.9499V15.9499H27.5V12.0499H15.9499V0.5H12.0499V12.0499H0.5V15.9499H12.0499Z" fill="white"/>
  411. </svg>
  412. </div>
  413. </div>
  414. </div>
  415. <!-- 报告基础信息 -->
  416. <van-popup
  417. v-model:show="showReportBaseInfo"
  418. position="bottom"
  419. :style="{ height: '100%' }"
  420. >
  421. <EditReportBaseInfo @close="showReportBaseInfo=false" @confirm="handleReportBaseInfoChange"/>
  422. </van-popup>
  423. <!-- 报告插入数据模块 -->
  424. <van-popup
  425. v-model:show="showReportInsertPop"
  426. position="bottom"
  427. round
  428. >
  429. <report-insert-content v-if="showReportInsertPop" @insert="handleInsert"/>
  430. </van-popup>
  431. <!-- 发布提示 -->
  432. <van-popup
  433. v-model:show="showPublishPop"
  434. >
  435. <div class="publish-report-pop-box">
  436. <div class="title">发布提示</div>
  437. <p class="tips">是否立即发布报告,并推送模板消息?</p>
  438. <div class="btns">
  439. <div class="btn blue" @click="handleConfirmPublish(2)">发布&推送</div>
  440. <div class="btn" @click="handleConfirmPublish(1)">仅发布</div>
  441. <div class="btn" @click="showPublishPop=false">取消</div>
  442. </div>
  443. </div>
  444. </van-popup>
  445. <!-- 定时发布选择时间 -->
  446. <ReportPublishTimeSet v-model="showDSFBTime" @confirm="onConfirmDSFBTime" />
  447. </template>
  448. <style lang="scss" scoped>
  449. .publish-report-pop-box{
  450. padding: 48px;
  451. .title{
  452. font-size: 36px;
  453. text-align: center;
  454. margin-bottom: 32px;
  455. }
  456. .tips{
  457. color: $font-grey;
  458. margin-bottom: 48px;
  459. }
  460. .btns{
  461. .btn{
  462. line-height: 96px;
  463. border-radius: 12px;
  464. text-align: center;
  465. font-size: 32px;
  466. font-weight: 600;
  467. margin-bottom: 24px;
  468. background-color: #F2F3FF;
  469. color: $theme-color;
  470. }
  471. .blue{
  472. background-color: $theme-color;
  473. color: #fff;
  474. }
  475. .disabled{
  476. background-color: #a8b0fc;
  477. }
  478. }
  479. }
  480. @media screen and (min-width:$media-width){
  481. .publish-report-pop-box{
  482. padding: 24px;
  483. .title{
  484. font-size: 18px;
  485. margin-bottom: 16px;
  486. }
  487. .tips{
  488. margin-bottom: 24px;
  489. }
  490. .btns{
  491. .btn{
  492. line-height: 48px;
  493. border-radius: 12px;
  494. font-size: 16px;
  495. margin-bottom: 12px;
  496. }
  497. }
  498. }
  499. }
  500. .add-report-page{
  501. height: 100dvh;
  502. min-height: 95vh;
  503. display: flex;
  504. flex-direction: column;
  505. overflow: hidden;
  506. }
  507. @media screen and (min-width:$media-width){
  508. .add-report-page{
  509. height: calc(100dvh - 60px);
  510. min-height: calc(95vh - 60px);
  511. }
  512. }
  513. .van-cell{
  514. flex-shrink: 0;
  515. }
  516. .main-wrap{
  517. flex: 1;
  518. width: calc(100% - 32PX);
  519. margin: 0 auto;
  520. margin-top: 30px;
  521. .editor-box{
  522. width: 100%;
  523. height: 100%;
  524. }
  525. }
  526. @media screen and (min-width:$media-width){
  527. .main-wrap{
  528. margin-top: 15px;
  529. }
  530. }
  531. .bot-action-box{
  532. padding: 20px 16PX;
  533. display: flex;
  534. align-items: center;
  535. .left-box{
  536. flex: 1;
  537. background: #FFFFFF;
  538. box-shadow: 0px 12px 60px 10px rgba(0, 0, 0, 0.05), 0px 32px 48px 4px rgba(0, 0, 0, 0.04), 0px 16px 20px -10px rgba(0, 0, 0, 0.08);
  539. border-radius: 100px;
  540. height: 112px;
  541. display: flex;
  542. align-items: center;
  543. margin-right: 20px;
  544. padding: 0 20px;
  545. .item{
  546. flex: 1;
  547. text-align: center;
  548. font-size: 20px;
  549. img{
  550. width: 40px;
  551. height: 40px;
  552. display: block;
  553. margin: 5px auto;
  554. }
  555. }
  556. }
  557. .right-btn{
  558. flex-shrink: 0;
  559. position: relative;
  560. width: 96px;
  561. height: 96px;
  562. background-color: $theme-color;
  563. border-radius: 50%;
  564. box-shadow: 0px 6px 28px 4px rgba(0, 0, 0, 0.05), 0px 16px 20px 2px rgba(0, 0, 0, 0.06), 0px 10px 10px -6px rgba(0, 0, 0, 0.1);
  565. svg{
  566. width: 27px;
  567. height: 27px;
  568. position: absolute;
  569. top: 50%;
  570. left: 50%;
  571. transform: translate(-50%,-50%);
  572. }
  573. }
  574. }
  575. @media screen and (min-width:$media-width){
  576. .bot-action-box{
  577. margin: 0 auto;
  578. width: 600px;
  579. padding: 10px 16px;
  580. .left-box{
  581. border-radius: 50px;
  582. height: 56px;
  583. margin-right: 10px;
  584. padding: 0 10px;
  585. .item{
  586. font-size: 12px;
  587. img{
  588. width: 20px;
  589. height: 20px;
  590. margin: 3px auto;
  591. }
  592. }
  593. }
  594. .right-btn{
  595. width: 48px;
  596. height: 48px;
  597. svg{
  598. width: 14px;
  599. height: 14px;
  600. }
  601. }
  602. }
  603. }
  604. </style>