25 Commits 9011c0f182 ... 0039a2f1f3

Autor SHA1 Mensaje Fecha
  Karsa 0039a2f1f3 Merge branch 'eta1.8' hace 2 horas
  Karsa ce5e270b8a fix bug hace 3 horas
  Karsa 1bc03a80e5 fix bug hace 2 días
  Karsa 4b240fee90 fix hace 3 días
  Karsa dabbc91738 fixbug hace 3 días
  Karsa 264e524e05 fix bug hace 3 días
  Karsa e3695a0755 fix bug hace 1 semana
  Karsa bdc8f89dee style hace 1 semana
  Karsa 5c7d235f78 需求池205:下载文件名称优化 hace 1 semana
  Karsa 7aaef69112 保存隐藏元素 hace 1 semana
  Karsa e23bb58bea insert hace 1 semana
  Karsa 68391ab7fb 处理章节报告操作 hace 2 semanas
  Karsa 11fdaeb7db 智能章节报告设置版图;报告发布提交hooks统一 hace 2 semanas
  Karsa 662bf17ed3 智能报告编辑;预览页兼容样式 hace 3 semanas
  jwyu 74a8f6356a Merge branch 'ETA1.9.7' hace 3 semanas
  Karsa 77bc3bb130 报告列表页,章节列表页调整 hace 3 semanas
  Karsa 22bf6696dd switch branch hace 4 semanas
  Karsa 373f38a3b2 checkout branch hace 4 semanas
  Karsa 8f9a3bb5a3 checkout branch hace 1 mes
  Karsa b95b449c85 switch branch hace 1 mes
  Karsa b0e530dcfc switch branch hace 1 mes
  jwyu f34c5678ce 区间分析Y轴单位显示fix hace 1 mes
  jwyu d55ffe340d 区间分析 hace 1 mes
  jwyu 4c39ae9218 区间分析 hace 1 mes
  Karsa 73e5ae85d2 commit hace 1 mes
Se han modificado 45 ficheros con 5371 adiciones y 1096 borrados
  1. 1 0
      package.json
  2. 9 0
      src/api/common.js
  3. 19 0
      src/api/intervalAnalysis.js
  4. 178 0
      src/api/report.js
  5. BIN
      src/assets/imgs/report/icon_copy.png
  6. BIN
      src/assets/imgs/report/icon_drag.png
  7. BIN
      src/assets/imgs/report/icon_info.png
  8. BIN
      src/assets/imgs/report/icon_set.png
  9. BIN
      src/assets/imgs/report/icon_wx_black.png
  10. 4 0
      src/assets/styles/common.scss
  11. 30 0
      src/assets/svg/add-comp.svg
  12. 3 3
      src/hooks/chart/render.js
  13. 11 0
      src/hooks/useAuthBtn.js
  14. 4 4
      src/hooks/useFroalaEditor.js
  15. 3 2
      src/hooks/useReportApprove.js
  16. 1 1
      src/router/index.js
  17. 18 0
      src/router/report.js
  18. 5 2
      src/views/myETA/ChartDetail.vue
  19. 6 1
      src/views/myETA/components/ChartSaveOther.vue
  20. 166 319
      src/views/report/EditReport.vue
  21. 207 203
      src/views/report/List.vue
  22. 170 50
      src/views/report/PreviewDetail.vue
  23. 27 1
      src/views/report/Search.vue
  24. 1 81
      src/views/report/chapter/Detail.vue
  25. 704 172
      src/views/report/chapter/List.vue
  26. 127 0
      src/views/report/chapter/Preview.vue
  27. 105 173
      src/views/report/chapter/components/EditChapterBaseInfo.vue
  28. 611 0
      src/views/report/components/AddReportBaseInfoV2.vue
  29. 127 0
      src/views/report/components/CooperUserSelect.vue
  30. 213 0
      src/views/report/components/InheritReportSearch.vue
  31. 35 49
      src/views/report/components/ListClassify.vue
  32. 281 0
      src/views/report/components/ReportFilter.vue
  33. 2 1
      src/views/report/components/reportInsert/Index.vue
  34. 304 0
      src/views/report/hooks/useReport.js
  35. 1458 0
      src/views/report/smartReport/EditReport.vue
  36. 18 0
      src/views/report/smartReport/components/ChartComp.vue
  37. 19 0
      src/views/report/smartReport/components/ImgComp.vue
  38. 87 0
      src/views/report/smartReport/components/ImgEditor.vue
  39. 175 0
      src/views/report/smartReport/components/ReportLayoutImg.vue
  40. 42 0
      src/views/report/smartReport/components/SheetComp.vue
  41. 18 0
      src/views/report/smartReport/components/TextComp.vue
  42. 109 0
      src/views/report/smartReport/components/TextEditor.vue
  43. 10 0
      src/views/report/smartReport/hooks/useSmartEditor.js
  44. 41 12
      src/views/reportEn/List.vue
  45. 22 22
      src/views/tabbar/Home.vue

+ 1 - 0
package.json

@@ -38,6 +38,7 @@
     "vant": "^4.6.4",
     "vconsole": "^3.15.0",
     "vue": "^3.2.47",
+    "vue-qr": "^4.0.9",
     "vue-router": "^4.1.6",
     "vue3-clipboard": "^1.0.0",
     "vue3-tree-org": "^4.2.2",

+ 9 - 0
src/api/common.js

