浏览代码

帮助中心配置、文章预览 完成

cxmo 9 月之前
父节点
当前提交
ec35c7f396

+ 52 - 0
src/router/modules/operateRutes.js

@@ -84,5 +84,57 @@ export default [{
             pathName: '进门到会管理',
             keepAlive: false
         }
+    },{
+        path:"assistanceCenter",
+        component: () => import('@/views/system_manage/assistance_center/assistanceCenter.vue'),
+        name: 'assistanceCenter',
+        hidden: false,
+        meta:{
+            title:'帮助中心配置'
+        }
+    },
+    {
+        path:"assistanceDocDetail",
+        component: () => import('@/views/system_manage/assistance_center/assistanceDocDetail.vue'),
+        name: 'assistanceDocDetail',
+        hidden: true,
+        meta: {
+            title:'查看文章',
+            pathFrom: 'assistanceCenter',
+            pathName: '帮助中心配置'
+        }
+    },
+    {
+        path:"assistanceDocAdd",
+        component: () => import('@/views/system_manage/assistance_center/assistanceDocAdd.vue'),
+        name: 'assistanceDocAdd',
+        hidden: true,
+        meta: {
+            title:'添加文章',
+            pathFrom: 'assistanceCenter',
+            pathName: '帮助中心配置'
+        }
+    },
+    {
+        path:"assistanceDocEdit",
+        component: () => import('@/views/system_manage/assistance_center/assistanceDocAdd.vue'),
+        name: 'assistanceDocEdit',
+        hidden: true,
+        meta: {
+            title:'编辑文章',
+            pathFrom: 'assistanceCenter',
+            pathName: '帮助中心配置'
+        }
+    },
+    {
+        path:"docClassifyManage",
+        component: () => import('@/views/system_manage/assistance_center/docClassifyManage.vue'),
+        name: 'docClassifyManage',
+        hidden: true,
+        meta: {
+            title:'分类管理',
+            pathFrom: 'assistanceCenter',
+            pathName: '帮助中心配置'
+        }
     }]
 }];

+ 0 - 6
src/router/modules/stopUpdateRoutes.js

