zhuliu 2 лет назад
Родитель
Сommit
cce1ff6902
49 измененных файлов с 5206 добавлено и 493 удалено
  1. 0 52
      src/App.vue
  2. 4 0
      src/assets/css/theme.css
  3. 0 13
      src/directives/index.js
  4. 0 37
      src/directives/permission.js
  5. 15 16
      src/main.js
  6. 4 1
      src/store/index.js
  7. 18 0
      src/store/persisPlugin.js
  8. 20 1
      src/utils/direct/index.js
  9. 0 266
      src/utils/directives.js
  10. 0 105
      src/utils/drag.js
  11. 33 0
      src/views/analyse/custom/components/Charts/Area/index.vue
  12. 34 0
      src/views/analyse/custom/components/Charts/Bar/index.vue
  13. 37 0
      src/views/analyse/custom/components/Charts/Column/index.vue
  14. 46 0
      src/views/analyse/custom/components/Charts/Doughnut/index.vue
  15. 98 0
      src/views/analyse/custom/components/Charts/Heat/index.vue
  16. 33 0
      src/views/analyse/custom/components/Charts/Line/index.vue
  17. 67 0
      src/views/analyse/custom/components/Charts/Map/index.vue
  18. 67 0
      src/views/analyse/custom/components/Charts/Map/index1.vue
  19. 81 0
      src/views/analyse/custom/components/Charts/Map/index2.vue
  20. 34 0
      src/views/analyse/custom/components/Charts/MultipleBar/index.vue
  21. 35 0
      src/views/analyse/custom/components/Charts/MultipleColumn/index.vue
  22. 33 0
      src/views/analyse/custom/components/Charts/MultipleLine/index.vue
  23. 45 0
      src/views/analyse/custom/components/Charts/Pie/index.vue
  24. 105 0
      src/views/analyse/custom/components/Charts/Radar/index.vue
  25. 85 0
      src/views/analyse/custom/components/Charts/Radar/index2.vue
  26. 51 0
      src/views/analyse/custom/components/Charts/Sankey/index.vue
  27. 111 0
      src/views/analyse/custom/components/Charts/Scatter/index.vue
  28. 34 0
      src/views/analyse/custom/components/Charts/StackedBar/index.vue
  29. 35 0
      src/views/analyse/custom/components/Charts/StackedColumn/index.vue
  30. 47 0
      src/views/analyse/custom/components/Charts/Sunburst/index.vue
  31. 21 0
      src/views/analyse/custom/components/Charts/Table/Column.vue
  32. 265 0
      src/views/analyse/custom/components/Charts/Table/index.vue
  33. 47 0
      src/views/analyse/custom/components/Charts/Tree/index.vue
  34. 89 0
      src/views/analyse/custom/components/Charts/Treemap/index.vue
  35. 51 0
      src/views/analyse/custom/components/Charts/index.js
  36. 56 0
      src/views/analyse/custom/components/Charts/index.vue
  37. 50 0
      src/views/analyse/custom/components/Tabs/Data.vue
  38. 599 0
      src/views/analyse/custom/components/Tabs/DataItem.vue
  39. 107 0
      src/views/analyse/custom/components/Tabs/Item.vue
  40. 50 0
      src/views/analyse/custom/components/Tabs/Line.vue
  41. 401 0
      src/views/analyse/custom/components/Tabs/Style.vue
  42. 53 0
      src/views/analyse/custom/components/Tabs/StyleType.vue
  43. 19 0
      src/views/analyse/custom/components/Tabs/mixins.js
  44. 728 0
      src/views/analyse/custom/index.vue
  45. 445 0
      src/views/analyse/custom/mixins.js
  46. 628 0
      src/views/analyse/report/create/components/PatentList.vue
  47. 282 0
      src/views/analyse/report/create/index.vue
  48. 142 0
      src/views/analyse/report/record/index.vue
  49. 1 2
      src/views/layout/index.vue

+ 0 - 52
src/App.vue

@@ -266,56 +266,4 @@ html, body, #app {
 // .el-main {flex-basis: 0}
 .el-container {height: 100%;}
 </style>
-<style lang="less" scoped>
-.absoParent1{
-  cursor: move;
-  width: 40px;
-  height: 50px;
-  border: 1px solid #1a65b8;
-  background: #fff;
-  color: black;
-  border-top-left-radius:10px; 
-  border-bottom-left-radius:10px; 
-  float: right;
-  position: relative;
-  bottom: 100px;
-  right: 0px;
-  z-index: 6;
-  .icon{
-    font-size: 24px;
-  };
-  .mouseEnter{
-    display: none;
-  }
-}
-.absoParent1:hover{
-  width:100px;
-  background:#1a65b8;
-  color:white;
-  .icon{
-    font-size: 30px;
-  };
-  .mouseEnter{
-    display: inline-block;
-  };
-  .abso1{
-    width:100px
-  }
-}
-.abso1{
-  width: 40px;
-  height: 50px;
-  border-top-left-radius:10px; 
-  border-bottom-left-radius:10px; 
-  position: absolute;
-  top: 0;
-  right: 0;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-.abso1:hover{
-  width:100px;
-}
 
-</style>

+ 4 - 0
src/assets/css/theme.css

@@ -0,0 +1,4 @@
+html{
+    --bg:#316192;
+    --fm:黑体;
+}

+ 0 - 13
src/directives/index.js

@@ -1,13 +0,0 @@
-// import permission from "./permission"
-
-// const directives = {
-//     permission
-// }
-
-// export default {
-//     install(Vue){
-//         Object.keys(directives).forEach(key=>{
-//             Vue.directive(key,directives[key])
-//         })
-//     }
-// }

+ 0 - 37
src/directives/permission.js

@@ -1,42 +1,5 @@
-import axios from '@/utils/axios'
 import Store from '@/store'
-// export default {
-//     inserted(el,bindling){
-    	
-        
-//         let perVal = bindling.value;
-//         console.log(perVal)
-//         if(bindling.value){
-//            axios.get('/permission/api/admin/getPermissionList').then(response => {
-//                 console.log(JSON.parse( response.data).data)
-//                 let pers=JSON.parse( response.data).data;
-//                 let hasPer = pers.some(item=>{
-//                     return item == perVal
-//                 });
-//                 if(!hasPer){
-                    
-//                    if(perVal.indexOf('modify')>-1){
-    
-//                     el.children[0].children[0].disabled='true'
-              
-//                     // el.children[0].children[0].style.border='1px solid #C0C0C0'
-//                    }else{
-//                     el.style.display="none"
-                  
-    
-//                    }
-//                 }
-//             })
-          
-//         }
-//     }
-// }
-
-// export function getPermissionList(){
-//     console.log(11,Store.state.admin.permission)
-// }
 
 export const hasPermission = (sign) => {
-  
     return (Store.state.admin.permission).indexOf(sign) !== -1
   }

+ 15 - 16
src/main.js

@@ -20,18 +20,22 @@ import './icons'
 import lodash from 'lodash'
 import "@/icons/icon/iconfont.css"
 import "@/icons/icon2/iconfont.css"
+import "@/assets/css/theme.css"
 
 
-
+//是否有功能权限
 import {hasPermission} from './directives/permission';
 Vue.prototype.$permission = hasPermission
 
-import model from './utils/model'
-Vue.use(model)
-
+//报告的权限
 import {reportPermission} from '@/utils/reportPermission'
 Vue.prototype.$reportPermission = reportPermission
 
+//组件库
+import model from './utils/model'
+Vue.use(model)
+
+//自定义指令
 import directive from './utils/directives'
 Vue.use(directive)
 
@@ -40,30 +44,25 @@ Vue.prototype.$cookie = Cookies
 
 
 Vue.config.productionTip = false
-Vue.prototype.$constants = constants
+Vue.prototype.$constants = constants//公用数据
 Vue.prototype.$api = api
 Vue.prototype.$echarts = echarts
-Vue.prototype.$d = formatTableDate
-Vue.prototype.$p = Config.imgURL
-Vue.prototype.$rImg = Config.reportImgURL
-Vue.prototype.$c = Config
-Vue.prototype.$s = Storage
+Vue.prototype.$d = formatTableDate//格式化时间
+Vue.prototype.$c = Config//配置信息
+Vue.prototype.$s = Storage//本地存储
 Vue.prototype.$_ = lodash
-Vue.prototype.$r = hasRole
+Vue.prototype.$r = hasRole//专题库权限
 Vue.use(Charts)
 Vue.use(Particles)
-// Vue.use(Element)
 
 // 首页引导插件
 import intro from 'intro.js' // introjs库
 import 'intro.js/introjs.css' // introjs默认css样式
-// introjs还提供了多种主题,可以通过以下方式引入
 import 'intro.js/themes/introjs-modern.css' // introjs主题
-// 把intro.js加入到vue的prototype中,方便使用,就可以直接通过this.$intro()来调用了
 Vue.prototype.$intro = intro 
 
-import i18n from '@/components/language'  
- 
+//语言库
+import i18n from '@/components/language' 
  Vue.use(Element,{
   i18n: (key, value) => i18n.t(key, value)// 在注册Element时设置i18n的处理方法
 })

+ 4 - 1
src/store/index.js

@@ -11,6 +11,8 @@ import getters from './getters'
 import dictMessage from "./modules/dictMessage"
 import contextMenu from './modules/contextMenu'
 
+import persisPlugin from './persisPlugin'
+
 Vue.use(Vuex)
 
 export default new Vuex.Store({
@@ -25,5 +27,6 @@ export default new Vuex.Store({
     dictMessage,
     contextMenu
   },
-  getters
+  getters,
+  plugins:[persisPlugin]
 })

+ 18 - 0
src/store/persisPlugin.js

@@ -0,0 +1,18 @@
+const KEY = 'VUEX_STORE'
+export default function(store){
+    //保存仓库数据到本地
+    window.addEventListener('beforeunload',()=>{
+        localStorage.setItem(KEY, JSON.stringify(store.state))
+    })
+    //恢复仓库数据
+    try{
+       const localStorage = localStorage.getItem(KEY)
+        if(localStorage){
+            store.replaceState(JSON.parse(localStorage.getItem(KEY)))
+        } 
+    }
+    catch{
+        
+    }
+    
+}

+ 20 - 1
src/utils/direct/index.js

@@ -230,11 +230,30 @@ const SelectLazyLoading=(el,binding)=>{
       }
     });
 }
+
+//块禁止样式
+const disabled = (el,binding)=>{
+    if(binding.value){
+        el.style.pointerEvents = 'none';
+        if(el.parentNode){
+            el.parentNode.style.cursor='not-allowed';
+        }
+        
+        el.style.color='#bbb'
+    }else{
+        el.style.pointerEvents = '';
+        if(el.parentNode){
+            el.parentNode.style.cursor='';
+        }
+        el.style.color=''
+    }
+}
 const directives = {
     draggable,
     dragControllerDiv,
     dragControllerDiv1,
-    SelectLazyLoading
+    SelectLazyLoading,
+    disabled
 };
 // 这种写法可以批量注册指令
 export default {

+ 0 - 266
src/utils/directives.js

@@ -1,266 +0,0 @@
-// 引入拖拽js
-import { startDrag } from './drag.js'
- 
-/**
- * 为el-dialog弹框增加拖拽功能
- * @param {*} el 指定dom
- * @param {*} binding 绑定对象
- * desc   只要用到了el-dialog的组件,都可以通过增加v-draggable属性变为可拖拽的弹框
- */
-const draggable = (el, binding) => {
-    // 绑定拖拽事件 [绑定拖拽触发元素为弹框头部、拖拽移动元素为整个弹框]
-    startDrag(el.querySelector('.el-dialog__header'), el.querySelector('.el-dialog'), binding.value);
-};
-
-const dragControllerDiv=(el, binding)=>{
-  var resize = el.children[1];
-  var left =el.children[0];
-  var mid = el.children[2];
-  var box = el;
-  // if(!resize){
-  //   return false
-  // }
-  resize.onmousedown = function (e) {
-    //颜色改变提醒
-    resize.style.background = '#818181';
-    var startX = e.clientX;
-    resize.left = resize.offsetLeft;
-    // 鼠标拖动事件
-    document.onmousemove = function (e) {
-        var endX = e.clientX;
-        var moveLen = resize.left + (endX - startX); // (endx-startx)=移动的距离。resize[i].left+移动的距离=左边区域最后的宽度
-        var maxT = box.clientWidth - resize.offsetWidth; // 容器宽度 - 左边区域的宽度 = 右边区域的宽度
-
-        if (moveLen < 32) moveLen = 32; // 左边区域的最小宽度为32px
-        if (moveLen > maxT - 150) moveLen = maxT - 150; //右边区域最小宽度为150px
-
-        resize.style.left = moveLen; // 设置左侧区域的宽度
-
-            left.style.width = moveLen + 'px';
-           if(mid){
-            mid.style.width = (box.clientWidth - moveLen - 10) + 'px';
-           }
-        
-    };
-    // 鼠标松开事件
-    document.onmouseup = function (evt) {
-        //颜色恢复
-        resize.style.background = '#d6d6d6';
-        document.onmousemove = null;
-        document.onmouseup = null;
-        resize.releaseCapture && resize.releaseCapture(); //当你不在需要继续获得鼠标消息就要应该调用ReleaseCapture()释放掉
-    };
-    resize.setCapture && resize.setCapture(); //该函数在属于当前线程的指定窗口里设置鼠标捕获
-    return false;
-};
- 
-};
-const dragControllerDiv1=(el,binding)=>{
-  var resize1 = el.children[2];
-  var resize2 = el.children[1];
-  var left = el.children[0];
-  var mid = el.children[3];
-  var box = el;
-  if(resize2){
-    dragControllerDiv2(el,binding)
-    // return false
-  }
-      // 鼠标按下事件
-      resize1.onmousedown = function (e) {
-        var flex = box.style.flexDirection
-        switch (flex) {
-          case 'row-reverse':
-            resize1.left = mid.offsetWidth;
-              break;
-          case 'row':
-            resize1.left = left.offsetWidth;
-              break;
-          case 'column-reverse':
-              break;
-          case 'column':
-              break;
-          default:
-              break;
-      }
-          //颜色改变提醒
-          resize1.style.background = '#818181';
-          var startX = e.clientX;
-          // resize1.left = resize1.offsetLeft-50;
-          // 鼠标拖动事件
-          document.onmousemove = function (e) {
-             
-              var endX = e.clientX;
-              var moveLen = resize1.left + (endX - startX); // (endx-startx)=移动的距离。resize[i].left+移动的距离=左边区域最后的宽度
-              var maxT = box.clientWidth - resize1.offsetWidth; // 容器宽度 - 左边区域的宽度 = 右边区域的宽度
-
-              if (moveLen < 32) moveLen = 32; // 左边区域的最小宽度为32px
-              if (moveLen > maxT - 150) moveLen = maxT - 150; //右边区域最小宽度为150px
-
-              // resize1.style.left = moveLen; // 设置左侧区域的宽度
-
-                  switch (flex) {
-                      case 'row-reverse':
-                          mid.style.width = moveLen + 'px';
-                          if(left){
-                           left.style.width = (box.clientWidth - moveLen - 10) + 'px';
-                          }
-                          break;
-                      case 'row':
-                          left.style.width = moveLen + 'px';
-                          if(mid){
-                              mid.style.width = (box.clientWidth - moveLen - 10) + 'px';
-                          }
-                          break;
-                      case 'column-reverse':
-                          
-                          break;
-                      case 'column':
-                          
-                          break;
-                      default:
-                          break;
-                  }
-             
-              
-          };
-          // 鼠标松开事件
-          document.onmouseup = function (evt) {
-              //颜色恢复
-              resize1.style.background = '#d6d6d6';
-              document.onmousemove = null;
-              document.onmouseup = null;
-              resize1.releaseCapture && resize1.releaseCapture(); //当你不在需要继续获得鼠标消息就要应该调用ReleaseCapture()释放掉
-          };
-          resize1.setCapture && resize1.setCapture(); //该函数在属于当前线程的指定窗口里设置鼠标捕获
-          return false;
-        }
-};
-const dragControllerDiv2=(el,binding)=> {
-  var resize1 = el.children[1];
-  // var resize2 = el.children[1];
-  var left = el.children[0];
-  var mid = el.children[3];
-  var box = el
-  mid.style.width = '100%'
-  left.style.width = '100%'
-      // 鼠标按下事件
-      resize1.onmousedown = function (e) {
-          var flex = box.style.flexDirection
-          switch (flex) {
-              case 'row-reverse':
-                 
-                  break;
-              case 'row':
-                  
-                  break;
-              case 'column-reverse':
-                  resize1.top = mid.offsetHeight;
-                  break;
-              case 'column':
-                  resize1.top = left.offsetHeight;
-                  break;
-              default:
-                  break;
-          }
-          //颜色改变提醒
-          resize1.style.background = '#818181';
-          var startX = e.clientY;
-          // resize1[i].top = resize1[i].offsetTop;
-          // 鼠标拖动事件
-          document.onmousemove = function (e) {
-              var endX = e.clientY;
-              var moveLen = resize1.top + (endX - startX); // (endx-startx)=移动的距离。resize[i].left+移动的距离=左边区域最后的宽度
-              var maxT = box.clientHeight - resize1.offsetHeight; // 容器宽度 - 左边区域的宽度 = 右边区域的宽度
-              if (moveLen < 150) moveLen = 150; // 左边区域的最小宽度为32px
-              if (moveLen > maxT - 150) moveLen = maxT - 150; //右边区域最小宽度为150px
-
-              resize1.style.top = moveLen; // 设置左侧区域的宽度
-                  switch (flex) {
-                      case 'row-reverse':
-                         
-                          break;
-                      case 'row':
-                          
-                          break;
-                      case 'column-reverse':
-                          mid.style.height = moveLen + 10 + 'px';
-                          if(left){
-                              left.style.height = (box.clientHeight - moveLen ) + 'px';
-                          }
-                          break;
-                      case 'column':
-                          left.style.height = moveLen + 10 + 'px';
-                          if(mid){
-                              mid.style.height = (box.clientHeight - moveLen ) + 'px';
-                          }
-                          break;
-                      default:
-                          break;
-                  }
-             
-          };
-          // 鼠标松开事件
-          document.onmouseup = function (evt) {
-              //颜色恢复
-              resize1.style.background = '#d6d6d6';
-              document.onmousemove = null;
-              document.onmouseup = null;
-              resize1.releaseCapture && resize1.releaseCapture(); //当你不在需要继续获得鼠标消息就要应该调用ReleaseCapture()释放掉
-          };
-          resize1.setCapture && resize1.setCapture(); //该函数在属于当前线程的指定窗口里设置鼠标捕获
-          return false;
-        }
-};
-//下拉框懒加载
-const SelectLazyLoading=(el,binding)=>{
-   
-    let SELECT_DOM = el.querySelector(
-      ".el-select-dropdown .el-select-dropdown__wrap"
-    );
-    if(!SELECT_DOM){
-        return false
-    }
-    SELECT_DOM.addEventListener("scroll", function () {
-        // scrollHeight:当前所有选项的高度
-      // scrollTop:滚动的距离
-      // clientHeight:下拉框的高度
-      let condition = this.scrollHeight - this.scrollTop - 1 <= this.clientHeight;
-      if (condition) {
-        binding.value();
-      }
-    });
-}
-
-//块禁止样式
-const disabled = (el,binding)=>{
-    if(binding.value){
-        el.style.pointerEvents = 'none';
-        if(el.parentNode){
-            el.parentNode.style.cursor='not-allowed';
-        }
-        
-        el.style.color='#bbb'
-    }else{
-        el.style.pointerEvents = '';
-        if(el.parentNode){
-            el.parentNode.style.cursor='';
-        }
-        el.style.color=''
-    }
-}
-
-const directives = {
-    draggable,
-    dragControllerDiv,
-    dragControllerDiv1,
-    SelectLazyLoading,
-    disabled
-};
-// 这种写法可以批量注册指令
-export default {
-  install(Vue) {
-      Object.keys(directives).forEach((key) => {
-      Vue.directive(key, directives[key]);
-    });
-  },
-};

+ 0 - 105
src/utils/drag.js

@@ -1,105 +0,0 @@
-/**
- * 拖拽移动
- * @param  {elementObjct} bar 鼠标点击控制拖拽的元素
- * @param {elementObjct}  target 移动的元素
- * @param {function}  callback 移动后的回调
- */
- export function startDrag(bar, target, callback) {
-    var params = {
-      top: 0,
-      left: 0,
-      currentX: 0,
-      currentY: 0,
-      flag: false,
-      cWidth: 0,
-      cHeight: 0,
-      tWidth: 0,
-      tHeight: 0
-    };
-   
-    // 给拖动块添加样式
-    bar.style.cursor = 'move';
-   
-    // 获取相关CSS属性
-    // o是移动对象
-    // var getCss = function (o, key) {
-    //   return o.currentStyle ? o.currentStyle[key] :             document.defaultView.getComputedStyle(o, false)[key];
-    // };
-   
-    bar.onmousedown = function (event) {
-      // 按下时初始化params
-      var e = event ? event : window.event;
-      params = {
-        top: target.offsetTop,
-        left: target.offsetLeft,
-        currentX: e.clientX,
-        currentY: e.clientY,
-        flag: true,
-        cWidth: document.body.clientWidth,
-        cHeight: document.body.clientHeight,
-        tWidth: target.offsetWidth,
-        tHeight: target.offsetHeight
-      };
-   
-      // 给被拖动块初始化样式
-      target.style.margin = 0;
-      target.style.top = params.top + 'px';
-      target.style.left = params.left + 'px';
-   
-      if (!event) {
-        // 防止IE文字选中
-        bar.onselectstart = function () {
-          return false;
-        }
-      }
-   
-      document.onmousemove = function (event) {
-        // 防止文字选中
-        window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
-   
-        var e = event ? event : window.event;
-        if (params.flag) {
-          var nowX = e.clientX;
-          var nowY = e.clientY;
-          // 差异距离
-          var disX = nowX - params.currentX;
-          var disY = nowY - params.currentY;
-          // 最终移动位置
-          var zLeft = 0;
-          var zTop = 0;
-   
-          zLeft = parseInt(params.left) + disX;
-          // 限制X轴范围
-          if (zLeft <= -parseInt(params.tWidth / 2)) {
-            zLeft = -parseInt(params.tWidth / 2);
-          }
-          if (zLeft >= params.cWidth - parseInt(params.tWidth * 0.5)) {
-            zLeft = params.cWidth - parseInt(params.tWidth * 0.5);
-          }
-   
-          zTop = parseInt(params.top) + disY;
-          // 限制Y轴范围
-          if (zTop <= 0) {
-            zTop = 0;
-          }
-          if (zTop >= params.cHeight - parseInt(params.tHeight * 0.5)) {
-            zTop = params.cHeight - parseInt(params.tHeight * 0.5);
-          }
-   
-          // 执行移动
-          target.style.left = zLeft + 'px';
-          target.style.top = zTop + 'px';
-        }
-   
-        if (typeof callback == "function") {
-          callback(zLeft, zTop);
-        }
-      }
-   
-      document.onmouseup = function () {
-        params.flag = false;
-        document.onmousemove = null;
-        document.onmouseup = null;
-      };
-    };
-  }

+ 33 - 0
src/views/analyse/custom/components/Charts/Area/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CArea",
+  data() {
+    return {
+      id: 'area'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { xAxis, yAxis, series, grid } = this.get1AxisOption('line', { opacity: 1 })
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        grid: grid,
+        xAxis: xAxis,
+        yAxis: yAxis,
+        series: series
+      }
+    },
+  }
+}
+</script>

