Преглед изворни кода

报告列表页,章节列表页调整

Karsa пре 7 месеци
родитељ
комит
77bc3bb130

+ 45 - 0
src/api/report.js

@@ -362,5 +362,50 @@ export default {
      */
     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) 
     }
+
 }

+ 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) {

+ 95 - 191
src/views/report/EditReport.vue

@@ -1,11 +1,10 @@
 <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 moment from 'moment'
 import { showToast,showDialog } from 'vant'
@@ -16,6 +15,7 @@ import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
 import {useReportApprove} from '@/hooks/useReportApprove'
 import {Base64} from 'js-base64'
 import AddReportBaseInfoV2 from './components/AddReportBaseInfoV2.vue'
+import { useReportHandles } from './hooks/useReport'
 
 const cachedViewsStore=useCachedViewsStore()
 const publicSettingStore = usePublicSettingStore()
@@ -27,6 +27,8 @@ const {checkAuthBtn} = useAuthBtn()
 const {lastFocusPosition,initFroalaEditor,imgUploadFlag,frolaEditorContentChange}=useInitFroalaEditor()
 let reportContentEditorIns=null//报告内容编辑器实例
 
+const { handleRefresh,handleSubmitReport } = useReportHandles()
+
 let autoSaveTimer=null
 // 水印
 const waterMarkStr=ref('')
@@ -46,16 +48,23 @@ onUnmounted(()=>{
 })
 
 // 自动保存报告
-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
+   
 }
 
 // 获取报告详情
@@ -71,18 +80,21 @@ 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团队']
         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(_ => ({
@@ -118,28 +130,13 @@ let reportBaseInfoData={
     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
 }
@@ -203,49 +200,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: reportData.value.Id,chapterId:reportData.value.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,
@@ -262,94 +222,72 @@ 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('保存成功')
+    sessionStorage.setItem('reportPreData',JSON.stringify(params))
+    const routerEl=router.resolve({
+        path:'/report/preview',
+        query:{
+            id:-1
         }
-    }
-    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
-        }
-        // 没有客群了发布都有二次提示弹窗
-        showPublishPop.value=true
-    }
-    if(type==='dsfb'){
-        showDSFBTime.value=true
-    }
-    if(type==='submit'){
-        handleReportSubmit(params)
-    }
+    })
+    window.open(routerEl.href,'_blank')
+    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
+
+//发布 定时 提交
+async function handlePublishReport(tp) {
+    cachedViewsStore.removeCaches('ReportList')
+
+    const saveRes = await autoSaveReportContent('auto');
+    if(!saveRes) return
+
+    if(tp==='dsfb'){
+        showDSFBTime.value=true
+		return
+    }else if(tp==='submit'){
+        handleSubmitReport({id:Number(route.query.id)})
+        return
     }
-    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()
+
+    let sendMsg = reportData.value.MsgIsSend;
+    if(sendMsg===1){
+        reportPublish({sendMsg: false})
+    }else {
+        const isPost = checkAuthBtn(reportManageBtn.reportManage_sendMsg)
+
+        showDialog({
+            title: '发布提示',
+            showCancelButton: true,
+            message: isPost?'发布后,是否推送模板消息?':'是否立即发布报告?',
+            confirmButtonText: isPost?'推送':'发布',
+            cancelButtonText: isPost?'不推送':'取消',      
+        }).then(() => {
+            reportPublish({sendMsg: isPost?true:false})
+        })
+        .catch(() => {
+            if(isPost) reportPublish({sendMsg: false});
+        });
+
     }
 }
 
-function generatePdfLinks(Code){
-    return `${publicSettingStore.publicSetting.ReportViewUrl}/reportshare_pdf?code=${Code}&flag=${waterMarkStr.value}`
+
+function generatePdfLinks(){
+    let code = reportData.value.ReportCode
+
+    return `${publicSettingStore.publicSetting.ReportViewUrl}/reportshare_pdf?code=${code}&flag=${waterMarkStr.value}`
 }
 
-async function reportPublish(id,code){
-    const res=await apiReport.reportPublish({ReportIds:id.toString(),ReportUrl:generatePdfLinks(code)})
+async function reportPublish({sendMsg}){
+    const res=await apiReport.reportPublish({ReportIds:String(route.query.id),ReportUrl:generatePdfLinks()})
     if(res.Ret===200){
+        sendMsg && apiReport.reportMessageSend({ReportId: Number(route.query.id)})
+
+        showToast('发布成功')
         console.log('back');
-        router.back()
+        setTimeout(() => {
+            router.back()
+        },1000)
     }
 }
 
@@ -362,7 +300,7 @@ function onConfirmDSFBTime(time){
             ReportId:reportData.value.Id,
             PrePublishTime:time,
             PreMsgSend:0,
-            ReportUrl:generatePdfLinks(reportData.value.ReportCode)
+            ReportUrl:generatePdfLinks()
         }).then(res=>{
             if(res.Ret===200){
                 showToast('定时发布成功!')
@@ -386,7 +324,7 @@ function onConfirmDSFBTime(time){
                 ReportId:reportData.value.Id,
                 PrePublishTime:time,
                 PreMsgSend:0,
-                ReportUrl:generatePdfLinks(reportData.value.ReportCode)
+                ReportUrl:generatePdfLinks()
             }).then(res=>{
                 if(res.Ret===200){
                     showToast('定时发布成功!')
@@ -402,7 +340,7 @@ function onConfirmDSFBTime(time){
             ReportId:reportData.value.Id,
             PrePublishTime:time,
             PreMsgSend:1,
-            ReportUrl:generatePdfLinks(reportData.value.ReportCode)
+            ReportUrl:generatePdfLinks()
         }).then(res=>{
             if(res.Ret===200){
                 showToast('定时发布成功!')
@@ -418,7 +356,7 @@ function onConfirmDSFBTime(time){
             ReportId:reportData.value.Id,
             PrePublishTime:time,
             PreMsgSend:0,
-            ReportUrl:generatePdfLinks(reportData.value.ReportCode)
+            ReportUrl:generatePdfLinks()
         }).then(res=>{
             if(res.Ret===200){
                 showToast('定时发布成功!')
@@ -430,25 +368,6 @@ function onConfirmDSFBTime(time){
     })
 }
 
-//提交报告
-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()
-        })
-    }).catch(()=>{})
-}
-
 const getSystemInfoFun=()=>{
     getSystemInfo().then(res=>{
         if(res.Ret===200){
@@ -482,26 +401,26 @@ const getSystemInfoFun=()=>{
                     <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="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="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">
+                    <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="handleReportOpt('fb')" v-permission="reportManageBtn.reportManage_publish">
+                    <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="handleReportOpt('submit')" v-permission="reportManageBtn.reportManage_publish">
+                    <div class="item" @click="handlePublishReport('submit')" v-permission="reportManageBtn.reportManage_publish">
                         <img src="@/assets/imgs/report/icon_publish3.png" alt="">
                         <span>提交</span>
                     </div>
@@ -521,7 +440,6 @@ 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)"
@@ -539,20 +457,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" />

+ 33 - 28
src/views/report/List.vue

@@ -412,35 +412,39 @@ function handeAddReport(e){
 async function handleReportEdit(e){
     console.log(e);
     showReportItemOpt.value=false
-    // 先mark一下
-    const markRes=await apiReport.reportMark({
-        Status:1,
-        ReportId:e.Id
-    })
-    if(markRes.Ret===200){
-        if(markRes.Data.Status===1){
-            showToast(markRes.Data.Msg || '该研报正在编辑,不可重复编辑')
-            listState.list.forEach(item=>{
-                if(item.Id===e.Id){
-                    item.CanEdit=false
-                    item.Editor=markRes.Data.Editor || ''
-                }
-            })
+
+    if(e.CollaborateType===1) {
+        // 先mark一下
+        const markRes=await apiReport.reportMark({
+            Status:1,
+            ReportId:e.Id
+        })
+        if(markRes.Ret===200){
+            if(markRes.Data.Status===1){
+                showToast(markRes.Data.Msg || '该研报正在编辑,不可重复编辑')
+                listState.list.forEach(item=>{
+                    if(item.Id===e.Id){
+                        item.CanEdit=false
+                        item.Editor=markRes.Data.Editor || ''
+                    }
+                })
+                return
+            }else if(markRes.Data.Status===0){
+                listState.list.forEach(item=>{
+                    if(item.Id===e.Id){
+                        item.CanEdit=true
+                        item.Editor=markRes.Data.Editor || ''
+                    }
+                })
+            }
+        }else{
+            showToast(markRes.ErrMsg || '未知错误,请稍后重试')
             return
-        }else if(markRes.Data.Status===0){
-            listState.list.forEach(item=>{
-                if(item.Id===e.Id){
-                    item.CanEdit=true
-                    item.Editor=markRes.Data.Editor || ''
-                }
-            })
         }
-    }else{
-        showToast(markRes.ErrMsg || '未知错误,请稍后重试')
-        return
     }
+
     // 晨报周报
-    if(['day','week'].includes(e.ChapterType)){
+    if(e.CollaborateType===2){
         router.push({
             path:"/report/chapter/list",
             query:{
@@ -450,11 +454,12 @@ async function handleReportEdit(e){
         return
     }
 
-    // 研报
+    // 研报d
     router.push({
-        path:'/report/edit',
+        path:e.ReportLayout===1 ? '/report/edit' : "/smart_report/edit",
         query:{
-            id:e.Id
+            id:e.Id,
+            coopType: e.CollaborateType
         }
     })
 }

+ 4 - 3
src/views/report/PreviewDetail.vue

@@ -47,7 +47,7 @@ async function goEdit(){
         return
     }
 
-    if(['day','week'].includes(reportInfo.value.ChapterType)){
+    if(reportInfo.value.CollaborateType===2){
         router.push({
             path:"/report/chapter/list",
             query:{
@@ -57,9 +57,10 @@ async function goEdit(){
     }else{
         // 研报
         router.push({
-            path:'/report/edit',
+            path:reportInfo.value.ReportLayout===1 ? '/report/edit' : "/smart_report/edit",
             query:{
-                id:reportInfo.value.Id
+                id:reportInfo.value.Id,
+                coopType: reportInfo.value.CollaborateType
             }
         })
     }

+ 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"

+ 327 - 76
src/views/report/chapter/List.vue

@@ -1,43 +1,115 @@
 <script setup name="reportChapterList">
-import {nextTick, reactive, ref} from 'vue'
+import {computed, nextTick, reactive, ref} from 'vue'
 import { useRoute, useRouter } from "vue-router";
 import apiReport from '@/api/report'
 import {apiGetWXQRCodeImg} from '@/api/common'
 import moment from 'moment';
+import {Base64} from 'js-base64'
 import { showToast,showDialog } from 'vant';
 import { useWindowSize } from '@vueuse/core'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
 import EditBaseInfo from './components/EidtBaseInfo.vue'
 import html2canvas from "html2canvas";
 import {transfImgTobase64} from '@/hooks/common'
+import draggable from 'vuedraggable'
 import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+import {useReportApprove} from '@/hooks/useReportApprove'
+import AddReportBaseInfoV2 from '../components/AddReportBaseInfoV2.vue'
+import EditChapterBaseInfo from './components/EditChapterBaseInfo.vue'
 
 const cachedViewsStore=useCachedViewsStore()
 const { width, height } = useWindowSize()
 
-
 const route=useRoute()
 const router=useRouter()
 
+
+const {isApprove,hasApproveFlow,getEtaConfig,checkClassifyNameArr} = useReportApprove()
+
+const {checkAuthBtn} = useAuthBtn()
+
 //基础信息
 const showBaseInfoPop=ref(false)
 //更新基本信息
-function handleUpdateBaseInfo(e){
+let reportBaseInfoData={}
+async function handleReportBaseInfoChange(e){
+    reportBaseInfoData={
+        ...e,
+        classifyName:e.classifys,
+        createtime:e.time,
+    }
+
     reportInfo.value.Title=e.title
     reportInfo.value.Author=e.author
+    reportInfo.value.Abstract=e.abstract
     reportInfo.value.CreateTime=e.createtime
+    reportInfo.value.ClassifyIdFirst=e.classifys[0]?.id
+    reportInfo.value.ClassifyIdSecond=e.classifys[1]?.id
+    reportInfo.value.ClassifyIdThird=e.classifys[2]?.id
+    reportInfo.value.ClassifyNameFirst=e.classifys[0]?.text
+    reportInfo.value.ClassifyNameSecond=e.classifys[1]?.text
+    reportInfo.value.ClassifyNameThird=e.classifys[2]?.text
+
+    const classify = e.classifys.map(i=>i.id)
+    checkClassifyNameArr(1,classify)
     showBaseInfoPop.value=false
 }
 
+
+const isCreator = computed(() => {
+
+    let userInfo = JSON.parse(Base64.decode(localStorage.getItem('userInfo')))
+    return userInfo.AdminId === reportInfo.value.AdminId;
+})
+
 // 获取报告详情
 let reportInfo=ref(null)
+const reportVarietyOpts = ref([])
+const userOpts = ref([])
 async function getReportDetail(){
-    const res=await apiReport.getReportDetail({ReportId:Number(route.query.id)})
+    const res=await apiReport.getRportBase({ReportId:Number(route.query.id)})
     if(res.Ret===200){
-        reportInfo.value=res.Data
+        reportInfo.value=res.Data;
+        
+        reportBaseInfoData = {
+            addType: res.Data.AddType,
+            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,
+                }
+            ],
+            author:res.Data.Author ? res.Data.Author.split(',') : ['FICC团队'],
+            createtime:moment(res.Data.CreateTime).format('YYYY-MM-DD'),
+            title:res.Data.Title,
+            abstract:res.Data.Abstract,
+            cooperationType:res.Data.CollaborateType,
+            cooperationUsers:res.Data.GrandAdminList
+                ? res.Data.GrandAdminList.map(_ => ({
+                    NodeId: _.AdminId,
+                    NodeName: _.AdminName
+                }))
+                : [],
+            reportLayout: res.Data.ReportLayout,
+            isPublcPublish: res.Data.IsPublicPublish
+        }
+
+        reportVarietyOpts.value = res.Data.PermissionList||[];
+        userOpts.value = res.Data.GrandAdminList||[];
+
         document.title=res.Data.ClassifyNameFirst
     }
     getChapterList()
+
+    checkClassifyNameArr(1,[res.Data.ClassifyIdFirst,res.Data.ClassifyIdSecond,res.Data.ClassifyIdThird])
 }
 getReportDetail()
 
@@ -52,6 +124,75 @@ async function getChapterList(){
 }
 
 
+/* 章节操作弹窗 */
+const showItemOpt = ref(false)
+const activeItem = ref(null)
+function handleOptChapterItem(e) {
+    activeItem.value = e;
+    showItemOpt.value = true
+}
+
+
+/* 章节信息弹窗 */
+const showChapterBaseInfo = ref(false)
+function handleChapterInfo(e) {
+    activeItem.value = e;
+    showItemOpt.value = false
+    showChapterBaseInfo.value = true
+}
+function handleChapterBaseInfoSave() {
+    getChapterList()
+    showChapterBaseInfo.value = false
+}
+
+
+ /* 删除章节 */
+function handleDelChapter(item) {
+    showItemOpt.value = false
+    showDialog({
+        title: '删除提示',
+        showCancelButton: true,
+        message: '是否确认删除该章节?',
+        confirmButtonText: '确认',
+        cancelButtonText: '取消', 
+    }).then( async() => {
+        const res = await apiReport.removeChapter({
+            ReportChapterId: item.ReportChapterId
+        })
+
+        if(res.Ret!==200) return
+        showToast('删除成功')
+        chapterList.value.splice(chapterList.value.findIndex(_=>_.ReportChapterId===item.ReportChapterId),1)
+
+    }).catch(() =>{})
+
+}
+
+/* 上传章节音频 */
+function handleUploadChapterAudio(item) {
+    showItemOpt.value = false
+}
+
+async function  handleMoveChapter({oldIndex,newIndex}) {
+    console.log(oldIndex,newIndex)
+    console.log(chapterList.value)
+    
+    let ReportChapterId = chapterList.value[newIndex].ReportChapterId,
+        PrevReportChapterId = (newIndex > 0 ? chapterList.value[newIndex-1].ReportChapterId : 0),
+        NextReportChapterId = newIndex === chapterList.value.length - 1 ? 0 : chapterList.value[newIndex + 1].ReportChapterId;
+    const res = await apiReport.moveChapter({
+        ReportChapterId,
+        PrevReportChapterId,
+        NextReportChapterId
+    })
+    
+    if (res.Ret !== 200) return;
+    showToast('移动成功')
+}
+
+
+
+
 // 获取章节标签数据
 let tagOpts=ref([])
 function getChapterTrendTagList(){
@@ -73,6 +214,7 @@ const temTrendTagData=reactive({
     tagVal:['']
 })
 function handleShowTrendTag(item){
+    showItemOpt.value = false
     temTrendTagData.ReportChapterId=item.ReportChapterId
     temTrendTagData.tagVal[0]=item.Trend||tagOpts.value[0].KeyWord
     showTagPop.value=true
@@ -245,51 +387,73 @@ async function handleShowPoster(item){
 
 <template>
     <div class="report-chapterlist-page" v-if="reportInfo">
-        <van-cell title="基础信息" is-link @click="showBaseInfoPop=true"/>
+        <!-- <van-cell title="基础信息" is-link @click="showBaseInfoPop=true"/> -->
         <div class="chapter-list-wrap">
-            <div class="top-box">
-                <span class="list-lable">章节列表</span>
-            </div>
             
-            <ul class="chapter-list">
-                <li class="item" v-for="item in chapterList" :key="item.ReportChapterId" @click="goChapterDetail(item)">
-                    <div class="img-box">
-                        <img :src="item.TypeEditImg" alt="">
-                        <span>{{item.TypeName}}</span>
-                    </div>
-                    <div class="content">
-                        <div class="title">{{item.TypeName}}</div>
-                        <div class="tag-text" v-if="item.Trend">#{{item.Trend}}</div>
-                        <div class="time">{{item.ModifyTime}}</div>
-                        <!-- 上传了音频icon -->
-                        <img class="audio-icon" src="@/assets/imgs/report/icon_audio.png" alt="" v-if="reportInfo.ClassifyNameFirst=='周报'&&item.VideoUrl&&item.VideoKind==1">
-                        <div :class="['status',item.PublishState!=1&&'status-success']">{{item.PublishState==1?'未发布':'已发布'}}</div>
-                        <img @click.stop="handleShowTrendTag(item)" class="icon icon-tag" src="@/assets/imgs/report/icon_tag.png" alt="">
-                        <img v-if="item.PublishState==2" @click.stop="handleShowPoster(item)" class="icon icon-wx" src="@/assets/imgs/report/icon_wx.png" alt="">
-                    </div>
-                </li>
-            </ul>
+            <!-- <ul class="chapter-list"> -->
+            <draggable 
+                class="chapter-list"
+                :list="chapterList" 
+                item-key="ReportChapterId"
+                animation="300"
+                @end="handleMoveChapter"
+                tag="ul"
+            >
+                    <!-- v-for="item in chapterList" :key="item.ReportChapterId" -->
+                <template #item="{element}">
+                    <li class="item"  @click="goChapterDetail(element)">
+                        <div class="item-top">
+                            <div class="type-name" v-if="element.TypeName">{{element.TypeName}}宏观</div>
+                        </div>
+
+                        <div class="content">
+                            <div class="title">{{element.Title}}</div>
+                            <div class="bottom">
+                                <div class="flex-info">
+                                    <div 
+                                        :class="['status',element.PublishState!=1&&'status-success']"
+                                        v-if="element.CanEdit"
+                                    >{{element.PublishState==1?'未提交':'已提交'}}</div>
+                                    <div :style="`color:${$theme-warning}`" v-else>{{element.Editor}}编辑中...</div>
+                                    <div class="tag-text" v-if="element.Trend">#{{element.Trend}}</div>
+                                </div>
+                                <div class="flex-info">
+                                    <img v-if="element.PublishState==2" @click.stop="handleShowPoster(element)" class="icon icon-wx" src="@/assets/imgs/report/icon_wx.png" alt="">
+
+                                    <div class="handle-icon" @click.stop="handleOptChapterItem(element)">
+                                        <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>
+                            </div>
+                            
+                        </div>
+                    </li>
+                </template>
+            </draggable>      
+            <!-- </ul> -->
+
+            <van-button
+                v-if="checkAuthBtn(reportManageBtn.reportMange_chapter_add)"
+                type="primary" 
+                :size="chapterList.length?'normal':'large'" 
+                @click="handleChapterInfo(null)"
+            >添加章节</van-button>
         </div>
 
-        <!-- <div class="bot-btns">
-            <van-button class="bot-btn" type="default" @click="handlePublishValid">发布</van-button>
-            <van-button class="bot-btn" type="primary" @click="handleSaveReportInfo">保存</van-button>
-        </div> -->
-
         <div class="bot-action-box">
             <div class="action-box">
-                <div class="item" @click="handleEditBaseInfo">
-                    <img src="@/assets/imgs/report/icon_refresh.png" alt="">
+                <div class="item" @click="showBaseInfoPop=true">
+                    <img src="@/assets/imgs/report/icon_info.png" alt="">
                     <span>基础信息</span>
                 </div>
                 <div class="item" @click="handleReportOpt('yl')" v-permission="reportManageBtn.reportManage_reportView">
                     <img src="@/assets/imgs/report/icon_preview.png" alt="">
                     <span>预览</span>
                 </div>
-                <div class="item" @click="handleReportOpt('cg')">
-                    <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="">
@@ -316,11 +480,17 @@ async function handleShowPoster(item){
         position="bottom"
         :style="{height:'100%'}"
     >
-        <EditBaseInfo 
+        <!-- <EditBaseInfo 
             v-if="showBaseInfoPop" 
             :defaultData="reportInfo"
             @close="showBaseInfoPop=false"
             @confirm="handleUpdateBaseInfo"
+        /> -->
+        <AddReportBaseInfoV2
+            @close="showBaseInfoPop=false"
+            :id="Number(route.query.id)"
+            :defaultData="reportBaseInfoData"
+            @confirm="handleReportBaseInfoChange"
         />
     </van-popup>
 
@@ -349,6 +519,66 @@ async function handleShowPoster(item){
         <img class="chapter-poster-img" :src="posterImgBase64" alt="">
     </van-popup>
 
+    <!-- 章节信息弹窗 -->
+    <van-popup 
+        v-model:show="showChapterBaseInfo" 
+        position="bottom"
+        :style="{height:'100%'}"
+    >
+        <EditChapterBaseInfo
+            v-if="showChapterBaseInfo"
+            :defaultData="activeItem"
+            :userOpts="userOpts"
+            :varietyOpts="reportVarietyOpts"
+            @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>
+
+    <!-- item操作 -->
+    <van-action-sheet 
+        teleport="body"
+        v-model:show="showItemOpt"
+        cancel-text="取消"
+        close-on-click-action
+    >
+        <div class="report-item-action-box" v-if="activeItem">
+            <div class="title">{{activeItem.Title}}</div>
+
+            <div class="item" @click="handleChapterInfo(activeItem)" v-if="isCreator">基础信息</div>
+            <div class="item" @click="handleDelChapter(activeItem)" v-if="isCreator">删除</div>
+            <div class="item" @click="handleShowTrendTag(activeItem)" v-permission="reportManageBtn.reportMange_chapter_editTag">添加标签</div>
+            <div class="item" @click="handleUploadChapterAudio(activeItem)">上传录音</div>
+        </div>
+    </van-action-sheet>
+
     <!-- 海报dom模块 -->
         <div v-if="chapterItemPosterInfo" class="select-text-disabled chapter-poster-box" id="chapter-poster-box">
             <img class="bg" :src="chapterItemPosterInfo.TypeEditImg" alt="">
@@ -364,7 +594,7 @@ async function handleShowPoster(item){
     min-height: 95vh;
     display: flex;
     flex-direction: column;
-    padding-bottom: 130px;
+    padding-bottom: 20px;
     .bot-btns{
         position: fixed;
         left: 0;
@@ -394,7 +624,7 @@ async function handleShowPoster(item){
 }
 .chapter-list{
     .item{
-        display: flex;
+        /* display: flex; */
         padding:20px;
         border: 1px solid $border-color;
         border-radius: 8px;
@@ -423,52 +653,51 @@ async function handleShowPoster(item){
         }
         .content{
             padding-left: 20px;
-            flex: 1;
-            display: flex;
-            flex-direction: column;
-            justify-content: space-between;
-            position: relative;
             .title{
                 font-size: 32px;
                 font-weight: 600;
                 padding-right: 160px;
             }
-            .time{
-                color: $font-grey;
-            }
-            
-            .audio-icon{
-                position: absolute;
-                width: 36px;
-                height: 36px;
-                top: 0;
-                right: 100px;
-            }
 
-            .status{
-                position: absolute;
-                top: 0;
-                right: 0;
-                color: $font-grey_999;
-                &.status-success{
-                    color: $font-success;
+            .bottom {
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                margin-top: 20px;
+                .status{
+                    color: $font-grey_999;
+                    margin-right: 10px;
+                    &.status-success{
+                        color: $font-success;
+                    }
+                }
+                
+                .icon{
+                    width: 70px;
+                    height: 70px;
                 }
-            }
 
-            .icon{
-                width: 70px;
-                height: 70px;
-                position: absolute;
-                bottom: 0;
-            }
-            .icon-tag{
-                right: 0;
-            }
-            .icon-wx{
-                right: 108px;
+                .flex-info {
+                    display: flex;
+                    align-items: center;
+                }
+                .handle-icon {
+                    margin-left: 20px;
+                }
             }
 
         }
+
+        .item-top {
+            margin-bottom: 20px;
+            .type-name {
+                color: #fff;
+                padding: 4px 30px;
+                border-radius: 16px;
+                background: $theme-color;
+                width: fit-content;
+            }
+        }
     }
 }
 
@@ -539,6 +768,22 @@ async function handleShowPoster(item){
     }
 }
 
+.report-item-action-box{
+    .title{
+        padding: 30px 32px;
+        font-weight: 700;
+        text-align: center;
+        background: #eee;
+    }
+    .item{
+        padding: 10px 0;
+        text-align: center;
+        line-height: 48px;
+        font-size: 32px;
+        border-top: 1px solid $border-color;
+    }
+}
+
 @media screen and (min-width:$media-width){
     .report-chapterlist-page{
         padding-bottom: 80px;
@@ -619,5 +864,11 @@ async function handleShowPoster(item){
             }
         }
     }
+
+    .report-item-action-box{
+        .item{
+            font-size: 12px;
+        }
+    }
 }
 </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,52 +100,32 @@ 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"
             />
+        </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"
+                required
             />
         </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>
 
 
         <div class="bot-btns">
@@ -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;

+ 130 - 82
src/views/report/components/AddReportBaseInfoV2.vue

@@ -23,29 +23,37 @@ const props = defineProps({
     default: null
   }
 })
-const emit = defineEmits(['close','change','confirm'])
+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) return  ''
+  if(!reportBaseInfo.relationVariety.length) return  ''
 
   return reportBaseInfo.relationVariety.map(_ => _.PermissionName).join(',')
 })
 const cooperationUsersLabel = computed(() => {
-  if(!reportBaseInfo.cooperationUsers) return  ''
+  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({
-    type: props.defaultData?props.defaultData.addType:1,
+    addType: props.defaultData?props.defaultData.addType:1,
     inheritId: 0,//继承report id
-    classifyIds: props.defaultData?props.defaultData.classifyName.map(_=>_.id):[],
-    classifyName:props.defaultData?props.defaultData.classifyName.map(_=>_.text).join('/'):'',
+    classifys: props.defaultData?props.defaultData.classifyName:[],
     relationVariety: [],
     title: props.defaultData?props.defaultData.title:"",
     abstract: props.defaultData?props.defaultData.abstract:"",
@@ -58,12 +66,12 @@ const reportBaseInfo=reactive({
 })
 
 
-const reportAuthor = ref([])
+const authorOpts = ref([])
 // 获取作者
 function getReportauthor() {
   apiReport.reportAuthorList().then((res) => {
     if (res.Ret == 200) {
-      reportAuthor.value = res.Data.List || [];
+      authorOpts.value = res.Data.List || [];
     }
   });
 }
@@ -80,8 +88,7 @@ function handleShowClassify() {
   showClassify.value=true
 }
 function handleConfirmClassify(arr){
-    reportBaseInfo.classifyName = arr.map(_=>_.text).join('/')
-    reportBaseInfo.classifyIds = arr.map(_=>_.id);
+    reportBaseInfo.classifys = arr;
     showClassify.value=false
 
     getRelationPermission()
@@ -89,9 +96,9 @@ function handleConfirmClassify(arr){
 }
 /* 获取关联品种 */
 async function getRelationPermission() {
-  if(!reportBaseInfo.classifyIds.length) return
+  if(!reportBaseInfo.classifys.length) return
 
-  const res = await apiReport.classifyPermissionList({ClassifyId:reportBaseInfo.classifyIds[reportBaseInfo.classifyIds.length-1]})
+  const res = await apiReport.classifyPermissionList({ClassifyId:reportBaseInfo.classifys[reportBaseInfo.classifys.length-1].id})
 
     if(res.Ret!==200) return
     reportBaseInfo.relationVariety = res.Data || []
@@ -100,29 +107,29 @@ async function getRelationPermission() {
 function handleUpdateBaseInfo() {
   reportBaseInfo.inheritId = 0;
 
-  if(!reportBaseInfo.classifyIds.length) return
+  if(!reportBaseInfo.classifys.length) return
 
   
   //获取上次报告
   apiReport.getAuthReportList({
-      ClassifyIdFirst: reportBaseInfo.classifyIds[0],
-      ClassifyIdSecond: reportBaseInfo.classifyIds[1],
-      ClassifyIdThird: reportBaseInfo.classifyIds[3],
+      ClassifyIdFirst: reportBaseInfo.classifys[0].id,
+      ClassifyIdSecond: reportBaseInfo.classifys[1]?.id,
+      ClassifyIdThird: reportBaseInfo.classifys[2]?.id,
       CurrentIndex: 1,
       PageSize:1,
-      Keyword:''
+      Keyword:'',
     }).then((res) => {
       if (res.Ret !== 200) return;
       if (!res.Data.List) {
-        showToast('该分类暂无报告')
+        reportBaseInfo.addType===2 && showToast('该分类暂无报告')
         
         return false;
       }
 
-      if(reportBaseInfo.type===1) { //默认只带出作者
+      if(reportBaseInfo.addType===1) { //默认只带出作者
         reportBaseInfo.author = res.Data.List
-          ? res.Data.List[0].Author
-          : '';
+          ? res.Data.List[0].Author.split(",")
+          : [''];
       }else {
         chooseInheritReport(res.Data.List[0])
       }
@@ -131,20 +138,31 @@ function handleUpdateBaseInfo() {
 
 /* 选择继承报告 */
 async function chooseInheritReport(item) {
-  const { Id,Title,Abstract,Author,CollaborateType,ReportLayout,IsPublicPublish,ClassifyIdFirst,ClassifyIdSecond,ClassifyIdThird } = 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;
+  reportBaseInfo.author = Author
+        ? Author.split(",")
+        : "";;
   reportBaseInfo.cooperationType = CollaborateType;
   reportBaseInfo.inheritId = Id;
   reportBaseInfo.reportLayout = ReportLayout;
   reportBaseInfo.isPublcPublish = IsPublicPublish;
-  reportBaseInfo.classifyIds = ClassifyIdThird
-    ? [ClassifyIdFirst,ClassifyIdSecond,ClassifyIdThird]
-    : ClassifyIdSecond 
-    ? [ClassifyIdFirst,ClassifyIdSecond]
-    : [ClassifyIdFirst]
+  reportBaseInfo.classifys = [
+      {
+          id:ClassifyIdFirst,
+          text:ClassifyNameFirst,
+      },
+      {
+          id:ClassifyIdSecond,
+          text:ClassifyNameSecond,
+      },
+      {
+          id:ClassifyIdThird,
+          text:ClassifyNameThird,
+      }
+  ]
   
   //继承的章节报告默认带出协作人
   if(CollaborateType===1) {
@@ -211,24 +229,26 @@ function close() {
 }
 async function handleSave() {
 
-  let classifyNameArr = reportBaseInfo.classifyName.split(',')
+
   const params = {
-    AddType: reportBaseInfo.type,
-    ClassifyIdFirst: reportBaseInfo.classifyIds[0]
-      ? reportBaseInfo.classifyIds[0]
+    AddType: reportBaseInfo.addType,
+    ClassifyIdFirst: reportBaseInfo.classifys[0]
+      ? reportBaseInfo.classifys[0].id
       : 0,
-    ClassifyNameFirst: classifyNameArr[0]||'',
-    ClassifyIdSecond: reportBaseInfo.classifyIds[1]
-      ? reportBaseInfo.classifyIds[1]
+    ClassifyNameFirst: reportBaseInfo.classifys[0]?.text,
+    ClassifyIdSecond: reportBaseInfo.classifys[1]
+      ? reportBaseInfo.classifys[1].id
       : 0,
-    ClassifyNameSecond: classifyNameArr[1]||'',
-    ClassifyIdThird: reportBaseInfo.classifyIds[2]
-      ? reportBaseInfo.classifyIds[2]
+    ClassifyNameSecond: reportBaseInfo.classifys[1]?.text,
+    ClassifyIdThird: reportBaseInfo.classifys[2]
+      ? reportBaseInfo.classifys[2].id
       : 0,
-    ClassifyNameThird: classifyNameArr[2]||'',
+    ClassifyNameThird: reportBaseInfo.classifys[2]?.text,
     Title: reportBaseInfo.title,
     Abstract: reportBaseInfo.abstract,
-    Author: reportBaseInfo.author,
+    Author: reportBaseInfo.author.length > 0
+            ? reportBaseInfo.author.join(",")
+            : "",
     CreateTime: reportBaseInfo.time,
     ReportLayout: reportBaseInfo.reportLayout,
     CollaborateType: reportBaseInfo.cooperationType,
@@ -240,26 +260,33 @@ async function handleSave() {
 
   // 编辑
   if (props.id) {
-    emit("change", params);
-    return;
+
+    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 { href } = router.resolve({
+          path: reportBaseInfo.reportLayout===1 
+            ? '/report/edit'
+            : "/smart_report/edit",
+          query: { 
+            id: res.Data.ReportId,
+            coopType: params.CollaborateType
+          },
+        });
+        window.open(href, "_blank");
+  
+        close();
+      }
+    });
   }
-  apiReport.reportAdd(params).then((res) => {
-    if (res.Ret === 200) {
-
-      let { href } = router.resolve({
-        path: reportBaseInfo.reportLayout===1 
-          ? '/report/edit'
-          : "/smpartReportEditV2",
-        query: { 
-          id: res.Data.ReportId,
-          coopType: params.CollaborateType
-        },
-      });
-      window.open(href, "_blank");
-
-      close();
-    }
-  });
 }
 
 </script>
@@ -269,7 +296,7 @@ async function handleSave() {
       <van-field name="radio" label="报告类型">
         <template #input>
           <van-radio-group 
-            v-model="reportBaseInfo.type" 
+            v-model="reportBaseInfo.addType" 
             direction="horizontal" 
             @change="handleUpdateBaseInfo"
             :disabled="id?true:false"
@@ -284,7 +311,7 @@ async function handleSave() {
       <van-cell
         required
         title="报告分类"
-        :label="reportBaseInfo.classifyName"
+        :label="classifyNameLabel"
         is-link
         @click="handleShowClassify"
       />
@@ -318,7 +345,7 @@ async function handleSave() {
       <van-cell
         required
         title="报告作者"
-        :value="reportBaseInfo.author"
+        :value="authorLabel"
         is-link
         @click="handleShowAuthor"
       />
@@ -339,7 +366,7 @@ async function handleSave() {
           <van-radio-group 
             v-model="reportBaseInfo.cooperationType" 
             direction="horizontal"
-            :disabled="id||reportBaseInfo.type===2"
+            :disabled="id||reportBaseInfo.addType===2"
           >
             <van-radio :name="1">个人</van-radio>
             <van-radio :name="2">多人协作</van-radio>
@@ -361,7 +388,7 @@ async function handleSave() {
           <van-radio-group 
             v-model="reportBaseInfo.reportLayout" 
             direction="horizontal"
-            :disabled="id||reportBaseInfo.type===2"
+            :disabled="id||reportBaseInfo.addType===2"
             >
             <van-radio :name="1">常规布局</van-radio>
             <van-radio :name="2">智能布局</van-radio>
@@ -415,23 +442,18 @@ async function handleSave() {
     position="bottom"
     :style="{ height: '100%' }"
   >
-    <div class="input-report-title-pop">
-      <van-field v-model="temAuthorVal" placeholder="请填写作者" />
-      <div class="bot-btns">
-        <van-button
-          class="bot-btn"
-          type="default"
-          @click="showAuthorPop = false"
-          >取消</van-button
-        >
-        <van-button
-          class="bot-btn"
-          type="primary"
-          :disabled="!temAuthorVal"
-          @click="handleConfirmAuthor"
-          >确定</van-button
-        >
-      </div>
+    <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>
 
@@ -443,7 +465,11 @@ async function handleSave() {
       :style="width>650?{ width: '400px'}:''"
       round
   >
-      <ListClassify @close="showClassify=false" @confirm="handleConfirmClassify"/>
+      <ListClassify
+        :defaultVal="defaultData?props.defaultData.classifyName:null"
+        @close="showClassify=false" 
+        @confirm="handleConfirmClassify"
+      />
   </van-popup>
 
   <!-- 协作人弹窗 -->
@@ -493,6 +519,28 @@ async function handleSave() {
         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);

+ 7 - 10
src/views/report/components/ListClassify.vue

@@ -23,11 +23,10 @@ function getClassifyList(){
     }).then(res=>{
         if(res.Ret===200){
             const arr=res.Data.List||[]
-            
             list.value = arr;
 
-            if(props.defaultVal&&props.defaultVal[1]){
-                activeId.value=props.defaultVal[1].id
+            if(props.defaultVal){
+                activeId.value=props.defaultVal[props.defaultVal.length-1].id
                 selectClassify.value = props.defaultVal
             }
         }
@@ -49,17 +48,15 @@ function handleReset(){
 
 function onFinish(e) {
     console.log(e)
-    selectClassify.value = e.selectedOptions
-}
-
-function handleConfirm(){
-
-    let arr = selectClassify.value.map(_ => ({
+    selectClassify.value = e.selectedOptions.map(_ => ({
         text:_.ClassifyName,
         id:_.Id,
     }))
+}
+
+function handleConfirm(){
 
-    emits('confirm',arr)
+    emits('confirm',selectClassify.value)
 }
 </script>
 

+ 10 - 0
src/views/report/components/ReportOptsBottom.vue

@@ -0,0 +1,10 @@
+<script setup>
+import { ref } from 'vue'
+
+</script>
+<template>
+  <div></div>
+</template>
+<style scoped lang="scss">
+
+</style>

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

@@ -0,0 +1,115 @@
+import { ref } from 'vue'
+import { router } from '@/router'
+import apiReport from '@/api/report'
+import { showToast,showDialog } from 'vant'
+import _ from 'lodash'
+
+
+export function useReportHandles() {
+
+
+  // 刷新所有图表和表格
+  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(()=>{})
+  }
+
+  return {
+    handleRefresh,
+    handleSubmitReport
+  }
+}
+
+
+/* 获取地址栏参数值 */
+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]
+}

+ 780 - 2
src/views/report/smartReport/EditReport.vue

@@ -1,10 +1,788 @@
 <script setup>
-import { ref } from 'vue'
+import { nextTick, reactive, ref,toRefs } from 'vue'
+import ReportInsertContent from '../components/reportInsert/Index.vue'
+// import ReportPublishTimeSet from './components/ReportPublishTimeSet.vue'
+import apiReport from '@/api/report'
+import {getSystemInfo} from '@/api/common'
+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 {Base64} from 'js-base64'
+import AddReportBaseInfoV2 from '../components/AddReportBaseInfoV2.vue'
+import { useReportHandles } from '../hooks/useReport'
+import draggable from 'vuedraggable'
+import TextComp from './components/TextComp.vue'
+import ChartComp from './components/ChartComp.vue'
+import ImgComp from './components/ImgComp.vue'
+import SheetComp from './components/SheetComp.vue'
 
+
+const cachedViewsStore=useCachedViewsStore()
+const publicSettingStore = usePublicSettingStore()
+const {isApprove,hasApproveFlow,getEtaConfig,checkClassifyNameArr} = useReportApprove()
+const router=useRouter()
+const route=useRoute()
+const {checkAuthBtn} = useAuthBtn()
+
+// 获取报告详情
+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)
+
+    }
+}
+getReportDetail()
+
+/* 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);
+				conList.value[index].style = targetBox.style.cssText
+				el.preventDefault()
+				document.onmousemove=null
+				setTimeout(() => {
+						isDragResize.value=false
+				}, 50);
+				
+		}
+}
+// 内部元素的缩放
+function handleResizeC(e,index,cindex,type){
+		this.isDragResize=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)=>{
+								this.$set(this.conList[index].child[idx],'style',item.style.cssText)
+						})
+				}
+				el.preventDefault()
+				document.onmousemove=null
+				setTimeout(() => {
+						this.isDragResize=false
+						document.onmouseup=null
+				}, 50);
+		}
+}
+
+// 点击删除某个
+function handleDelItem(pindex,cindex){
+		if(cindex===-1){
+				conList.value.splice(pindex,1)
+		}else{//删除子盒子
+				conList.value[pindex].child.splice(cindex,1)
+				if(conList.value[pindex].child.length===1){//只剩一个子盒子了则变成一个大盒子
+						const styleArr=conList.value[pindex].child[0].style.split(';').filter(s=>s&&(s.indexOf('width')===-1&&s.indexOf('flex')===-1)).join(';')
+						conList.value[pindex]=conList.value[pindex].child[0]
+						conList.value[pindex].style=styleArr
+				}
+		}
+}
+
+
+const showPopover = ref(true)
+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)
+
+  showInsertCompType.value = e.compId;
+  showReportInsertPop.value = true
+}
+/* 插入图表表格 */
+function handleChartInsert({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:``,
+            titleText: item.ExcelName,
+            style:'',
+            child:[]
+				}))
+    }
+
+    conList.value.splice(0,1,...tempCompDataArr)
+		console.log(conList.value)
+    showReportInsertPop.value=false
+}
+//插入文本
+function handleInsertText() {
+
+}
+function getCompId(type) {
+	return type+new Date().getTime()
+}
+
+
+// 报告基本信息
+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
+}
+
+
+const { 
+  conList,
+  bgColor,
+  headImg,
+  endImg,
+  headImgId,
+  endImgId,
+  headImgStyle,
+  endImgStyle,
+  layoutBaseInfo
+} = toRefs(smartState)
 </script>
 <template>
-  <div></div>
+  <div class="add-report-page">
+    <div class="main-wrap">
+
+      <div class="report-content-box" id="report-content-box" :style="{backgroundColor:bgColor}">
+          <!-- 版头 -->
+          <div class="html-head-img-box">
+              <div class="opt-btn-box">
+                  <div class="del-btn" @click.stop="deleteLayoutPic(1)"></div>
+              </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>
+          <draggable
+              :list="conList"
+              :group="{ name: 'component', pull: true, put: true }"
+              class="report-html-wrap"
+              id="report-html-content"
+              animation="300"
+              tag="div"
+              handle=".drag-btn_p"
+              @add="handleParentAdd"
+              @remove="handleParentRemove"
+              :move="handleParentMove"
+          >
+            <template #item="{element,index}">
+              <div 
+                  :class="[
+                      'report-drag-item-wrap',
+                      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">
+                      <div class="drag-btn drag-btn_p"></div>
+                      <!-- <div class="del-btn" @click.stop="handleDelItem(index,-1)"></div> -->
+											<van-icon name="delete-o" @click.stop="handleDelItem(index,-1)"/>
+                  </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>
+            </template>
+          </draggable>
+          
+          <!-- 版尾 -->
+          <div class="html-end-img-box">
+              <div class="opt-btn-box">
+                  <div class="del-btn" @click.stop="deleteLayoutPic(2)"></div>
+              </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 class="add-comp-wrapper">
+          <van-popover v-model:show="showPopover" @select="handleOpenComPop">
+            <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>
+                <van-icon name="add-o" size="24" class="add-ico"/>
+            </template>
+          </van-popover>
+        </div>
+      </div>
+    </div>
+
+    <!-- 底部操作 -->
+    <div class="bot-action-box">
+        <div class="left-box">
+            <div class="item" @click="showReportBaseInfo=true">
+                <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="handlePreviewReport" >
+                <img src="@/assets/imgs/report/icon_preview.png" alt="">
+                <span>预览</span>
+            </div>
+            <div class="item" @click="autoSaveReportContent('save')">
+                <img src="@/assets/imgs/report/icon_save2.png" alt="">
+                <span>保存</span>
+            </div>
+            <template v-if="!isApprove||!hasApproveFlow">
+                <div class="item" @click="handlePublishReport('dsfb')" >
+                    <img src="@/assets/imgs/report/icon_time.png" alt="">
+                    <span>定时发布</span>
+                </div>
+                <div class="item" @click="handlePublishReport('fb')">
+                    <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>
+            <div class="item" @click="handlePreviewReport" >
+                <img src="@/assets/imgs/report/icon_preview.png" alt="">
+                <span>更多设置</span>
+            </div>
+        </div>
+    </div>
+  </div>
+
+  <!-- 报告插入数据模块 -->
+  <van-popup
+      v-model:show="showReportInsertPop"
+      position="bottom"
+      round
+  >
+      <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>
 </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;
+		pointer-events:auto;
+
+		.report-drag-item-wrap{
+				width: 100%;
+				padding: 6px;
+				min-height: 80px;
+				border: 1px dashed #0052D9;
+				position: relative;
+				margin-bottom: 3px;
+				&:hover{
+						border-style: solid;
+						.opt-btn-box{
+								display: block !important;
+						}
+						.resize-drag-box{
+								display: block;
+						}
+				}
+				.opt-btn-box{
+						position: absolute;
+						left: -36px;
+						padding-right: 8px;
+						top: 0;
+						.drag-btn::after{
+								content: '';
+								display: block;
+								width: 28px;
+								height: 28px;
+								background-image: url('~@/assets/img/smartReport/icon12.png');
+								background-size: cover;
+								cursor: pointer;
+						}
+						.del-btn::after{
+								content: '';
+								display: block;
+								width: 28px;
+								height: 28px;
+								background-image: url('~@/assets/img/smartReport/icon13.png');
+								background-size: cover;
+								cursor: pointer;
+						}
+				}
+				.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-wrap{
+				// min-height: 30px;
+		}
+		.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;
+						right: -15px;
+						top: 0;
+						z-index: 10;
+						.drag-btn::after{
+								content: '';
+								display: block;
+								width: 28px;
+								height: 28px;
+								background-image: url('~@/assets/img/smartReport/icon12.png');
+								background-size: cover;
+								cursor: pointer;
+						}
+						.del-btn::after{
+								content: '';
+								display: block;
+								width: 28px;
+								height: 28px;
+								background-image: url('~@/assets/img/smartReport/icon13.png');
+								background-size: cover;
+								cursor: pointer;
+						}
+				}
+				.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;
+    height: 100%;
+		padding: 20px 20px 20px 44px;
+}
+.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%);
+        }
+    }
+}
+@media screen and (min-width:$media-width){
+    .add-report-page{
+        height: calc(100dvh - 60px);
+        min-height: calc(95vh - 60px);
+    }
+
+    .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;
+    }
+    .report-content-box{ 
+      max-width: 800px;
+    }
+}
 
 </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>

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

@@ -0,0 +1,24 @@
+<script setup>
+import { ref } from 'vue'
+
+const props = defineProps({
+  compData:{}
+})
+</script>
+<template>
+  <div 
+      class="report-comp-item sheet-comp"
+      style="width:100%;overflow: hidden;"
+  >
+      <div 
+          style="padding-left:10px;font-size:17px" 
+          contenteditable="true"
+          v-html="compData.titleText"
+          v-if="compData.titleText"
+      ></div>
+      <iframe :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>

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

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