@@ -197,12 +197,6 @@ export default [
 			// 	name:'推送客户群设置',
 			// 	hidden:false
 			// },
-      //       {
-			// 	path: 'chartJurisdiction',
-			// 	component: () => import('@/views/dataReport_manage/jurisdiction.vue'),
-			// 	name: '图库权限开通统计',
-			// 	hidden: false
-			// },
 			// {
 			// 	path:"enAuthManage",
 			// 	component: () => import('@/views/system_manage/enAuthManage.vue'),

+ 212 - 0
src/views/system_manage/assistance_center/assistanceCenter.vue

@@ -0,0 +1,212 @@
+<script setup>
+import mPage from '@/components/mPage.vue';
+import { assistanceDocInterence } from '@/api/modules/assistanceDoc';
+import { ref, reactive } from 'vue'
+import { Search } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useRouter } from "vue-router"
+const router = useRouter()
+
+let classifyList = ref([])
+let documentList = ref([])
+let ClassifyIdList = ref([])
+let queryParams = reactive({
+    CurrentIndex:1,
+    PageSize:10,
+    ClassifyIds:"",
+    KeyWord:""
+})
+let total = ref(100)
+
+function getClassifyList(){
+    assistanceDocInterence.getAssistanceClassifyList().then(res=>{
+        if(res.Ret == 200){
+            classifyList.value = res.Data?.AllNodes||[]
+        }
+    })
+}
+function getDocumentList(){
+    assistanceDocInterence.getAssistanceDocList(queryParams).then(res=>{
+        if(res.Ret == 200){
+            documentList.value=res.Data.List || []
+            total.value = res.Data.Paging.Totals || 0
+        }
+    })
+}
+
+function searchDocumentList(){
+    queryParams.CurrentIndex=1
+    getDocumentList()
+}
+
+function handlSearchClassify(value){
+    queryParams.ClassifyIds = value.join(',')
+    queryParams.CurrentIndex=1
+    getDocumentList()
+}
+
+function pageChange(page_no){
+    queryParams.CurrentIndex=page_no
+    getDocumentList()
+}
+
+function goDetail(item){
+    router.push({path:"/assistanceDocDetail",query:{DocId:item.Id}})
+}
+//发布/取消发布
+function publish(item){
+    let text = item.Status==2?'取消发布':'发布'
+
+    ElMessageBox.confirm(`是否确认${text}?`,"提示",{
+        type:"warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+    }).then(res=>{
+        assistanceDocInterence.assistanceDocPublish({
+            DocId:item.Id,
+            Status:3-item.Status
+        }).then(res=>{
+            if(res.Ret == 200){
+                ElMessage.success(text+"成功")
+                getDocumentList()
+            }
+        })
+    }).catch(() => {});
+}
+function addDocument(){
+    router.push("/assistanceDocAdd")
+}
+function editDocument(item){
+    router.push({path:"/assistanceDocEdit",query:{DocId:item.Id}})
+}
+
+//删除文章
+function deleteDocument(item){
+    ElMessageBox.confirm('删除后不可恢复,是否确认删除?',"提示",{
+        type:"warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+    }).then(res=>{
+        assistanceDocInterence.assistanceDocDelete({DocId:item.Id}).then(res=>{
+            if(res.Ret == 200){
+                ElMessage.success("删除成功")
+                getDocumentList()
+            }
+        })
+    }).catch(() => {});
+}
+
+function classifyManage(){
+    router.push("/docClassifyManage")
+}
+getClassifyList()
+getDocumentList()
+</script>
+
+<template>
+  <div class="assistance-center-container">
+    <div class="assistance-center-top-zone">
+      <div class="assistance-center-top-operation">
+        <el-button type="primary" @click="addDocument">添加文章</el-button>
+        <el-button type="primary" @click="classifyManage" style="margin-left: 20px;">分类管理</el-button>
+      </div>
+      <div class="assistance-center-top-search">
+        <el-cascader
+          :options="classifyList"
+          collapse-tags
+          clearable
+          :props="{
+            multiple:true,
+            value:'ClassifyId',
+            label:'ClassifyName',
+            children:'Children',
+            emitPath:false
+          }"
+          placeholder="所属分类"
+          @change="handlSearchClassify"
+        />
+        <el-input :prefix-icon="Search" placeholder="文章标题" v-model="queryParams.KeyWord" clearable @input="searchDocumentList"
+            style="width:500px;margin-left: 20px;">
+        </el-input>
+      </div>
+    </div>
+    <el-table ref="documentTable" :data="documentList" border class="document-table">
+      <el-table-column prop="Title" label="文章标题" align="center">
+        <template #default="scope">
+          <span @click="goDetail(scope.row)" class="document-title">{{scope.row.Title}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="ClassifyName" label="所属分类" align="center">
+        <template #default="scope"> <span>{{scope.row.ClassifyName}}</span> </template>
+      </el-table-column>
+      <el-table-column prop="Author" label="文章作者" align="center">
+        <template #default="scope"> <span>{{scope.row.Author}}</span> </template>
+      </el-table-column>
+      <el-table-column prop="Status" label="发布状态" align="center">
+        <template #default="scope"> 
+          <span :style="{color:scope.row.Status==2?'#4FB112':'#F56C6C'}">{{scope.row.Status==2?"已发布":"未发布"}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="CreateTime" label="创建时间" align="center">
+        <template #default="scope"> <span>{{scope.row.CreateTime}}</span> </template>
+      </el-table-column>
+      <el-table-column prop="ModifyTime" label="更新时间" align="center">
+        <template #default="scope"> <span>{{scope.row.ModifyTime}}</span> </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" min-width="110" v-if="Role!='admin'">
+        <template #default="scope">
+          <div style="color:#4099ef;">
+            <span style="margin-right:10px;cursor: pointer;" @click="publish(scope.row)" v-if="scope.row.Status==2">取消发布</span>
+            <template v-if="scope.row.Status==1">
+              <span style="margin-right:10px;cursor: pointer;" @click="editDocument(scope.row)">编辑</span>
+              <span style="margin-right:10px;cursor: pointer;" @click="publish(scope.row)">发布</span>
+            </template>
+            <span style="color: #F56C6C;cursor: pointer;" @click="deleteDocument(scope.row)">删除</span>
+          </div>
+        </template>
+      </el-table-column>
+      <template #empty>
+        <div style="line-height:44px;margin:60px 0;color:#999;">
+            <img src="~@/assets/img/cus_m/nodata.png" style="display:block;width:160px;height:128px;margin: auto;">
+            <span>暂无数据</span>
+        </div>
+      </template>
+    </el-table>
+    <!-- 页数选择器 -->
+    <m-page :page_no="queryParams.CurrentIndex" :pageSize="queryParams.PageSize"
+    :total="total" style="position: absolute;right: 40px;bottom: 40px;" @handleCurrentChange="pageChange"
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.assistance-center-container{
+  background-color: white;
+  min-height: calc(100vh - 110px);
+  padding: 30px 30px 80px;
+  box-sizing: border-box;
+  border: solid 1px #ECECEC;
+  .assistance-center-top-zone{
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    flex-wrap: wrap;
+    margin-bottom: 20px;
+    .assistance-center-top-operation{
+      display: flex;
+      align-items: center;
+      justify-content: flex-start;
+      margin: 0 30px 8px 0;
+    }
+    .assistance-center-top-search{
+      margin-bottom: 8px;
+    }
+  }
+  .document-table{
+    .document-title{
+      color:#409EFF;
+      cursor:pointer;
+    }
+  }
+}
+</style>

+ 424 - 0
src/views/system_manage/assistance_center/assistanceDocAdd.vue

@@ -0,0 +1,424 @@
+<template>
+  <div class="assistance-edit-container">
+    <div class="edit-container-rich-text">
+      <froala
+        id="froala-editor"
+        ref="froalaEditor"
+        :tag="'textarea'"
+        :config="froalaConfig"
+        v-model="addDocForm.Content"
+      ></froala>
+    </div>
+    <div class="right-area">
+      <div class="save-time" v-if="modifyTime">
+        最近保存时间:{{ modifyTime }}
+      </div>
+      <div class="edit-container-document-options">
+        <div class="document-options-button-box">
+          <el-button type="primary" class="document-options-button" @click="previewDocument" v-loading="isSubmiting">预览</el-button>
+          <el-button type="primary" class="document-options-button" @click="saveDocument('保存')" v-loading="isSubmiting">保存</el-button>
+          <el-button type="primary" class="document-options-button" @click="saveDocument('发布')" v-loading="isSubmiting">发布</el-button>
+        </div>
+        <div class="document-options-form">
+          <el-form :model="addDocForm" ref="addDocForm" :rules="addDocRules">
+            <el-form-item label="文章标题" prop="Title">
+              <el-input v-model="addDocForm.Title" placeholder="请输入文章标题"></el-input>
+            </el-form-item>
+            <el-form-item label="所属分类" prop="ClassifyId">
+              <el-cascader style="width: 100%;"
+                v-model="addDocForm.ClassifyId" :options="classifyList"
+                :props="{value:'ClassifyId',label:'ClassifyName',children:'Children',emitPath:false,disabled:'Disabled'}" placeholder="所属分类"/>
+            </el-form-item>
+            <el-form-item label="文章作者" prop="Author">
+              <el-input v-model="addDocForm.Author" placeholder="请输入文章作者"></el-input>
+            </el-form-item>
+            <el-form-item label="相关推荐">
+              <div v-for="(item,index) in addDocForm.RecommendData" :key="index" class="form-item-recommendedLink">
+                <el-input v-model="item.Name" placeholder="请输入链接名称" style="width: 190px;"></el-input>
+                <div class="recommendedLink-line"></div>
+                <el-input v-model="item.Url" placeholder="请输入链接" style="width: 190px;"></el-input>
+              </div>
+            </el-form-item>
+          </el-form>
+        </div>
+      </div>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import {assistanceDocInterence} from "@/api/api.js"
+import {createBottomHref} from "./utils/common"
+  export default {
+    name:"assistanceDocAdd",
+    data() {
+      const that = this;
+      return {
+        editor: null,
+        froalaConfig: {
+          toolbarButtons: [
+            "insertImage",
+            "insertVideo",
+            "embedly",
+            "insertFile",
+            "textColor",
+            "bold",
+            "italic",
+            "underline",
+            "strikeThrough",
+            "subscript",
+            "superscript",
+            "fontFamily",
+            "fontSize",
+            "color",
+            "inlineClass",
+            "inlineStyle",
+            "paragraphStyle",
+            "lineHeight",
+            "paragraphFormat",
+            "align",
+            "formatOL",
+            "formatUL",
+            "outdent",
+            "indent",
+            "quote",
+            "insertTable",
+            "emoticons",
+            "fontAwesome",
+            "specialCharacters",
+            "insertHR",
+            "selectAll",
+            "clearFormatting",
+            "html",
+            "undo",
+            "redo"
+          ],
+          height:"calc(100vh - 230px)",
+          fontSize: ["12", "14", "16", "18", "20", "24", "28", "32", "36", "40"],
+          fontSizeDefaultSelection: "16",
+          theme: "dark", //主题
+          placeholderText: "请输入内容",
+          language: "zh_cn", //国际化
+          imageUploadURL: process.env.API_ROOT + "/report/uploadImg", //上传url
+          videoUploadURL: process.env.API_ROOT + "/report/uploadImg", //上传url
+          fileUploadURL: process.env.API_ROOT + "/report/uploadImg", //上传url 更多上传介绍 请访问https://www.froala.com/wysiwyg-editor/docs/options
+          imageEditButtons:['imageAlign', 'imageCaption', 'imageRemove',  '-', 'imageDisplay',  'imageSize'],
+          quickInsertButtons: ["image","video","hr"], //快速插入项
+          quickInsertEnabled:false, // 是否启用快速插入功能
+          toolbarVisibleWithoutSelection: false, //是否开启 不选中模式
+          // disableRightClick:true,//是否屏蔽右击
+          toolbarSticky: false, //操作栏是否自动吸顶
+          // zIndex:99999,
+          saveInterval: 0,
+          /* 				saveParam: 'content',
+            saveURL: process.env.API_ROOT+'/report/saveReportContent',
+            saveMethod: 'POST',
+            saveParams: {}, */
+          events: {
+            //this.editor 定义在vue data 中
+            initialized: function () {
+              that.editor = this;
+            },
+            keyup: function (e, editor) {
+              //添加事件,在每次按键按下时,都记录一下最后停留位置
+              that.$nextTick(function () {
+                that.lastEditRange = getSelection().getRangeAt(0);
+              });
+            },
+            click: function (e, editor) {
+              //添加事件,在每次鼠标点击时,都记录一下最后停留位置
+              that.$nextTick(function () {
+                that.lastEditRange = getSelection().getRangeAt(0);
+              });
+            },
+          },
+        },
+        classifyList:[],
+        addDocForm:{
+          Title:"",
+          ClassifyId:"",
+          Author:"",
+          Status:1,
+          Content:'',
+          AnchorData:[],
+          RecommendData:[{Name:"",Url:""},{Name:"",Url:""}],
+        },
+        addDocRules:{
+          Title:{required:true,message:'文章标题不能为空',trigger:'blur'},
+          ClassifyId:{required:true,message:'文章所属分类不能为空',trigger:'change'},
+          Author:{required:true,message:'文章作者不能为空',trigger:'blur'}
+        },
+        anchorData:[],
+        isSubmiting:false,
+        autoSaveTimer:null,
+        modifyTime:'',
+        contentChange:false
+      }
+    },
+    watch:{
+      addDocForm:{
+        handler:function(){
+          console.log('内容改变');
+          this.contentChange=true
+        },
+        deep:true
+      }
+    },
+    created() {
+      this.getClassifyData()
+      if(this.$route.query.DocId){
+        assistanceDocInterence.getAssistanceDoc({DocId:this.$route.query.DocId}).then(res=>{
+          if(res.Ret == 200){
+            this.addDocForm={
+              Id:res.Data.Id,
+              Title:res.Data.Title,
+              ClassifyId:res.Data.ClassifyId,
+              Author:res.Data.Author,
+              Status:res.Data.Status,
+              Content:res.Data.Content,
+              RecommendData:res.Data.Recommend || [{Name:"",Url:""},{Name:"",Url:""}]
+            }
+            this.modifyTime=res.Data.ModifyTime
+            if((!this.autoSaveTimer) && this.addDocForm.Id){
+              this.autoSaveTimer=setInterval(()=>{
+                this.saveDocument('保存',true)
+              },6000)
+            }
+          }
+        })
+      }
+    },
+    destroyed(){
+      clearInterval(this.autoSaveTimer)
+      this.autoSaveTimer=null
+    },
+    mounted(){
+      this.addDocForm.Content=""
+    },
+    methods: {
+      getClassifyData(){
+        assistanceDocInterence.getAssistanceClassifyList().then(res=>{
+          if(res.Ret == 200){
+            this.classifyList = res.Data?res.Data.AllNodes||[]:[]
+          }
+        })
+      },
+      // 生成锚点
+      generateAnchor(){
+        this.anchorData=[]
+        // 搜索富文本中的h1和h2标签 当做一级和二级的锚点
+        this.searchTitleTag(0,1)
+        // console.log(this.addDocForm.Content,this.anchorData);
+      },
+      // 搜索标题标签h1,h2
+      searchTitleTag(searchPosition,firstLevel){
+        let frontH1Posiiton,nextH1Posiiton,H2Posiiton=0
+        let frontH1RightPosiiton,H2RightPosiiton=0
+        let backH1Posiiton,backH2Posiiton=0
+        // 本次搜索第一个h1的位置
+        frontH1Posiiton = this.addDocForm.Content.indexOf('<h1',searchPosition)
+        // 右闭合标签
+        frontH1RightPosiiton = this.addDocForm.Content.indexOf('>',frontH1Posiiton)
+
+        if(frontH1Posiiton == -1) return 
+
+        let anchorText=`id="doc_anchor_${firstLevel}"`
+        // console.log(frontH1Posiiton,firstLevel,'firstLevel');
+        this.addDocForm.Content = this.addDocForm.Content.substring(0, frontH1Posiiton+3) 
+                                  +" "+anchorText + this.addDocForm.Content.substring(frontH1RightPosiiton);
+        // 再次获取右闭合标签
+        frontH1RightPosiiton = this.addDocForm.Content.indexOf('>',frontH1Posiiton)
+        // 对应的</h1>的位置 原本用的</h1>,后来发现> 和 </h1>之间会掺其他标签
+        backH1Posiiton = this.addDocForm.Content.indexOf('<',frontH1RightPosiiton)
+        // 获取标题
+        let AnchorTitle = this.addDocForm.Content.substring(frontH1RightPosiiton+1,backH1Posiiton)
+
+        this.anchorData.push({
+          AnchorId:`${firstLevel}`,
+          Anchor:`doc_anchor_${firstLevel}`,
+          AnchorName:AnchorTitle,
+          Child:[]})
+        // 本次搜索下一个h1的位置
+        nextH1Posiiton = this.addDocForm.Content.indexOf('<h1',backH1Posiiton)==-1?
+                          this.addDocForm.Content.length:this.addDocForm.Content.indexOf('<h1',backH1Posiiton)
+        // 从第一个h1的位置开始查找h2标签
+        H2Posiiton = this.addDocForm.Content.indexOf('<h2',backH1Posiiton)
+
+        let secondLevel=1
+        while (!(H2Posiiton==-1 || H2Posiiton>nextH1Posiiton)) {
+          // 右闭合标签
+          H2RightPosiiton = this.addDocForm.Content.indexOf('>',H2Posiiton)
+
+          // 找到了,并且位置小于下一个h1的位置 
+          let anchorTextH2=`id="doc_anchor_${firstLevel}_${secondLevel}"`
+          // console.log(H2Posiiton,secondLevel,'secondLevel');
+          this.addDocForm.Content = this.addDocForm.Content.substring(0, H2Posiiton+3) 
+                                    +" "+anchorTextH2 + this.addDocForm.Content.substring(H2RightPosiiton);
+          // 再次获取右闭合标签
+          H2RightPosiiton = this.addDocForm.Content.indexOf('>',H2Posiiton)
+          // 对应的</h2>的位置  原本用的</h2>,后来发现> 和 </h2>之间会掺其他标签
+          backH2Posiiton = this.addDocForm.Content.indexOf('<',H2RightPosiiton)
+          // 获取标题
+          let AnchorTitleLevelTwo = this.addDocForm.Content.substring(H2RightPosiiton+1,backH2Posiiton)
+          this.anchorData[firstLevel-1].Child.push(
+            {AnchorId:`${firstLevel}_${secondLevel}`,
+            Anchor:`doc_anchor_${firstLevel}_${secondLevel}`,
+            AnchorName:AnchorTitleLevelTwo,
+            Child:[]
+          })
+          // nextH1Posiiton 和 secondLevel 随之增加
+          // nextH1Posiiton +=anchorTextH2.length+1
+          // 更新nextH1Posiiton位置
+          nextH1Posiiton = this.addDocForm.Content.indexOf('<h1',backH1Posiiton)==-1?
+                          this.addDocForm.Content.length:this.addDocForm.Content.indexOf('<h1',backH1Posiiton)
+          secondLevel++
+          H2Posiiton = this.addDocForm.Content.indexOf('<h2',backH2Posiiton)
+        }
+        // 结束一轮 <h1></h1>标签的寻找
+        if(this.addDocForm.Content.indexOf('<h1',backH1Posiiton+4)!=-1){
+          // 如果有下一个h1的标签,说明寻找还没结束,继续寻找
+          firstLevel++
+          this.searchTitleTag(nextH1Posiiton,firstLevel)
+        }
+      },
+      previewDocument(){
+        if(this.isSubmiting) return 
+        if(!this.addDocForm.Content){
+          this.$message.error("文章内容不能为空")
+          return
+        }
+        let bottomLink = createBottomHref(this.addDocForm.RecommendData)
+        
+        sessionStorage.setItem("documentDoc",this.addDocForm.Content+bottomLink)
+        let { href } = this.$router.resolve({ path: "/assistanceDocDetail" });
+        window.open(href, "_blank");
+      },
+      saveDocument(type,isAuto){
+        if(this.isSubmiting) return 
+        this.$refs.addDocForm.validate(valid=>{
+          if(valid){
+            if(!this.addDocForm.Content){
+              this.$message.error("文章内容不能为空")
+              return
+            }
+            if(!isAuto) this.isSubmiting=true
+            if(type=="发布") this.addDocForm.Status=2
+
+            this.generateAnchor()
+            this.addDocForm.AnchorData = this.anchorData
+            //保存
+            assistanceDocInterence.addAssistanceDoc({...this.addDocForm,IsChange:this.contentChange}).then(res=>{
+              if(res.Ret == 200){
+                this.contentChange=false
+
+                !isAuto && this.$message({
+                  type:'success',
+                  message:'操作成功',
+                  duration:1000
+                })
+                if(type=="发布"){
+                  setTimeout(()=>{
+                    this.$router.back()
+                  },1000)
+                }else{
+                  this.isSubmiting=false
+                  this.modifyTime=res.Data.ModifyTime
+                  if(!this.addDocForm.Id){
+                    //新增
+                    setTimeout(()=>{
+                      this.$router.replace("/assistanceDocAdd?DocId="+res.Data.HelpDocId)
+                      this.addDocForm.Id = res.Data.HelpDocId
+                      if((!this.autoSaveTimer) && this.addDocForm.Id){
+                        this.autoSaveTimer=setInterval(()=>{
+                          this.saveDocument('保存',true)
+                        },6000)
+                      }
+                    },1000)
+                  }
+                }
+              }
+            }).catch(()=>{
+              this.isSubmiting=false
+            })
+          }
+        })
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .assistance-edit-container{
+    display: flex;
+    justify-content: flex-start;
+    .edit-container-rich-text{
+      flex-grow: 1;
+      min-height: calc(100vh - 110px);
+      background-color: white;
+      border:solid 1px #ECECEC;
+      box-sizing: border-box;
+    }
+    .right-area{
+      margin-left: 20px;
+      min-height: calc(100vh - 112px);
+      .save-time{
+        font-size: 16px;
+        line-height: 22px;
+        color: #000000;
+        font-weight: 400;
+        background-color: unset;
+        margin-bottom: 10px;
+      }
+      .edit-container-document-options{
+        min-height: calc(100% - 32px);
+        background-color: white;
+        border:solid 1px #ECECEC;
+        box-sizing: border-box;
+        width: 440px;
+        min-width: 440px;
+        .document-options-button-box{
+          width: 100%;
+          height: 80px;
+          box-sizing: border-box;
+          padding: 20px;
+          box-shadow: 0px 5px 10px #ECECEC;
+          border-bottom: solid 1px #ECECEC;
+          .document-options-button{
+            height: 40px;
+            width: 120px;
+          }
+        }
+        .document-options-form{
+          padding: 30px 20px;
+          .form-item-recommendedLink{
+            display: flex;
+            align-items: center;
+            justify-content: flex-start;
+            width: 100%;
+            margin-bottom: 20px;
+            &:last-child{
+              margin-bottom: 0;
+            }
+            .recommendedLink-line{
+              flex: 1;
+              height: 1px;
+              background-color:#DCDFE6 ;
+            }
+          }
+        }
+      }
+    }
+  }
+</style>
+<style lang="scss">
+.assistance-edit-container{
+  .fr-toolbar,.fr-box.fr-basic .fr-wrapper{
+    border: none;
+  }
+}
+.fr-popup.fr-active{
+  z-index: 100000!important;
+  opacity: 1!important;
+}
+</style>

+ 41 - 0
src/views/system_manage/assistance_center/assistanceDocDetail.vue

@@ -0,0 +1,41 @@
+<script setup>
+import {assistanceDocInterence} from '@/api/api.js'
+import {createBottomHref} from './utils/common'
+import { ref } from 'vue'
+import { useRoute } from "vue-router"
+const route = useRoute()
+let content = ref('')
+function init(){
+    if(route.query.DocId){
+        assistanceDocInterence.getAssistanceDoc({DocId:route.query.DocId}).then(res=>{
+          if(res.Ret == 200){
+            content.value = res.Data.Content + createBottomHref(res.Data.Recommend || [{Name:"",Url:""},{Name:"",Url:""}])
+          }
+        })
+      }else{
+        content.value = sessionStorage.getItem("documentDoc") || ''
+      }
+}
+init()
+</script>
+<template>
+  <div class="assistance-detail-container">
+    <div class="assistance-detail-box fr-view" v-html="content"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.assistance-detail-container{
+  background-color: white;
+  min-height: calc(100vh - 110px);
+  // height: calc(100vh - 110px);
+  padding: 20px;
+  box-sizing: border-box;
+  border: solid 1px #ECECEC;
+  // .assistance-detail-box{
+  //   border: solid 1px #333333;
+  //   height: 100%;
+  //   width: 100%;
+  // }
+}
+</style>

+ 472 - 0
src/views/system_manage/assistance_center/docClassifyManage.vue

@@ -0,0 +1,472 @@
+<!-- 
+    表格树形数据拖拽 本来使用el-table和sortablejs 但是奇怪的判断导致前端无法做跨级拖拽的限制。
+  得由后端来限制,固使用drag-tree-table 但是这个插件构造比较简单,不是用table标签实现的
+  如果产品要实现el-table上面的功能,需要自定义,有更好的方法,还请通知一声。
+ -->
+<template>
+  <div class="doc-classifyMana-container">
+    <div class="doc-classifyMana-top-zone">
+      <el-button type="primary" @click="addClassify">添加分类</el-button>
+      <el-input placeholder="分类名称" v-model="queryParams.KeyWord" @input="getclassifyData" clearable 
+      style="width:500px;margin-left: 20px;">
+        <i slot="prefix" class="el-input__icon el-icon-search"></i>
+      </el-input>
+    </div>
+    <!-- <el-table style="border:1px solid #eaeaea;" ref="classifyTableRef"
+    :data="classifyList" :row-class-name="tableRowClassName" row-key="id" :tree-props="{children:'child'}">
+      <el-table-column prop="text" label="一级分类">
+        <template slot-scope="scope">
+          <span>{{scope.row.level==1?scope.row.text:''}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="text" label="二级分类">
+        <template slot-scope="scope">
+          <span>{{scope.row.level==2?scope.row.text:''}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="text" label="三级分类">
+        <template slot-scope="scope">
+          <span>{{scope.row.level==3?scope.row.text:''}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <div class="table-button">
+            <span @click="configItem(scope.row)">权限配置</span>
+            <span @click="edititem(scope.row)">编辑</span>
+            <span @click="checkdeleteitem(scope.row)" style="margin-right: 0;color: #F56C6C;">删除</span>
+          </div>
+            </template>
+      </el-table-column>
+    </el-table> -->
+    <dragTreeTable ref="dragTreeTableRef" :beforeDragOver="beforeDrag"
+    :data="treeData" :onDrag="onTreeDataChange"
+    hightRowChange resize border >
+    <!-- onlySameLevelCanDrag -->
+    </dragTreeTable>
+    <!-- 添加分类弹窗 -->
+    <el-dialog :title="dialogTitle" :visible.sync="showAddClassifyDia" width="625px"
+    append-to-body :close-on-click-modal="false" @close="resetForm">
+      <div style="display: flex;flex-direction: column; align-items: center;margin-bottom: 35px;">
+        <el-form :model="classifyForm" ref="classifyFormRef" label-width="80px">
+          <el-form-item label="上级目录" prop="ParentId">
+            <el-cascader v-model="classifyForm.ParentId" :options="noLevelThreeList" @change="selectParentId"
+              placeholder="请选择上级目录(不选默认添加的是一级分类)" ref="parentIdCascaderRef"
+              :props="{ value:'ClassifyId',label:'ClassifyName',children:'Children',checkStrictly:true,emitPath:false,disabled:'Disabled'}" 
+              class="lastCatalogCascader" :disabled="this.dialogTitle.indexOf('编辑分类')!=-1" />
+          </el-form-item>
+          <el-form-item label="分类名称" prop="HelpDocClassifyName" :rules="{required:true,message:'分类名称不能为空',trigger:'blur'}">
+            <el-input v-model="classifyForm.HelpDocClassifyName" placeholder="请输入分类名称" style="width: 337px;"
+             maxlength="10"></el-input>
+          </el-form-item>
+        </el-form>
+        <div style="margin-top: 40px;">
+          <el-button type="primary" style="width:120px;margin-right:10px" @click="classifySave" size="medium">保存</el-button>
+          <el-button  style="width:120px;" @click="showAddClassifyDia=false" size="medium">取消</el-button>
+        </div>
+      </div>
+    </el-dialog>
+    <!-- 设置权限弹窗 -->
+    <el-dialog :title="dialogTitle" :visible.sync="showPermissionDia" width="625px"
+    append-to-body :close-on-click-modal="false" @close="resetPermissonForm">
+      <div style="display: flex;flex-direction: column; align-items: center;margin-bottom: 35px;">
+        <el-form :model="permissionForm" ref="permissionFormRef" label-width="80px">
+          <el-form-item label="可见权限" prop="merchantIds">
+            <el-select v-model="permissionForm.merchantIds" placeholder="请选择商家" 
+            multiple style="width: 337px;" collapse-tags clearable filterable >
+              <el-option :label="item.BusinessName" :value="item.EtaBusinessId" 
+              v-for="item in merchantList" :key="item.merchantId"></el-option>
+            </el-select>
+          </el-form-item>
+        </el-form>
+        <div style="margin-top: 40px;">
+          <el-button type="primary" style="width:120px;margin-right:10px" @click="configSave" size="medium">保存</el-button>
+          <el-button  style="width:120px;" @click="showPermissionDia=false" size="medium">取消</el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// import Sortable from "sortablejs";
+import {assistanceDocInterence,businessCustomInterence} from "@/api/api.js"
+import dragTreeTable from "drag-tree-table";
+  export default {
+    name:"docClassifyManage",
+    components:{dragTreeTable},
+    data() {
+      // 表格头设置
+      this.tableHeadList=[
+        {
+          type:"selection",
+          title: '一级分类',
+          field: 'ClassifyName',
+          flex:1,
+          align: 'center',
+          formatter: (item) => {    
+            return item.Level==1?item.ClassifyName:""
+          }
+        },
+        {
+          title: '二级分类',
+          field: 'ClassifyName',
+          // width: 200,
+          flex:1,
+          align: 'center',
+          formatter: (item) => {
+            return item.Level==2?item.ClassifyName:""
+          }
+        },
+        {
+          title: '三级分类',
+          field: 'ClassifyName',
+          // width: 200,
+          flex:1,
+          align: 'center',
+          formatter: (item) => {
+            return item.Level==3?item.ClassifyName:""
+          }
+        },
+        {
+          type: 'action',
+          title: '操作',
+          width: 260,
+          // flex:1,
+          align: 'center',
+          actions:[
+            {
+              text: '权限配置',
+              onclick: (item) => {
+                // item是当前行的数据
+                this.configClassify(item)
+              },
+              formatter: (item) => {
+                return '<span class="table-button">权限配置</span>'
+              }
+            },
+            {
+              text: '编辑',
+              onclick: (item) => {
+                this.editClassify(item)
+              },
+              formatter: (item) => {
+                return '<span class="table-button">编辑</span>'
+              }
+            },
+            {
+              text: '删除',
+              onclick: (item) => {
+                this.deleteClassify(item)
+              },
+              formatter: (item) => {
+                return '<span class="table-button" style="margin-right: 0;color: #F56C6C;">删除</span>'
+              }
+            }
+          ]
+        }
+      ]
+      // 自定义字段替换
+      this.custom_field={
+        id: 'ClassifyId',
+        order: 'Sort',
+        lists: 'Children',
+        parent_id: 'ParentId'
+      }
+
+      return {
+        queryParams:{
+          KeyWord:''
+        },
+        treeData:{
+          columns:this.tableHeadList,
+          lists:[],
+          custom_field:this.custom_field
+        },
+        // 没有三级分类的分类数组,用于添加
+        noLevelThreeList:[],
+        // // 扁平化之后的数据
+        // classifyListFlat:[],
+        dialogTitle:"",
+        showAddClassifyDia:false,
+        classifyForm:{
+          ParentId:0,
+          HelpDocClassifyName:'',
+          Level:0,
+        },
+        merchantList:[],
+        showPermissionDia:false,
+        permissionForm:{
+          HelpDocClassifyId:0,
+          merchantIds:[]
+        },
+      }
+    },
+    created(){
+      this.getmerchantList()
+      this.getclassifyData()
+    },
+    mounted(){
+      this.$nextTick(()=>{
+        // this.tableDropSet()
+      })
+    },
+    methods: {
+      getmerchantList(){
+        businessCustomInterence.getBusinessList({PageSize:9999999,CurrentIndex:1}).then(res=>{
+          if(res.Ret == 200){
+            this.merchantList = res.Data.List||[]
+          }
+        })
+      },
+      getclassifyData(){
+        assistanceDocInterence.getAssistanceClassifyList(this.queryParams).then(res=>{
+          if(res.Ret == 200){
+            this.treeData.lists = res.Data?res.Data.AllNodes||[]:[]
+            this.noLevelThreeList = res.Data?res.Data.TwoLevelNodes||[]:[]
+            this.treeData.lists.map(list=> {
+              // 一级展开
+              list.open=true
+            })
+            // console.log(this.treeData.lists);
+          }
+        })
+      },
+      // tableRowClassName({row,rowIndex}) {
+      //   if( row.child && row.child.length>0 ){
+      //     return 'has-child-row';
+      //   }else{
+      //     return '';
+      //   }
+      // },
+      beforeDrag(dragItem,effectItem,type){
+        if(type=='center'){
+          // 放里面
+          if((dragItem.Level-1)!=effectItem.Level) return false
+        }else{
+          //不放里面
+          if(dragItem.Level!=effectItem.Level) return false
+        }
+        // return false
+      },
+      onTreeDataChange(list,dragItem){
+        // console.log(list,dragItem,'arguments');
+        // list - 拖拽后的数据 dragItem-拖拽的项
+        let ClassifyId,PrevClassifyId,NextClassifyId,ParentClassifyId=0
+        // 在拖拽后的数据中找到拖拽项对应的位置
+        const findDraggedItem=(list)=>{
+          let itemIndex = list.findIndex(it => it.ClassifyId == dragItem.ClassifyId)
+          if(itemIndex!=-1){
+            if(itemIndex==0){
+              PrevClassifyId=0
+            }else{
+              PrevClassifyId = list[itemIndex-1].ClassifyId
+            }
+            if(itemIndex==(list.length-1)){
+              NextClassifyId=0
+            }else{
+              NextClassifyId = list[itemIndex+1].ClassifyId
+            }
+            ClassifyId = dragItem.ClassifyId
+            ParentClassifyId = dragItem.ParentId
+            console.log({
+              ClassifyId,ParentClassifyId,PrevClassifyId,NextClassifyId
+            });
+            assistanceDocInterence.moveAssistanceClassify({
+              ClassifyId,ParentClassifyId,PrevClassifyId,NextClassifyId
+            }).then(res=>{
+              if(res.Ret == 200){
+                this.$message.success("移动分类成功")
+                this.getclassifyData()
+              }
+            })
+          }else{
+            list.map(li => {
+              li.Children && li.Children.length>0 && findDraggedItem(li.Children)
+            })
+          }
+        }
+        findDraggedItem(list)
+      },
+      // tableDropSet(){
+      //   const tbody = this.$refs.classifyTableRef.$el.querySelector(
+      //     ".el-table__body-wrapper > table > tbody"
+      //   );
+      //   const _this = this;
+      //   Sortable.create(tbody, {
+      //     animation: 150, 
+      //     onStart(evt){
+      //       if(_this.classifyListFlat && _this.classifyListFlat.length>0){
+      //         return 
+      //       }else{
+      //         _this.flatClassifyList(_this.classifyList,0)
+      //       }
+      //       console.log(_this.classifyListFlat);
+      //     },
+      //     onEnd({ newIndex, oldIndex }) {
+      //       console.log({ newIndex, oldIndex });
+      //       if(newIndex == oldIndex) return 
+      //       const oldRow = _this.classifyListFlat[oldIndex]
+      //       const newRow = _this.classifyListFlat[newIndex]
+      //       console.log(oldRow,newRow);
+      //       _this.$message.success("拖拽成功")
+      //       _this.getclassifyData()
+      //     },
+      //     onMove({ dragged, related }){
+      //       console.log(dragged.rowIndex, related.rowIndex);
+      //       const oldRow = _this.classifyListFlat[dragged.rowIndex]
+      //       const newRow = _this.classifyListFlat[related.rowIndex]
+      //       console.log(oldRow.level,newRow.level);
+      //       if (oldRow.level !== newRow.level) {
+      //         return false // 不允许不同级的拖动
+      //       }
+      //     }
+      //   });
+      // },
+      // flatClassifyList(list,parentId){
+      //   for (let i = 0; i < list.length; i++) {
+      //     const element = list[i];
+      //     const lastElementId = i==0?0:list[i-1].id
+      //     const nextElementId = i==(list.length-1)?0:list[i+1].id
+      //     this.classifyListFlat.push({id:element.id,lastElementId,nextElementId,parentId,level:element.level})
+      //     if(element.child && element.child.length>0){
+      //       this.flatClassifyList(element.child,element.id)
+      //     }
+      //   }
+      // },
+      // 添加分类
+      addClassify(){
+        this.dialogTitle="添加分类"
+        this.showAddClassifyDia=true
+      },
+      // 编辑分类
+      editClassify(item){
+        this.classifyForm={
+          ParentId:item.ParentId,
+          HelpDocClassifyId:item.ClassifyId,
+          HelpDocClassifyName:item.ClassifyName
+        }
+        this.dialogTitle="编辑分类"
+        this.showAddClassifyDia=true
+      },
+      selectParentId(value){
+        this.classifyForm.Level = this.$refs.parentIdCascaderRef.getCheckedNodes()[0].data.Level
+      },
+      deleteClassify(item){
+        this.$confirm('是否确认删除?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        }).then(() => {
+          assistanceDocInterence.deleteAssistanceClassify({ClassifyId:item.ClassifyId}).then(res=>{
+            if(res.Ret == 200){
+              this.$message.success("删除分类成功")
+              this.getclassifyData()
+            }
+          })
+        }).catch(()=>{})
+      },
+      // 保存
+      classifySave(){
+        this.$refs.classifyFormRef.validate(valid=>{
+          if(valid){
+            console.log(this.classifyForm);
+            if(this.classifyForm.HelpDocClassifyId){
+              //编辑
+              assistanceDocInterence.editAssistanceClassify(this.classifyForm).then(res=>{
+                if(res.Ret == 200){
+                  this.$message.success(this.dialogTitle+"成功")
+                  this.showAddClassifyDia=false
+                  this.resetForm()
+                  this.getclassifyData()
+                }
+              })
+            }else{
+              //新增
+              assistanceDocInterence.addAssistanceClassify({...this.classifyForm}).then(res=>{
+                if(res.Ret == 200){
+                  this.$message.success(this.dialogTitle+"成功")
+                  this.showAddClassifyDia=false
+                  this.resetForm()
+                  this.getclassifyData()
+                }
+              })
+            }
+          }
+        })
+      },
+      resetForm(){
+        this.classifyForm={
+          ParentId:0,
+          HelpDocClassifyName:'',
+          Level:0
+        }
+        this.$refs.classifyFormRef.clearValidate()
+      },
+      //权限配置
+      configClassify(row){
+        this.permissionForm={
+          HelpDocClassifyId:row.ClassifyId,
+          merchantIds:row.VisibleBusinessIds?row.VisibleBusinessIds.split(',').map(element => {
+            return parseInt(element)
+          }):[]
+        }
+        // console.log(this.permissionForm.merchantIds);
+        this.dialogTitle="设置权限"
+        this.showPermissionDia=true
+      },
+      resetPermissonForm(){
+        this.permissionForm={
+          HelpDocClassifyId:0,
+          merchantIds:[]
+        }
+        this.$refs.permissionFormRef.clearValidate()
+      },
+      configSave(){
+        assistanceDocInterence.editAssistanceClassifyVisible({
+          HelpDocClassifyId:this.permissionForm.HelpDocClassifyId,
+          VisibleBusinessIds:this.permissionForm.merchantIds.join(',')
+        }).then(res=>{
+          if(res.Ret == 200){
+            this.$message.success("设置成功")
+            this.showPermissionDia=false
+            this.resetPermissonForm()
+            this.getclassifyData()
+          }
+        })
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+.doc-classifyMana-container{
+  background-color: white;
+  min-height: calc(100vh - 110px);
+  padding: 30px;
+  box-sizing: border-box;
+  border: solid 1px #ECECEC;
+  .doc-classifyMana-top-zone{
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    margin-bottom: 20px;
+  }
+}
+</style>
+<style lang="scss">
+.lastCatalogCascader{
+  width: 337px;
+  .el-input{
+    width: 337px;
+  }
+}
+.has-child-row {
+    background-color: #f2f6fa!important;
+}
+.table-button{
+    color:#4099ef; 
+    cursor: pointer;
+    margin-right: 10px;
+  }
+</style>

+ 10 - 0
src/views/system_manage/assistance_center/utils/common.js

@@ -0,0 +1,10 @@
+// 生成底部的两个链接
+export function createBottomHref(RecommendData){
+  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>"
+}