瀏覽代碼

Merge branch 'CRM_14.0'

hbchen 1 年之前
父節點
當前提交
8d6b376305

文件差異過大導致無法顯示
+ 6 - 0
public/froala_style.min.css


+ 185 - 0
public/reset.min.css

@@ -0,0 +1,185 @@
+@charset "utf-8";
+body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, button, input, textarea, th, td {
+    margin: 0;
+    padding: 0
+}
+
+body {
+    font-size: 12px;
+    font-style: normal;
+    font-family: "\5FAE\8F6F\96C5\9ED1", Helvetica, sans-serif,;
+
+}
+
+html {
+    /* overflow: scroll; */
+    overflow: auto;
+    min-width: 1000px;
+}
+
+small {
+    font-size: 12px
+}
+
+h1 {
+    font-size: 18px
+}
+
+h2 {
+    font-size: 16px
+}
+
+h3 {
+    font-size: 14px
+}
+
+h4, h5, h6 {
+    font-size: 100%
+}
+
+ul, ol {
+    list-style: none
+}
+
+a {
+    text-decoration: none;
+    background-color: transparent
+}
+
+a:hover, a:active {
+    outline-width: 0;
+    text-decoration: none
+}
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0
+}
+
+hr {
+    border: 0;
+    height: 1px
+}
+
+img {
+    border-style: none
+}
+
+img:not([src]) {
+    display: none
+}
+
+svg:not(:root) {
+    overflow: hidden
+}
+
+html {
+    -webkit-touch-callout: none;
+    -webkit-text-size-adjust: 100%
+}
+
+input, textarea, button, a {
+    -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
+}
+
+article, aside, details, figcaption, figure, footer, header, main, menu, nav, section, summary {
+    display: block
+}
+
+audio, canvas, progress, video {
+    display: inline-block
+}
+
+audio:not([controls]), video:not([controls]) {
+    display: none;
+    height: 0
+}
+
+progress {
+    vertical-align: baseline
+}
+
+mark {
+    background-color: #ff0;
+    color: #000
+}
+
+sub, sup {
+    position: relative;
+    font-size: 75%;
+    line-height: 0;
+    vertical-align: baseline
+}
+
+sub {
+    bottom: -0.25em
+}
+
+sup {
+    top: -0.5em
+}
+
+button, input, select, textarea {
+    font-size: 100%;
+    outline: 0
+}
+
+button, input {
+    overflow: visible
+}
+
+button, select {
+    text-transform: none
+}
+
+textarea {
+    overflow: auto
+}
+
+button, html [type="button"], [type="reset"], [type="submit"] {
+    -webkit-appearance: button
+}
+
+button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner {
+    border-style: none;
+    padding: 0
+}
+
+button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring {
+    outline: 1px dotted ButtonText
+}
+
+[type="checkbox"], [type="radio"] {
+    box-sizing: border-box;
+    padding: 0
+}
+
+[type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button {
+    height: auto
+}
+
+[type="search"] {
+    -webkit-appearance: textfield;
+    outline-offset: -2px
+}
+
+[type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration {
+    -webkit-appearance: none
+}
+
+::-webkit-input-placeholder {
+    color: inherit;
+    opacity: .54
+}
+
+::-webkit-file-upload-button {
+    -webkit-appearance: button;
+    font: inherit
+}
+
+.clear:after {
+    display: block;
+    height: 0;
+    content: "";
+    clear: both
+}

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

+ 9 - 1
src/api/index.js

@@ -1,5 +1,6 @@
 import axios from "axios";
 import CryptoJS from './crypto'
+import { ElMessage } from 'element-plus'
 
 let config = {
   baseURL: import.meta.env.VITE_APP_API_URL,
@@ -31,10 +32,17 @@ _axios.interceptors.response.use(
       //错误提示
       if(response.status!==200){
         //网络异常
+        ElMessage.error(data.msg || "网络异常")
       }
+      
       if(!data){
-        //服务器开了个小差
+        ElMessage.error(data.msg || "服务器开了个小差")
+      }
+
+      if([500,505,400].includes(data.code)){
+        ElMessage.error(data.msg || "网络异常")
       }
+
       if(data.Ret===408){//token失效
       }
       if(data.Ret===403){

二進制
src/assets/img/help/chart.png


二進制
src/assets/img/help/data.png


二進制
src/assets/img/help/database.png


二進制
src/assets/img/help/report.png


二進制
src/assets/img/icon/arrow-grey.png


二進制
src/assets/img/icon/fold.png


二進制
src/assets/img/icon/line-arrow-blue.png


二進制
src/assets/img/icon/unfold.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投研系统帮助中心",
                 },
             },
             {

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

@@ -1,12 +1,426 @@
 <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('')
+let isFirstLoad=true
+
+let videoList=[]
+
+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)
+      if(!(helpDocument.value.Anchor && helpDocument.value.Anchor.length>0)) isRightFold.value=true
+      isRightFold.value=false
+      nextTick(()=>{
+        getScrollTopList(helpDocument.value.Anchor || [])
+        videoList=document.getElementsByTagName('video') || []
+        if(videoList && videoList.length>0){
+          for (let i = 0; i < videoList.length; i++) {
+            const element = videoList[i];
+            element.addEventListener('play',setVideosStatus(i))
+          }
+        }
+      })
+      window.scrollTo(0, document.getElementById('operation-document-body').offsetTop)
+    }else if(res.code == 4001){
+      helpDocument.value={}
+      isRightFold.value=true
+    }
+    if(isFirstLoad){
+      isFirstLoad=false
+      return
+    }
+
+  })
+}
+
+const setVideosStatus=(i)=>{
+  return ()=>{
+    // 暂停其他播放的视频,只能播放一个
+    for (let j = 0; j < videoList.length; j++) {
+      if(j==i) continue; 
+      if(!videoList[j].paused){
+        videoList[j].pause()
+      }
+    }
+  }
+}
+
+// 生成底部的两个链接
+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)
+    
+    if(videoList && videoList.length>0){
+      for (let i = 0; i < videoList.length; i++) {
+        const element = videoList[i];
+        element.removeEventListener('play',setVideosStatus(i))
+      }
+    }
+})
+
+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=''
+    }
+}
 </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'}">