+ 34 - 0
src/views/analyse/custom/components/Charts/Bar/index.vue

@@ -0,0 +1,34 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CBar",
+  data() {
+    return {
+      id: 'bar',
+      barWidth: ''
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { xAxis, yAxis, series, grid } = this.get1AxisOption('bar', undefined)
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        grid: grid,
+        xAxis: xAxis,
+        yAxis: yAxis,
+        series: series
+      }
+    },
+  }
+}
+</script>

+ 37 - 0
src/views/analyse/custom/components/Charts/Column/index.vue

@@ -0,0 +1,37 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CColumn",
+  data() {
+    return {
+      id: 'column',
+      barWidth: ''
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { xAxis, yAxis, series, grid } = this.get1AxisOption('bar', undefined)
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        grid: grid,
+        xAxis: yAxis,
+        yAxis: {
+          ...xAxis,
+          inverse: true
+        },
+        series: series
+      }
+    },
+  }
+}
+</script>

+ 46 - 0
src/views/analyse/custom/components/Charts/Doughnut/index.vue

@@ -0,0 +1,46 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CDoughnut",
+  data() {
+    return {
+      id: 'doughnut'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        legend: {
+          type: this.form.setting.type2,
+          show: this.form.setting.legend,
+          textStyle: {
+            fontFamily: this.form.setting.fontFamily3,
+            color: this.form.setting.legendColor,
+            fontSize: this.form.setting.legendFontSize
+          },
+          ...this.getLegendLocation(),
+        },
+        series: [
+          {
+            name: '',
+            type: 'pie',
+            radius: ['40%', '70%'],
+            data: this.getSeriesData()
+          }
+        ]
+      }
+    },
+  }
+}
+</script>

+ 98 - 0
src/views/analyse/custom/components/Charts/Heat/index.vue

@@ -0,0 +1,98 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+import { random } from "@/utils";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CHeat",
+  data() {
+    return {
+      id: 'heat'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      if (this.form.schema.x.ptype === 2) {
+        this.dateTimeSort()
+      }
+      let data = [], x = this.selected.x, y = this.selected.y, max = 0
+      for (let i = 0; i < x.length; i++) {
+        for (let j = 0; j < y.length; j++) {
+          if (x[i] && y[j]) {
+            let val = this.getDataCount(x[i], y[j]) || undefined
+            data.push([i, j, val])
+            if (val > max) {
+              max = val
+            }
+          }
+        }
+      }
+      return {
+        tooltip: {
+          trigger: 'item',
+        },
+        grid: {
+          top: this.form.setting.gridTop + '%',
+          left: this.form.setting.gridLeft + '%',
+          right: this.form.setting.gridRight + '%',
+          bottom: this.form.setting.gridBottom + '%',
+          containLabel: true
+        },
+        xAxis: {
+          type: 'category',
+          name: this.getAxisName('title1'),
+          nameTextStyle: this.getNameTextStyle('x'),
+          nameLocation: this.form.setting.nameLocation,
+          axisLabel: this.getAxisLabel('category', 'x'),
+          data: x,
+          splitArea: {
+            show: true
+          }
+        },
+        yAxis: {
+          type: 'category',
+          name: this.getAxisName('title2'),
+          axisLabel: this.getAxisLabel('category', 'y'),
+          nameTextStyle: this.getNameTextStyle('y'),
+          nameLocation: this.form.setting.nameLocation2,
+          data: y,
+          splitArea: {
+            show: true
+          }
+        },
+        visualMap: {
+          min: 0,
+          max: max,
+          calculable: true,
+          orient: 'horizontal',
+          left: 'center',
+          show: false,
+          inRange: {
+            color: this.form.setting.config.color.map(item => item.color)
+          }
+        },
+        series: [
+          {
+            name: '',
+            type: 'heatmap',
+            data: data,
+            label: this.getDataLabel(),
+            emphasis: {
+              itemStyle: {
+                shadowBlur: 10,
+                shadowColor: 'rgba(0, 0, 0, 0.5)'
+              }
+            }
+          }
+        ]
+      }
+    },
+  }
+}
+</script>

+ 33 - 0
src/views/analyse/custom/components/Charts/Line/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CLine",
+  data() {
+    return {
+      id: 'line'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { xAxis, yAxis, series, grid } = this.get1AxisOption('line', undefined)
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        grid: grid,
+        xAxis: xAxis,
+        yAxis: yAxis,
+        series: series
+      }
+    },
+  }
+}
+</script>

+ 67 - 0
src/views/analyse/custom/components/Charts/Map/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOption } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOption],
+  name: "CMap",
+  data() {
+    return {
+      id: 'map',
+      map: {
+        name: '全球',
+        data: {}
+      }
+    }
+  },
+  async mounted() {
+    this.$api.getMapData({ path: `/map/${this.map.name}.json` }).then(response => {
+      this.map.data = response.data
+      this.$echarts.registerMap(this.map.name, this.map.data)
+      this.initChart(this.id, this.getOption())
+    })
+  },
+  methods: {
+    getOption() {
+      let max = 0, x = this.selected.x, data = [], nameMap = {}
+      for (let i = 0; i < x.length; i++) {
+        const value = this.getDataCount(x[i])
+        data.push({
+          name: x[i],
+          value: value
+        })
+        if (value > max) {
+          max = value
+        }
+      }
+      return {
+        visualMap: {
+          min: 0,
+          max: max,
+          calculable: true,
+          orient: 'horizontal',
+          left: 'center',
+          show: false,
+        },
+        series: [
+          {
+            name: '',
+            type: 'map',
+            map: this.map.name,
+            roam: true,
+            label: this.getDataLabel(),
+            itemStyle: {
+              borderWidth: 0
+            },
+            data: data,
+            nameMap: nameMap
+          }
+        ]
+      }
+    },
+  }
+}
+</script>

+ 67 - 0
src/views/analyse/custom/components/Charts/Map/index1.vue

@@ -0,0 +1,67 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOption } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOption],
+  name: "CMap1",
+  data() {
+    return {
+      id: 'map',
+      map: {
+        name: '中国',
+        data: {}
+      }
+    }
+  },
+  async mounted() {
+    this.$api.getMapData({ path: `/map/${this.map.name}.json` }).then(response => {
+      this.map.data = response.data
+      this.$echarts.registerMap(this.map.name, this.map.data)
+      this.initChart(this.id, this.getOption())
+    })
+  },
+  methods: {
+    getOption() {
+      let max = 0, x = this.selected.x, data = [], nameMap = {}
+      for (let i = 0; i < x.length; i++) {
+        const value = this.getDataCount(x[i])
+        data.push({
+          name: x[i],
+          value: value
+        })
+        if (value > max) {
+          max = value
+        }
+      }
+      return {
+        visualMap: {
+          min: 0,
+          max: max,
+          calculable: true,
+          show: false,
+          orient: 'horizontal',
+          left: 'center',
+        },
+        series: [
+          {
+            name: '',
+            type: 'map',
+            map: this.map.name,
+            roam: true,
+            label: this.getDataLabel(),
+            itemStyle: {
+              borderWidth: 0
+            },
+            data: data,
+            nameMap: nameMap
+          }
+        ]
+      }
+    },
+  }
+}
+</script>

+ 81 - 0
src/views/analyse/custom/components/Charts/Map/index2.vue

@@ -0,0 +1,81 @@
+<template>
+  <div style="position: relative">
+    <div v-for="map in maps">
+      <div style="position: absolute;right: 0;z-index: 100">
+        <el-button icon="el-icon-camera-solid" size="mini" @click="handleScreenshot(map.name, map.name)">截图</el-button>
+      </div>
+      <el-tabs v-model="active[map.name]">
+        <el-tab-pane :label="map.name" :name="map.name">
+          <div :id="map.name" :style="{ width: width, height: height }"></div>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<script>
+import { chartOption } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOption],
+  name: "CMap2",
+  data() {
+    return {
+      maps: [],
+      active: {}
+    }
+  },
+  async mounted() {
+    await Promise.all(this.selected.x.map(async (item) => {
+      const { data } = await this.$api.getMapData({ path: `/map/${item}.json` })
+      this.maps.push({
+        name: item,
+        map: data
+      })
+      this.$set(this.active, item, item)
+      this.$echarts.registerMap(item, data)
+    }))
+    this.maps.map(item => {
+      this.initChart(item.name, this.getOption(this.getDataCount(item.name), item.name))
+    })
+  },
+  methods: {
+    getOption(count, name) {
+      let max = 1, x = Object.keys(count || {}), data = [], nameMap = {}
+      for (let i = 0; i < x.length; i++) {
+        data.push({
+          name: x[i],
+          value: count[x[i]]
+        })
+        if (count[x[i]] > max) {
+          max = count[x[i]]
+        }
+      }
+      return {
+        visualMap: {
+          min: 0,
+          max: max,
+          calculable: true,
+          orient: 'horizontal',
+          left: 'center',
+          show: false,
+        },
+        series: [
+          {
+            name: '',
+            type: 'map',
+            map: name,
+            roam: true,
+            label: this.getDataLabel(),
+            itemStyle: {
+              borderWidth: 0
+            },
+            data: data,
+            nameMap: nameMap
+          }
+        ]
+      }
+    },
+  }
+}
+</script>

+ 34 - 0
src/views/analyse/custom/components/Charts/MultipleBar/index.vue

@@ -0,0 +1,34 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+export default {
+  mixins: [chartOptionMixins],
+  name: "CMultipleBar",
+  data() {
+    return {
+      id: 'multipleBar',
+      barWidth: ''
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { legend, xAxis, yAxis, series, grid } = this.get2AxisOption('bar', undefined)
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        legend,
+        grid: grid,
+        xAxis: xAxis,
+        yAxis: yAxis,
+        series: series
+      }
+    }
+  }
+}
+</script>

+ 35 - 0
src/views/analyse/custom/components/Charts/MultipleColumn/index.vue

@@ -0,0 +1,35 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+import { random } from "@/utils";
+export default {
+  mixins: [chartOptionMixins],
+  name: "CMultipleColumn",
+  data() {
+    return {
+      id: 'multipleColumn',
+      barWidth: ''
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { legend, xAxis, yAxis, series, grid } = this.get2AxisOption('bar', undefined)
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        legend,
+        grid: grid,
+        xAxis: yAxis,
+        yAxis: xAxis,
+        series: series
+      }
+    }
+  }
+}
+</script>

+ 33 - 0
src/views/analyse/custom/components/Charts/MultipleLine/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+export default {
+  mixins: [chartOptionMixins],
+  name: "CMultipleLine",
+  data() {
+    return {
+      id: 'multipleLine'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { legend, xAxis, yAxis, series, grid } = this.get2AxisOption('line', '')
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        legend,
+        grid: grid,
+        xAxis: xAxis,
+        yAxis: yAxis,
+        series: series
+      }
+    }
+  }
+}
+</script>

+ 45 - 0
src/views/analyse/custom/components/Charts/Pie/index.vue

@@ -0,0 +1,45 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CPie",
+  data() {
+    return {
+      id: 'pie'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        legend: {
+          type: this.form.setting.type2,
+          show: this.form.setting.legend,
+          textStyle: {
+            fontFamily: this.form.setting.fontFamily3,
+            color: this.form.setting.legendColor,
+            fontSize: this.form.setting.legendFontSize
+          },
+          ...this.getLegendLocation(),
+        },
+        series: [
+          {
+            name: '',
+            type: 'pie',
+            data: this.getSeriesData()
+          }
+        ]
+      }
+    },
+  }
+}
+</script>

+ 105 - 0
src/views/analyse/custom/components/Charts/Radar/index.vue

@@ -0,0 +1,105 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOption, chartOptionMixins } from "@/views/analyse/custom/mixins";
+import { random } from "@/utils";
+import { getLineDataArr } from "@/utils/chart";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CRadar",
+  data() {
+    return {
+      id: 'radar',
+      max: 0
+    }
+  },
+  mounted() {
+
+  },
+  methods: {
+    getSum(x) {
+      let sum = 0
+      for (let i = 0; i < this.selected.y.length; i++) {
+        sum += this.getDataCount(x, this.selected.y[i])
+      }
+      if (this.max < sum) {
+        this.max = sum
+      }
+      return this.max
+    },
+    getMax() {
+      let max = 0
+      for (let i = 0; i < this.selected.x.length; i++) {
+        let data = this.getDataCount(this.selected.x[i])
+        if (max < data) {
+          max = data
+        }
+      }
+      return max
+    },
+    getOption() {
+      const matrix = this.form.schema.y.field
+      const enable = this.form.setting.config.line.enable
+      let legend = matrix ? this.selected.y.map(item => item) : []
+      let color = matrix ? this.selected.y.map(item => this.getColor(item)) : undefined
+      let radar = this.selected.x.map(x => {
+        return {
+          name: x,
+          max: matrix ? this.getSum(x) : this.getMax()
+        }
+      })
+      let data = matrix ? this.selected.y.map(y => {
+        return {
+          value: this.selected.x.map(x => this.getDataCount(x, y)),
+          name: y,
+          label: this.getDataLabel()
+        }
+      }) : [{
+        value: this.selected.x.map(x => this.getDataCount(x)),
+        name: '总和',
+        lineStyle: {
+          color: this.getColor('主题颜色')
+        },
+        label: this.getDataLabel()
+      }]
+      if (matrix && enable) {
+        const name = this.form.setting.config.line.name
+        legend.push(name)
+        data.push({
+          value: getLineDataArr(this.form, this.selected, this.count),
+          name: name,
+          label: this.getDataLabel()
+        })
+        color.push(this.form.setting.config.line.color)
+      }
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        color: color,
+        legend: {
+          data: legend
+        },
+        radar: {
+          indicator: radar,
+          axisName: {
+            show: this.form.setting.show,
+            fontFamily: this.form.setting.fontFamily,
+            fontSize: this.form.setting.fontSize,
+            fontWeight: this.form.setting.fontWeight ? 'bold' : 'normal',
+            color: this.form.setting.fontColor,
+          },
+        },
+        series: [{
+          name: '',
+          type: 'radar',
+          data: data
+        }]
+      }
+    },
+  }
+}
+</script>

+ 85 - 0
src/views/analyse/custom/components/Charts/Radar/index2.vue

@@ -0,0 +1,85 @@
+<template>
+  <div style="position: relative">
+    <div v-for="item in selected.y">
+      <div style="position: absolute;right: 0;z-index: 100">
+        <el-button icon="el-icon-camera-solid" size="mini" @click="handleScreenshot(item, item)">截图</el-button>
+      </div>
+      <el-tabs v-model="active[item]">
+        <el-tab-pane :label="item" :name="item">
+          <div :id="item" :style="{ width: width, height: height }"></div>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<script>
+import { chartOption, chartOptionMixins } from "@/views/analyse/custom/mixins";
+import { random } from "@/utils";
+
+export default {
+  mixins: [chartOption],
+  name: "CRadar2",
+  data() {
+    return {
+      active: {},
+      max: 0
+    }
+  },
+  mounted() {
+    this.selected.y.map(item => {
+      this.$set(this.active, item, item)
+      this.$nextTick(() => {
+        this.initChart(item, this.getOption(item))
+      })
+    })
+  },
+  methods: {
+    getSum(x) {
+      let sum = 0
+      for (let i = 0; i < this.selected.y.length; i++) {
+        sum += this.getDataCount(x, this.selected.y[i])
+      }
+      if (this.max < sum) {
+        this.max = sum
+      }
+      return this.max
+    },
+    getOption(y) {
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        legend: {},
+        radar: {
+          indicator: this.selected.x.map(x => {
+            return {
+              name: x,
+              max: this.getSum(x)
+            }
+          }),
+          axisName: {
+            show: this.form.setting.show,
+            fontFamily: this.form.setting.fontFamily,
+            fontSize: this.form.setting.fontSize,
+            fontWeight: this.form.setting.fontWeight ? 'bold' : 'normal',
+            color: this.form.setting.fontColor,
+          },
+        },
+        series: [{
+          name: y,
+          type: 'radar',
+          data: [{
+            value: this.selected.x.map(x => this.getDataCount(x, y)),
+            name: '',
+            lineStyle: {
+              color: this.getColor(y)
+            },
+            label: this.getDataLabel()
+          }]
+        }]
+      }
+    },
+  }
+}
+</script>

+ 51 - 0
src/views/analyse/custom/components/Charts/Sankey/index.vue

@@ -0,0 +1,51 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+import { random } from "@/utils";
+export default {
+  mixins: [chartOptionMixins],
+  name: "CSankey",
+  data() {
+    return {
+      id: 'sankey'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let links = [], x = this.selected.x, y = this.selected.y
+      for (let i = 0; i < y.length; i++) {
+        for (let j = 0; j < x.length; j++) {
+          links.push({
+            source: x[j],
+            target: y[i],
+            value: this.getDataCount(x[j], y[i])
+          })
+        }
+      }
+      return {
+        series: {
+          type: 'sankey',
+          layout: 'none',
+          emphasis: {
+            focus: 'adjacency'
+          },
+          data: [...x, ...y].map(item => {
+            return {
+              name: item,
+              itemStyle: {
+                color: this.getColor(item),
+              }
+            }
+          }),
+          links: links
+        }
+      }
+    }
+  }
+}
+</script>

+ 111 - 0
src/views/analyse/custom/components/Charts/Scatter/index.vue

@@ -0,0 +1,111 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+import { random } from "@/utils";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CScatter",
+  data() {
+    return {
+      id: 'scatter',
+      size: 0,
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      if (this.form.schema.x.ptype === 2) {
+        this.dateTimeSort()
+      }
+      let data = [], x = ['', ...this.selected.x, ''], y = ['', ...this.selected.y, '']
+      for (let i = 0; i < x.length; i++) {
+        for (let j = 0; j < y.length; j++) {
+          if (x[i] && y[j]) {
+            data.push([i, j, this.getDataCount(x[i], y[j])])
+          }
+        }
+      }
+      const symbolSize = this.getSymbolSize(data)
+      return {
+        grid: {
+          top: this.form.setting.gridTop + '%',
+          left: this.form.setting.gridLeft + '%',
+          right: this.form.setting.gridRight + '%',
+          bottom: this.form.setting.gridBottom + '%',
+          containLabel: true
+        },
+        xAxis: [
+          {
+            type: 'category',
+            boundaryGap: false,
+            name: this.form.setting.title1,
+            nameTextStyle: this.getNameTextStyle('x'),
+            nameLocation: this.form.setting.nameLocation,
+            scale: true,
+            data: x,
+            axisLabel: this.getAxisLabel('category', 'x'),
+            splitLine: {
+              show: this.form.setting.splitLine
+            }
+          }
+        ],
+        yAxis: [
+          {
+            type: 'category',
+            boundaryGap: false,
+            name: this.form.setting.title2,
+            nameTextStyle: this.getNameTextStyle('y'),
+            nameLocation: this.form.setting.nameLocation2,
+            scale: true,
+            data: y,
+            axisLabel: this.getAxisLabel('category', 'y'),
+            splitLine: {
+              show: this.form.setting.splitLine2
+            }
+          }
+        ],
+        series: [
+          {
+            name: '',
+            type: 'scatter',
+            emphasis: {
+              focus: 'series'
+            },
+            symbolSize: (val) => {
+              return symbolSize[val[2]]
+            },
+            itemStyle: {
+              color: (val) => {
+                const data = val.value[2]
+                const color = this.form.setting.config.table.find(item => data >= item.min && data <= item.max)
+                if (color) {
+                  return color.color
+                }
+                return '#5470c6'
+              }
+            },
+            label: this.getDataLabel(),
+            data: data,
+          },
+        ]
+      }
+    },
+    getSymbolSize(dataList) {
+      const data = dataList.map(item => item[2]).sort((a, b) => {
+        return b - a
+      })
+      const maxVal = data[0]
+      let symbolSize = {}
+      data.map(item => {
+        symbolSize[item] = Math.round(item / maxVal * 100)
+      })
+      return symbolSize
+    }
+  }
+}
+</script>

