Selaa lähdekoodia

Merge branch 'master' into gn_approval

cldu 6 kuukautta sitten
vanhempi
commit
896b430bb7

+ 1 - 0
config/dev.env.js

@@ -4,6 +4,7 @@ var prodEnv = require('./prod.env');
 module.exports = merge(prodEnv, {
   NODE_ENV:'"development"',
   VUE_APP_API_ROOT:'"/adminapi"',  //
+  VUE_APP_BASE_URL:'"/"',
   Domain:'"brilliantstart.cn"',
   // Login:'"http://localhost:3030/login"',
   // CHART_LINK:'"https://charttest.hzinsights.com/chartshow"',

+ 1 - 1
config/index.js

@@ -13,7 +13,7 @@ module.exports = {
     assetsRoot: path.resolve(__dirname, '../eta_front'),
     assetsSubDirectory: 'static',
 //  assetsPublicPath: '/vue-admin/',
-		assetsPublicPath: './',
+		assetsPublicPath: process.env.VUE_APP_BASE_URL,
     productionSourceMap: false,
     // Gzip off by default as many popular static hosts such as
     // Surge or Netlify already gzip all static assets for you.

+ 2 - 1
config/prod.env.js

@@ -1,6 +1,7 @@
 module.exports = {
 	NODE_ENV:'"production"',
-	VUE_APP_API_ROOT:'"/adminapi"',  //生产环境
+	VUE_APP_API_ROOT:'"/ybeta/adminapi"',  //生产环境
+	VUE_APP_BASE_URL:'"/ybeta/"',
 	Domain:'"hzinsights.com"',
 	// Login:'"https://eta.hzinsights.com/login"',
 	// CHART_LINK:'"https://chartlib.hzinsights.com/chartshow"',

+ 1 - 0
config/prod.test.env.js

@@ -1,6 +1,7 @@
 module.exports = {
 	NODE_ENV:'"test"',
 	VUE_APP_API_ROOT:'"/adminapi"',  //测试环境
+	VUE_APP_BASE_URL:'"/"',
 	Domain:'"brilliantstart.cn"',
 	// Login:'"http://8.136.199.33:7778/login"',
 	// CHART_LINK:'"https://charttest.hzinsights.com/chartshow"',

+ 4 - 2
src/api/http.js

@@ -1,6 +1,7 @@
 "use strict";
 import axios from "axios";
 import bus from "./bus.js";
+import {router} from '../main'
 let store=null
 // 异步加载store模块 不异步加载的话,@/api/modules/oldApi.js的导入导出写法会有问题
 import('@/vuex/index.js')
@@ -77,14 +78,15 @@ function checkStatus(response,closableErrMsg) {
     } else if (res.Ret == 403) {
       errorMsgShow(closableErrMsg,res.Msg)
     } else if (res.Ret === 408) {
-      localStorage.setItem("auth", "")
+      // localStorage.setItem("auth", "")
       localStorage.setItem("loginTime", "")
       bus
         .$alert(res.Msg, "提示", {
           showClose: false,
         })
         .then(() => {
-          window.location.href = window.location.origin + '/login';
+          router.replace('/login')
+          // window.location.href = window.location.origin + '/login';
         });
     }
     return res;

+ 31 - 0
src/api/modules/BIBoard.js

@@ -0,0 +1,31 @@
+import http from "@/api/http.js"
+
+//区间分析
+export default{
+  //我i的看板列表
+  myBoardList:params=>{
+    return http.get('/bi_dashborad/my_list',{})
+  },
+  //共享看板列表
+  shareBoardList:params=>{
+    return http.get('/bi_dashborad/share_list',{})
+  },
+  //公共看板列表
+  commonBoardList:params=>{
+    return http.get('/bi_dashborad/public_list',{})
+  },
+  //看板详情
+  boardDetail:params=>{
+    return http.get('/bi_dashborad/detail',params)
+  },
+
+  // 新增看板
+  addBoard:params=>{
+    return http.post('/bi_dashborad/add',params)
+  },
+  // 编辑看板
+  editBoard:params=>{
+    return http.post('/bi_dashborad/edit',params)
+  },
+  
+}

+ 2 - 1
src/main.js

@@ -180,6 +180,7 @@ Array.prototype.flat = function (count) {
 };
 export const router = new VueRouter({
   mode: "history",
+  base:process.env.VUE_APP_BASE_URL,
   routes,
 });
 
@@ -201,7 +202,7 @@ router.beforeEach(async(to, from, next) => {
 
   let auth = localStorage.getItem("auth") || false;
   if (to.path != "/login" && to.path!='/temppage' &&to.path!='/fogetpassword' && !auth) {
-    window.location.href =  window.location.origin + '/login';
+    next('/login')
     return false;
   }
 

+ 33 - 0
src/routes/modules/BIRoutes.js

@@ -0,0 +1,33 @@
+const home = (r) => require.ensure([], () => r(require("@/views/Home.vue")), "Home"); //主页
+export default [
+  {
+    path: "/",
+    component: home,
+    name: "BI看板",
+    hidden: false,
+    icon_path: require("@/assets/img/home/data_ic.png"),
+    meta: {
+      name_en: "",
+    },
+    children: [
+      {
+        path: "BIBoard",
+        name: "BI看板",
+        component: () => import("@/views/BI_manage/index.vue"),
+        hidden: false,
+        meta: {
+          name_en: "",
+        },
+      },
+      {
+        path: "editBIBoard",
+        name: "添加看板",
+        component: () => import("@/views/BI_manage/editBoard.vue"),
+        hidden: false,
+        meta: {
+          name_en: "",
+        },
+      },
+    ],
+  },
+];

+ 14 - 1
src/utils/buttonConfig.js

@@ -866,6 +866,18 @@ export const chartThemePermission = {
     chartTheme_chartsource:'chartTheme:chartsource',//图表的数据来源
 }
 
+/* BI看板配置 */
+export const BIBoardPermission = {
+    BIBoard_add:'BI:addBoard',//添加
+    BIBoard_setshare:'BI:setShare',//设置共享
+    BIBoard_setcommon:'BI:setCommon',//
+    BIBoard_addclassify:'BI:setCommonClassify',//
+    BIBoard_edit:'BI:edit',//
+    BIBoard_delete:'BI:delete',//
+    BIBoard_refresh:'BI:refresh',//
+    BIBoard_sort:'BI:sort',//
+}
+
 
 //创建了新的ManageBtn记得添加到这里
 const btnMap  = {
@@ -884,7 +896,8 @@ const btnMap  = {
     approvePermission,
     outlinkConfigPermission,
     chartThemePermission,
-    toolBoxPermission
+    toolBoxPermission,
+    BIBoardPermission
 }
 
 /**

+ 107 - 0
src/views/BI_manage/components/BoardContent.vue

@@ -0,0 +1,107 @@
+<template>
+  <div class="BI-board-content">
+    <div class="BI-board-list">
+      <table-no-data v-if="value.length===0" style="flex:1"/>
+      <div
+        class="BI-board-item-box"
+        v-for="(item, index) in value"
+        :key="item.BiDashboardDetailId"
+        @dragover.prevent
+        @drop="drop(index)"
+      >
+        <component :is="getCompType(item.Type)" :compData="item">
+          <template v-slot:drag>
+            <!-- Draggable icon -->
+            <img
+              v-if="canDrag"
+              class="icon"
+              src="~@/assets/img/data_m/move_ico.png"
+              alt=""
+              @mousedown="setDraggable(true, index)"
+              @dragstart="dragStart(index)"
+              @dragend="setDraggable(false, index)"
+              style="cursor: move"
+            />
+          </template>
+          <template v-slot:delete>
+            <img class="icon" v-if="canDelete" src="~@/assets/img/icons/delete-red.png" alt="" @click="handleDel(index)"/>
+          </template>
+        </component>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import TableNoData from '../../../components/tableNoData.vue';
+import ChartBox from './ChartBox.vue';
+import TableBox from './TableBox.vue';
+
+export default {
+  components: { ChartBox, TableBox, TableNoData },
+  props: {
+    value: {
+      type: Array,
+      default: () => []
+    },
+    canDrag: {//能否拖动
+      type: Boolean,
+      default: true
+    },
+    canDelete:{//能否删除
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      draggedIndex: null,
+    };
+  },
+  methods: {
+    getCompType(type) {
+      return type === 1 ? ChartBox : TableBox;
+    },
+    handleDel(index){
+      this.value.splice(index,1)
+      this.$emit('delete',this.value)
+    },
+    setDraggable(draggable, index) {
+      const box = this.$el.querySelectorAll('.BI-board-item-box')[index];
+      box.draggable = draggable;
+    },
+    dragStart(index) {
+      this.draggedIndex = index;
+    },
+    drop(index) {
+      if (this.draggedIndex === null) return
+      // Swap the two items
+      const temp = this.value[this.draggedIndex];
+      this.$set(this.value, this.draggedIndex, this.value[index]);
+      this.$set(this.value, index, temp);
+      this.draggedIndex = null;
+      this.$emit('input',this.value)
+      this.$emit('change',this.value)
+    },
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.BI-board-content {
+  padding: 20px;
+  .BI-board-list {
+    height: calc(100vh - 300px);
+    overflow-y: auto;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+    .BI-board-item-box {
+      width: calc(50% - 14px);
+      height: 560px;
+      border: 1px solid #dcdfe6;
+      box-shadow: 0px 2px 8px 0px #00000014;
+    }
+  }
+}
+</style>

+ 292 - 0
src/views/BI_manage/components/ChartBox.vue

@@ -0,0 +1,292 @@
+<template>
+  <div class="chart-box" v-if="compData" ref="compRef" v-loading="loading">
+    <div class="top-title-box">
+      <div class="title" @click="goDetail">{{ chartInfo.ChartName }}</div>
+      <div class="opt-box">
+        <img
+          class="icon"
+          src="~@/assets/img/icons/refresh_blue_new.png"
+          alt=""
+          @click="refreshHandle"
+        />
+        <slot name="drag"></slot>
+        <slot name="delete"></slot>
+      </div>
+    </div>
+    <!-- 无权限 -->
+    <div class="nodata" v-if="chartInfo.HaveOperaAuth === false">
+      <noDataAuth :text="$t('MsgPrompt.no_chart_auth')" />
+    </div>
+    <div class="chart-render-wrap" v-else>
+      <Chart
+        minHeight="480px"
+        height="480px"
+        :options="options"
+        :chartInfo="chartInfo"
+        :index="compData.BiDashboardDetailId"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import Chart from '@/views/dataEntry_manage/components/chart';
+import { chartSetMixin } from '@/views/dataEntry_manage/mixins/chartPublic'
+import { dataBaseInterface } from '@/api/api.js';
+import chartRelevanceApi from '@/api/modules/chartRelevanceApi';
+import apiIntervalAnalysis from '@/api/modules/intervalAnalysis'
+import { fittingEquationInterface,statisticFeatureInterface,crossVarietyInterface } from '@/api/modules/chartRelevanceApi';
+export default {
+  components: { Chart },
+  mixins: [chartSetMixin],
+  props: {
+    compData: null
+  },
+  watch: {
+    tableData: {
+      handler(newval) {
+        newval.length && !this.chartInfo.WarnMsg && [1, 11].includes(this.chartInfo.Source) && this.setChartOptionHandle(newval);
+      },
+      deep: true,
+    },
+  },
+  data() {
+    return {
+      loading: true,
+      observer: null,
+      isVisible: false, // 是否可见
+
+      chartInfo: {},
+    }
+  },
+  mounted() {
+    console.log('图表组件挂载');
+    this.createObserver();
+  },
+  beforeUnmount() {
+    if (this.observer) {
+      this.observer.disconnect();
+    }
+  },
+  methods: {
+    /* 一键刷新 超长等待..*/
+    async refreshHandle() {
+      // 清除缓存配置项
+      this.loading = true;
+      const { Source, ChartInfoId, } = this.chartInfo;
+
+      let res = null
+      if (Source === 1) {
+        res = await dataBaseInterface.chartRefresh({ ChartInfoId })
+      } else if ([3].includes(Source)) {
+        res = await chartRelevanceApi.refreshChart({ ChartInfoId })
+      } else if (Source === 6) {
+        res = await fittingEquationInterface.refreshChart({ ChartInfoId })
+      } else if ([7].includes(Source)) {
+        res = await statisticFeatureInterface.refreshChart({ ChartInfoId })
+      } else if (Source === 10) {
+        res = await crossVarietyInterface.refreshChart({ ChartInfoId })
+      }  else if (Source === 12) {
+        res = await apiIntervalAnalysis.chartRefresh({ ChartInfoId })
+      }
+
+      this.loading = false;
+      if (res.Ret !== 200) return
+      this.$message.success(res.Msg);
+
+      this.handleGetChartData()
+    },
+
+    goDetail() {
+      const pathMap = new Map([
+        [1, '/chartsetting'],
+        [3, '/chartrelevance'],
+        [6, '/fittingEquationList'],
+        [7, '/statisticFeatureList'],
+        [10, '/crossVarietyChartList'],
+        [12, '/rangeAnalysis']
+      ])
+      const href = this.$router.resolve({
+        path: pathMap.get(this.chartInfo.Source),
+        query: {
+          code: this.chartInfo.UniqueCode,
+          id: this.chartInfo.ChartInfoId
+        }
+      }).href
+      window.open(href, "_blank")
+    },
+
+    // 获取图表数据
+    async handleGetChartData() {
+      const res = await dataBaseInterface.getChartByCode({ UniqueCode: this.compData.UniqueCode })
+      this.loading = false
+      if (res.Ret !== 200) return;
+      this.chartInfo = res.Data.ChartInfo;
+
+      this.chartInfo.SeasonExtraConfig && (this.SeasonExtraConfig = JSON.parse(this.chartInfo.SeasonExtraConfig))
+
+      if (!this.chartInfo.HaveOperaAuth) return
+
+      if ([1, 11, 12].includes(this.chartInfo.Source)) {
+        //季节性图处理SeasonAverageConfig,SeasonRightEdbConfig
+        if (this.chartInfo.ChartType === 2) {
+          const { MaxMinLimits, SamePeriodAverage, SamePeriodStandardDeviation, RightAxis } = res.Data.DataResp
+          this.chartInfo.SeasonAverageConfig = { MaxMinLimits, SamePeriodAverage, SamePeriodStandardDeviation }
+          this.chartInfo.SeasonRightConfig = RightAxis
+        }
+        this.tableData = res.Data.EdbInfoList;
+        //初始化上下限
+        this.setLimitData(this.tableData)
+
+        this.chartInfo.ChartType === 7 && this.initBarData(res.Data);
+
+        //截面散点图
+        this.chartInfo.ChartType === 10 && this.initSectionScatterData(res.Data);
+
+        //雷达图
+        this.chartInfo.ChartType === 11 && this.initRadarData(res.Data);
+
+        // 截面组合图
+        this.chartInfo.ChartType === 14 && this.initSectionalCombinationChart(res.Data);
+
+        // 区间分析
+        this.chartInfo.Source === 12 && this.initIntervalAnalysisChartData(res.Data)
+
+      } else if ([2, 5].includes(this.chartInfo.Source)) {
+        // this.tableData = [res.Data.EdbInfoList[0]];
+        this.tableData = res.Data.EdbInfoList.filter(_e => _e.Source)
+        this.chartInfo = res.Data.ChartInfo.Source === 5 ? {
+          ...res.Data.ChartInfo,
+          ProfitNameEn: res.Data.DataResp.ProfitNameEn,
+          ProfitName: res.Data.DataResp.ProfitName,
+        } : res.Data.ChartInfo;
+        //商品价格图
+        this.initCommodityData(res.Data);
+      } else if ([3].includes(this.chartInfo.Source)) {//相关性 滚动相关性
+
+        this.relevanceChartData = {
+          ChartInfo: res.Data.ChartInfo,
+          EdbInfoList: res.Data.EdbInfoList,
+          XEdbIdValue: res.Data.XEdbIdValue,
+          CorrelationChartInfo: res.Data.CorrelationChartInfo,
+          YDataList: [
+            {
+              Value: res.Data.YDataList[0].Value,
+              Color: '#00f',
+              Name: res.Data.YDataList[0].Name || res.Data.ChartInfo.ChartName,
+              NameEn: res.Data.YDataList[0].NameEn || res.Data.ChartInfo.ChartNameEn
+            }
+          ]
+        }
+        //多因子
+        if (res.Data.CorrelationChartInfo.AnalysisMode === 1) {
+          this.relevanceChartData.YDataList = res.Data.YDataList
+        }
+
+        this.initRelevanceChartData()
+        this.tableData = res.Data.EdbInfoList
+      } else if (this.chartInfo.Source === 4) {//滚动相关性换成曲线图绘图
+        this.tableData = res.Data.EdbInfoList
+        this.relevanceChartData = {
+          CorrelationChartInfo: res.Data.CorrelationChartInfo
+        }
+        this.setDefaultChart([res.Data.DataResp]);
+
+      } else if ([6, 7, 8].includes(this.chartInfo.Source)) {//拟合方程 标准差 百分比
+        this.tableData = res.Data.EdbInfoList;
+
+        this.setDefaultChart([res.Data.DataResp]);
+      } else if (this.chartInfo.Source === 9) { //统计频率
+        this.tableData = res.Data.EdbInfoList;
+        this.statisticFrequencyData = res.Data.DataResp;
+        this.setStatisticFrequency();
+      } else if (this.chartInfo.Source === 10) { //跨品种分析
+        this.tableData = res.Data.EdbInfoList;
+        this.crossVarietyChartData = res.Data.DataResp;
+
+        /* 历史数据chartInfo里全是空 兼容下历史数据不崩 */
+        this.chartLimit = {
+          min: res.Data.ChartInfo.LeftMin ? Number(res.Data.ChartInfo.LeftMin) : Number(res.Data.DataResp.YMinValue),
+          max: res.Data.ChartInfo.LeftMax ? Number(res.Data.ChartInfo.LeftMax) : Number(res.Data.DataResp.YMaxValue),
+          x_min: res.Data.ChartInfo.XMin ? Number(res.Data.ChartInfo.XMin) : Number(res.Data.DataResp.XMinValue),
+          x_max: res.Data.ChartInfo.XMax ? Number(res.Data.ChartInfo.XMax) : Number(res.Data.DataResp.XMaxValue),
+        }
+        this.setCrossVarietyChart();
+      }
+    },
+
+    // 利用判断是否进入可视区域 来加载数据 
+    // 如果加载过了就不用再加载了
+    createObserver() {
+      const options = {
+        root: null, // 使用浏览器可视区域为根
+        threshold: 0.1, // 当至少10%的内容进入可视区时触发回调
+      };
+
+      this.observer = new IntersectionObserver(this.handleIntersect, options);
+      this.observer.observe(this.$refs.compRef); // 监听组件
+    },
+    handleIntersect(entries) {
+      if (this.isVisible) return
+      entries.forEach(entry => {
+        // 判断是否在可视范围内
+        if (entry.isIntersecting) {
+          this.isVisible = true;
+          console.log('Component is visible');
+          // 在这里你可以执行其他操作,比如懒加载数据或图片等
+          this.handleGetChartData()
+        }
+      });
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.chart-box {
+  width: 100%;
+  height: 100%;
+  padding: 20px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  .top-title-box {
+    display: flex;
+    margin-bottom: 10px;
+    .title {
+      font-size: 20px;
+      font-weight: bold;
+      flex: 1;
+      display: -webkit-box;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      -webkit-line-clamp: 1;
+      line-break: anywhere;
+      -webkit-box-orient: vertical;
+      &::before {
+        content: "";
+        display: inline-block;
+        width: 4px;
+        height: 20px;
+        background-color: #0052d9;
+        position: relative;
+        top: 2px;
+        margin-right: 5px;
+      }
+    }
+    .opt-box {
+      flex-shrink: 0;
+      margin-left: 10px;
+      .icon {
+        width: 24px;
+        height: 24px;
+        margin-left: 5px;
+      }
+    }
+  }
+  .chart-render-wrap {
+    width: 100%;
+    flex: 1;
+  }
+}
+</style>

+ 79 - 0
src/views/BI_manage/components/CommonClassify.vue

@@ -0,0 +1,79 @@
+<template>
+  <el-dialog
+    title="设置公共看板分类"
+    :visible.sync="show"
+    :center="true"
+    v-dialogDrag
+    custom-class="dialogclass"
+    width="680px"
+    @close="handleClose"
+    append-to-body
+  >
+    <div class="common-classify-wrap">
+      <el-button type="text" icon="el-icon-plus" @click="showEdit=true">添加分类</el-button>
+      <div class="classify-list-box">
+        <div class="item-box" v-for="item in 10" :key="item">
+          <el-input disabled style="flex:1"></el-input>
+          <img class="icon" src="~@/assets/img/icons/edit-blue.png" alt="">
+          <img class="icon" src="~@/assets/img/icons/delete-red.png" alt="">
+        </div>
+      </div>
+    </div>
+    <EditCommonClassify v-model="showEdit"/>
+  </el-dialog>
+</template>
+
+<script>
+import EditCommonClassify from './EditCommonClassify.vue'
+export default {
+  name: "CommonClassify",
+  components:{EditCommonClassify},
+  model: {
+    prop: 'show',
+    event: 'showChange'
+  },
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      showEdit:false
+    }
+  },
+  methods: {
+    handleClose() {
+      this.$emit('showChange', false)
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.common-classify-wrap{
+  .classify-list-box{
+    margin: 20px 0;
+    height: 300px;
+    overflow-y: auto;
+    .item-box{
+      margin-bottom: 10px;
+      display: flex;
+      align-items: center;
+      gap: 0 5px;
+      .icon{
+        width: 24px;
+        height: 24px;
+        cursor: pointer;
+        margin-left: 10px;
+      }
+    }
+  }
+}
+.dia-bot {
+  display: flex;
+  justify-content: center;
+  margin: 40px 0;
+}
+</style>

+ 83 - 0
src/views/BI_manage/components/EditCommonClassify.vue

@@ -0,0 +1,83 @@
+<template>
+  <el-dialog
+    title="设置公共看板分类"
+    :visible.sync="show"
+    :center="true"
+    v-dialogDrag
+    custom-class="dialogclass"
+    width="680px"
+    @close="handleClose"
+    append-to-body
+  >
+    <div class="edit-common-classify-wrap" style="padding-top: 40px">
+      <el-form
+        :model="formData"
+        :rules="rules"
+        ref="ruleForm"
+        label-width="100px"
+      >
+        <el-form-item label="分类名称" prop="name">
+          <el-input
+            v-model="formData.name"
+            placeholder="请输入分类名称"
+          ></el-input>
+        </el-form-item>
+      </el-form>
+      <div class="dia-bot">
+        <el-button
+          type="primary"
+          plain
+          @click="handleClose"
+          style="margin-right: 20px"
+          >{{ $t("Dialog.cancel_btn") }}</el-button
+        >
+        <el-button type="primary" @click="saveHandle">{{
+          $t("Dialog.confirm_save_btn")
+        }}</el-button>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: "EditCommonClassify",
+  model: {
+    prop: 'show',
+    event: 'showChange'
+  },
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      rules: { name: [{ required: true, message: '请输入分类名称', trigger: 'blur' },] },
+      formData: { name: "" },
+
+    }
+  },
+  methods: {
+    handleClose() {
+      this.$emit('showChange', false)
+    },
+    saveHandle() {
+      this.$refs.ruleForm.validate((valid) => {
+        if (valid) {
+
+        }
+      })
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.dia-bot {
+  display: flex;
+  justify-content: center;
+  margin: 60px 0 40px 0;
+}
+</style>

+ 257 - 0
src/views/BI_manage/components/SelectChart.vue

@@ -0,0 +1,257 @@
+<template>
+  <div class="choose-chart-container">
+    <el-dialog
+      :visible.sync="show"
+      :close-on-click-modal="false"
+      :modal-append-to-body="false"
+      @close="cancelHandle"
+      custom-class="dialog"
+      title="添加图表"
+      top="3vh"
+      center
+      width="86%"
+			style="min-width:1200px;"
+      v-dialogDrag
+    >
+      <div class="dialog-top">
+        <el-input
+          :placeholder="$t('Chart.Detail.chart_name')"
+          v-model="search_txt"
+          style="width: 100%"
+          @input="searchHandle"
+          clearable
+        >
+          <i slot="prefix" class="el-input__icon el-icon-search"></i>
+        </el-input>
+        <div style="margin-top: 10px">
+          <el-radio-group v-model="chart_source" @change="searchHandle" style="margin-right:15px;">
+						<el-radio :label="1"><!-- ETA图库 -->{{$t('Chart.AllChartSource.eta_chart')}}</el-radio>
+						<el-radio :label="3"><!-- 相关性图表 -->{{$t('Chart.AllChartSource.correla_chart')}}</el-radio>
+						<el-radio :label="6"><!-- 拟合方程曲线 -->{{$t('Chart.AllChartSource.equation_chart')}}</el-radio>
+						<el-radio :label="7"><!-- 统计特征 -->{{$t('Chart.AllChartSource.statis_chart')}}</el-radio>
+						<el-radio :label="10"><!-- 跨品种分析 -->{{$t('Chart.AllChartSource.cross_chart')}}</el-radio>
+            <el-radio :label="12">区间分析</el-radio>
+					</el-radio-group>
+          <el-checkbox v-model="isShowMe"  @change="searchHandle"><!-- 只看我的 -->{{$t('MyEtaPage.label_see_mine')}}</el-checkbox>
+        </div>
+      </div>
+      <div class="choose-dialog-min">
+        <el-checkbox-group v-model="checkList" v-if="haveData">
+        <div
+          class="chart-public-list"
+          style="margin-bottom: 20px;padding-right: 20px;"
+          :infinite-scroll-disabled="!haveMove"
+          v-infinite-scroll="loadMove"
+          ref="scrollCont"
+        >
+          <el-col
+            :span="5"
+            v-for="chart in chartPublicList"
+            :key="chart.ChartInfoId"
+            style="padding-right: 20px;margin-bottom:20px;min-width: 260px;"
+          >
+            <el-card shadow class="public-chart-item">
+              <div slot="header" class="item-top">
+                <span class="text_oneLine">{{chart.ChartName}}</span>
+                <el-checkbox :disabled="chart_source===1&&!chart.HaveOperaAuth" :label="chart.ChartInfoId"
+                    ><span>&nbsp;</span></el-checkbox
+                  >
+              </div>
+              <img :src="(chart_source===1&&!chart.HaveOperaAuth) ? $icons.lock_big : chart.ChartImage" alt="" class="chart-img" />
+              <div class="item-bottom">
+                <span class="last-time">创建时间:{{ chart.CreateTime.substr(0,10) }}</span>
+              </div>
+            </el-card>
+          </el-col>
+        </div>
+        </el-checkbox-group>
+        <div v-else class="nodata">
+          <tableNoData :text="$t('Common.no_chart_msg')"/>
+        </div>
+      </div>
+      <div class="dia-bot">
+        <el-button
+          type="primary"
+          plain
+          @click="cancelHandle"
+          style="margin-right: 20px"
+          >{{ $t("Dialog.cancel_btn") }}</el-button
+        >
+        <el-button type="primary" @click="saveHandle">{{
+          $t("Dialog.confirm_save_btn")
+        }}</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { mychartInterface } from '@/api/api.js';
+import chartRelevanceApi from '@/api/modules/chartRelevanceApi';
+import { fittingEquationInterface,statisticFeatureInterface,crossVarietyInterface } from '@/api/modules/chartRelevanceApi';
+import futuresInterface from '@/api/modules/futuresBaseApi';
+import apiIntervalAnalysis from '@/api/modules/intervalAnalysis'
+export default {
+  model: {
+    prop: 'show',
+    event: 'showChange'
+  },
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  watch: {
+    show(newval) {
+      newval && this.getPublicChartList();
+    },
+  },
+  data() {
+    return {
+      haveData: false,
+      search_txt: '',
+      page_no: 1,
+      page_size: 15,
+      haveMove: true,
+      chartPublicList: [],
+
+      chart_source:1,
+      isShowMe: false,
+      checkList:[]
+    };
+  },
+  methods: {
+    saveHandle(){
+      const arr=this.chartPublicList.filter(item=>this.checkList.includes(item.ChartInfoId))
+      this.$emit('addChart', arr)
+    },
+
+    /* 获取图库列表 */
+    async getPublicChartList() {
+      let params = {
+        PageSize: this.page_size,
+        CurrentIndex: this.page_no,
+        ChartClassifyId: 0,
+        KeyWord: this.search_txt,
+        IsShowMe: this.isShowMe
+      };
+
+       const apiMap = {
+        1: mychartInterface.publicList,
+        3: chartRelevanceApi.getChartList,
+        6: fittingEquationInterface.getChartList,
+        7: statisticFeatureInterface.getChartList,
+        10: crossVarietyInterface.searchChart,
+        12: apiIntervalAnalysis.chartSearch
+      }
+      let res = await apiMap[this.chart_source](params)
+
+
+      if (res.Ret !== 200) return;
+      this.haveMove = res.Data ? this.page_no < res.Data.Paging.Pages : false;
+      this.chartPublicList = res.Data
+        ? this.page_no === 1
+          ? res.Data.List
+          : [...this.chartPublicList, ...res.Data.List]
+        : [];
+      this.haveData = this.chartPublicList.length ? true : false;
+      
+    },
+
+    loadMove() {
+      this.page_no++;
+      this.getPublicChartList();
+    },
+
+    searchHandle() {
+      this.page_no = 1;
+      if(this.$refs.scrollCont) this.$refs.scrollCont.scrollTop = 0;
+      this.getPublicChartList();
+    },
+
+    init() {
+      this.page_no = 1;
+      this.checkList=[]
+      if(this.$refs.scrollCont) this.$refs.scrollCont.scrollTop = 0;
+      this.search_txt = '';
+    },
+    cancelHandle() {
+      this.init();
+      this.$emit('showChange', false)
+    },
+  },
+  created() {},
+  mounted() {},
+};
+</script>
+<style lang="scss">
+.choose-chart-container {
+  .el-col-5 {
+    width: 20%;
+  }
+  .el-card .el-card__header,
+  .el-card__body {
+    padding: 10px;
+  }
+  .el-dialog--center .el-dialog__body {
+    padding: 25px 0 10px;
+  }
+  .dialog-top {
+    padding: 0 25px;
+  }
+  .choose-dialog-min {
+    margin-top: 20px;
+    .chart-public-list {
+      display: flex;
+      flex-wrap: wrap;
+      /* height: 650px; */
+      max-height: 650px;
+      height: 65vh;
+      padding: 0 0 0 20px;
+      overflow: hidden;
+      overflow-y: auto;
+      .dragShdow {
+        box-shadow: 0 1px 8px rgba(64, 158, 255, 0.8);
+        opacity: 0.5;
+      }
+      .public-chart-item {
+        .item-top {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          font-size: 15px;
+          font-weight: 600;
+        }
+        .chart-img {
+          width: 100%;
+          height: 180px;
+          object-fit: fill !important;
+        }
+        .item-bottom {
+          margin-top: 10px;
+          font-size: 12px;
+          display: flex;
+          justify-content: space-between;
+          color: #666;
+          .join_txt {
+            color: #409eff;
+            cursor: pointer;
+          }
+        }
+      }
+    }
+    .nodata {
+      height: 500px;
+      text-align: center;
+      font-size: 14px;
+      color: #666;
+    }
+  }
+}
+.dia-bot {
+  display: flex;
+  justify-content: center;
+  margin: 40px 0;
+}
+</style>

+ 268 - 0
src/views/BI_manage/components/SelectTable.vue

@@ -0,0 +1,268 @@
+<template>
+  <div class="choose-chart-container">
+    <el-dialog
+      :visible.sync="show"
+      :close-on-click-modal="false"
+      :modal-append-to-body="false"
+      @close="cancelHandle"
+      custom-class="dialog"
+      title="添加表格"
+      top="3vh"
+      center
+      width="86%"
+      style="min-width: 1200px"
+      v-dialogDrag
+    >
+      <div class="dialog-top">
+        <el-input
+          :placeholder="$t('Chart.Detail.chart_name')"
+          v-model="search_txt"
+          style="width: 100%"
+          @input="searchHandle"
+          clearable
+        >
+          <i slot="prefix" class="el-input__icon el-icon-search"></i>
+        </el-input>
+        <div style="margin-top: 10px">
+          <el-radio-group
+            v-model="table_source"
+            @change="searchHandle"
+            style="margin-right: 15px"
+          >
+            <el-radio :label="1">共享表格</el-radio>
+            <el-radio :label="2">时间序列表格</el-radio>
+            <el-radio :label="3">混合表格</el-radio>
+          </el-radio-group>
+          <el-checkbox style="float:right" v-model="isShowMe" @change="searchHandle"
+            ><!-- 只看我的 -->{{ $t("MyEtaPage.label_see_mine") }}</el-checkbox
+          >
+        </div>
+      </div>
+      <div class="choose-dialog-min">
+        <el-checkbox-group v-model="checkList" v-if="haveData">
+          <div
+            class="chart-public-list"
+            style="margin-bottom: 20px; padding-right: 20px"
+            :infinite-scroll-disabled="!haveMove"
+            v-infinite-scroll="loadMove"
+            ref="scrollCont"
+          >
+            <el-col
+              :span="5"
+              v-for="table in tablePublicList"
+              :key="table.ExcelInfoId"
+              style="padding-right: 20px; margin-bottom: 20px; min-width: 260px"
+            >
+              <el-card
+                shadow
+                :class="[
+                  'public-chart-item',
+                  checkList.includes(table.ExcelInfoId) ? 'select_box' : '',
+                ]"
+              >
+                <div slot="header" class="item-top">
+                  <span class="text_oneLine">{{ table.ExcelName }}</span>
+                  <el-checkbox :disabled="!table.HaveOperaAuth" :label="table.ExcelInfoId"
+                    ><span>&nbsp;</span></el-checkbox
+                  >
+                </div>
+                <img
+                  :src="
+                    !table.HaveOperaAuth ? $icons.lock_big : table.ExcelImage
+                  "
+                  alt=""
+                  class="chart-img"
+                />
+                <div class="item-bottom">
+                  <span class="last-time"
+                    >创建时间:{{ table.CreateTime.substr(0, 10) }}</span
+                  >
+                </div>
+              </el-card>
+            </el-col>
+          </div>
+        </el-checkbox-group>
+        <div v-else class="nodata">
+          <tableNoData text="暂无表格" />
+        </div>
+      </div>
+      <div class="dia-bot">
+        <el-button
+          type="primary"
+          plain
+          @click="cancelHandle"
+          style="margin-right: 20px"
+          >{{ $t("Dialog.cancel_btn") }}</el-button
+        >
+        <el-button type="primary" @click="saveHandle">{{
+          $t("Dialog.confirm_save_btn")
+        }}</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import * as sheetInterface from "@/api/modules/sheetApi.js";
+export default {
+  model: {
+    prop: 'show',
+    event: 'showChange'
+  },
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  watch: {
+    show(newval) {
+      newval && this.getPublicTableList();
+    },
+  },
+  data() {
+    return {
+      haveData: false,
+      search_txt: '',
+      page_no: 1,
+      page_size: 15,
+      haveMove: true,
+      tablePublicList: [],
+
+      checkList: [],
+
+      table_source: 1,
+      isShowMe: false
+    };
+  },
+  methods: {
+    saveHandle(){
+      const arr=this.tablePublicList.filter(item=>this.checkList.includes(item.ExcelInfoId))
+      this.$emit('addTable', arr)
+    },
+
+    /* 获取图库列表 */
+    async getPublicTableList() {
+      let params = {
+        PageSize: this.page_size,
+        CurrentIndex: this.page_no,
+        Source: this.table_source,
+        KeyWord: this.search_txt,
+        IsShowMe: this.isShowMe
+      };
+
+      let res = await sheetInterface.sheetList(params)
+
+      if (res.Ret !== 200) return;
+      this.haveMove = res.Data ? this.page_no < res.Data.Paging.Pages : false;
+      this.tablePublicList = res.Data
+        ? this.page_no === 1
+          ? res.Data.List
+          : [...this.tablePublicList, ...res.Data.List]
+        : [];
+      this.haveData = this.tablePublicList.length ? true : false;
+
+    },
+
+    loadMove() {
+      this.page_no++;
+      this.getPublicTableList();
+    },
+
+    searchHandle() {
+      this.page_no = 1;
+      if (this.$refs.scrollCont) this.$refs.scrollCont.scrollTop = 0;
+      this.getPublicTableList();
+    },
+
+    init() {
+      this.checkList=[]
+      this.page_no = 1;
+      if (this.$refs.scrollCont) this.$refs.scrollCont.scrollTop = 0;
+      this.search_txt = '';
+    },
+    cancelHandle() {
+      this.init();
+      this.$emit('showChange', false)
+    },
+  },
+  created() { },
+  mounted() { },
+};
+</script>
+<style lang="scss">
+.choose-chart-container {
+  .el-col-5 {
+    width: 20%;
+  }
+  .el-card .el-card__header,
+  .el-card__body {
+    padding: 10px;
+  }
+  .el-dialog--center .el-dialog__body {
+    padding: 25px 0 10px;
+  }
+  .dialog-top {
+    padding: 0 25px;
+  }
+  .choose-dialog-min {
+    margin-top: 20px;
+    .chart-public-list {
+      display: flex;
+      flex-wrap: wrap;
+      /* height: 650px; */
+      max-height: 650px;
+      height: 65vh;
+      padding: 0 0 0 20px;
+      overflow: hidden;
+      overflow-y: auto;
+      .el-card__header {
+        box-shadow: none !important;
+      }
+      .dragShdow {
+        box-shadow: 0 1px 8px rgba(64, 158, 255, 0.8);
+        opacity: 0.5;
+      }
+      .public-chart-item {
+        .item-top {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          font-size: 15px;
+          font-weight: 600;
+        }
+        .chart-img {
+          width: 100%;
+          height: 180px;
+          object-fit: fill !important;
+        }
+        .item-bottom {
+          margin-top: 10px;
+          font-size: 12px;
+          display: flex;
+          justify-content: space-between;
+          color: #666;
+          .join_txt {
+            color: #409eff;
+            cursor: pointer;
+          }
+        }
+      }
+      .select_box {
+        border-color: #409eff !important;
+      }
+    }
+    .nodata {
+      height: 500px;
+      text-align: center;
+      font-size: 14px;
+      color: #666;
+    }
+  }
+}
+.dia-bot {
+  display: flex;
+  justify-content: center;
+  margin: 40px 0;
+}
+</style>

+ 81 - 0
src/views/BI_manage/components/SetCommon.vue

@@ -0,0 +1,81 @@
+<template>
+  <el-dialog
+    title="设置公共看板"
+    :visible.sync="show"
+    :modal-append-to-body="false"
+    :close-on-click-modal="false"
+    :center="true"
+    v-dialogDrag
+    custom-class="dialogclass"
+    width="680px"
+    @close="handleClose"
+  >
+    <div class="BI-share-wrap">
+      <div class="board-title">
+        <div style="flex:1">看板名称</div>
+        <el-tag size="mini">审批状态</el-tag>
+      </div>
+      <el-select placeholder="请选择分类" style="width:100%;margin:20px 0"></el-select>
+      <div class="tips">提示:个人看板设为公共看板,请先选择公共看板分类,提交审批,审批通过后可设置公开!</div>
+      <div class="tips">提示:设置公开看板失败,若要编辑,请先执行撤销操作!<el-button type="text">撤销</el-button></div>
+      <div class="tips">提示:审批通过后,个人看板则设置公开成功!</div>
+      <div class="tips">提示:设置公开看板成功,若要编辑,请先撤销操作(即取消公开)!<el-button type="text">撤销</el-button></div>
+      
+      <div class="dia-bot">
+        <el-button
+          type="primary"
+          plain
+          @click="handleClose"
+          style="margin-right: 20px"
+          >{{ $t("Dialog.cancel_btn") }}</el-button
+        >
+        <el-button type="primary" @click="saveHandle">提交审批</el-button>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: "setCommon",
+  model: {
+    prop: 'show',
+    event: 'showChange'
+  },
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+    }
+  },
+  created() {
+  },
+  methods: {
+    handleClose() {
+      this.$emit('showChange', false)
+    },
+    
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.select-user-box {
+  margin-top: 20px;
+  padding: 20px;
+  border: 1px dashed #c8cdd9;
+}
+.board-title{
+  display: flex;
+  
+}
+.dia-bot {
+  display: flex;
+  justify-content: center;
+  margin: 40px 0;
+}
+</style>

+ 111 - 0
src/views/BI_manage/components/SetShare.vue

@@ -0,0 +1,111 @@
+<template>
+  <el-dialog
+    title="共享当前看板"
+    :visible.sync="show"
+    :modal-append-to-body="false"
+    :close-on-click-modal="false"
+    :center="true"
+    v-dialogDrag
+    custom-class="dialogclass"
+    width="680px"
+    @close="handleClose"
+  >
+    <div class="BI-share-wrap">
+      <div>
+        <span style="color: #0052d9">共享看板</span>
+        <el-switch v-model="open"> </el-switch>
+        <el-cascader
+          v-model="select_users"
+          :options="researcherList"
+          :show-all-levels="false"
+          filterable
+          :props="{
+            value: 'ItemId',
+            label: 'ItemName',
+            children: 'Children',
+            expandTrigger: 'hover',
+            emitPath: false,
+            multiple: true,
+          }"
+          clearable
+          placeholder="选择共享人"
+          :key="cascaderIdx"
+          @remove-tag="removeResearchersChange"
+        />
+      </div>
+      <div class="select-user-box">
+        <el-tag
+          v-for="tag in select_users"
+          :key="tag.name"
+          closable
+        >
+          {{ tag.name }}
+        </el-tag>
+      </div>
+
+      <div class="dia-bot">
+        <el-button
+          type="primary"
+          plain
+          @click="handleClose"
+          style="margin-right: 20px"
+          >{{ $t("Dialog.cancel_btn") }}</el-button
+        >
+        <el-button type="primary" @click="saveHandle">{{
+          $t("Dialog.confirm_save_btn")
+        }}</el-button>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { dataAuthInterface } from '@/api/api.js';
+export default {
+  name: "setShare",
+  model: {
+    prop: 'show',
+    event: 'showChange'
+  },
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      open: false,
+      select_users: [],
+      researcherList: [],
+      cascaderIdx: 1
+    }
+  },
+  created() {
+    this.getSystemUserList()
+  },
+  methods: {
+    handleClose() {
+      this.$emit('showChange', false)
+    },
+    async getSystemUserList() {
+      const res = await dataAuthInterface.userSearch();
+      if (res.Ret !== 200) return
+      this.researcherList = res.Data || []
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.select-user-box {
+  margin-top: 20px;
+  padding: 20px;
+  border: 1px dashed #c8cdd9;
+}
+.dia-bot {
+  display: flex;
+  justify-content: center;
+  margin: 40px 0;
+}
+</style>

+ 115 - 0
src/views/BI_manage/components/TableBox.vue

@@ -0,0 +1,115 @@
+<template>
+  <div class="table-box" v-if="compData" ref="compRef" v-loading="loading">
+    <div class="top-title-box">
+      <div class="title">{{ compData.ExcelName }}</div>
+      <div class="opt-box">
+        <img
+          class="icon"
+          src="~@/assets/img/icons/refresh_blue_new.png"
+          alt=""
+        />
+        <slot name="drag"></slot>
+        <slot name="delete"></slot>
+      </div>
+    </div>
+    <img class="bg" :src="compData.ExcelImage" alt="" />
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    compData: null
+  },
+  data() {
+    return {
+      loading: true,
+      observer:null,
+      isVisible: false, // 是否可见
+    }
+  },
+  mounted() {
+    console.log('表格组件挂载', this.compData.ExcelInfoId);
+    this.createObserver();
+  },
+  beforeUnmount() {
+    if (this.observer) {
+      this.observer.disconnect();
+    }
+  },
+  methods: {
+    // 获取表格数据
+    handleGetTableData(){
+      setTimeout(() => {
+        console.log('表格组件加载结束', this.compData.ExcelInfoId);
+        this.loading=false
+      }, 2000);
+    },
+
+    // 利用判断是否进入可视区域 来加载数据 
+    // 如果加载过了就不用再加载了
+    createObserver() {
+      const options = {
+        root: null, // 使用浏览器可视区域为根
+        threshold: 0.1, // 当至少10%的内容进入可视区时触发回调
+      };
+
+      this.observer = new IntersectionObserver(this.handleIntersect, options);
+      this.observer.observe(this.$refs.compRef); // 监听组件
+    },
+    handleIntersect(entries) {
+      if(this.isVisible) return 
+      entries.forEach(entry => {
+        // 判断是否在可视范围内
+        if (entry.isIntersecting) {
+          this.isVisible = true;
+          console.log('Component is visible');
+          // 在这里你可以执行其他操作,比如懒加载数据或图片等
+          this.handleGetTableData()
+        }
+      });
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.table-box {
+  width: 100%;
+  height: 100%;
+  padding: 20px;
+  box-sizing: border-box;
+  .top-title-box {
+    display: flex;
+    margin-bottom: 10px;
+    .title {
+      font-size: 20px;
+      font-weight: bold;
+      flex: 1;
+      &::before {
+        content: "";
+        display: inline-block;
+        width: 4px;
+        height: 20px;
+        background-color: #0052d9;
+        position: relative;
+        top: 2px;
+        margin-right: 5px;
+      }
+    }
+    .opt-box {
+      flex-shrink: 0;
+      margin-left: 10px;
+      .icon {
+        width: 24px;
+        height: 24px;
+        margin-left: 5px;
+      }
+    }
+  }
+  .bg {
+    width: 100%;
+    height: 200px;
+  }
+}
+</style>

+ 150 - 0
src/views/BI_manage/editBoard.vue

@@ -0,0 +1,150 @@
+<template>
+  <div class="edit-BI-board-page">
+    <div class="top-box">
+      <el-input
+        placeholder="请输入看板名称"
+        v-model="name"
+        style="width: 300px; margin-right: 20px"
+      ></el-input>
+      <el-button type="primary" plain @click="showSelectTable = true"
+        >添加表格</el-button
+      >
+      <el-button type="primary" plain @click="showSelectChart = true"
+        >添加图表</el-button
+      >
+      <div class="right-btns">
+        <el-button type="primary" plain @click="$router.back()">取消</el-button>
+        <el-button type="primary" @click="handleSave">保存</el-button>
+      </div>
+    </div>
+    <!-- 看板内容模块 -->
+    <BIBoardContent v-model="boardDataList" :canDelete="true" />
+
+    <!-- 选择图表 -->
+    <SelectChart
+      v-model="showSelectChart"
+      @addChart="handleAddComp('chart', $event)"
+    />
+    <!-- 选择表格 -->
+    <SelectTable
+      v-model="showSelectTable"
+      @addTable="handleAddComp('table', $event)"
+    />
+  </div>
+</template>
+
+<script>
+import apiBiBoard from '@/api/modules/BIBoard.js'
+import BIBoardContent from './components/BoardContent.vue'
+import SelectChart from './components/SelectChart.vue'
+import SelectTable from './components/SelectTable.vue'
+const MAX_COUNT = 50
+// 生成唯一ID 
+function createUniqueIdGenerator() {
+  let id = 0;
+  return function generateUniqueId() {
+    id += 1;
+    return `selfId_${id}`;
+  };
+}
+const getUniqueId = createUniqueIdGenerator();
+
+export default {
+  components: {
+    BIBoardContent,
+    SelectChart,
+    SelectTable
+  },
+  data() {
+    return {
+      name: '',
+      boardDataList: [],
+
+      showSelectChart: false,
+      showSelectTable: false
+    }
+  },
+  created() {
+    if(this.$route.query.id){
+      this.getBoardDetail()
+    }
+  },
+  methods: {
+    // 获取看板详情
+    async getBoardDetail(){
+      const res=await apiBiBoard.boardDetail({DashboardId:Number(this.$route.query.id)})
+      if(res.Ret===200){
+        this.name=res.Data.BiDashboardName
+        this.boardDataList=res.Data.List||[]
+      }
+    },
+
+    async handleSave() {
+      if (!this.name) {
+        this.$message.warning('请填写看板名称')
+        return
+      }
+      if (this.boardDataList.length === 0) {
+        this.$message.warning('请至少选择一个图表或表格!')
+        return
+      }
+
+      const arr=this.boardDataList.map(item=>{
+        return {
+          Type:item.Type,
+          UniqueCode:item.UniqueCode
+        }
+      })
+      const params={
+        BiDashboardName:this.name,
+        List:arr||[]
+      }
+      const res=this.$route.query.id?await apiBiBoard.editBoard({
+        BiDashboardId:Number(this.$route.query.id),
+        ...params
+      }) :await apiBiBoard.addBoard(params)
+      if(res.Ret===200){
+        this.$message.success(this.$route.query.id?'编辑成功':'新增成功')
+        setTimeout(() => {
+          this.$router.back()
+        }, 1000);
+      }
+
+    },
+
+    handleAddComp(type, data) {
+      const arr = data || []
+      if (this.boardData.length + arr.length > MAX_COUNT) {
+        this.$message.warning('添加已达上限(上限50)!')
+        return
+      }
+      arr.forEach(item => {
+        const obj = {
+          Type: type === 'chart' ? 1 : 2,
+          UniqueCode:item.UniqueCode,
+          BiDashboardDetailId:getUniqueId()
+        }
+        this.boardData.push(obj)
+      });
+
+      this.showSelectChart = false
+      this.showSelectTable = false
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.edit-BI-board-page {
+  $border-color: #c8cdd9;
+  background-color: #fff;
+  border: 1px solid $border-color;
+  .top-box {
+    padding: 14px 20px;
+    border-bottom: 1px solid $border-color;
+    .right-btns {
+      float: right;
+    }
+  }
+}
+</style>

+ 270 - 0
src/views/BI_manage/index.vue

@@ -0,0 +1,270 @@
+<template>
+  <div class="BI-page">
+    <div class="top-nav-box">
+      <div class="nav-btns">
+        <el-button
+          class="nav-btn"
+          type="primary"
+          :plain="navType !== 1"
+          @click="handleNavTypeChange(1)"
+          >我的看板</el-button
+        >
+        <el-button
+          class="nav-btn"
+          type="primary"
+          :plain="navType !== 2"
+          @click="handleNavTypeChange(2)"
+          >共享看板</el-button
+        >
+        <el-button
+          class="nav-btn"
+          type="primary"
+          :plain="navType !== 3"
+          @click="handleNavTypeChange(3)"
+          >公共看板</el-button
+        >
+      </div>
+      <!-- 添加看板 -->
+      <div class="right-btn-box" v-if="navType === 1">
+        <el-button
+          type="primary"
+          @click="$router.push('/editBIBoard')"
+          v-permission="permissionBtn.BIBoardPermission.BIBoard_add"
+          >添加看板</el-button
+        >
+      </div>
+      <!-- 设置公共看板分类 -->
+      <div class="right-btn-box" v-if="navType === 3">
+        <el-button
+          type="primary"
+          @click="showCommonClassify = true"
+          v-permission="permissionBtn.BIBoardPermission.BIBoard_addclassify"
+          >设置公共看板分类</el-button
+        >
+      </div>
+    </div>
+    <div class="opt-box">
+      <!-- 我的看板  -->
+      <el-select
+        v-model="selectBoardId"
+        placeholder="请选择看板"
+        v-if="navType === 1"
+      >
+        <el-option
+          v-for="item in myBoardList"
+          :key="item.BiDashboardId"
+          :value="item.BiDashboardId"
+          :label="item.BiDashboardName"
+        ></el-option>
+      </el-select>
+      <!-- 共享看板 -->
+      <el-cascader
+        v-model="selectBoardId"
+        :props="{ emitPath: false }"
+        :options="shareBoardList"
+        v-if="navType === 2"
+      ></el-cascader>
+      <!-- 公共看板 -->
+      <el-cascader
+        v-model="selectBoardId"
+        :props="{ emitPath: false }"
+        :options="commonBoardList"
+        v-if="navType === 3"
+      ></el-cascader>
+
+      <div class="right-opt-box">
+        <el-button
+          v-if="navType === 1&&permissionBtn.isShowBtn('BIBoardPermission','BIBoard_setcommon')"
+          type="text"
+          @click="showSetCommon = true"
+          >设置公共</el-button
+        >
+        <el-button type="text" @click="showSetShare = true">设置共享</el-button>
+        <el-button type="text" @click="handleGoEdit">编辑</el-button>
+        <el-button type="text" style="color: #f00">删除</el-button>
+      </div>
+    </div>
+    <!-- 看板内容模块 -->
+    <BIBoardContent v-model="boardDataList" :canDrag="false" />
+
+    <!-- 设置共享 -->
+    <set-share v-model="showSetShare" />
+    <!-- 设置公共 -->
+    <SetCommon v-model="showSetCommon" />
+    <!-- 公共看板分类 -->
+    <CommonClassify v-model="showCommonClassify" />
+  </div>
+</template>
+
+<script>
+import apiBiBoard from '@/api/modules/BIBoard.js'
+import BIBoardContent from './components/BoardContent.vue'
+import CommonClassify from './components/CommonClassify.vue'
+import SetCommon from './components/SetCommon.vue'
+import SetShare from './components/SetShare.vue'
+
+export default {
+  components: { BIBoardContent, SetShare, SetCommon, CommonClassify },
+  data() {
+    return {
+      navType: 1,// 
+
+      boardInfo: null,//看板详情数据
+      boardDataList: [],//看板数据
+
+      selectBoardId: 0,//当前选择的看板id
+      myBoardList: [],
+      shareBoardList: [],
+      commonBoardList: [],
+
+      showSetShare: false,
+      showSetCommon: false,
+      showCommonClassify: false,
+    }
+  },
+  watch: {
+    selectBoardId(n) {
+      n && this.getBoardDetail()
+    }
+  },
+  created() {
+    this.getMyBoardList()
+  },
+  methods: {
+    handleGoEdit() {
+      this.$router.push({
+        path: "/editBIBoard",
+        query: {
+          id: this.selectBoardId
+        }
+      })
+    },
+
+    // 获取看板详情
+    async getBoardDetail() {
+      const res = await apiBiBoard.boardDetail({ DashboardId: this.selectBoardId })
+      if (res.Ret === 200) {
+        this.boardInfo = res.Data
+        this.boardDataList = res.Data.List || []
+        console.log(this.permissionBtn);
+      }
+    },
+
+    // 我的看板列表
+    async getMyBoardList() {
+      const res = await apiBiBoard.myBoardList()
+      if (res.Ret === 200) {
+        this.myBoardList = res.Data || []
+        if (this.myBoardList.length > 0) {
+          this.selectBoardId = this.myBoardList[0].BiDashboardId
+        }
+      }
+    },
+
+    // 共享看板列表
+    async getShareBoardList() {
+      const res = await apiBiBoard.shareBoardList()
+      if (res.Ret === 200) {
+        const myArr = res.Data.MyList || []
+        const otherArr = res.Data.OtherList || []
+        const temarr = [...myArr, ...otherArr]
+        this.shareBoardList = [
+          {
+            label: '我共享的',
+            value: 'my_share',
+            children: myArr.map(item => {
+              return {
+                label: item.BiDashboardName,
+                value: item.BiDashboardId
+              }
+            })
+          },
+          {
+            label: '共享给我的',
+            value: 'other_share',
+            children: otherArr.map(item => {
+              return {
+                label: item.BiDashboardName,
+                value: item.BiDashboardId
+              }
+            })
+          }
+        ]
+
+        if (temarr.length > 0) {
+          this.selectBoardId = temarr[0].BiDashboardId
+        }
+      }
+    },
+
+    // 公共看板列表
+    async getCommonBoardList() {
+      const res = await apiBiBoard.commonBoardList()
+      if (res.Ret === 200) {
+        this.commonBoardList = res.Data || []
+        if (this.commonBoardList.length > 0) {
+          this.selectBoardId = this.commonBoardList[0].BiDashboardId
+        }
+      }
+    },
+
+    handleNavTypeChange(e) {
+      if (this.navType === e) return
+      this.navType = e
+
+      this.selectBoardId = 0
+      this.boardDataList = []
+      this.boardDetail = null
+      if (this.navType === 1) {
+        this.getMyBoardList()
+        return
+      }
+      if (this.navType === 2) {
+        this.getShareBoardList()
+        return
+      }
+      if (this.navType === 3) {
+        this.getCommonBoardList()
+        return
+      }
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.BI-page {
+  $border-color: #c8cdd9;
+  background-color: #fff;
+  border: 1px solid $border-color;
+  .top-nav-box {
+    padding: 14px 20px;
+    border-bottom: 1px solid $border-color;
+    position: relative;
+    .nav-btns {
+      position: relative;
+      bottom: -15px;
+      .nav-btn {
+        border-bottom: none;
+        border-bottom-left-radius: 0;
+        border-bottom-right-radius: 0;
+      }
+    }
+    .right-btn-box {
+      position: absolute;
+      right: 20px;
+      top: 14px;
+      border-left: 1px solid $border-color;
+      padding-left: 10px;
+    }
+  }
+  .opt-box {
+    padding: 14px 20px;
+    border-bottom: 1px solid $border-color;
+    .right-opt-box {
+      float: right;
+    }
+  }
+}
+</style>
+<!-- 1+3+1+1+2+3+2=13 (后台) -->