+        <template v-if="helpDocument.Title">
+          <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 fr-view" id="rich-text-box" v-html="Content">
+          </div>
+        </template>
+        <template v-else>
+          <div class="nodata" style="text-align: center;">
+            <img src="~@/assets/img/nodata.png" style="width: 300px;"/>
+            <p>暂无信息</p>
+          </div>
+        </template>
+      </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" empty-text="暂无数据"
+           default-expand-all :expand-on-click-node="false" >
+           <template #default="{ node, data }">
+            <a @click="(e)=>navigate(e,'#'+data.Anchor)" class="custom-tree-node" v-html="data.AnchorName"
+             :class="currentNodeKey==data.AnchorId?'active-node':''" :style="!node.isLeaf?'margin-top:12px':''" ></a>
+           </template>
+          </el-tree>
+        </div>
+      </div>
+      <div class="fold-right-icon" @click="isRightFold=!isRightFold">
+        <img src="@/assets/img/icon/fold.png" v-if="!isRightFold" />
+        <img src="@/assets/img/icon/unfold.png" v-else />
+      </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">
+// froala-editor 预览时的样式,如需使用在展示富文本的节点上加上 fr-view 的类
+@import '/public/froala_style.min.css';
+// 因为富文本编辑的地方在hz_crm_web 项目,加入后台的样式保持两边看起来一致
+@import '/public/reset.min.css';
+
+p[data-f-id="pbf"] {
+  display: none;
+}
+
+.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;
+}
 </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)" style="width: 100%;">{{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>

+ 2 - 2
src/views/update/Index.vue

@@ -108,13 +108,13 @@ onMounted(()=>{
         </div>
     </div>
     <!-- 跳转帮助文档的按钮 -->
-    <!-- <Teleport to=".layout-header-other" v-if="beforeMounted">
+    <Teleport to=".layout-header-other" v-if="beforeMounted">
         <div class="update-header-btn" @click="goHelpDoc">
             <el-icon color="#FFF">
                 <QuestionFilled />
             </el-icon>
             帮助文档</div>
-    </Teleport> -->
+    </Teleport>
 </template>
 
 <style scoped lang="scss">

部分文件因文件數量過多而無法顯示