+ 34 - 0
src/views/analyse/custom/components/Charts/StackedBar/index.vue

@@ -0,0 +1,34 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+export default {
+  mixins: [chartOptionMixins],
+  name: "CStackedBar",
+  data() {
+    return {
+      id: 'stackedBar',
+      barWidth: ''
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { legend, xAxis, yAxis, series, grid } = this.get2AxisOption('bar', 'Ad')
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        legend,
+        grid: grid,
+        xAxis: xAxis,
+        yAxis: yAxis,
+        series: series
+      }
+    }
+  }
+}
+</script>

+ 35 - 0
src/views/analyse/custom/components/Charts/StackedColumn/index.vue

@@ -0,0 +1,35 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+import { random } from "@/utils";
+export default {
+  mixins: [chartOptionMixins],
+  name: "CStackedColumn",
+  data() {
+    return {
+      id: 'stackedColumn',
+      barWidth: ''
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let { legend, xAxis, yAxis, series, grid } = this.get2AxisOption('bar', 'total')
+      return {
+        tooltip: {
+          trigger: 'item'
+        },
+        legend,
+        grid: grid,
+        xAxis: yAxis,
+        yAxis: xAxis,
+        series: series
+      }
+    }
+  }
+}
+</script>

+ 47 - 0
src/views/analyse/custom/components/Charts/Sunburst/index.vue

@@ -0,0 +1,47 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CSunburst",
+  data() {
+    return {
+      id: 'sunburst'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let data = []
+      this.selected.x.map(x => {
+        let value = {
+          name: x,
+          children: []
+        }
+        this.selected.y.map(y => {
+          value.children.push({
+            name: y + ' (' + this.getDataCount(x, y) + ')',
+            value: this.getDataCount(x, y),
+            itemStyle: {
+              color: this.getColor(y)
+            }
+          })
+        })
+        data.push(value)
+      })
+      return {
+        series: {
+          type: 'sunburst',
+          data: data,
+          label: this.getDataLabel()
+        }
+      }
+    },
+  }
+}
+</script>

+ 21 - 0
src/views/analyse/custom/components/Charts/Table/Column.vue

@@ -0,0 +1,21 @@
+<template>
+  <el-table-column :label="column.name" :prop="String(column.id)" align="center">
+    <template v-for="(item, index) in column.children">
+      <table-column v-if="item.children && item.children.length" :column="item"></table-column>
+      <el-table-column v-else :label="item.name" :prop="String(item.id)" align="center"></el-table-column>
+    </template>
+  </el-table-column>
+</template>
+
+<script>
+export default {
+  name: "TableColumn",
+  props: {
+    column: Object
+  }
+}
+</script>
+
+<style lang="less">
+
+</style>

+ 265 - 0
src/views/analyse/custom/components/Charts/Table/index.vue

@@ -0,0 +1,265 @@
+<template>
+  <div :id="id">
+    <el-table :data="tableData" :span-method="objectSpanMethod" :cell-style="cellStyle" border header-row-class-name="custom-table-header" style="margin-bottom: 40px;">
+      <el-table-column v-if="showIndex" type="index" label="序号" align="center"></el-table-column>
+      <el-table-column v-for="col in tableCol" :prop="col.prop" :label="col.label" align="center" show-overflow-tooltip></el-table-column>
+      <table-column v-if="treeType !== 0" v-for="col in source" :column="col" />
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { chartOption, customPage } from "@/views/analyse/custom/mixins";
+import { mapGetters } from "vuex";
+import { findChildren, getTreeDataList, getTreeLastChildren } from "@/utils";
+import TableColumn from "./Column";
+import { getXAxisName } from "@/utils/chart";
+
+export default {
+  name: "CTable",
+  components: { TableColumn },
+  mixins: [chartOption],
+  data() {
+    return {
+      id: 'cTable',
+      tableData: [],
+      tableCol: [],
+      showIndex: false,
+      isLevel: 0,
+      maxLevel: 0,
+      treeType: 0,
+      rowspan: {},
+      source: [],
+      number: ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'],
+      tableConfig: []
+    }
+  },
+  computed: {
+    ...mapGetters(['patentField'])
+  },
+  mounted() {
+    if (this.form.schema.x.type === 6 || this.form.schema.y.type === 6) {
+      this.showIndex = false
+      this.initTree(this.form.schema.x.type === 6, this.form.schema.y.type === 6)
+    } else {
+      this.showIndex = true
+      this.initData()
+    }
+    this.tableConfig = JSON.parse(JSON.stringify(this.form.setting.config.table))
+  },
+  methods: {
+    cellStyle({ row, column, rowIndex, columnIndex }) {
+      const { property, label, type } = column
+      let color = ''
+      let level = ['level-1', 'level-2', 'level-3', 'level-4', 'level-5', 'level-6', 'level-7', 'level-8', 'level-9', 'level-10']
+      if (['name', 'index', ...level].indexOf(property) === -1 && type === 'default') {
+        const data = row[property]
+        this.tableConfig.map(item => {
+          if (data >= item.min && data <= item.max) {
+            color = item.color
+          }
+        })
+      }
+      return {
+        background: color,
+        color: '#000'
+      }
+    },
+    getTreeLastChildren(dimension) {
+      const tree = getTreeDataList(this.form.source[dimension], [])
+      const data = tree.filter(item => this.treeKey[dimension].indexOf(item.id) !== -1)
+      let arr = data.filter(item =>  !item.children || !item.children.length)
+      data.map(item => arr.push(...getTreeLastChildren(item.children || [], [])))
+      return arr.map(item => item.id)
+    },
+    getTreeColumnSource() {
+      const tree = getTreeDataList(this.form.source.y, [])
+      return tree.filter(item => this.treeKey.y.indexOf(item.id) !== -1)
+    },
+    getTreeSource(tree1, tree2) {
+      let x, y, source
+      if (tree1 && !tree2) {
+        this.treeType = 0
+        x = this.treeKey.x
+        y = this.selected.y
+        source = JSON.parse(JSON.stringify(this.form.source.x))
+      }
+      if (!tree1 && tree2) {
+        this.treeType = 1
+        x = this.selected.x
+        y = this.getTreeLastChildren('y')
+        source = []
+        this.source = this.getTreeColumnSource()
+      }
+      if (tree1 && tree2) {
+        this.treeType = 2
+        x = this.treeKey.x
+        y = this.getTreeLastChildren('y')
+        source = JSON.parse(JSON.stringify(this.form.source.x))
+        this.source = this.getTreeColumnSource()
+      }
+      return { x, y, source }
+    },
+    initTree(tree1, tree2) {
+      const matrix = this.form.schema.y.field
+      const { x, y, source } = this.getTreeSource(tree1, tree2)
+      if (!x.length) {
+        return false
+      }
+      if (this.treeType === 0 || this.treeType === 2) {
+        let column = []
+        let tree = getTreeDataList(source, [])
+        tree.map(item => item.path = item.path.split('/').map(p => parseInt(p, 0)))
+        this.isLevel = tree.find(item => item.id === x[0]).level || 0
+        const isTree = tree.filter(item => x.indexOf(item.id) !== -1)
+        const maxLevel = Math.max(...getTreeDataList(isTree, []).map(item => item.level))
+        const lastChildren = this.findLastChildren(isTree, [])
+        let index = 1
+        for (let i = this.isLevel; i <= maxLevel; i++) {
+          this.tableCol.push({
+            prop: `level-${i}`,
+            label: `${this.number[index++]}级`
+          })
+        }
+        this.tableCol.map(col => {
+          const level = parseInt(col.prop.replaceAll('level-', ''))
+          const data = getTreeDataList(isTree, [])
+          this.$set(this.rowspan, col.prop, this.getRowspan(data, level, lastChildren))
+        })
+        if (matrix) {
+          if (this.treeType === 2) {
+
+          } else {
+            y.map(item => {
+              this.tableCol.push({
+                prop: item,
+                label: item
+              })
+            })
+          }
+          column = [...y]
+        } else {
+          this.tableCol.push({
+            prop: 'count',
+            label: '数量'
+          })
+          column = ['count']
+        }
+        this.treeRecFun(isTree, {}, column)
+      } else {
+        this.tableCol.push({
+          prop: 'name',
+          label: getXAxisName(this.patentField, this.form.schema.x.field, this.form.schema.x.expand)
+        })
+        x.map(x => {
+          let data = {
+            name: x
+          }
+          y.map(y => {
+            data[y] = this.getDataCount(x, y, false)
+          })
+          this.tableData.push(data)
+        })
+      }
+      this.initTreeData()
+    },
+    initTreeData() {
+
+    },
+    getRowspan(tree, level, lastChildren) {
+      let arr = []
+      let index = 0
+      const data = tree.filter(item => item.level === level)
+      arr = lastChildren.map(item => { return 1 })
+      for (let i = 0; i < data.length; i++) {
+        const children = this.findLastChildren(data[i].children || [], [])
+        arr[index] = children.length
+        if (arr[index]) {
+          for (let j = index + 1; j < (arr[index] + index); j++) {
+            arr[j] = 0
+          }
+          index += children.length
+        } else {
+          arr[index++] = 1
+        }
+      }
+      return arr
+    },
+    findLastChildren(treeList, arr) {
+      for (let i = 0; i < treeList.length; i++) {
+        if (treeList[i].children && treeList[i].children.length) {
+          this.findLastChildren(treeList[i].children, arr)
+        } else {
+          arr.push(treeList[i])
+        }
+      }
+      return arr
+    },
+    treeRecFun(treeData, data, column) {
+      treeData.forEach(tree => {
+        let key = `level-${tree.level}`
+        data[key] = tree.name
+        if (tree.children && tree.children.length) {
+          this.treeRecFun(tree.children, data, column)
+        } else {
+          let c = {}
+          column.map(item => {
+            c[item] = this.getDataCount(tree.id, item === 'count' ? undefined : item, false)
+          })
+          this.tableData.push({ ...data, ...c })
+          data[key] = null
+        }
+      })
+    },
+    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
+      if (!this.showIndex) {
+        const key = `level-${columnIndex + this.isLevel}`
+        const rowspan = this.rowspan[key]
+        if (rowspan !== undefined) {
+          return { rowspan: rowspan[rowIndex], colspan: rowspan[rowIndex] ? 1 : 0 }
+        } else {
+          return { rowspan: 1, colspan: 1 }
+        }
+      }
+    },
+    initData() {
+      const matrix = this.form.schema.y.field
+      if (matrix) {
+        this.tableCol.push({
+          prop: 'name',
+          label: getXAxisName(this.patentField, this.form.schema.x.field, this.form.schema.x.expand)
+        })
+        this.selected.y.map(y => {
+          this.tableCol.push({
+            prop: y,
+            label: y
+          })
+        })
+        this.selected.x.map(x => {
+          let data = {
+            name: x
+          }
+          this.selected.y.map(y => {
+            data[y] = this.getDataCount(x, y)
+          })
+          this.tableData.push(data)
+        })
+      } else {
+        this.tableCol.push({
+          prop: 'name',
+          label: getXAxisName(this.patentField, this.form.schema.x.field, this.form.schema.x.expand)
+        }, {
+          prop: 'count',
+          label: '数量'
+        })
+        for (let x of this.selected.x) {
+          this.tableData.push({
+            name: x,
+            count: this.getDataCount(x)
+          })
+        }
+      }
+    },
+  }
+}
+</script>

+ 47 - 0
src/views/analyse/custom/components/Charts/Tree/index.vue

@@ -0,0 +1,47 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+import { mapGetters } from "vuex";
+import { getTreeDataList } from "@/utils";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CTree",
+  data() {
+    return {
+      id: 'tree'
+    }
+  },
+  computed: {
+    ...mapGetters(['patentField'])
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      let tree = []
+      let data = JSON.parse(JSON.stringify(this.form.source.x))
+      const field = this.patentField.find(item => item.id === this.form.schema.x.field)
+      let treeData = getTreeDataList(data, [])
+      treeData.map(item => {
+        item.name = `${item.name} (${this.getDataCount(item.id)})`
+      })
+      tree = [{
+        name: field.name,
+        id: field.id,
+        children: data
+      }]
+      return {
+        series: {
+          type: 'tree',
+          data: tree,
+          label: this.getDataLabel()
+        }
+      }
+    },
+  }
+}
+</script>

+ 89 - 0
src/views/analyse/custom/components/Charts/Treemap/index.vue

@@ -0,0 +1,89 @@
+<template>
+  <div :id="id" :style="{ width: width, height: height }"></div>
+</template>
+
+<script>
+import { chartOptionMixins } from "@/views/analyse/custom/mixins";
+
+export default {
+  mixins: [chartOptionMixins],
+  name: "CTreemap",
+  data() {
+    return {
+      id: 'treemap'
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    getOption() {
+      const matrix = this.form.schema.y.field
+      let data = []
+      if (matrix) {
+        this.selected.x.map(x => {
+          let value = {
+            name: x,
+            value: this.getDataCount(x),
+            itemStyle: {
+              color: this.getColor(x)
+            },
+            children: []
+          }
+          this.selected.y.map(y => {
+            value.children.push({
+              name: y,
+              value: this.getDataCount(x, y),
+              children: []
+            })
+          })
+          data.push(value)
+        })
+      } else {
+        this.selected.x.map(x => {
+          data.push({
+            name: x,
+            value: this.getDataCount(x),
+            itemStyle: {
+              color: this.getColor(x)
+            },
+            children: []
+          })
+        })
+      }
+      return {
+        series: [{
+          breadcrumb: {
+            show: false
+          },
+          type: 'treemap',
+          itemStyle: {
+            borderColor: '#fff'
+          },
+          label: this.getDataLabel(),
+          levels: [
+            {
+              itemStyle: {
+                borderWidth: 0,
+                gapWidth: 5
+              }
+            },
+            {
+              itemStyle: {
+                gapWidth: 1
+              }
+            },
+            {
+              colorSaturation: [0.35, 0.5],
+              itemStyle: {
+                gapWidth: 1,
+                borderColorSaturation: 0.6
+              }
+            }
+          ],
+          data: data,
+        }]
+      }
+    },
+  }
+}
+</script>

+ 51 - 0
src/views/analyse/custom/components/Charts/index.js

@@ -0,0 +1,51 @@
+import Line from "./Line";
+import Bar from "./Bar";
+import Area from "./Area";
+import Column from "./Column";
+import MultipleLine from "./MultipleLine";
+import StackedBar from "./StackedBar";
+import MultipleBar from "./MultipleBar";
+import StackedColumn from "./StackedColumn";
+import MultipleColumn from "./MultipleColumn";
+import Doughnut from "./Doughnut";
+import Pie from "./Pie";
+import Radar from "./Radar";
+import Radar2 from "./Radar/index2";
+import Scatter from "./Scatter";
+import Heat from "./Heat";
+import Sankey from "./Sankey";
+import Sunburst from "./Sunburst";
+import Treemap from "./Treemap";
+import Tree from "./Tree";
+import Map from "./Map";
+import Map1 from "./Map/index1";
+import Map2 from "./Map/index2";
+import Table from "./Table";
+
+export default {
+  install(Vue, options) {
+    Vue.component(Line.name, Line);
+    Vue.component(Bar.name, Bar);
+    Vue.component(Area.name, Area);
+    Vue.component(Column.name, Column);
+    Vue.component(MultipleLine.name, MultipleLine);
+    Vue.component(StackedBar.name, StackedBar);
+    Vue.component(MultipleBar.name, MultipleBar);
+    Vue.component(StackedColumn.name, StackedColumn);
+    Vue.component(MultipleColumn.name, MultipleColumn);
+    Vue.component(Doughnut.name, Doughnut);
+    Vue.component(Pie.name, Pie);
+    Vue.component(Radar.name, Radar);
+    Vue.component(Radar2.name, Radar2);
+    Vue.component(Scatter.name, Scatter);
+    Vue.component(Heat.name, Heat);
+    Vue.component(Sankey.name, Sankey);
+    Vue.component(Sunburst.name, Sunburst);
+    Vue.component(Treemap.name, Treemap);
+    Vue.component(Tree.name, Tree);
+    Vue.component(Map.name, Map);
+    Vue.component(Map1.name, Map1);
+    Vue.component(Map2.name, Map2);
+    Vue.component(Table.name, Table);
+  }
+}

+ 56 - 0
src/views/analyse/custom/components/Charts/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <div v-if="refresh">
+    <component :is="chartType.component" :width="width" :height="height"></component>
+  </div>
+</template>
+
+<script>
+import { customPage } from "../../mixins";
+
+export default {
+  mixins: [customPage],
+  props: {
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '500px'
+    },
+  },
+  data() {
+    return {
+      refresh: false,
+      chartType: {},
+    }
+  },
+  mounted() {
+    this.refreshChart()
+  },
+  methods: {
+    initData() {
+      if (!this.form.setting.type) {
+        return false
+      }
+      this.chartType = this.$constants.chartType.find(item => item.value === this.form.setting.type)
+      if (this.chartType.type === 1 && this.form.schema.y.field) {
+        return false
+      }
+      this.$store.commit('SET_MY_CHART', null)
+      return true
+    },
+    refreshChart() {
+      this.refresh = false
+      const ret = this.initData()
+      this.$nextTick(() => {
+        this.refresh = ret
+      })
+    },
+  }
+}
+</script>
+
+<style lang="less">
+
+</style>

+ 50 - 0
src/views/analyse/custom/components/Tabs/Data.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="custom-analyse-data">
+    <el-collapse v-model="activeNames">
+      <el-collapse-item title="一维数据" name="2">
+        <data-item dimension="x"/>
+      </el-collapse-item>
+      <el-collapse-item title="二维数据" name="3">
+        <data-item dimension="y"/>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<script>
+import mixins from "./mixins";
+import DataItem from "./DataItem";
+export default {
+  mixins: [mixins],
+  components: {
+    DataItem
+  },
+  data() {
+    return {
+      activeNames: ['2', '3']
+    }
+  },
+  mounted() {
+  }
+}
+</script>
+
+<style lang="scss">
+.custom-analyse-data {
+  .el-collapse {
+    border-top: transparent !important;
+  }
+  .el-collapse-item__header {
+    font-weight: bold !important;
+    padding: 0 20px !important;
+    border-bottom: 1px solid #EBEEF5 !important;
+  }
+  .el-form-item {
+    padding: 0 15px;
+    margin-bottom: 0 !important;
+  }
+  .el-collapse {
+    border-top: 0 !important;
+  }
+}
+</style>

+ 599 - 0
src/views/analyse/custom/components/Tabs/DataItem.vue