@@ -84,4 +84,13 @@ export function apiCheckClassify(params){
  */
 export function getSystemInfo(){
     return get('/system/sysuser/detail')
+}
+
+/**
+ * 上传图片
+ * @param {*} params 
+ * @returns 
+ */
+export function uploadImgAPi(params) {
+    return post('/banner/upload',params)
 }

+ 19 - 0
src/api/intervalAnalysis.js

@@ -0,0 +1,19 @@
+import { get,post } from "./index";
+
+//区间分析
+export default{  
+  // 不含图表的所有目录
+  classifyListNoChart:params=>{
+    return get('/range_analysis/chart_classify/tree',params)
+  },
+
+  // 图表刷新
+  chartRefresh:params=>{
+    return get('/range_analysis/chart_info/refresh',params)
+  },
+  // 图表另存为
+  chartSaveOther:params=>{
+    return post('/range_analysis/chart_info/copy',params)
+  },
+  
+}

+ 178 - 0
src/api/report.js

@@ -121,6 +121,7 @@ export default {
     reportAuthorList:params=>{
         return get('/report/author',params)
     },
+    
     /**
      * 通过报告分类id获取报告内容(继承上一篇报告)
      * @param ClassifyIdFirst
@@ -240,4 +241,181 @@ export default {
     report2PdfImg(params){
         return post("/smart_report/get_pdf_url",params)
     },
+
+     // 资源库列表
+    imgReourceList:params=>{
+        return get('/smart_report/resource/list',params)
+    },
+
+    //音频上传
+    uploadAudio: params => {
+        return post('/voice/upload',params)
+    },
+
+        
+    /* v2=============================== */
+    /**
+     * 获取分类关联品种
+     * @param {*} params 
+     * @returns 
+     */
+    classifyPermissionList: params => {
+        return get('/classify/permission_list',params)
+    },
+
+    /**
+     * 获取系统用户
+     * @param {*} params 
+     * @returns 
+     */
+    getSystemUser: params => {
+        return get("/system/department/user_tree",params)
+    },
+
+    /**
+     * 获取有权限的列表 
+     * @param {
+     *  CurrentIndex PageSize KeyWord ClassifyIdFirst ClassifyIdSecond ClassifyIdThird
+    * } params 
+    * @returns 
+    */
+    getAuthReportList: params => {
+        return get('/report/list/authorized',params)
+    },
+ 
+    /**
+    * 报告基础信息
+    * @param {*ReportId} params 
+    * @returns 
+    */
+    getRportBase: params => {
+        return get('/report/detail/base',params)
+    },
+ 
+    /**
+    * 修改章节基础信息
+    * @param {*} params 
+    * @returns 
+    */
+    editChapterBase: params => {
+        return post('/report/chapter/base_info/edit',params)
+    },
+ 
+    /**
+    * 章节拖动排序
+    * @param {*ReportChapterId PrevReportChapterId NextReportChapterId} params 
+    * @returns 
+    */
+    moveChapter: params => {
+        return post('/report/chapter/move',params)
+    },
+ 
+    /**
+     * 删除章节
+     * @param {*ReportChapterId} params 
+     * @returns 
+     */
+    removeChapter: params => {
+        return post('/report/chapter/del',params)
+    },
+ 
+    /**
+     * 添加章节
+     * 
+     * @param {* Title  ReportId PermissionIdList AdminIdList} params 
+     * @returns 
+     */
+    addChapter: params => {
+        return post('/report/chapter/add',params)
+    },
+ 
+    /**
+     * 上传章节音频
+     * @param {*ReportChapterId File} params 
+     * @returns 
+     */
+    uploadChpterAudio: params => {
+        return post('/report/chapter/voice/upload',params)
+    },
+ 
+    /**
+     * 校验所有章节是否发布
+     * @param {*ReportId} params 
+     * @returns 
+     */
+    checkChaterPublishState: params => {
+        return get('/report/chapter/un_publish/list',params)
+    },
+ 
+    /**
+     * 设置版图
+     * @param {*ReportId HeadImg HeadResourceId EndImg EndResourceId CanvasColor} params 
+     * @returns 
+     */
+    setReportLayoutImg: params => {
+        return post('/report/layout_img/edit',params)
+    },
+ 
+    /**
+     * 获取分类关联的品种列表
+     * @param {*ClassifyId} params 
+     * @returns 
+     */
+    getClassifyPermissionList: params => {
+        return	get('/classify/permission/list',params)
+    },
+
+    /**
+     * 保存章节标题 
+     * @param {*ReportChapterId Title} params 
+     * @returns 
+     */
+    saveChapterTitle: params => {
+        return post('/report/chapter/title/edit',params)
+    },
+
+    /**
+     * 获取报告图表刷新状态
+     * @param {*} params 
+     * @returns 
+     */
+    getChartRefreshStatus: params => {
+        return post('/datamanage/chart_info/batch_refresh/result',params)
+    },
+
+    /**
+     * 图表刷新
+     * @param {*} params 
+     *  ChartInfoCode: code_arr,
+        PrimarSource: 'report',
+        PrimaryId: id,
+        SubId: chapterId
+     * @returns 
+     */
+    reportChartRefresh: params => {
+        return post('/datamanage/chart_info/batch_refresh',params)
+    },
+
+    /**
+     * 获取报告表格刷新状态
+     * @param {*} params 
+     * @returns 
+     */
+    getSheetRefreshResult: params => {
+        return post('/datamanage/excel_info/table/batch_refresh/result',params)
+    },
+
+    /**
+     * 表格刷新
+     * @param {*} params 
+     * ExcelCodes: sheet_code_arr,
+        Source: 'report',
+        PrimaryId: id,
+        SubId: chapterId
+     * @returns 
+     */
+    reportSheetRefresh: params => {
+        return post('/datamanage/excel_info/table/batch_refresh',params) 
+    }
+
 }

BIN
src/assets/imgs/report/icon_copy.png


BIN
src/assets/imgs/report/icon_drag.png


BIN
src/assets/imgs/report/icon_info.png


BIN
src/assets/imgs/report/icon_set.png


BIN
src/assets/imgs/report/icon_wx_black.png


+ 4 - 0
src/assets/styles/common.scss

@@ -102,6 +102,10 @@ img {
 .report-html-wrap{
     line-height: 1.8;
     font-size: 36px;
+    .report-drag-item-wrap{
+        padding: 6px;
+        margin-bottom: 3px;
+    }
     img{
         width: 100% !important;
     }

+ 30 - 0
src/assets/svg/add-comp.svg

@@ -0,0 +1,30 @@
+<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_ddd_2477_12714)">
+<rect x="16" y="13" width="96" height="96" rx="48" fill="#0052D9"/>
+<path d="M62.0499 62.9499V74.5H65.9499V62.9499H77.5V59.0499H65.9499V47.5H62.0499V59.0499H50.5V62.9499H62.0499Z" fill="white"/>
+</g>
+<defs>
+<filter id="filter0_ddd_2477_12714" x="0" y="0" width="128" height="128" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feMorphology radius="3" operator="erode" in="SourceAlpha" result="effect1_dropShadow_2477_12714"/>
+<feOffset dy="5"/>
+<feGaussianBlur stdDeviation="2.5"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2477_12714"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="effect2_dropShadow_2477_12714"/>
+<feOffset dy="8"/>
+<feGaussianBlur stdDeviation="5"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow_2477_12714" result="effect2_dropShadow_2477_12714"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="effect3_dropShadow_2477_12714"/>
+<feOffset dy="3"/>
+<feGaussianBlur stdDeviation="7"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
+<feBlend mode="normal" in2="effect2_dropShadow_2477_12714" result="effect3_dropShadow_2477_12714"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_2477_12714" result="shape"/>
+</filter>
+</defs>
+</svg>

+ 3 - 3
src/hooks/chart/render.js

@@ -108,7 +108,7 @@ export function chartRender({data,renderId,lang='zh',changeLangIsCheck,showChart
     chartData.value=data
    /*  useSelfLimit = ['/myETA/chartdetail','/chartETA/chartdetail'].includes(window.location.pathname) */
    useSelfLimit = shouldUseSelfLimit
-    if([1,11].includes(data.ChartInfo.Source)){
+    if([1,11,12].includes(data.ChartInfo.Source)){
         const chartSetMap = {
             1: setSplineOpt,
             2: setSeasonOpt,
@@ -895,8 +895,8 @@ function setSplineOpt(e){
                 maxLimit = axisLimitState[`${limitMap[item.IsAxis][1]}`]||0
             }
         }
-        const textZh = item.ConvertUnit||item.Unit
-        const textEn = item.ConvertEnUnit||item.UnitEn||item.ConvertUnit||item.Unit
+        const textZh =e.ChartInfo.Source===12?item.ConvertUnit : item.ConvertUnit||item.Unit
+        const textEn =e.ChartInfo.Source===12?item.ConvertEnUnit : item.ConvertEnUnit||item.UnitEn||item.ConvertUnit||item.Unit
         let yItem={
             ...basicYAxis,
             IsAxis:item.IsAxis,

+ 11 - 0
src/hooks/useAuthBtn.js

@@ -7,9 +7,14 @@ const authBtnStore = useAuthBtnStore()
 export const reportManageBtn = {
     reportManage_sendMsg:'reportManage:sendMsg',//推送消息/已推送消息
     reportManage_reportView:'reportManage:reportView',//研报预览:即是否能点击研报名称跳转预览页面
+    reportManage_reportView_wechartShare:'reportManage:reportView:wechartShare',//研报预览页面-微信分享
+    reportManage_reportView_copyWechat:'reportManage:reportView:copyWechat',//研报预览页面-复制链接
+    
     reportManage_reportDel:'reportManage:reportDel',//删除研报
     reportManage_reportEdit:'reportManage:reportEdit',//编辑研报
     reportManage_cancelPublish:'reportManage:cancelPublish',//取消发布
+    reportManage_audioDownload:'reportManage:audioDownload',//音频下载
+    reportManage_audioUpload:'reportManage:audioUpload',//音频上传
     reportManage_exportPdf:'reportManage:exportPdf',//下载pdf
     reportManage_exportImg:'reportManage:exportImg',//下载长图
     reportManage_publish:'reportManage:publish',//发布研报
@@ -18,6 +23,12 @@ export const reportManageBtn = {
     reportManage_reportList_sendTime:'reportManage:reportList:sendTime',//研报列表-报告推送时间
     reportManage_dayWeekReportAdd:'reportManage:dayWeekReportAdd',//添加晨报周报
     reportManage_reportAdd:'reportManage:reportAdd',//添加研报
+
+    reportManage_clearCont:'reportMange:clearCont',//一键清空内容
+    reportMange_chapter_add: 'reportMange:chapter:add',//章节添加
+    reportMange_chapter_sort: 'reportMange:chapter:sort',//章节排序
+    reportMange_chapter_share: 'reportMange:chapter:share',//章节分享
+    reportMange_chapter_editTag: 'reportMange:chapter:editTag',//章节添加标签
 }
 export const enReportManageBtn = {
     enReport_reportView:'enReport:reportView',//研报预览:即是否能点击研报名称跳转预览页面

+ 4 - 4
src/hooks/useFroalaEditor.js

@@ -5,7 +5,7 @@ export function useInitFroalaEditor() {
 	let lastFocusPosition=ref(null)//最后焦点位置
 	let imgUploadFlag=ref(true)//图片是否上传完成
 
-	const options = {
+	let options = {
 		toolbarButtons: [
 			"insertImage",
 			"insertVideo",
@@ -31,7 +31,7 @@ export function useInitFroalaEditor() {
 			"undo",
 			"redo",
 		],
-		height: 400,
+		height: 500,
 		fontSize: ["6","8","10","12", "13", "14", "15", "16", "18", "20", "24", "28", "32", "36", "40"],
 		
 		fontSizeDefaultSelection: "16",
@@ -101,8 +101,8 @@ export function useInitFroalaEditor() {
 		// })
 
 		// 方案二
-		options.height=opts?.height??500
-		options.height=options.height<350?350:options.height
+		options={...options,...opts}
+		// options.height=options.height<350?350:options.height
 		console.log(options);
 		return new FroalaEditor(el, options);
 	};

+ 3 - 2
src/hooks/useReportApprove.js

@@ -19,8 +19,9 @@ export function useReportApprove(){
     const checkClassifyNameArr = (type=1,classify=[])=>{
         let params = {
             ReportType:type,
-            ClassifyFirstId:classify[classify.length-2]||0,
-            ClassifySecondId:classify[classify.length-1]||0,
+            ClassifyFirstId:classify[0]||0,
+            ClassifySecondId:classify[1]||0,
+            ClassifyThirId:classify[2]||0,
         }
         apiCheckClassify(params).then(res=>{
             if(res.Ret!==200) return 

+ 1 - 1
src/router/index.js

@@ -85,7 +85,7 @@ const routes = [
 	},
 ];
 
-const router = createRouter({
+export const router = createRouter({
 	history: createWebHistory(import.meta.env.VITE_APP_BASE_URL),
   	routes,
 	scrollBehavior(to, from, savedPosition) {

+ 18 - 0
src/router/report.js

@@ -72,6 +72,16 @@ export const reportRoutes=[
         },
     },
     {
+        path:"/report/chapter/preview",
+        name:"ReportChapterDetail",
+        component: () => import("@/views/report/chapter/Preview.vue"),
+        meta: { 
+            title: "中文研报",
+            keepAlive:false,
+            hasBackTop:true
+        },
+    },
+    {
         path:"/report/search",
         name:"ReportSearch",
         component: () => import("@/views/report/Search.vue"),
@@ -81,4 +91,12 @@ export const reportRoutes=[
             hasBackTop:true
         },
     },
+    {
+        path:"/smart_report/edit",
+        name:"SmartReportEdit",
+        component: () => import("@/views/report/smartReport/EditReport.vue"),
+        meta: { 
+            title: "编辑研报",
+        },
+    },
 ]

+ 5 - 2
src/views/myETA/ChartDetail.vue

@@ -10,6 +10,7 @@ import apiCrossVarietyChart from '@/api/crossVarietyChart'
 import apiMyETAChart from '@/api/myETA'
 import apiDataEDB from '@/api/dataEDB'
 import apiSheet from '@/api/sheet'
+import apiIntervalAnalysis from '@/api/intervalAnalysis'
 import { useRoute, useRouter } from 'vue-router'
 import {useChartRender} from '@/hooks/chart/render'
 import {yearSelectOpt,sameOptionType} from '@/hooks/chart/config'
@@ -141,7 +142,7 @@ async function getChartInfo(){
         temEndTime.value=chartState.endTime.split('-')
     }
     //仅ETA图库图表需要初始化上下限
-    if(res.Data.ChartInfo.Source===1&&![7,10,11].includes(res.Data.ChartInfo.ChartType)){
+    if(res.Data.ChartInfo.Source===1&&![7,10,11,12].includes(res.Data.ChartInfo.ChartType)){
         isUseSelfLimit.value = true
         setLimitData(res.Data)
     }else{
@@ -562,6 +563,8 @@ async function handleChartRefresh(){
         res=await apiCrossVarietyChart.refreshChart({ ChartInfoId })
     }else if(Source===11){
         res=await apiSheet.sheetRefresh({ChartInfoId})
+    }else if(Source===12){
+        res=await apiIntervalAnalysis.chartRefresh({ChartInfoId})
     }
     if(res.Ret!==200)return
     showToast('刷新成功')
@@ -801,7 +804,7 @@ const isShowAddToMyETADialog=ref(false)
                     >{{chartState.startTime?chartState.startTime+'~'+(chartState.endTime?chartState.endTime:'至今'):'请选择时间段'}}</span>
                 </div>
                 <div class="right-action-box">
-                    <div class="item" @click="handleShowAxisLimitOpt" v-if="[1,2,5,10].includes(chartInfo.Source)&&checkAuthBtn(myETABtn.myChart_editLimit)">
+                    <div class="item" @click="handleShowAxisLimitOpt" v-if="[1,2,5,10,12].includes(chartInfo.Source)&&checkAuthBtn(myETABtn.myChart_editLimit)">
                         <img src="@/assets/imgs/myETA/icon_limit2.png" alt="">
                         <span>上下限设置</span>
                     </div>

+ 6 - 1
src/views/myETA/components/ChartSaveOther.vue

@@ -6,6 +6,7 @@ import apiFuture from '@/api/futureChart'
 import apiLineEquationChart from '@/api/lineEquationChart'
 import apiStatisticFeatureChart from '@/api/statisticFeatureChart'
 import apiCrossVarietyChart from '@/api/crossVarietyChart'
+import apiIntervalAnalysis from '@/api/intervalAnalysis'
 import { showToast } from 'vant'
 import { useWindowSize } from '@vueuse/core'
 
@@ -54,6 +55,8 @@ async function getOpts(){
         res=await apiLineEquationChart.chartClassifyList({})
     }else if([7,8,9].includes(props.source)){
         res=await apiStatisticFeatureChart.chartClassifyList({})
+    }else if(props.source==12){
+        res=await apiIntervalAnalysis.classifyListNoChart()
     }else{
         res=await apiETAChart.ETAChartClassifyList({})
     }
@@ -115,6 +118,8 @@ async function handleConfirmSave(){
         res=await apiStatisticFeatureChart.chartSaveOther(params)
     }else if(props.source===10){
         res=await apiCrossVarietyChart.saveOtherChart(params)
+    }else if(props.source===12){
+        res=await apiIntervalAnalysis.chartSaveOther(params)
     }else{
         res=await apiETAChart.ETAChartSaveOther(params)
     }
@@ -138,7 +143,7 @@ async function handleConfirmSave(){
                 <div class="title">另存为</div>
             </div>
             <div class="content">
-                <template v-if="[2,3,5,6,7,8,9].includes(source)">
+                <template v-if="[2,3,5,6,7,8,9,12].includes(source)">
                     <ul class="list-box">
                         <li 
                             :class="['item',activeIds==item.ChartClassifyId?'active':'']"

+ 166 - 319
src/views/report/EditReport.vue

@@ -1,12 +1,11 @@
 <script setup name="ReportEdit">
 import {ref,onMounted,onUnmounted} from 'vue'
 import {useInitFroalaEditor} from '@/hooks/useFroalaEditor'
-import EditReportBaseInfo from './components/EditReportBaseInfo.vue'
+// import EditReportBaseInfo from './components/EditReportBaseInfo.vue'
 import ReportInsertContent from './components/reportInsert/Index.vue'
 import ReportPublishTimeSet from './components/ReportPublishTimeSet.vue'
 import apiReport from '@/api/report'
-import apiChart from '@/api/chart'
-import {getSystemInfo} from '@/api/common'
+// import {getSystemInfo} from '@/api/common'
 import moment from 'moment'
 import { showToast,showDialog } from 'vant'
 import { useRoute, useRouter } from 'vue-router'
@@ -14,7 +13,8 @@ import {useCachedViewsStore} from '@/store/modules/cachedViews'
 import {usePublicSettingStore} from '@/store/modules/publicSetting'
 import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
 import {useReportApprove} from '@/hooks/useReportApprove'
-import {Base64} from 'js-base64'
+import AddReportBaseInfoV2 from './components/AddReportBaseInfoV2.vue'
+import { useReportHandles,useChapterRepoprtHandles } from './hooks/useReport'
 
 const cachedViewsStore=useCachedViewsStore()
 const publicSettingStore = usePublicSettingStore()
@@ -26,35 +26,51 @@ const {checkAuthBtn} = useAuthBtn()
 const {lastFocusPosition,initFroalaEditor,imgUploadFlag,frolaEditorContentChange}=useInitFroalaEditor()
 let reportContentEditorIns=null//报告内容编辑器实例
 
+
+const { handleRefresh,handlePublishReportHook,handleDSPublish,showDSFBTime } = useReportHandles()
+
+const reportCoopType = ref(Number(route.query.coopType))
 let autoSaveTimer=null
-// 水印
-const waterMarkStr=ref('')
 
 onMounted(() => {
     const el=document.getElementById('editor')
     reportContentEditorIns=initFroalaEditor('#editor',{height:el.offsetHeight-150})
-    getEtaConfig()
-    getReportDetail()
-    autoSaveTimer=setInterval(() => {
-        autoSaveReportContent()
-    }, 6000);
-    getSystemInfoFun()
+
+    if(reportCoopType.value===1) {//单人报告
+        getEtaConfig()
+        getReportDetail()
+        autoSaveTimer=setInterval(() => {
+            autoSaveReportContent()
+        }, 6000);
+    }else if(reportCoopType.value===2) { //章节报告
+        getChapterDetail()
+        autoSaveTimer=setInterval(() => {
+            autoSaveReportChapter()
+        }, 6000);
+    }
 })
 onUnmounted(()=>{
     clearInterval(autoSaveTimer)
 })
 
 // 自动保存报告
-async function autoSaveReportContent(){
+async function autoSaveReportContent(type="auto"){
     if(!imgUploadFlag.value)return
     //如果富文本中有未上传完成的图片,去除这个dom
     $('.fr-element').find('img.fr-uploading').length&&$('.fr-element').find('img.fr-uploading').remove()
-    const res=await apiReport.reportContentSave({
-        ReportId:Number(route.query.id),
-        Content:$('.fr-element').html(),
-        NoChange:frolaEditorContentChange.value?0:1
+    return new Promise(async (resolve,reject)=>{
+        const res=await apiReport.reportContentSave({
+            ReportId:Number(route.query.id),
+            Content:$('.fr-element').html(),
+            NoChange:frolaEditorContentChange.value?0:1
+        })
+        if(res.Ret === 200) {
+            resolve(true)
+            type==='save' && showToast("保存成功");
+            frolaEditorContentChange.value=false
+        }
     })
-    frolaEditorContentChange.value=false
+   
 }
 
 // 获取报告详情
@@ -70,12 +86,14 @@ async function getReportDetail(){
             {
                 id:res.Data.ClassifyIdFirst,
                 text:res.Data.ClassifyNameFirst,
-                HasTeleconference:0
             },
             {
                 id:res.Data.ClassifyIdSecond,
                 text:res.Data.ClassifyNameSecond,
-                HasTeleconference:0
+            },
+            {
+                id:res.Data.ClassifyIdThird,
+                text:res.Data.ClassifyNameThird,
             }
         ]
         reportBaseInfoData.author=res.Data.Author ? res.Data.Author.split(',') : ['FICC团队']
@@ -83,36 +101,34 @@ async function getReportDetail(){
         reportBaseInfoData.createtime=moment(res.Data.CreateTime).format('YYYY-MM-DD')
         reportBaseInfoData.title=res.Data.Title
         reportBaseInfoData.abstract=res.Data.Abstract
+        reportBaseInfoData.cooperationType=res.Data.CollaborateType
+        reportBaseInfoData.cooperationUsers=res.Data.GrandAdminList
+            ? res.Data.GrandAdminList.map(_ => ({
+                NodeId: _.AdminId,
+                NodeName: _.AdminName
+            }))
+            : []
+        reportBaseInfoData.reportLayout=res.Data.ReportLayout
+        reportBaseInfoData.isPublcPublish=res.Data.IsPublicPublish
 
         reportContentEditorIns.html.set(res.Data.Content);
         
         const classify = reportBaseInfoData.classifyName.map(i=>i.id)
         checkClassifyNameArr(1,classify)
-        // 查找选中的分类是否有电话会
-        const classifyRes=await apiReport.getClassifyList({
-            CurrentIndex:1,
-            PageSize:1000,
-            KeyWord:'',
-            HideDayWeek:1
-        })
-        if(classifyRes.Ret===200){
-            const arr=classifyRes.Data.List||[]
-            arr.forEach(item=>{
-                if(item.Id==reportBaseInfoData.classifyName[0].id){
-                    reportBaseInfoData.classifyName[0].HasTeleconference=item.HasTeleconference
-                }
-                item.Child&&item.Child.forEach(_item=>{
-                    if(_item.Id==reportBaseInfoData.classifyName[1].id){
-                        reportBaseInfoData.classifyName[1].HasTeleconference=_item.HasTeleconference
-                    }
-                })
-            })
-        }
-        
 
     }
 }
 
+//获取章节详情
+async function getChapterDetail() {
+    const res=await apiReport.getChapterDetail({
+        ReportChapterId:Number(route.query.chapterId)
+    })
+    reportData.value=res.Data;
+
+    reportContentEditorIns.html.set(res.Data.Content);
+}
+
 
 // 报告基本内容
 const showReportBaseInfo=ref(false)
@@ -123,31 +139,20 @@ let reportBaseInfoData={
     frequency: ['日度'],
     createtime:moment().format('YYYY-MM-DD'),
     title:'',
-    abstract:''
+    abstract:'',
+    cooperationType:1,
+    cooperationUsers:[],
+    reportLayout: 1,
+    isPublcPublish: 1
 }
 async function handleReportBaseInfoChange(e){
-    reportBaseInfoData=e
-
-    //继承报告 覆盖一次
-    if(e.addType===2&&e.classifyName.length===2){
-        const res=await apiReport.reportDetailByClassifyId({
-            ClassifyIdFirst:e.classifyName[0].id,
-            ClassifyIdSecond:e.classifyName[1].id
-        })
-        if(res.Ret===200){
-            if(res.Data===null){
-                showToast('此分类暂无报告')
-            }else{
-                reportBaseInfoData.author=res.Data.Author ? res.Data.Author.split(',') : ['FICC团队']
-                reportBaseInfoData.frequency=[res.Data.Frequency]
-                reportBaseInfoData.createtime=moment().format('YYYY-MM-DD')
-                reportBaseInfoData.title=res.Data.Title
-                reportBaseInfoData.abstract=res.Data.Abstract
-                reportContentEditorIns.html.set(res.Data.Content);
-            }
-        }
+    reportBaseInfoData={
+        ...e,
+        classifyName:e.classifys,
+        createtime:e.time,
     }
-    const classify = e.classifyName.map(i=>i.id)
+
+    const classify = e.classifys.map(i=>i.id)
     checkClassifyNameArr(1,classify)
     showReportBaseInfo.value=false
 }
@@ -211,49 +216,12 @@ onUnmounted(()=>{
 
 // 刷新所有图表
 async function handleRefreshAllChart(){
-    let code_arr = [];
-    $('iframe').each((k,i) => {
-        try {
-          let href = $(i).attr('src');
-          code_arr.push(href.slice(href.indexOf('code=') + 5));
-        } catch (err) {
-        }
-    });
-    if(!code_arr.length) return showToast('请插入图表');
-
-    if(route.query.id) {
-        let res = await apiChart.getReportrefreshStatus({
-            Source: 'report',
-            ReportId: Number(route.query.id),
-            ReportChapterId: 0
-        });
-        
-        if(!res.Data.RefreshResult) return showToast('图表正在刷新中,请勿重复操作')
-    }
-
-    const res=await apiChart.refreshChartMultiple({ChartInfoCode:code_arr})
-    if(res.Ret===200){
-        $('iframe').each((k,i) => {
-          $(i).attr('src',$(i).attr('src'))
-        });
-        showToast(res.Msg)
-    }
+    handleRefresh({ id: Number(route.query.id),chapterId:Number(route.query.chapterId) })
 }
 
 
-// 发布报告-fb;保存-cg;预览-yl
-const showPublishPop=ref(false)
-async function handleReportOpt(type){
-    if(reportBaseInfoData.classifyName.length===0){
-        showToast('请选择报告分类')
-        return
-    }
-    if(!reportBaseInfoData.title){
-        showToast('请填写报告标题')
-        return
-    }
-    //如果富文本中有未上传完成的图片,去除这个dom
-    $('.fr-element').find('img.fr-uploading').length&&$('.fr-element').find('img.fr-uploading').remove()
+/* 预览 */
+function handlePreviewReport() {
     const params={
         ReportId:Number(route.query.id),
         AddType: reportBaseInfoData.addType,
@@ -270,245 +238,133 @@ async function handleReportOpt(type){
 		ReportVersion: 2,
         State:1
     }
-    console.log(params);
-    if(type==='yl'){
-        sessionStorage.setItem('reportPreData',JSON.stringify(params))
-        const routerEl=router.resolve({
-            path:'/report/preview',
-            query:{
-                id:-1
-            }
-        })
-        window.open(routerEl.href,'_blank')
-        return
-    }
-    cachedViewsStore.removeCaches('ReportList')
-    if(type==='cg'){
-        // 存草稿
-        const res=await apiReport.reportEdit(params)
-        if(res.Ret===200){
-            showToast('保存成功')
-        }
-    }
-    if(type==='fb'){
-        // 发布
-        // const hasTel=reportBaseInfoData.classifyName[1].HasTeleconference
-        // //有电话会的不提示推送客群
-        // if(hasTel==1){
-        //     const res=await apiReport.reportEdit(params)
-        //     if(res.Ret===200){
-        //         reportPublish(res.Data.ReportId)
-        //     }
-        // }else{
-        //     // 显示发布提示弹窗,提示推送客群
-        //     showPublishPop.value=true
-        // }
-        if(isApprove.value||!checkAuthBtn(reportManageBtn.reportManage_sendMsg)){
-            handleConfirmPublish(1)
-            return
+    sessionStorage.setItem('reportPreData',JSON.stringify(params))
+
+    router.push({
+        path:'/report/preview',
+        query:{
+            id:-1
         }
-        // 没有客群了发布都有二次提示弹窗
-        showPublishPop.value=true
-    }
-    if(type==='dsfb'){
-        showDSFBTime.value=true
-    }
-    if(type==='submit'){
-        handleReportSubmit(params)
-    }
+    })
+    return
 }
 
-// 点击发布提示弹窗中的操作按钮
-async function handleConfirmPublish(e){
-    const params={
-        ReportId:Number(route.query.id),
-        AddType: reportBaseInfoData.addType,
-		ClassifyIdFirst: reportBaseInfoData.classifyName[0].id,
-		ClassifyNameFirst: reportBaseInfoData.classifyName[0].text,
-        ClassifyIdSecond: reportBaseInfoData.classifyName[1].id,
-		ClassifyNameSecond: reportBaseInfoData.classifyName[1].text,
-		Title: reportBaseInfoData.title,
-		Abstract: reportBaseInfoData.abstract,
-		Author:reportBaseInfoData.author.join(','),
-		Frequency: reportBaseInfoData.frequency[0],
-		Content: $('.fr-element').html(),
-		CreateTime: reportBaseInfoData.createtime,
-		ReportVersion: 2,
-        State:1
-    }
-    const saveRes=await apiReport.reportEdit(params)
-    if(e===1){//仅发布
-        reportPublish(saveRes.Data.ReportId,saveRes.Data.ReportCode)
-    }else if(e===2){
-        if(reportData.value.MsgIsSend===1) return
-        const pubRes=await apiReport.reportPublish({ReportIds:saveRes.Data.ReportId.toString(),ReportUrl:generatePdfLinks(saveRes.Data.ReportCode)})
-        if(pubRes.Ret!==200) return
-        const msgRes=await apiReport.reportMessageSend({ReportId:saveRes.Data.ReportId})
-        if(msgRes.Ret!==200) return
-        router.back()
-    }
-}
 
-function generatePdfLinks(Code){
-    return `${publicSettingStore.publicSetting.ReportViewUrl}/reportshare_pdf?code=${Code}&flag=${waterMarkStr.value}`
-}
+//发布 定时 提交
+async function handlePublishReport(tp) {
+    cachedViewsStore.removeCaches('ReportList')
 
-async function reportPublish(id,code){
-    const res=await apiReport.reportPublish({ReportIds:id.toString(),ReportUrl:generatePdfLinks(code)})
-    if(res.Ret===200){
-        console.log('back');
-        router.back()
-    }
+    const saveRes = await autoSaveReportContent('auto');
+    if(!saveRes) return
+
+    handlePublishReportHook(tp,reportData.value)
 }
 
 // 定时发布报告选择时间
-const showDSFBTime=ref(false)
 function onConfirmDSFBTime(time){
-    console.log(time);
-    if(reportData.value.MsgIsSend===1){//已经推送过了
-        apiReport.reportPublishTimeSet({
-            ReportId:reportData.value.Id,
-            PrePublishTime:time,
-            PreMsgSend:0,
-            ReportUrl:generatePdfLinks(reportData.value.ReportCode)
-        }).then(res=>{
-            if(res.Ret===200){
-                showToast('定时发布成功!')
-                setTimeout(() => {
-                    router.back()
-                }, 1000);
-            }
-        })
-        return
-    }
-    const isAuthPushMsg=checkAuthBtn(reportManageBtn.reportManage_sendMsg)
-    showDialog({
-        title: '提示',
-        message:isAuthPushMsg?'是否发布定时报告,并推送模板消息?':'是否发布定时报告',
-        confirmButtonText:isAuthPushMsg?'推送':'确定',
-        cancelButtonText:isAuthPushMsg?'不推送':'取消',
-        showCancelButton:true
-    }).then(()=>{
-        if(!isAuthPushMsg){
-            apiReport.reportPublishTimeSet({
-                ReportId:reportData.value.Id,
-                PrePublishTime:time,
-                PreMsgSend:0,
-                ReportUrl:generatePdfLinks(reportData.value.ReportCode)
-            }).then(res=>{
-                if(res.Ret===200){
-                    showToast('定时发布成功!')
-                    setTimeout(() => {
-                        router.back()
-                    }, 1000);
-                }
-            })
-            return
+    // console.log(time);
+    handleDSPublish(time,reportData.value)
+}
+
+
+
+const { handlePublishChapterApi } = useChapterRepoprtHandles()
+//预览章节
+async function handlePreviewChapter() {
+    const saveRes = await autoSaveReportChapter('auto');
+    if(!saveRes) return
+    
+    router.push({
+        path:'/report/chapter/preview',
+        query:{
+            id:reportData.value.ReportChapterId
         }
-        //推送
-        apiReport.reportPublishTimeSet({
-            ReportId:reportData.value.Id,
-            PrePublishTime:time,
-            PreMsgSend:1,
-            ReportUrl:generatePdfLinks(reportData.value.ReportCode)
-        }).then(res=>{
-            if(res.Ret===200){
-                showToast('定时发布成功!')
-                setTimeout(() => {
-                    router.back()
-                }, 1000);
-            }
-        })
-    }).catch(()=>{
-        if(!isAuthPushMsg) return
-        //不推送
-        apiReport.reportPublishTimeSet({
-            ReportId:reportData.value.Id,
-            PrePublishTime:time,
-            PreMsgSend:0,
-            ReportUrl:generatePdfLinks(reportData.value.ReportCode)
-        }).then(res=>{
-            if(res.Ret===200){
-                showToast('定时发布成功!')
-                setTimeout(() => {
-                    router.back()
-                }, 1000);
-            }
-        })
     })
 }
 
-//提交报告
-async function handleReportSubmit(params){
-    const res = await apiReport.reportEdit(params)
-    if(res.Ret!==200) return
-    showDialog({
-        title: '提示',
-        message: '是否确认提交该报告进入审批流程?',
-        showCancelButton:true
-    }).then(()=>{
-        apiReport.reportCnSubmit({
-            ReportId:Number(route.query.id)
-        }).then(res=>{
-            if(res.Ret!==200) return 
-            showToast('提交成功')
-            router.back()
+// 自动保存章节报告
+async function autoSaveReportChapter(type="auto"){
+    if(!imgUploadFlag.value)return
+    if(!route.query.chapterId) return 
+    //如果富文本中有未上传完成的图片,去除这个dom
+    $('.fr-element').find('img.fr-uploading').length&&$('.fr-element').find('img.fr-uploading').remove()
+    return new Promise(async (resolve,reject)=>{
+        const res=await apiReport.chapterDetailSave({
+            ReportChapterId:Number(route.query.chapterId),
+            Content:$('.fr-element').html(),
         })
-    }).catch(()=>{})
-}
-
-const getSystemInfoFun=()=>{
-    getSystemInfo().then(res=>{
-        if(res.Ret===200){
-          const systemUserInfo=res.Data
-          // 设置水印文案
-          let waterMarkString=''
-          if(systemUserInfo){
-            waterMarkString=`${systemUserInfo.RealName}${systemUserInfo.Mobile?systemUserInfo.Mobile:systemUserInfo.Email}`
-            waterMarkString=encodeURIComponent(waterMarkString)
-            waterMarkStr.value=Base64.encode(waterMarkString)
-          }
+        if(res.Ret === 200) {
+            resolve(true)
+            type==='save' && showToast("保存成功");
         }
     })
+   
+}
+
+/* 提交章节 */
+async function handlePublishChapter() {
+    const saveRes = await autoSaveReportChapter('auto');
+    if(!saveRes) return
+
+    handlePublishChapterApi(reportData.value.ReportChapterId)
 }
+
 </script>
 
 <template>
     <div class="add-report-page">
-        <van-cell title="基础信息" is-link @click="showReportBaseInfo=true"/>
         <div class="main-wrap">
             <div class="editor-box" id="editor"></div>
         </div>
         <!-- 底部操作 -->
         <div class="bot-action-box">
-            <div class="left-box">
+            <div class="left-box" v-if="reportData">
+                <div class="item" @click="showReportBaseInfo=true" v-if="!reportData.ReportChapterId">
+                    <img src="@/assets/imgs/report/icon_info.png" alt="">
+                    <span>基础信息</span>
+                </div>
                 <div class="item" @click="handleRefreshAllChart">
                     <img src="@/assets/imgs/report/icon_refresh.png" alt="">
                     <span>刷新</span>
                 </div>
-                <div class="item" @click="handleReportOpt('yl')" v-permission="reportManageBtn.reportManage_reportView">
+                <div class="item" @click="reportData.ReportChapterId?handlePreviewChapter():handlePreviewReport()" v-permission="reportManageBtn.reportManage_reportView">
                     <img src="@/assets/imgs/report/icon_preview.png" alt="">
                     <span>预览</span>
                 </div>
-                <div class="item" @click="handleReportOpt('cg')">
+                <div class="item" @click="reportData.ReportChapterId?autoSaveReportChapter('save'):autoSaveReportContent('save')">
                     <img src="@/assets/imgs/report/icon_save2.png" alt="">
                     <span>保存</span>
                 </div>
-                <template v-if="!isApprove||!hasApproveFlow">
-                    <div class="item" @click="handleReportOpt('dsfb')" v-permission="reportManageBtn.reportManage_publish">
-                        <img src="@/assets/imgs/report/icon_time.png" alt="">
-                        <span>定时发布</span>
-                    </div>
-                    <div class="item" @click="handleReportOpt('fb')" v-permission="reportManageBtn.reportManage_publish">
-                        <img src="@/assets/imgs/report/icon_publish3.png" alt="">
-                        <span>发布</span>
-                    </div>
-                </template>
-                <template v-if="isApprove&&hasApproveFlow">
-                    <div class="item" @click="handleReportOpt('submit')" v-permission="reportManageBtn.reportManage_publish">
+
+                <!-- 章节报告提交章节 -->
+                <div 
+                    class="item" 
+                    @click="handlePublishChapter" 
+                    v-permission="reportManageBtn.reportManage_publish" 
+                    v-if="reportData.ReportChapterId"
+                >
                         <img src="@/assets/imgs/report/icon_publish3.png" alt="">
                         <span>提交</span>
-                    </div>
+                </div>
+
+                <!-- 单人报告提交发布 -->
+                <template v-else>
+                    <template v-if="!isApprove||!hasApproveFlow">
+                        <div class="item" @click="handlePublishReport('dsfb')" v-permission="reportManageBtn.reportManage_publish">
+                            <img src="@/assets/imgs/report/icon_time.png" alt="">
+                            <span>定时发布</span>
+                        </div>
+                        <div class="item" @click="handlePublishReport('fb')" v-permission="reportManageBtn.reportManage_publish">
+                            <img src="@/assets/imgs/report/icon_publish3.png" alt="">
+                            <span>发布</span>
+                        </div>
+                    </template>
+                    <template v-if="isApprove&&hasApproveFlow">
+                        <div class="item" @click="handlePublishReport('submit')" v-permission="reportManageBtn.reportManage_publish">
+                            <img src="@/assets/imgs/report/icon_publish3.png" alt="">
+                            <span>提交</span>
+                        </div>
+                    </template>
                 </template>
             </div>
             <div class="right-btn" @click="showReportInsertPop=true">
@@ -525,7 +381,12 @@ const getSystemInfoFun=()=>{
         position="bottom"
         :style="{ height: '100%' }"
     >
-        <EditReportBaseInfo v-if="showReportBaseInfo" :defaultData="reportBaseInfoData" @close="showReportBaseInfo=false" @confirm="handleReportBaseInfoChange"/>
+        <AddReportBaseInfoV2
+            @close="showReportBaseInfo=false"
+            :id="Number(route.query.id)"
+            :defaultData="reportBaseInfoData"
+            @confirm="handleReportBaseInfoChange"
+        />
     </van-popup>
 
     <!-- 报告插入数据模块 -->
@@ -537,20 +398,6 @@ const getSystemInfoFun=()=>{
         <report-insert-content v-if="showReportInsertPop" @insert="handleInsert"/>
     </van-popup>
 
-    <!-- 发布提示 -->
-    <van-popup
-        v-model:show="showPublishPop"
-    >
-        <div class="publish-report-pop-box">
-            <div class="title">发布提示</div>
-            <p class="tips">{{reportData.PrePublishTime?'该报告已设置定时发布,是否立即发布报告并推送模板消息?':'是否立即发布报告,并推送模板消息?'}}</p>
-            <div class="btns">
-                <div :class="['btn blue',reportData.MsgIsSend===1?'disabled':'']" @click="handleConfirmPublish(2)">发布&推送</div>
-                <div class="btn" @click="handleConfirmPublish(1)">仅发布</div>
-                <div class="btn" @click="showPublishPop=false">取消</div>
-            </div>
-        </div>
-    </van-popup>
 
     <!-- 定时发布选择时间 -->
     <ReportPublishTimeSet v-model="showDSFBTime" :prePublishTime="reportData?.PrePublishTime" @confirm="onConfirmDSFBTime" />

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 207 - 203
src/views/report/List.vue


+ 170 - 50
src/views/report/PreviewDetail.vue

@@ -1,11 +1,17 @@
 <script setup name="ReportPreview">
-import {ref} from 'vue'
+import { ref,computed, nextTick, reactive,toRefs } from 'vue'
 import { useRoute, useRouter } from "vue-router";
 import apiReport from '@/api/report'
 import AudioBox from './components/AudioBox.vue'
 import {showToast} from 'vant'
 import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+import { copyText } from 'vue3-clipboard'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
+import vueQr from 'vue-qr/src/packages/vue-qr.vue'
+
+
 const {checkAuthBtn} = useAuthBtn()
+const publicSettingStore = usePublicSettingStore()
 
 const route=useRoute()
 const router=useRouter()
@@ -13,11 +19,34 @@ const router=useRouter()
 
 // 获取报告详情
 let reportInfo=ref(null)
+const smartState = reactive({
+    bgColor:'',
+    headImgStyle:null,//版头style
+    endImgStyle:null,//版尾style
+    layoutBaseInfo:{
+        研报标题:'',
+        研报作者:'',
+        创建时间:''
+    }
+})
 async function getReportDetail(){
     const res=await apiReport.getReportDetail({ReportId:Number(route.query.id)})
     if(res.Ret===200){
         reportInfo.value=res.Data
         document.title=res.Data.Title
+
+        smartState.bgColor=res.Data.CanvasColor
+        smartState.headImgStyle=reportInfo.value.HeadStyle?JSON.parse(reportInfo.value.HeadStyle):[]
+        smartState.headImgStyle.map(st =>{
+            st.value=st.value || st.label
+        })
+        smartState.endImgStyle=reportInfo.value.EndStyle?JSON.parse(reportInfo.value.EndStyle):[]
+        smartState.endImgStyle.map(st =>{
+            st.value=st.value || st.label
+        })
+        smartState.layoutBaseInfo['研报标题']=reportInfo.value.Title
+        smartState.layoutBaseInfo['研报作者']=reportInfo.value.Author
+        smartState.layoutBaseInfo['创建时间']=[2,6].includes(reportInfo.value.State)?reportInfo.value.PublishTime:''
     }
 }
 if(route.query.id==-1){
@@ -29,58 +58,68 @@ if(route.query.id==-1){
     getReportDetail()
 }
 
+const { bgColor,headImgStyle,endImgStyle,layoutBaseInfo } = toRefs(smartState)
 
-//去编辑
-async function goEdit(){
-    // 先mark一下
-    const markRes=await apiReport.reportMark({
-        Status:1,
-        ReportId:reportInfo.value.Id
-    })
-    if(markRes.Ret===200){
-        if(markRes.Data.Status===1){
-            showToast(markRes.Data.Msg || '该研报正在编辑,不可重复编辑')
-            return
-        }
-    }else{
-        showToast(markRes.ErrMsg || '未知错误,请稍后重试')
-        return
-    }
 
-    if(['day','week'].includes(reportInfo.value.ChapterType)){
-        router.push({
-            path:"/report/chapter/list",
-            query:{
-                id:reportInfo.value.Id
-            }
-        })
-    }else{
-        // 研报
-        router.push({
-            path:'/report/edit',
-            query:{
-                id:reportInfo.value.Id
-            }
-        })
+const showImgPop = ref(false)
+const linkUrl = computed(() =>{
+    console.log(publicSettingStore)
+    let str=''
+    const baseUrl= publicSettingStore.publicSetting.ReportViewUrl;
+    if(reportInfo.value.ReportCode){
+        // 设置水印文案
+        let waterMarkStr= '';
+
+        str= reportInfo.value.ReportLayout===1 
+            ? `${baseUrl}/reportshare_crm_report?code=${reportInfo.value.ReportCode}&flag=${waterMarkStr}& ${reportInfo.value.Title}`
+            : `${baseUrl}/reportshare_smart_report?code=${reportInfo.value.ReportCode}& ${reportInfo.value.Title}`
     }
-}
+    
+    return str
+})
+function handleCopyLink() {
+    copyText(linkUrl.value,undefined,(error,event)=>{
+        if(error){
+            showToast('复制链接成功')
 
+            throw new Error('复制数据失败'+JSON.stringify(error))
+        }else{
+            showToast('复制链接成功')
+        }
+    })
+}
 </script>
 
 <template>
-    <div class="report-detail-page" v-if="reportInfo">
-        <div class="top-stage-box" v-if="$route.query.id!=-1">
+    <div class="report-detail-page" v-if="reportInfo" :style="{backgroundColor:bgColor}">
+        <!-- <div class="top-stage-box" v-if="$route.query.id!=-1">
             <span class="stage">第{{reportInfo.Stage}}期 / {{reportInfo.Frequency}}</span>
-            <img v-if="reportInfo.State==1&&checkAuthBtn(reportManageBtn.reportManage_reportEdit)" class="edit-icon" src="@/assets/imgs/report/icon_edit2.png" alt="" @click="goEdit">
-        </div>
-        <h1 class="report-title">{{reportInfo.Title}}</h1>
-        <div class="auth-box">
-            <span>{{reportInfo.Author}}</span>
-            <span>{{reportInfo.PublishTime}}</span>
+        </div> -->
+
+        <!-- 版头 -->
+        <div class="html-head-img-box" v-if="reportInfo && reportInfo.HeadImg">
+            <img :src="reportInfo.HeadImg" alt="" style="display:block;width:100%">
+            <div class="head-layout-item" v-for="item in headImgStyle" :key="item.value"
+            :style="{fontFamily:item.family,fontSize:(item.size*2)+'px',fontWeight:item.weight,textAlign:item.align,color:item.color,
+                width:item.width,height:item.height,left:item.left,top:item.top
+            }">
+                {{ layoutBaseInfo[item.value] }}
+            </div>
         </div>
-        <!-- 晨报/周报 -->
-        <template v-if="['day','week'].includes(reportInfo.ChapterType)">
-            <div class="report-abstract" v-if="reportInfo.Abstract">摘要:{{reportInfo.Abstract}}</div>
+
+        <template v-if="reportInfo&&(!reportInfo.HeadImg) && (!reportInfo.EndImg)">
+            <h1 class="report-title">{{reportInfo.Title}}</h1>
+            <div class="auth-box">
+                <span>{{reportInfo.Author}}</span>
+                <span>{{reportInfo.PublishTime}}</span>
+            </div>
+        </template>
+        <!-- 音频 -->
+        <AudioBox :url="reportInfo.VideoUrl" v-if="reportInfo.VideoUrl"/>
+        <div class="report-abstract" v-if="reportInfo.Abstract">摘要:{{reportInfo.Abstract}}</div>
+        
+        <!-- 章节 -->
+        <template v-if="reportInfo.CollaborateType===2">
             <ul class="chapter-list-wrap">
                 <li class="chapter-item-box" v-for="item in reportInfo.ChapterList" :key="item.ReportChapterId">
                     <div class="type-box">
@@ -95,17 +134,47 @@ async function goEdit(){
         </template>
         <!-- 研报 -->
         <template v-else>
-            <!-- 音频 -->
-            <AudioBox :url="reportInfo.VideoUrl" v-if="reportInfo.VideoUrl"/>
-            <div class="report-abstract" v-if="reportInfo.Abstract">摘要:{{reportInfo.Abstract}}</div>
             <div class="report-html-wrap" v-html="reportInfo.Content"></div>
         </template>
+
+
+          <!-- 板尾 -->
+        <div class="html-end-img-box" v-if="reportInfo && reportInfo.EndImg">
+            <img :src="reportInfo.EndImg" alt="" style="display:block;width:100%">
+            <div class="head-layout-item" v-for="item in endImgStyle" :key="item.value"
+            :style="{fontFamily:item.family,fontSize:(item.size*2)+'px',fontWeight:item.weight,textAlign:item.align,color:item.color,
+                width:item.width,height:item.height,left:item.left,top:item.top
+            }">
+                {{ layoutBaseInfo[item.value] }}
+            </div>
+        </div> 
+
+        <!-- 浮动操作 -->
+        <div class="fix-bot-action-box">
+            <div class="item" @click="handleCopyLink" v-permission="reportManageBtn.reportManage_reportView_copyWechat">
+                <img class="icon" src="@/assets/imgs/report/icon_copy.png" alt="">
+                <div>复制链接</div>
+            </div>
+            <div class="item" @click="showImgPop=true" v-permission="reportManageBtn.reportManage_reportView_wechartShare">
+                <img class="icon" src="@/assets/imgs/report/icon_wx_black.png" alt="">
+                <div>微信分享</div>
+            </div>
+        </div>
     </div>
+
+
+    <van-popup 
+        v-model:show="showImgPop" 
+        round
+    >
+        <vue-qr :text="linkUrl" colorDark="#333" colorLight="#fff" :dotScale="1"></vue-qr>
+    </van-popup>
 </template>
 
 <style lang="scss" scoped>
 .report-detail-page{
     padding: 30px 34px;
+    margin-bottom: 112px;
     .report-title{
         margin: 30px 0;
         font-weight: 600;
@@ -117,8 +186,6 @@ async function goEdit(){
         justify-content: space-between;
         font-size: $font-grey;
         font-size: 36px;
-        padding-bottom: 40px;
-        border-bottom: 1px solid $border-color;
         margin-bottom: 40px;
     }
     .report-abstract{
@@ -149,6 +216,20 @@ async function goEdit(){
             }
         }
     }
+
+    .report-drag-item-wrap{
+        padding: 6px;
+        margin-bottom: 3px;
+    }
+    .html-head-img-box,.html-end-img-box{
+        position: relative;
+        margin: 20px 0;
+        .head-layout-item{
+            position: absolute;
+            overflow: hidden;
+            box-sizing: border-box
+        }
+    }
 }
 .top-stage-box{
     .stage{
@@ -167,10 +248,38 @@ async function goEdit(){
     }
 }
 
+
+.fix-bot-action-box{
+    position: fixed;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    z-index: 99;
+    background-color: #fff;
+    border-top: 1px solid $border-color;
+    height: 112px;
+    display: flex;
+    align-items: center;
+    .item{
+        height: 100%;
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        font-size: 20px;
+        .icon{
+            width: 40px;
+            height: 40px;
+            margin-bottom: 5px;
+        }
+    }
+}
+
 @media screen and (min-width:$media-width){
     .report-detail-page{
         max-width: 800px;
-        margin: 0 auto;
+        margin: 0 auto 110px;
         padding: 30px;
         .report-title{
             margin: 15px 0;
@@ -215,5 +324,16 @@ async function goEdit(){
             height: 35px;
         }
     }
+
+    .fix-bot-action-box{ 
+        height: 110px;
+        .item {
+            font-size: 18px;
+            .icon{
+                width: 40px;
+                height: 40px;
+            }
+        }
+    }
 }
 </style>

+ 27 - 1
src/views/report/Search.vue

@@ -9,8 +9,15 @@ console.log('搜索页setup');
 
 const router=useRouter()
 
+const reportTypes = [
+    { label: '我的研报',key:'3' },
+    { label: '共享研报',key:'2' },
+    { label: '公共研报',key:'1' },
+]
+
 const keyword=ref('')
 const listState = reactive({
+    listType: '3',
     list:[],
     page:1,
     pageSize:20,
@@ -21,7 +28,8 @@ async function getList(){
     const res=await apiReport.getList({
         CurrentIndex:listState.page,
         PageSize:listState.pageSize,
-        KeyWord:keyword.value
+        KeyWord:keyword.value,
+        FilterReportType: listState.listType
     })
     if(res.Ret===200){
         listState.loading=false
@@ -71,7 +79,25 @@ function goDetail(item){
                 v-model="keyword"
                 @search="handleSearch"
             />
+
+            <div class="report-type">
+                <van-tabs 
+                    v-model:active="listState.listType" 
+                    title-active-color="#0052D9" 
+                    title-inactive-color="#333"
+                    @change="handleSearch"
+                >
+                    <van-tab 
+                        :title="tab.label" 
+                        v-for="tab in reportTypes" 
+                        :key="tab.key" 
+                        :name="tab.key"
+                    ></van-tab>
+                </van-tabs>
+            </div>
         </div>
+        
+
         <img v-if="listState.list.length==0&&listState.finished&&keyword" class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
         <template v-if="keyword">
         <van-list

+ 1 - 81
src/views/report/chapter/Detail.vue

@@ -142,43 +142,6 @@ const chapterBaseInfo=reactive({
     audioDuration:0,
     audioSize:0,
 })
-// 更新报告基本内容
-async function handleChapterBaseInfoSave(e){
-    console.log(e);
-    chapterBaseInfo.type=e.type
-    chapterBaseInfo.title=e.title
-    chapterBaseInfo.author=e.author
-    chapterBaseInfo.createTime=e.createTime
-    ticketList.value=e.ticket
-
-    // 如果修改报告的新增方式 则刷新数据
-    if(chapterBaseInfo.addType!=e.addType){
-        chapterBaseInfo.addType=e.addType
-        // 继承报告
-        if(e.addType==2){
-            const res=await apiReport.chapterReportPreContent({
-                TypeId:info.value.TypeId,
-                ReportType:info.value.ReportType
-            })
-            if(res.Ret===200){
-                chapterBaseInfo.type=res.Data.TypeName
-                chapterBaseInfo.title=res.Data.Title
-                chapterBaseInfo.author=res.Data.Author||userInfo.RealName
-                chapterBaseInfo.createTime=moment(res.Data.CreateTime).format('YYYY-MM-DD')
-                reportContentEditorIns.html.set(res.Data.Content);
-                // 晨报获取指标数据
-				if(res.Data.ReportType=='day'){
-					getDayTicketList()
-				}
-            }
-        }else{
-            reportContentEditorIns.html.set('');
-        }
-    }
-    
-    
-    showChapterBaseInfo.value=false
-}
 
 // 上传音频
 const showUploadAudio=ref(false)
@@ -447,7 +410,7 @@ async function handleReportOpt(type){
 
 <template>
     <div class="chapter-detail-edit-page">
-        <van-cell title="章节基础配置" is-link @click="showChapterBaseInfo=true"/>
+        <!-- <van-cell title="章节基础配置" is-link @click="showChapterBaseInfo=true"/> -->
         <van-cell title="上传音频" is-link v-if="info?.ReportType==='week'" @click="handleShowUploadAudio"/>
         <div class="main-wrap">
             <div class="editor-box" id="editor"></div>
@@ -480,49 +443,6 @@ async function handleReportOpt(type){
         </div>
     </div>
 
-    <!-- 基础信息 -->
-    <van-popup 
-        v-model:show="showChapterBaseInfo" 
-        position="bottom"
-        :style="{height:'100%'}"
-    >
-        <EditChapterBaseInfo
-            v-if="showChapterBaseInfo"
-            :ticketData="ticketList"
-            :chapterInfo="info"
-            :defaultData="chapterBaseInfo"
-            @close="showChapterBaseInfo=false"
-            @confirm="handleChapterBaseInfoSave"
-        />
-    </van-popup>
-
-    <!-- 上传音频 -->
-    <van-popup 
-        v-model:show="showUploadAudio" 
-        position="bottom"
-        round
-        :style="{height:'60%'}"
-    >
-        <div class="upload-audio-wrap" v-if="showUploadAudio">
-            <div style="font-size:12px;color:#666">tips:如果是在微信中访问,请上传完音频点击播放,获取音频时长后方可保存</div>
-            <template v-if="temAudioData.url">
-                <h2>音频链接</h2>
-                <p>{{temAudioData.url}}</p>
-                <AudioBox :time="temAudioData.time" :url="temAudioData.url" @updateDuration="handleUpdateAudioTime"/>
-            </template>
-            <div class="bot-btns">
-                <van-uploader 
-                    accept="*" 
-                    :after-read="handleAudioUploadAfterRead"
-                    :before-read="handleAudioUploadBeforeRead"
-                >
-                    <van-button class="bot-btn" type="default">{{temAudioData.url?'重新上传':'上传音频'}}</van-button>
-                </van-uploader>
-                <van-button class="bot-btn" type="primary" :disabled="!temAudioData.url||temAudioData.time===0" @click="handleUpdateAudio">保存</van-button>
-            </div>
-        </div>
-    </van-popup>
-
     <!-- 报告插入数据模块 -->
     <van-popup
         v-model:show="showReportInsertPop"

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 704 - 172
src/views/report/chapter/List.vue


+ 127 - 0
src/views/report/chapter/Preview.vue

@@ -0,0 +1,127 @@
+<script setup name="ReportPreview">
+import { ref,computed, nextTick, reactive,toRefs } from 'vue'
+import { useRoute, useRouter } from "vue-router";
+import apiReport from '@/api/report'
+import AudioBox from '../components/AudioBox.vue'
+import {showToast} from 'vant'
+import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+
+
+const {checkAuthBtn} = useAuthBtn()
+
+const route=useRoute()
+const router=useRouter()
+
+
+// 获取报告详情
+let reportInfo=ref(null)
+async function getChapterDetail(){
+    const res=await apiReport.getChapterDetail({ReportChapterId:Number(route.query.id)})
+    if(res.Ret===200){
+        reportInfo.value=res.Data
+        document.title=res.Data.Title
+    }
+}
+getChapterDetail()
+</script>
+<template>
+    <div class="report-detail-page" v-if="reportInfo">
+        <h1 class="report-title">{{reportInfo.Title}}</h1>
+        <div class="auth-box">
+            <span>{{reportInfo.Author}}</span>
+            <span v-if="[2,6].includes(reportInfo.PublishState)">{{reportInfo.PublishTime}}</span>
+        </div>
+        <!-- 音频 -->
+        <!-- <AudioBox :url="reportInfo.VideoUrl" v-if="reportInfo.VideoUrl"/> -->
+        <div class="report-abstract" v-if="reportInfo.Abstract">摘要:{{reportInfo.Abstract}}</div>
+        
+        <div class="report-html-wrap" v-html="reportInfo.Content"></div>
+
+    </div>
+</template>
+
+<style lang="scss" scoped>
+  .report-drag-item-wrap{
+        padding: 6px;
+        margin-bottom: 3px;
+    }
+.report-detail-page{
+    padding: 30px 34px;
+    margin-bottom: 112px;
+    .report-title{
+        margin: 30px 0;
+        font-weight: 600;
+        font-size: 42px;
+        line-height: 56px;
+    }
+    .auth-box{
+        display: flex;
+        justify-content: space-between;
+        font-size: $font-grey;
+        font-size: 36px;
+        margin-bottom: 40px;
+    }
+    .report-abstract{
+        font-size: 34px;
+        line-height: 54px;
+        margin: 40px 0;
+    }
+    .audio-box{
+        margin: 40px 0;
+    }
+
+  
+}
+.top-stage-box{
+    .stage{
+        display: inline-block;
+        background-color: #F2F3FF;
+        border-radius: 8px;
+        height: 72px;
+        line-height: 72px;
+        padding: 0 20px;
+        font-size: 28px;
+    }
+    .edit-icon{
+        float: right;
+        width: 70px;
+        height: 70px;
+    }
+}
+
+@media screen and (min-width:$media-width){
+    .report-detail-page{
+        max-width: 800px;
+        margin: 0 auto 110px;
+        padding: 30px;
+        .report-title{
+            margin: 15px 0;
+            font-size: 21px;
+            line-height: 28px;
+        }
+        .auth-box{
+            font-size: 18px;
+            padding-bottom: 20px;
+            margin-bottom: 20px;
+        }
+        .report-abstract{
+            font-size: 17px;
+            line-height: 27px;
+            margin: 20px 0;
+        }
+    }
+    .top-stage-box{
+        .stage{
+            border-radius: 4px;
+            height: 36px;
+            line-height: 36px;
+            padding: 0 10px;
+            font-size: 14px;
+        }
+        .edit-icon{
+            width: 35px;
+            height: 35px;
+        }
+    }
+}
+</style>

+ 105 - 173
src/views/report/chapter/components/EditChapterBaseInfo.vue

@@ -1,120 +1,97 @@
 <script setup>
-import { showToast } from "vant";
 import { computed, reactive,ref } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { showToast } from "vant";
 import apiReport from '@/api/report'
 import moment from "moment";
 import { useWindowSize } from '@vueuse/core'
 
+const route = useRoute()
 
 const { width, height } = useWindowSize()
 
 const props=defineProps({
-    ticketData:{
-        default:[]
-    },
-    chapterInfo:null,
-    defaultData:null
+    defaultData:null,
+    userOpts: [],
+    varietyOpts:[]
 })
 const emits=defineEmits(['close','confirm'])
 
-// 获取晨报指标数据
-const showTicketPop=ref(false)
-let activeTicket=ref([])//选中的指标
-const activeTicketArr=computed(()=>{
-    return props.ticketData.filter(item=>activeTicket.value.includes(item.BaseColumnId))
-})
-if(props.chapterInfo.ReportType==='day'){
-    props.ticketData.forEach(item => {
-        if(item.Selected){
-            activeTicket.value.push(item.BaseColumnId)
-        }
-    });
-}
+console.log(props)
 
 const baseInfo=reactive({
-    type:props.defaultData.type||'',
-    title:props.defaultData.title||'',
-    addType:props.defaultData.addType||1,
-    ticket:[],
-    author:props.defaultData.author||'',
-    createTime:props.defaultData.createTime||moment().format('YYYY-MM-DD'),
+    chapterName: props.defaultData?.Title||'',
+    varietys:props.defaultData?.PermissionIdList||[],
+    editors: props.defaultData?.GrandAdminIdList || []
 })
 
-// 报告新增类型
-const showAddTypePop=ref(false)
-const addTypeOpts=[
-    {
-        value:1,
-        name:'空白报告'
-    },
-    {
-        value:2,
-        name:'继承报告'
-    }
-]
-function getAddTypeName(value){
-    return addTypeOpts.filter(item=>item.value===value)[0].name
-}
-function selectAddType(e){
-    baseInfo.addType=e.value
-}
+const editorsLabel = computed(() => {
+    if(!baseInfo.editors.length) return ''
 
-// 创建日期
-const minDate=new Date(2015, 0, 1)
-const defaultDate=ref(new Date())
-const showCreateTimePop=ref(false)
-function handleShowCreatetime(){
-    defaultDate.value=new Date(baseInfo.createTime.replace(/-/g,'/'))
-    showCreateTimePop.value=true
-}
-function handleConfirmCreatime(e){
-    baseInfo.createtime=moment(e).format('YYYY-MM-DD')
-    showCreateTimePop.value=false
-}
+    return props.userOpts.filter(_ => baseInfo.editors.includes(_.AdminId)).map(_ => _.AdminName).join(',')
+})
+const varietysLabel = computed(() => {
+    if(!baseInfo.varietys.length) return ''
+
+    return props.varietyOpts.filter(_ => baseInfo.varietys.includes(_.PermissionId)).map(_ => _.PermissionName).join(',') 
+})
 
-// 报告标题
-const showReportTitlePop=ref(false)
-const temReportTitleVal=ref('')
-function handleShowReportTitle(){
-    temReportTitleVal.value=baseInfo.title
-    showReportTitlePop.value=true
-}
-function handleConfirmReportTitle(){
-    baseInfo.title=temReportTitleVal.value
-    showReportTitlePop.value=false
-}
 
 //作者
 const showReportAuthorPop=ref(false)
 const temReportAuthorVal=ref('')
 function handleShowReportAuthor(){
-    temReportAuthorVal.value=baseInfo.author
+    temReportAuthorVal.value=baseInfo.editors
     showReportAuthorPop.value=true
 }
 function handleConfirmReportAuthor(){
-    baseInfo.author=temReportAuthorVal.value
+    baseInfo.editors=temReportAuthorVal.value
     showReportAuthorPop.value=false
 }
 
 
+//品种
+const showReportVarietyPop = ref(false)
+const temReportVarietyVal = ref('')
+function handleShowReportVariety() {
+    temReportVarietyVal.value = baseInfo.varietys
+    showReportVarietyPop.value = true
+}
+function handleConfirmReportVariety() {
+    baseInfo.varietys = temReportVarietyVal.value
+    showReportVarietyPop.value = false
+}
+
 
 
 function close(){
     emits('close')
 }
 
-function handleSave(){
-    // if(!baseInfo.title){
-    //     showToast('请填写标题')
-    //     return
-    // }
-    // 处理晨报选中的指标
-    baseInfo.ticket=props.ticketData.map(item=>{
-        return {
-            ...item,
-            Selected:activeTicket.value.includes(item.BaseColumnId)?1:0
-        }
-    })
+async function handleSave(){
+    if(!baseInfo.chapterName){
+        showToast('请输入章节名称')
+        return
+    }
+    let params = {
+        Title: baseInfo.chapterName,
+        PermissionIdList: baseInfo.varietys,
+        AdminIdList: baseInfo.editors
+      }
+
+    const res = props.defaultData
+        ? await apiReport.editChapterBase({
+        ReportChapterId: props.defaultData.ReportChapterId,
+        ...params
+        })
+        : await apiReport.addChapter({
+            ReportId:  Number(route.query.id),
+            ...params
+        })
+    if(res.Ret !== 200) return
+
+    showToast(res.Msg)
+
     emits('confirm',baseInfo)
 }
 
@@ -123,51 +100,31 @@ function handleSave(){
 <template>
     <div class="chapter-baseinfo-wrap">
         <van-cell-group>
-            <van-cell 
-                value-class="cell-con"
-                title="品种" 
-                :value="baseInfo.type"
+            <van-field 
+                v-model="baseInfo.chapterName"
+                label="章节名称"
+                placeholder="请输入章节名称"
+                input-align="right"     
+                required
             />
+        </van-cell-group>
+        <van-cell-group>
             <van-cell 
-                value-class="cell-con" 
-                required 
-                title="新增方式" 
+                value-class="cell-con"
+                title="关联品种" 
+                :value="varietysLabel"
                 is-link 
-                :value="getAddTypeName(baseInfo.addType)" 
-                @click="showAddTypePop=true"
+                @click="handleShowReportVariety"
             />
+        </van-cell-group>
+        <van-cell-group>
             <van-cell 
                 value-class="cell-con" 
-                title="显示指标" 
-                is-link 
-                value="" 
-                v-if="chapterInfo.ReportType==='day'" 
-                @click="showTicketPop=true"
-            >
-                <template #value>
-                    <div class="ticket-name-box">
-                        <span style="margin-right:5px">{{activeTicketArr[0]?.BaseColumnName}}</span>
-                        <van-tag type="primary" v-if="activeTicketArr.length>1">+{{activeTicketArr.length-1}}</van-tag>
-                    </div>
-                </template>
-            </van-cell>
-            <van-cell 
-                value-class="cell-con" 
-                title="创建人" 
+                title="编辑人" 
                 is-link 
-                :value="baseInfo.author"
+                :value="editorsLabel"
                 @click="handleShowReportAuthor"
             />
-            <van-cell 
-                value-class="cell-con" 
-                title="创建时间" 
-                is-link 
-                :value="baseInfo.createTime" 
-                @click="handleShowCreatetime"
-            />
-        </van-cell-group>
-        <van-cell-group style="margin-top:10px">
-             <van-cell value-class="cell-con" required title="标题" is-link :label="baseInfo.title" @click="handleShowReportTitle"/>
         </van-cell-group>
 
 
@@ -177,43 +134,23 @@ function handleSave(){
         </div>
     </div>
 
-    <!-- 新增方式 -->
-    <van-action-sheet 
-        v-model:show="showAddTypePop"
-        cancel-text="取消"
-        close-on-click-action
-        :actions="addTypeOpts" 
-        @select="selectAddType" 
-    />
-
-    <!-- 创建日期 -->
-    <van-popup 
-        v-model:show="showCreateTimePop"
-        :position="width>650?'center':'bottom'"
-        :style="width>650?{ width: '400px'}:''"
-        round
-    >
-        <van-calendar
-            :poppable="false"
-            :min-date="minDate"
-            :default-date="defaultDate"
-            title="选择创建日期"
-            @confirm="handleConfirmCreatime"
-            :style="{ height: '500px' }"
-        />
-    </van-popup>
-
-    <!-- 标题 -->
+    <!-- 品种选择 -->
     <van-popup
-        v-model:show="showReportTitlePop"
+        v-model:show="showReportVarietyPop"
         position="bottom"
         :style="{ height: '100%' }"
     >
         <div class="input-report-title-pop">
-             <van-field v-model="temReportTitleVal" placeholder="请输入报告标题" />
+            <van-checkbox-group v-model="temReportVarietyVal">
+                <van-checkbox
+                    v-for="item in varietyOpts"
+                    :key="item.PermissionId"
+                    :name="item.PermissionId"
+                >{{item.PermissionName}}</van-checkbox>
+            </van-checkbox-group>
             <div class="bot-btns">
-                <van-button class="bot-btn" type="default" @click="showReportTitlePop=false">取消</van-button>
-                <van-button class="bot-btn" type="primary" :disabled="!temReportTitleVal" @click="handleConfirmReportTitle">确定</van-button>
+                <van-button class="bot-btn" type="default" @click="showReportVarietyPop=false">取消</van-button>
+                <van-button class="bot-btn" type="primary" @click="handleConfirmReportVariety">确定</van-button>
             </div>
         </div>
     </van-popup>
@@ -225,39 +162,21 @@ function handleSave(){
         :style="{ height: '100%' }"
     >
         <div class="input-report-title-pop">
-             <van-field v-model="temReportAuthorVal" placeholder="请输入作者" />
+            <van-checkbox-group v-model="temReportAuthorVal">
+                <van-checkbox
+                    v-for="item in userOpts"
+                    :key="item.AdminId"
+                    :name="item.AdminId"
+                >{{item.AdminName}}</van-checkbox>
+            </van-checkbox-group>
             <div class="bot-btns">
                 <van-button class="bot-btn" type="default" @click="showReportAuthorPop=false">取消</van-button>
                 <van-button class="bot-btn" type="primary" :disabled="!temReportAuthorVal" @click="handleConfirmReportAuthor">确定</van-button>
             </div>
         </div>
     </van-popup>
+    
 
-    <!-- 选择晨报的指标 -->
-    <van-popup
-        v-model:show="showTicketPop"
-        position="bottom"
-        :style="{ height: '100%' }"
-    >
-        <div class="day-report-ticket-list-wrap">
-            <div v-if="ticketData.length==0">
-                <img class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
-                <p style="text-align:center;color:#999999;font-size:12px">暂无数据</p>
-            </div>
-            <ul class="list">
-                <van-checkbox-group v-model="activeTicket">
-                    <van-checkbox 
-                        :name="item.BaseColumnId" 
-                        v-for="item in ticketData" 
-                        :key="item.BaseColumnId"
-                    >{{item.BaseColumnName}}</van-checkbox>
-                </van-checkbox-group>
-            </ul>
-            <div class="btns">
-                <van-button type="primary" @click="showTicketPop=false">确定</van-button>
-            </div>
-        </div>
-    </van-popup>
 </template>
 
 <style lang="scss" scoped>
@@ -286,6 +205,19 @@ function handleSave(){
     flex-direction: column;
     justify-content: space-between;
     background-color: $page-bg-grey;
+
+    .van-checkbox-group{
+        padding: $page-padding;
+        flex: 1;
+        overflow-y: auto;
+        .van-checkbox{
+            :deep(.van-checkbox__label){
+                padding: 30px 0;
+                flex: 1;
+                border-bottom: 1px solid $border-color;
+            }
+        }
+    }
     
     .bot-btns{
         flex-shrink: 0;

+ 611 - 0
src/views/report/components/AddReportBaseInfoV2.vue

@@ -0,0 +1,611 @@
+<script setup name="AddReportInfo">
+import moment from 'moment'
+import {ref,reactive, computed} from 'vue'
+import { useRouter } from 'vue-router'
+import apiReport from '@/api/report'
+import { showToast } from 'vant'
+import { useWindowSize } from '@vueuse/core'
+import ListClassify from './ListClassify.vue'
+import CooperUserSelect from './CooperUserSelect.vue'
+import InheritReportSearch from './InheritReportSearch.vue'
+
+
+const router=useRouter()
+
+const { width, height } = useWindowSize()
+
+const props = defineProps({
+  id: {
+    type: Number,
+    default: 0,
+  },
+  defaultData: {
+    type: Object,
+    default: null
+  }
+})
+const emit = defineEmits(['close','confirm'])
+
+
+const classifyNameLabel = computed(() => {
+  if(!reportBaseInfo.classifys.length) return  ''
+
+  return reportBaseInfo.classifys.map(_ => _.text).join('/')
+})
+const relationVarietyLabel = computed(() => {
+  if(!reportBaseInfo.relationVariety.length) return  ''
+
+  return reportBaseInfo.relationVariety.map(_ => _.PermissionName).join(',')
+})
+const cooperationUsersLabel = computed(() => {
+  if(!reportBaseInfo.cooperationUsers.length) return  ''
+
+  return reportBaseInfo.cooperationUsers.map(_ => _.NodeName).join(',')
+})
+const authorLabel = computed(() => {
+  if(!reportBaseInfo.author.length) return  ''
+
+  return reportBaseInfo.author.join(',')
+})
+
+
+
+console.log(props.defaultData)
+const reportBaseInfo=reactive({
+    addType: props.defaultData?props.defaultData.addType:1,
+    inheritId: 0,//继承report id
+    classifys: props.defaultData?props.defaultData.classifyName:[],
+    relationVariety: [],
+    title: props.defaultData?props.defaultData.title:"",
+    abstract: props.defaultData?props.defaultData.abstract:"",
+    author: props.defaultData?props.defaultData.author:[],
+    time: props.defaultData?props.defaultData.createtime:moment().format("YYYY-MM-DD"),
+    cooperationType: props.defaultData?props.defaultData.cooperationType:1,//协作方式
+    cooperationUsers: props.defaultData?props.defaultData.cooperationUsers:[],
+    reportLayout: props.defaultData?props.defaultData.reportLayout:1,//报告布局
+    isPublcPublish: props.defaultData?props.defaultData.isPublcPublish:1
+})
+
+
+const authorOpts = ref([])
+// 获取作者
+function getReportauthor() {
+  apiReport.reportAuthorList().then((res) => {
+    if (res.Ret == 200) {
+      authorOpts.value = res.Data.List || [];
+    }
+  });
+}
+getReportauthor()
+
+
+
+
+// 分类弹窗
+const showClassify=ref(false)
+// 分类筛选
+function handleShowClassify() {
+  if(props.id) return
+  // temReportTitleVal.value=reportBaseInfo.title
+  showClassify.value=true
+}
+function handleConfirmClassify(arr){
+    reportBaseInfo.classifys = arr;
+    showClassify.value=false
+
+    getRelationPermission()
+    handleUpdateBaseInfo()
+}
+/* 获取关联品种 */
+async function getRelationPermission() {
+  if(!reportBaseInfo.classifys.length){
+    reportBaseInfo.relationVariety = []
+    return
+  } 
+
+  const res = await apiReport.classifyPermissionList({ClassifyId:reportBaseInfo.classifys[reportBaseInfo.classifys.length-1].id})
+
+    if(res.Ret!==200) return
+    reportBaseInfo.relationVariety = res.Data || []
+}
+
+function handleUpdateBaseInfo() {
+  reportBaseInfo.inheritId = 0;
+
+  if(!reportBaseInfo.classifys.length) return
+
+  
+  //获取上次报告
+  apiReport.getAuthReportList({
+      ClassifyIdFirst: reportBaseInfo.classifys[0].id,
+      ClassifyIdSecond: reportBaseInfo.classifys[1]?.id,
+      ClassifyIdThird: reportBaseInfo.classifys[2]?.id,
+      CurrentIndex: 1,
+      PageSize:1,
+      Keyword:'',
+    }).then((res) => {
+      if (res.Ret !== 200) return;
+      if (!res.Data.List) {
+        reportBaseInfo.addType===2 && showToast('该分类暂无报告')
+        
+        return false;
+      }
+
+      if(reportBaseInfo.addType===1) { //默认只带出作者
+        reportBaseInfo.author = res.Data.List
+          ? res.Data.List[0].Author.split(",")
+          : [''];
+      }else {
+        chooseInheritReport(res.Data.List[0])
+      }
+    });
+}
+
+/* 选择继承报告 */
+async function chooseInheritReport(item) {
+  const { Id,Title,Abstract,Author,CollaborateType,ReportLayout,IsPublicPublish,ClassifyIdFirst,ClassifyIdSecond,ClassifyIdThird,ClassifyNameFirst,ClassifyNameSecond,ClassifyNameThird } = item;
+
+  reportBaseInfo.title = Title;
+  reportBaseInfo.abstract = Abstract;
+  reportBaseInfo.author = Author
+        ? Author.split(",")
+        : "";;
+  reportBaseInfo.cooperationType = CollaborateType;
+  reportBaseInfo.inheritId = Id;
+  reportBaseInfo.reportLayout = ReportLayout;
+  reportBaseInfo.isPublcPublish = IsPublicPublish;
+  reportBaseInfo.classifys = [
+      {
+          id:ClassifyIdFirst,
+          text:ClassifyNameFirst,
+      },
+      {
+          id:ClassifyIdSecond,
+          text:ClassifyNameSecond,
+      },
+      {
+          id:ClassifyIdThird,
+          text:ClassifyNameThird,
+      }
+  ]
+  
+  //继承的章节报告默认带出协作人
+  if(CollaborateType===1) {
+    reportBaseInfo.cooperationUsers = [];
+  }else {
+    const res = await apiReport.getRportBase({
+      ReportId: Id
+    })
+    if(res.Ret!==200) return
+    
+    reportBaseInfo.cooperationUsers = res.Data.GrandAdminList
+      ? res.Data.GrandAdminList.map(_ => ({
+          NodeId: _.AdminId,
+          NodeName: _.AdminName
+        }))
+      : [];
+  }
+}
+
+
+
+//作者弹窗
+const showAuthorPop=ref(false)
+const temAuthorVal=ref('')
+function handleShowAuthor(){
+    temAuthorVal.value=reportBaseInfo.author
+    showAuthorPop.value=true
+}
+function handleConfirmAuthor(){
+    reportBaseInfo.author=temAuthorVal.value
+    showAuthorPop.value=false
+}
+
+
+// 创建日期
+const minDate=new Date(2015, 0, 1)
+const defaultDate=ref(new Date())
+const showCreateTimePop=ref(false)
+function handleShowCreatetime(){
+    defaultDate.value=new Date(reportBaseInfo.time.replace(/-/g,'/'))
+    showCreateTimePop.value=true
+}
+function handleConfirmCreatime(e){
+    reportBaseInfo.time=moment(e).format('YYYY-MM-DD')
+    showCreateTimePop.value=false
+}
+
+
+/* 协作人弹窗 */
+const isChooseCooperaUserPop = ref(false)
+//选择协作人
+function handleShowCooperUser() {
+  isChooseCooperaUserPop.value = true
+}
+function handleConfirmCooperaUser(e) {
+  reportBaseInfo.cooperationUsers = e;
+  
+  isChooseCooperaUserPop.value = false
+}
+
+
+/* 继承报告弹窗 */
+const isChooseInherReportPop = ref(false)
+
+
+
+function close() {
+  emit('close')
+}
+async function handleSave() {
+  if(!reportBaseInfo.title) return showToast('请输入报告标题')
+  if(!reportBaseInfo.classifys.length) return showToast('请选择报告分类')
+
+  const params = {
+    AddType: reportBaseInfo.addType,
+    ClassifyIdFirst: reportBaseInfo.classifys[0]
+      ? reportBaseInfo.classifys[0].id
+      : 0,
+    ClassifyNameFirst: reportBaseInfo.classifys[0]?.text,
+    ClassifyIdSecond: reportBaseInfo.classifys[1]
+      ? reportBaseInfo.classifys[1].id
+      : 0,
+    ClassifyNameSecond: reportBaseInfo.classifys[1]?.text,
+    ClassifyIdThird: reportBaseInfo.classifys[2]
+      ? reportBaseInfo.classifys[2].id
+      : 0,
+    ClassifyNameThird: reportBaseInfo.classifys[2]?.text,
+    Title: reportBaseInfo.title,
+    Abstract: reportBaseInfo.abstract,
+    Author: reportBaseInfo.author.length > 0
+            ? reportBaseInfo.author.join(",")
+            : "",
+    CreateTime: reportBaseInfo.time,
+    ReportLayout: reportBaseInfo.reportLayout,
+    CollaborateType: reportBaseInfo.cooperationType,
+    IsPublicPublish: reportBaseInfo.isPublcPublish,
+    InheritReportId: reportBaseInfo.inheritId,
+    GrantAdminIdList: reportBaseInfo.cooperationUsers.map(_ => _.NodeId)
+  };
+
+
+  // 编辑
+  if (props.id) {
+
+    apiReport.reportEdit({...params,ReportId: Number(props.id)}).then(res => {
+      if (res.Ret !== 200) return
+      
+      emit("confirm", reportBaseInfo);
+      return;
+    })
+  }else {
+    
+    apiReport.reportAdd(params).then((res) => {
+      if (res.Ret === 200) {
+  
+        let pathInfo = {};
+        if(reportBaseInfo.cooperationType===2) {
+          pathInfo = {
+            path: "/report/chapter/list",
+            query: { 
+              id: res.Data.ReportId
+            },
+          }
+        }else {
+            pathInfo = {
+              path: reportBaseInfo.reportLayout===1 
+                ? '/report/edit'
+                : "/smart_report/edit",
+              query: { 
+                id: res.Data.ReportId,
+                coopType: params.CollaborateType
+              },
+            }
+        }
+
+        // let { href } = router.resolve(pathInfo);
+        // window.open(href, "_blank");
+        router.push(pathInfo)
+  
+        close();
+      }
+    });
+  }
+}
+
+</script>
+<template>
+  <div class="reportInfo-page">
+    <van-cell-group>
+      <van-field name="radio" label="报告类型">
+        <template #input>
+          <van-radio-group 
+            v-model="reportBaseInfo.addType" 
+            direction="horizontal" 
+            @change="handleUpdateBaseInfo"
+            :disabled="id?true:false"
+          >
+            <van-radio :name="1">新增</van-radio>
+            <van-radio :name="2">继承</van-radio>
+          </van-radio-group>
+        </template>
+      </van-field>
+    </van-cell-group>
+    <van-cell-group>
+      <van-cell
+        required
+        title="报告分类"
+        :label="classifyNameLabel"
+        :is-link="id?false:true"
+        @click="handleShowClassify"
+      />
+    </van-cell-group>
+    <van-cell-group v-if="reportBaseInfo.relationVariety.length">
+      <van-cell
+        title="关联品种"
+        :label="relationVarietyLabel"
+      />
+    </van-cell-group>
+    <van-cell-group>
+      <van-field 
+        v-model="reportBaseInfo.title"
+        label="报告标题"
+        placeholder="请输入报告标题"
+        input-align="right"
+      >
+        <template #button v-if="reportBaseInfo.addType===2&&!id">
+          <van-button size="small" type="primary" @click="isChooseInherReportPop=true">选择继承报告</van-button>
+        </template>
+      </van-field>
+    </van-cell-group>
+    <van-cell-group>
+      <van-field
+        v-model="reportBaseInfo.abstract"
+        rows="2"
+        autosize
+        label="报告摘要"
+        type="textarea"
+        placeholder="请输入摘要"
+        input-align="right"
+      />
+    </van-cell-group>
+    <van-cell-group>
+      <van-cell
+        required
+        title="报告作者"
+        :value="authorLabel"
+        is-link
+        @click="handleShowAuthor"
+      />
+     
+    </van-cell-group>
+    <van-cell-group>
+      <van-cell
+        required
+        title="创建时间"
+        :value="reportBaseInfo.time"
+        is-link
+        @click="handleShowCreatetime"
+      />
+    </van-cell-group>
+    <van-cell-group>
+      <van-field name="radio" label="协作方式">
+        <template #input>
+          <van-radio-group 
+            v-model="reportBaseInfo.cooperationType" 
+            direction="horizontal"
+            :disabled="id||reportBaseInfo.addType===2"
+          >
+            <van-radio :name="1">个人</van-radio>
+            <van-radio :name="2">多人协作</van-radio>
+          </van-radio-group>
+        </template>
+      </van-field>
+    </van-cell-group>
+    <van-cell-group v-if="reportBaseInfo.cooperationType===2">
+      <van-cell
+        title="协作人"
+        :value="cooperationUsersLabel"
+        is-link
+        @click="handleShowCooperUser"
+      />
+    </van-cell-group>
+    <van-cell-group>
+      <van-field name="radio" label="报告布局">
+        <template #input>
+          <van-radio-group 
+            v-model="reportBaseInfo.reportLayout" 
+            direction="horizontal"
+            :disabled="id||reportBaseInfo.addType===2"
+            >
+            <van-radio :name="1">常规布局</van-radio>
+            <van-radio :name="2">智能布局</van-radio>
+          </van-radio-group>
+        </template>
+      </van-field>
+    </van-cell-group>
+    <van-cell-group>
+      <van-field name="radio" label="公开发布">
+        <template #input>
+          <van-radio-group v-model="reportBaseInfo.isPublcPublish" direction="horizontal">
+            <van-radio :name="1">是</van-radio>
+            <van-radio :name="2">否</van-radio>
+          </van-radio-group>
+        </template>
+      </van-field>
+    </van-cell-group>
+
+    <div class="bot-btns">
+      <van-button class="bot-btn" type="default" @click="close"
+        >取消</van-button
+      >
+      <van-button class="bot-btn" type="primary" @click="handleSave"
+        >保存</van-button
+      >
+    </div>
+  </div>
+
+
+  <!-- 创建日期 -->
+  <van-popup
+    v-model:show="showCreateTimePop"
+    :position="width > 650 ? 'center' : 'bottom'"
+    :style="width > 650 ? { width: '400px' } : ''"
+    round
+  >
+    <van-calendar
+      :poppable="false"
+      :min-date="minDate"
+      :default-date="defaultDate"
+      v-model:show="showCreateTimePop"
+      title="选择创建日期"
+      @confirm="handleConfirmCreatime"
+      :style="{ height: '500px' }"
+    />
+  </van-popup>
+
+  <!-- 作者 -->
+  <van-popup
+    v-model:show="showAuthorPop"
+    position="bottom"
+    :style="{ height: '100%' }"
+  >
+    <div class="select-author-pop">
+        <van-checkbox-group v-model="temAuthorVal">
+            <van-checkbox
+                v-for="item in authorOpts"
+                :key="item.Id"
+                :name="item.ReportAuthor"
+            >{{item.ReportAuthor}}</van-checkbox>
+        </van-checkbox-group>
+        <div class="bot-btns">
+            <van-button class="bot-btn" type="default" @click="showAuthorPop=false">取消</van-button>
+            <van-button class="bot-btn" type="primary" @click="handleConfirmAuthor">确定</van-button>
+        </div>
+    </div>
+  </van-popup>
+
+
+  <!-- 分类弹窗 -->
+  <van-popup 
+      v-model:show="showClassify"
+      :position="width>650?'center':'bottom'"
+      :style="width>650?{ width: '400px'}:''"
+      round
+  >
+      <ListClassify
+        :defaultVal="defaultData?props.defaultData.classifyName:null"
+        @close="showClassify=false" 
+        @confirm="handleConfirmClassify"
+      />
+  </van-popup>
+
+  <!-- 协作人弹窗 -->
+  <van-popup 
+      v-model:show="isChooseCooperaUserPop"
+      position="bottom"
+      :style="{height: '100%'}"
+  >
+      <CooperUserSelect
+        v-if="isChooseCooperaUserPop"
+        :defaultVal="reportBaseInfo.cooperationUsers"
+        @close="isChooseCooperaUserPop=false" 
+        @confirm="handleConfirmCooperaUser"
+      />
+  </van-popup>
+
+  <!-- 继承报告弹窗 -->
+  <van-popup 
+      v-model:show="isChooseInherReportPop"
+      position="bottom"
+      :style="{height: '100%'}"
+  >
+      <InheritReportSearch
+        v-if="isChooseInherReportPop"
+        @close="isChooseInherReportPop=false" 
+        @confirm="chooseInheritReport"
+      />
+  </van-popup>
+</template>
+<style scoped lang="scss">
+.reportInfo-page{
+    min-height: 95vh;
+    position: relative;
+    background: #EDEDED;
+    padding-bottom: 140px;
+    :deep(.cell-con){
+        flex: 2;
+    }
+    
+    :deep(.van-cell-group) {
+      margin: 10px 0;
+    }
+    .bot-btns{
+        height: 140px;
+        position: fixed;
+        bottom: 0;
+        left: 0;
+        width: 100%;
+        background-color: #fff;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+    }
+}
+.bot-btn{
+    width: 315px;
+    margin: 0 10px;
+}
+.input-report-title-pop{
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    background-color: $page-bg-grey;
+    
+    .bot-btns{
+        flex-shrink: 0;
+        padding: 20px 0;
+        text-align: center;
+    }
+}
+.select-author-pop{
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    .van-checkbox-group{
+        padding: $page-padding;
+        flex: 1;
+        overflow-y: auto;
+        .van-checkbox{
+            :deep(.van-checkbox__label){
+                padding: 32px 0;
+                flex: 1;
+                border-bottom: 1px solid $border-color;
+            }
+        }
+    }
+    .bot-btns{
+        flex-shrink: 0;
+        padding: 20px 0;
+        text-align: center;
+    }
+}
+@media screen and (min-width:$media-width){
+    .reportInfo-page{
+        height: calc(100dvh - 60px);
+        min-height: calc(95vh - 60px);
+        .bot-btns{
+            bottom: 24px;
+        }
+    }
+    .bot-btn{
+        margin: 0 10px;
+    }
+    .input-report-title-pop{
+        .bot-btns{
+            padding: 10px 0;
+        }
+    }
+}
+</style>

+ 127 - 0
src/views/report/components/CooperUserSelect.vue

@@ -0,0 +1,127 @@
+<script setup>
+import { ref } from 'vue'
+import apiReport from '@/api/report'
+import _ from 'lodash'
+
+const props = defineProps({
+  defaultVal: null
+})
+const emit = defineEmits(['close',' confirm'])
+
+
+const activeUser = ref(props.defaultVal?props.defaultVal.map(_ => _.NodeId):[])
+const list = ref([])
+const userOpts = ref([])
+const keyWord = ref([])
+async function getUser() {
+  const res = await apiReport.getSystemUser({
+    KeyWord: ''
+  })
+  
+  if(res.Ret !== 200) return 
+//   list.value = res.Data || [];
+    
+  list.value = filterTreeEmpty({Children:res.Data||[]})
+	userOpts.value =  _.cloneDeep(list.value);
+
+}
+getUser()
+
+// 递归处理数组
+function filterTreeEmpty(arr) {
+    let users = []
+    function dfs(node) {
+        if (Array.isArray(node.Children)) {
+            for (let child of node.Children) {
+                dfs(child);
+            }
+            if(node.Children.length===0) delete node.Children
+        }
+        //若为叶子节点,且不为用户,禁止选中
+        if(!node.Children||!Array.isArray(node.Children)){
+            if(node.NodeType===3) users.push(node)
+        }
+    }
+    dfs(arr);
+
+    return users
+}
+
+function handleSearch() {
+	userOpts.value = list.value.filter(_ => _.NodeName.startsWith(keyWord.value))
+}
+
+function handleCancle() {
+  emit('close')
+}
+function handleConfirm() {
+
+	let selectUsers = list.value.filter(_ => activeUser.value.includes(_.NodeId));
+	emit('confirm',selectUsers)
+}
+</script>
+<template>
+  <div class="choose-user-wrap">
+		<div class="top-box">
+			<van-search 
+					shape="round"
+					placeholder="请输入用户"
+					v-model="keyWord"
+					@search="handleSearch"
+			/>
+		</div>	
+
+		<van-checkbox-group v-model="activeUser">
+				<van-checkbox
+						v-for="item in userOpts"
+						:key="item.NodeId"
+						:name="item.NodeId"
+				>{{item.NodeName}}</van-checkbox>
+		</van-checkbox-group>
+
+    <div class="bot-btns">
+        <van-button class="bot-btn" type="default" @click="handleCancle">取消</van-button>
+        <van-button class="bot-btn" type="primary" @click="handleConfirm">确定</van-button>
+    </div>
+      
+  </div>
+</template>
+<style scoped lang="scss">
+.choose-user-wrap{
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+		.top-box{
+				position: sticky;
+        top: 0;
+        z-index: 99;
+        background-color: #fff;
+    }
+		.van-checkbox-group{
+				padding: $page-padding;
+				flex: 1;
+				overflow-y: auto;
+				.van-checkbox{
+						:deep(.van-checkbox__label){
+								padding: 32px 0;
+								flex: 1;
+								border-bottom: 1px solid $border-color;
+						}
+				}
+		}
+    
+    .bot-btns{
+			flex-shrink: 0;
+			padding: 20px 0;
+			text-align: center;
+			.bot-btn {
+				width: 315px;
+    		margin: 0 10px;
+			}
+    }
+}
+@media screen and (min-width:$media-width){
+    .choose-user-wrap{
+    }
+}
+</style>

+ 213 - 0
src/views/report/components/InheritReportSearch.vue

@@ -0,0 +1,213 @@
+<script setup name="InheritReportSearch">
+import {ref,reactive} from 'vue'
+import apiReport from '@/api/report'
+import { showToast } from 'vant'
+import moment from 'moment'
+import { useRouter } from 'vue-router'
+
+const emit = defineEmits(['close','confirm'])
+
+const router=useRouter()
+
+
+const keyword=ref('')
+const selectItem = ref({})
+const listState = reactive({
+    list:[],
+    page:1,
+    pageSize:20,
+    finished:false,
+    loading:false
+})
+async function getList(){
+    const res=await apiReport.getAuthReportList({
+        CurrentIndex:listState.page,
+        PageSize:listState.pageSize,
+        Keyword:keyword.value
+    })
+    if(res.Ret===200){
+        listState.loading=false
+        if(!res.Data){
+            listState.finished=true
+            return
+        }
+        
+        listState.finished=res.Data.Paging.IsEnd
+        const arr=res.Data.List||[]
+        listState.list=[...listState.list,...arr]
+    }
+}
+function onLoad(){
+    console.log('onload');
+    listState.page++
+    getList()
+}
+
+function handleSearch(){
+    if(!keyword.value){
+        showToast('请输入关键词')
+        return
+    }
+    listState.page=1
+    listState.list=[]
+    listState.finished=false
+    getList()
+}
+
+
+function handleCancle() {
+  emit('close')
+}
+function handleConfirm() {
+  handleCancle()
+	emit('confirm',selectItem.value)
+}
+</script>
+
+<template>
+    <div class="report-search-page">
+        <div class="search-box">
+            <van-search 
+                shape="round"
+                placeholder="请输入报告标题"
+                v-model="keyword"
+                @search="handleSearch"
+            />
+        </div>
+        
+
+        <img v-if="listState.list.length==0&&listState.finished&&keyword" class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
+        <template v-if="keyword">
+        <van-list
+            v-model:loading="listState.loading"
+            :finished="listState.finished"
+            :finished-text="listState.list.length>0?'没有更多了':'暂无报告'"
+            :immediate-check="false"
+            @load="onLoad"
+        >
+            <ul class="list-wrap">
+                <li 
+                    v-for="item in listState.list"
+                    :key="item.Id"
+                    :class="[
+                      'item',
+                      {'active': selectItem?.Id===item.Id}
+                    ]" 
+                    @click="selectItem=item"
+                >
+                    <h2 :class="['van-ellipsis title',item.Title.startsWith('【')?'inline-title':'']">{{item.Title}}</h2>
+                    <p class="van-multi-ellipsis--l2 des">{{item.Abstract}}</p>
+                    <div class="bot-info">
+                        <div>
+                            <span style="margin-right:10px">{{moment(item.ModifyTime).format('YYYY-MM-DD HH:mm:ss')}}</span>
+                            <span>(第{{item.Stage}}期)</span>
+                        </div>
+                        <div>{{item.ClassifyNameFirst}}</div>
+                    </div>
+                </li>
+            </ul>
+        </van-list>
+        </template>
+
+
+      <div class="bot-btns">
+          <van-button class="bot-btn" type="default" @click="handleCancle">取消</van-button>
+          <van-button class="bot-btn" type="primary" @click="handleConfirm">确定</van-button>
+      </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.list-wrap{
+    padding: 30px 34px;
+    .item{
+        padding: 20px;
+        margin-bottom: 20px;
+        border: 1px solid $border-color;
+        box-shadow: 0px 3px 12px rgba(52, 75, 120, 0.08);
+        border-radius: 8px;
+        .title{
+            font-size: 32px;
+            line-height: 44px;
+            margin: 0;
+        }
+        .inline-title{
+            margin-left: -14px;
+        }
+        .des{
+            margin-top: 10px;
+            margin-bottom: 20px;
+            font-size: 28px;
+            color: $font-grey;
+            min-height: 60px;
+        }
+        .bot-info{
+            display: flex;
+            justify-content: space-between;
+            color: $font-grey;
+            font-size: 28px;
+            .active-status{
+                color: $font-success;
+            }
+        }
+        &.active {
+          border-color: $theme-color;
+        }
+    }
+}
+.report-search-page{
+    padding-bottom: 130px;
+    .search-box{
+        position: sticky;
+        top: 0;
+        z-index: 99;
+        background-color: #fff;
+    }
+}
+.bot-btns{
+  width: 100%;
+  height: 130px;
+  position: fixed;
+  bottom: 0;
+  flex-shrink: 0;
+  padding: 20px 0;
+  text-align: center;
+  background-color: #fff;
+  .bot-btn {
+    width: 315px;
+    margin: 0 10px;
+  }
+}
+
+@media screen and (min-width:$media-width){
+    .list-wrap{
+        padding: 30px;
+        .item{
+            padding: 20px;
+            margin-bottom: 20px;
+            border-radius: 4px;
+            .title{
+                font-size: 16px;
+                line-height: 22px;
+            }
+            .inline-title{
+                margin-left: -14px;
+            }
+            .des{
+                margin-top: 5px;
+                margin-bottom: 10px;
+                font-size: 14px;
+                min-height: 30px;
+            }
+            .bot-info{
+                font-size: 14px;
+            }
+        }
+    }
+    .report-search-page{
+        .search-box{
+            top: 60px;
+        }
+    }
+}
+</style>

+ 35 - 49
src/views/report/components/ListClassify.vue

@@ -1,6 +1,7 @@
 <script setup>
 import {ref} from 'vue'
 import apiReport from '@/api/report'
+import { showToast } from 'vant'
 
 const props=defineProps({
     defaultVal:null,
@@ -13,39 +14,21 @@ const props=defineProps({
 const emits=defineEmits(['close','confirm'])
 
 const list=ref([])
+const childList = ref([])
 function getClassifyList(){
     apiReport.getClassifyList({
         CurrentIndex:1,
         PageSize:1000,
         KeyWord:'',
-        HideDayWeek:props.hideDayWeek,
         Enabled:props.enable
     }).then(res=>{
         if(res.Ret===200){
             const arr=res.Data.List||[]
-            list.value=arr.map(item=>{
-                const child=item.Child?item.Child.map(e=>{
-                    return {
-                        text:e.ClassifyName,
-                        id:e.Id,
-                        HasTeleconference:e.HasTeleconference
-                    }
-                }):[]
-                return {
-                    text:item.ClassifyName,
-                    id:item.Id,
-                    children:child,
-                    disabled:props.firstClassifyDisabled&&child.length===0?true:false,
-                    HasTeleconference:item.HasTeleconference
-                }
-            })
-            if(props.defaultVal&&props.defaultVal[1]){
-                activeId.value=props.defaultVal[1].id
-                list.value.forEach((item,index)=>{
-                    if(item.id===props.defaultVal[0].id){
-                        activeIndex.value=index
-                    }
-                })
+            list.value = arr;
+
+            if(props.defaultVal){
+                activeId.value=props.defaultVal[props.defaultVal.length-1].id
+                selectClassify.value = props.defaultVal
             }
         }
     })
@@ -53,36 +36,28 @@ function getClassifyList(){
 getClassifyList()
 
 const activeId = ref(null);
-const activeIndex = ref(0);
+const selectClassify = ref([])
 
 function handleCancle(){
     emits('close')
 }
 
 function handleReset(){
-    activeIndex.value=0
     activeId.value=null
-    emits('confirm',{firstClassify:'',secondClassify:''})
+    emits('confirm',[])
 }
 
-function handleConfirm(){
-    const firstClassify={
-        text:list.value[activeIndex.value].text,
-        id:list.value[activeIndex.value].id,
-        HasTeleconference:list.value[activeIndex.value].HasTeleconference,
-    }
-    let secondClassify={text:'',id:'',HasTeleconference:''}
-    if(activeId.value){
-        list.value[activeIndex.value].children.forEach(e => {
-            if(e.id===activeId.value){
-                secondClassify.text=e.text
-                secondClassify.id=e.id
-                secondClassify.HasTeleconference=e.HasTeleconference
-            }
-        });
-    }
+function onFinish(e) {
+    console.log(e)
+    selectClassify.value = e.selectedOptions.map(_ => ({
+        text:_.ClassifyName,
+        id:_.Id,
+    }))
+}
 
-    emits('confirm',{firstClassify,secondClassify})
+function handleConfirm(){
+    if(selectClassify.value.length===0) return showToast('请选择最小级分类')
+    emits('confirm',selectClassify.value)
 }
 </script>
 
@@ -94,12 +69,20 @@ function handleConfirm(){
             <span style="font-size:18px;font-weight:bold">选择分类</span>
             <span style="color:#0052D9" @click="handleConfirm">确定</span>
         </div>
-        <van-tree-select
-            v-model:active-id="activeId"
-            v-model:main-active-index="activeIndex"
-            :items="list"
-            @click-nav="activeId=null"
+
+        <van-cascader
+            v-model="activeId"
+            :show-header="false"
+            :closeable="false"
+            :options="list"
+            :field-names="{
+                text: 'ClassifyName',
+                value: 'Id',
+                children: 'Child'
+            }"
+            @finish="onFinish"
         />
+        
     </div>
 </template>
 
@@ -114,6 +97,9 @@ function handleConfirm(){
         padding: 0 34px;
     }
 }
+.van-cascader {
+    width: 100%;
+}
 @media screen and (min-width:$media-width){
     .report-list-classify-wrap{
         .top-box{

+ 281 - 0
src/views/report/components/ReportFilter.vue

@@ -0,0 +1,281 @@
+<script setup>
+import { reactive, ref } from 'vue'
+import { useWindowSize } from '@vueuse/core'
+import moment from 'moment'
+
+const { width, height } = useWindowSize()
+
+const props = defineProps({
+  filter: {
+    type:Object
+  }
+})
+const emit = defineEmits(['change','close'])
+
+const timeTypeOpt=[
+    {
+        text:'发布时间',
+        value:'publish_time'
+    },
+    {
+        text:'审批时间',
+        value:'approve_time'
+    },
+    {
+        text:'更新时间',
+        value:'modify_time'
+    },
+]
+const reportStatusOpt=[
+    {
+        text:'已推送消息',
+        value:2
+    },
+    {
+        text:'未推送消息',
+        value:1
+    }
+]
+const publishStatusOpt=[
+    {
+        text:'未发布',
+        value:1
+    },
+    {
+        text:'已发布',
+        value:2
+    },
+    {
+        text:'待提交',
+        value:3
+    },
+    {
+        text:'待审批',
+        value:4
+    },
+    {
+        text:'已驳回',
+        value:5
+    },
+    {
+        text:'已通过',
+        value:6
+    },
+]
+
+const filterState = reactive({
+  publishStatus: props.filter.publishStatus||'',
+  msgIsSend: props.filter.MsgIsSend||'',
+  timeType: props.filter.timeType||'',
+  startDate: props.filter.StartDate,
+  endDate:props.filter.EndDate,
+  date:[]
+})
+
+function handleSave() {
+  emit('change',filterState)
+}
+function handleClose() {
+  emit('close')
+}
+
+
+function getName(key) {
+  const nameMap = {
+    'publishStatus': publishStatusOpt.find(_ => _.value===filterState.publishStatus),
+    'timeType': timeTypeOpt.find(_ => _.value===filterState.timeType),
+    'msgIsSend': reportStatusOpt.find(_ => _.value===filterState.msgIsSend)
+  }
+
+  let title = nameMap[key]?.text
+  return title
+}
+function getDate() {
+    if(!filterState.startDate||!filterState.endDate) return ''
+
+    return `${filterState.startDate}~${filterState.endDate}`
+}
+
+
+const showPicker = ref(false)
+const selectKey = ref('')
+const selectTimeType = ref([])
+const selectStatus = ref([])
+const selectMsgSend = ref([])
+function handleOpenPicker(key) {
+    selectKey.value = key;
+    selectTimeType.value = [filterState[key]]
+    selectStatus.value = [filterState[key]]
+    selectMsgSend.value = [filterState[key]]
+    showPicker.value = true;
+
+    console.log( selectTimeType.value)
+}
+function handleConfirmTimeType() {
+  filterState.timeType=selectTimeType.value[0]
+  showPicker.value=false
+}
+function handleConfirmStatus(e) {
+  filterState.publishStatus=selectStatus.value[0]
+  showPicker.value=false
+}
+function handleConfirmMsgSend() {
+  console.log(selectMsgSend.value)
+  filterState.msgIsSend=selectMsgSend.value[0]
+  showPicker.value=false
+}
+
+
+
+/* 日期弹窗 */
+const selectDate = ref([])
+const showCalendar = ref(false)
+const calendarMinDate=new Date(2010,0,1)
+const calendarIns=ref(null)
+function handleOpenDatePicker() {
+    selectDate.value = [filterState.startDate?new Date(filterState.startDate):'',filterState.endDate?new Date(filterState.endDate):'']
+    showCalendar.value = true;
+}
+function handleCalendarChange(e){
+    filterState.startDate=moment(e[0]).format('YYYY-MM-DD')
+    filterState.endDate=moment(e[1]).format('YYYY-MM-DD')
+    
+    showCalendar.value=false
+}
+function handleResetCalendar(){
+    filterState.startDate=''
+    filterState.endDate=''
+    calendarIns.value.reset()
+
+    showCalendar.value=false
+}
+</script>
+<template>
+  <div class="filter-cont">
+      <van-cell-group>
+          <van-cell
+              value-class="cell-con"
+              required
+              title="报告状态"
+              :value="getName('publishStatus')"
+              is-link
+              @click="handleOpenPicker('publishStatus')"
+          />
+      </van-cell-group>
+      <van-cell-group>
+          <van-cell
+              value-class="cell-con"
+              required
+              title="推送消息状态"
+              :value="getName('msgIsSend')"
+              is-link
+              @click="handleOpenPicker('msgIsSend')"
+          />
+      </van-cell-group>
+      <van-cell-group>
+          <van-cell
+              value-class="cell-con"
+              required
+              title="筛选时间类型"
+              :value="getName('timeType')"
+              is-link
+              @click="handleOpenPicker('timeType')"
+          />
+      </van-cell-group>
+      <van-cell-group>
+          <van-cell
+              value-class="cell-con"
+              required
+              :title="getName('timeType')"
+              :value="getDate()"
+              is-link
+              @click="handleOpenDatePicker"
+          />
+      </van-cell-group>
+
+
+      <div class="bot-btns">
+          <van-button class="bot-btn" type="default" @click="handleClose"
+              >取消</van-button
+          >
+          <van-button class="bot-btn" type="primary" @click="handleSave"
+              >确认</van-button
+          >
+      </div>
+  </div>
+
+    <!-- 日期筛选 -->
+    <van-popup 
+        v-model:show="showCalendar"
+        :position="width>650?'center':'bottom'"
+        :style="width>650?{ width: '400px'}:''"
+        round
+    >
+        <van-calendar
+            :default-date="selectDate"
+            ref="calendarIns"
+            :poppable="false"
+            type="range"
+            allow-same-day
+            :min-date="calendarMinDate"
+            @confirm="handleCalendarChange" 
+            :style="{ height: '500px' }"
+            class="calendar-box"
+        >
+            <template #title>
+                <div style="position: relative;">
+                    <span style="color:#666;position: absolute;left:16px" @click="handleResetCalendar">重置</span>
+                    <span>日期选择</span>
+                </div>
+            </template>
+        </van-calendar>
+    </van-popup>
+
+
+    <!-- 下拉选择 -->
+    <van-popup 
+        v-model:show="showPicker"
+        :position="width>650?'center':'bottom'"
+        :style="width>650?{ width: '400px'}:''"
+        round
+    >
+        <van-picker
+        v-if="selectKey==='timeType'"
+        v-model="selectTimeType"
+        :columns="timeTypeOpt"
+        @cancel="showPicker = false"
+        @confirm="handleConfirmTimeType"
+        />
+        <van-picker
+        v-else-if="selectKey==='publishStatus'"
+        v-model="selectStatus"
+        :columns="publishStatusOpt"
+        @cancel="showPicker = false"
+        @confirm="handleConfirmStatus"
+        />
+        <van-picker
+        v-else-if="selectKey==='msgIsSend'"
+        v-model="selectMsgSend"
+        :columns="reportStatusOpt"
+        @cancel="showPicker = false"
+        @confirm="handleConfirmMsgSend"
+        />
+    </van-popup>  
+
+
+</template>
+<style scoped lang="scss">
+.filter-cont {
+    .bot-btns{
+        position: absolute;
+        bottom: 48px;
+        left: 0;
+        width: 100%;
+        text-align: center;
+        .bot-btn{
+            width: 315px;
+            margin: 0 10px;
+        }
+    }
+}
+</style>

+ 2 - 1
src/views/report/components/reportInsert/Index.vue

@@ -19,7 +19,8 @@ function handleSelectChart(data){
 
 function handleConfirmInsert(){
     if(['图表插入','批量插入'].includes(activeType.value)){
-        let filterList = activeType.value === '批量插入' ? list.value.filter(_ => _.HaveOperaAuth) : list.value;
+        console.log(list.value)
+        let filterList = list.value;
 
         emits('insert',{list: filterList,type:'iframe',chartType:'chart'})
     }else if(activeType.value==='表格插入'){

+ 304 - 0
src/views/report/hooks/useReport.js

@@ -0,0 +1,304 @@
+import { ref } from 'vue'
+import { router } from '@/router'
+import {Base64} from 'js-base64'
+import apiReport from '@/api/report'
+import {getSystemInfo} from '@/api/common'
+import { showToast,showDialog } from 'vant'
+import _ from 'lodash'
+import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
+
+const publicSettingStore = usePublicSettingStore()
+const { checkAuthBtn } = useAuthBtn()
+
+
+/* 报告操作 */
+export function useReportHandles() {
+	
+	// 水印
+	const waterMarkStr=ref('')
+	function getSystemInfoFun() {
+			getSystemInfo().then(res=>{
+					if(res.Ret===200){
+						const systemUserInfo=res.Data
+						// 设置水印文案
+						let waterMarkString=''
+						if(systemUserInfo){
+							waterMarkString=`${systemUserInfo.RealName}${systemUserInfo.Mobile?systemUserInfo.Mobile:systemUserInfo.Email}`
+							waterMarkString=encodeURIComponent(waterMarkString)
+							waterMarkStr.value=Base64.encode(waterMarkString)
+						}
+					}
+			})
+	}
+	getSystemInfoFun()
+
+
+  // 刷新所有图表和表格
+  const handleRefresh = _.debounce (async function({id,chapterId}) {
+    let code_arr = [];
+    let sheet_code_arr = []
+    $('iframe').each((k,i) => {
+        try {
+            let href = $(i).attr('src');
+            if(href.includes('chartshow')){
+                code_arr.push(getUrlParams(href,'code'));
+            }
+            if(href.includes('sheetshow')){
+                sheet_code_arr.push(getUrlParams(href,'code'))
+            }
+        } catch (err) {
+        }
+    });
+
+    if(!code_arr.length&&!sheet_code_arr.length) return showToast('请插入图表');
+
+    if(id&&code_arr.length) {
+        let res = await apiReport.getChartRefreshStatus({
+            Source: 'report',
+            PrimaryId:id,
+            SubId: chapterId
+        });
+        
+        if(!res.Data.RefreshResult) return showToast('图表正在刷新中,请勿重复操作')
+
+        const { Ret,Msg } = await apiReport.reportChartRefresh({
+            ChartInfoCode: code_arr,
+            Source: 'report',
+            PrimaryId: id,
+            SubId: chapterId
+        })
+        
+        if(Ret === 200) {
+            $('iframe').each((k,i) => {
+                let href = $(i).attr('src');
+                if(href.includes('chartshow')){
+                    $(i).attr('src',$(i).attr('src'))
+                }
+            });
+            showToast(Msg);
+        }
+    }
+    if(id&&sheet_code_arr.length){
+        //获取刷新结果
+        let res = await apiReport.getSheetRefreshResult({
+            Source: 'report',
+            PrimaryId: id,
+            SubId: chapterId
+        });
+        if(!res.Data.RefreshResult) return showToast('表格正在刷新中,请勿重复操作')
+        const { Ret,Msg } = await apiReport.reportSheetRefresh({
+            ExcelCodes: sheet_code_arr,
+            Source: 'report',
+            PrimaryId: id,
+            SubId: chapterId
+        })
+        
+        if(Ret === 200) {
+            $('iframe').each((k,i) => {
+                let href = $(i).attr('src');
+                if(href.includes('sheetshow')){
+                    $(i).attr('src',$(i).attr('src'))
+                }
+            });
+            showToast(Msg);
+        }
+    }
+
+  },1000)
+
+
+  /* 提交报告进入审批流 */
+  function handleSubmitReport({id}) {
+    showDialog({
+        title: '提示',
+        message: '是否确认提交该报告进入审批流程?',
+        showCancelButton:true
+    }).then(()=>{
+        apiReport.reportCnSubmit({
+            ReportId: id
+        }).then(res=>{
+            if(res.Ret!==200) return 
+            showToast('提交成功')
+            router.back()
+        })
+    }).catch(()=>{})
+  }
+
+
+	const isPost = checkAuthBtn(reportManageBtn.reportManage_sendMsg)
+	//发布 定时 提交
+	async function handlePublishReportHook(tp,reportInfo) {
+
+		if(tp==='dsfb'){
+				showDSFBTime.value=true
+		return
+		}else if(tp==='submit'){
+				handleSubmitReport({id:Number(reportInfo.Id)})
+				return
+		}
+
+		let sendMsg = reportInfo.MsgIsSend;
+		if(sendMsg===1){
+				reportPublish({
+						sendMsg: false,
+						reportInfo
+				})
+		}else {
+
+				showDialog({
+						title: '发布提示',
+						showCancelButton: true,
+						message: isPost?'发布后,是否推送模板消息?':'是否立即发布报告?',
+						confirmButtonText: isPost?'推送':'发布',
+						cancelButtonText: isPost?'不推送':'取消',
+						closeOnClickOverlay: true    
+				}).then(() => {
+						reportPublish({
+								sendMsg: isPost?true:false,
+								reportInfo
+						})
+				})
+				.catch(() => {
+						if(isPost) reportPublish({
+								sendMsg: false,
+								reportInfo
+						});
+				});
+
+		}
+	}
+
+	/* 发布报告 */
+	async function reportPublish({sendMsg,reportInfo}){
+			const res=await apiReport.reportPublish({ReportIds:String(reportInfo.Id),ReportUrl:generatePdfLinks(reportInfo)})
+			if(res.Ret===200){
+					sendMsg && apiReport.reportMessageSend({ReportId: Number(reportInfo.Id)})
+
+					showToast('发布成功')
+					setTimeout(() => {
+							router.back()
+					},1000)
+			}
+	}
+
+
+	//定时发布
+	const showDSFBTime = ref(false)
+	function handleDSPublish(time,reportInfo) {
+		if(reportInfo.MsgIsSend===1){//已经推送过了
+			apiReport.reportPublishTimeSet({
+					ReportId:reportInfo.Id,
+					PrePublishTime:time,
+					PreMsgSend:0,
+					ReportUrl:generatePdfLinks(reportInfo)
+			}).then(res=>{
+					if(res.Ret===200){
+							showToast('定时发布成功!')
+							setTimeout(() => {
+									router.back()
+							}, 1000);
+					}
+			})
+			return
+		}
+
+		showDialog({
+				title: '提示',
+				message:isPost?'是否发布定时报告,并推送模板消息?':'是否发布定时报告',
+				confirmButtonText:isPost?'推送':'确定',
+				cancelButtonText:isPost?'不推送':'取消',
+				showCancelButton:true
+		}).then(()=>{
+				if(!isPost){
+						apiReport.reportPublishTimeSet({
+								ReportId:reportInfo.Id,
+								PrePublishTime:time,
+								PreMsgSend:0,
+								ReportUrl:generatePdfLinks(reportInfo)
+						}).then(res=>{
+								if(res.Ret===200){
+										showToast('定时发布成功!')
+										setTimeout(() => {
+												router.back()
+										}, 1000);
+								}
+						})
+						return
+				}
+				//推送
+				apiReport.reportPublishTimeSet({
+						ReportId:reportInfo.Id,
+						PrePublishTime:time,
+						PreMsgSend:1,
+						ReportUrl:generatePdfLinks(reportInfo)
+				}).then(res=>{
+						if(res.Ret===200){
+								showToast('定时发布成功!')
+								setTimeout(() => {
+										router.back()
+								}, 1000);
+						}
+				})
+		}).catch(()=>{
+				if(!isPost) return
+				//不推送
+				apiReport.reportPublishTimeSet({
+						ReportId:reportInfo.Id,
+						PrePublishTime:time,
+						PreMsgSend:0,
+						ReportUrl:generatePdfLinks(reportInfo)
+				}).then(res=>{
+						if(res.Ret===200){
+								showToast('定时发布成功!')
+								setTimeout(() => {
+										router.back()
+								}, 1000);
+						}
+				})
+		})
+	}
+
+	function generatePdfLinks(reportInfo){
+			let code = reportInfo.ReportCode
+
+			return `${publicSettingStore.publicSetting.ReportViewUrl}/reportshare_pdf?code=${code}&flag=${waterMarkStr.value}`
+	}
+  
+
+  return {
+    handleRefresh,
+		handleDSPublish,
+		showDSFBTime,
+		handlePublishReportHook
+  }
+}
+
+
+/* 章节报告操作 */
+export function useChapterRepoprtHandles() {
+
+	//提交章节
+	async function handlePublishChapterApi(chapterId) {
+		const res = await apiReport.chapterReportPublish({
+			ReportChapterId: chapterId
+		});
+
+		if (res.Ret !== 200) return
+		showToast('提交成功')
+		router.back()
+	}
+
+	return {
+		handlePublishChapterApi
+	}
+}
+
+
+/* 获取地址栏参数值 */
+function getUrlParams(str=window.location.href,key) {
+  let obj = {};
+	str.split('?')[1].split('&').map(i => obj[(i.split('=')[0])] = i.split('=')[1]);
+
+  return obj[key]
+}

+ 1458 - 0
src/views/report/smartReport/EditReport.vue

@@ -0,0 +1,1458 @@
+<script setup>
+import { nextTick, reactive, ref,toRefs, watch,onMounted,onUnmounted } from 'vue'
+import { V3ColorPicker } from "v3-color-picker-teleport"
+import ReportInsertContent from '../components/reportInsert/Index.vue'
+import ReportPublishTimeSet from '../components/ReportPublishTimeSet.vue'
+import apiReport from '@/api/report'
+import _ from 'lodash'
+import moment from 'moment'
+import { showToast,showDialog } from 'vant'
+import { useRoute, useRouter } from 'vue-router'
+import {useCachedViewsStore} from '@/store/modules/cachedViews'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
+import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+import {useReportApprove} from '@/hooks/useReportApprove'
+import AddReportBaseInfoV2 from '../components/AddReportBaseInfoV2.vue'
+import { useReportHandles,useChapterRepoprtHandles } from '../hooks/useReport'
+import draggable from 'vuedraggable'
+import TextEditor from './components/TextEditor.vue'
+import ImgEditor from  './components/ImgEditor.vue'
+import TextComp from './components/TextComp.vue'
+import ChartComp from './components/ChartComp.vue'
+import ImgComp from './components/ImgComp.vue'
+import SheetComp from './components/SheetComp.vue'
+import ReportLayoutImg from './components/ReportLayoutImg.vue'
+
+
+const cachedViewsStore=useCachedViewsStore()
+const publicSettingStore = usePublicSettingStore()
+const {isApprove,hasApproveFlow,getEtaConfig,checkClassifyNameArr} = useReportApprove()
+const router=useRouter()
+const route=useRoute()
+const {checkAuthBtn} = useAuthBtn()
+
+
+const reportCoopType = ref(Number(route.query.coopType))
+let autoSaveTimer=null
+onMounted(() => {
+	if(reportCoopType.value===1) {
+    getEtaConfig()
+    getReportDetail()
+    autoSaveTimer=setInterval(() => {
+        autoSaveReportContent()
+    }, 6000);
+	}else if(reportCoopType.value===2) { //章节报告
+			getChapterDetail()
+			autoSaveTimer=setInterval(() => {
+					autoSaveReportChapter()
+			}, 6000);
+  }
+})
+onUnmounted(()=>{
+    clearInterval(autoSaveTimer)
+})
+
+
+// 获取报告详情
+const reportInfo=ref(null)
+const contentChange = ref(false)//内容是否发生变化
+const smartState = reactive({
+  conList: [],//内容列表
+  bgColor:'',//背景色
+  headImg:'',//版头图片
+  endImg:'',//版尾图片
+  headImgId:0,//版头Id
+  endImgId:0,//版尾Id
+  headImgStyle:'',//版头style
+  endImgStyle:'',//版尾style
+  layoutBaseInfo:{
+      研报标题:'',
+      研报作者:'',
+      创建时间:''
+  },
+})
+async function getReportDetail(){
+    const res=await apiReport.getReportDetail({
+        ReportId:Number(route.query.id)
+    })
+    if(res.Ret===200){
+        reportInfo.value=res.Data;
+
+        smartState.conList=res.Data.ContentStruct?JSON.parse(res.Data.ContentStruct):[]
+        smartState.headImg=res.Data.HeadImg
+        smartState.endImg=res.Data.EndImg
+        smartState.headImgId=res.Data.HeadResourceId
+        smartState.endImgId=res.Data.EndResourceId
+        smartState.headImgStyle=res.Data.HeadStyle?JSON.parse(res.Data.HeadStyle):[]
+        smartState.headImgStyle.map(st =>{
+            st.value=st.value || st.label
+        })
+        smartState.endImgStyle=res.Data.EndStyle?JSON.parse(res.Data.EndStyle):[]
+        smartState.endImgStyle.map(st =>{
+            st.value=st.value || st.label
+        })
+        smartState.bgColor=res.Data.CanvasColor
+        smartState.layoutBaseInfo['研报标题']=res.Data.Title
+        smartState.layoutBaseInfo['研报作者']=res.Data.Author
+
+
+
+        reportBaseInfoData.addType=res.Data.AddType
+        reportBaseInfoData.classifyName=[
+            {
+                id:res.Data.ClassifyIdFirst,
+                text:res.Data.ClassifyNameFirst,
+            },
+            {
+                id:res.Data.ClassifyIdSecond,
+                text:res.Data.ClassifyNameSecond,
+            },
+            {
+                id:res.Data.ClassifyIdThird,
+                text:res.Data.ClassifyNameThird,
+            }
+        ]
+        reportBaseInfoData.author=res.Data.Author ? res.Data.Author.split(',') : ['FICC团队']
+        reportBaseInfoData.frequency=[res.Data.Frequency]
+        reportBaseInfoData.createtime=moment(res.Data.CreateTime).format('YYYY-MM-DD')
+        reportBaseInfoData.title=res.Data.Title
+        reportBaseInfoData.abstract=res.Data.Abstract
+        reportBaseInfoData.cooperationType=res.Data.CollaborateType
+        reportBaseInfoData.cooperationUsers=res.Data.GrandAdminList
+            ? res.Data.GrandAdminList.map(_ => ({
+                NodeId: _.AdminId,
+                NodeName: _.AdminName
+            }))
+            : []
+        reportBaseInfoData.reportLayout=res.Data.ReportLayout
+        reportBaseInfoData.isPublcPublish=res.Data.IsPublicPublish
+
+        nextTick(() => {
+          contentChange.value = false
+        })
+        
+        const classify = reportBaseInfoData.classifyName.map(i=>i.id)
+        checkClassifyNameArr(1,classify)
+
+    }
+}
+
+//获取章节详情
+async function getChapterDetail() {
+    const res=await apiReport.getChapterDetail({
+        ReportChapterId:Number(route.query.chapterId)
+    })
+    reportInfo.value=res.Data;
+
+    smartState.conList=res.Data.ContentStruct?JSON.parse(res.Data.ContentStruct):[]
+}
+
+
+watch(
+	() => smartState.conList,
+	() => {
+		contentChange.value = true;
+	},
+	{
+		deep: true
+	}
+)
+
+
+/* map类型对应组件 */
+function getComponentName(item){
+  const temMap=new Map([
+      ['text',TextComp],
+      ['chart',ChartComp],
+      ['img',ImgComp],
+      ['sheet',SheetComp]
+  ])
+  return temMap.get(item.compType)
+}
+
+const isDragResize = ref(false)//是否正在拖动缩放
+// 大盒子的高度缩放
+function handleResizeP(e,index){
+		isDragResize.value=true
+		e.preventDefault()
+		const parentBox=e.target.parentNode.parentNode
+		const parentBoxWidth=parentBox.offsetWidth
+
+		const targetBox=e.target.parentNode
+		const targetBoxHeight=targetBox.offsetHeight
+		const targetBoxWidth=targetBox.offsetWidth
+
+		const startY=e.clientY
+		const startX=e.clientX
+		document.onmousemove=(mouseEl)=>{
+				mouseEl.preventDefault()
+				const h=mouseEl.clientY-startY+targetBoxHeight
+				// targetBox.style.minHeight=`${h<50?50:h}px`
+				targetBox.style.height=`${h}px`
+
+				// 去除minhight
+				targetBox.style.minHeight=`10px`
+
+				// 计算宽度
+				const w=mouseEl.clientX-startX+targetBoxWidth
+				const resW= (w/parentBoxWidth)*100//计算出的百分比结果值
+				targetBox.style.width=`${resW>=100?100:resW}%`
+		}
+		document.onmouseup=(el)=>{
+				console.log(targetBox.style.cssText);
+				smartState.conList[index].style = targetBox.style.cssText
+				el.preventDefault()
+				document.onmousemove=null
+				setTimeout(() => {
+						isDragResize.value=false
+				}, 50);
+				
+		}
+}
+// 内部元素的缩放
+function handleResizeC(e,index,cindex,type){
+		isDragResize.value=true
+		e.preventDefault()
+		const parentBox=e.target.parentNode.parentNode
+		const parentBoxWidth=parentBox.offsetWidth
+		console.log(e);
+		const targetBox=e.target.parentNode
+		const targetBoxHeight=targetBox.offsetHeight
+		const targetBoxWidth=targetBox.offsetWidth
+		const startY=e.clientY
+		const startX=e.clientX
+		const initW=targetBoxWidth //拖动前要拖动盒子所占的宽度
+
+		let computerW=0//存放其他兄弟节点要改变的宽度的值
+
+		document.onmousemove=(mouseEl)=>{
+				mouseEl.preventDefault()
+				const h=mouseEl.clientY-startY+targetBoxHeight
+				// 计算宽度
+				const w=type==='rb'?mouseEl.clientX-startX+targetBoxWidth:startX-mouseEl.clientX+targetBoxWidth
+				const resW= (w/parentBoxWidth)*100//计算出的百分比结果值
+				targetBox.style.width=resW+'%'
+				targetBox.style.flex='none'
+
+				// 处理兄弟盒子的宽度
+				const changeW=w-initW //宽度变化值
+				computerW=changeW
+				if(parentBox.childNodes.length===3){
+						computerW=changeW/2
+				}
+				// console.log('改变的宽度',computerW);
+
+				// targetBox.style.height=`${h<50?50:h}px`
+				targetBox.style.height=`${h}px`
+		}   
+		document.onmouseup=(el)=>{
+				if(document.onmousemove){
+						parentBox.childNodes.forEach(item=>{
+								if(item!==targetBox){
+										const temw=item.offsetWidth-computerW
+										item.style.width=((temw/parentBoxWidth)*100)+'%'
+								}
+						})
+						// 存储修改的值
+						parentBox.childNodes.forEach((item,idx)=>{
+								smartState.conList[index].child[idx].style = item.style.cssText
+						})
+				}
+				el.preventDefault()
+				document.onmousemove=null
+				setTimeout(() => {
+						isDragResize.value=false
+						document.onmouseup=null
+				}, 50);
+		}
+}
+//新增内部元素
+function handleChildAdd(e,parent,parentIndex){
+		console.log('child-onAdd操作------------------->');
+
+		const {item,newDraggableIndex}=e
+
+		const compData=JSON.parse(item.getAttribute('comp-data'))
+
+		console.log(compData,newDraggableIndex);
+
+		const index=parentIndex
+		// if(index>-1){
+				let obj=_.cloneDeep(smartState.conList[index]) 
+
+				console.log(obj);
+
+				if(obj.child&&obj.child.length===1&&obj.id){
+						if(compData){
+								obj={
+										child:[
+												{
+														compId:obj.compId,
+														compType:obj.compType,
+														id:obj.id,
+														content:obj.content,
+														style:obj.compType==='chart'?'height:350px':'',
+														child:[]
+												},
+												{
+														compId:compData.compId,
+														compType:compData.compType,
+														content:compData.content||'',
+														id:getCompId(compData.compType),
+														style:compData.compType==='chart'?'height:350px':'',
+														child:[]
+												}
+										]
+								}
+						}else{//是内容区域拖动排序的
+								const temItem=_.cloneDeep(obj.child[0])
+								if(temItem.child.length>0){//如果拖动的盒子里面有子元素则不能进入
+										obj={
+												...obj,
+												child:[]
+										}
+										setTimeout(() => {
+												smartState.conList.splice(index,0,temItem)
+										}, 50);
+								}else{
+										obj={
+												child:[
+														{
+																compId:obj.compId,
+																compType:obj.compType,
+																id:obj.id,
+																content:obj.content,
+																style:obj.compType==='chart'?'height:350px':'',
+																child:[]
+														},
+														{
+																...temItem
+														}
+												]
+										}
+								}
+						}
+						
+				}else{
+						if(compData){//如果是从内容区域拖入的没有compData
+								obj.child.splice(newDraggableIndex,1,{
+										compId:compData.compId,
+										compType:compData.compType,
+										content:compData.content||'',
+										id:getCompId(compData.compType),
+										style:compData.compType==='chart'?'height:350px':'',
+										child:[]
+								})
+						}
+				}
+				console.log(obj);
+
+				smartState.conList.splice(index,1,obj)
+		// }
+}
+//移除内部元素事件 
+function handleChildRemove(e,arr){
+		console.log('child-remove操作------------------->');
+
+		// 如果当前移出的这个child还有两个的话则重置他们的宽度
+		arr.forEach(item=>{
+				if(item.style){
+						const styleArr=item.style.split(';').filter(s=>s&&(s.indexOf('width')===-1&&s.indexOf('flex')===-1)).join(';')
+						item.style=styleArr
+				}
+		})
+
+
+		// 如果child只剩一个了则移出来
+		smartState.conList=smartState.conList.map(_item=>{
+				if(_item.child&&_item.child.length===1){
+						const obj=_item.child[0]
+						if(obj.style){
+								const styleArr=obj.style.split(';').filter(s=>s&&(s.indexOf('width')===-1&&s.indexOf('flex')===-1)).join(';')
+								obj.style=styleArr
+						}
+						return obj
+				}else{
+						return _item
+				}
+		})
+}
+
+
+
+// 点击删除某个元素
+function handleDelItem(pindex,cindex){
+		if(cindex===-1){
+				smartState.conList.splice(pindex,1)
+		}else{//删除子盒子
+				smartState.conList[pindex].child.splice(cindex,1)
+				if(smartState.conList[pindex].child.length===1){//只剩一个子盒子了则变成一个大盒子
+						const styleArr=smartState.conList[pindex].child[0].style.split(';').filter(s=>s&&(s.indexOf('width')===-1&&s.indexOf('flex')===-1)).join(';')
+						smartState.conList[pindex]=smartState.conList[pindex].child[0]
+						smartState.conList[pindex].style=styleArr
+				}
+		}
+}
+
+// 删除版头版尾
+function deleteLayoutPic(type){
+		if(type===1){
+				smartState.headImg=''
+				smartState.headImgId = 0
+		}else{
+				smartState.endImg=''
+				smartState.endImgId = 0
+		}
+		contentChange.value=true
+}
+
+
+//当前选中的
+const currentState = reactive({
+	activeId: 0,
+	activeContent: '',
+	activePindex: '',
+	activeCindex: ''
+})
+// 当前再编辑哪个
+function handleChoose(item,index,cindex){
+	// console.log(item,index,cindex)
+		//{item:数据,index:父序号,cindex:子序号}
+		if(!item.id||isDragResize.value||!['text','img'].includes(item.compType)) return
+		currentState.activeId=item.id
+		currentState.activeContent=item.content||''
+		currentState.activePindex=index
+		currentState.activeCindex=cindex>=0?cindex:''
+
+		insertState.activeId = 0;
+		insertState.activePindex = '';
+
+		showInsertCompType.value = item.compId;
+		showReportInsertPop.value = true
+		if(item.compId === 1) {//文字组件
+			temTextVal.value = item.content
+		}else if(item.compId === 2) { //图片组件
+			temImgVal.value = item.content
+		}
+}
+
+
+//在哪项后插入
+const insertState = reactive({
+	activeId: 0,
+	activePindex: '',
+})
+function handleOpenInsertCompPop(e,{element,index}) {
+	currentState.activeId=0
+	currentState.activeContent=''
+	currentState.activePindex=''
+	currentState.activeCindex=''
+
+	insertState.activeId = element.child?.length ? element.child.map(_ =>_.id).join('/') : element.id;
+	insertState.activePindex = index;
+
+  showInsertCompType.value = e.compId;
+	temTextVal.value = ''
+	temImgVal.value = ''
+  showReportInsertPop.value = true
+}
+
+
+const showPopover = ref(false)
+const showReportInsertPop = ref(false)
+const showInsertCompType=ref(0)
+const  compType = [
+  {   
+      compId:1,
+      compType:'text',
+      text:'文字',
+      icon:'font-o'
+  },
+  {
+      compId:2,
+      compType:'img',
+      text:'图片',
+      icon:'photo-o'
+  },
+  {
+      compId:3,
+      compType:'chart',
+      text:'图表',
+      icon:'chart-trending-o'
+  },
+]
+function handleOpenComPop(e) {
+  showPopover.value = false
+  console.log(e)
+	
+	currentState.activeId=0
+	currentState.activeContent=''
+	currentState.activePindex=''
+	currentState.activeCindex=''
+
+	insertState.activeId = 0;
+	insertState.activePindex = '';
+
+  showInsertCompType.value = e.compId;
+	temTextVal.value = ''
+	temImgVal.value = ''
+  showReportInsertPop.value = true
+}
+/* 插入图表表格 */
+function handleChartInsert({list,type,chartType}){
+		console.log(list,type,chartType)
+    let tempCompDataArr=[]
+    
+    if(type==='iframe'){
+        let link=publicSettingStore.publicSetting.ChartViewUrl;
+        if(chartType==='chart'){
+
+          link=link+'/chartshow'
+
+          tempCompDataArr = list.map(item =>({
+            compId:3,
+            compType:'chart',
+            id:getCompId(3),
+            content:`${link}?code=${item}`,
+            titleText: '',
+            style:'height:350px',
+            child:[]
+          }))
+        }else if(chartType==='sheet'){
+            link=link+'/sheetshow'
+            tempCompDataArr = list.map(item =>({
+                compId:4,
+                compType:'sheet',
+                id:getCompId(4),
+                content:`${link}?code=${item}&fromScene=1`,
+                titleText: '',
+                style:'',
+                child:[]
+            }))
+        }
+    }else if(type==='img'){
+
+				tempCompDataArr = list.map(item =>({
+						compId:2,
+            compType:'img',
+            id:getCompId(2),
+            content:item,
+            titleText: '',
+            style:'',
+            child:[]
+				}))
+    }
+		
+		if(insertState.activeId) {
+			smartState.conList.splice(insertState.activePindex+1,0,...tempCompDataArr)
+		}else {
+			smartState.conList.splice(0,0,...tempCompDataArr)
+		}
+    showReportInsertPop.value=false
+}
+//插入文本
+const temTextVal = ref('')
+function handleInsertText(content) {
+	if(currentState.activeId) { //编辑
+		currentState.activeContent=content
+		if(currentState.activeCindex>=0&&currentState.activeCindex!==''){
+				smartState.conList[currentState.activePindex].child[currentState.activeCindex].content=content
+		}else{
+				smartState.conList[currentState.activePindex].content = content
+				console.log(smartState.conList[currentState.activePindex])
+		}
+	}else { //新增
+
+		let tempCompData = {
+				compId:1,
+				compType:'text',
+				id:getCompId(1),
+				content:content,
+				style:'',
+				child:[]
+		}
+		if(insertState.activeId) {
+			smartState.conList.splice(insertState.activePindex+1,0,tempCompData)
+		}else {
+			smartState.conList.splice(0,0,tempCompData)
+		}
+	}
+
+	console.log(smartState.conList)
+	showReportInsertPop.value = false
+}
+//插入图片
+const temImgVal=ref('')
+function handleInsertImg(e) {
+	if(temImgVal.value) { //编辑
+		currentState.activeContent=e
+		if(currentState.activeCindex>=0&&currentState.activeCindex!==''){
+				smartState.conList[currentState.activePindex].child[currentState.activeCindex].content=e
+		}else{
+				smartState.conList[currentState.activePindex].content = e
+		}
+	}else { //新增
+		let tempCompData = {
+				compId:2,
+				compType:'img',
+				id:getCompId(2),
+				content:e,
+				style:'',
+				child:[]
+		}
+		if(insertState.activeId) {
+			smartState.conList.splice(insertState.activePindex+1,0,tempCompData)
+		}else {
+			smartState.conList.splice(0,0,tempCompData)
+		}
+	}
+
+		showReportInsertPop.value = false
+}
+function getCompId(type) {
+	return type+new Date().getTime()
+}
+
+// 更新sheet表格高度
+function reInitSheetIframe(e){
+    const { height,code } = e.data;
+    let iframeDom = document.getElementsByClassName(`iframe${code}`)
+    Array.prototype.forEach.call(iframeDom, function (ele) {
+        ele.height = `${height}px`;
+    });
+}
+onMounted(()=>{
+    window.addEventListener('message',reInitSheetIframe)
+})
+onUnmounted(()=>{
+    window.removeEventListener('message',reInitSheetIframe)
+})
+
+
+
+//更多设置弹窗
+const showMoreHandlePop = ref(false)
+const showLayoutPop = ref(false)
+const showBgPop = ref(false)
+const temBgVar=ref("")
+//设置版图
+function handleConfirmSetLayout(e) {
+		if(e.type=='1'){
+				showToast('版头设置成功')
+				smartState.headImg=e.data.ImgUrl
+				smartState.headImgId = e.data.ResourceId
+				smartState.headImgStyle=e.data.Style?JSON.parse(e.data.Style):[]
+		}else{
+				showToast('版尾设置成功')
+				smartState.endImg=e.data.ImgUrl
+				smartState.endImgId = e.data.ResourceId
+				smartState.endImgStyle=e.data.Style?JSON.parse(e.data.Style):[]
+		}
+		contentChange.value=true
+		showLayoutPop.value = false
+}
+function handleOpenBgPop() {
+	temBgVar.value=smartState.bgColor||''
+	showMoreHandlePop.value=false 
+	showBgPop.value=true;
+}
+//设置背景色
+function handleConfirmBgColor() {
+		smartState.bgColor=temBgVar.value||''
+  	contentChange.value=true
+		showBgPop.value = false
+}
+
+
+/* 报告流程操作 */
+const { handleRefresh,handlePublishReportHook,handleDSPublish,showDSFBTime } = useReportHandles()
+// 报告基本信息
+const showReportBaseInfo=ref(false)
+let reportBaseInfoData={
+    addType:1,
+    classifyName:[],
+    author:['FICC团队'],
+    frequency: ['日度'],
+    createtime:moment().format('YYYY-MM-DD'),
+    title:'',
+    abstract:'',
+    cooperationType:1,
+    cooperationUsers:[],
+    reportLayout: 1,
+    isPublcPublish: 1
+}
+async function handleReportBaseInfoChange(e){
+    reportBaseInfoData={
+        ...e,
+        classifyName:e.classifys,
+        createtime:e.time,
+    }
+
+    const classify = e.classifys.map(i=>i.id)
+    checkClassifyNameArr(1,classify)
+    showReportBaseInfo.value=false
+}
+
+// 刷新所有图表
+async function handleRefreshAllChart(){
+  handleRefresh({ id: Number(route.query.id),chapterId:Number(route.query.chapterId) })
+}
+
+//预览
+async function handlePreviewReport() {
+		if(document.getElementById('report-html-content')) { 
+				// 存一次草稿
+				const saveRes=await autoSaveReportContent("auto")
+				if(!saveRes) return
+		}
+	  router.push({
+        path:'/report/preview',
+        query:{
+            id: reportInfo.value.Id
+        }
+    })
+    return
+}
+
+/* 保存 定时保存 */
+function autoSaveReportContent(type="auto") {
+	  if(!route.query.id) return
+
+		const html= handleContentFormat() 
+    return new Promise(async (resolve,reject)=>{
+
+				let imgParams = {
+						HeadImg:smartState.headImg,
+						EndImg:smartState.endImg,
+						HeadResourceId:smartState.headImgId,
+						EndResourceId:smartState.endImgId,
+						CanvasColor:smartState.bgColor
+				}
+
+        const res=await apiReport.reportContentSave({
+            ReportId:Number(route.query.id),
+            Content:html,
+						ContentStruct:JSON.stringify(smartState.conList),
+            NoChange:contentChange.value?0:1,
+						...imgParams
+        })
+        if(res.Ret === 200) {
+            resolve(true)
+            type==='save' && showToast("保存成功");
+            contentChange.value=false
+        }
+    })
+}
+//处理内容格式
+function handleContentFormat() {
+	let htmlStr = document.getElementById('report-html-content').outerHTML.replace(/contenteditable="true"/g,'contenteditable="false"').replace(/comp-insert-add"/g,'comp-insert-add" style="display:none"');
+
+	contentChange.value = true;
+	return htmlStr
+}
+
+//发布,定时发布,提交
+async function handlePublishReport(tp) {
+		cachedViewsStore.removeCaches('ReportList')
+	 	if(document.getElementById('report-html-content')) { 
+				// 存一次草稿
+				const saveRes=await autoSaveReportContent("auto")
+				if(!saveRes) return
+		}
+
+		handlePublishReportHook(tp,reportInfo.value)
+}
+
+// 定时发布报告选择时间
+function onConfirmDSFBTime(time){
+    // console.log(time);
+    handleDSPublish(time,reportInfo.value)
+}
+
+
+
+const { handlePublishChapterApi } = useChapterRepoprtHandles()
+//预览章节
+async function handlePreviewChapter() {
+    const saveRes = await autoSaveReportChapter('auto');
+    if(!saveRes) return
+    
+		router.push({
+        path:'/report/chapter/preview',
+        query:{
+            id:reportInfo.value.ReportChapterId
+        }
+    })
+}
+
+// 自动保存章节报告
+async function autoSaveReportChapter(type="auto"){
+    if(!route.query.chapterId) return 
+   const html = handleContentFormat();
+
+    return new Promise(async (resolve,reject)=>{
+        const res=await apiReport.chapterDetailSave({
+            ReportChapterId:Number(route.query.chapterId),
+						Content: html,
+						ContentStruct:JSON.stringify(smartState.conList)
+        })
+        if(res.Ret === 200) {
+            resolve(true)
+            type==='save' && showToast("保存成功");
+        }
+    })
+   
+}
+
+/* 提交章节 */
+async function handlePublishChapter() {
+    const saveRes = await autoSaveReportChapter('auto');
+    if(!saveRes) return
+
+    handlePublishChapterApi(reportInfo.value.ReportChapterId)
+}
+
+const { 
+  conList,
+  bgColor,
+  headImg,
+  endImg,
+  headImgId,
+  endImgId,
+  headImgStyle,
+  endImgStyle,
+  layoutBaseInfo
+} = toRefs(smartState)
+</script>
+<template>
+  <div class="add-report-page">
+    <div class="main-wrap" :style="{backgroundColor:bgColor}">
+
+      <div class="report-content-box" id="report-content-box" >
+
+          <!-- 版头 -->
+          <div class="html-head-img-box">
+              <div class="opt-btn-box"  style="display: none;">
+									<van-icon name="delete-o" @click.stop="deleteLayoutPic(1)" color="#f00" size="22"/>
+              </div>
+              <img :src="headImg" alt="" style="display:block;width:100%">
+              <div class="head-layout-item" v-for="item in headImgStyle" :key="item.value"
+              :style="{fontFamily:item.family,fontSize:(item.size*2)+'px',fontWeight:item.weight,textAlign:item.align,color:item.color,
+                  width:item.width,height:item.height,left:item.left,top:item.top
+              }">
+                  {{ layoutBaseInfo[item.value] }}
+              </div>
+          </div>
+
+					<div class="add-comp-wrapper">
+						<van-popover v-model:show="showPopover">
+							<van-grid
+								square
+								clickable
+								:border="false"
+								column-num="3"
+								style="width: 240px;"
+							>
+								<van-grid-item
+									v-for="i in compType"
+									:key="i.id"
+									:text="i.text"
+									:icon="i.icon"
+									@click="handleOpenComPop(i)"
+								/>
+							</van-grid>
+							<template #reference>
+									<svg-icon name="add-comp" size="40"/>
+							</template>
+						</van-popover>
+					</div>
+
+          <draggable
+              :list="conList"
+							item-key="id"
+              :group="{ name: 'component', pull: true, put: true }"
+							handle=".drag-btn_p"
+              class="report-html-wrap"
+              id="report-html-content"
+              animation="300"
+              tag="div"
+          >
+            <template #item="{element,index}">
+              <div 
+                  :class="[
+                      'report-drag-item-wrap',
+                      currentState.activeId===element.id?'blue-bg':'',
+                      element.child&&!element.child.length?'report-drag-item-out':''
+                  ]"
+                  :comp-type="element.compType"
+                  @click="handleChoose(element,index)"
+                  :style="element.style"
+              >
+                  <div class="resize-drag-box" @mousedown.stop="handleResizeP($event,index)"></div>
+                  <div class="opt-btn-box"  style="display: none;">
+											<van-icon name="delete-o" @click.stop="handleDelItem(index,-1)" color="#f00" size="22"/>
+											<div class="drag-btn drag-btn_p"></div>
+                  </div>
+                  <div 
+                      v-if="element.child&&!element.child.length"
+                      class="report-drag-item-wrap_content"
+                      style="width:100%;height:100%"
+                      :data-id="element.id"
+                  >
+                      <component :is="getComponentName(element)" :compData="element"/>
+                  </div>
+
+									<!-- 插入 -->
+									<div class="add-comp-wrapper comp-insert-add" @click.stop>
+										<van-popover v-model:show="element.showAddPopover">
+											<van-grid
+												square
+												clickable
+												:border="false"
+												column-num="3"
+												style="width: 240px;"
+											>
+												<van-grid-item
+													v-for="i in compType"
+													:key="i.id"
+													:text="i.text"
+													:icon="i.icon"
+													@click.stop="handleOpenInsertCompPop(i,{element,index})"
+												/>
+											</van-grid>
+											<template #reference>
+													<svg-icon name="add-comp" size="40"/>
+											</template>
+										</van-popover>
+									</div>
+
+									<draggable
+											:list="element.child"
+											item-key="id"
+											:group="{ name: 'component', pull: true, put: element.child&&element.child.length<3?true:false }"
+											handle=".drag-btn_c"
+											animation="300"
+											tag="div"
+											class="report-drag-item-wrap_child-wrap"
+											@add="handleChildAdd($event,element,index)"
+											@remove="handleChildRemove($event,element.child)"
+											style="display: flex;gap: 3px;align-items: flex-start;"
+									>		
+										<template #item="child">
+											<div 
+													:class="['report-drag-item-wrap_child_content',currentState.activeId===child.element.id?'blue-bg':'']" 
+													:comp-type="child.element.compType"
+													:data-id="child.element.id"
+													@click.stop="handleChoose(child.element,index,child.index)"
+													style="flex:1"
+													:style="child.element.style"
+											>
+													<div class="opt-btn-box2" style="display: none;">
+															<div class="drag-btn drag-btn_c"></div>
+															<van-icon name="delete-o" @click.stop="handleDelItem(index,child.index)" color="#f00" size="22"/>
+													</div>
+													<!-- 拖动按钮 -->
+													<div class="resize-drag-box_lb" @mousedown.stop="handleResizeC($event,index,child.index,'lb')"></div>
+													<div class="resize-drag-box_rb" @mousedown.stop="handleResizeC($event,index,child.index,'rb')"></div>
+													<component :is="getComponentName(child.element)" :compData="child.element"/>
+													<!--  -->
+													<div class="mark-box" v-if="isDragResize" style="position: absolute;left:0;right:0;top:0;bottom: 0;z-index: 10;"></div>
+											</div>
+										 </template>
+									</draggable>
+              </div>
+            </template>
+          </draggable>
+          
+          <!-- 版尾 -->
+          <div class="html-end-img-box">
+              <div class="opt-btn-box"  style="display: none;">
+									<van-icon name="delete-o" @click.stop="deleteLayoutPic(2)" color="#f00" size="22"/>
+              </div>
+              <img :src="endImg" alt="" style="display:block;width:100%">
+              <div class="head-layout-item" v-for="item in endImgStyle" :key="item.value"
+              :style="{fontFamily:item.family,fontSize:(item.size*2)+'px',fontWeight:item.weight,textAlign:item.align,color:item.color,
+                  width:item.width,height:item.height,left:item.left,top:item.top
+              }">
+                  {{ layoutBaseInfo[item.value] }}
+              </div>
+          </div>
+      </div>
+    </div>
+
+    <!-- 底部操作 -->
+    <div class="bot-action-box">
+        <div class="left-box" v-if="reportInfo">
+            <div class="item" @click="showReportBaseInfo=true" v-if="!reportInfo.ReportChapterId">
+                <img src="@/assets/imgs/report/icon_info.png" alt="">
+                <span>基础信息</span>
+            </div>
+            <div class="item" @click="handleRefreshAllChart">
+                <img src="@/assets/imgs/report/icon_refresh.png" alt="">
+                <span>刷新</span>
+            </div>
+            <div class="item" @click="reportInfo.ReportChapterId?handlePreviewChapter():handlePreviewReport()" v-permission="reportManageBtn.reportManage_reportView">
+                <img src="@/assets/imgs/report/icon_preview.png" alt="">
+                <span>预览</span>
+            </div>
+            <div class="item" @click="reportInfo.ReportChapterId?autoSaveReportChapter('save'):autoSaveReportContent('save')" v-permission="reportManageBtn.reportManage_publish">
+                <img src="@/assets/imgs/report/icon_save2.png" alt="">
+                <span>保存</span>
+            </div>
+
+						<!-- 章节报告提交章节 -->
+						<div 
+								class="item" 
+								@click="handlePublishChapter" 
+								v-permission="reportManageBtn.reportManage_publish" 
+								v-if="reportInfo.ReportChapterId"
+						>
+										<img src="@/assets/imgs/report/icon_publish3.png" alt="">
+										<span>提交</span>
+						</div>
+						<!-- 单人报告提交发布 -->
+						<template v-else>
+							<template v-if="!isApprove||!hasApproveFlow">
+									<div class="item" @click="handlePublishReport('dsfb')" v-permission="reportManageBtn.reportManage_publish">
+											<img src="@/assets/imgs/report/icon_time.png" alt="">
+											<span>定时发布</span>
+									</div>
+									<div class="item" @click="handlePublishReport('fb')" v-permission="reportManageBtn.reportManage_publish">
+											<img src="@/assets/imgs/report/icon_publish3.png" alt="">
+											<span>发布</span>
+									</div>
+							</template>
+							<template v-if="isApprove&&hasApproveFlow">
+									<div class="item" @click="handlePublishReport('submit')" >
+											<img src="@/assets/imgs/report/icon_publish3.png" alt="">
+											<span>提交</span>
+									</div>
+							</template>
+						</template>	
+
+            <div class="item" @click="showMoreHandlePop=true" v-if="!reportInfo.ReportChapterId">
+								<van-icon name="ellipsis" size="24"/>
+                <div>更多设置</div>
+            </div>
+        </div>
+    </div>
+  </div>
+
+  <!-- 报告插入数据模块 -->
+	<van-popup
+			v-model:show="showReportInsertPop"
+			position="bottom"
+			round
+	>
+			<!-- 文字插入 -->
+			<TextEditor 
+				v-if="showInsertCompType===1"
+				:defaultVal="temTextVal"
+				@close="showReportInsertPop=false;temTextVal=''"
+				@confirm="handleInsertText"
+			/>
+
+			<!-- 图片 -->
+			<ImgEditor
+				v-else-if="showInsertCompType===2"
+				:defaultVal="temImgVal"
+				@close="showReportInsertPop=false;temImgVal=''"
+				@confirm="handleInsertImg"
+			/>
+			<!-- 图表资源 -->
+			<ReportInsertContent v-if="showInsertCompType===3" @insert="handleChartInsert"/>
+	</van-popup>
+
+  <!-- 报告基础信息 -->
+  <van-popup
+      v-model:show="showReportBaseInfo"
+      position="bottom"
+      :style="{ height: '100%' }"
+  >
+      <AddReportBaseInfoV2
+          @close="showReportBaseInfo=false"
+          :id="Number(route.query.id)"
+          :defaultData="reportBaseInfoData"
+          @confirm="handleReportBaseInfoChange"
+      />
+  </van-popup>
+
+	 <!-- 定时发布选择时间 -->
+  <ReportPublishTimeSet 
+		v-model="showDSFBTime" 
+		:prePublishTime="reportInfo?.PrePublishTime" 
+		@confirm="onConfirmDSFBTime"
+	/>
+
+	<!-- 更多操作 背景色 版图 -->
+	<van-action-sheet 
+			teleport="body"
+			v-model:show="showMoreHandlePop"
+			cancel-text="取消"
+			close-on-click-action
+	>
+			<div class="report-item-action-box">
+					
+					<div class="item" @click="showLayoutPop=true;showMoreHandlePop=false">版图选择</div>
+					<div class="item" @click="handleOpenBgPop">背景色设置</div>
+			</div>
+	</van-action-sheet>
+
+	<!-- 版图设置 -->
+  <van-popup
+      v-model:show="showLayoutPop"
+      position="bottom"
+  >
+      <ReportLayoutImg 
+				v-if="showLayoutPop" 
+				@close="showLayoutPop=false" 
+				@confirm="handleConfirmSetLayout"
+			/>
+  </van-popup>
+
+	<!-- 背景色设置 -->
+  <van-popup
+      v-model:show="showBgPop"
+      position="bottom"
+			:style="{ height: '50%' }"
+  >
+		<div class="top-box">
+        <span style="color:#666666" @click="showBgPop=false">取消</span>
+        <span style="font-size:18px;font-weight:bold">背景色设置</span>
+        <span style="color:#0052D9" @click="handleConfirmBgColor">确定</span>
+    </div>
+		<div class="color-box">
+				<span>颜色选择</span>
+				<V3ColorPicker v-model:value="temBgVar" :zIndex="9999"/>
+		</div>
+  </van-popup>
+</template>
+<style scoped lang="scss">
+.add-report-page{
+    height: 100dvh;
+    min-height: 95vh;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+}
+.main-wrap{
+    flex: 1;
+    width: calc(100% - 32PX);
+    margin: 0 auto;
+		overflow-y: auto;
+    margin-top: 30px;
+    border: 1px solid #DCDFE6;
+
+		.report-drag-item-wrap{
+				width: 100%;
+				padding: 6px;
+				min-height: 80px;
+				border: 1px dashed #0052D9;
+				position: relative;
+				margin-bottom: 120px;
+				&:hover{
+						border-style: solid;
+						.opt-btn-box{
+								display: block !important;
+						}
+						.resize-drag-box{
+								display: block;
+						}
+				}
+
+				.drag-btn::after{
+						content: '';
+						display: block;
+						width: 40px;
+						height: 40px;
+						background-image: url('@/assets/imgs/report/icon_drag.png');
+						background-size: cover;
+						cursor:move;
+				}
+				.opt-btn-box{
+						position: absolute;
+						right: 0;
+						padding: 6px 10px 0;
+						top: -80px;
+						background: #fff;
+						border: 1px solid #ECF2FE;
+						box-shadow: 0 4px 8px rgba(0, 0, 0, 0.04);
+
+					.drag-btn {
+						display: inline-block;
+						margin-left: 10px;
+					}
+				}
+				.resize-drag-box{
+						position: absolute;
+						right: -4px;
+						bottom: -4px;
+						width: 8px;
+						height: 8px;
+						display: none;
+						border: 1px solid #0052D9;
+						cursor: nw-resize;
+				}
+		}
+		.report-drag-item-out{
+				.report-drag-item-wrap_child-wrap{
+						position: absolute;
+						width: 40px;
+						right: 0;
+						top: 0;
+						height: 100%;
+				}
+		}
+
+		.report-drag-item-wrap_child_content{
+				min-height: 80px;
+				border: 1px dashed #0052D9;
+				flex: 1;
+				position: relative;
+				&:hover{
+						border-style: solid;
+						.opt-btn-box2{
+								display: block !important;
+						}
+						.resize-drag-box_lt,
+						.resize-drag-box_lb,
+						.resize-drag-box_rt,
+						.resize-drag-box_rb{
+								display: block;
+						}
+				}
+				.opt-btn-box2{
+						position: absolute;
+						padding: 6px 4px;
+						right: 0;
+						top: 0;
+						z-index: 10;
+						background: #fff;
+						border: 1px solid #ECF2FE;
+						box-shadow: 0 4px 8px rgba(0, 0, 0, 0.04);
+					.drag-btn {
+						display: block;
+						margin-bottom: 8px;
+					}
+				}
+				.resize-drag-box_lt,
+				.resize-drag-box_lb,
+				.resize-drag-box_rt,
+				.resize-drag-box_rb{
+						position: absolute;
+						width: 8px;
+						height: 8px;
+						display: none;
+						border: 1px solid #0052D9;
+				}
+				.resize-drag-box_lt{
+						left: -4px;
+						top: -4px;
+						cursor: nw-resize;
+				}
+				.resize-drag-box_lb{
+						left: -4px;
+						bottom: -4px;
+						cursor: ne-resize;
+				}
+				.resize-drag-box_rt{
+						right: -4px;
+						top: -4px;
+						cursor: ne-resize;
+				}
+				.resize-drag-box_rb{
+						right: -4px;
+						bottom: -4px;
+						cursor: nw-resize;
+				}
+		}
+		.blue-bg{
+				background: var(--brand-brand-1-light, #ECF2FE);
+		}
+}
+.report-content-box{
+    width: 100%;
+    margin: 0 auto;
+		padding: 20px;
+		.html-head-img-box,.html-end-img-box{
+				position: relative;
+				&:hover{
+						.opt-btn-box{
+								display: block !important;
+						}
+				}
+				.opt-btn-box{
+						position: absolute;
+						right: 0;
+						padding: 6px 10px 0;
+						top: -60px;
+						background: #fff;
+						border: 1px solid #ECF2FE;
+						box-shadow: 0 4px 8px rgba(0, 0, 0, 0.04);
+				}
+				.head-layout-item{
+						position: absolute;
+						overflow: hidden;
+						box-sizing: border-box
+				}
+		}
+}
+.add-comp-wrapper {
+  display: flex;
+  justify-content: center;
+  .add-ico {
+    background: $theme-color;
+    color: #fff;
+    border-radius: 50%;
+  }
+}
+.bot-action-box{
+    padding: 20px 16PX;
+    display: flex;
+    align-items: center;
+    .left-box{
+        flex: 1;
+        background: #FFFFFF;
+        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);
+        border-radius: 100px;
+        height: 112px;
+        display: flex;
+        align-items: center;
+        margin-right: 20px;
+        padding: 0 20px;
+        .item{
+            flex: 1;
+            text-align: center;
+            font-size: 20px;
+            img{
+                width: 40px;
+                height: 40px;
+                display: block;
+                margin: 5px auto;
+            }
+        }
+    }
+    .right-btn{
+        flex-shrink: 0;
+        position: relative;
+        width: 96px;
+        height: 96px;
+        background-color: $theme-color;
+        border-radius: 50%;
+        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);
+        svg{
+            width: 27px;
+            height: 27px;
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%,-50%);
+        }
+    }
+}
+.report-item-action-box{
+    .item{
+			 	padding: 20px;
+        text-align: center;
+        font-size: 32px;
+        border-top: 1px solid $border-color;
+    }
+}
+
+.top-box{
+    padding:32px;
+    display: flex;
+    justify-content: space-between;
+    border-bottom: 1px solid #DCDFE6;
+    .close{
+        color:#666666;
+    }
+    .title{
+        font-size: 36px;
+    }
+    .add-btn{
+        color:$theme-color;
+    }
+}
+.color-box {
+	padding: 20px;
+	display: flex;
+	align-items: center;
+}
+.comp-insert-add {
+	position: absolute;
+	bottom: -110px;
+	left: 50%;
+	transform: translateX(-50%);
+}
+@media screen and (min-width:$media-width){
+    .add-report-page{
+        height: calc(100dvh - 60px);
+        min-height: calc(95vh - 60px);
+    }
+		.report-content-box{
+			.html-head-img-box,.html-end-img-box{
+					.opt-btn-box{
+						top:-32px;
+						padding: 6px 10px 0;
+					}
+			}
+		} 
+
+    .bot-action-box{
+        margin: 0 auto;
+        width: 600px;
+        padding: 10px 16px;
+        .left-box{
+            border-radius: 50px;
+            height: 56px;
+            margin-right: 10px;
+            padding: 0 10px;
+            .item{
+                font-size: 12px;
+                img{
+                    width: 20px;
+                    height: 20px;
+                    margin: 3px auto;
+                }
+            }
+        }
+        .right-btn{
+            width: 48px;
+            height: 48px;
+            svg{
+                width: 14px;
+                height: 14px;
+            }
+        }
+    }
+    .main-wrap{ 
+      margin-top: 20px;
+			max-width: 800px;
+			.report-content-box {
+				padding: 20px ;
+			}
+			.report-drag-item-wrap {
+				margin-bottom: 80px;
+
+				.drag-btn::after{
+						width: 22px;
+						height: 22px;
+				}
+				.opt-btn-box{
+					top:-42px;
+					padding: 6px 10px 0;
+				}
+			}
+    }
+		.report-item-action-box{
+			.item{
+				font-size: 16px;
+			}
+		}	
+		.comp-insert-add {
+			bottom: -60px;
+		}
+}
+
+</style>

+ 18 - 0
src/views/report/smartReport/components/ChartComp.vue

@@ -0,0 +1,18 @@
+<script setup>
+import { ref } from 'vue'
+
+const props = defineProps({
+  compData:{}
+})
+</script>
+<template>
+  <div 
+      class="report-comp-item chart-comp"
+      style="width:100%;height:100%;overflow: hidden;display:flex;flex-direction: column;"
+  >
+      <iframe :src="compData.content" style="flex:1;width:100%;height:100%;border-width:0px;"></iframe>
+  </div>
+</template>
+<style scoped lang="scss">
+
+</style>

+ 19 - 0
src/views/report/smartReport/components/ImgComp.vue

@@ -0,0 +1,19 @@
+<script setup>
+import { ref } from 'vue'
+
+const props = defineProps({
+  compData:{}
+})
+
+</script>
+<template>
+  <div 
+      class="report-comp-item img-comp" 
+      style="width:100%;height:100%;overflow-y: auto;"
+  >
+      <img style="width:100%;display:block" :src="compData.content" alt="">
+  </div>
+</template>
+<style scoped lang="scss">
+
+</style>

+ 87 - 0
src/views/report/smartReport/components/ImgEditor.vue

@@ -0,0 +1,87 @@
+<script setup>
+import { ref, watch } from 'vue'
+import { uploadImgAPi } from '@/api/common'
+import { showToast } from 'vant'
+
+const props = defineProps({
+  defaultVal: ''
+})
+const emit = defineEmits(['close','confirm'])
+
+watch(() => props.defaultVal,
+  (newval) => {
+    fileList.value = newval ? [{url:newval}] : []
+  }
+)
+
+const fileList = ref(props.defaultVal?[{url: props.defaultVal}]:[])
+async function uploadAfter(e){
+    // console.log(e);
+   
+    let form = new FormData();
+        form.append('file',e.file);  //hostfile.name
+
+    const res = await uploadImgAPi(form)
+    if( res.Ret !== 200 ) return
+    fileList.value = [{url:res.Data.ResourceUrl}]
+}
+
+
+
+function handleCancle() {
+  fileList.value = []
+  emit('close')
+}
+
+function handleConfirm() {
+  if(!fileList.value[0]) return showToast('请上传图片')
+  emit('confirm',fileList.value[0].url)
+}
+</script>
+<template>
+  <div>
+  <div class="top-box">
+      <span style="color:#666666" @click="handleCancle">取消</span>
+      <span style="font-size:18px;font-weight:bold">插入图片</span>
+      <span style="color:#0052D9" @click="handleConfirm">确定</span>
+  </div>
+  <div class="img-box">
+    <van-uploader 
+      v-model="fileList"
+      accept="image/*"
+      :after-read="uploadAfter"
+      :max-count="1"
+    />
+  </div>
+</div>
+</template>
+<style scoped lang="scss">
+.top-box{
+    padding:32px;
+    display: flex;
+    justify-content: space-between;
+    border-bottom: 1px solid #DCDFE6;
+    .close{
+        color:#666666;
+    }
+    .title{
+        font-size: 36px;
+    }
+    .add-btn{
+        color:$theme-color;
+    }
+    
+}
+.img-box {
+  height: 50vh;
+  padding: 10px;
+}
+@media screen and (min-width:$media-width){
+  .top-box{
+      padding:16px;
+      .title{
+          font-size: 18px;
+      }
+  }
+}
+</style>

+ 175 - 0
src/views/report/smartReport/components/ReportLayoutImg.vue

@@ -0,0 +1,175 @@
+<script setup>
+import { reactive, ref } from 'vue'
+import apiReport from '@/api/report'
+import { vInfiniteScroll } from '@vueuse/components'
+import { showToast } from 'vant'
+
+
+
+const props = defineProps({
+  defaultVal: ''
+})
+const emit = defineEmits(['close','confirm'])
+
+const imgTypeOpts = [
+  { text:'版头',value: 1 },
+  { text:'版尾',value: 2 },
+]
+
+const list = ref([])
+const filterState = reactive({
+    type: props.defaultVal?props.defaultVal:1,
+    page:1,
+    pageSize:20,
+    finished:false,
+    searchVal: ''
+})            
+async function getList() {
+  const res=await apiReport.imgReourceList({
+      CurrentIndex:filterState.page,
+      PageSize:filterState.pageSize,
+      Type:filterState.type,
+      Keyword:filterState.searchVal
+  })
+  if(res.Ret===200){
+      const arr = res.Data.List || [];
+      list.value =
+          filterState.page === 1
+          ? arr
+          : [...this.list.value, ...arr];
+      filterState.finished =  res.Data.Paging.IsEnd;
+  }
+}
+getList()
+
+function initList() {
+  filterState.page = 1;
+  getList()
+}
+
+function onLoadMore() {
+  if(filterState.finished) return
+
+  filterState.page++
+  getList()
+}
+
+
+const selectItem = ref(null)
+function handleSelect(e) {
+  selectItem.value = e
+}
+
+function handleCancle() {
+  emit('close')
+}
+function handleConfirm() {
+  if(!selectItem.value) return showToast('请选择图片')
+
+  emit('confirm',{
+    type:filterState.type,
+    data:selectItem.value
+  })
+}
+
+</script>
+<template>
+  <div class="img-source-cont">
+    <div class="top-box">
+        <span style="color:#666666" @click="handleCancle">取消</span>
+        <van-dropdown-menu>
+          <van-dropdown-item v-model="filterState.type" :options="imgTypeOpts" @change="initList" :disabled="defaultVal?true:false"/>
+        </van-dropdown-menu>
+        <span :style="{color:selectItem?'#0052D9':'#666'}" @click="handleConfirm">确定</span>
+    </div>
+
+    <div class="container">
+      <div class="top">
+
+        <van-search v-model="filterState.searchVal" placeholder="请输入搜索关键词" @search="initList"/>
+
+      </div>
+      <ul class="img-list" v-infinite-scroll="[onLoadMore, { 'distance' : 10 }]" v-if="list.length">
+        <li 
+            :class="['item',selectItem&&selectItem.ResourceId===item.ResourceId?'active':'']" 
+            v-for="item in list" 
+            :key="item.ResourceId"
+            @click="handleSelect(item)"
+        >
+            
+            <img :src="item.ImgUrl" alt="">
+            <div class="title">{{item.ImgName}}</div>
+            <svg v-if="selectItem&&selectItem.ResourceId===item.ResourceId" width="29" height="28" viewBox="0 0 29 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <path d="M14.5 28C22.232 28 28.5 21.732 28.5 14C28.5 6.26801 22.232 0 14.5 0C6.76801 0 0.5 6.26801 0.5 14C0.5 21.732 6.76801 28 14.5 28ZM7.5 14.413L8.913 13L12.5 16.586L20.085 9L21.5 10.415L12.5 19.414L7.5 14.413Z" fill="#0052D9"/>
+            </svg>
+        </li>
+      </ul>
+
+      <div v-if="!list.length&&filterState.finished">
+          <img class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
+          <p style="text-align:center;color:#999999;font-size:12px">暂无图片</p>
+      </div>
+    </div>
+  </div>
+</template>
+<style scoped lang="scss">
+.img-source-cont {
+  display: flex;
+  flex-direction: column;
+}
+.top-box{
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 116px;
+    border-bottom: 1px solid $border-color;
+    padding: 0 34px;
+}
+.img-list{
+    height: 800px;
+    padding: 4px;
+    flex: 1;
+    overflow-y: auto;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    .item{
+        width: 48%;
+        background: #FFFFFF;
+        border: 1px solid $border-color;
+        box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.03);
+        border-radius: 4px;
+        margin-bottom: 30px;
+        overflow: hidden;
+        padding: 14px;
+        box-sizing: border-box;
+        position: relative;
+        max-height: 400px;
+        img{
+            width: 100%;
+        }
+    }
+    .active{
+        border-color: $theme-color;
+        svg{
+            width: 28px;
+            height: 28px;
+            position: absolute;
+            right: 22px;
+            bottom: 22px;
+        }
+    }
+}
+:deep(.van-dropdown-menu__bar) {
+  box-shadow: none;
+}
+@media screen and (min-width:$media-width){
+  .top-box{
+    height: 60px;
+    padding: 0 30px;
+  }
+  .img-list{
+    height: 600px;
+  }  
+}
+</style>

+ 42 - 0
src/views/report/smartReport/components/SheetComp.vue

@@ -0,0 +1,42 @@
+<script setup>
+import { computed, ref } from 'vue'
+
+const props = defineProps({
+  compData:{}
+})
+
+const id = computed(() => {
+    if(props.compData.content){
+        let params = GetQueryString(props.compData.content)
+        return `iframe${params.code}`
+    }
+    return ''
+})
+
+function GetQueryString(url) {
+    let urlStr=url.split('?')[1]
+    let obj={}
+    let paramsArr=urlStr.split('&')
+    for (let index = 0; index < paramsArr.length; index++) {
+        let arr=paramsArr[index].split('=')
+        obj[arr[0]]=arr[1]
+    }
+    return obj
+}
+</script>
+<template>
+  <div 
+      class="report-comp-item sheet-comp"
+      style="width:100%;overflow: hidden;"
+  >
+      <div 
+          style="padding-left:10px;font-size:17px"
+          v-html="compData.titleText"
+          v-if="compData.titleText"
+      ></div>
+      <iframe :class="id" :src="compData.content" width="100%" style="border-width:0px;"></iframe>
+  </div>
+</template>
+<style scoped lang="scss">
+
+</style>

+ 18 - 0
src/views/report/smartReport/components/TextComp.vue

@@ -0,0 +1,18 @@
+<script setup>
+import { ref } from 'vue'
+
+const props = defineProps({
+  compData:{}
+})
+</script>
+<template>
+  <div 
+      class="report-comp-item text-comp" 
+      style="width:100%;height: 100%;overflow-y: auto;"
+  >
+      <div class="rich-text-box" v-html="compData.content"></div>
+  </div>
+</template>
+<style scoped lang="scss">
+
+</style>

+ 109 - 0
src/views/report/smartReport/components/TextEditor.vue

@@ -0,0 +1,109 @@
+<script setup>
+import { nextTick, onMounted, ref, watch } from 'vue'
+import {useInitFroalaEditor} from '@/hooks/useFroalaEditor'
+
+const props = defineProps({
+  defaultVal: ''
+})
+const emit = defineEmits(['close','confirm'])
+
+const {lastFocusPosition,initFroalaEditor,imgUploadFlag,frolaEditorContentChange}=useInitFroalaEditor()
+
+let reportContentEditorIns=null//报告内容编辑器实例
+
+
+onMounted(() => {
+  reportContentEditorIns = initFroalaEditor('#editor',{
+    height:400, 
+    toolbarButtons: [
+      "textColor",
+      "bold",
+      "italic",
+      "underline",
+      "strikeThrough",
+      "subscript",
+      "superscript",
+      "fontFamily",
+      "fontSize",
+      "color",
+      "inlineClass",
+      "inlineStyle",
+      "paragraphStyle",
+      "lineHeight",
+      "paragraphFormat",
+      "align",
+      "formatOL",
+      "formatUL",
+      "outdent",
+      "indent",
+      "quote",
+      "specialCharacters",
+      "insertHR",
+      "selectAll",
+      "clearFormatting",
+      /* "html", */
+      "undo",
+      "redo",
+    ]
+  })
+
+  setTimeout(() => {
+    props.defaultVal&&reportContentEditorIns.html.set(props.defaultVal);
+  })
+})
+
+watch(() => props.defaultVal,
+  (nval) => {
+    reportContentEditorIns.html.set(nval)
+  }
+)
+
+
+function handleCancle() {
+  emit('close')
+}
+
+function handleConfirm() {
+  emit('confirm',$('.fr-element').html())
+}
+</script>
+<template>
+<div>
+  <div class="top-box">
+      <span style="color:#666666" @click="handleCancle">取消</span>
+      <span style="font-size:18px;font-weight:bold">编辑文字</span>
+      <span style="color:#0052D9" @click="handleConfirm">确定</span>
+  </div>
+  <div class="editor-box" id="editor"></div>
+</div>
+</template>
+<style scoped lang="scss">
+.top-box{
+    padding:32px;
+    display: flex;
+    justify-content: space-between;
+    border-bottom: 1px solid #DCDFE6;
+    .close{
+        color:#666666;
+    }
+    .title{
+        font-size: 36px;
+    }
+    .add-btn{
+        color:$theme-color;
+    }
+}
+.editor-box {
+  width: 100%;
+  height: 100%;
+  padding: 10px;
+}
+@media screen and (min-width:$media-width){
+  .top-box{
+      padding:16px;
+      .title{
+          font-size: 18px;
+      }
+  }
+}
+</style>

+ 10 - 0
src/views/report/smartReport/hooks/useSmartEditor.js

@@ -0,0 +1,10 @@
+import { nextTick, reactive, ref,toRefs } from 'vue'
+
+
+
+
+export function useSmartEditor() {
+
+}
+
+

+ 41 - 12
src/views/reportEn/List.vue

@@ -27,6 +27,16 @@ const {isApprove,isOtherApprove,getEtaConfig} = useReportApprove()
 const { width, height } = useWindowSize()
 
 const router=useRouter()
+
+const statusMap = new Map([
+    [1,{ bg:'#EEEEEE', color:'#333', msg:'未发布'}],
+    [2,{ bg:'rgba(0, 82, 217, 0.1)', color:'#0052D9', msg:'已发布'}],
+    [3,{ bg:'#FFF1E9', color:'#BE5A00', msg:'待提交'}],
+    [4,{ bg:'#FFF1E9', color:'#BE5A00', msg:'待审批'}],
+    [5,{ bg:'#FFF0ED', color:'#AD352F', msg:'已驳回'}],
+    [6,{ bg:'#E3F9E9', color:'#006C45', msg:'已通过'}]
+])
+
 // 水印
 const waterMarkStr=ref('')
 //是否显示一键清空选项
@@ -628,12 +638,23 @@ onMounted(()=>{
                     :key="item.Id"
                     class="select-text-disabled item" 
                     @click="goDetail(item)"
-                    v-longpress="{ handler: onLongPressItem, args: item, duration: 1000 }"
                 >
-                    <h2 :class="['van-ellipsis title',item.Title.startsWith('【')?'inline-title':'']">
-                        <span>{{item.Title}}</span>
-                        <span v-if="item.CreateTime">({{item.CreateTime.substring(5,7)}}{{item.CreateTime.substring(8,10)}})</span>
-                    </h2>
+                    <div class="list-top">
+                        <h2 :class="['van-ellipsis title',item.Title.startsWith('【')?'inline-title':'']">
+                            <span>{{item.Title}}</span>
+                            <span v-if="item.CreateTime">({{item.CreateTime.substring(5,7)}}{{item.CreateTime.substring(8,10)}})</span>
+                        </h2>
+
+                        <div class="status">
+                            <van-tag 
+                                type="primary" 
+                                size="large"
+                                :color="statusMap.get(item.State).bg"
+                                :text-color="statusMap.get(item.State).color"
+                            >{{statusMap.get(item.State).msg}}</van-tag>
+                        </div>
+
+                    </div>
                     <p class="van-multi-ellipsis--l2 des">{{item.Abstract}}</p>
                     <div class="bot-info">
                         <div style="flex:1">
@@ -647,13 +668,13 @@ onMounted(()=>{
                         <div class="read-count">
                             <span>PV:{{item.Pv}}</span>
                         </div>
-                        <div>
-                            <span v-if="item.State===1">未发布</span>
-                            <span v-if="item.State===2" class="active-status">已发布</span>
-                            <span v-if="item.State===3">待提交</span>
-                            <span v-if="item.State===4">待审批</span>
-                            <span v-if="item.State===5">已驳回</span>
-                            <span v-if="item.State===6" class="active-status">已通过</span>
+                        <div class="handle-icon" @click.stop="onLongPressItem(item)">
+                            <svg width="24" height="24" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                <path d="M16 8C17.1045 8 18 7.10455 18 6C18 4.89545 17.1045 4 16 4C14.8955 4 14 4.89545 14 6C14 7.10455 14.8955 8 16 8Z" fill="black" fill-opacity="0.9"/>
+                                <path d="M16 18C17.1045 18 18 17.1046 18 16C18 14.8954 17.1045 14 16 14C14.8955 14 14 14.8954 14 16C14 17.1046 14.8955 18 16 18Z" fill="black" fill-opacity="0.9"/>
+                                <path d="M18 26C18 24.8954 17.1045 24 16 24C14.8955 24 14 24.8954 14 26C14 27.1046 14.8955 28 16 28C17.1045 28 18 27.1046 18 26Z" fill="black" fill-opacity="0.9"/>
+                            </svg>
+
                         </div>
                     </div>
                 </li>
@@ -943,6 +964,13 @@ onMounted(()=>{
         border: 1px solid $border-color;
         box-shadow: 0px 3px 12px rgba(52, 75, 120, 0.08);
         border-radius: 8px;
+        .list-top {
+            display: flex;
+            justify-content: space-between;
+            .status {
+                flex-shrink: 0;
+            }
+        }
         .title{
             font-size: 32px;
             line-height: 44px;
@@ -961,6 +989,7 @@ onMounted(()=>{
         .bot-info{
             display: flex;
             justify-content: space-between;
+            align-items: center;
             color: $font-grey;
             font-size: 28px;
             .active-status{

+ 22 - 22
src/views/tabbar/Home.vue

@@ -62,28 +62,28 @@ const topImg=computed(()=>{
  * type 类型 zh中文 en英文
  */
 const menuConfig=[
-    // {
-    //     name:'中文研报',
-    //     des:'研报一体化管理',
-    //     key:'研报列表',
-    //     type:'zh',
-    //     level:2,
-    //     path:'/report/list',
-    //     icon:getStaticImg('tabbar/icon_report.png'),
-    //     backgroundColor:'#FFFBF6',
-    //     show:false
-    // },
-    // {
-    //     name:'English Research Report',
-    //     des:'Integrated Research Report Management',
-    //     key:'英文研报',
-    //     type:'en',
-    //     level:2,
-    //     path:'/reportEn/list',
-    //     icon:getStaticImg('tabbar/icon_report.png'),
-    //     backgroundColor:'#FFFBF6',
-    //     show:false
-    // },
+    {
+        name:'研报中心',
+        des:'研报一体化管理',
+        key:'研报中心',
+        type:'zh',
+        level:2,
+        path:'/report/list',
+        icon:getStaticImg('tabbar/icon_report.png'),
+        backgroundColor:'#FFFBF6',
+        show:false
+    },
+    {
+        name:'English Research Report',
+        des:'Integrated Research Report Management',
+        key:'英文研报',
+        type:'en',
+        level:2,
+        path:'/reportEn/list',
+        icon:getStaticImg('tabbar/icon_report.png'),
+        backgroundColor:'#FFFBF6',
+        show:false
+    },
     {
         name:'智能PPT',
         des:"支持共享协作编辑",

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio