Browse Source

CRM_14.0 帮助文档

hbchen 1 year ago
parent
commit
fb4f49a25b

+ 19 - 0
src/api/helpApi.js

@@ -0,0 +1,19 @@
+import {get,post} from './index'
+/**
+ * 获取帮助文档分类列表
+ * @param bus_code String 商家code
+ * @returns 
+ */
+export function apiGetHelpDocClassify(params){
+    return get('help_doc/classify/list',params)
+}
+
+/**
+ * 获取帮助文档详情
+ * @param bus_code String 商家code
+ * @param classify_id number 分类Id
+ * @returns 
+ */
+export function apiGetHelpDocDetail(params){
+    return get('help_doc/detail',params)
+}

BIN
src/assets/img/help/chart.png


BIN
src/assets/img/help/data.png


BIN
src/assets/img/help/database.png


BIN
src/assets/img/help/report.png


BIN
src/assets/img/icon/arrow-grey.png


BIN
src/assets/img/icon/fold.png


BIN
src/assets/img/icon/line-arrow-blue.png


+ 1 - 1
src/router/index.js

@@ -11,7 +11,7 @@ const routes=[
                 name:"HelpIndex",
                 component: () => import("@/views/help/Index.vue"),
                 meta: { 
-                    title: "帮助中心",
+                    title: "ETA投研系统帮助中心",
                 },
             },
             {

+ 395 - 3
src/views/help/Index.vue

@@ -1,12 +1,404 @@
 <script setup>
+
+import subMenuBox from "./components/subMenuBox.vue"
+import menuBox from "./components/menubox.vue"
+import {apiGetHelpDocClassify,apiGetHelpDocDetail} from "@/api/helpApi.js"
+import {ref,nextTick,onMounted,onUnmounted } from 'vue'
+import {useRoute} from 'vue-router'
+const route = useRoute()
+
+const documentData=ref([])
+// const anchorData=ref([])
+const currentNodeKey=ref('')
+const defaultActiveId=ref(0)
+const isRightFold=ref(false)
+const helpDocument=ref({})
+const Content=ref('')
+
+const businessCode = route.query.bus_code || ''
+ 
+const treeProp={
+    label:'text',
+    children: 'Child'
+}
+
+const getDocumentData=()=>{
+    apiGetHelpDocClassify({bus_code:businessCode}).then(res=>{
+        if(res.code == 200){
+            documentData.value=res.data || []
+            getFirstId(documentData.value[0])
+            getDocument(defaultActiveId.value)
+        }
+    })
+}
+// 获取第一个分类
+const getFirstId=(item)=>{
+    if(item.Children && item.Children.length>0){
+        getFirstId(item.Children[0])
+    }else{
+        defaultActiveId.value = item.ClassifyId
+    }
+}
+
+const menuChange=(data,node)=>{
+    currentNodeKey.value = data.AnchorId
+}
+const getDocument=(id)=>{
+    apiGetHelpDocDetail({bus_code:businessCode,classify_id:id}).then(res=>{
+        if(res.code == 200){
+            helpDocument.value=res.data || {}
+            Content.value = helpDocument.value.Content + createBottomHref(helpDocument.value.Recommend)
+            nextTick(()=>{
+                getScrollTopList(helpDocument.value.Anchor || [])
+            })
+        }
+    })
+    window.scrollTo(0, document.getElementById('operation-document-body').offsetTop)
+}
+// 生成底部的两个链接
+const createBottomHref=(RecommendData)=>{
+    if(!(RecommendData && RecommendData.length>0)) return ''
+    
+    let hrefStringBuiler='<ul style="margin-top:40px">'
+    RecommendData.map(item =>{
+    if(item.Name){
+        hrefStringBuiler+=`<li><a href="${item.Url}" target="_blank" style="text-decoration: underline;">${item.Name}</a></li>`
+    }
+    })
+    return hrefStringBuiler+"</ul>"
+}
+
+getDocumentData()
+
+
+
+const headerHeight = ref(0)
+const scrollTopList=ref([])
+const stop=ref(false)
+
+onMounted(() => {
+    document.addEventListener('scroll',scrollChange)
+    headerHeight.value=document.getElementById("operation-document-body").offsetTop
+})
+
+onUnmounted(() => {
+    document.removeEventListener('scroll',scrollChange)
+})
+
+const getScrollTopList=(list)=>{
+    list.map(item =>{
+        scrollTopList.value.push({key:item.AnchorId,clientRectTop:document.getElementById(item.Anchor).offsetTop+headerHeight.value})
+        if(item.Child && item.Child.length>0) getScrollTopList(item.Child || [])
+    })
+}
+const navigate = (e,id)=>{
+    // 阻止滚动监听,防止右侧节点的定位 被滚动监听干扰
+    stop.value=true
+    e.preventDefault();
+    document.querySelector(id).scrollIntoView(true)
+    nextTick(()=>{
+        // 放开滚动监听
+        stop.value=false
+    })
+}
+const scrollChange=()=>{
+    if(stop.value) return
+    let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
+    
+    let hashit=false
+    for (let i = scrollTopList.value.length ; i > 0; i--) {
+            const element = scrollTopList.value[i-1];
+            if(scrollTop - element.clientRectTop>-5){
+            hashit=true
+            currentNodeKey.value = element.key
+            break;
+        }
+    }
+    if(!hashit){
+        currentNodeKey.value=''
+    }
+}
+const location=(e,id)=>{
+
+}
 </script>
 
 <template>
-    <div>
-        help
+  <div class="operation-document-container" id="operation-document-container">
+    <div class="operation-document-neck">
+      <div class="banner-image">
+        <img src="@/assets/img//help/data.png">
+        <div class="banner-text">
+          <div class="text">数据源</div>
+          <div class="text">数据录入、更新</div>
+        </div>
+      </div>
+      <img src="@/assets/img/icon/line-arrow-blue.png" class="banner-line">
+      <div class="banner-image">
+        <img src="@/assets/img/help/database.png">
+        <div class="banner-text">
+          <div class="text">ETA指标库</div>
+          <div class="text">添加指标</div>
+        </div>
+      </div>
+      <img src="@/assets/img/icon/line-arrow-blue.png" class="banner-line">
+      <div class="banner-image">
+        <img src="@/assets/img/help/chart.png">
+        <div class="banner-text">
+          <div class="text">ETA图库</div>
+          <div class="text">指标作图</div>
+        </div>
+      </div>
+      <img src="@/assets/img/icon/line-arrow-blue.png" class="banner-line">
+      <div class="banner-image">
+        <img src="@/assets/img/help/report.png">
+        <div class="banner-text">
+          <div class="text">研报</div>
+          <div class="text">编辑研报/插入图表</div>
+        </div>
+      </div>
+    </div>
+    <div class="operation-document-body" id="operation-document-body">
+      <div class="document-body-left">
+        <el-scrollbar style="height: 100%;">
+          <el-menu class="docuemnt-menu" text-color="#333333" :default-active="defaultActiveId+''">
+            <template  v-for="menuItem in documentData" :key="menuItem.ClassifyId">
+                <subMenuBox @getDocument="getDocument" v-if="menuItem.Children && menuItem.Children.length>0"
+                    :item="menuItem"></subMenuBox>
+                <menuBox @getDocument="getDocument" :item="menuItem" v-else></menuBox>
+            </template>
+          </el-menu>
+        </el-scrollbar>
+      </div>
+
+      <div class="document-body-center" id="document-body-center" :style="{'border-right':isRightFold?'none':'solid 1px #DCDFE6'}">
+        <div class="body-center-title">
+          <div class="body-center-title-text">{{ helpDocument.Title }}</div>
+          <div class="body-center-title-signature">最后更新时间:{{ helpDocument.Author }} {{ helpDocument.ModifyTime }}</div>
+        </div>
+        <div class="rich-text-box" id="rich-text-box" v-html="Content">
+        </div>
+      </div>
+      <div class="document-body-right" :style="{'max-width': isRightFold?'0':'300px'}">
+        <div class="body-right-box">
+          <el-tree :data="helpDocument.Anchor" node-key="AnchorId" @current-change="menuChange" class="right-anchor-tree"
+          ref="rightTreeRef" :props="treeProp" :current-node-key="currentNodeKey" icon="none"
+           default-expand-all :expand-on-click-node="false" >
+           <template #default="{ node, data }">
+            <a @click="(e)=>navigate(e,'#'+data.Anchor)" class="custom-tree-node" 
+             :class="currentNodeKey==data.AnchorId?'active-node':''" :style="!node.isLeaf?'margin-top:12px':''" >{{ data.AnchorName }}</a>
+           </template>
+          </el-tree>
+        </div>
+      </div>
+      <div class="fold-right-icon" @click="isRightFold=!isRightFold">
+        <img src="@/assets/img/icon/fold.png" />
+      </div>
     </div>
+  </div>
 </template>
 
-<style scoped lang="scss">
+<style lang="scss" scoped>
+  .operation-document-container{
+    min-width: 1000px;
+    width: 100%;
+    .operation-document-neck{
+      padding: 60px 20px;
+      height: 140px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      .banner-image{
+        height: 140px;
+        width: 250px;
+        min-width: 200px;
+        position: relative;
+        .banner-text{
+          position: absolute;
+          top: 24px;
+          left: 24px;
+          .text{
+            color: white;
+            font-weight: 400;
+            line-height: 28px;
+            font-size: 20px;
+            &:last-child{
+              margin-top: 20px;
+              line-height: 22px;
+              font-size: 16px;
+            }
+          }
+        }
+        img{
+          height:100%;
+          width:100%;
+        }
+      }
+      .banner-line{
+        width: 40px;
+        margin: 0 10px;
+      }
+    }
+    .operation-document-body{
+      width: 100%;
+      // min-height:100vh;
+      display: flex;
+      position: relative;
+      .document-body-left{
+        max-width: 300px;
+        flex:1;
+        max-height: 100vh;
+        position: sticky;
+        top: 0;
+        padding: 24px 24px 24px 12px;
+        box-sizing: border-box;
+        align-self: flex-start;
+        .docuemnt-menu{
+          border-right: none;
+        }
+      }
+      .document-body-center{
+        flex: 3;
+        min-height: 100%;
+        padding: 20px;
+        border-left: solid 1px #DCDFE6;
+        // border-right: solid 1px #DCDFE6;
+        box-sizing: border-box;
+        min-height:100vh;
+        .body-center-title{
+          border-bottom: solid 1px #C0C4CC;
+          margin-bottom: 30px;
+          .body-center-title-text{
+            font-size: 34px;
+            line-height: 48px;
+            text-align: center;
+          }
+          .body-center-title-signature{
+            font-size: 14px;
+            color: #666666;
+            padding: 10px 0 10px 10px;
+          }
+        }
+      }
+      .document-body-right{
+        flex: 1;
+        max-width:  300px;
+        max-height: 100vh;
+        position: sticky;
+        top: 0;
+        overflow: hidden;
+        transition: all 0.1s ease;
+        box-sizing: border-box;
+        align-self: flex-start;
+        .body-right-box{
+          padding: 12px 24px 24px;
+          box-sizing: border-box;
+          .custom-tree-node{
+            padding: 8px 12px;
+            font-size: 14px;
+            line-height: 20px;
+            color: #333333;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            width: 100%;
+          }
+        }
+      }
+      .fold-right-icon{
+        width: 20px;
+        height: 20px;
+        position: sticky;
+        right: 10px;
+        top: 0;
+        cursor: pointer;
+      } 
+    }
+  }
+</style>
+<style lang="scss">
+
+.el-scrollbar__wrap {
+    overflow-x: hidden;
+}
+.el-sub-menu .el-menu-item{
+  padding-left:12px !important;
+}
+
+a{
+    text-decoration: none;
+}
+
+.el-tree-node:focus > .el-tree-node__content {
+background-color: transparent !important;
+}
+
+.el-tree-node__content{
+  height: unset!important;
+}
+.el-tree-node__content:hover {
+    background-color: transparent;
+    .custom-tree-node{
+      color: #366EF4!important;
+    }
+}
+.active-node{
+  color: #366EF4!important;
+}
+.el-tree-node__children{
+  .active-node{
+    color: #666666!important;
+    text-decoration:underline;
+  }
+  .custom-tree-node{
+    color: #666666!important;
+    padding: 0 8px!important;
+  }
+}
+
+.el-tree-node__children{
+  .el-tree-node__content:hover{
+    background-color: transparent;
+    text-decoration: underline;
+    .custom-tree-node{
+      color: #666666!important;
+    }
+  }
+}
+.el-tree-node__expand-icon{
+    display: none;
+}
+
+// froala富文本编辑器图片居中、居左、居右样式类
+img.fr-dib {
+  margin: 5px auto;
+  display: block;
+  float: none;
+  vertical-align: top;
+}
+img.fr-dib.fr-fil {
+  margin-left: 0;
+  text-align: left;
+}
+img.fr-dib.fr-fir {
+  margin-right: 0;
+  text-align: right;
+}
+
+// froala富文本编辑器 视频居中、居左、居右样式类
+.fr-video {
+  text-align: center;
+  position: relative;
+}
+.fr-video.fr-dvb {
+  display: block;
+  clear: both;
+}
+.fr-video.fr-dvb.fr-fvl {
+  text-align: left;
+}
+.fr-video.fr-dvb.fr-fvr {
+  text-align: right;
+}
 
 </style>

+ 63 - 0
src/views/help/components/menuBox.vue

@@ -0,0 +1,63 @@
+
+<script setup>
+
+import {defineProps,defineEmits} from 'vue'
+
+const prop=defineProps({
+  item:{
+    type:Object,
+    required:true
+  }
+})
+const emits=defineEmits(['getDocument'])
+
+const getDocument=(id)=>{
+  emits('getDocument',id)
+}
+</script>
+
+<template>
+  <el-menu-item :index="(prop.item.ClassifyId+'')">
+    <span @click="getDocument(prop.item.ClassifyId)">{{prop.item.ClassifyName}}</span>
+  </el-menu-item>
+</template>
+
+<style lang="scss" scoped>
+
+</style>
+<style lang="scss">
+// .menu-item{
+  .el-menu-item{
+    height: unset!important;
+    line-height: unset!important;
+    margin-bottom: 8px;
+    span{
+      font-size: 14px;
+      line-height: 20px;
+      padding-top: 8px!important;
+      padding-bottom: 8px!important;
+      display: block;
+    }
+    &:hover{
+      background-color: #e6eefb!important;
+    }
+  }
+  .el-menu-item.is-active{
+    background-color: #F2F6FA!important;
+    color: #366EF4;
+    border-radius: 4px 0 0 4px;
+    position: relative;
+    &::before{
+      content: '';
+      background-color: #366EF4;
+      width: 4px;
+      height: 36px;
+      border-radius: 4px 0 0 4px;
+      position: absolute;
+      left: 0;
+      top: 0;
+    }
+  }
+// }
+
+</style>

+ 69 - 0
src/views/help/components/subMenuBox.vue

@@ -0,0 +1,69 @@
+<script setup>
+import menuBox from "./menubox.vue"
+
+import {defineProps,defineEmits} from 'vue'
+
+const prop=defineProps({
+  item:{
+    type:Object,
+    required:true
+  }
+})
+const emits=defineEmits(['getDocument'])
+
+const getDocument=(id)=>{
+  emits('getDocument',id)
+}
+
+</script>
+
+<template>
+  <el-sub-menu :index="(prop.item.ClassifyId+'')" >
+    <template #title>
+      <div>
+        <img src="@/assets/img/icon/arrow-grey.png" style="margin-right: 2px;" class="icon-row">
+        <span>{{ prop.item.ClassifyName }}</span>
+      </div>
+    </template>
+    <template  v-for="menuItem in prop.item.Children" :key="menuItem.ClassifyId" >
+      <subMenuBox @getDocument="getDocument" v-if="menuItem.Children && menuItem.Children.length>0" :item="menuItem"></subMenuBox>
+      <menuBox @getDocument="getDocument" :item="menuItem" v-else ></menuBox>
+    </template>
+  </el-sub-menu>
+</template>
+
+<style lang="scss" scoped>
+
+</style>
+<style lang="scss">
+.el-sub-menu{
+  margin-left: 12px;
+  margin-bottom: 4px;
+  .el-sub-menu__icon-arrow{
+    display: none;
+  }
+  .el-sub-menu__title{
+    height: unset;
+    padding: 8px 0 !important;
+    line-height: unset;
+    margin-bottom: 8px;
+    .icon-row{
+      transform: rotate(0);
+      transition: 0.1s ease-in;
+    }
+    span{
+      font-size: 14px;
+      line-height: 20px;
+      font-weight: bold;
+    }
+  }
+}
+.is-opened{
+  .el-sub-menu__title{
+    .icon-row{
+      transform: rotate(90deg);
+    }
+  }
+}
+
+</style>