@@ -0,0 +1,599 @@
+<template>
+  <div class="select-box" v-loading="selectedLoad[dimension]">
+    <el-cascader v-model="form.schema[dimension].field" :options="options[dimension]" :props="props" @change="onChange(true, true)" :show-all-levels="false" clearable style="width: 100%"></el-cascader>
+    <el-select v-if="expandField[dimension].length" v-model="form.schema[dimension].expand" @change="onChange3(dimension)" placeholder="请选择拓展字段" style="width: 100%;margin-top: 15px;">
+      <el-option v-for="item in expandField[dimension]" :value="item.id" :label="item.name"></el-option>
+    </el-select>
+    <div>
+      <div style="text-align: right;height: 40px;">
+        <template v-if="form.schema[dimension].type !== 6">
+          <el-button v-if="form.schema[dimension].ptype === 3 || form.schema[dimension].ptype === 4" type="text" @click="handleEdit">编辑</el-button>
+          <template v-else-if="form.schema[dimension].ptype === 1 || form.schema[dimension].ptype === 2">
+            <el-dropdown size="medium" @command="handleCommand">
+              <el-button type="text" class="el-dropdown-link">
+                <span v-if="form.schema[dimension].num !== allData">{{ form.schema[dimension].ptype === 1 ? `Top` : `最近` }} {{ form.schema[dimension].num }}</span>
+                <span v-else>全部数据</span>
+                <i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item v-for="i in 5" :command="i * 10"><span>{{ form.schema[dimension].ptype === 1 ? `Top` : `最近` }}</span> {{ i * 10 }}</el-dropdown-item>
+                <el-dropdown-item :command="allData">全部数据</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </template>
+        </template>
+        <template v-else>
+          <el-button type="text" @click="handleSelectAll">全选</el-button>
+          <el-button type="text" @click="handleCancelAll">取消</el-button>
+        </template>
+      </div>
+      <el-container class="selected-list">
+        <template v-if="form.schema[dimension].ptype !== 0">
+          <el-main :style="{ padding: form.schema[dimension].type === 6 ? '15px 0' : '15px' }">
+            <template v-if="form.schema[dimension].type !== 6">
+              <el-checkbox-group v-model="selected[dimension]">
+                <el-row v-for="item in form.source[dimension]">
+                  <el-checkbox :label="item.name">{{ item.name }}</el-checkbox>
+                </el-row>
+              </el-checkbox-group>
+            </template>
+            <template v-else>
+              <el-tree ref="tree" :data="form.source[dimension]" check-strictly :default-checked-keys="treeKey[dimension]" @check-change="checkChange" show-checkbox node-key="id" :props="defaultProps"></el-tree>
+            </template>
+          </el-main>
+        </template>
+      </el-container>
+    </div>
+    <el-dialog title="编辑" :visible.sync="dialogVisible" width="700px" append-to-body>
+      <div class="sc-form-table">
+        <el-table :data="copyData" border>
+          <el-table-column type="index" width="50" fixed="left">
+            <template slot="header">
+              <el-button type="primary" icon="el-icon-plus" size="mini" circle @click="rowAdd()"></el-button>
+            </template>
+            <template slot-scope="scope">
+              <div class="sc-form-table-handle">
+                <span>{{scope.$index + 1}}</span>
+                <el-button type="danger" icon="el-icon-delete" size="mini" plain circle @click="rowDel(scope.row, scope.$index)"></el-button>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="名称" align="center" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <el-input v-model="scope.row.name" size="small"></el-input>
+            </template>
+          </el-table-column>
+          <el-table-column label="开始" align="center" width="200">
+            <template slot-scope="scope">
+              <div v-if="form.schema[dimension].ptype === 3">
+                <el-select v-model="scope.row.min.operator" size="small" placeholder="" style="width: 60px;">
+                  <el-option :value="0" label=">"></el-option>
+                  <el-option :value="1" label="≥"></el-option>
+                </el-select>
+                <el-input v-model="scope.row.min.value" size="small" style="width: 100px;padding-left: 10px;"></el-input>
+              </div>
+              <div v-else-if="form.schema[dimension].ptype === 4">
+                <el-date-picker v-model="scope.row.startTime" value-format="yyyy-MM-dd" size="small" type="date" placeholder="选择开始日期" style="width: 100%;"></el-date-picker>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="结束" align="center" width="200">
+            <template slot-scope="scope">
+              <div v-if="form.schema[dimension].ptype === 3">
+                <el-select v-model="scope.row.max.operator" size="small" placeholder="" style="width: 60px;">
+                  <el-option :value="2" label="<"></el-option>
+                  <el-option :value="3" label="≤"></el-option>
+                </el-select>
+                <el-input v-model="scope.row.max.value" size="small" style="width: 100px;padding-left: 10px;"></el-input>
+              </div>
+              <div v-else-if="form.schema[dimension].ptype === 4">
+                <el-date-picker v-model="scope.row.endTime" value-format="yyyy-MM-dd" size="small" type="date" placeholder="选择结束日期" style="width: 100%;"></el-date-picker>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="cancel">取 消</el-button>
+        <el-button type="primary" @click="handleConfirm" :loading="loadingBtn">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { customPage } from "@/views/analyse/custom/mixins";
+import { mapGetters } from "vuex";
+import { findChildren, getTreeDataList } from "@/utils";
+import { getSourceName, getTreeNameByIds } from "@/utils/chart";
+
+export default {
+  mixins: [customPage],
+  props: ['dimension'],
+  data() {
+    return {
+      allData: 9999999,
+      dialogVisible: false,
+      loadingBtn: false,
+      copyData: [],
+      parentId: -1,
+      props: {
+        expandTrigger: 'click',
+        value: 'id',
+        label: 'name',
+        emitPath: false
+      },
+      expandField: {
+        x: [],
+        y: [],
+      },
+      options: {
+        x: [],
+        y: [],
+      },
+      typeList: {
+        '90': '分类号',
+        '91': '公司/人',
+        '92': '日期',
+        '93': '区域',
+        '94': '引用/同族',
+        '95': '其他',
+        '96': '自定义字段',
+      },
+      defaultProps: {
+        children: 'children',
+        label: 'name'
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['projectId', 'userinfo', 'customField', 'patentField'])
+  },
+  watch: {
+    selected: {
+      deep: true,
+      handler(n, o) {
+        if (this.form.schema[this.dimension].type !== 6) {
+          this.form.source[this.dimension].map(item => {
+            item.selected = this.selected[this.dimension].indexOf(item.name) !== -1
+          })
+          this.$store.dispatch('getItemSettingColor', [])
+        }
+      }
+    }
+  },
+  mounted() {
+    this.onChange(false, false)
+    if (this.form.schema[this.dimension].type === 6) {
+      let tree = getTreeDataList(this.form.source[this.dimension], [])
+      if (this.treeKey[this.dimension].length) {
+        let data = tree.find(item => item.id === this.treeKey[this.dimension][0])
+        this.parentId = data ? data.parentId : -1
+        this.setChildren(this.form.source[this.dimension])
+      }
+    }
+    this.initOptions()
+  },
+  methods: {
+    getSourceName() {
+      switch (this.dimension) {
+        case 'x':
+          this.form.setting.title1 = getSourceName(this.form.schema.x.field, this.form.schema.x.expand, this.form.schema.x.num)
+          break
+        case 'y':
+          this.form.setting.title2 = getSourceName(this.form.schema.y.field, this.form.schema.y.expand, this.form.schema.y.num)
+          break
+      }
+    },
+    initOptions() {
+      for (let key in this.options) {
+        let data = []
+        for (let type in this.typeList) {
+          let children = {
+            id: type,
+            name: this.typeList[type],
+            children: []
+          }
+          if (type === '96') {
+            children.children = this.patentField.filter(item => item.type < 90 || item.type === 96)
+          } else {
+            children.children = this.patentField.filter(item => String(item.type) === type)
+          }
+          data.push(children)
+        }
+        this.$set(this.options, key, data)
+      }
+    },
+    updateSelected() {
+      this.selected[this.dimension] = this.form.source[this.dimension].slice(0, this.form.schema[this.dimension].num).map(item => item.name)
+      this.getSourceName()
+    },
+    async handleCommand(command) {
+      this.form.schema[this.dimension].num = command
+      await this.$store.dispatch('getSourceDataList', this.dimension)
+      this.updateSelected()
+    },
+    async onChange(option, reset) {
+      const field = this.patentField.find(item => item.id === this.form.schema[this.dimension].field)
+      if (field) {
+        this.$set(this.expandField, this.dimension, field.expand || [])
+      } else {
+        this.$set(this.form.schema[this.dimension], 'field', null)
+        this.$set(this.expandField, this.dimension, [])
+      }
+      if (option && !field) {
+        this.$store.commit('SET_RELOAD_DATA', true)
+      }
+      if (reset) {
+        this.$set(this.form.schema[this.dimension], 'expand', null)
+        await this.onChange3(this.dimension)
+      }
+    },
+    getSchemaType() {
+      let ptype = 0, type = 0
+      const field = this.patentField.find(item => item.id === this.form.schema[this.dimension].field)
+      if (field) {
+        type = field.type
+        if (field.ptype === 0 && this.form.schema[this.dimension].expand) {
+          const expand = field.expand.find(item => item.id === this.form.schema[this.dimension].expand)
+          ptype = expand.ptype
+        } else {
+          ptype = field.ptype
+        }
+      } else {
+        ptype = 0
+      }
+      this.$set(this.form.schema[this.dimension], 'type', type)
+      this.$set(this.form.schema[this.dimension], 'ptype', ptype)
+    },
+    async onChange3(dimension) {
+      this.copyData = []
+      this.getSchemaType()
+      this.$store.commit('SET_CHANGE_NUM')
+      if (this.form.schema[dimension].field) {
+        await this.$store.dispatch('getSourceDataList', dimension)
+      } else {
+        this.form.source[dimension] = []
+      }
+      if (this.form.source[dimension].length === 0) {
+        switch (this.form.schema[dimension].field) {
+          case 32:
+            this.form.source[dimension] = [{
+              name: '0',
+              min: {
+                operator: 1,
+                value: '0'
+              },
+              max: {
+                operator: 3,
+                value: '0'
+              },
+              selected: true
+            }, {
+              name: '1-5',
+              min: {
+                operator: 1,
+                value: '1'
+              },
+              max: {
+                operator: 3,
+                value: '5'
+              },
+              selected: true
+            }, {
+              name: '6-10',
+              min: {
+                operator: 1,
+                value: '6'
+              },
+              max: {
+                operator: 3,
+                value: '10'
+              },
+              selected: true
+            }, {
+              name: '11-20',
+              min: {
+                operator: 1,
+                value: '11'
+              },
+              max: {
+                operator: 3,
+                value: '20'
+              },
+              selected: true
+            }, {
+              name: '21-30',
+              min: {
+                operator: 1,
+                value: '21'
+              },
+              max: {
+                operator: 3,
+                value: '30'
+              },
+              selected: true
+            }, {
+              name: '30以上',
+              min: {
+                operator: 1,
+                value: '30'
+              },
+              max: {
+                operator: 3,
+                value: '99999'
+              },
+              selected: true
+            }]
+            break
+          case 33:
+            this.form.source[dimension] = [{
+              name: '0',
+              min: {
+                operator: 1,
+                value: '0'
+              },
+              max: {
+                operator: 3,
+                value: '0'
+              },
+              selected: true
+            }, {
+              name: '1-5',
+              min: {
+                operator: 1,
+                value: '1'
+              },
+              max: {
+                operator: 3,
+                value: '5'
+              },
+              selected: true
+            }, {
+              name: '6-10',
+              min: {
+                operator: 1,
+                value: '6'
+              },
+              max: {
+                operator: 3,
+                value: '10'
+              },
+              selected: true
+            }, {
+              name: '11-30',
+              min: {
+                operator: 1,
+                value: '11'
+              },
+              max: {
+                operator: 3,
+                value: '30'
+              },
+              selected: true
+            }, {
+              name: '30以上',
+              min: {
+                operator: 1,
+                value: '30'
+              },
+              max: {
+                operator: 3,
+                value: '99999'
+              },
+              selected: true
+            }]
+            break
+          case 34:
+          case 35:
+          case 55:
+            this.form.source[dimension] = [{
+              name: '1',
+              min: {
+                operator: 1,
+                value: '1'
+              },
+              max: {
+                operator: 3,
+                value: '1'
+              },
+              selected: true
+            }, {
+              name: '2-5',
+              min: {
+                operator: 1,
+                value: '2'
+              },
+              max: {
+                operator: 3,
+                value: '5'
+              },
+              selected: true
+            }, {
+              name: '6-10',
+              min: {
+                operator: 1,
+                value: '6'
+              },
+              max: {
+                operator: 3,
+                value: '10'
+              },
+              selected: true
+            }, {
+              name: '11-20',
+              min: {
+                operator: 1,
+                value: '11'
+              },
+              max: {
+                operator: 3,
+                value: '20'
+              },
+              selected: true
+            }, {
+              name: '20-50',
+              min: {
+                operator: 1,
+                value: '20'
+              },
+              max: {
+                operator: 3,
+                value: '50'
+              },
+              selected: true
+            }, {
+              name: '50以上',
+              min: {
+                operator: 1,
+                value: '50'
+              },
+              max: {
+                operator: 3,
+                value: '99999'
+              },
+              selected: true
+            }]
+            break
+        }
+      }
+      this.updateSelected()
+    },
+    handleEdit() {
+      this.copyData = JSON.parse(JSON.stringify(this.form.source[this.dimension]))
+      this.dialogVisible = true
+    },
+    cancel() {
+      this.dialogVisible = false
+    },
+    async handleConfirm() {
+      this.validateForm().then(async (response) => {
+        this.form.source[this.dimension] = JSON.parse(JSON.stringify(this.copyData))
+        let selected = this.selected
+        selected[this.dimension] = this.form.source[this.dimension].map(item => item.name)
+        this.$store.commit('SET_CHART_SELECTED', selected)
+        this.$store.commit('SET_RELOAD_DATA', true)
+        this.dialogVisible = false
+      }).catch(error => {
+        this.$message.error(error)
+      })
+    },
+    validateForm() {
+      return new Promise((resolve, reject) => {
+        const name = this.copyData.map(item => item.name)
+        if (name.indexOf("") !== -1) {
+          reject('请输入名称')
+        }
+        if (new Set(name).size !== name.length) {
+          reject('名称不能重复')
+        }
+        switch (this.form.schema[this.dimension].ptype) {
+          case 3:
+            if (this.copyData.map(item => item.min.value).indexOf("") !== -1 || this.copyData.map(item => item.min.operator).indexOf("") !== -1 || this.copyData.map(item => item.max.value).indexOf("") !== -1 || this.copyData.map(item => item.max.operator).indexOf("") !== -1) {
+              reject('运算符或范围不能为空')
+            }
+            resolve()
+            break;
+          case 4:
+            if (this.copyData.map(item => item.startTime).indexOf("") !== -1 || this.copyData.map(item => item.endTime).indexOf("") !== -1 || this.copyData.map(item => item.startTime).indexOf(null) !== -1 || this.copyData.map(item => item.endTime).indexOf(null) !== -1) {
+              reject('开始或结束时间不能为空')
+            }
+            resolve()
+            break;
+        }
+        reject('参数类型错误')
+      })
+    },
+    rowAdd() {
+      switch (this.form.schema[this.dimension].ptype) {
+        case 3:
+          this.copyData.push({
+            name: '',
+            min: {
+              operator: '',
+              value: ''
+            },
+            max: {
+              operator: '',
+              value: ''
+            },
+            selected: true
+          })
+          break
+        case 4:
+          this.copyData.push({
+            name: '',
+            startTime: '',
+            endTime: '',
+            selected: true
+          })
+          break
+      }
+    },
+    rowDel(row, index) {
+      this.copyData.splice(index, 1)
+    },
+    setChildren(arr) {
+      arr.forEach(item => {
+        this.$set(item, 'disabled', item.parentId !== this.parentId && this.parentId !== -1)
+        if (item.children && item.children.length) {
+          this.setChildren(item.children)
+        } else {
+
+        }
+      })
+    },
+    checkChange(data, checked, indeterminate) {
+      this.parentId = -1
+      this.treeKey[this.dimension] = this.$refs.tree.getCheckedKeys()
+      if (this.parentId === -1) {
+        this.parentId = data.parentId
+      }
+      if (this.treeKey[this.dimension].length === 0) {
+        this.parentId = -1
+      }
+      this.selected[this.dimension] = getTreeNameByIds(this.form.source[this.dimension], this.treeKey[this.dimension])
+      this.$store.commit('SET_RELOAD_DATA', true)
+      this.setChildren(this.form.source[this.dimension])
+    },
+    handleSelectAll() {
+      if (this.parentId === -1) {
+        return false
+      }
+      const tree = getTreeDataList(this.form.source[this.dimension], [])
+      const data = tree.filter(item => item.parentId === this.parentId)
+      this.treeKey[this.dimension] = data.map(item => item.id)
+      this.selected[this.dimension] = getTreeNameByIds(this.form.source[this.dimension], this.treeKey[this.dimension])
+      this.$store.commit('SET_RELOAD_DATA', true)
+    },
+    handleCancelAll() {
+      this.parentId = -1
+      this.treeKey[this.dimension] = []
+      this.selected[this.dimension] = []
+      this.$refs.tree.setCheckedKeys([])
+      this.$store.commit('SET_RELOAD_DATA', true)
+      this.setChildren(this.form.source[this.dimension])
+    }
+  }
+}
+</script>
+
+<style lang="less">
+.select-box {
+  padding: 15px;
+}
+.selected-list {
+  background: #f4f4f4;
+  height: 250px !important;
+  border: 1px solid #DCDFE6;
+  border-radius: 4px;
+  .el-main {
+    .el-row {
+      line-height: 20px;
+    }
+    .el-tree {
+      background: none !important;
+    }
+  }
+}
+</style>

+ 107 - 0
src/views/analyse/custom/components/Tabs/Item.vue

@@ -0,0 +1,107 @@
+<template>
+  <div class="custom-analyse-tabs-item">
+    <el-collapse v-model="activeItem2">
+      <el-collapse-item v-for="menu in treeList" :name="menu.id">
+        <template slot="title">
+          <div class="custom-title">
+            <span class="label">{{ menu.name }}</span>
+            <span class="do" v-if="$permission('/workspace/folder/analyticSystem/chartAnalysis/groupModify') && $r(projectId,[1,2])">
+              <i class="el-icon-edit" @click.stop="handleEdit(menu)"></i>
+              <i class="el-icon-delete" @click.stop="handleDelete(menu)"></i>
+            </span>
+          </div>
+        </template>
+        <div v-for="item in menu.children" @click="handleSelect(item.uid)" class="tree-item" :class="{ 'is-active2' : item.uid === form.uid }">
+          <div class="custom-title">
+            <span class="label">{{ item.name }}</span>
+            <span class="do" v-if="$permission('/workspace/folder/analyticSystem/chartAnalysis/groupItemDelete') && $r(projectId,[1,2])">
+              <i class="el-icon-delete" @click.stop="handleDelete(item)"></i>
+            </span>
+          </div>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<script>
+import mixins from "./mixins";
+
+export default {
+  mixins: [mixins],
+  data() {
+    return {
+      activeItem2: this.activeItem,
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    handleEdit(value) {
+      this.$emit('edit', value)
+    },
+    handleDelete(value) {
+      this.$emit('delete', value)
+    },
+    handleSelect(key) {
+      this.$emit('select', key)
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.custom-analyse-tabs-item {
+  .el-collapse-item__header {
+    padding: 0 15px !important;
+    font-weight: bold;
+    border-bottom: 1px solid #EBEEF5 !important;
+  }
+  .el-collapse {
+    border-top: 0 !important;
+  }
+  .tree-item {
+    padding: 0 25px;
+    height: 45px;
+    cursor: pointer;
+  }
+  .el-collapse-item__content {
+    padding-bottom: 0 !important;
+  }
+  .is-active2 {
+    background: #ecf5ff !important;
+    color: #409EFF!important;
+  }
+  .custom-title {
+    display: flex;
+    flex: 1;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 14px;
+    padding-right: 10px;
+    height:100%;
+    .label {
+      display: flex;
+      align-items: center;
+      height: 100%;
+    }
+    .do {
+      display: none;
+      i {
+        margin-left: 10px;
+        margin-top: -5px;
+        color: #999;
+        font-size: 13px;
+      }
+      &:hover {
+        color: #333;
+      }
+    }
+    &:hover {
+      .do {
+        display: inline-block;
+      }
+    }
+  }
+}
+</style>

+ 50 - 0
src/views/analyse/custom/components/Tabs/Line.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="custom-analyse-tabs-line">
+    <el-form :model="form" label-width="60px" label-position="left" class="line-form">
+      <el-form-item label="开启">
+        <el-switch v-model="form.setting.config.line.enable"></el-switch>
+      </el-form-item>
+      <el-form-item label="名称">
+        <el-input v-model="form.setting.config.line.name"></el-input>
+      </el-form-item>
+      <el-form-item label="运算">
+        <el-select v-model="form.setting.config.line.operator" style="width: 100%;" clearable>
+          <el-option label="平均值" :value="0"></el-option>
+          <el-option label="占比" :value="1"></el-option>
+          <el-option label="总和" :value="2"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="第一项" v-if="form.setting.config.line.operator === 1">
+        <el-select v-model="form.setting.config.line.first" style="width: 100%;" clearable placeholder="请选择第一项">
+          <el-option v-for="(item, index) in selected.y" :label="item" :value="item"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="第二项" v-if="form.setting.config.line.operator === 1">
+        <el-select v-model="form.setting.config.line.second" style="width: 100%;" clearable placeholder="请选择第二项">
+          <el-option label="总和" value="0"></el-option>
+          <el-option v-for="(item, index) in selected.y" :label="item" :value="item"></el-option>
+        </el-select>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import mixins from "./mixins";
+export default {
+  mixins: [mixins],
+  mounted() {
+  }
+}
+</script>
+
+<style lang="scss">
+.custom-analyse-tabs-line {
+  padding: 10px;
+  .line-form {
+    .el-form-item {
+      margin-top: 15px !important;
+    }
+  }
+}
+</style>

+ 401 - 0
src/views/analyse/custom/components/Tabs/Style.vue

@@ -0,0 +1,401 @@
+<template>
+  <div class="custom-analyse-tabs-style">
+    <el-collapse v-model="activeNames">
+      <el-collapse-item title="类型" name="0">
+        <div style="margin-left: 20px;margin-top: 10px;">
+          <style-type v-for="item in chartIcon" :item="item" @click.native="handleClick(item)" />
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="框架" name="8" v-if="!getShow([30])">
+        <div class="content">
+          <div>
+            <span class="label">宽度</span>
+            <el-input placeholder="请输入宽度" v-model="form.setting.width" size="small" style="width: calc(100% - 170px);padding-left: 10px;"></el-input>
+            <el-select size="small" v-model="form.setting.widthUnit" placeholder="请选择" style="width: calc(100% - 190px);margin-left: 10px;">
+              <el-option label="百分比" value="%"></el-option>
+              <el-option label="像素" value="px"></el-option>
+            </el-select>
+          </div>
+          <div style="margin-top: 10px;">
+            <span class="label">高度</span>
+            <el-input placeholder="请输入高度" v-model="form.setting.height" size="small" style="width: calc(100% - 170px);padding-left: 10px;"></el-input>
+            <el-select size="small" v-model="form.setting.heightUnit" placeholder="请选择" style="width: calc(100% - 190px);margin-left: 10px;">
+              <el-option label="百分比" value="%" disabled></el-option>
+              <el-option label="像素" value="px"></el-option>
+            </el-select>
+          </div>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="气泡图" name="9" v-if="getShow([])">
+        <div class="content">
+          <span class="label">气泡比例</span>
+          <el-input placeholder="请输入气泡比例" v-model="form.setting.scatterSize" size="small" style="width: calc(100% - 160px);padding-left: 10px;"></el-input>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="柱形图" name="7" v-if="getShow([4, 7, 5, 6, 8, 9])">
+        <div class="content">
+          <span class="label">宽度</span>
+          <el-input placeholder="请输入宽度" v-model="form.setting.barWidth" size="small" style="width: calc(100% - 190px);padding-left: 10px;"></el-input>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="标签" name="1" v-if="!getShow([30, 19])">
+        <div class="content">
+          <div>
+            <el-checkbox v-model="form.setting.dataLabel">显示标签</el-checkbox>
+            <template v-if="!getShow([13, 12, 20, 18, 21, 23])">
+              <span style="margin-left: 42px;" class="label">位置</span>
+              <el-select size="small" v-model="form.setting.dataPosition" placeholder="请选择" style="width: calc(100% - 160px);margin-left: 10px;">
+                <el-option v-for="item in positionList" :label="item.label" :value="item.value"></el-option>
+              </el-select>
+            </template>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-select size="small" v-model="form.setting.fontFamily2" placeholder="请选择" style="width: calc(100% - 160px)">
+              <el-option v-for="item in $constants.fontFamily" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <el-input size="small" v-model.number="form.setting.dataSize" type="number" placeholder="请输入" style="width: 105px;margin-left: 10px;"></el-input>
+            <el-color-picker size="small" v-model="form.setting.labelColor" style="margin-left: 10px; float:right;"></el-color-picker>
+          </div>
+          <div style="margin-top: 10px;" v-if="getShow([10, 11])">
+            <el-checkbox v-model="form.setting.dataLabel2">显示数值</el-checkbox>
+            <el-checkbox v-model="form.setting.dataLabel3">显示百分比</el-checkbox>
+          </div>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="颜色" name="2" v-if="!getShow([20, 18, 21, 23])">
+        <div class="content">
+          <el-container>
+            <el-main style="max-height: 300px;" class="color-config">
+              <template v-if="!getShow([15, 30])">
+                <div v-for="item in form.setting.config.color" class="color-config-content">
+                  <el-color-picker size="small" v-model="item.color"></el-color-picker>
+                  <span class="name">{{ item.name }}</span>
+                </div>
+                <div v-if="form.setting.config.line.enable" class="color-config-content">
+                  <el-color-picker size="small" v-model="form.setting.config.line.color"></el-color-picker>
+                  <span class="name">{{ form.setting.config.line.name }}</span>
+                </div>
+              </template>
+              <template v-else>
+                <div v-for="(item, index) in form.setting.config.table">
+                  <el-color-picker size="small" v-model="item.color"></el-color-picker>
+                  <div style="float: right; padding-right: 20px;">
+                    <el-input v-model="item.min" style="width: 80px;" size="small"></el-input>
+                    <span class="o-code">≤</span><span>数值</span><span class="o-code">≤</span>
+                    <el-input v-model="item.max" style="width: 80px;" size="small"></el-input>
+                  </div>
+                </div>
+              </template>
+            </el-main>
+          </el-container>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="图例" name="3" v-if="getShow([5, 6, 8, 9, 10, 11])">
+        <div class="content">
+          <div>
+            <el-checkbox v-model="form.setting.legend">显示图例</el-checkbox>
+            <span class="label" style="margin-left: 42px;">位置</span>
+            <el-select size="small" v-model="form.setting.legendLocation" style="width: calc(100% - 160px);margin-left: 10px;">
+              <el-option value="top" label="上"></el-option>
+              <el-option value="left" label="左"></el-option>
+              <el-option value="bottom" label="下"></el-option>
+              <el-option value="right" label="右"></el-option>
+            </el-select>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-select size="small" v-model="form.setting.fontFamily3" placeholder="请选择" style="width: calc(100% - 160px)">
+              <el-option v-for="item in $constants.fontFamily" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <el-input size="small" v-model.number="form.setting.legendFontSize" type="number" placeholder="请输入" style="width: 105px;margin-left: 10px;"></el-input>
+            <el-color-picker size="small" v-model="form.setting.legendColor" style="margin-left: 10px; float:right;"></el-color-picker>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-select size="small" v-model="form.setting.type2" style="width: calc(100% - 160px)">
+              <el-option value="scroll" label="滚动图例"></el-option>
+              <el-option value="plain" label="普通图例"></el-option>
+            </el-select>
+          </div>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="距离" name="4" v-if="!getShow([30, 22, 17, 19, 12, 20, 18, 21, 23, 13])">
+        <div class="content">
+          <div>
+            <span class="label">顶部</span>
+            <el-input size="small" v-model="form.setting.gridTop" style="width: 240px;margin-left: 10px;"></el-input>
+            <span style="margin-left: 10px;">%</span>
+          </div>
+          <div style="margin-top: 10px;">
+            <span class="label">底部</span>
+            <el-input size="small" v-model="form.setting.gridBottom" style="width: 240px;margin-left: 10px;"></el-input>
+            <span style="margin-left: 10px;">%</span>
+          </div>
+          <div style="margin-top: 10px;">
+            <span class="label">左侧</span>
+            <el-input size="small" v-model="form.setting.gridLeft" style="width: 240px;margin-left: 10px;"></el-input>
+            <span style="margin-left: 10px;">%</span>
+          </div>
+          <div style="margin-top: 10px;">
+            <span class="label">右侧</span>
+            <el-input size="small" v-model="form.setting.gridRight" style="width: 240px;margin-left: 10px;"></el-input>
+            <span style="margin-left: 10px;">%</span>
+          </div>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="X轴" name="5" v-if="getShow([1, 3, 4, 7, 5, 6, 8, 9, 2, 15, 14])">
+        <div class="content">
+          <div>
+            <el-checkbox v-model="form.setting.show">显示坐标轴</el-checkbox>
+            <el-checkbox v-model="form.setting.splitLine" v-if="!getShow([14])">显示背景线</el-checkbox>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-select size="small" v-model="form.setting.fontFamily" placeholder="请选择" style="width: calc(100% - 160px)">
+              <el-option v-for="item in $constants.fontFamily" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <el-input size="small" v-model.number="form.setting.fontSize" type="number" placeholder="请输入" style="width: 105px;margin-left: 10px;"></el-input>
+            <el-color-picker size="small" v-model="form.setting.fontColor" style="margin-left: 10px; float:right;"></el-color-picker>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-checkbox v-model="form.setting.fontWeight">字体加粗</el-checkbox>
+            <span class="label" style="margin-left: 15px;">标题位置</span>
+            <el-select size="small" v-model="form.setting.nameLocation" style="width: calc(100% - 160px);margin-left: 10px;">
+              <el-option value="start" label="左"></el-option>
+              <el-option value="middle" label="中"></el-option>
+              <el-option value="end" label="右"></el-option>
+            </el-select>
+          </div>
+          <div style="margin-top: 10px;">
+            <span class="label">标题</span>
+            <el-input size="small" v-model="form.setting.title1" placeholder="请输入" style="width: calc(100% - 37px);margin-left: 10px;"></el-input>
+          </div>
+          <div style="margin-top: 10px;" class="title-padding">
+            <span class="label">位移</span>
+            <span class="label" style="margin: 0 5px;">上</span>
+            <el-input v-model.number="form.setting.paddingTop" size="small" placeholder="" style="width: 45px;"></el-input>
+            <span class="label" style="margin: 0 5px;">左</span>
+            <el-input v-model.number="form.setting.paddingLeft" size="small" placeholder="" style="width: 45px;"></el-input>
+            <span class="label" style="margin: 0 5px;">下</span>
+            <el-input v-model.number="form.setting.paddingBottom" size="small" placeholder="" style="width: 45px;"></el-input>
+            <span class="label" style="margin: 0 5px;">右</span>
+            <el-input v-model.number="form.setting.paddingRight" size="small" placeholder="" style="width: 45px;"></el-input>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-checkbox v-model="form.setting.interval">完整显示</el-checkbox>
+            <span class="label" style="margin-left: 15px;">倾斜角度</span>
+            <el-input size="small" v-model="form.setting.rotate" placeholder="请输入" style="width: calc(100% - 160px);margin-left: 10px;"></el-input>
+          </div>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="Y轴" name="6" v-if="getShow([1, 3, 4, 7, 5, 6, 8, 9, 2, 15, 14])">
+        <div class="content">
+          <div>
+            <el-checkbox v-model="form.setting.show2">显示坐标轴</el-checkbox>
+            <el-checkbox v-model="form.setting.splitLine2" v-if="!getShow([14])">显示背景线</el-checkbox>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-select size="small" v-model="form.setting.fontFamily2" placeholder="请选择" style="width: calc(100% - 160px)">
+              <el-option v-for="item in $constants.fontFamily" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <el-input size="small" v-model.number="form.setting.fontSize2" type="number" placeholder="请输入" style="width: 105px;margin-left: 10px;"></el-input>
+            <el-color-picker size="small" v-model="form.setting.fontColor2" style="margin-left: 10px; float:right;"></el-color-picker>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-checkbox v-model="form.setting.fontWeight2">字体加粗</el-checkbox>
+            <span class="label" style="margin-left: 15px;">标题位置</span>
+            <el-select size="small" v-model="form.setting.nameLocation2" style="width: calc(100% - 160px);margin-left: 10px;">
+              <el-option value="end" label="上"></el-option>
+              <el-option value="middle" label="中"></el-option>
+              <el-option value="start" label="下"></el-option>
+            </el-select>
+          </div>
+          <div style="margin-top: 10px;">
+            <span class="label">标题</span>
+            <el-input size="small" v-model="form.setting.title2" placeholder="请输入" style="width: calc(100% - 37px);margin-left: 10px;"></el-input>
+          </div>
+          <div style="margin-top: 10px;" class="title-padding">
+            <span class="label">位移</span>
+            <span class="label" style="margin: 0 5px;">上</span>
+            <el-input v-model.number="form.setting.paddingTop2" size="small" placeholder="" style="width: 45px;"></el-input>
+            <span class="label" style="margin: 0 5px;">左</span>
+            <el-input v-model.number="form.setting.paddingLeft2" size="small" placeholder="" style="width: 45px;"></el-input>
+            <span class="label" style="margin: 0 5px;">下</span>
+            <el-input v-model.number="form.setting.paddingBottom2" size="small" placeholder="" style="width: 45px;"></el-input>
+            <span class="label" style="margin: 0 5px;">右</span>
+            <el-input v-model.number="form.setting.paddingRight2" size="small" placeholder="" style="width: 45px;"></el-input>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-checkbox v-model="form.setting.interval2">完整显示</el-checkbox>
+            <span class="label" style="margin-left: 15px;">倾斜角度</span>
+            <el-input size="small" v-model="form.setting.rotate2" placeholder="请输入" style="width: calc(100% - 160px);margin-left: 10px;"></el-input>
+          </div>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="坐标" name="6" v-if="getShow([17, 22])">
+        <div class="content">
+          <div>
+            <el-checkbox v-model="form.setting.show">显示</el-checkbox>
+            <el-checkbox v-model="form.setting.fontWeight">字体加粗</el-checkbox>
+          </div>
+          <div style="margin-top: 10px;">
+            <el-select size="small" v-model="form.setting.fontFamily" placeholder="请选择" style="width: calc(100% - 160px)">
+              <el-option v-for="item in $constants.fontFamily" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <el-input size="small" v-model.number="form.setting.fontSize" type="number" placeholder="请输入" style="width: 105px;margin-left: 10px;"></el-input>
+            <el-color-picker size="small" v-model="form.setting.fontColor" style="margin-left: 10px; float:right;"></el-color-picker>
+          </div>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<script>
+import mixins from "./mixins";
+import { detectionChartType } from "@/utils/chart";
+import StyleType from "./StyleType";
+
+export default {
+  mixins: [mixins],
+  components: {
+    StyleType
+  },
+  data() {
+    return {
+      activeNames: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
+      chartIcon: [],
+      positionList: [],
+    }
+  },
+  mounted() {
+    this.chartIcon = JSON.parse(JSON.stringify(this.$constants.chartType))
+    this.chartIcon.map(item => {
+      let { op, cg } = detectionChartType(item, this.form)
+      if (op && cg) {
+        cg = false
+        this.$store.dispatch('resetSettingColor')
+      }
+      if (op) {
+        this.$set(item, 'disable', true)
+        this.$set(item, 'active', false)
+      }
+      if (cg) {
+        this.$set(item, 'disable', false)
+        this.$set(item, 'active', true)
+      }
+    })
+    this.getPositionList()
+  },
+  methods: {
+    getShow(arr) {
+      return arr.indexOf(this.form.setting.type) !== -1
+    },
+    getPositionList() {
+      if ([10, 11].indexOf(this.form.setting.type) !== -1) {
+        this.positionList = [
+          { label: '外侧', value: 'outside' },
+          { label: '内部', value: 'inside' },
+          { label: '中心', value: 'center' },
+        ]
+      } else {
+        this.positionList = [
+          { label: '上', value: 'top' },
+          { label: '左', value: 'left' },
+          { label: '右', value: 'right' },
+          { label: '下', value: 'bottom' },
+          { label: '内', value: '' },
+        ]
+      }
+    },
+    handleClick(item) {
+      if (item.disable) {
+        return false
+      }
+      const chartType = this.$constants.chartType.find(c => c.value === this.form.setting.type)
+      if (item.value === 21 || this.form.setting.type === 21) {
+        this.$store.commit('SET_RELOAD_DATA', true)
+      }
+      if (item.value === 22 && chartType.type === 1) {
+        this.$store.commit('SET_RELOAD_DATA', true)
+      }
+      if (!chartType) {
+        this.$store.commit('SET_RELOAD_DATA', true)
+      }
+      if ((this.form.schema.x.type === 6 || this.form.schema.y.type === 6) && chartType) {
+        if (chartType.value === 30 && item.value !== 30) {
+          this.$store.commit('SET_RELOAD_DATA', true)
+        }
+        if (chartType.value !== 30 && item.value === 30) {
+          this.$store.commit('SET_RELOAD_DATA', true)
+        }
+        if (chartType.value === 20 && item.value !== 20) {
+          this.$store.commit('SET_RELOAD_DATA', true)
+        }
+        if (chartType.value !== 20 && item.value === 20) {
+          this.$store.commit('SET_RELOAD_DATA', true)
+        }
+      }
+      this.form.setting.type = item.value
+      this.chartIcon.map(c => {
+        if (c.value === item.value) {
+          this.$set(c, 'active', true)
+        } else {
+          this.$set(c, 'active', false)
+        }
+      })
+      this.$store.dispatch('getItemSettingColor', [])
+      this.form.setting.dataPosition = ''
+      this.getPositionList()
+    },
+  }
+}
+</script>
+
+<style lang="scss">
+.custom-analyse-tabs-style {
+  .el-collapse {
+    border-top: transparent !important;
+  }
+  .el-collapse-item__header {
+    font-weight: bold !important;
+    padding: 0 20px !important;
+    border-bottom: 1px solid #EBEEF5 !important;
+  }
+  .el-form-item {
+    padding: 0 15px;
+    margin-bottom: 0 !important;
+  }
+  .el-collapse {
+    border-top: 0 !important;
+  }
+  .color-config {
+    padding: 0 !important;
+    .o-code {
+      margin: 0 5px;
+      display: inline-block;
+    }
+    .color-config-content {
+      line-height: 50px;
+      position: relative;
+      .name {
+        margin-left: 10px;
+        font-size: 14px;
+        top: -10px;
+        position: absolute;
+      }
+    }
+  }
+  .title-padding {
+    .el-input__inner {
+      padding: 0 10px !important;
+    }
+  }
+  .el-collapse-item__content {
+    padding-bottom: 0 !important;
+  }
+  .content {
+    padding: 20px;
+  }
+  .label {
+    color: #6b6868;
+  }
+}
+</style>

+ 53 - 0
src/views/analyse/custom/components/Tabs/StyleType.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="select-chart">
+    <div class="item" :style="{ cursor: item.disable ? 'not-allowed' : 'pointer' }" :class="{ 'active' : item.active }" :title="item.label">
+      <span v-if="item.disable" class="icon" :style="{ backgroundImage: `url(${require(`@/assets/img/${item.icon}-disable.svg`)})` }"></span>
+      <span v-else-if="item.active" class="icon" :style="{ backgroundImage: `url(${require(`@/assets/img/${item.icon}-active.svg`)})` }"></span>
+      <span v-else class="icon" :style="{ backgroundImage: `url(${require(`@/assets/img/${item.icon}.svg`)})` }"></span>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "SelectChart",
+  props: ['item'],
+  data() {
+    return {
+
+    }
+  },
+  methods: {
+
+  }
+}
+</script>
+
+<style lang="less">
+.select-chart {
+  display: inline;
+  .item {
+    width: 38px;
+    height: 38px;
+    background: #ebecf0;
+    border-radius: 4px;
+    display: inline-block;
+    margin-bottom: 8px;
+    margin-right: 8px;
+    position: relative;
+
+    .icon {
+      height: 24px;
+      width: 24px;
+      background-repeat: no-repeat;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+  .active {
+    background: #6b6868 !important;
+  }
+}
+</style>

+ 19 - 0
src/views/analyse/custom/components/Tabs/mixins.js

@@ -0,0 +1,19 @@
+import { mapGetters } from "vuex";
+
+export default {
+  props: {
+    activeItem: Array
+  },
+  computed: {
+    ...mapGetters(['userinfo', 'projectId']),
+    form() {
+      return this.$store.state.chart.form
+    },
+    treeList() {
+      return this.$store.state.chart.treeList
+    },
+    selected() {
+      return this.$store.state.chart.selected
+    },
+  },
+}

+ 728 - 0
src/views/analyse/custom/index.vue

@@ -0,0 +1,728 @@
+<template>
+  <div class="custom-analyse">
+    <el-container v-if="showPage" v-loading="loading">
+      <el-aside width="350px">
+        <el-container>
+          <el-header class="custom-analyse-tabs">
+            <el-tabs v-model="activeTab" type="card">
+              <el-tab-pane label="分析项" name="TabItem"></el-tab-pane>
+              <el-tab-pane label="数据" name="TabData"></el-tab-pane>
+              <el-tab-pane label="样式" name="TabStyle"></el-tab-pane>
+              <el-tab-pane label="辅助线" name="TabLine" :disabled="enable.indexOf(form.setting.type) === -1"></el-tab-pane>
+            </el-tabs>
+          </el-header>
+          <el-main class="nopadding">
+            <component
+                :is="activeTab"
+                :active-item="activeItem"
+                @edit="handleEdit"
+                @delete="handleDelete"
+                @select="handleSelect"
+            ></component>
+          </el-main>
+        </el-container>
+      </el-aside>
+      <el-container>
+        <el-header>
+          <template>
+            <div class="custom-analyse-chart-title">
+              <span>{{ chartTitle }}</span>
+            </div>
+            <div class="custom-analyse-options">
+              <span class="text">专利数量<span class="number">{{ patentNum }} 件</span></span>
+              <el-button size="small" type="primary" @click="handleAdd3" :disabled="!($permission('/workspace/folder/analyticSystem/chartAnalysis/addGroup') && $r(projectId,[1,2]))">添加组</el-button>
+              <el-button size="small" type="success" @click="submit" :loading="loadingBtn">预览图表</el-button>
+              <el-dropdown size="small">
+                <el-button size="small" type="warning" :disabled="!$permission('/workspace/folder/analyticSystem/chartAnalysis/moreMenu')">
+                  更多菜单<i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item @click.native="handleScreenshot" :disabled="!($permission('/workspace/folder/analyticSystem/chartAnalysis/moreMenu/screenShot') && $r(projectId,[1,2,4]))">截图</el-dropdown-item>
+                  <el-dropdown-item @click.native="handleAdd1(0)" :disabled="!($permission('/workspace/folder/analyticSystem/chartAnalysis/moreMenu/save') && $r(projectId,[1,2]))">保存修改</el-dropdown-item>
+                  <el-dropdown-item @click.native="handleAdd1(1)" :disabled="!$permission('/workspace/folder/analyticSystem/chartAnalysis/moreMenu/saveAs')">另存为</el-dropdown-item>
+                  <el-dropdown-item @click.native="handleExport" :disabled="!($permission('/workspace/folder/analyticSystem/chartAnalysis/moreMenu/export') && $r(projectId,[1,2,4]))">导出数据</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
+            </div>
+          </template>
+        </el-header>
+        <el-main class="custom-analyse-chart-img-box">
+          <div v-if="showChart">
+            <chart ref="chartDom" :width="form.setting.width + form.setting.widthUnit" :height="form.setting.height + form.setting.heightUnit" />
+          </div>
+        </el-main>
+      </el-container>
+    </el-container>
+
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="500px" :before-close="handleClose">
+      <el-form v-if="dialogVisible" ref="ruleForm" :model="form2" :rules="rules" label-position="left" label-width="100px">
+        <el-form-item label="所属组" prop="parentId" v-if="form2.type">
+          <el-select v-model="form2.parentId" filterable allow-create placeholder="请选择" style="width: 100%">
+            <el-option v-for="item in treeList" :label="item.name" :value="item.id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item :label="`${form2.type ? '项目名称' : '组名称'}`" prop="name">
+          <el-input v-model="form2.name" placeholder="请输入"></el-input>
+        </el-form-item>
+        <el-form-item label="显示顺序" prop="sort">
+          <el-input v-model.number="form2.sort" type="number" placeholder="请输入"></el-input>
+        </el-form-item>
+        <el-form-item label="使用权限" prop="permissions" v-if="form2.type">
+          <el-select v-model="form2.permissions" placeholder="请选择" style="width: 100%">
+            <el-option v-for="item in $constants.permissions" :label="item.label" :value="item.value"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="cancel">关 闭</el-button>
+        <el-button type="primary" @click="handleConfirm" :loading="loadingBtn">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import Chart from "./components/Charts/index.vue";
+import { mapGetters } from "vuex";
+import { downLoad, findChildren, getTreeLastChildren, getTreeDataList, downLoadBase64 } from "@/utils";
+import { customPage } from "./mixins";
+import moment from "moment";
+import TreeSelect from '@riophae/vue-treeselect'
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import { detectionChartType, getFormPermissions, getTreeNameByIds, getXAxisName, getSourceName } from "@/utils/chart";
+import TabItem from "./components/Tabs/Item";
+import TabData from "./components/Tabs/Data";
+import TabStyle from "./components/Tabs/Style";
+import TabLine from "./components/Tabs/Line";
+import html2canvas from "html2canvas";
+
+export default {
+  components: {
+    TreeSelect,
+    Chart,
+    TabItem,
+    TabData,
+    TabStyle,
+    TabLine
+  },
+  mixins: [customPage],
+  data() {
+    return {
+      activeTab: 'TabItem',
+      enable: [2, 5, 6, 8, 9, 17],
+      saveType: 0,
+      form2: {},
+      rules: {
+        parentId: [
+          {required: true, message: '请选择上级目录', trigger: 'change'},
+        ],
+        name: [
+          {required: true, message: '请输入名称', trigger: 'blur'},
+        ],
+        permissions: [
+          {required: true, message: '请输选择使用权限', trigger: 'change'},
+        ],
+        sort: [
+          {required: true, message: '请输入显示次序', trigger: 'blur'},
+        ],
+      },
+      patentNum: 0,
+      showChart: true,
+      showPage: false,
+      loadingBtn2: false,
+      loadingBtn3: false,
+      activeName: '1',
+      activeItem: [],
+      loading: false,
+      loadingBtn: false,
+      title: '',
+      chartTitle: '',
+      dialogVisible: false,
+      loadData: true,
+      drawer: false,
+      tempForm: {
+        name: '',
+        sort: 0
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['userinfo', 'projectId', 'customField', 'patentField'])
+  },
+  mounted() {
+    this.showPage = false
+    if (this.patentKey) {
+      this.getPatentListNum()
+    } else {
+      this.errorPage()
+    }
+  },
+  methods: {
+    normalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children;
+      }
+      return {
+        id: node.id,
+        label: node.name,
+        children: node.children
+      };
+    },
+    cancel() {
+      this.saveType = 0
+      this.dialogVisible = false
+    },
+    errorPage() {
+
+    },
+    handleClose() {
+      this.saveType = 0
+      this.dialogVisible = false
+    },
+    reset() {
+
+    },
+    getPatentListNum() {
+      this.$api.getPatentListNum({patentKey: this.patentKey}).then(async (response) => {
+        this.patentNum = response.data
+        await this.getTreeList()
+        if (this.treeList.length === 0 || this.treeList[0].children.length === 0) {
+          this.handleAdd()
+          this.showChart = false
+          this.saveType = 1
+        } else {
+          this.activeItem = [this.treeList[0].id]
+          await this.handleSelect(this.treeList[0].children[0].uid)
+        }
+        this.showPage = true
+      }).catch(error => {
+        this.errorPage()
+      })
+    },
+    handleDelete(value) {
+      this.$confirm('确认删除本条数据吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async () => {
+        const response = await this.$api.deleteCustomItem(value.id)
+        this.$message.success('删除成功')
+        await this.getTreeList()
+        if (this.form.id === value.id) {
+          const tree = this.treeList.find(item => item.id === value.parentId)
+          if (tree && tree.children.length) {
+            await this.handleSelect(tree.children[tree.children.length - 1].uid)
+          } else {
+            this.handleAdd()
+          }
+        }
+      })
+    },
+    async getTreeList() {
+      this.loading = true
+      const response = await this.$api.getCustomTree({projectId: this.projectId})
+      this.$store.commit('SET_CHART_TREE', response.data)
+      this.setChildren(this.treeList, 'isDisabled')
+      this.loading = false
+    },
+    beforeClose() {
+      this.drawer = false
+    },
+    setChildren(arr, key) {
+      arr.forEach(item => {
+        if (item.children && item.children.length) {
+          this.setChildren(item.children, key)
+        } else {
+          this.$set(item, key, item.type === 1)
+        }
+      })
+    },
+    refreshChart() {
+      this.$nextTick(() => {
+        let chartDom = this.$refs.chartDom
+        if (chartDom) {
+          chartDom.refreshChart()
+        }
+      })
+    },
+    onChange() {
+      if (this.saveType || !this.form2.id) {
+        this.form2.name = getSourceName(this.form.schema.x.field, this.form.schema.x.expand, this.form.schema.x.num)
+        const tree = this.treeList.find(item => item.id === this.form2.parentId)
+        if (tree) {
+          this.form2.sort = tree.children.length + 1
+        }
+      } else {
+        this.form2.name = this.tempForm.name
+        this.form2.sort = this.tempForm.sort
+      }
+    },
+    handleAdd3() {
+      this.title = '添加组'
+      this.form2 = {
+        parentId: 0,
+        name: '',
+        sort: this.treeList.length + 1,
+        type: 0,
+        permissions: 2,
+        projectId: this.projectId
+      }
+      this.dialogVisible = true
+    },
+    async handleScreenshot() {
+      const canvas = await html2canvas(this.$refs.chartDom.$el)
+      const base64 = canvas.toDataURL('image/jpg')
+      downLoadBase64(base64, '图片.png')
+    },
+    async handleAdd1(saveType) {
+      this.saveType = saveType
+      this.onChange()
+      this.title = '保存自定义分析'
+      let data = {
+        parentId: null,
+        permissions: 2,
+        name: '',
+        sort: 0
+      }
+      if (this.form.uid) {
+        const response = await this.$api.getCustomItem(this.form.uid)
+        data = response.data
+        this.tempForm.name = data.name
+        this.tempForm.sort = data.sort
+      }
+      this.form2 = {
+        id: data.id,
+        parentId: data.parentId,
+        type: 1,
+        permissions: data.permissions,
+        name: data.name,
+        sort: data.sort,
+        setting: this.form.setting,
+        schema: this.form.schema,
+        source: this.form.source,
+        projectId: this.projectId
+      }
+      this.dialogVisible = true
+    },
+    handleAdd() {
+      let table = []
+      for (let i = 0; i < 10; i++) {
+        table.push({
+          min: 0,
+          max: 0,
+          color: null
+        })
+      }
+      let form = {
+        setting: {
+          type: 1,
+          showType: 0,
+          tableType: 0,
+          theme: "customed",
+          titleSize: 14,
+          dataSize: 14,
+          axisSize: 14,
+          dataLabel: false,
+          dataLabel2: false,
+          dataLabel3: false,
+          title1Dir: 0,
+          title1: "",
+          title2: "",
+          dataPosition: "top",
+          fontFamily: "sans-serif",
+          fontFamily2: "sans-serif",
+          fontFamily3: "sans-serif",
+          fontFamily4: "sans-serif",
+          fontWeight: false,
+          fontWeight2: false,
+          nameLocation: "end",
+          nameLocation2: "end",
+          width: "100",
+          height: "650",
+          widthUnit: "%",
+          heightUnit: "px",
+          splitLine: false,
+          splitLine2: false,
+          legend: true,
+          gridTop: "0",
+          gridLeft: "0",
+          gridBottom: "0",
+          gridRight: "0",
+          labelColor: "#000000",
+          legendColor: "#000000",
+          legendLocation: "top",
+          legendFontSize: "13",
+          fontSize: "13",
+          fontSize2: "13",
+          fontColor: "#000000",
+          fontColor2: "#000000",
+          show: true,
+          show2: true,
+          interval: true,
+          interval2: true,
+          rotate: "0",
+          rotate2: "0",
+          paddingTop: 0,
+          paddingBottom: 0,
+          paddingRight: 0,
+          paddingLeft: 0,
+          paddingTop2: 0,
+          paddingLeft2: 0,
+          paddingBottom2: 0,
+          paddingRight2: 0,
+          type2: "scroll",
+          barWidth: "",
+          scatterSize: "0",
+          config: {
+            color: [],
+            line: {
+              name: "",
+              first: "",
+              second: "",
+              operator: 0,
+              enable: false
+            },
+            table: table
+          }
+        },
+        schema: {
+          x: {
+            num: 10
+          },
+          y: {
+            num: 10
+          },
+        },
+        source: {
+          x: [],
+          y: []
+        },
+      }
+      this.$store.commit('SET_CHART_FORM', form)
+      this.drawer = true
+    },
+    async handleEdit(value) {
+      this.title = '编辑组'
+      this.form2 = { ...value }
+      this.dialogVisible = true
+    },
+    getDataCountParams() {
+      let source = {
+        x: [],
+        y: [],
+      }
+      let schema = {
+        x: {},
+        y: {}
+      }
+      let merge = {
+        x: 0,
+        y: 0
+      }
+      Object.keys(source).forEach(dimension => {
+        if (this.form.schema[dimension].type === 6) {
+          if (this.form.setting.type === 20) {
+            source[dimension] = this.getTreeAllNode(dimension)
+            merge[dimension] = 1
+          } else if (this.form.setting.type === 30) {
+            source[dimension] = this.getTreeLastChildren(dimension)
+          } else {
+            source[dimension] = this.getPatentTreeChildren(dimension)
+            merge[dimension] = 1
+          }
+        } else {
+          source[dimension] = this.form.source[dimension].filter(item => item.selected)
+        }
+      })
+      Object.keys(schema).forEach(dimension => {
+        schema[dimension] = {
+          expand: this.form.schema[dimension].expand || 0,
+          field: this.form.schema[dimension].field || 0,
+          num: this.form.schema[dimension].num,
+          type: this.form.schema[dimension].type,
+          ptype: this.form.schema[dimension].ptype,
+          merge: merge[dimension]
+        }
+      })
+      return { source, schema }
+    },
+    async getDataCount() {
+      if (this.reloadData) {
+        const { source, schema } = this.getDataCountParams()
+        let params = {
+          type: this.form.setting.type,
+          schema: schema,
+          source: source,
+          patentKey: this.patentKey,
+          projectId: this.projectId
+        }
+        this.loading = true
+        try {
+          const response = await this.$api.getDataCount(params)
+          this.$store.commit('SET_DATA_COUNT', response.data)
+          this.refreshChart()
+          this.$store.commit('SET_RELOAD_DATA', false)
+          this.loading = false
+          this.showChart = true
+        } catch (e) {
+          this.loading = false
+          this.showChart = false
+        }
+      }
+    },
+    getTreeAllNode(dimension) {
+      const tree = getTreeDataList(this.form.source[dimension], [])
+      return tree.map(item => { return { id : item.id } })
+    },
+    getTreeLastChildren(dimension) {
+      const tree = getTreeDataList(this.form.source[dimension], [])
+      const data = tree.filter(item => this.treeKey[dimension].indexOf(item.id) !== -1)
+      let arr = data.filter(item =>  !item.children || !item.children.length)
+      data.map(item => arr.push(...getTreeLastChildren(item.children || [], [])))
+      return arr.map(item => { return { id : item.id } })
+    },
+    getPatentTreeChildren(dimension) {
+      const tree = getTreeDataList(this.form.source[dimension], [])
+      const selected = this.treeKey[dimension].map(item => item)
+      let arr = []
+      selected.map(item => {
+        const node = tree.find(t => t.id === item)
+        arr.push(node)
+        arr.push(...getTreeLastChildren(node.children || [], []))
+      })
+      return arr.map(item => { return { id : item.id } })
+    },
+    getTreeNodeName(node, tree) {
+      const path = node.path.split('/').map(p => parseInt(p, 0))
+      let name = []
+      path.map(item => {
+        const n = tree.find(x => x.id === item)
+        if (n) {
+          name.push(n.name)
+        }
+      })
+      return name.join('/')
+    },
+    getExportParams() {
+      const xAxis = getXAxisName(this.patentField, this.form.schema.x.field, this.form.schema.x.expand)
+      let type = this.form.schema.y.field ? 2 : 1
+      type = this.form.setting.type === 21 ? 3 : type
+      let count = JSON.parse(JSON.stringify(this.count))
+      const xt = this.form.schema.x.type === 6
+      const yt = this.form.schema.y.type === 6
+      const xs = getTreeDataList(this.form.source.x, [])
+      const ys = getTreeDataList(this.form.source.y, [])
+      if (xt && !yt) {
+        for (let key in count) {
+          const xn = xs.find(item => item.id === parseInt(key))
+          const name = this.getTreeNodeName(xn, xs)
+          if (xn) {
+            count[name] = count[key]
+            delete count[key]
+          }
+        }
+      }
+      if (xt && yt) {
+        for (let xk in count) {
+          const xn = xs.find(item => item.id === parseInt(xk))
+          const xa = this.getTreeNodeName(xn, xs)
+          let data = {}
+          for (let yk in count[xk]) {
+            const yn = ys.find(item => item.id === parseInt(yk))
+            const ya = this.getTreeNodeName(yn, ys)
+            if (yn) {
+              data[ya] = count[xk][yk]
+            }
+          }
+          if (xn) {
+            count[xa] = data
+            delete count[xk]
+          }
+        }
+      }
+      if (!xt && yt) {
+        for (let key in count) {
+          let data = {}
+          for (let y in count[key]) {
+            const yn = ys.find(item => item.id === parseInt(y))
+            const ya = this.getTreeNodeName(yn, ys)
+            if (yn) {
+              data[ya] = count[key][y]
+              delete count[y]
+            }
+          }
+          count[key] = data
+        }
+      }
+      return { count, type, xAxis }
+    },
+    async handleExport() {
+      this.loadingBtn3 = true
+      const { count, type, xAxis } = this.getExportParams()
+      this.$api.exportCustomItem(count, type, xAxis).then(response => {
+        downLoad(response, moment().format('YYYYMMDDHHmmss') + '.xls')
+        this.loadingBtn3 = false
+      }).catch(error => {
+        this.loadingBtn3 = false
+      })
+    },
+    resetChartType() {
+      this.form.setting.type = null
+      this.$store.dispatch('resetSettingColor')
+    },
+    async submit() {
+      if (!this.form.schema.x.field) {
+        this.$message.error('请选择一维数据')
+        return false
+      }else{
+        if(this.form.source.x.length == 0){
+          this.$message.error('一维数据为空')
+          return false
+        }
+      }
+      if(this.form.schema.y.field){
+        if(this.form.source.y.length == 0){
+          this.$message.error('二维数据为空')
+          return false
+        }
+      }
+      const chartType = this.$constants.chartType.find(c => c.value === this.form.setting.type)
+      const { op, cg } = detectionChartType(chartType || {}, this.form)
+      if (op && cg) {
+        this.resetChartType()
+      }
+      if (!this.form.setting.type) {
+        this.$message.error('请选择图表类型')
+        return false
+      }
+      this.drawer = false
+      await this.getDataCount()
+      this.refreshChart()
+      this.showChart = true
+    },
+    async handleSelect(key) {
+      let source = {
+        x: [],
+        y: []
+      }
+      this.$store.commit('SET_RELOAD_DATA', true)
+      this.activeName = '1'
+      this.loading = true
+      const response = await this.$api.getCustomItem(key)
+      this.$store.commit('SET_CHART_FORM', JSON.parse(JSON.stringify(response.data)))
+      if (this.enable.indexOf(this.form.setting.type) === -1) {
+        this.form.setting.config.line.enable = false
+      }
+      for (let key in response.data.source) {
+        source[key] = response.data.source[key]
+        if (response.data.source[key].length === 0 || response.data.schema[key].type === 6) {
+          await this.$store.dispatch('getSourceDataList', key)
+          if (response.data.setting.title1) {
+            this.form.setting.title1 = response.data.setting.title1
+          }
+          if (response.data.setting.title2) {
+            this.form.setting.title2 = response.data.setting.title2
+          }
+        }
+      }
+      for (let key in response.data.schema) {
+        if (this.form.schema[key].type !== 6) {
+          this.$set(this.selected, key, this.form.source[key].slice(0, response.data.schema[key].num).map(item => item.name))
+          this.form.source[key].map(item => {
+            item.selected = this.selected[key].indexOf(item.name) !== -1
+          })
+        } else {
+          this.treeKey[key] = source[key].map(item => parseInt(item.name))
+          this.selected[key] = getTreeNameByIds(this.form.source[key], this.treeKey[key])
+        }
+      }
+      await this.$store.dispatch('getItemSettingColor', [])
+      await this.getDataCount()
+      this.$nextTick(() => {
+        this.loading = false
+      })
+    },
+    handleConfirm() {
+      this.$refs.ruleForm.validate(async (valid) => {
+        if (valid) {
+          this.loadingBtn = true
+          let form = JSON.parse(JSON.stringify(this.form2))
+          for (let key in form.schema) {
+            form.schema[key].field = form.schema[key].field || 0
+            form.schema[key].expand = form.schema[key].expand || 0
+            if (form.schema[key].type === 6) {
+              form.source[key] = this.treeKey[key].map(item => { return { name: item, selected: true } })
+            }
+          }
+          try {
+            let response
+            if (typeof form.parentId === 'string') {
+              form.parentName = form.parentId
+              form.parentId = undefined
+            }
+            if (form.type) {
+              form.permissions = getFormPermissions(form)
+            }
+            if (!this.saveType && form.id) {
+              response = await this.$api.editCustomItem(form)
+              this.$message.success('编辑成功')
+            } else {
+              form.id = null
+              response = await this.$api.addCustomItem(form)
+              this.activeItem.push(response.data.parentId)
+              this.$message.success('添加成功')
+              if (form.type) {
+                await this.handleSelect(response.data.uid)
+              }
+            }
+            if (form.type) {
+              this.chartTitle = response.data.name
+            }
+            await this.getTreeList()
+            this.loadingBtn = false
+            this.drawer = false
+            this.handleClose()
+          } catch (e) {
+            this.loadingBtn = false
+          }
+        } else {
+          // console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+  }
+}
+</script>
+
+<style lang="scss">
+.custom-analyse {
+  .custom-analyse-tabs {
+    .el-tabs__header {
+      margin-top: 35px !important;
+      margin-left: 22px;
+    }
+  }
+  .custom-analyse-chart-img-box {
+    background: #ffffff;
+  }
+  .custom-analyse-chart-title {
+    padding: 20px;
+    span {
+      font-weight: bold;
+      font-size: 16px;
+    }
+  }
+  .custom-analyse-options {
+    padding: 20px;
+    .number {
+      padding-left: 10px;
+      color: #409EFF;
+    }
+    .text {
+      font-size: 13px;
+      padding-right: 20px;
+    }
+  }
+}
+</style>

+ 445 - 0
src/views/analyse/custom/mixins.js

@@ -0,0 +1,445 @@
+import { mapGetters } from "vuex";
+import constants from '@/utils/constants'
+import { downLoadBase64, getTreeDataList, random } from "@/utils";
+import { getLineDataArr, getTreeNameByIds } from "@/utils/chart";
+import html2canvas from 'html2canvas'
+
+export const customPage = {
+  computed: {
+    form() {
+      return this.$store.state.chart.form
+    },
+    changeNum() {
+      return this.$store.state.chart.changeNum
+    },
+    patentKey() {
+      return this.$store.state.chart.patentKey
+    },
+    treeList() {
+      return this.$store.state.chart.treeList
+    },
+    treeKey() {
+      return this.$store.state.chart.treeKey
+    },
+    selected() {
+      return this.$store.state.chart.selected
+    },
+    selectedLoad() {
+      return this.$store.state.chart.selectedLoad
+    },
+    count() {
+      return this.$store.state.chart.count
+    },
+    matrix() {
+      return this.$store.state.chart.matrix
+    },
+    reloadData() {
+      return this.$store.state.chart.reloadData
+    }
+  },
+  watch:{
+    patentKey(val){
+      if (val) {
+        this.getPatentListNum()
+      } else {
+        this.errorPage()
+      }
+    }
+  },
+  methods: {
+    getShowLabel(dict, value) {
+      const data = dict.find(s => s.value === parseInt(value))
+      if (data) {
+        return data.label
+      }
+    },
+  }
+}
+
+export const chartOption = {
+  mixins: [customPage],
+  props: {
+    width: String,
+    height: String
+  },
+  data() {
+    return {}
+  },
+  methods: {
+    async handleScreenshot(id, name) {
+      const canvas = await html2canvas(document.getElementById(id))
+      const base64 = canvas.toDataURL('image/jpg')
+      downLoadBase64(base64, name + '.png')
+    },
+    getDataLabel() {
+      return {
+        show: this.form.setting.dataLabel,
+        fontSize: this.form.setting.dataSize,
+        position: this.form.setting.dataPosition,
+        fontFamily: this.form.setting.fontFamily2,
+        color: this.form.setting.labelColor,
+        formatter: (val) => {
+          switch (val.seriesType) {
+            case 'sunburst':
+              return val.name
+            case 'heatmap':
+              return val.value[2]
+            case 'tree':
+              return val.data.name
+            case 'scatter':
+              return !val.data[2] ? '' : val.data[2]
+            case 'treemap':
+              return `${val.name} (${val.value})`
+            case 'map':
+              return val.value ? `${val.name} \n ${val.value}` : ''
+            default:
+              if (val.value) {
+                return val.value
+              } else {
+                return ''
+              }
+          }
+        }
+      }
+    },
+    getAxisName(dimension) {
+      return this.form.setting[dimension]
+    },
+    getAxisData(dimension) {
+      return this.selected[dimension]
+    },
+    getColor(name) {
+      const color = this.form.setting.config.color.find(item => item.name === name);
+      return color ? color.color : undefined
+    },
+    getDataCount(x, y, b = true) {
+      if (b) {
+        if (this.form.schema.x.type === 6) {
+          const xn = getTreeDataList(this.form.source.x, []).find(item => item.name === x)
+          if (xn) {
+            x = xn.id
+          }
+        }
+        if (this.form.schema.y.type === 6) {
+          const yn = getTreeDataList(this.form.source.y, []).find(item => item.name === y)
+          if (yn) {
+            y = yn.id
+          }
+        }
+      }
+      if (x && y) {
+        if (this.count[x]) {
+          return this.count[x][y] || 0
+        } else {
+          return 0
+        }
+      }
+      if (x && !y) {
+        return this.count[x] || 0
+      }
+      if (!x && !y) {
+        return random(0, 100)
+      }
+    },
+    getSeriesData() {
+      let data = [], count = this.count
+      let source = this.selected.x
+      switch (this.$options.name) {
+        case 'CLine':
+        case 'CArea':
+        case 'CBar':
+        case 'CColumn':
+          source.map(s => {
+            const color = this.form.setting.config.color.find(item => item.name === s);
+            data.push({
+              value: this.getDataCount(s),
+              itemStyle: {
+                color: color ? color.color : undefined
+              }
+            })
+          })
+          break
+        case 'CDoughnut':
+        case 'CPie':
+          source.map(s => {
+            const color = this.form.setting.config.color.find(item => item.name === s);
+            data.push({
+              value: this.getDataCount(s),
+              name: s,
+              label: {
+                show: this.form.setting.dataLabel,
+                fontSize: this.form.setting.dataSize,
+                fontFamily: this.form.setting.fontFamily2,
+                color: this.form.setting.labelColor,
+                position: this.form.setting.dataPosition,
+                formatter: ({ value, name, percent }) => {
+                  let text = ""
+                  if (this.form.setting.dataLabel) {
+                    text += name
+                  }
+                  if (this.form.setting.dataLabel2) {
+                    text += `-${value}件`
+                  }
+                  if (this.form.setting.dataLabel3) {
+                    text += `/${percent}%`
+                  }
+                  return text
+                }
+              },
+              itemStyle: {
+                color: color ? color.color : undefined
+              }
+            })
+          })
+          break
+      }
+      return data
+    },
+    getAxisLabel(type, axis) {
+      return  {
+        interval: axis === 'x' ? (this.form.setting.interval ? 0 : undefined) : (this.form.setting.interval2 ? 0 : undefined),
+        rotate: axis === 'x' ? this.form.setting.rotate : this.form.setting.rotate2,
+        color: axis === 'x' ? this.form.setting.fontColor : this.form.setting.fontColor2,
+        show: axis === 'x' ? this.form.setting.show : this.form.setting.show2,
+        fontSize: axis === 'x' ? this.form.setting.fontSize : this.form.setting.fontSize2,
+        fontFamily: axis === 'x' ? this.form.setting.fontFamily : this.form.setting.fontFamily2,
+        fontWeight: (axis === 'x' ? this.form.setting.fontWeight : this.form.setting.fontWeight2) ? 'bold' : 'normal'
+      }
+    },
+    getNameTextStyle(axis) {
+      return {
+        padding: axis === 'x' ? [this.form.setting.paddingTop, this.form.setting.paddingLeft, this.form.setting.paddingBottom, this.form.setting.paddingRight] : [this.form.setting.paddingTop2, this.form.setting.paddingLeft2, this.form.setting.paddingBottom2, this.form.setting.paddingRight2],
+        color: axis === 'x' ? this.form.setting.fontColor : this.form.setting.fontColor2,
+        fontSize: axis === 'x' ? this.form.setting.fontSize : this.form.setting.fontSize2,
+        fontFamily: axis === 'x' ? this.form.setting.fontFamily : this.form.setting.fontFamily2,
+        fontWeight: (axis === 'x' ? this.form.setting.fontWeight : this.form.setting.fontWeight2) ? 'bold' : 'normal'
+      }
+    },
+    dateTimeSort() {
+      let temp = JSON.parse(JSON.stringify(this.selected.x))
+      let data = []
+      let t, y, m
+      temp.map(item => {
+        switch (this.form.schema.x.expand) {
+          case 9:
+          case 12:
+            data.push({
+              name: item,
+              time: new Date(item).getTime()
+            })
+            break
+          case 10:
+            t = item.split('-')
+            y = t[0]
+            m = (parseInt(t[1].replaceAll('Q', '')) - 1) * 3 + 1
+            data.push({
+              name: item,
+              time: new Date(y + '-' + m).getTime()
+            })
+            break
+          case 11:
+            t = item.split('-')
+            y = t[0]
+            m = parseInt(t[1].replaceAll('H', '')) * 6
+            data.push({
+              name: item,
+              time: new Date(y + '-' + m).getTime()
+            })
+            break
+          case 13:
+          case 14:
+          case 15:
+            t = item.split('-')
+            y = t[0]
+            data.push({
+              name: item,
+              time: new Date(y + '-01').getTime()
+            })
+            break
+        }
+      })
+      this.selected.x = data.sort((a, b) => a.time - b.time).map(item => item.name)
+    },
+    get1AxisOption(type, areaStyle) {
+      if (this.form.schema.x.ptype === 2) {
+        this.dateTimeSort()
+      }
+      let xAxis = {
+        name: this.getAxisName('title1'),
+        nameTextStyle: this.getNameTextStyle('x'),
+        type: 'category',
+        data: this.getAxisData('x'),
+        nameLocation: this.form.setting.nameLocation,
+        splitLine: {
+          show: this.form.setting.splitLine
+        },
+        axisLabel: this.getAxisLabel('category', 'x')
+      }
+      let yAxis = {
+        name: this.getAxisName('title2'),
+        nameTextStyle: this.getNameTextStyle('y'),
+        type: 'value',
+        nameLocation: this.form.setting.nameLocation2,
+        splitLine: {
+          show: this.form.setting.splitLine2
+        },
+        axisLabel: this.getAxisLabel('value', 'y')
+      }
+      let series = [{
+        data: this.getSeriesData(),
+        type: type,
+        areaStyle: areaStyle,
+        itemStyle: {
+          color: this.form.setting.config.color[0].color
+        },
+        barWidth: this.form.setting.barWidth,
+        label: this.getDataLabel()
+      }]
+      let grid = {
+        top: this.form.setting.gridTop + '%',
+        left: this.form.setting.gridLeft + '%',
+        right: this.form.setting.gridRight + '%',
+        bottom: this.form.setting.gridBottom + '%',
+        containLabel: true,
+      }
+      return {
+        xAxis,
+        yAxis,
+        series,
+        grid
+      }
+    },
+    get2AxisOption(type, stack) {
+      if (this.form.schema.x.ptype === 2) {
+        this.dateTimeSort()
+      }
+      const enable = this.form.setting.config.line.enable
+      const operator = this.form.setting.config.line.operator
+      let series = this.selected.y.map(y => {
+        return {
+          name: y,
+          type: type,
+          stack: stack,
+          itemStyle: {
+            color: this.getColor(y)
+          },
+          barWidth: this.form.setting.barWidth,
+          data: this.selected.x.map(x => this.getDataCount(x, y)),
+          label: this.getDataLabel()
+        }
+      })
+      let xAxis = {
+        name: this.form.setting.title1,
+        nameTextStyle: this.getNameTextStyle('x'),
+        type: 'category',
+        nameLocation: this.form.setting.nameLocation,
+        data: this.selected.x,
+        splitLine: {
+          show: this.form.setting.splitLine
+        },
+        axisLabel: this.getAxisLabel('category', 'x')
+      }
+      let yAxis = [{
+        name: this.form.setting.title2,
+        nameTextStyle: this.getNameTextStyle('y'),
+        type: 'value',
+        nameLocation: this.form.setting.nameLocation2,
+        splitLine: {
+          show: this.form.setting.splitLine2
+        },
+        axisLabel: this.getAxisLabel('value', 'y')
+      }]
+      let legend = [...this.selected.y]
+      if (enable && ([5, 6].indexOf(this.form.setting.type) !== -1)) {
+        let name = this.form.setting.config.line.name
+        series.push({
+          name: name,
+          type: 'line',
+          yAxisIndex: 1,
+          data: getLineDataArr(this.form, this.selected, this.count),
+          itemStyle: {
+            color: this.form.setting.config.line.color
+          },
+          label: {
+            ...this.getDataLabel(),
+            formatter({ value }) {
+              return operator === 1 ? `${Math.round(parseFloat(value) * 100)} %` : value
+            }
+          }
+        })
+        yAxis.push({
+          type: 'value',
+          name: name,
+        })
+        legend.push(name)
+      }
+      let grid = {
+        top: this.form.setting.gridTop + '%',
+        left: this.form.setting.gridLeft + '%',
+        right: this.form.setting.gridRight + '%',
+        bottom: this.form.setting.gridBottom + '%',
+        containLabel: true
+      }
+      return {
+        legend: {
+          show: this.form.setting.legend,
+          data: legend,
+          textStyle: {
+            fontFamily: this.form.setting.fontFamily3,
+            color: this.form.setting.legendColor,
+            fontSize: this.form.setting.legendFontSize
+          },
+          ...this.getLegendLocation(),
+          type: this.form.setting.type2,
+        },
+        grid,
+        xAxis,
+        yAxis,
+        series
+      }
+    },
+    getLegendLocation() {
+      let x = ''
+      let y = ''
+      switch (this.form.setting.legendLocation) {
+        case 'top':
+          x = 'center'
+          y = 'top'
+          break
+        case 'left':
+          x = 'left'
+          y = 'center'
+          break
+        case 'bottom':
+          x = 'center'
+          y = 'bottom'
+          break
+        case 'right':
+          x = 'right'
+          y = 'center'
+          break
+      }
+      return {
+        x: x,
+        y: y,
+        orient: ['top', 'bottom'].indexOf(this.form.setting.legendLocation) !== -1 ? 'horizontal' : "vertical",
+      }
+    },
+    initChart(id, chartOption) {
+      const chartDom = document.getElementById(id);
+      const myChart = this.$echarts.init(chartDom);
+      myChart.setOption(chartOption)
+      this.$store.commit('SET_MY_CHART', myChart)
+      return myChart
+    },
+  }
+}
+
+export const chartOptionMixins = {
+  mixins: [chartOption],
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart(this.id, this.getOption())
+    })
+  },
+}

+ 628 - 0
src/views/analyse/report/create/components/PatentList.vue

@@ -0,0 +1,628 @@
+<template>
+  <el-container class="patent-setting">
+    <el-header>
+      <div style="margin-left: 15px;">
+        <el-button type="success" size="small" icon="el-icon-check" @click="handleCheck(1)">列表全选</el-button>
+        <el-button type="danger" size="small" icon="el-icon-minus" @click="handleCheck(0)">全部取消</el-button>
+      </div>
+      <div style="float: right;margin-right: 25px;">
+        <el-popover
+            placement="bottom"
+            title=""
+            width="300"
+            trigger="click"
+        >
+          <div style="height: 400px;overflow-y: auto">
+            <div v-for="item in reportList" style="margin-bottom: 10px;line-height: 30px;background: #e4e4e4;padding: 5px;margin-right: 5px;border-radius: 5px;">
+              <div>
+                <span>报告名称:{{ item.name }}</span>
+              </div>
+              <div>
+                <span>创建时间:{{ item.createTime }}</span>
+              </div>
+              <div>
+                <span>下载文件:</span>
+                <el-link type="primary" v-if="item.status === 1" @click.native="handleDownload(item)">点击下载</el-link>
+                <span v-else-if="item.status === 0">等待文件生成</span>
+                <span v-else style="color: red">生成失败</span>
+              </div>
+            </div>
+          </div>
+          <el-button type="primary" size="small" slot="reference">
+            <span>生成记录</span>
+            <span style="padding-left: 5px;">({{ reportList.length }})</span>
+          </el-button>
+        </el-popover>
+      </div>
+    </el-header>
+    <el-main style="background: #fff" v-if="showList">
+      <el-table :data="dataList" border header-row-class-name="custom-table-header">
+        <el-table-column prop="" label="" width="55" align="center">
+          <template slot-scope="scope">
+            <el-checkbox :checked="getValueIndex(scope.row.id) !== -1" @change="handleClick(scope.row)"></el-checkbox>
+          </template>
+        </el-table-column>
+        <el-table-column prop="patentno" label="专利号" show-overflow-tooltip align="center"></el-table-column>
+        <el-table-column prop="applicationno" label="申请号" show-overflow-tooltip align="center"></el-table-column>
+        <el-table-column prop="name" label="专利名称" show-overflow-tooltip align="center"></el-table-column>
+        <el-table-column label="申请人" show-overflow-tooltip align="center">
+          <template slot-scope="scope">
+            <div v-for="(name, index) in scope.row.applicant">{{ name }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="权利人" show-overflow-tooltip align="center">
+          <template slot-scope="scope">
+            <div v-for="(name, index) in scope.row.obligee">{{ name }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="专利类型" show-overflow-tooltip align="center">
+          <template slot-scope="scope">
+            <table-tag style="display: inline-block" :dict-data="$constants.patentType" :value="scope.row.type" effect="dark" size="small" />
+          </template>
+        </el-table-column>
+        <el-table-column label="法律状态" show-overflow-tooltip align="center">
+          <template slot-scope="scope">
+            <table-tag style="display: inline-block" :dict-data="$constants.patentSimpleStatus" :value="scope.row.status" effect="dark" size="small" />
+          </template>
+        </el-table-column>
+        <el-table-column label="标签" show-overflow-tooltip align="center">
+          <template slot-scope="scope">
+            <div v-for="(label, index) in scope.row.label">
+              <span class="tag" :style="{ background: colorList[index % 10] }">{{ label }}</span>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div @click="drawer = true" class="search-option">
+        <i class="el-icon-d-arrow-left" style="font-size: 20px;"></i>
+      </div>
+    </el-main>
+
+    <el-drawer :withHeader="false" :visible.sync="drawer" size="350px" direction="rtl" :before-close="beforeClose">
+      <el-container>
+        <el-header class="search-tabs">
+          <el-tabs v-model="activeName" type="card">
+            <el-tab-pane label="专利信息" name="1"></el-tab-pane>
+            <el-tab-pane label="自定义字段" name="2"></el-tab-pane>
+          </el-tabs>
+        </el-header>
+        <el-main style="padding: 0;">
+          <el-collapse v-model="activeNames" v-if="activeName === '1'">
+            <el-collapse-item title="申请人" name="1">
+              <el-container>
+                <el-main style="max-height: 200px;">
+                  <el-checkbox-group v-model="queryParams.applicant">
+                    <el-row v-for="item in searchData.applicant">
+                      <el-checkbox :label="item.name"></el-checkbox>
+                    </el-row>
+                  </el-checkbox-group>
+                </el-main>
+              </el-container>
+            </el-collapse-item>
+            <el-collapse-item title="权利人" name="2">
+              <el-container>
+                <el-main style="max-height: 200px;">
+                  <el-checkbox-group v-model="queryParams.obligee">
+                    <el-row v-for="item in searchData.obligee">
+                      <el-checkbox :label="item.name"></el-checkbox>
+                    </el-row>
+                  </el-checkbox-group>
+                </el-main>
+              </el-container>
+            </el-collapse-item>
+            <el-collapse-item title="法律状态" name="3">
+              <el-container>
+                <el-main style="max-height: 200px;">
+                  <el-checkbox-group v-model="queryParams.status">
+                    <el-row v-for="item in $constants.patentSimpleStatus">
+                      <el-checkbox :label="item.value" v-if="patentList.map(patent => patent.status).indexOf(item.value) !== -1">{{ item.label }}</el-checkbox>
+                    </el-row>
+                  </el-checkbox-group>
+                </el-main>
+              </el-container>
+            </el-collapse-item>
+            <el-collapse-item title="专利类型" name="4">
+              <el-container>
+                <el-main style="max-height: 200px;">
+                  <el-checkbox-group v-model="queryParams.type">
+                    <el-row v-for="item in $constants.patentType">
+                      <el-checkbox :label="item.value" v-if="patentList.map(patent => patent.type).indexOf(item.value) !== -1">{{ item.label }}</el-checkbox>
+                    </el-row>
+                  </el-checkbox-group>
+                </el-main>
+              </el-container>
+            </el-collapse-item>
+          </el-collapse>
+
+          <el-collapse v-model="activeNames2" v-if="activeName === '2'">
+            <el-collapse-item title="标签" name="1">
+              <el-container>
+                <el-main style="max-height: 200px;">
+                  <el-checkbox-group v-model="queryParams2.label">
+                    <el-row v-for="item in searchData2.label">
+                      <el-checkbox :label="item"></el-checkbox>
+                    </el-row>
+                  </el-checkbox-group>
+                </el-main>
+              </el-container>
+            </el-collapse-item>
+            <el-collapse-item title="文件夹" name="2">
+              <el-container>
+                <el-main style="max-height: 400px;">
+                  <el-tree
+                      ref="folder"
+                      :default-checked-keys="queryParams2.folder"
+                      :data="searchData2.field.folder"
+                      :props="defaultProps"
+                      show-checkbox
+                      node-key="id"
+                      check-strictly
+                      @check-change="handleCheckChange('folder')"
+                  ></el-tree>
+                </el-main>
+              </el-container>
+            </el-collapse-item>
+            <el-collapse-item title="标引" name="3">
+              <el-container>
+                <el-main style="max-height: 500px;">
+                  <template v-for="(item, index) in searchData2.field.index">
+                    <p class="field-title">{{ item.name }}</p>
+                    <template v-if="item.type === 3 || item.type === 4 || item.type === 5">
+                      <el-checkbox-group v-model="queryParams2.field[item.id]">
+                        <el-row v-for="option in item.option">
+                          <el-checkbox :label="option.id">{{ option.name }}</el-checkbox>
+                        </el-row>
+                      </el-checkbox-group>
+                    </template>
+                    <template v-if="item.type === 1">
+                      <el-date-picker v-model="queryParams2.field[item.id]" type="date" size="small" placeholder="选择日期" style="width: 100%;"></el-date-picker>
+                    </template>
+                    <template v-if="item.type === 2">
+                      <el-input v-model="queryParams2.field[item.id]" placeholder="请输入内容" size="small"></el-input>
+                    </template>
+                  </template>
+                </el-main>
+              </el-container>
+            </el-collapse-item>
+            <el-collapse-item title="分类" name="4">
+              <el-container>
+                <el-main style="max-height: 500px;">
+                  <template v-for="(item, index) in searchData2.field.classify">
+                    <p class="field-title">{{ item.name }}</p>
+                    <el-tree
+                        :ref="item.id"
+                        :default-checked-keys="queryParams2.field[item.id]"
+                        :data="item.option"
+                        :props="defaultProps"
+                        show-checkbox
+                        node-key="id"
+                        check-strictly
+                        @check-change="handleCheckChange(item.id)"
+                    ></el-tree>
+                  </template>
+                </el-main>
+              </el-container>
+            </el-collapse-item>
+          </el-collapse>
+        </el-main>
+        <el-footer style="text-align: right;height: 70px;">
+          <el-button @click="resetSearchQuery">重置</el-button>
+          <el-button type="primary" @click="handleSearch">搜索</el-button>
+        </el-footer>
+      </el-container>
+    </el-drawer>
+
+    <el-footer style="height: 70px;text-align: right">
+      <el-button>关 闭</el-button>
+      <el-button type="primary" @click="handleConfirm">确 定</el-button>
+    </el-footer>
+  </el-container>
+</template>
+
+<script>
+import { downLoad2, formatDate, getFileName } from "@/utils";
+import TableTag from "@/components/TableTag";
+import { mapGetters } from "vuex";
+export default {
+  components: { TableTag },
+  data() {
+    return {
+      activeNames: [],
+      activeNames2: [],
+      activeName: '1',
+      showList: true,
+      refresh: true,
+      dataList: [],
+      queryParams: {},
+      queryParams2: {},
+      drawer: false,
+      reportList: [],
+      searchData: {
+        applicant: [],
+        status: [],
+        obligee: [],
+        type: []
+      },
+      colorList: [
+        '#409eff',
+        '#67c23a',
+        '#909399',
+        '#f56c6c',
+        '#e6a23c',
+        '#e63c91',
+        '#e6553c',
+        '#3ce6cc',
+        '#8e3ce6',
+        '#e63c3c'
+      ],
+      searchData2: {
+        field: {},
+        label: []
+      },
+      defaultProps: {
+        children: 'children',
+        label: 'name'
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['patentField', 'customField']),
+    template() {
+      return this.$store.state.report.template
+    },
+    patentList() {
+      return this.$store.state.report.patentList
+    },
+    form() {
+      return this.$store.state.report.form
+    },
+    activeItem() {
+      return this.$store.state.report.activeItem
+    },
+    patentKey() {
+      return this.$store.state.chart.patentKey
+    }
+  },
+  mounted() {
+    this.getReportList()
+    if (this.template.label.length) {
+      this.resetSearchQuery()
+      for (let key in this.searchData) {
+        this.getSearchData(key)
+      }
+      for (let key in this.searchData2) {
+        this.getSearchData2(key)
+      }
+    }
+  },
+  methods: {
+    getReportList() {
+      this.$api.getReportList({ patentKey: this.patentKey }).then(response => {
+        this.reportList = response.data
+      })
+    },
+    handleConfirm() {
+      this.$emit('submit', {})
+    },
+    handleCheckChange(id) {
+      if (id === 'folder') {
+        const data = this.$refs[id].getCheckedNodes()
+        this.queryParams2.folder = data.map(item => item.id)
+      } else {
+        const data = this.$refs[id][0].getCheckedNodes()
+        this.$set(this.queryParams2.field, id, data.map(item => item.id))
+      }
+    },
+    getSearchData2(key) {
+      switch (key) {
+        case 'label':
+          let value = [];
+          this.dataList.map(item => {
+            item.label.map(v => {
+              value.push(v)
+            })
+          })
+          this.$set(this.searchData2, key, [...new Set(value)])
+          break
+        case 'field':
+          let data = {};
+          for (let field in this.customField) {
+            switch (field) {
+              case 'folder':
+                data[field] = this.customField[field];
+                break
+              case 'classify':
+              case 'index':
+                let ids = new Set();
+                let arr = []
+                this.dataList.map(item => item.field.map(f => {
+                  ids.add(f.id)
+                }));
+                this.customField[field].map(item => {
+                  if ([...ids].indexOf(item.id) !== -1) {
+                    arr.push(item)
+                  }
+                });
+                data[field] = arr
+                break
+            }
+          }
+          this.resetIndexFieldId()
+          this.$set(this.searchData2, key, data)
+          break
+      }
+    },
+    resetIndexFieldId() {
+      this.customField.index.map(item => {
+        if (item.type === 1 || item.type === 2) {
+          this.$set(this.queryParams2.field, item.id, null)
+        } else {
+          this.$set(this.queryParams2.field, item.id, [])
+        }
+      })
+    },
+    handleSelect(index, indexPath) {
+      this.activeItem = index
+    },
+    beforeClose() {
+      this.drawer = false
+    },
+    resetSearchQuery() {
+      this.queryParams = {
+        applicant: [],
+        status: [],
+        name: "",
+        no: "",
+        obligee: [],
+        type: [],
+      }
+      this.queryParams2 = {
+        field: {},
+        label: [],
+        folder: []
+      }
+      this.resetIndexFieldId()
+      this.activeNames = []
+      this.activeNames2 = []
+      this.activeName = '1'
+      this.dataList = JSON.parse(JSON.stringify(this.patentList))
+      this.drawer = false
+      this.refreshTableList()
+    },
+    handleSearch() {
+      let dataList = JSON.parse(JSON.stringify(this.patentList))
+      for (let key in this.queryParams) {
+        if (this.queryParams[key].length) {
+          if (key === 'applicant' || key === 'obligee') {
+            dataList = dataList.map(item => {
+              for (let value of item[key]) {
+                if (this.queryParams[key].indexOf(value) !== -1) {
+                  return item
+                }
+              }
+            }).filter(item => item !== undefined)
+          } else {
+            dataList = dataList.filter(item => this.queryParams[key].indexOf(item[key]) !== -1)
+          }
+        }
+      }
+      if (this.queryParams2.label.length) {
+        dataList = dataList.map(item => {
+          for (let label of item.label) {
+            if (this.queryParams2.label.indexOf(label) !== -1) {
+              return item
+            }
+          }
+        }).filter(item => item !== undefined)
+      }
+      if (this.queryParams2.folder.length) {
+        dataList = dataList.map(item => {
+          for (let folder of item.folder) {
+            if (this.queryParams2.folder.indexOf(folder) !== -1) {
+              return item
+            }
+          }
+        }).filter(item => item !== undefined)
+      }
+      for (let key in this.queryParams2.field) {
+        if (!this.queryParams2.field[key] || (this.queryParams2.field[key] instanceof Array && this.queryParams2.field[key].length === 0)) {
+          continue
+        }
+        const index = this.searchData2.field.index.find(index => index.id === parseInt(key))
+        const classify = this.searchData2.field.classify.find(index => index.id === parseInt(key))
+        dataList = dataList.map(item => {
+          for (let field of item.field) {
+            if (field.id === parseInt(key)) {
+              if (index) {
+                switch (index.type) {
+                  case 0:
+                  case 1:
+                  case 2:
+                    let data
+                    if (index.type === 1) {
+                      data = formatDate(this.queryParams2.field[key]);
+                    } else {
+                      data = this.queryParams2.field[key]
+                    }
+                    if (data == field.value) {
+                      return item
+                    }
+                    break
+                  case 3:
+                  case 4:
+                    if (this.queryParams2.field[key].indexOf(field.value) !== -1) {
+                      return item
+                    }
+                    break
+                  case 5:
+                    for (let value of field.value) {
+                      if (this.queryParams2.field[key].indexOf(value) !== -1) {
+                        return item
+                      }
+                    }
+                    break
+                }
+              } else if (classify) {
+                for (let value of field.value) {
+                  if (this.queryParams2.field[key].indexOf(value) !== -1) {
+                    return item
+                  }
+                }
+              }
+            }
+          }
+        }).filter(item => item !== undefined)
+      }
+      // console.log(dataList)
+      this.dataList = dataList
+      this.drawer = false
+      this.refreshTableList()
+    },
+    getSearchData(key) {
+      let temp = []
+      this.dataList.map(item => {
+        if (item[key] instanceof Array) {
+          item[key].map(c => temp.push(c))
+        } else {
+          temp.push(item[key])
+        }
+      })
+      this.$set(this.searchData, key, [...new Set(temp)].map(item => {
+        return {
+          name: item,
+          count: 0
+        }
+      }))
+    },
+    getLabelValue() {
+      const label = this.form.patentList.find(item => item.label === parseInt(this.activeItem))
+      if (label === undefined) {
+        return false
+      }
+      return label
+    },
+    getValueIndex(id) {
+      const label = this.getLabelValue()
+      return label ? label.id.indexOf(id) : -1
+    },
+    handleDownload(item) {
+      downLoad2(item.path)
+    },
+    handleCheck(type) {
+      const ids = this.dataList.map(item => item.id)
+      const label = parseInt(this.activeItem)
+      let patentList = this.form.patentList.find(item => item.label === label)
+      switch (type) {
+        case 0:
+          if (patentList) {
+            ids.map(id => {
+              const index = patentList.id.indexOf(id)
+              if (index !== -1) {
+                patentList.id.splice(index, 1)
+              }
+            })
+          }
+          break
+        case 1:
+          if (patentList) {
+            ids.map(id => {
+              if (patentList.id.indexOf(id) === -1) {
+                patentList.id.push(id)
+              }
+            })
+          } else {
+            this.form.patentList.push({
+              id: ids,
+              label: label
+            })
+          }
+          break
+      }
+      this.refreshTableList()
+    },
+    refreshTableList() {
+      this.showList = false
+      this.$nextTick(() => {
+        this.showList = true
+      })
+    },
+    handleClick(item) {
+      let label = this.getLabelValue()
+      if (!label) {
+        this.form.patentList.push({
+          id: [item.id],
+          label: parseInt(this.activeItem)
+        })
+        return false
+      }
+      const index = this.getValueIndex(item.id)
+      if (index !== -1) {
+        label.id.splice(index, 1)
+      } else {
+        label.id.push(item.id)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less">
+.patent-setting {
+  .el-menu-item.is-active {
+    outline: 0;
+    background-color: #ecf5ff !important;
+  }
+  .el-collapse-item__header {
+    padding: 0 20px !important;
+  }
+  .search-tabs {
+    .el-tabs__header {
+      margin-top: 35px !important;
+    }
+  }
+  .el-collapse {
+    border-top: 0 !important;
+  }
+  .field-title {
+    font-weight: bold;
+    margin: 0 !important;
+    padding: 10px 0;
+  }
+  .title {
+    .name {
+      font-size: 17px;
+      color: #409EFF;
+    }
+    .el-tag {
+      margin-left: 10px;
+    }
+    .no {
+      padding-left: 15px;
+      color: #515a6e;
+      font-size: 13px;
+    }
+  }
+  .applicant, .obligee, .type, .label {
+    margin-top: 10px;
+    .title {
+      width: 70px;
+      float: left;
+      font-weight: bold;
+    }
+    .name {
+      overflow: hidden;
+      span {
+        padding-right: 5px;
+      }
+    }
+  }
+  .tag {
+    display: inline-block;
+    color: #fff;
+    padding: 0px 5px;
+    border-radius: 4px;
+    margin-right: 5px;
+    margin-top: 5px;
+  }
+}
+</style>

+ 282 - 0
src/views/analyse/report/create/index.vue

@@ -0,0 +1,282 @@
+<template>
+  <div class="create-report">
+    <el-container v-if="showPage" v-loading="loading">
+      <el-aside width="300px">
+        <el-container>
+          <el-main class="nopadding">
+            <el-header style="padding: 0 20px !important;">
+              <el-button type="primary" size="small" style="width: 100%" @click="handleChangeTemplate">选择报告模板</el-button>
+            </el-header>
+            <div v-if="showItem">
+              <el-menu :default-active="activeItem" style="height: 100%;width: 100%;" @select="handleSelect">
+                <el-menu-item v-for="item in template.label" :index="String(item.id)">
+                  <span slot="title">{{ item.name }}</span>
+                </el-menu-item>
+              </el-menu>
+            </div>
+          </el-main>
+        </el-container>
+      </el-aside>
+      <el-main style="background: #fff;padding: 0">
+        <patent-list ref="patentList" v-if="showList" @submit="handleConfirm2" />
+      </el-main>
+    </el-container>
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="900px" :before-close="handleClose">
+      <el-form inline>
+        <el-form-item label="模板名称">
+          <el-input v-model="queryParams.name" placeholder="请输入模板名称" size="small"></el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="" size="small" @click="getTemplateList">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table ref="multipleTable" border :data="templateList" header-row-class-name="custom-table-header" v-loading="loading2">
+        <el-table-column prop="" label="选择" width="55" align="center">
+          <template slot-scope="scope">
+            <el-radio v-model="templateId" :label="scope.row.id"></el-radio>
+          </template>
+        </el-table-column>
+        <el-table-column prop="name" label="模板名称" show-overflow-tooltip align="center"></el-table-column>
+        <el-table-column label="使用权限" show-overflow-tooltip align="center">
+          <template slot-scope="scope">
+            <table-tag :dict-data="$constants.permissions" :value="scope.row.permissions" size="small" effect="dark" />
+          </template>
+        </el-table-column>
+        <el-table-column label="所属专题库" show-overflow-tooltip align="center">
+          <template slot-scope="scope">
+            <span>{{ scope.row.projectName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="" label="创建人" show-overflow-tooltip align="center">
+          <template slot-scope="scope">
+            <span>{{ scope.row.createName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createTime" label="创建日期" show-overflow-tooltip align="center"></el-table-column>
+      </el-table>
+      <div class="pagination">
+        <el-pagination :current-page.sync="queryParams.current" :page-size="queryParams.size" :total="total" @current-change="handleCurrentChange" layout="total, prev, pager, next, jumper" background></el-pagination>
+      </div>
+      <div style="text-align: right">
+        <el-button @click="handleClose">取 消</el-button>
+        <el-button type="primary" @click="handleConfirm">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="title" :visible.sync="dialogVisible2" width="450px" :before-close="handleClose2">
+      <el-form :model="form" :rules="rules" ref="ruleForm" label-width="80px">
+        <el-form-item label="报告名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入报告名称"></el-input>
+        </el-form-item>
+        <el-form-item label="备注信息">
+          <el-input type="textarea" v-model="form.remark" placeholder="请输入内容"></el-input>
+        </el-form-item>
+      </el-form>
+      <div style="text-align: right">
+        <el-button @click="handleClose2">取 消</el-button>
+        <el-button type="primary" @click="submit" :loading="loadingBtn">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+import TableTag from "@/components/TableTag";
+import PatentList from "./components/PatentList";
+
+export default {
+  components: {
+    TableTag,
+    PatentList,
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      dialogVisible2: false,
+      showItem: false,
+      showList: false,
+      loading: false,
+      loading2: false,
+      loading3: false,
+      showPage: true,
+      activeItem: '',
+      title: '',
+      template: {},
+      templateId: 0,
+      rules: {
+        name: [{ required: true, message: '请输入报告名称', trigger: 'blur' },],
+      },
+      queryParams: {
+        name: '',
+        current: 1,
+        size: 10,
+        projectId: 0,
+        permissions: 1
+      },
+      colorList: [
+        '#409eff',
+        '#67c23a',
+        '#909399',
+        '#f56c6c',
+        '#e6a23c',
+        '#e63c91',
+        '#e6553c',
+        '#3ce6cc',
+        '#8e3ce6',
+        '#e63c3c'
+      ],
+      total: 0,
+      patentList: [],
+      templateList: [],
+      loadingBtn: false,
+    }
+  },
+  computed: {
+    ...mapGetters(['projectId', 'webSocket', 'userinfo']),
+    form() {
+      return this.$store.state.report.form
+    },
+    patentKey() {
+      return this.$store.state.chart.patentKey
+    }
+  },
+  async mounted() {
+    this.resetForm()
+    this.loading = true
+    this.queryParams.projectId = this.projectId
+    await this.getTemplateList()
+    await this.getPatentList()
+    this.loading = false
+    this.handleChangeTemplate()
+    this.initWebSocket()
+  },
+  methods: {
+    initWebSocket() {
+      this.webSocket.onmessage = (e) => {
+        const data = JSON.parse(e.data)
+        if (data.data.success) {
+          this.$message.success('任务完成')
+          this.$refs.patentList.getReportList()
+        }
+      }
+    },
+    close() {
+
+    },
+    resetForm() {
+      this.$store.commit('SET_FORM')
+      this.form.projectId = this.projectId
+      this.form.patentKey = this.patentKey
+    },
+    handleConfirm() {
+      if (!this.templateId) {
+        this.$message.error('请选择模板')
+        return false
+      }
+      this.template = this.templateList.find(item => item.id === this.templateId)
+      this.activeItem = String(this.template.label[0].id)
+      this.$store.commit('SET_TEMPLATE', this.template)
+      this.$store.commit('SET_ACTIVE_ITEM', this.activeItem)
+      this.form.templateId = this.template.id
+      this.showItem = false
+      this.showList = false
+      this.$nextTick(() => {
+        this.showItem = true
+        this.showList = true
+      })
+      this.dialogVisible = false
+    },
+    submit() {
+      this.$refs.ruleForm.validate((valid) => {
+        if (valid) {
+          this.loadingBtn = true
+          if (this.form.id) {
+            this.$api.editNewReport(this.form).then(response => {
+              this.$message.success('更新成功')
+              this.dialogVisible2 = false
+              this.loadingBtn = false
+            }).catch(error => {
+              this.loadingBtn = false
+            })
+          } else {
+            this.$api.addNewReport(this.form).then(response => {
+              this.$message.success('保存成功')
+              this.dialogVisible2 = false
+              this.loadingBtn = false
+            }).catch(error => {
+              this.loadingBtn = false
+            })
+          }
+        } else {
+          this.$message.error('请完善表单内容')
+          return false;
+        }
+      });
+    },
+    handleConfirm2() {
+      this.title = '一键生成报告'
+      this.dialogVisible2 = true
+    },
+    handleCurrentChange(val) {
+      this.queryParams.current = val;
+      this.getTemplateList();
+    },
+    handleClose() {
+      this.dialogVisible = false
+    },
+    handleClose2() {
+      this.dialogVisible2 = false
+    },
+    handleSelect(index, indexPath) {
+      this.showList = false
+      this.activeItem = index
+      this.$store.commit('SET_ACTIVE_ITEM', this.activeItem)
+      this.$nextTick(() => {
+        this.showList = true
+      })
+    },
+    handleChangeTemplate() {
+      this.title = '选择报告模板'
+      this.dialogVisible = true
+    },
+    handleCheck(type) {
+      this.$refs.patentList.handleCheck(type)
+      this.showList = false
+      this.$nextTick(() => {
+        this.showList = true
+      })
+    },
+    async getTemplateList() {
+      try {
+        const response = await this.$api.getNewTemplateList(this.queryParams)
+        this.templateList = response.data.records
+        this.total = response.data.total
+      } catch (e) {
+        this.loading = false
+      }
+    },
+    async getPatentList() {
+      try {
+        const { data } = await this.$api.getReportPatentList({ patentKey: this.patentKey, projectId: this.projectId })
+        this.patentList = data
+        this.$store.commit('SET_PATENT_LIST', this.patentList)
+      } catch (e) {
+        this.loading = false
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less">
+.create-report {
+  .el-radio__label {
+    display: none !important;
+  }
+  .el-menu-item.is-active {
+    outline: 0;
+    background-color: #ecf5ff !important;
+  }
+}
+</style>

+ 142 - 0
src/views/analyse/report/record/index.vue

@@ -0,0 +1,142 @@
+<template>
+  <div>
+    <el-card shadow="never" style="height: 100%" :body-style="{height: '100%'}">
+      <el-container>
+        <el-header>
+          <el-form :inline="true">
+            <el-form-item label="报告名称">
+              <el-input v-model="queryParams.name" size="small" placeholder="请输入报告名称"></el-input>
+            </el-form-item>
+            <el-form-item label="创建人">
+              <el-input v-model="queryParams.createBy" size="small" placeholder="请输入创建人"></el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="" size="small" @click="getList">查询</el-button>
+            </el-form-item>
+          </el-form>
+        </el-header>
+        <el-main style="padding: 15px 0;">
+          <el-table v-loading="loading" :data="tableData" border header-row-class-name="custom-table-header">
+            <el-table-column type="index" label="序号" width="55" align="center"></el-table-column>
+            <el-table-column prop="name" label="报告名称" align="center" show-overflow-tooltip></el-table-column>
+            <el-table-column label="模板名称" align="center" show-overflow-tooltip>
+              <template slot-scope="scope">
+                <span v-if="scope.row.template">{{ scope.row.template.name }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="专利数量" align="center">
+              <template slot-scope="scope">
+                <el-tag type="success">{{ scope.row.patentNum }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="下载报告" align="center" width="100">
+              <template slot-scope="scope">
+                <img class="download-report-btn" src="@/assets/WORD.png" alt="点击下载报告" width="30" height="30" @click="handleDownLoad(scope.row)">
+              </template>
+            </el-table-column>
+            <el-table-column label="创建人" align="center" show-overflow-tooltip>
+              <template slot-scope="scope">
+                <span v-if="scope.row.user">{{ scope.row.user.name }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="createTime" label="创建时间" align="center" show-overflow-tooltip></el-table-column>
+            <el-table-column prop="remark" label="备注" align="center" show-overflow-tooltip></el-table-column>
+            <el-table-column label="操作" align="center" width="100">
+              <template slot-scope="scope">
+                <el-button type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div class="pagination">
+            <el-pagination :current-page.sync="queryParams.page" :page-size="queryParams.size" :total="total" @current-change="handleCurrentChange" layout="total, prev, pager, next, jumper" background></el-pagination>
+          </div>
+        </el-main>
+      </el-container>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import { downLoad, downLoad2 } from "@/utils";
+import moment from "moment";
+export default {
+  name: 'reportList',
+  components: {
+  },
+  data() {
+    return {
+      queryParams: {
+        name: '',
+        createBy: '',
+        page: 1,
+        size: 10,
+        projectId: ''
+      },
+      loading: true,
+      total: 0,
+      tableData: [],
+    }
+  },
+  computed: {
+    ...mapGetters(['userinfo', 'projectId'])
+  },
+  activated() {
+    this.queryParams.projectId = this.projectId
+    this.getList()
+  },
+  methods: {
+    handleCurrentChange(val) {
+      this.queryParams.page = val;
+      this.getList();
+    },
+    getList() {
+      this.loading = true
+      this.$api.getNewReportList(this.queryParams).then(response => {
+        this.tableData = response.data
+        this.total = response.total
+        this.loading = false
+      }).catch(error => {
+        this.loading = false
+      })
+    },
+    handleEdit(row) {
+      this.$router.push({
+        path: '/report/new/project/manage',
+        query: {
+          id: row.id
+        }
+      })
+    },
+    handleDownLoad(row) {
+      if (row.reportUrl) {
+        downLoad2(row.reportUrl)
+      } else {
+        this.$message.error('暂未生成报告文件')
+      }
+    },
+    handleDelete(row) {
+      this.$confirm('确认删除本条数据吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.loading = true
+        this.$api.deleteNewReport({id: row.id}).then(response => {
+          this.$message.success('删除成功')
+          this.loading = false
+          this.getList()
+        }).catch(error => {
+          this.loading = false
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style lang="less">
+.download-report-btn {
+  cursor: pointer;
+}
+</style>

+ 1 - 2
src/views/layout/index.vue

@@ -98,7 +98,6 @@ export default {
     //获取字典项
     DictMessage() {
       this.$api.DictMessage().then(response => {
-        // console.log("response", response);
         this.$store.commit("SET_DictMessage",response.data)
       })
     },
@@ -124,7 +123,7 @@ export default {
   //获取权限
   getPermissionList(){
     var code = "6a8d3f4d1d5f11eda41c00163e2f0200;5051395c24e311eda41c00163e2f0200;a97c468b924111ed905500163e2f0200"
-   this.$api.getPermissionList({code:code}).then(response=>{
+    this.$api.getPermissionList({code:code}).then(response=>{
         this.$store.commit('SET_ADMIN_PERMISSION', response.data)
       }).catch(error=>{
         this.$store.commit('SET_ADMIN_PERMISSION', [])