瀏覽代碼

Merge branch 'master' into eta1.2.0

Karsa 1 年之前
父節點
當前提交
14582bcf5b
共有 65 個文件被更改,包括 1900 次插入376 次删除
  1. 0 1
      index.html
  2. 6 0
      package.json
  3. 38 1
      src/api/chart.js
  4. 17 2
      src/api/common.js
  5. 66 0
      src/api/crossVarietyChart.js
  6. 5 3
      src/api/crypto.js
  7. 11 0
      src/api/etaConfig.js
  8. 3 1
      src/api/index.js
  9. 7 0
      src/api/myETA.js
  10. 15 1
      src/api/reportEn.js
  11. 9 0
      src/api/statisticFeatureChart.js
  12. 91 3
      src/components/SetChartEnName.vue
  13. 51 0
      src/directives/AuthBtn.js
  14. 2 1
      src/directives/Index.js
  15. 146 50
      src/hooks/chart/render.js
  16. 145 0
      src/hooks/useAuthBtn.js
  17. 96 5
      src/hooks/useUploadFileToOSS.js
  18. 9 0
      src/main.js
  19. 12 2
      src/router/index.js
  20. 27 0
      src/store/modules/authBtn.js
  21. 19 0
      src/store/modules/etaConfig.js
  22. 26 0
      src/store/modules/publicSetting.js
  23. 4 1
      src/views/Login.vue
  24. 155 17
      src/views/chartETA/ChartDetail.vue
  25. 6 5
      src/views/chartETA/List.vue
  26. 1 0
      src/views/chartETA/Search.vue
  27. 12 2
      src/views/chartETA/hooks/useCatalogList.js
  28. 27 12
      src/views/dataEDB/Detail.vue
  29. 50 20
      src/views/dataEDB/Index.vue
  30. 16 13
      src/views/dataEDB/components/EDBChartDetail.vue
  31. 31 7
      src/views/dataEDB/components/EDBClassify.vue
  32. 152 16
      src/views/myETA/ChartDetail.vue
  33. 22 8
      src/views/myETA/Index.vue
  34. 9 1
      src/views/myETA/components/ChartSaveOther.vue
  35. 5 4
      src/views/myETA/components/EDBInfo.vue
  36. 20 14
      src/views/ppt/Detail.vue
  37. 3 0
      src/views/ppt/Index.vue
  38. 2 1
      src/views/ppt/Preview.vue
  39. 10 7
      src/views/ppt/components/MobileClassifyWrap.vue
  40. 21 12
      src/views/ppt/components/PadPPTIndexWrap.vue
  41. 20 0
      src/views/ppt/hooks/createPPTContent.js
  42. 8 1
      src/views/ppt/hooks/useClassify.js
  43. 46 13
      src/views/ppt/hooks/usePPTPublish.js
  44. 17 12
      src/views/ppt/template/Cover.vue
  45. 3 2
      src/views/ppt/template/Footer.vue
  46. 1 1
      src/views/ppt/template/FormatEight.vue
  47. 1 1
      src/views/ppt/template/FormatFive.vue
  48. 1 1
      src/views/ppt/template/FormatFour.vue
  49. 1 1
      src/views/ppt/template/FormatNine.vue
  50. 1 1
      src/views/ppt/template/FormatOne.vue
  51. 1 1
      src/views/ppt/template/FormatSeven.vue
  52. 1 1
      src/views/ppt/template/FormatSix.vue
  53. 1 1
      src/views/ppt/template/FormatThree.vue
  54. 1 1
      src/views/ppt/template/FormatTwo.vue
  55. 38 9
      src/views/report/AddReport.vue
  56. 67 16
      src/views/report/EditReport.vue
  57. 113 24
      src/views/report/List.vue
  58. 3 1
      src/views/report/PreviewDetail.vue
  59. 20 5
      src/views/report/chapter/Detail.vue
  60. 19 14
      src/views/report/components/reportInsert/ETAChart.vue
  61. 21 6
      src/views/reportEn/AddReport.vue
  62. 3 1
      src/views/reportEn/Detail.vue
  63. 24 9
      src/views/reportEn/List.vue
  64. 121 44
      src/views/reportEn/components/SendEmail.vue
  65. 21 0
      vite.config.js

+ 0 - 1
index.html

@@ -13,6 +13,5 @@
     <script src="/jquery-3.6.0.min.js"></script>
     <script type='text/javascript' src='/froala_editor.pkgd.min.js'></script>
     <script type='text/javascript' src='/froala_editor_zh_cn.js'></script>
-    <script type='text/javascript' src='/base_config.js'></script>
   </body>
 </html>

+ 6 - 0
package.json

@@ -10,11 +10,14 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@esbuild-plugins/node-globals-polyfill": "^0.2.3",
+    "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
     "@vant/touch-emulator": "^1.4.0",
     "@vueuse/components": "^9.13.0",
     "@vueuse/core": "^9.13.0",
     "ali-oss": "^6.17.1",
     "axios": "^1.3.4",
+    "buffer": "^6.0.3",
     "file-saver": "^2.0.5",
     "highcharts": "^9.3.2",
     "himalaya": "^1.1.0",
@@ -23,10 +26,13 @@
     "js-base64": "^3.7.5",
     "js-md5": "^0.7.3",
     "lodash": "^4.17.21",
+    "minio": "^7.0.18",
+    "minio-es": "0.0.2",
     "moment": "^2.29.4",
     "normalize.css": "^8.0.1",
     "pinia": "^2.0.36",
     "pptxgenjs": "^3.12.0",
+    "rollup-plugin-node-polyfills": "^0.2.1",
     "v3-color-picker-teleport": "^1.0.9",
     "vant": "^4.6.4",
     "vconsole": "^3.15.0",

+ 38 - 1
src/api/chart.js

@@ -237,5 +237,42 @@ export default{
      */
     chartLocate(params){
         return get('/my_chart/chart/locate',params)
-    }
+    },
+    /*
+     * 保存图表
+     * @param 
+     *    ChartClassifyId: number,
+          ChartInfoId: number,
+          ChartEdbInfoList: [],
+          不同图表类型有不同参数
+     * @returns 
+     */
+    chartSave(params){
+        return post('/datamanage/chart_info/save',params)
+    },
+    /*
+     * 上传图表缩略图
+     * @param FormData
+     * @returns 
+     */
+    uploadChartImg(params){
+        return post('/datamanage/chart_info/base64Upload',params)
+    },
+    /*
+     * 设置图表缩略图
+     * @param ChartInfoId ImageUrl
+     * @returns 
+     */
+    setChartImg(params){
+        return post('/datamanage/chart_info/image/set',params)
+    },
+
+    /** 
+	 * 获取图表批量刷新结果
+	 *   Source ReportId ReportChapterId
+	 * 
+	 */
+	getReportrefreshStatus: params => {
+		return post('/datamanage/chart_info/batch_refresh/result',params)
+	}
 } 

+ 17 - 2
src/api/common.js

@@ -20,9 +20,10 @@ export function apiReportingErrInfo(params){
 
 /**
  * 获取oss临时签名
+ * @param {*} params.StorageSource number 1:oss 2:minio
  */
-export function apiGetOSSSign(){
-    return get('/resource/oss/get_sts_token',{})
+export function apiGetOSSSign(params){
+    return get('/resource/oss/get_sts_token',params)
 }
 
 /**
@@ -50,4 +51,18 @@ export function apiCommonSetSysConfig(params){
  */
 export function apiGetLanguageConfig(params){
     return get('/system/config/language',params)
+}
+
+/**
+ * 获取系统按钮权限
+ */
+export function apiGetAuthBtnList(){
+    return get('/system/role/menu/buttons',{})
+}
+
+/**
+ * 获取系统动态配置配置
+ */
+export function apiGetPublicSetting(){
+    return get('/system/config',{})
 }

+ 66 - 0
src/api/crossVarietyChart.js

@@ -0,0 +1,66 @@
+/* 跨品种分析 */
+import { get,post } from "./index";
+export default {
+  
+    /**
+     * 图表详情
+     * @param {*} params  ChartInfoId
+     * @returns 
+     */
+    chartDetail: params => {
+      return get('/cross_variety/chart_info/detail',params)
+    },
+
+    /**
+     * 设置英文
+     * @param {*} params  ChartInfoId ChartNameEn
+     */
+    editChartEn: params => {
+      return post('/cross_variety/chart_info/en/edit',params)
+    },
+
+    /**
+     * 图表搜索
+     * @param {*} params Keyword
+     * @returns 
+     */
+    searchChart: params => {
+      return get('/cross_variety/chart_info/search_by_es',params)
+    },
+
+    /**
+     * 刷新图表
+     * @param {*} params ChartInfoId
+     * @returns 
+     */
+    refreshChart: params => {
+      return get('/cross_variety/chart_info/refresh',params)
+    },
+
+    /**
+     * 图表另存为
+     * @param {*} params ChartInfoId ChartName
+     * @returns 
+     */
+    saveOtherChart: params => {
+      return post('/cross_variety/chart_info/copy',params)
+    },
+    
+    /**
+     * 编辑英文名称
+     * @param {*} params 
+     * @returns 
+     */
+    editChartEn: params => {
+      return post('/cross_variety/chart_info/en/edit',params)
+    },
+
+     /**
+     * 图表设置语言配置信息
+     * @param {*} params  ChartInfoId
+     * @returns 
+     */
+     chartLangOption: params => {
+      return get('/cross_variety/chart_info/relation',params)
+    },
+}

+ 5 - 3
src/api/crypto.js

@@ -1,14 +1,16 @@
-const key = window.key||'Y2kBxILL6jTkc80N860qbHc6';
+let key = '';
 
 class CryptoJS {
 	// 3DES加密,CBC/PKCS5Padding
-	static Des3Encrypt (input) {
+	static Des3Encrypt (input,keyVal) {
+		key=keyVal
 		let genKey = genkey(key, 0, 24);
 		return base64encode(des(genKey.key, input, 1, 1, key.substr(0, 8), 1));
 	}
 
 	// 3DES解密,CBC/PKCS5Padding
-	static Des3Decrypt (input) {
+	static Des3Decrypt (input,keyVal) {
+		key=keyVal
 		let genKey = genkey(key, 0, 24);
 		return des(genKey.key, base64decode(input), 0, 1, key.substr(0, 8), 1);
 	}

+ 11 - 0
src/api/etaConfig.js

@@ -0,0 +1,11 @@
+/* ETA-系统设置-基础配置 相关接口 */
+import { get,post } from "./index";
+
+/**
+ * 获取基本配置信息
+ * @param {*} params 
+ * @returns 
+ */
+export const getBaseConfig = params =>{
+    return get('/business_conf/fetch',params)
+}

+ 3 - 1
src/api/index.js

@@ -59,7 +59,9 @@ _axios.interceptors.response.use(
     // Do something with response data
     let data
     if(import.meta.env.MODE==='production'){
-      data=JSON.parse(CryptoJS.Des3Decrypt(response.data));//解密
+      const headKeyStr=response.headers.dk
+      const desKey=CryptoJS.Des3Decrypt(headKeyStr,'JMCqSoUrTAmyNNIRb0TtlrPk') 
+      data=JSON.parse(CryptoJS.Des3Decrypt(response.data,desKey));//解密
     }else{
       data=response.data
     }

+ 7 - 0
src/api/myETA.js

@@ -235,5 +235,12 @@ export default{
      */
     myETAClassifySort(params){
         return post('/my_chart/classify/move',params)
+    },
+    /**
+     * myETA中保存商品价格曲线图
+     * @param {*} params ChartInfoId LeftMin LeftMax
+     */
+    myETASaveChart(params){
+        return post('/future_good/chart_info/save',params)
     }
 }

+ 15 - 1
src/api/reportEn.js

@@ -146,5 +146,19 @@ export default {
      */
     reportPublishTimeSet(params){
         return post('/english_report/pre_publish',params)
-    }
+    },
+
+    /**
+     * 英文客户列表
+     * @param {
+     * PageSize - 每页数据量 - 必填
+     * CurrentIndex - 当前页数 - 必填
+     * Keywords - 客户名称
+     * SortType - 点击量排序方式: 1-倒序;2-正序
+     * } params 
+     * @returns 
+     */
+    getCustomListEn: params => {
+        return get('/english_report/company/list',params)
+    },
 }

+ 9 - 0
src/api/statisticFeatureChart.js

@@ -17,6 +17,15 @@ export default {
     chartInfoEditEn(params){
         return post('/line_feature/chart_info/en/edit',params)
     },
+
+    /**
+     * 图表搜索
+     * @param {*} params Keyword IsShowMe CurrentIndex
+     * @returns 
+   */
+    searchChart: params => {
+        return get('/line_feature/chart_info/search_by_es',params)
+    },
     /**
      * 分类列表
      */

+ 91 - 3
src/components/SetChartEnName.vue

@@ -6,6 +6,7 @@ import apiFutureChart from '@/api/futureChart'
 import apiCorrelationChart from '@/api/correlationChart'
 import apiLineEquationChart from '@/api/lineEquationChart'
 import apiStatisticFeatureChart from '@/api/statisticFeatureChart'
+import apiCrossVarietyChart from '@/api/crossVarietyChart'
 import { showToast } from 'vant';
 const props = defineProps({
     isShow:{
@@ -53,7 +54,7 @@ watch(
     {immediate:true}
 )
 
-function initForm(){
+async function initForm(){
     const {EdbInfoList} = props.chartInfo
     const {ChartName,ChartNameEn,ExtraConfig} = props.chartInfo.ChartInfo
     //第一项固定为图表名称
@@ -286,6 +287,76 @@ function initForm(){
         return
     }
 
+    if(props.chartInfo.ChartInfo.Source===10) { //跨品种分析
+        let res = await apiCrossVarietyChart.chartLangOption({ChartInfoId: props.chartInfo.ChartInfo.ChartInfoId})
+
+        const { TagList,VarietyList } = res.Data;
+
+        let forwardFormList=[
+            {
+                label:'X轴名称',
+                id: TagList[0].ChartTagId,
+                cnValue:TagList[0].ChartTagName,
+                enValue:TagList[0].ChartTagNameEn,
+                Value:TagList[0].ChartTagName,
+                noEdit:true
+            },
+            {
+                label:'英文X轴名称',
+                id: TagList[0].ChartTagId,
+                cnValue:TagList[0].ChartTagName,
+                enValue:TagList[0].ChartTagNameEn,
+                Value:TagList[0].ChartTagNameEn,
+                placeholder:'请输入英文X轴名称'
+            },
+            {
+                label:'Y轴名称',
+                id: TagList[1].ChartTagId,
+                cnValue:TagList[1].ChartTagName,
+                enValue:TagList[1].ChartTagNameEn,
+                Value:TagList[1].ChartTagName,
+                noEdit:true
+            },
+            {
+                label:'英文Y轴名称',
+                id: TagList[1].ChartTagId,
+                cnValue:TagList[1].ChartTagName,
+                enValue:TagList[1].ChartTagNameEn,
+                Value:TagList[1].ChartTagNameEn,
+                placeholder:'请输入英文Y轴名称'
+            }
+        ]
+
+        formGroup.value[0].formList = formGroup.value[0].formList.concat(forwardFormList)
+
+        for(const item of VarietyList){
+            formGroup.value.push({
+                groupName:`${item.ChartVarietyName}`,
+                id:item.ChartVarietyId,
+                formList:[
+                    {
+                        label:'品种名称',
+                        cnValue:item.ChartVarietyName,
+                        enValue:item.ChartVarietyNameEn,
+                        Value:item.ChartVarietyName,
+                        noEdit:true,
+                    },
+                    {
+                        label:'英文品种名称',
+                        cnValue:item.ChartVarietyName,
+                        enValue:item.ChartVarietyNameEn,
+                        Value:item.ChartVarietyNameEn,
+                        placeholder:'请输入英文品种名称'
+                    }
+                ]
+            })
+        }
+
+        activeGroup.value = formGroup.value.map(i=>i.groupName)
+        return
+
+    }
+
     //普通图
     for(const edb of EdbInfoList){
         formGroup.value.push({
@@ -375,7 +446,22 @@ function handleSave(){
         }
         _ExtraConfig.value = {..._ExtraConfig.value,...suppleConfig}
         params.ExtraConfig = JSON.stringify(_ExtraConfig.value)
-    }else{
+    }
+    else if(Number(props.chartInfo.ChartInfo.Source)===10) {
+        params = {
+            ChartInfoId:props.chartInfo.ChartInfo.ChartInfoId,
+            ChartNameEn:formGroup.value[0].formList[1].Value,
+            TagList: [
+                { ChartTagId: formGroup.value[0].formList[3].id,TagNameEn: formGroup.value[0].formList[3].Value },
+                { ChartTagId: formGroup.value[0].formList[5].id,TagNameEn: formGroup.value[0].formList[5].Value }
+            ],
+            VarietyList: formGroup.value.filter((_,index)=>index>0).map(_ => ({
+                ChartVarietyId: _.id,
+                VarietyNameEn: _.formList[1].Value
+            }))
+        }
+    }
+    else{
         let ChartEdbInfoList = []
         for(let index = 1;index<formGroup.value.length;index++){
             ChartEdbInfoList.push({
@@ -416,8 +502,10 @@ async function saveChartEn(params){
             ChartInfoId: params.ChartInfoId,
             ChartNameEn: params.ChartNameEn,
         })
+    }else if(props.chartInfo.ChartInfo.Source===10){//统计特征
+        res=await apiCrossVarietyChart.editChartEn(params)
     }else{
-        res  = await apiChart.chartInfoEditEn(params)
+        res = await apiChart.chartInfoEditEn(params)
     }
 
     

+ 51 - 0
src/directives/AuthBtn.js

@@ -0,0 +1,51 @@
+//按钮权限指令
+//eg: v-permission="buttonCode"
+//元素必须在组件内存在父元素,否则el.parentNode.removeChild不起作用
+import {useAuthBtnStore} from '@/store/modules/authBtn'
+export default {
+    mounted(el, binding) {
+        let {
+            value
+        } = binding
+        // 拿出所有按钮的code
+        const authBtnStore = useAuthBtnStore()
+        let buttonCodes = authBtnStore.authBtnList.map(item => item.ButtonCode)
+        if (value && typeof (value) == 'string') {
+            // 字符类型
+            if (!buttonCodes.includes(value)) {
+                // 没有权限,删除dom
+                el.parentNode && el.parentNode.removeChild(el)
+            }
+        } else if (Array.isArray(value)) {
+            // 数组类型
+            /**
+             * 权限类型 type
+             * or-只要一个满足 and-全部都满足
+             */
+            let type;
+            let hasType = ['or', 'and'].includes(value[value.length - 1].toLocaleLowerCase())
+            if (hasType) {
+                //参数中有标明type
+                type = value[value.length - 1].toLocaleLowerCase()
+                // 去掉最后一个权限类型参数
+                value.pop()
+                // console.log(value,true);
+                let operation = type == 'or' ? 'some' : 'every'
+                if (!value[operation](item => buttonCodes.includes(item))) {
+                    // 没有权限,删除dom
+                    el.parentNode && el.parentNode.removeChild(el)
+                }
+            } else {
+                type = 'or'
+                // console.log(value,false);
+                if (!value.some(item => buttonCodes.includes(item))) {
+                    // 没有权限,删除dom
+                    el.parentNode && el.parentNode.removeChild(el)
+                }
+            }
+        } else {
+            throw new Error('permission指令参数类型错误')
+        }
+
+    },
+}

+ 2 - 1
src/directives/Index.js

@@ -1,8 +1,9 @@
 // 注册自定义指令
 import ScreenshotDirective from './Screenshot'
 import LongPress from './LongPress';
-
+import Permission from './AuthBtn';
 export function RegisterDirective(app){
     app.directive('screenshot', ScreenshotDirective);
     app.directive('longpress', LongPress);
+    app.directive('permission',Permission)
 }

+ 146 - 50
src/hooks/chart/render.js

@@ -3,6 +3,7 @@
 import {onMounted,ref,nextTick,reactive} from 'vue'
 import {chartDefaultOpts,scatterXAxis,basicYAxis,basicXAxis,leadUnitEnMap,relevanceUnitEnMap} from './config'
 import Highcharts from 'highcharts/highstock';
+import HighchartsFormat from 'highcharts';
 import HightchartsExport from 'highcharts/modules/exporting';
 import Boost from 'highcharts/modules/boost'
 import HighchartszhCN  from './highcahrts-zh_CN.js'
@@ -54,6 +55,19 @@ export function useChartRender(){
     }
 }
 
+/**备注一下 越多越乱 
+ * @params
+ * Source 1 ; chartType只在source:1用到 1曲线 2季节 3面积 4堆积柱 5散点 6组合 7柱形 8截面散点
+ * 2 商品价格
+ * 3 相关性
+ * 4 滚动相关性
+ * 5 商品利润
+ * 6 拟合方程
+ * 7 统计特征/标准差
+ * 8 统计特征/百分位
+ * 9 统计特征/频率
+ * 10 跨品种分析
+ */
 export function chartRender({data,renderId,lang='zh',changeLangIsCheck,showChartTitle=true}){
     // 初始化掉极值数据
     axisLimitState.leftIndex=-1
@@ -80,26 +94,6 @@ export function chartRender({data,renderId,lang='zh',changeLangIsCheck,showChart
     RenderDomId.value=renderId
     chartData.value=data
 
-    // if([1,3,4,5,6].includes(data.ChartInfo.ChartType)){
-    //     const chartSetMap = {
-    //         1: setSplineOpt,
-    //         3: setStackOrCombinChart,
-    //         4: setStackOrCombinChart,
-    //         5: setScatterOptions,
-    //         6: setStackOrCombinChart
-    //     };
-    //     chartOpt=chartSetMap[data.ChartInfo.ChartType](data)
-    // }else if(data.ChartInfo.ChartType ===2 ) {
-    //     chartOpt=setSeasonOpt(data)
-    // }else if(data.ChartInfo.ChartType ===7){//奇怪柱形图依赖数据
-    //     chartOpt=initBarData(data);
-    // }else if(data.ChartInfo.ChartType ===8){//商品价格曲线
-    //     chartOpt=initCommodityData(data);
-    // }else if(data.ChartInfo.ChartType ===9){//相关性图
-    //     chartOpt=initRelevanceChart(data);
-    // }else if(data.ChartInfo.ChartType ===10){//截面散点图
-    //     chartOpt=setSectionScatterChart(data);
-    // }
     if(data.ChartInfo.Source===1){
         const chartSetMap = {
             1: setSplineOpt,
@@ -120,6 +114,8 @@ export function chartRender({data,renderId,lang='zh',changeLangIsCheck,showChart
         chartOpt=setSplineOpt(data);
     }else if([9].includes(data.ChartInfo.Source)){//统计频率
         chartOpt=setStatisticFrequency(data);
+    }else if(data.ChartInfo.Source===10) {
+        chartOpt = setCrossVarietyChart(data)
     }
 
 
@@ -565,23 +561,16 @@ function setSplineOpt(e){
     let maxTime=Math.max.apply(null,minAndMaxTimeTemArr)
 
 
-    const bool_time = xTimeDiffer(minTime,maxTime)
-    if(bool_time){
-        xAxis={
-            ...basicXAxis,
-            labels: {
-              formatter: (ctx)=> {
-                return Highcharts.dateFormat('%m/%d', ctx.value)
-              }
-            }
-        }
-    }
-    // console.log(((maxTime-minTime)/6)/(24*3600*1000),':天');
-    // let maxYear=new Date(maxTime).getFullYear()
-    // let minYear=new Date(minTime).getFullYear()
-
+    const isLessThanOneYear = xTimeDiffer(minTime,maxTime)
     xAxis={
         ...basicXAxis,
+        labels: {
+            formatter: function (ctx) {
+                return isLessThanOneYear
+                ? HighchartsFormat.dateFormat('%m/%d', ctx.value)
+                : HighchartsFormat.dateFormat('%y/%m', ctx.value);
+            }
+        },
         tickInterval:((maxTime-minTime)/6)/(24*3600*1000)>30?(maxTime-minTime)/6:24*3600*1000*30,
     }
     
@@ -839,7 +828,7 @@ function setSeasonOpt(e){
 		},
         labels: {
             formatter: (ctx)=> {
-                return Highcharts.dateFormat('%m/%d', ctx.value)
+                return HighchartsFormat.dateFormat('%m/%d', ctx.value)
             }
         }
     }
@@ -1032,21 +1021,16 @@ function setStackOrCombinChart(e){
     let minTime=Math.min.apply(null,minAndMaxTimeTemArr)
     let maxTime=Math.max.apply(null,minAndMaxTimeTemArr)
 
-
-    const bool_time = xTimeDiffer(minTime,maxTime)
-    if(bool_time){
-        xAxis={
-            ...basicXAxis,
-            labels: {
-              formatter: (ctx)=> {
-                return Highcharts.dateFormat('%m/%d', ctx.value)
-              }
-            }
-        }
-    }
-
+    const isLessThanOneYear = xTimeDiffer(minTime,maxTime)
     xAxis={
         ...basicXAxis,
+        labels: {
+            formatter: function (ctx) {
+                return isLessThanOneYear
+                ? HighchartsFormat.dateFormat('%m/%d', ctx.value)
+                : HighchartsFormat.dateFormat('%y/%m', ctx.value);
+            }
+        },
         tickInterval:((maxTime-minTime)/6)/(24*3600*1000)>30?(maxTime-minTime)/6:24*3600*1000*30,
     }
     
@@ -1078,7 +1062,7 @@ function setStackOrCombinChart(e){
 
     return {
         series,
-        xAxis:[xAxis],
+        xAxis:xAxis,
         yAxis,
         rangeSelector:{ enabled: false}
     }
@@ -1820,3 +1804,115 @@ function setSectionScatterChart({DataResp}) {
         tooltip
     }
 }
+
+/* 跨品种分析 */
+function setCrossVarietyChart({DataResp,EdbInfoList}) {
+    axisLimitState.leftIndex=-1
+    axisLimitState.rightIndex=-1
+    axisLimitState.rightTwoIndex=-1
+
+    const { DataList,XName,YName,XNameEn,YNameEn,XMaxValue,XMinValue,YMaxValue,YMinValue } = DataResp;
+
+     //y轴
+     let yAxis = {
+        ...basicYAxis,
+        title: {
+          text: YName,
+          textCh:YName,// 中文
+          textEn:YNameEn||YName,
+          align: 'middle',
+        },
+        opposite: false,
+        reversed: false,
+        min: Number(YMinValue),
+        max: Number(YMaxValue),
+        tickWidth: 1,
+      }
+
+      // x轴
+      let xAxis = {
+        ...scatterXAxis,
+        title: {
+          text: XName,
+          textCh:XName,// 中文
+          textEn:XNameEn || XName,
+          align: 'middle',
+        },
+        min: Number(XMinValue),
+        max: Number(XMaxValue),
+      }
+
+       //数据列
+      let series = [];
+      DataList.forEach(item => {
+        //数据列
+        let series_item = {
+          data: [],
+          type: 'scatter',
+          name: item.Name,
+          nameCh: item.Name,
+          nameEn: item.NameEn||item.Name,
+          color: item.Color,
+          lineWidth: 0,
+          chartType: 'linear',
+          zIndex:1
+        }
+        item.CoordinatePointData.forEach(_ => {
+          series_item.data.push({x: _.X,y: _.Y,})
+        })
+        series.push(series_item);
+      })
+
+      let tooltip = {
+        formatter: function() {
+          let series_obj = DataList.find(_ => _.Name === this.series.name);
+          let ponit_obj = series_obj.CoordinatePointData.find(_ => _.X ===this.x && _.Y===this.y);
+          
+          let xEdbInfo = EdbInfoList.find(_ => _.EdbInfoId===ponit_obj.XEdbInfoId);
+          let yEdbInfo = EdbInfoList.find(_ => _.EdbInfoId===ponit_obj.YEdbInfoId);
+
+          let str=`<b>${ this.series.name }</b>`;
+          str += `<br><span style="color:${this.color}">\u25CF</span>${xEdbInfo.EdbName}: ${this.x} ${ponit_obj.XDate}<br>`;
+          str += `<span style="color:${this.color}">\u25CF</span>${yEdbInfo.EdbName}: ${this.y} ${ponit_obj.YDate}`;
+
+          return str
+        },
+        formatterCh: function() {
+          let series_obj = DataList.find(_ => _.Name === this.series.name);
+          let ponit_obj = series_obj.CoordinatePointData.find(_ => _.X ===this.x && _.Y===this.y);
+          
+          let xEdbInfo = EdbInfoList.find(_ => _.EdbInfoId===ponit_obj.XEdbInfoId);
+          let yEdbInfo = EdbInfoList.find(_ => _.EdbInfoId===ponit_obj.YEdbInfoId);
+
+          let str=`<b>${ this.series.name }</b>`;
+          str += `<br><span style="color:${this.color}">\u25CF</span>${xEdbInfo.EdbName}: ${this.x} ${ponit_obj.XDate}<br>`;
+          str += `<span style="color:${this.color}">\u25CF</span>${yEdbInfo.EdbName}: ${this.y} ${ponit_obj.YDate}`;
+
+          return str
+          
+        },
+        formatterEn: function() {
+          let series_obj = DataList.find(_ => _.NameEn === this.series.name);
+          let ponit_obj = series_obj.CoordinatePointData.find(_ => _.X ===this.x && _.Y===this.y);
+          
+          let xEdbInfo = EdbInfoList.find(_ => _.EdbInfoId===ponit_obj.XEdbInfoId);
+          let yEdbInfo = EdbInfoList.find(_ => _.EdbInfoId===ponit_obj.YEdbInfoId);
+
+          let str=`<b>${ this.series.name }</b>`;
+          str += `<br><span style="color:${this.color}">\u25CF</span>${xEdbInfo.EdbNameEn}: ${this.x} ${ponit_obj.XDate}<br>`;
+          str += `<span style="color:${this.color}">\u25CF</span>${yEdbInfo.EdbNameEn}: ${this.y} ${ponit_obj.YDate}`;
+
+          return str
+        }
+      }
+
+      return {
+        title: {
+          text:''
+        },
+        series,
+        yAxis: [yAxis],
+        xAxis,
+        tooltip
+      }
+}

+ 145 - 0
src/hooks/useAuthBtn.js

@@ -0,0 +1,145 @@
+//权限按钮相关
+import {useAuthBtnStore} from '@/store/modules/authBtn'
+const authBtnStore = useAuthBtnStore()
+/*
+---------------------------研报管理----------------- 
+*/
+export const reportManageBtn = {
+    reportManage_sendMsg:'reportManage:sendMsg',//推送消息/已推送消息
+    reportManage_reportView:'reportManage:reportView',//研报预览:即是否能点击研报名称跳转预览页面
+    reportManage_reportDel:'reportManage:reportDel',//删除研报
+    reportManage_reportEdit:'reportManage:reportEdit',//编辑研报
+    reportManage_cancelPublish:'reportManage:cancelPublish',//取消发布
+    reportManage_publish:'reportManage:publish',//发布研报
+    reportManage_reportList:'reportManage:reportList',//研报列表的选项
+    reportManage_reportList_uv:'reportManage:reportList:uv',//研报列表-PV/UV
+    reportManage_reportList_sendTime:'reportManage:reportList:sendTime',//研报列表-报告推送时间
+    reportManage_dayWeekReportAdd:'reportManage:dayWeekReportAdd',//添加晨报周报
+    reportManage_reportAdd:'reportManage:reportAdd',//添加研报
+}
+export const enReportManageBtn = {
+    enReport_reportView:'enReport:reportView',//研报预览:即是否能点击研报名称跳转预览页面
+    enReport_sendEmail:'enReport:sendEmail',//群发邮件/群发日志
+    enReport_reportDel:'enReport:reportDel',//删除研报
+    enReport_reportEdit:'enReport:reportEdit',//编辑研报
+    enReport_cancelPublish:'enReport:cancelPublish',//取消发布
+    enReport_publish:'enReport:publish',//发布研报
+    enReport_reportAdd:'enReport:reportAdd',//添加研报
+}
+
+/*
+--------------------------智能PPT-----------------
+PS:由于移动端中英文PPT是一个页面,特地把按钮名称设置为一致的方便处理
+与ETA后台不同,修改时请注意
+*/
+export const pptBtn={
+    ppt_del:'ppt:del',
+    ppt_copy:'ppt:copy',
+    ppt_download:'ppt:download',
+    ppt_show:'ppt:show',//演示
+    ppt_publish:'ppt:publish',
+    ppt_visible:'ppt:visible',//可见权限
+    ppt_save:'ppt:save',//添加PPT按钮,同时也控制目录重命名权限
+}
+export const enPPTBtn={
+    ppt_del:'pptEn:del',
+    ppt_copy:'pptEn:copy',
+    ppt_download:'pptEn:download',
+    ppt_show:'pptEn:show',
+    ppt_publish:'pptEn:publish',
+    ppt_visible:'pptEn:visible',
+    ppt_save:'pptEn:save',//添加PPT按钮,同时也控制目录重命名权限
+}
+
+/*
+--------------------------ETA指标库---------------
+*/
+export const edbDataBtn={
+        /*-----------指标详情按钮--------- */
+        edbData_refreshAll:'edbData:refreshAll',//全部刷新
+        edbData_newestValue:'edbData:newestValue',//添加最新值
+        edbData_enNameSetting:'edbData:enNameSetting',//设置英文名称
+        edbData_edbSource:'edbData:edbSource',//指标溯源
+        edbData_copyData:'edbData:copyData',//复制数据
+        edbData_edit:'edbData:edit',//指标编辑,也包括列表项的编辑按钮
+        edbData_update:'edbData:update',//更新指标
+        edbData_saveEdb:'edbData:saveEdb',//保存
+        edbData_deleteEdb:'edbData:deleteEdb',//删除
+        edbData_showChartBasis:'edbData:showChartBasis',//展示/隐藏同比图
+        edbData_switchSeason:'edbData:switchSeason',//切换季节性图
+        edbData_editLimit:'edbData:editLimit',//编辑上下限
+        edbData_calculateAgain:'edbData:calculateAgain',//重新计算
+        /*------------页面按钮---------- */
+        edbData_replaceEdb:'edbData:replaceEdb',//替换指标
+        edbData_calcuEdb:'edbData:calcuEdb',//计算指标
+        edbData_addEdb:'edbData:addEdb',//添加指标
+        edbData_switchEn:'edbData:switchEn',//切换英文版
+        edbData_classifyOpt_add:'edbData:classifyOpt:add',//添加/编辑分类
+        edbData_classifyOpt_delete:'edbData:classifyOpt:delete',//删除分类
+        edbData_classifyOpt_move:'edbData:classifyOpt:move',//移动分类
+        edbData_checkRelatedChart:'edbData:checkRelatedChart',//查看关联图表
+        edbData_checkRelatedEdb:'edbData:checkRelatedEdb',//查看关联指标
+        edbData_checkCalcChart:'edbData:checkCalcChart',//查看计算指标
+}
+
+/*
+-------------------------ETA图库------------------
+*/
+export const chartLibBtn={
+    /*-----------图表详情按钮--------- */
+    chartLib_del:'chartLib:del',//删除
+    chartLib_enNameSetting:'chartLib:enNameSetting',//设置英文名称
+    chartLib_copyWechat:'chartLib:copyWechat',//保存图片(两个按钮控制)
+    chartLib_copyOffice:'chartLib:copyOffice',//保存图片(两个按钮控制)
+    chartLib_otherSave:'chartLib:otherSave',//另存为
+    chartLib_save:'chartLib:save',//保存
+    chartLib_refresh:'chartLib:refresh',//刷新
+    chartLib_addMy:'chartLib:addMy',//加入我的图库
+    chartLib_share:'chartLib:share',//分享
+    chartLib_editLimit:'chartLib:editLimit',//编辑上下限
+    /*------------页面按钮---------- */
+    chartLib_switchEn:'chartLib:switchEn',//切换中英文
+    chartLib_isOnlyMine:'chartLib:isOnlyMine',//只看我的
+    chartLib_classifyOpt_add:'chartLib:classifyOpt:add',//新增/编辑分类
+	chartLib_classifyOpt_delete:'chartLib:classifyOpt:delete',//删除分类
+}
+
+/*
+------------------------MyETA--------------------
+*/
+export const myETABtn={
+    /*-----------图表详情弹窗按钮--------- */
+    myChart_del:'myChart:del',
+    myChart_enNameSetting:'myChart:enNameSetting',
+    myChart_copyWechat:'myChart:copyWechat',
+    myChart_copyOffice:'myChart:copyOffice',
+    myChart_otherSave:'myChart:otherSave',
+    myChart_save:'myChart:save',
+    myChart_refresh:'myChart:refresh',
+    myChart_copyTo:'myChart:copyTo',
+    myChart_share:'myChart:share',
+    myChart_move:'myChart:move',//移出,同时也控制列表项的移出
+    myChart_copyData:'myChart:copyData',//复制数据
+    myChart_checkData:'myChart:checkData',//查看数据
+    myChart_editLimit:'myChart:editLimit',//编辑上下限
+    /*-----------页面按钮--------- */
+    myChart_selectChart:'myChart:selectChart',//选择图表
+    myChart_classifyOpt_copy:'myChart:classifyOpt:copy',//复制
+    myChart_classifyOpt_show:'myChart:classifyOpt:show',//可见权限
+    myChart_classifyOpt_edit:'myChart:classifyOpt:edit',//添加我的分类
+    myChart_classifyOpt_rename:'myChart:classifyOpt:rename',//重命名
+    myChart_classifyOpt_delete:'myChart:classifyOpt:delete',//删除
+}
+
+
+export function useAuthBtn(){
+    const isShowBtn = ()=>{}
+    const checkAuthBtn = (buttonCode)=>{
+        const list = authBtnStore.authBtnList||[]
+        return list.map(item=>item.ButtonCode).includes(buttonCode)
+    }
+    return {
+        isShowBtn,
+        checkAuthBtn
+    }
+}

+ 96 - 5
src/hooks/useUploadFileToOSS.js

@@ -1,6 +1,12 @@
 // 上传文件到阿里云oss
 import OSS from 'ali-oss'
 import {apiGetOSSSign} from '@/api/common'
+/* const Minio = require('minio')
+const stream = require('stream') */
+import * as Minio from 'minio-es'
+import * as stream from 'stream'
+import * as buffer from 'buffer'
+const Buffer = buffer.Buffer
 
 /**
  * 上传到oss
@@ -10,7 +16,7 @@ import {apiGetOSSSign} from '@/api/common'
  * @returns fileUrl 返回文件在阿里云上的地址
  */
 export async function useUploadFileToOSS(data,fileName,isMultipart=false){
-    const signRes=await apiGetOSSSign()
+    const signRes=await apiGetOSSSign({StorageSource:1})
     if(signRes.Ret!==200) return
     const accessKeyId=signRes.Data.AccessKeyId
     const accessKeySecret=signRes.Data.AccessKeySecret
@@ -18,15 +24,15 @@ export async function useUploadFileToOSS(data,fileName,isMultipart=false){
 
     const ALOSSINS=new OSS({
         // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
-        region: "oss-cn-shanghai",
+        region: signRes.Data.RegionId,
         // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
         accessKeyId: accessKeyId,
         accessKeySecret: accessKeySecret,
         // 从STS服务获取的安全令牌(SecurityToken)。
         stsToken: stsToken,
         // 填写Bucket名称,例如examplebucket。
-        bucket: "hzchart",
-        endpoint:'hzstatic.hzinsights.com',
+        bucket: signRes.Data.Bucketname,
+        endpoint:signRes.Data.Endpoint,
         cname:true,
         timeout:6000000
     });
@@ -52,7 +58,7 @@ export async function useUploadFileToOSS(data,fileName,isMultipart=false){
         }
 
         if(res.res.status===200){
-            resUrl='https://hzstatic.hzinsights.com/'+res.name
+            resUrl=signRes.Data.Imghost+res.name
         }
     } catch (error) {
         console.log('阿里云上传失败',error);
@@ -60,4 +66,89 @@ export async function useUploadFileToOSS(data,fileName,isMultipart=false){
     
 
     return resUrl
+}
+
+//上传到minio
+// minio sdk 文档 https://min.io/docs/minio/linux/developers/javascript/API.html
+export async function useUploadToMinIO (file, fileName, options = {}){
+    const res = await apiGetOSSSign({StorageSource:2})
+    if (res.Ret !== 200) {
+        console.log("获取minio临时签名错误,res.Ret=" + res.Ret)
+        return
+    }
+    const minioClient = new Minio.Client({
+        endPoint: res.Data.Endpoint.split(':')[0],
+        port: Number(res.Data.Port) || undefined,
+        useSSL: res.Data.UseSSL.toLocaleLowerCase() == "false" ? false : true,
+        accessKey: res.Data.AccessKeyId,
+        secretKey: res.Data.SecretKeyId,
+    })
+    console.log(minioClient);
+    try {
+        var metaData = {
+            ...{
+                'Content-Type': file.type || 'application/octet-stream',
+                "Content-Length": file.size,
+            },
+            ...options
+        }
+
+        minioClient.bucketExists(res.Data.Bucketname, function (err, exists) {
+            if (err) {
+                return console.log("minio 查看桶是否存在失败" + err)
+            }
+            if (!exists) {
+                // 不存在桶,创建桶
+                console.log("桶不存在,先创建桶", res.Data.Bucketname);
+                minioClient.makeBucket(res.Data.Bucketname, res.Data.RegionId, function (err) {
+                    if (err) {
+                        console.log("minio 创建桶失败" + err)
+                        return 
+                    }
+                    let reader = new FileReader();
+                    // console.log(reader);
+                    reader.readAsArrayBuffer(file);
+                    reader.onloadend = function (e) {
+                        const dataurl = e.target.result;
+                        let bufferStream = new stream.PassThrough();
+                        // 转化成数据流  minio接受数据流格式
+                        bufferStream.end(Buffer.from(dataurl))
+                        minioClient.putObject(res.Data.Bucketname, fileName, bufferStream, file.size, metaData, function (err, etag) {
+                            if (err) {
+                                return console.log("上传到minio失败:" + err)
+                            }
+                            let fileUrl = fileName.startsWith('/') ? res.Data.ImgHost + fileName : res.Data.ImgHost + "/" + fileName
+                            return fileUrl
+                        })
+                    }
+                })
+            }
+            if (exists) {
+                // console.log("桶存在",res.Data.Bucketname);
+                let reader = new FileReader();
+                console.log('reader',reader);
+                reader.readAsArrayBuffer(file);
+                reader.onloadend = function (e) {
+                    const dataurl = e.target.result;
+                    let bufferStream = new stream.PassThrough();
+                    bufferStream.end(Buffer.from(dataurl))
+                    minioClient.putObject(res.Data.Bucketname, fileName, bufferStream, metaData, function (err, etag) {
+                        if (err) {
+                            console.log("上传到minio失败:" + err)
+                            return 
+                        }
+                        let fileUrl = fileName.startsWith('/') ? res.Data.ImgHost + fileName : res.Data.ImgHost + "/" + fileName
+                        //console.log('test',fileUrl)
+                        return fileUrl
+                    })
+                }
+            }
+        })
+    } catch (error) {
+        console.error(error);
+        if (error.name !== "cancel") {
+            //不是取消上传的则给错误提示
+            this.$message.warning("上传失败,请刷新重试");
+        }
+    }
 }

+ 9 - 0
src/main.js

@@ -17,6 +17,15 @@ import svgIcon from "@/components/SvgIcon.vue";
 //引入注册脚本
 import 'virtual:svg-icons-register'
 
+/* import { Buffer } from 'buffer'; */
+import * as buffer from 'buffer'
+if(typeof(window.global)==="undefined"){
+    window.global = window
+}
+if(typeof(window.Buffer)==="undefined"){
+    window.Buffer = buffer.Buffer
+}
+
 
 if(import.meta.env.MODE==='test'){
     const vConsole = new VConsole();

+ 12 - 2
src/router/index.js

@@ -10,6 +10,8 @@
  */
 import { createRouter, createWebHistory } from "vue-router";
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
+import {useAuthBtnStore} from '@/store/modules/authBtn'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
 
 import { pptRoutes } from "./ppt";
 import {pptENRoutes} from './pptEn'
@@ -18,6 +20,7 @@ import {reportRoutes} from './report'
 import {reportEnRoutes} from './reportEn'
 import {chartETARoutes} from './chartETA'
 import {dataEDBRoutes} from './dataEDB'
+import { storeToRefs } from "pinia";
 
 const routes = [
   	{
@@ -113,9 +116,16 @@ function setKeeplive(to){
 	}
 }
 
-router.beforeEach((to, from, next) => {
+router.beforeEach(async(to, from, next) => {
 	setKeeplive(to)
-
+    const authBtnStore = useAuthBtnStore()
+	const publicSettingStore = usePublicSettingStore()
+	if(to.path!='/login'){
+		authBtnStore.getAuthList()
+		if(Object.keys(publicSettingStore.publicSetting).length===0){
+			publicSettingStore.getPublicSetting()
+		}
+	}
 	document.title = to.meta.title;
   	next();
 });

+ 27 - 0
src/store/modules/authBtn.js

@@ -0,0 +1,27 @@
+/**
+ * 权限按钮
+ */
+import {apiGetAuthBtnList} from '@/api/common';
+import { defineStore } from "pinia";
+export const useAuthBtnStore = defineStore('authBtn',{
+    state:()=>{
+        return {
+            authBtnList:[]
+        }
+    },
+    actions:{
+        getAuthList(){
+            return new Promise((resolve)=>{
+                apiGetAuthBtnList().then(res=>{
+                    this.authBtnList = res.Data||[]
+                })
+                /* 
+                //mock
+                this.authBtnList = [
+                    {ButtonCode:'reportManage:reportAdd'},
+                ] */
+                resolve('权限获取成功')
+            })
+        }
+    }
+})

+ 19 - 0
src/store/modules/etaConfig.js

@@ -0,0 +1,19 @@
+/* eta基本配置信息 */
+import { defineStore } from "pinia";
+import { getBaseConfig } from '@/api/etaConfig.js'
+
+export const useConfigSettingStore = defineStore('etaConfig',{
+  state: () => {
+    return {
+      etaConfigInfo: null
+    }
+  },
+
+  actions: {
+    async getBaseConfigSetting() {
+      const res = await getBaseConfig();
+      if(res.Ret !== 200) return
+      this.etaConfigInfo = res.Data
+    }
+  }
+})

+ 26 - 0
src/store/modules/publicSetting.js

@@ -0,0 +1,26 @@
+/* 系统动态配置配置 */
+import { defineStore } from "pinia";
+import { apiGetPublicSetting } from '@/api/common'
+
+export const usePublicSettingStore = defineStore('publicSetting',{
+  state: () => {
+    return {
+      publicSetting: {}
+    }
+  },
+
+  actions: {
+    async getPublicSetting() {
+      const res = await apiGetPublicSetting();
+      if(res.Ret !== 200) return
+      let filterObj = {}
+      let data = res.Data||[]
+      data.forEach(item => {
+        if(!filterObj[item.ConfKey]) {
+          filterObj[item.ConfKey] = item.ConfVal;
+        }
+      })
+      this.publicSetting = filterObj
+    }
+  }
+})

+ 4 - 1
src/views/Login.vue

@@ -59,10 +59,13 @@ const onSubmit2 = ()=>{
     const values = loginForm.value.getValues()
     if(activeModel.value==='ordinaryModel'){
         loginForm.value.validate(['username','password']).then(()=>{
+            const t=new Date().getTime()
+            const md5Key='MiQM9yusNA9T2uIH'
             userLogin({
                 LoginType:1,
                 Username:values.username,
-                Password:md5(values.password),
+                Password:md5(md5(values.password)+md5Key+t),
+                ReqTime:`${t}`
             },values)
         }).catch((error)=>{
             console.log('error',error)

+ 155 - 17
src/views/chartETA/ChartDetail.vue

@@ -14,10 +14,12 @@ import AddChartToMyETA from './components/AddChartToMyETA.vue'
 import SetChartEnName from './components/SetChartEnName.vue'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
 import { setExtremumDate } from '@/hooks/chart/commonFun.js'
-
+import {chartLibBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
+const {checkAuthBtn} = useAuthBtn()
 import _ from 'lodash';
 
-
+const publicSettingStore = usePublicSettingStore()
 const {options,axisLimitState,chartRender}=useChartRender()
 const { width } = useWindowSize()
 const cachedViewsStore=useCachedViewsStore()
@@ -260,43 +262,68 @@ function handleShowSourceDetail(){
 let showMoreAction = ref(false)
 let chartActions = ref([])
 function getChartActions(chartInfo){
-    return [
+    //加上权限判断
+    const actionsArr = [
         {
             type:'refresh',
             label:'刷新',
-            show:true
+            show:true,
+            btnCode:chartLibBtn.chartLib_refresh
         },
         {
             type:'share',
             label:'分享',
-            show:!Boolean(chartInfo.Disabled)
+            show:!Boolean(chartInfo.Disabled),
+            btnCode:chartLibBtn.chartLib_share
+        },
+        {
+            type:'save',
+            label:'保存',
+            show:true,
+            btnCode:chartLibBtn.chartLib_save
         },
         {
             type:'saveOther',
             label:'另存为',
-            show:true
+            show:true,
+            btnCode:chartLibBtn.chartLib_otherSave
         },
         {
             type:'savePic',
             label:'保存图片',
-            show:true
+            show:true,
         },
         {
             type:'setEnName',
             label:'设置英文名称',
-            show:true
+            show:true,
+            btnCode:chartLibBtn.chartLib_enNameSetting
         },
         {
             type:'addToMyETA',
             label:'加入我的图库',
-            show:true
+            show:true,
+            btnCode:chartLibBtn.chartLib_addMy
         },
         {
             type:'delete',
             label:'删除',
-            show:chartInfo.IsEdit
+            show:chartInfo.IsEdit,
+            btnCode:chartLibBtn.chartLib_del
         }
     ]
+    let currentArr=[]
+    actionsArr.forEach(item=>{
+        if(item.btnCode&&checkAuthBtn(item.btnCode)){
+            currentArr.push(item)
+        }
+        if(item.type==='savePic'){
+            if(checkAuthBtn(chartLibBtn.chartLib_copyWechat)&&checkAuthBtn(chartLibBtn.chartLib_copyOffice)){
+                currentArr.push(item)
+            }
+        }
+    })
+    return currentArr
 }
 
 function handleActionClick(action){
@@ -307,7 +334,8 @@ function handleActionClick(action){
         'savePic':saveChartPic,
         'setEnName':openSetChartEnNameDialog,
         'addToMyETA':openAddToMyETADialog,
-        'delete':deleteChart
+        'delete':deleteChart,
+        'save':saveChart,
     }
     eventMap[action.type]()
     //showMoreAction.value = false
@@ -341,7 +369,8 @@ async function getShareLink(){
         )
     }
     if(!confirmFlag.value) return 
-    const linkUrl = `${import.meta.env.VITE_CHART_LINK}?code=${chartInfo.value.UniqueCode}&fromType=share&lang=${currentLang}`
+    // const linkUrl = `${import.meta.env.VITE_CHART_LINK}?code=${chartInfo.value.UniqueCode}&fromType=share&lang=${currentLang}`
+    const linkUrl = `${publicSettingStore.publicSetting.ChartViewUrl}/chartshow?code=${chartInfo.value.UniqueCode}&fromType=share&lang=${currentLang}`
     //console.log('url',linkUrl)
     if(navigator.clipboard&&window.isSecureContext){
          try{
@@ -372,7 +401,116 @@ function checkChartEnOption(){
     }
     return true
 }
-
+//保存图表
+function saveChart(){
+    //获取每条线的指标配置
+    let arr = edbList.value.map((item)=>{
+        return {
+            ChartColor: item.ChartColor,
+            PredictChartColor: item.PredictChartColor,
+            ChartStyle: item.ChartStyle,
+            ChartWidth: Number(item.ChartWidth),
+            EdbInfoId: item.EdbInfoId,
+            EdbInfoType: item.EdbInfoType,
+            IsAxis: item.IsAxis,
+            IsOrder: item.IsOrder,
+            LeadUnit: item.EdbInfoType ? '' : item.LeadUnit,
+            LeadValue: item.EdbInfoType ? 0 : Number(item.LeadValue),
+            MaxData: Number(item.MaxData),
+            MinData: Number(item.MinData),
+        }
+    })
+    //所有图表的公共参数
+    let public_param = {
+        ChartClassifyId: chartInfo.value.ChartClassifyId,
+        ChartInfoId: chartInfo.value.ChartInfoId,
+        ChartEdbInfoList: arr,
+    }
+    //根据ChartType决定剩余参数
+    let type_param = {}
+    switch(chartInfo.value.ChartType){
+        case 2:
+            type_param = {
+              DateType: chartState.yearVal,
+              StartYear:chartState.startYear || 0,
+              Calendar: chartState.calendarType,
+              StartDate: chartState.startTime||'',
+              EndDate: chartState.endTime||'',
+            }
+            break
+        case 7:
+            type_param = {
+              DateType: 6,
+              LeftMin: String(axisLimitState.leftMin),
+              LeftMax: String(axisLimitState.leftMax),
+            }
+            break
+        case 10:
+            type_param = {
+              DateType: 6,
+              Calendar: "公历",
+              ExtraConfig: JSON.stringify({
+                ...JSON.parse(chartInfo.value.ExtraConfig),
+                XMinValue: String(axisLimitState.xMin),
+                XMaxValue: String(axisLimitState.xMax),
+                YMinValue: String(axisLimitState.leftMin),
+                YMaxValue: String(axisLimitState.leftMax),
+              })
+            }
+            break
+    }
+    if(sameOptionType.includes(chartInfo.value.ChartType)){
+        type_param = {
+            DateType: chartState.yearVal,
+            StartYear:chartState.startYear || 0,
+            StartDate:chartState.yearVal === 5 || chartState.yearVal === 6
+                ? chartState.startTime
+                : '',
+            EndDate: chartState.yearVal === 5 ? chartState.endTime : '',
+        }
+    }
+    let params = {...public_param,...type_param}
+    //保存
+    apiChart.chartSave(params).then(async res=>{
+        if(res.Ret!==200) return
+        showToast("保存成功")
+        //更新缩略图
+        setChartImage()
+        //保存成功后修改route.query防止刷新后请求旧参数
+        router.replace({
+            path:'/chartETA/chartdetail',
+            query:{
+                id:route.query.id,
+                chartType:route.query.chartType,
+                chartClassifyId:route.query.chartClassifyId,
+                DateType:params.DateType,
+                StartDate:chartState.startTime,
+                EndDate:chartState.endTime,
+                Calendar:chartState.calendarType,
+                SeasonStartDate:chartState.startTime,
+                SeasonEndDate:chartState.endTime,
+                StartYear:chartState.startYear
+            }
+        })
+    })
+    
+}
+async function setChartImage(){
+    const svgData = highChart.value.getSVG({
+        chart: {
+            width: 340,
+            height: 230,
+        }
+    })
+    let form = new FormData();
+    form.append('Img', svgData);
+    let {Data,Ret} = await apiChart.uploadChartImg(form)
+    if(Ret!==200||!Data) return
+    await apiChart.setChartImg({
+        ChartInfoId:Number(route.query.id),
+        ImageUrl:Data.ResourceUrl
+    })
+}
 let isShowSaveOtherDialog = ref(false)
 let catalogNodes = ref([])
 //另存为
@@ -571,7 +709,7 @@ function openDateSelect(){
                 </span>
             </div>
             <div class="right-action-box">
-                <div class="item" @click="showMoreAction=true" >
+                <div class="item" @click="showMoreAction=true" v-if="chartActions.length">
                     <img src="@/assets/imgs/chartETA/more-icon.png" alt="">
                     <span>更多设置</span>
                 </div>
@@ -611,7 +749,7 @@ function openDateSelect(){
         <!-- 指标模块 -->
         <div class="edb-list-box">
             <!-- pad 设置上下限按钮 -->
-            <div class="pad-limit-set-btn" @click="handleShowAxisLimitOpt" v-if="![3,4,6,7,8,9].includes(chartInfo.Source)">设置上下限</div>
+            <div class="pad-limit-set-btn" @click="handleShowAxisLimitOpt" v-if="![3,4,6,7,8,9].includes(chartInfo.Source)&&checkAuthBtn(chartLibBtn.chartLib_editLimit)">设置上下限</div>
             <!-- <div class="list-lable">指标信息</div> -->
             <div class="list-box">
                 <div class="list-item" v-for="item in edbList" :key="item.EdbInfoId" @click="handleShowEDBInfo(item)">
@@ -631,11 +769,11 @@ function openDateSelect(){
                 <img class="icon" style="transform: rotate(180deg);" src="@/assets/imgs/icon_arrow.png" alt="">
                 <div>下一张</div>
             </div>
-            <div class="item" @click="handleShowAxisLimitOpt" v-if="![3,4,6,7,8,9].includes(chartInfo.Source)">
+            <div class="item" @click="handleShowAxisLimitOpt" v-if="![3,4,6,7,8,9].includes(chartInfo.Source)&&checkAuthBtn(chartLibBtn.chartLib_editLimit)">
                 <img class="icon" src="@/assets/imgs/myETA/icon_limit.png" alt="">
                 <div>上下限</div>
             </div>
-            <div class="item" @click="showMoreAction=true">
+            <div class="item" @click="showMoreAction=true" v-if="chartActions.length">
                 <img class="icon" src="@/assets/imgs/myETA/icon_menu.png" alt="">
                 <div>更多</div>
             </div>

+ 6 - 5
src/views/chartETA/List.vue

@@ -9,8 +9,9 @@ import CatalogItem from './components/CatalogItem.vue';
 import OptionPopup from './components/OptionPopup.vue';
 import AddChartToMyETA from './components/AddChartToMyETA.vue';
 import TreeSelectPop from './components/TreeSelectPop.vue';
-import apiChart from '@/api/chart'
-
+import apiChart from '@/api/chart';
+import {chartLibBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
 import {useCatalogList} from './hooks/useCatalogList';
 
 const { width } = useWindowSize()
@@ -407,7 +408,7 @@ getChartList()
                     style="flex:1;padding-left:0"
                     @click-input="$router.push('/chartETA/search')"
                 />
-                <div class="lang-icon icon" @click="changeLang">
+                <div class="lang-icon icon" @click="changeLang" v-permission="chartLibBtn.chartLib_switchEn">
                     <img v-if="currentLang==='CN'" src="@/assets/imgs/chartETA/lang-icon.png" alt="">
                     <img v-if="currentLang==='EN'" src="@/assets/imgs/chartETA/langEn-icon.png" alt="">
                 </div>
@@ -418,7 +419,7 @@ getChartList()
             <p style="font-weight: bold;word-break: break-all;margin-bottom: 5px;">{{ catalogMenu }}</p>
             <div class="select-box">
                 <span>共{{listState.total}}张图表</span>
-                <span> <van-checkbox v-model="listState.IsShowMe">只看我的</van-checkbox></span>
+                <span v-permission="chartLibBtn.chartLib_isOnlyMine"> <van-checkbox v-model="listState.IsShowMe">只看我的</van-checkbox></span>
             </div>
         </div>
         <div class="chart-list-wrap">
@@ -478,7 +479,7 @@ getChartList()
                         </van-collapse-item>
                     </van-collapse>
                 </div>
-                <div class="bottom sticky-part" v-if="['rai_admin', 'ficc_admin','admin'].includes(UserInfo.RoleTypeCode)">
+                <div class="bottom sticky-part" v-if="['rai_admin', 'ficc_admin','admin'].includes(UserInfo.RoleTypeCode)&&checkAuthBtn(chartLibBtn.chartLib_classifyOpt_add)">
                     <span @click.stop="openEditNameDialog({Level:0},{id:'addNew',label:'添加图表分类'})">添加图表分类</span>
                 </div>
             </div>

+ 1 - 0
src/views/chartETA/Search.vue

@@ -66,6 +66,7 @@ function goDetail(item){
             Calendar:item.Calendar?item.Calendar:'公历',
             SeasonStartDate:item.SeasonStartDate,
             SeasonEndDate:item.SeasonEndDate,
+            StartYear:item.StartYear
         }
     })
 }

+ 12 - 2
src/views/chartETA/hooks/useCatalogList.js

@@ -1,6 +1,9 @@
 import {getStaticImg,useUserInfo} from '@/hooks/common'
 import {ref} from 'vue'
 import apiChart from '@/api/chart'
+import {chartLibBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+
+const {checkAuthBtn} = useAuthBtn()
 export function useCatalogList(){
     //操作栏图标的地址
     const iconSrc = {
@@ -112,13 +115,20 @@ export function useCatalogList(){
         const {authRole} = opt
         const {AdminId,RoleTypeCode} = UserInfo.value
         const roleType = ['rai_admin', 'ficc_admin','admin'].includes(RoleTypeCode)?'admin':RoleTypeCode
+        //在原先的基础上加上权限设置
+        const authMap = {
+            'addNext':chartLibBtn.chartLib_classifyOpt_add,
+            'reName':chartLibBtn.chartLib_classifyOpt_add,
+            'delete':chartLibBtn.chartLib_del,
+            'addChart':chartLibBtn.chartLib_addMy
+        }
         if(opt.id==='moveTo'||opt.id==='moveChart'){
             return node.SysUserId===AdminId||roleType==='admin'
         }
         if(authRole){
-            return authRole===roleType
+            return (authRole===roleType)&&checkAuthBtn(authMap[opt.id])
         }
-        return true
+        return checkAuthBtn(authMap[opt.id])
     }
 
 

+ 27 - 12
src/views/dataEDB/Detail.vue

@@ -14,6 +14,8 @@ import {useCopyEdbData} from '@/hooks/edb/useCopyEdbData'
 import {useEDBDelete} from './hooks/useEDBDelete'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
 import { useWindowSize } from '@vueuse/core'
+import {edbDataBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
 
 const { width } = useWindowSize()
 const cachedViewsStore=useCachedViewsStore()
@@ -178,6 +180,15 @@ async function handleCalculateReset(){
 function handleSave(){
     chartDetailIns?.value.handleSaveChartLimit()
 }
+function isMoreOptShow(){
+    return checkAuthBtn(edbDataBtn.edbData_enNameSetting)
+        || (edbInfo.value.Button.InsertNewDataButton&&checkAuthBtn(edbDataBtn.edbData_newestValue))
+        || (edbInfo.value.EdbType===1&&checkAuthBtn(edbDataBtn.edbData_refreshAll))
+        || (edbInfo.value.EdbType===2&&checkAuthBtn(edbDataBtn.edbData_edbSource))
+        || (edbInfo.value.EdbType===2&&checkAuthBtn(edbDataBtn.edbData_calculateAgain))
+        || (activeType.value==='chart'&&checkAuthBtn(edbDataBtn.edbData_saveEdb))
+        || checkAuthBtn(edbDataBtn.edbData_deleteEdb)
+}
 
 </script>
 
@@ -203,23 +214,23 @@ function handleSave(){
             </van-tab>
         </van-tabs>
         <div class="fix-bottom-box">
-            <div class="item" @click="handleRefreshEDBInfo">
+            <div class="item" @click="handleRefreshEDBInfo" v-permission="edbDataBtn.edbData_update">
                 <img class="icon" src="@/assets/imgs/report/icon_refresh.png" alt="">
                 <span>刷新</span>
             </div>
-            <div class="item" v-if="edbInfo.Button.OpButton" @click="handleEdit">
+            <div class="item" v-if="edbInfo.Button.OpButton&&checkAuthBtn(edbDataBtn.edbData_edit)" @click="handleEdit">
                 <img class="icon" src="@/assets/imgs/dataEDB/icon_edit.png" alt="">
                 <span>编辑</span>
             </div>
-            <div class="item" v-if="activeType==='chart'" @click="handleShowLimit">
+            <div class="item" v-if="activeType==='chart'&&checkAuthBtn(edbDataBtn.edbData_editLimit)" @click="handleShowLimit">
                 <img class="icon" src="@/assets/imgs/myETA/icon_limit.png" alt="">
                 <div>上下限</div>
             </div>
-            <div class="item" v-if="activeType==='data'" @click="handleCopyData">
+            <div class="item" v-if="activeType==='data'&&checkAuthBtn(edbDataBtn.edbData_copyData)" @click="handleCopyData">
                 <img class="icon" src="@/assets/imgs/myETA/icon_copy.png" alt="">
                 <div>复制</div>
             </div>
-            <div class="item" @click="showMoreOpt=true">
+            <div class="item" @click="showMoreOpt=true" v-if="isMoreOptShow()">
                 <img class="icon" src="@/assets/imgs/myETA/icon_menu.png" alt="">
                 <div>更多</div>
             </div>
@@ -244,16 +255,20 @@ function handleSave(){
             <ul class="edb-opt-list">
                 <li class="van-ellipsis item name">{{edbInfo.EdbName}}</li>
 
-                <li class="item" @click="showSetEn=true">设置英文名称</li>
                 <li class="item" 
-                    v-if="edbInfo.Button.InsertNewDataButton"
+                    v-if="checkAuthBtn(edbDataBtn.edbData_enNameSetting)"
+                    @click="showSetEn=true">设置英文名称</li>
+                <li class="item" 
+                    v-if="edbInfo.Button.InsertNewDataButton&&checkAuthBtn(edbDataBtn.edbData_newestValue)"
                     @click="showSetNewData=true"
                 >{{edbInfo.DataInsertConfig.Date?'编辑最新值':'添加最新值'}}</li>
-                <li class="item" v-if="edbInfo.EdbType===1" @click="handleRefreshBaseEDBData">全部刷新</li>
-                <li class="item" v-if="edbInfo.EdbType===2" @click="showEDBHistory=true">指标溯源</li>
-                <li class="item" v-if="edbInfo.EdbType===2" @click="handleCalculateReset">重新计算</li>  
-                <li class="item" v-if="activeType==='chart'" @click="handleSave">保存</li>
-                <li class="item color-red" @click="handleEDBDelete">删除</li>
+                <li class="item" v-if="edbInfo.EdbType===1&&checkAuthBtn(edbDataBtn.edbData_refreshAll)" @click="handleRefreshBaseEDBData">全部刷新</li>
+                <li class="item" v-if="edbInfo.EdbType===2&&checkAuthBtn(edbDataBtn.edbData_edbSource)" @click="showEDBHistory=true">指标溯源</li>
+                <li class="item" v-if="edbInfo.EdbType===2&&checkAuthBtn(edbDataBtn.edbData_calculateAgain)" @click="handleCalculateReset">重新计算</li>  
+                <li class="item" v-if="activeType==='chart'&&checkAuthBtn(edbDataBtn.edbData_saveEdb)" @click="handleSave">保存</li>
+                <li class="item color-red" 
+                    v-if="checkAuthBtn(edbDataBtn.edbData_deleteEdb)"
+                    @click="handleEDBDelete">删除</li>
             </ul>
         </template>
     </van-action-sheet>

+ 50 - 20
src/views/dataEDB/Index.vue

@@ -1,5 +1,5 @@
 <script setup name="DataEDBIndex">
-import {reactive,ref, watch} from 'vue'
+import {computed, reactive,ref, watch} from 'vue'
 import apiDataEDB from '@/api/dataEDB'
 import {apiCommonSetSysConfig,apiGetLanguageConfig} from '@/api/common'
 import EDBClassify from './components/EDBClassify.vue'
@@ -8,6 +8,8 @@ import {useRouter} from 'vue-router'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
 import langIconZH from '@/assets/imgs/chartETA/lang-icon.png'
 import langIconEN from '@/assets/imgs/chartETA/langEn-icon.png'
+import {edbDataBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
 
 const cachedViewsStore=useCachedViewsStore()
 const router=useRouter()
@@ -36,6 +38,29 @@ async function langTypeChange(){
     }
 }
 
+//根据权限决定actions
+const actions = computed(()=>{
+    let actionArr = []
+    if(checkAuthBtn(edbDataBtn.edbData_addEdb)){
+        actionArr.push({
+            name:'添加指标',
+            path:'/dataEDB/addbaseEDB'
+        })
+    }
+    if(checkAuthBtn(edbDataBtn.edbData_calcuEdb)){
+        actionArr.push({
+            name:'计算指标',
+            path:'/dataEDB/calculate/index'
+        })
+    }
+    if(checkAuthBtn(edbDataBtn.edbData_replaceEdb)){
+        actionArr.push({
+                name:'替换指标',
+                path:'/dataEDB/replaceEDB'
+            })
+    }
+    return actionArr
+})
 
 const listState = reactive({
     list:[],
@@ -129,6 +154,23 @@ watch(
         }
     }
 )
+//图表option check
+function checkOption(item){
+    let optionArr = []
+    if(seeComputeEDBInfo(item)&&checkAuthBtn(edbDataBtn.edbData_checkCalcChart)){
+        optionArr.push('查看')
+    }
+    if(item.Button.ShowEdbRelation&&checkAuthBtn(edbDataBtn.edbData_checkRelatedEdb)){
+        optionArr.push('关联指标')
+    }
+    if(item.Button.ShowChartRelation&&checkAuthBtn(edbDataBtn.edbData_checkRelatedChart)){
+        optionArr.push('关联图表')
+    }
+    if(item.Button.MoveButton){
+        optionArr.push('移动至')
+    }
+    return optionArr.length
+}
 function handleShowEdbOpt(item){
     edbOptState.data=item
 
@@ -251,7 +293,7 @@ async function goSearch(){
                     style="flex:1;padding-left:0"
                     @click-input="goSearch"
                 />
-                <div class="lang-icon icon">
+                <div class="lang-icon icon" v-permission="edbDataBtn.edbData_switchEn">
                     <img :src="langType==='zh'?langIconZH:langIconEN" alt="" @click="langTypeChange">
                 </div>
             </div>
@@ -288,6 +330,7 @@ async function goSearch(){
                                 height="28" 
                                 viewBox="0 0 28 28" 
                                 fill="none"
+                                v-if="checkOption(item)"
                                 @click.stop="handleShowEdbOpt(item)"
                             >
                                 <rect width="28" height="28" rx="4" :fill="item.EdbInfoId===edbOptState.data?.EdbInfoId?'#0052D9':'none'"/>
@@ -302,7 +345,7 @@ async function goSearch(){
     </div>
 
     <!-- 悬浮操作按钮 -->
-    <div class="fixed-opt-btn" @click="showActionPop=true">
+    <div class="fixed-opt-btn" @click="showActionPop=true" v-if="actions.length">
         <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
             <path fill-rule="evenodd" clip-rule="evenodd" d="M43.4855 12.7505L25.4062 2.31235C24.536 1.80995 23.4639 1.80995 22.5937 2.31235L4.5144 12.7505V33.6267C4.5144 34.6315 5.05046 35.5599 5.92065 36.0623L24 46.5005L42.0793 36.0623C42.9495 35.5599 43.4855 34.6315 43.4855 33.6267V12.7505ZM23.997 22.2665L9.01233 13.6153L24 4.96245L38.9859 13.6158L23.997 22.2665ZM25.5 24.8665L40.485 16.2149V33.5165L25.5 42.1666V24.8665ZM22.5 24.8665V42.1667L7.51198 33.5165V16.2132L22.5 24.8665Z" fill="white"/>
         </svg>
@@ -314,20 +357,7 @@ async function goSearch(){
         v-model:show="showActionPop"
         cancel-text="取消"
         close-on-click-action
-        :actions="[
-            {
-                name:'添加指标',
-                path:'/dataEDB/addbaseEDB'
-            },
-            {
-                name:'计算指标',
-                path:'/dataEDB/calculate/index'
-            },
-            {
-                name:'替换指标',
-                path:'/dataEDB/replaceEDB'
-            }
-        ]"
+        :actions="actions"
         @select="handleSelectActionOpt"
     >
     </van-action-sheet>
@@ -345,9 +375,9 @@ async function goSearch(){
         <template #default>
             <ul class="edb-opt-list">
                 <!-- 计算指标查看 -->
-                <li class="item" v-if="seeComputeEDBInfo(edbOptState.data)" @click="handleEDBOpt('see',edbOptState.data)">查看</li>
-                <li class="item" v-if="edbOptState.data?.Button.ShowEdbRelation" @click="handleEDBOpt('relationEDB',edbOptState.data)">关联指标</li>
-                <li class="item" v-if="edbOptState.data?.Button.ShowChartRelation" @click="handleEDBOpt('relationChart',edbOptState.data)">关联图表</li>
+                <li class="item" v-if="seeComputeEDBInfo(edbOptState.data)&&checkAuthBtn(edbDataBtn.edbData_checkCalcChart)" @click="handleEDBOpt('see',edbOptState.data)">查看</li>
+                <li class="item" v-if="edbOptState.data?.Button.ShowEdbRelation&&checkAuthBtn(edbDataBtn.edbData_checkRelatedEdb)" @click="handleEDBOpt('relationEDB',edbOptState.data)">关联指标</li>
+                <li class="item" v-if="edbOptState.data?.Button.ShowChartRelation&&checkAuthBtn(edbDataBtn.edbData_checkRelatedChart)" @click="handleEDBOpt('relationChart',edbOptState.data)">关联图表</li>
                 <li class="item" v-if="edbOptState.data?.Button.MoveButton" @click="handleEDBOpt('move',edbOptState.data)">移动至</li>
             </ul>
         </template>

+ 16 - 13
src/views/dataEDB/components/EDBChartDetail.vue

@@ -7,6 +7,7 @@ import { useWindowSize } from '@vueuse/core'
 import {chartDefaultOpts,seasonOptions} from '@/hooks/chart/config'
 import lodash from 'lodash'
 import Highcharts from 'highcharts/highstock';
+import HighchartsFormat from 'highcharts';
 import Boost from 'highcharts/modules/boost'
 import HighchartszhCN  from '@/hooks/chart/highcahrts-zh_CN.js'
 HighchartszhCN(Highcharts)
@@ -15,6 +16,9 @@ Boost(Highcharts)
 import { useRoute } from 'vue-router'
 import { showToast } from 'vant'
 
+import {edbDataBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
+
 const { width } = useWindowSize()
 const route=useRoute()
 
@@ -168,7 +172,7 @@ function setLineChart(){
             ...chartDefaultOpts.xAxis,
             labels: {
               formatter: function (ctx) {
-                return Highcharts.dateFormat('%m/%d', ctx.value);
+                return HighchartsFormat.dateFormat('%m/%d', ctx.value);
               },
             },
         }
@@ -177,7 +181,7 @@ function setLineChart(){
             ...chartDefaultOpts.xAxis,
 			labels: {
                 formatter: function (ctx) {
-                    return Highcharts.dateFormat('%y/%m', ctx.value);
+                    return HighchartsFormat.dateFormat('%y/%m', ctx.value);
                 },
             },
         }
@@ -323,7 +327,7 @@ function setSeasonChart(){
     let xAxis={
         labels:{
             formatter: function () {
-                return Highcharts.dateFormat('%m/%d', this.value);
+                return HighchartsFormat.dateFormat('%m/%d', this.value);
             },
         }
     }
@@ -466,17 +470,16 @@ onMounted(() => {
 
 
 // 图表类型
-const chartTypeOpts=[
-    {
-        name:'曲线图'
-    },
-    {
-        name:'同比图'
-    },
-    {
-        name:'季节性图'
+const chartTypeOpts = computed(()=>{
+    let optionArr=[{name:'曲线图'}]
+    if(checkAuthBtn(edbDataBtn.edbData_showChartBasis)){
+        optionArr.push({name:'同比图'})
     }
-]
+    if(checkAuthBtn(edbDataBtn.edbData_switchSeason)){
+        optionArr.push({name:'季节性图'})
+    }
+    return optionArr
+})
 let beforeChartType='曲线图'
 const chartType=ref('曲线图')
 const showChartType=ref(false)

+ 31 - 7
src/views/dataEDB/components/EDBClassify.vue

@@ -5,6 +5,8 @@ import apiDataEDB from '@/api/dataEDB'
 import { showDialog , showToast } from 'vant';
 import {useEDBDelete} from '../hooks/useEDBDelete'
 import {deleteClassifyItemEmpty} from '../util/util'
+import {edbDataBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
 
 const {edbClassifyDelete}=useEDBDelete()
 const { width } = useWindowSize()
@@ -108,7 +110,7 @@ function getClassifyItemName(item){
     return langType.value==='zh'?item.ClassifyName:item.ClassifyNameEn||item.ClassifyName
 }
 
-// 获取分类数据
+// 获取分类数据classifyState
 let classifyList=ref([])
 async function getEDBClassifyList(){
     const res=await apiDataEDB.edbClassifyList()
@@ -185,6 +187,23 @@ watch(
         }
     }
 )
+//判断每一项的optArr并返回长度
+function checkOpt(item){
+    let optArr = []
+    if(item.Button.AddButton&&checkAuthBtn(edbDataBtn.edbData_classifyOpt_add)&&item.Level<6){
+        optArr.push({name:'添加下级目录',type:'add'})
+    }
+    if(checkAuthBtn(edbDataBtn.edbData_classifyOpt_add)){
+        optArr.push({name:'重命名',type:'edit'})
+    }
+    if(item.Button.DeleteButton&&checkAuthBtn(edbDataBtn.edbData_classifyOpt_delete)){
+        optArr.push({name:'删除',type:'delete',color:"#C54322"})
+    }
+    if(item.Level!==1&&item.Button.MoveButton&&checkAuthBtn(edbDataBtn.edbData_classifyOpt_move)){
+        optArr.push({name:'移动至',type:'move'})
+    }
+    return optArr.length
+}
 // 显示分类的具体操作选项
 function handleShowClassifyOpt(item){
     let optArr=[
@@ -192,12 +211,15 @@ function handleShowClassifyOpt(item){
         {name:'删除',type:'delete',color:"#C54322"},
     ]
     if(item.Button.AddButton&&showParentClassifyData.value.length<5){//添加权限
-        optArr.unshift({name:'添加下级目录',type:'add'})
+        checkAuthBtn(edbDataBtn.edbData_classifyOpt_add)&&optArr.unshift({name:'添加下级目录',type:'add'})
+    }
+    if(!checkAuthBtn(edbDataBtn.edbData_classifyOpt_add)){//重命名
+        optArr=optArr.filter(item=>item.type!='edit')
     }
-    if(!item.Button.DeleteButton){//删除
+    if(!item.Button.DeleteButton||!checkAuthBtn(edbDataBtn.edbData_classifyOpt_delete)){//删除
         optArr=optArr.filter(item=>item.type!='delete')
     }
-    if(item.Button.MoveButton){//移动权限
+    if(item.Button.MoveButton&&checkAuthBtn(edbDataBtn.edbData_classifyOpt_move)){//移动权限
         // 一级目录不能移动至
         if(selectClassifyArr.value.length){
             optArr.push({name:'移动至',type:'move'})
@@ -205,7 +227,7 @@ function handleShowClassifyOpt(item){
     }
     classifyOptState.actions=optArr
     classifyOptState.data=item
-    classifyOptState.show=true
+    optArr.length&&(classifyOptState.show=true)
 }
 function handleSelectOpt(e){
     if(e.type==='add'){
@@ -317,10 +339,12 @@ defineExpose({classifyList,langTypeChange})
                         :style="{color:selectClassifyArr[selectClassifyArr.length-1]?.ClassifyId===item.ClassifyId?'#0052D9':''}"
                         @click.stop="handleSelectClassify(item)"
                     >{{getClassifyItemName(item)}}</span>
-                    <img style="width:22px;height:22px;cursor: pointer;" src="@/assets/imgs/dataEDB/icon_opt.png" alt="" @click.stop="handleShowClassifyOpt(item)">
+                    <img style="width:22px;height:22px;cursor: pointer;" src="@/assets/imgs/dataEDB/icon_opt.png" alt="" 
+                        v-if="checkOpt(item)"
+                        @click.stop="handleShowClassifyOpt(item)">
                 </li>
             </ul>
-            <div class="bot-btn-box">
+            <div class="bot-btn-box" v-permission="edbDataBtn.edbData_classifyOpt_add">
                 <van-button type="primary" block @click="handleAddLevel1">添加一级目录</van-button>
             </div>
         </div>

+ 152 - 16
src/views/myETA/ChartDetail.vue

@@ -1,10 +1,11 @@
 <script setup>
-import {ref,nextTick, reactive} from 'vue'
+import {ref,nextTick, reactive,computed} from 'vue'
 import apiETAChart from '@/api/chart'
 import apiFutureChart from '@/api/futureChart'
 import apiCorrelationChart from '@/api/correlationChart'
 import apiLineEquationChart from '@/api/lineEquationChart'
 import apiStatisticFeatureChart from '@/api/statisticFeatureChart'
+import apiCrossVarietyChart from '@/api/crossVarietyChart'
 import apiMyETAChart from '@/api/myETA'
 import apiDataEDB from '@/api/dataEDB'
 import { useRoute, useRouter } from 'vue-router'
@@ -21,7 +22,22 @@ import SetChartEnName from '@/components/SetChartEnName.vue'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
 import AddChartToMyETA from '@/views/chartETA/components/AddChartToMyETA.vue'
 import { setExtremumDate } from '@/hooks/chart/commonFun.js'
-
+import {myETABtn,useAuthBtn} from '@/hooks/useAuthBtn'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
+const {checkAuthBtn} = useAuthBtn()
+
+
+const publicSettingStore = usePublicSettingStore()
+const isMoreActionShow = computed(()=>{
+    return checkAuthBtn(myETABtn.myChart_refresh)
+        || checkAuthBtn(myETABtn.myChart_share)
+        || checkAuthBtn(myETABtn.myChart_move)
+        || checkAuthBtn(myETABtn.myChart_copyTo)
+        || checkAuthBtn(myETABtn.myChart_otherSave)
+        || (checkAuthBtn(myETABtn.myChart_copyWechat)&&checkAuthBtn(myETABtn.myChart_copyOffice))
+        || checkAuthBtn(myETABtn.myChart_enNameSetting)
+        || checkAuthBtn(myETABtn.myChart_del)
+}) 
 const { width, height } = useWindowSize()
 const cachedViewsStore=useCachedViewsStore()
 const {options,axisLimitState,chartRender}=useChartRender()
@@ -512,6 +528,8 @@ async function handleChartRefresh(){
         res=await apiLineEquationChart.refreshChart({ ChartInfoId })
     }else if([7,8,9].includes(Source)) {
         res=await apiStatisticFeatureChart.refreshChart({ ChartInfoId })
+    }else if(Source===10) {
+        res=await apiCrossVarietyChart.refreshChart({ ChartInfoId })
     }
     if(res.Ret!==200)return
     showToast('刷新成功')
@@ -521,9 +539,124 @@ async function handleChartRefresh(){
 
 //分享
 function handleChartShare(){
-    const url=import.meta.env.VITE_CHART_LINK+`?code=${chartInfo.value.UniqueCode}&fromType=share&lang=ch`
+    // const url=import.meta.env.VITE_CHART_LINK+`?code=${chartInfo.value.UniqueCode}&fromType=share&lang=ch`
+    const url=publicSettingStore.publicSetting.ChartViewUrl+`/chartshow?code=${chartInfo.value.UniqueCode}&fromType=share&lang=ch`
     setClipboardData(url)
 }
+//保存图表:目前myETA里能保存的只有ETA图库的图和商品价格曲线图
+function handleChartSave(){
+    const sourceMap = {
+        1: saveChartHandle,
+        2: saveCommodityChart,//商品价格曲线
+        5: saveCommodityChart,//利润曲线
+      }
+      sourceMap[chartInfo.value.Source]&&sourceMap[chartInfo.value.Source]();
+}
+function saveChartHandle(){
+    //获取每条线的指标配置
+    let arr = edbList.value.map((item)=>{
+        return {
+            ChartColor: item.ChartColor,
+            PredictChartColor: item.PredictChartColor,
+            ChartStyle: item.ChartStyle,
+            ChartWidth: Number(item.ChartWidth),
+            EdbInfoId: item.EdbInfoId,
+            EdbInfoType: item.EdbInfoType,
+            IsAxis: item.IsAxis,
+            IsOrder: item.IsOrder,
+            LeadUnit: item.EdbInfoType ? '' : item.LeadUnit,
+            LeadValue: item.EdbInfoType ? 0 : Number(item.LeadValue),
+            MaxData: Number(item.MaxData),
+            MinData: Number(item.MinData),
+        }
+    })
+    //所有图表的公共参数
+    let public_param = {
+        ChartClassifyId: chartInfo.value.ChartClassifyId,
+        ChartInfoId: chartInfo.value.ChartInfoId,
+        ChartEdbInfoList: arr,
+    }
+    //根据ChartType决定剩余参数
+    let type_param = {}
+    switch(chartInfo.value.ChartType){
+        case 2:
+            type_param = {
+              DateType: chartState.yearVal,
+              StartYear:chartState.startYear || 0,
+              Calendar: chartState.calendarType,
+              StartDate: chartState.startTime||'',
+              EndDate: chartState.endTime||'',
+            }
+            break
+        case 7:
+            type_param = {
+              DateType: 6,
+              LeftMin: String(axisLimitState.leftMin),
+              LeftMax: String(axisLimitState.leftMax),
+            }
+            break
+        case 10:
+            type_param = {
+              DateType: 6,
+              Calendar: "公历",
+              ExtraConfig: JSON.stringify({
+                ...JSON.parse(chartInfo.value.ExtraConfig),
+                XMinValue: String(axisLimitState.xMin),
+                XMaxValue: String(axisLimitState.xMax),
+                YMinValue: String(axisLimitState.leftMin),
+                YMaxValue: String(axisLimitState.leftMax),
+              })
+            }
+            break
+    }
+    if(sameOptionType.includes(chartInfo.value.ChartType)){
+        type_param = {
+            DateType: chartState.yearVal,
+            StartYear:chartState.startYear || 0,
+            StartDate:chartState.yearVal === 5 || chartState.yearVal === 6
+                ? chartState.startTime
+                : '',
+            EndDate: chartState.yearVal === 5 ? chartState.endTime : '',
+        }
+    }
+    let params = {...public_param,...type_param}
+    //保存
+    apiChart.chartSave(params).then(async res=>{
+        if(res.Ret!==200) return
+        showToast("保存成功")
+        setChartImage()
+    })
+}
+function saveCommodityChart(){
+    apiMyETAChart.myETASaveChart({
+        ChartInfoId:chartInfo.value.ChartInfoId,
+        LeftMin:String(axisLimitState.leftMin),
+        LeftMax:String(axisLimitState.leftMax),
+    }).then(res=>{
+        if(res.Ret!==200) return 
+        showToast("保存成功")
+        setChartImage()
+    })
+}
+
+//更新缩略图
+async function setChartImage(){
+    //打开保存图片弹窗
+    const svgData = CHARTINS.getSVG({
+        chart: {
+            width: 340,
+            height: 230,
+        }
+    })
+    let form = new FormData();
+    form.append('Img', svgData);
+    let {Data,Ret} = await apiETAChart.uploadChartImg(form)
+    if(Ret!==200||!Data) return
+    await apiETAChart.setChartImg({
+        ChartInfoId:Number(chartInfo.value.ChartInfoId),
+        ImageUrl:Data.ResourceUrl
+    })
+}
 
 //保存图片
 let savePicDialogShow = ref(false)
@@ -556,7 +689,7 @@ function handleShowEditEnName(){
     showEidtEnName.value=true
 }
 function handleEditEnNameSuccess(){
-    reloadChartInfo()
+    chartInfo.value.Source===1?reloadChartInfo():getChartInfo()
 }
 
 // 加入我的图库
@@ -597,11 +730,11 @@ const isShowAddToMyETADialog=ref(false)
                 >{{chartState.startTime?chartState.startTime+'~'+(chartState.endTime?chartState.endTime:'至今'):'请选择时间段'}}</span>
             </div>
             <div class="right-action-box">
-                <div class="item" @click="handleShowAxisLimitOpt" v-if="![3,4,6,7,8,9].includes(chartInfo.Source)">
+                <div class="item" @click="handleShowAxisLimitOpt" v-if="[1,2,5].includes(chartInfo.Source)&&checkAuthBtn(myETABtn.myChart_editLimit)">
                     <img src="@/assets/imgs/myETA/icon_limit2.png" alt="">
                     <span>上下限设置</span>
                 </div>
-                <div class="item" @click="showMoreAction=true">
+                <div class="item" @click="showMoreAction=true" v-if="isMoreActionShow||$route.query.from==='edbRelationChart'">
                     <img src="@/assets/imgs/chartETA/more-icon.png" alt="">
                     <span>更多设置</span>
                 </div>
@@ -659,11 +792,11 @@ const isShowAddToMyETADialog=ref(false)
                 <img class="icon" style="transform: rotate(180deg);" src="@/assets/imgs/icon_arrow.png" alt="">
                 <div>下一张</div>
             </div>
-            <div class="item" @click="handleShowAxisLimitOpt" v-if="![3,4,6,7,8,9].includes(chartInfo.Source)">
+            <div class="item" @click="handleShowAxisLimitOpt" v-if="[1,2,5].includes(chartInfo.Source)&&checkAuthBtn(myETABtn.myChart_editLimit)">
                 <img class="icon" src="@/assets/imgs/myETA/icon_limit.png" alt="">
                 <div>上下限</div>
             </div>
-            <div class="item" @click="showMoreAction=true">
+            <div class="item" @click="showMoreAction=true" v-if="isMoreActionShow||$route.query.from==='edbRelationChart'">
                 <img class="icon" src="@/assets/imgs/myETA/icon_menu.png" alt="">
                 <div>更多</div>
             </div>
@@ -815,31 +948,34 @@ const isShowAddToMyETADialog=ref(false)
     >
         <div class="more-action-wrap">
             <div class="van-ellipsis title-box">{{chartInfo.ChartName}}</div>
-            <div class="item" @click.stop="handleChartRefresh">
+            <div class="item" @click.stop="handleChartRefresh" v-permission="myETABtn.myChart_refresh">
                 刷新
             </div>
-            <div class="item" @click.stop="handleChartShare" v-if="!chartInfo.Disabled">
+            <div class="item" @click.stop="handleChartShare" v-if="!chartInfo.Disabled&&checkAuthBtn(myETABtn.myChart_share)">
                 分享
             </div>
-            <div class="item" @click.stop="handleRemoveChart" v-if="$route.query.iscommon!='true'">
+            <div class="item" @click.stop="handleChartSave" v-if="[1,2,5].includes(chartInfo.Source)&&checkAuthBtn(myETABtn.myChart_save)">
+                保存
+            </div>
+            <div class="item" @click.stop="handleRemoveChart" v-if="$route.query.iscommon!='true'&&checkAuthBtn(myETABtn.myChart_move)">
                 移出
             </div>
-            <div class="item" @click.stop="handleShowCopyTo" v-if="$route.query.iscommon!='true'">
+            <div class="item" @click.stop="handleShowCopyTo" v-if="$route.query.iscommon!='true'&&checkAuthBtn(myETABtn.myChart_copyTo)">
                 复制到
             </div>
             <div class="item" @click.stop="isShowAddToMyETADialog=true" v-if="$route.query.from==='edbRelationChart'">
                 加入我的图库
             </div>
-            <div class="item" @click.stop="showSaveChartOther=true" v-if="chartInfo.Button.IsCopy">
+            <div class="item" @click.stop="showSaveChartOther=true" v-if="chartInfo.Button.IsCopy&&checkAuthBtn(myETABtn.myChart_otherSave)">
                 另存为
             </div>
-            <div class="item" @click.stop="handleChartSavePicture" v-if="!chartInfo.Disabled">
+            <div class="item" @click.stop="handleChartSavePicture" v-if="!chartInfo.Disabled&&checkAuthBtn(myETABtn.myChart_copyWechat)&&checkAuthBtn(myETABtn.myChart_copyOffice)">
                 保存图片
             </div>
-            <div class="item" @click.stop="handleShowEditEnName">
+            <div class="item" @click.stop="handleShowEditEnName" v-permission="myETABtn.myChart_enNameSetting">
                 设置英文名称
             </div>
-            <div class="item" @click.stop="handleDeleteChart" v-if="chartInfo.IsEdit">
+            <div class="item" @click.stop="handleDeleteChart" v-if="chartInfo.IsEdit&&checkAuthBtn(myETABtn.myChart_del)">
                 删除
             </div>
         </div>

+ 22 - 8
src/views/myETA/Index.vue

@@ -7,6 +7,8 @@ import { showConfirmDialog, showToast } from 'vant';
 import { useWindowSize } from '@vueuse/core'
 import draggable from 'vuedraggable'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
+import {myETABtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
 
 const cachedViewsStore=useCachedViewsStore()
 const { width, height } = useWindowSize()
@@ -32,6 +34,18 @@ const {
 const curChartClassifyList=computed(()=>{
     return classifyState.classifyTypeAct==1?classifyState.myClassifyList:classifyState.pubClassifyList
 })
+//我的图库/公共图库opt:重命名、可见、复制、删除
+const isOptShow = computed(()=>{
+    if(classifyState.classifyTypeAct===1){
+        return checkAuthBtn(myETABtn.myChart_classifyOpt_rename)
+            || checkAuthBtn(myETABtn.myChart_classifyOpt_show)
+            || checkAuthBtn(myETABtn.myChart_classifyOpt_copy)
+            || checkAuthBtn(myETABtn.myChart_classifyOpt_delete)
+    }
+    if(classifyState.classifyTypeAct===2){
+        return checkAuthBtn(myETABtn.myChart_classifyOpt_copy)
+    }
+})
 // 拖动排序结束
 function onClassifySortEnd(e){
     console.log(e);
@@ -172,7 +186,7 @@ async function goSearch(){
                     placeholder="请输入图表名称"
                     @click="goSearch"
                 />
-                <img @click="goChooseChart" class="icon" src="@/assets/imgs/myETA/icon_select.png" alt="icon">
+                <img @click="goChooseChart" class="icon" src="@/assets/imgs/myETA/icon_select.png" alt="icon" v-permission="myETABtn.myChart_selectChart">
                 <img @click="showClassifyPop=true" class="icon" src="@/assets/imgs/myETA/icon_menu2.png" alt="icon">
             </div>
             <p style="margin-top:10px;font-weight:bold">{{listState.ctype}}{{listState.cname?'/'+listState.cname:''}}</p>
@@ -197,7 +211,7 @@ async function goSearch(){
                     <img class="img" :src="item.ChartImage" alt="">   
                     <div class="time">
                         <span>{{item.CreateTime.slice(0,10)}}</span>
-                        <img v-if="listState.ctype==='我的图库'" class="remove-box" @click.stop="handleRemoveChart(item,index)" src="@/assets/imgs/myETA/icon_remove2.png" alt="">
+                        <img v-if="listState.ctype==='我的图库'&&checkAuthBtn(myETABtn.myChart_move)" class="remove-box" @click.stop="handleRemoveChart(item,index)" src="@/assets/imgs/myETA/icon_remove2.png" alt="">
                     </div>
                 </li>
             </ul>
@@ -240,7 +254,7 @@ async function goSearch(){
                                 <path d="M40.4087 23.8334L33.7785 18.3083V22.7283H24.9384V13.8882H29.3585L23.8334 7.25806L18.3083 13.8882H22.7283V22.7283H13.8882V18.3083L7.25806 23.8334L13.8882 29.3585V24.9384H22.7283V33.7785H18.3083L23.8334 40.4087L29.3585 33.7785H24.9384V24.9384H33.7785V29.3585L40.4087 23.8334Z" fill="#666666"/>
                             </svg>
                         </div>
-                        <div class="icon-menu-box" @click.stop="handleShowOpt(element)">
+                        <div class="icon-menu-box" @click.stop="handleShowOpt(element)" v-if="isOptShow">
                             <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
                                 <path d="M24 33C25.65 33 27 34.35 27 36C27 37.65 25.65 39 24 39C22.35 39 21 37.65 21 36C21 34.35 22.35 33 24 33ZM21 24C21 25.65 22.35 27 24 27C25.65 27 27 25.65 27 24C27 22.35 25.65 21 24 21C22.35 21 21 22.35 21 24ZM21 12C21 13.65 22.35 15 24 15C25.65 15 27 13.65 27 12C27 10.35 25.65 9 24 9C22.35 9 21 10.35 21 12Z" fill="#666666"/>
                             </svg>
@@ -248,7 +262,7 @@ async function goSearch(){
                     </li>
                 </template>
             </draggable>
-            <div class="bot-btns">
+            <div class="bot-btns" v-permission="myETABtn.myChart_classifyOpt_edit">
                 <van-button type="primary" block @click="handleAddClassify('')">添加我的分类</van-button>
             </div>
         </div>
@@ -281,11 +295,11 @@ async function goSearch(){
             </div> -->
             <!-- 我的图库分类操作 -->
             <template v-if="classifyState.classifyTypeAct==1">
-            <div class="item border" @click="handleAddClassify(classifyState.activeClassifyVal)">
+            <div class="item border" @click="handleAddClassify(classifyState.activeClassifyVal)" v-permission="myETABtn.myChart_classifyOpt_edit">
                 <!-- <img src="@/assets/imgs/ppt/ppt_icon_write.png" alt=""> -->
                 <span>重命名</span>
             </div>
-            <div class="item border">
+            <div class="item border" v-permission="myETABtn.myChart_classifyOpt_show">
                 <!-- <img src="@/assets/imgs/icon_user.png" alt=""> -->
                 <span style="display:inline-block;margin-right:10px">所有人可见</span>
                 <van-switch
@@ -295,14 +309,14 @@ async function goSearch(){
                     @update:model-value="handleClassifyShare"
                 />
             </div>
-            <div class="item border red" @click="handleClassifyDel(classifyState.activeClassifyVal)">
+            <div class="item border red" @click="handleClassifyDel(classifyState.activeClassifyVal)" v-permission="myETABtn.myChart_classifyOpt_delete">
                 <!-- <img src="@/assets/imgs/icon_del.png" alt=""> -->
                 <span>删除</span>
             </div>
             </template>
             <!-- 公共图库分类操作 -->
             <template v-if="classifyState.classifyTypeAct==2">
-            <div class="item border" @click="handleCopyClassify(classifyState.activeClassifyVal)">
+            <div class="item border" @click="handleCopyClassify(classifyState.activeClassifyVal)" v-permission="myETABtn.myChart_classifyOpt_copy">
                 <!-- <img src="@/assets/imgs/ppt/ppt_icon_copy.png" alt=""> -->
                 <span>复制</span>
             </div>

+ 9 - 1
src/views/myETA/components/ChartSaveOther.vue

@@ -5,6 +5,7 @@ import apiCorrelation from '@/api/correlationChart'
 import apiFuture from '@/api/futureChart'
 import apiLineEquationChart from '@/api/lineEquationChart'
 import apiStatisticFeatureChart from '@/api/statisticFeatureChart'
+import apiCrossVarietyChart from '@/api/crossVarietyChart'
 import { showToast } from 'vant'
 import { useWindowSize } from '@vueuse/core'
 
@@ -92,7 +93,7 @@ function getTreeData(topNode={}){
     })
 }
 
-let activeIds=ref('')//选中的分类
+let activeIds=ref(0)//选中的分类
 const activeIndex=ref(0)
 let activeTab=ref(0)
 
@@ -112,6 +113,8 @@ async function handleConfirmSave(){
         res=await apiLineEquationChart.chartSaveOther(params)
     }else if([7,8,9].includes(props.source)){
         res=await apiStatisticFeatureChart.chartSaveOther(params)
+    }else if(props.source===10){
+        res=await apiCrossVarietyChart.saveOtherChart(params)
     }else{
         res=await apiETAChart.ETAChartSaveOther(params)
     }
@@ -145,6 +148,11 @@ async function handleConfirmSave(){
                         >{{item.ChartClassifyName}}</li>
                     </ul>
                 </template>
+                <template v-else-if="source===10">
+                    <div class="item">
+                        图表名称: {{chartInfo.ChartName+'(1)'}}
+                    </div>
+                </template>
                 <template v-else>
                     <!-- <van-tree-select
                         v-model:active-id="activeIds"

+ 5 - 4
src/views/myETA/components/EDBInfo.vue

@@ -6,7 +6,8 @@ import _ from 'lodash'
 // import apiChart from '@/api/chart'
 import SourceDetail from '@/views/chartETA/components/SourceDetail.vue'
 import {useCopyEdbData} from '@/hooks/edb/useCopyEdbData'
-
+import {myETABtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
 const {copyData} =useCopyEdbData()
 
 const leadUnitOpt=[{text:'年'}, {text:'季'}, {text:'月'}, {text:'周'}, {text:'天'}]//领先指标频度配置
@@ -238,9 +239,9 @@ function handleCopyEDBData(){
                         </div>
                     </div>
                     </template>
-                    <div class="item-box">
-                        <van-button color="#F2F3FF" size="small" style="color:#0052D9;margin-right:10px" @click="handleCopyEDBData">复制数据</van-button>
-                        <van-button color="#0052D9" size="small">查看数据</van-button>
+                    <div class="item-box" v-if="checkAuthBtn(myETABtn.myChart_checkData)||checkAuthBtn(myETABtn.myChart_copyData)">
+                        <van-button color="#F2F3FF" size="small" style="color:#0052D9;margin-right:10px" @click="handleCopyEDBData" v-permission="myETABtn.myChart_copyData">复制数据</van-button>
+                        <van-button color="#0052D9" size="small" v-permission="myETABtn.myChart_checkData">查看数据</van-button>
                     </div>
                 </div>
             </div>

+ 20 - 14
src/views/ppt/Detail.vue

@@ -3,7 +3,7 @@ import { ref,nextTick} from 'vue'
 import { useRoute, useRouter } from "vue-router";
 import { vElementSize } from '@vueuse/components'
 import {apiPPTDetail} from '@/api/ppt'
-import {createPPTContent,getTemplate} from './hooks/createPPTContent'
+import {createPPTContent,getTemplate,getPPTConfig} from './hooks/createPPTContent'
 import {usePPTPublish} from './hooks/usePPTPublish'
 import {useClassify} from './hooks/useClassify'
 import {useUserInfo} from '@/hooks/common'
@@ -11,6 +11,7 @@ import {useDownLoadFile} from '@/hooks/useDownLoadFile'
 import { useWindowScroll } from '@vueuse/core'
 import moment from 'moment';
 import { showToast } from 'vant';
+import { useConfigSettingStore } from '@/store/modules/etaConfig'
 
 const route=useRoute()
 const router=useRouter()
@@ -24,12 +25,14 @@ const { y } = useWindowScroll()
 let PPTInfo=ref(null)
 let conArr=ref([])
 async function getPPTDetail(){
+    await useConfigSettingStore().getBaseConfigSetting()
     const res=await apiPPTDetail({PptId:Number(pptId)})
     if(res.Ret!=200) return
     PPTInfo.value=res.Data
     conArr.value=createPPTContent(res.Data)
     nextTick(()=>{
         onResize({width:document.getElementsByClassName('ppt-content-wrap')[0].clientWidth})
+        $('.ppt-item-page').css('background-image',`url(${getPPTConfig().pptBgImage})`);
     })
     document.title=res.Data.Title
     handleShowPPTOpt(true)
@@ -60,7 +63,10 @@ const {
     handleShowPPTCopy,
     handlePPTDel,
     handlePPTCopy,
-    handlePPTCopyBeforeClose
+    handlePPTCopyBeforeClose,
+
+    permissionBtn,
+    checkAuthBtn
 
 }=useClassify()
 
@@ -142,27 +148,27 @@ const showPopover=ref(false)
     <div class="ppt-detail-page" :style="{height:pptContentHeight+'px'}">
         <!-- pad端顶部操作栏 -->
         <div class="pad-top-action-wrap">
-            <div class="item-box" @click="handllePublishPPT">
+            <div class="item-box" @click="handllePublishPPT" v-permission="permissionBtn.ppt_publish">
                 <img src="@/assets/imgs/ppt/icon_action_publish.png" alt="">
                 <span>发布</span>
             </div>
-            <div class="item-box" @click="showPlayOpt=true">
+            <div class="item-box" @click="showPlayOpt=true" v-permission="permissionBtn.ppt_show">
                 <img src="@/assets/imgs/ppt/icon_action_play.png" alt="">
                 <span>播放</span>
             </div>
-            <div class="item-box" @click="handleShowPPTCopy({pptid:$route.query.id})">
+            <div class="item-box" @click="handleShowPPTCopy({pptid:$route.query.id})" v-permission="permissionBtn.ppt_copy">
                 <img src="@/assets/imgs/ppt/icon_action_copy.png" alt="">
                 <span>复制</span>
             </div>
-            <div class="item-box" @click="handleDownLoadFile">
+            <div class="item-box" @click="handleDownLoadFile" v-permission="permissionBtn.ppt_download">
                 <img src="@/assets/imgs/ppt/icon_action_download2.png" alt="">
                 <span>下载</span>
             </div>
-            <div class="item-box red" @click="handlePPTDel({back:true})" v-if="!PPTOptState.isCommon">
+            <div class="item-box red" @click="handlePPTDel({back:true})" v-if="!PPTOptState.isCommon&&checkAuthBtn(permissionBtn.ppt_del)">
                 <img src="@/assets/imgs/icon_del.png" alt="">
                 <span>删除</span>
             </div>
-            <div class="item-box" style="flex-direction:row">
+            <div class="item-box" style="flex-direction:row" v-if="!PPTOptState.isCommon&&checkAuthBtn(permissionBtn.ppt_visible)">
                 <span style="margin-right:4px">公开</span>
                 <van-switch 
                     size="22px" 
@@ -180,15 +186,15 @@ const showPopover=ref(false)
             </template>
         </div>
         <div class="mobile-fix-bot-warp">
-            <div class="item-box" @click="handllePublishPPT">
+            <div class="item-box" @click="handllePublishPPT" v-permission="permissionBtn.ppt_publish">
                 <img src="@/assets/imgs/ppt/icon_action_publish.png" alt="">
                 <span>发布</span>
             </div>
-            <div class="item-box" @click="showPlayOpt=true">
+            <div class="item-box" @click="showPlayOpt=true" v-permission="permissionBtn.ppt_show">
                 <img src="@/assets/imgs/ppt/icon_action_play.png" alt="">
                 <span>播放</span>
             </div>
-            <div class="item-box" @click="handleShowPPTCopy({pptid:$route.query.id})">
+            <div class="item-box" @click="handleShowPPTCopy({pptid:$route.query.id})" v-permission="permissionBtn.ppt_copy">
                 <img src="@/assets/imgs/ppt/icon_action_copy.png" alt="">
                 <span>复制</span>
             </div>
@@ -217,7 +223,7 @@ const showPopover=ref(false)
                         </div>
                     </div>
                 </div>
-                <div class="flex" v-if="!PPTOptState.isCommon">
+                <div class="flex" v-if="!PPTOptState.isCommon&&checkAuthBtn(permissionBtn.ppt_visible)">
                     <span style="margin-right:4px">公开</span>
                     <van-switch 
                         size="22px" 
@@ -227,11 +233,11 @@ const showPopover=ref(false)
                 </div>
                 
             </div>
-            <div class="item border blue" @click="handleDownLoadFile">
+            <div class="item border blue" @click="handleDownLoadFile" v-permission="permissionBtn.ppt_download">
                 <img src="@/assets/imgs/ppt/icon_action_download.png" alt="">
                 <span>下载</span>
             </div>
-            <div class="item border red" @click="handlePPTDel({back:true})" v-if="!PPTOptState.isCommon">
+            <div class="item border red" @click="handlePPTDel({back:true})" v-if="!PPTOptState.isCommon&&checkAuthBtn(permissionBtn.ppt_del)">
                 <img src="@/assets/imgs/icon_del.png" alt="">
                 <span>删除</span>
             </div>

+ 3 - 0
src/views/ppt/Index.vue

@@ -3,6 +3,9 @@ import {ref,reactive} from 'vue'
 import MobileClassifyWrap from './components/MobileClassifyWrap.vue'
 import PadPPTIndexWrap from './components/PadPPTIndexWrap.vue'
 
+import { useConfigSettingStore } from '@/store/modules/etaConfig'
+useConfigSettingStore().getBaseConfigSetting()
+
 
 </script>
 

+ 2 - 1
src/views/ppt/Preview.vue

@@ -2,7 +2,7 @@
 import { ref,nextTick, computed} from 'vue'
 import { useRoute, useRouter } from "vue-router";
 import {apiPPTDetail} from '@/api/ppt'
-import {createPPTContent,getTemplate} from './hooks/createPPTContent'
+import {createPPTContent,getTemplate,getPPTConfig} from './hooks/createPPTContent'
 import { useWindowSize } from '@vueuse/core'
 import { Swipe, SwipeItem,showToast } from 'vant';
 
@@ -21,6 +21,7 @@ async function getPPTDetail(){
     conArr.value=createPPTContent(res.Data)
     nextTick(()=>{
         showToast('左右滑动可翻页')
+        $('.ppt-item-page').css('background-image',`url(${getPPTConfig().pptBgImage})`);
     })
     document.title=res.Data.Title
 }

+ 10 - 7
src/views/ppt/components/MobileClassifyWrap.vue

@@ -24,7 +24,10 @@ const {
     handlePPTCopy,
     handlePPTCopyBeforeClose,
 
-    goPPTDetail
+    goPPTDetail,
+
+    permissionBtn,
+    checkAuthBtn
 }=useClassify()
 
 getPPTClassifyData()
@@ -134,15 +137,15 @@ const showPopover=ref(false)
                 <img src="@/assets/imgs/ppt/ppt_icon_file.png" alt="">
                 <span>{{fileOptState.data.GroupName}}</span>
             </div>
-            <div class="item border blue" @click="handlePPTCatalogueCopy">
+            <div class="item border blue" @click="handlePPTCatalogueCopy" v-permission="permissionBtn.ppt_copy">
                 <img src="@/assets/imgs/ppt/ppt_icon_copy.png" alt="">
                 <span>复制</span>
             </div>
-            <div class="item border blue" @click="fileOptState.showReName=true">
+            <div class="item border blue" @click="fileOptState.showReName=true" v-permission="permissionBtn.ppt_save">
                 <img src="@/assets/imgs/ppt/ppt_icon_write.png" alt="">
                 <span>重命名</span>
             </div>
-            <div class="item border red" @click="handlePPTCatalogueDel">
+            <div class="item border red" @click="handlePPTCatalogueDel" v-permission="permissionBtn.ppt_del">
                 <img src="@/assets/imgs/icon_del.png" alt="">
                 <span>删除</span>
             </div>
@@ -180,7 +183,7 @@ const showPopover=ref(false)
                         </div>
                     </div>
                 </div>
-                <div class="flex" v-if="!PPTOptState.isCommon">
+                <div class="flex" v-if="!PPTOptState.isCommon&&checkAuthBtn(permissionBtn.ppt_visible)">
                     <span style="margin-right:4px">公开</span>
                     <van-switch 
                         size="22px" 
@@ -190,11 +193,11 @@ const showPopover=ref(false)
                 </div>
                 
             </div>
-            <div class="item border blue" @click="handleShowPPTCopy">
+            <div class="item border blue" @click="handleShowPPTCopy" v-permission="permissionBtn.ppt_copy">
                 <img src="@/assets/imgs/ppt/ppt_icon_copy.png" alt="">
                 <span>复制</span>
             </div>
-            <div class="item border red" @click="handlePPTDel" v-if="!PPTOptState.isCommon">
+            <div class="item border red" @click="handlePPTDel" v-if="!PPTOptState.isCommon&&checkAuthBtn(permissionBtn.ppt_del)">
                 <img src="@/assets/imgs/icon_del.png" alt="">
                 <span>删除</span>
             </div>

+ 21 - 12
src/views/ppt/components/PadPPTIndexWrap.vue

@@ -1,5 +1,5 @@
 <script setup>
-import {ref} from 'vue'
+import {computed, ref} from 'vue'
 import {useClassify} from '../hooks/useClassify'
 import openShareIcon from '@/assets/imgs/ppt/ppt_icon_user1.png'
 import closeShareIcon from '@/assets/imgs/ppt/ppt_icon_user2.png'
@@ -30,10 +30,19 @@ const {
     handlePPTCopy,
     handlePPTCopyBeforeClose,
 
-    goPPTDetail
+    goPPTDetail,
+
+    permissionBtn,
+    checkAuthBtn
 }=useClassify()
 getPPTClassifyData()
 
+const isOptBoxShow = computed(()=>{
+    return checkAuthBtn(permissionBtn.ppt_copy)||checkAuthBtn(permissionBtn.ppt_save)||checkAuthBtn(permissionBtn.ppt_del)
+})
+const isOptPPTShow = computed(()=>{
+    return checkAuthBtn(permissionBtn.ppt_copy)||checkAuthBtn(permissionBtn.ppt_del)
+})
 const activeType=ref('myPPT')
 
 // 获取ppt列表
@@ -109,20 +118,20 @@ const showPopover=ref(false)
                                 <div @click.stop="handleShowFileOpt(item)">
                                     <van-popover position="bottom-start">
                                         <template #reference>
-                                            <div class="menu-icon">
+                                            <div class="menu-icon" v-if="isOptBoxShow">
                                                 <img class="icon" src="@/assets/imgs/ppt/ppt_icon_menu.png" alt="">
                                             </div>
                                         </template>
                                         <div class="pad-classify-file-opt-box">
-                                            <div class="item" @click="handlePPTCatalogueCopy">
+                                            <div class="item" @click="handlePPTCatalogueCopy" v-permission="permissionBtn.ppt_copy">
                                                 <img src="@/assets/imgs/ppt/icon_action_copy.png" alt="">
                                                 <span>复制</span>
                                             </div>
-                                            <div class="item" @click="fileOptState.showReName=true">
+                                            <div class="item" @click="fileOptState.showReName=true" v-permission="permissionBtn.ppt_save">
                                                 <img src="@/assets/imgs/ppt/ppt_icon_write02.png" alt="">
                                                 <span>重命名</span>
                                             </div>
-                                            <div class="item del" @click="handlePPTCatalogueDel">
+                                            <div class="item del" @click="handlePPTCatalogueDel" v-permission="permissionBtn.ppt_del">
                                                 <img src="@/assets/imgs/icon_del.png" alt="">
                                                 <span>删除</span>
                                             </div>
@@ -141,14 +150,14 @@ const showPopover=ref(false)
                             <div @click.stop="handleShowPPTOpt(_item)">
                                 <van-popover position="bottom-start">
                                     <template #reference>
-                                        <img class="share-icon" :src="_item.IsSingleShareBoolean?openShareIcon:closeShareIcon" alt="">
+                                        <img class="share-icon" :src="_item.IsSingleShareBoolean?openShareIcon:closeShareIcon" alt="" v-if="checkAuthBtn(permissionBtn.ppt_visible)">
                                     </template>
                                     <div class="pad-classify-file-opt-box">
-                                        <div class="item" @click="handleChangePPTShare(1,_item)">
+                                        <div class="item" @click="handleChangePPTShare(1,_item)" v-permission="permissionBtn.ppt_visible">
                                             <img :src="closeShareIcon" alt="">
                                             <span>仅自己可见</span>
                                         </div>
-                                        <div class="item" @click="handleChangePPTShare(2,_item)">
+                                        <div class="item" @click="handleChangePPTShare(2,_item)" v-permission="permissionBtn.ppt_visible">
                                             <img :src="openShareIcon" alt="">
                                             <span>所有人可见</span>
                                         </div>
@@ -158,16 +167,16 @@ const showPopover=ref(false)
                             <div @click.stop="handleShowPPTOpt(_item)">
                                 <van-popover position="bottom-start">
                                     <template #reference>
-                                        <div class="menu-icon">
+                                        <div class="menu-icon" v-if="isOptPPTShow">
                                             <img class="icon" src="@/assets/imgs/ppt/ppt_icon_menu.png" alt="">
                                         </div>
                                     </template>
                                     <div class="pad-classify-file-opt-box">
-                                        <div class="item" @click="handleShowPPTCopy">
+                                        <div class="item" @click="handleShowPPTCopy" v-permission="permissionBtn.ppt_copy">
                                             <img src="@/assets/imgs/ppt/icon_action_copy.png" alt="">
                                             <span>复制</span>
                                         </div>
-                                        <div class="item del" @click="handlePPTDel">
+                                        <div class="item del" @click="handlePPTDel" v-permission="permissionBtn.ppt_del">
                                             <img src="@/assets/imgs/icon_del.png" alt="">
                                             <span>删除</span>
                                         </div>

+ 20 - 0
src/views/ppt/hooks/createPPTContent.js

@@ -22,6 +22,26 @@ import RectShape from '../components/layers/RectShape.vue'
 import LineShape from '../components/layers/LineShape.vue'
 import TextShape from '../components/layers/TextShape.vue'
 
+import { storeToRefs } from 'pinia'
+import { useConfigSettingStore } from '@/store/modules/etaConfig'
+
+export function getPPTConfig() {
+
+    const lang=window.location.pathname.startsWith('/ppten')?'en':'zh'
+
+    const { etaConfigInfo } = storeToRefs(useConfigSettingStore())
+
+    const { CnPptCoverImgs,CnPptBackgroundImg,CnPptBottomImg,PptCompanyName,PptTeamName,PptFontColor,EnPptCoverImgs,EnPptBackgroundImg,EnPptBottomImg,PptCompanyNameEn,PptTeamNameEn,PptFontColorEn }  = etaConfigInfo.value
+    return {
+        pptCoverList: lang==='en' ? EnPptCoverImgs.split(',') : CnPptCoverImgs.split(','),
+        pptBgImage: lang==='en' ? EnPptBackgroundImg : CnPptBackgroundImg,
+        pptBackImage: lang==='en' ? EnPptBottomImg : CnPptBottomImg,
+        pptCoverCompenyName: lang==='en' ? PptCompanyNameEn : PptCompanyName,
+        pptCoverDepartName: lang==='en' ? PptTeamNameEn : PptTeamName,
+        pptCoverTextColor: lang==='en' ? PptFontColorEn : PptFontColor
+    }
+}
+
 /**
  * 格式化ppt内容
  * @param params ppt详情接口返回的数据

+ 8 - 1
src/views/ppt/hooks/useClassify.js

@@ -4,6 +4,9 @@ import {ref,reactive} from 'vue'
 import { useRouter } from 'vue-router';
 import { showToast,showDialog,Dialog  } from 'vant';
 import {useUserInfo} from '@/hooks/common'
+import {pptBtn,enPPTBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const  {checkAuthBtn} = useAuthBtn()
+const permissionBtn = window.location.pathname.startsWith('/ppten')?enPPTBtn:pptBtn
 import {
     apiPPTClassify,
     apiPPTCatalogueCopy,
@@ -123,6 +126,7 @@ export function useClassify(){
     // e ppt的数据
     const handleShowPPTOpt=(e)=>{
         PPTOptState.data=e
+        PPTOptState.data.IsSingleShareBoolean=e.IsSingleShare?true:false
         PPTOptState.isCommon=userInfo.value.AdminId!=e.AdminId?true:false
         PPTOptState.show=true
     }
@@ -242,6 +246,9 @@ export function useClassify(){
         handlePPTCopy,
         handlePPTCopyBeforeClose,
 
-        goPPTDetail
+        goPPTDetail,
+
+        permissionBtn,
+        checkAuthBtn
     }
 }

+ 46 - 13
src/views/ppt/hooks/usePPTPublish.js

@@ -6,9 +6,12 @@ import pptxgen from "pptxgenjs";
 import { parse } from "himalaya";
 import _ from 'lodash'
 import {pptLayout,pptSlideMaster,pptSlideMasterEn,modelConfig} from '../utils/config'
-import {useUploadFileToOSS} from '@/hooks/useUploadFileToOSS'
+import {useUploadFileToOSS,useUploadToMinIO} from '@/hooks/useUploadFileToOSS'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
 import moment from 'moment'
 
+const publicSettingStore = usePublicSettingStore()
+
 let LoadingINS=null
 let PPTContentList=[]
 let pptId=0
@@ -430,6 +433,28 @@ async function handleUploadToOSS(data){
 	}
 	handlePublishPPT(url)
 }
+// 上传到minio
+async function handleUploadToMinIO(data){
+    const loading=showLoadingToast({
+        message: "上传中...",
+        duration: 0,
+        forbidClick: true,
+      })
+    const t=new Date()
+	const month=moment(t).format('YYYYMM')
+	const day=moment(t).format('YYYYMMDD')
+	const temName=`ppt/${month}/${day}/${createRandomCode(32)}.pptx`
+	console.log('文件名',temName);
+    const url = await useUploadToMinIO(data,temName)
+    loading.close()
+    if(!url){
+        setTimeout(() => {
+            showToast('生成ppt失败')
+        }, 60);
+        return
+    }
+    handlePublishPPT(url)
+}
 
 // 页面转ppt
 async function pageToPPT(){
@@ -569,16 +594,18 @@ async function pageToPPT(){
     }
 
     //添加封底
-    let back = PPTINS.addSlide()
-    let backImg = $(`#ppt-last-page img`)[0].src
-    back.addImage({
-        path: backImg,
-        x: 0,
-        y: 0,
-        w:'100%',
-        h: '100%',
-        size: { type: "contain" },
-    })
+    if($(`#ppt-last-page img`)[0]){
+        let back = PPTINS.addSlide()
+        let backImg = $(`#ppt-last-page img`)[0].src
+        back.addImage({
+            path: backImg,
+            x: 0,
+            y: 0,
+            w:'100%',
+            h: '100%',
+            size: { type: "contain" },
+        })
+    }
 
     //为了把封面放到第一页,操作pptx.slides达不成想要的效果,于是弄了个pptx2
     //将封面放在最后生成是因为htmlToCanvans占用太多内存会导致页面假死
@@ -632,8 +659,13 @@ async function pageToPPT(){
     }
     pptx2.write('blob').then((data)=>{
       LoadingINS.close()
-      // 上传到阿里云oss
-      handleUploadToOSS(data)
+      if(publicSettingStore.publicSetting.ObjectStorageClient==='minio'){
+        //上传到minio
+        handleUploadToMinIO(data)
+      }else{
+        handleUploadToOSS(data)
+      }
+      //handleUploadToOSS(data)
     })
     // pptx2.writeFile({ fileName: "test.pptx" }) //本地测试可直接用该方法生成ppt文件
 }
@@ -670,5 +702,6 @@ export async function usePPTPublish(data,id){
         duration: 0,
         forbidClick: true,
     })
+    await publicSettingStore.getPublicSetting()
     pageToPPT()
 }

+ 17 - 12
src/views/ppt/template/Cover.vue

@@ -1,5 +1,8 @@
 <script setup>
 import { ref } from "vue";
+import { getPPTConfig } from '../hooks/createPPTContent'
+
+console.log(getPPTConfig())
 
 const props = defineProps({
   pageData: {
@@ -12,23 +15,24 @@ const lang = ref(window.location.pathname.startsWith("/ppten") ? "en" : "zh");
 </script>
 
 <template>
-  <div class="ppt-item-box ppt-cover-page" id="ppt-cover-page">
-    <img :src="pageData.imgLocalUrl.image_url" class="pptbg" style="width: 100%" />
-    <div v-if="lang == 'zh'" style="width: 62%; font-size: 16px; text-align: center; line-height: 1.6; color: #fff; position: absolute; right: 20px; top: 50%; zindex: 20">
-      <p style="height: 5px; border-top: 1px solid #fff; marginbottom: 21px"></p>
+  <div class="ppt-item-box ppt-cover-page" id="ppt-cover-page" :style="`color: ${getPPTConfig().pptCoverTextColor||'#fff'}`">
+    <!-- <img :src="pageData.imgLocalUrl.image_url" class="pptbg" style="width: 100%" /> -->
+    <img :src="pageData.BackgroundImg" class="pptbg" style="width: 100%" />
+    <div style="width: 62%; font-size: 16px; text-align: center; line-height: 1.6; position: absolute; right: 20px; top: 50%; zindex: 20">
+      <p style="height: 5px; marginbottom: 21px"></p>
       <p style="font-size: 28px">{{ pageData.Title }}</p>
       <p style="display: flex; align-items: center; justify-content: center; margin: 10px 0">
-        <span style="display: inline-block; width: 15px; margin-right: 5px; border-top: 1px solid #fff"></span>
-        <span>弘则弥道(上海)投资咨询有限公司</span>
-        <span style="display: inline-block; width: 14px; height: 14px; background: #fff; border-radius: 100%; margin: 0 5px"></span>
+        <span :style="`display: inline-block; width: 15px; margin-right: 5px; border-top: 1px solid ${getPPTConfig().pptCoverTextColor||'#fff'}`"></span>
+        <span>{{getPPTConfig().pptCoverCompenyName||'ETA'}}</span>
+        <span :style="`display: inline-block; width: 14px; height: 14px; background: ${getPPTConfig().pptCoverTextColor||'#fff'}; border-radius: 100%; margin: 0 5px`"></span>
         <span>{{ pageData.ReportType }}</span>
-        <span style="display: inline-block; width: 15px; margin-left: 5px; border-top: 1px solid #fff"></span>
+        <span :style="`display: inline-block; width: 15px; margin-left: 5px; border-top: 1px solid ${getPPTConfig().pptCoverTextColor||'#fff'};`"></span>
       </p>
-      <p>FICC研究部</p>
+      <p>{{getPPTConfig().pptCoverDepartName||'投研部'}}</p>
       <p>{{ pageData.PptDate }}</p>
-      <p style="width: 80%; height: 1px; border-bottom: 1px solid #fff; margin: 21px auto 0"></p>
+      <p style="width: 80%; height: 1px; margin: 21px auto 0"></p>
     </div>
-    <div v-if="lang == 'en'" style="width: 62%; font-size: 16px; text-align: center; line-height: 1.6; color: #fff; position: absolute; right: 20px; top: 50%; zindex: 20">
+    <!-- <div v-if="lang == 'en'" style="width: 62%; font-size: 16px; text-align: center; line-height: 1.6; color: #fff; position: absolute; right: 20px; top: 50%; zindex: 20">
       <p style="height: 5px; border-top: 1px solid #fff; marginbottom: 21px"></p>
       <p style="font-size: 28px">{{ pageData.Title }}</p>
       <p style="display: flex; align-items: center; justify-content: center; margin: 10px 0">
@@ -42,7 +46,7 @@ const lang = ref(window.location.pathname.startsWith("/ppten") ? "en" : "zh");
       <p>FICC Research Department</p>
       <p>{{ pageData.PptDate }}</p>
       <p style="width: 80%; height: 1px; border-bottom: 1px solid #fff; margin: 21px auto 0"></p>
-    </div>
+    </div> -->
   </div>
 </template>
 
@@ -51,5 +55,6 @@ const lang = ref(window.location.pathname.startsWith("/ppten") ? "en" : "zh");
 .ppt-cover-page {
   position: relative;
   overflow: hidden;
+  background: none;
 }
 </style>

+ 3 - 2
src/views/ppt/template/Footer.vue

@@ -1,4 +1,5 @@
 <script setup>
+import { getPPTConfig } from '../hooks/createPPTContent'
 
 const props=defineProps({
     pageData:{
@@ -9,8 +10,8 @@ const props=defineProps({
 </script>
 
 <template>
-    <div class="ppt-item-box ppt-last-page" id="ppt-last-page">
-        <img :src="pageData.bgImg" alt="">
+    <div class="ppt-item-box ppt-last-page" id="ppt-last-page" v-if="getPPTConfig().pptBackImage.length">
+        <img :src="getPPTConfig().pptBackImage" alt="">
     </div>
 </template>
 

+ 1 - 1
src/views/ppt/template/FormatEight.vue

@@ -14,7 +14,7 @@ const lang=ref(window.location.pathname.startsWith('/ppten')?'en':'zh')
 </script>
 
 <template>
-	<div :class="['ppt-item-box',lang=='en'?'ppt-item-box_en':'']">
+	<div :class="['ppt-item-box ppt-item-page',lang=='en'?'ppt-item-box_en':'']">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 1 - 1
src/views/ppt/template/FormatFive.vue

@@ -14,7 +14,7 @@ const lang=ref(window.location.pathname.startsWith('/ppten')?'en':'zh')
 </script>
 
 <template>
-	<div :class="['ppt-item-box',lang=='en'?'ppt-item-box_en':'']">
+	<div :class="['ppt-item-box ppt-item-page',lang=='en'?'ppt-item-box_en':'']">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 1 - 1
src/views/ppt/template/FormatFour.vue

@@ -14,7 +14,7 @@ const lang=ref(window.location.pathname.startsWith('/ppten')?'en':'zh')
 </script>
 
 <template>
-	<div :class="['ppt-item-box',lang=='en'?'ppt-item-box_en':'']">
+	<div :class="['ppt-item-box ppt-item-page',lang=='en'?'ppt-item-box_en':'']">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 1 - 1
src/views/ppt/template/FormatNine.vue

@@ -14,7 +14,7 @@ const lang=ref(window.location.pathname.startsWith('/ppten')?'en':'zh')
 </script>
 
 <template>
-	<div :class="['ppt-item-box',lang=='en'?'ppt-item-box_en':'']">
+	<div :class="['ppt-item-box ppt-item-page',lang=='en'?'ppt-item-box_en':'']">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 1 - 1
src/views/ppt/template/FormatOne.vue

@@ -14,7 +14,7 @@ const lang=ref(window.location.pathname.startsWith('/ppten')?'en':'zh')
 </script>
 
 <template>
-	<div :class="['ppt-item-box',lang=='en'?'ppt-item-box_en':'']">
+	<div :class="['ppt-item-box ppt-item-page',lang=='en'?'ppt-item-box_en':'']">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 1 - 1
src/views/ppt/template/FormatSeven.vue

@@ -14,7 +14,7 @@ const lang=ref(window.location.pathname.startsWith('/ppten')?'en':'zh')
 </script>
 
 <template>
-	<div :class="['ppt-item-box',lang=='en'?'ppt-item-box_en':'']">
+	<div :class="['ppt-item-box ppt-item-page',lang=='en'?'ppt-item-box_en':'']">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 1 - 1
src/views/ppt/template/FormatSix.vue

@@ -11,7 +11,7 @@ const props=defineProps({
 </script>
 
 <template>
-	<div class="ppt-item-box">
+	<div class="ppt-item-box ppt-item-page">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 1 - 1
src/views/ppt/template/FormatThree.vue

@@ -11,7 +11,7 @@ const props=defineProps({
 </script>
 
 <template>
-	<div class="ppt-item-box">
+	<div class="ppt-item-box ppt-item-page">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 1 - 1
src/views/ppt/template/FormatTwo.vue

@@ -12,7 +12,7 @@ const props=defineProps({
 </script>
 
 <template>
-	<div class="ppt-item-box">
+	<div class="ppt-item-box ppt-item-page">
 		<div class="ppt-title-box">{{pageData.title}}</div>
 		<div class="ppt-content-box">
 			<div class="container">

+ 38 - 9
src/views/report/AddReport.vue

@@ -10,9 +10,13 @@ import moment from 'moment'
 import { showToast,showDialog  } from 'vant'
 import { useRouter } from 'vue-router'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
+import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
 const cachedViewsStore=useCachedViewsStore()
+const publicSettingStore = usePublicSettingStore()
 
 const router=useRouter()
+const {checkAuthBtn} = useAuthBtn()
 
 
 const {lastFocusPosition,initFroalaEditor}=useInitFroalaEditor()
@@ -78,16 +82,18 @@ function handleInsert({list,type,chartType}){
         reportContentEditorIns.selection.get().addRange(lastFocusPosition.value)
     }
     if(type==='iframe'){
-        let link;
+        let link=publicSettingStore.publicSetting.ChartViewUrl;
         if(chartType==='chart'){
-            link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
+            // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
+            link=link+'/chartshow'
             list.forEach(item => {
                 reportContentEditorIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
 						<iframe src='${link}?code=${item}&fromPage=' width='100%' height='350' style='border-width:0px; min-height:350px;'></iframe>
 					</p>`)
             });
         }else if(chartType==='sheet'){
-            link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
+            // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
+            link=link+'/sheetshow'
             list.forEach(item => {
                 reportContentEditorIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
 						<iframe src='${link}?code=${item}' class='iframe${item}'  width='100%' style='border-width:0px;'></iframe>
@@ -205,6 +211,11 @@ async function handleReportOpt(type){
             }
         }else{
             // 显示发布提示弹窗,提示推送客群
+            //判断是否有推送权限
+            if(!checkAuthBtn(reportManageBtn.reportManage_sendMsg)){
+                handleConfirmPublish(1)
+                return
+            }
             showPublishPop.value=true
         }
     }
@@ -259,13 +270,30 @@ async function reportPublish(id){
 const showDSFBTime=ref(false)
 function onConfirmDSFBTime(time){
     console.log(time);
+    const isAuthPushMsg=checkAuthBtn(reportManageBtn.reportManage_sendMsg)
+    console.log(isAuthPushMsg);
     showDialog({
         title: '提示',
-        message:'是否发布定时报告,并推送模板消息?',
-        confirmButtonText:'推送',
-        cancelButtonText:'不推送',
+        message:isAuthPushMsg?'是否发布定时报告,并推送模板消息?':'是否发布定时报告',
+        confirmButtonText:isAuthPushMsg?'推送':'取消',
+        cancelButtonText:isAuthPushMsg?'不推送':'确定',
         showCancelButton:true
     }).then(()=>{
+        if(!isAuthPushMsg){
+            apiReport.reportPublishTimeSet({
+                ReportId:reportId,
+                PrePublishTime:time,
+                PreMsgSend:0
+            }).then(res=>{
+                if(res.Ret===200){
+                    showToast('定时发布成功!')
+                    setTimeout(() => {
+                        router.back()
+                    }, 1000);
+                }
+            })
+            return
+        }
         //推送
         apiReport.reportPublishTimeSet({
             ReportId:reportId,
@@ -280,6 +308,7 @@ function onConfirmDSFBTime(time){
             }
         })
     }).catch(()=>{
+        if(!isAuthPushMsg) return
         //不推送
         apiReport.reportPublishTimeSet({
             ReportId:reportId,
@@ -311,7 +340,7 @@ function onConfirmDSFBTime(time){
                     <img src="@/assets/imgs/report/icon_refresh.png" alt="">
                     <span>刷新</span>
                 </div>
-                <div class="item" @click="handleReportOpt('yl')">
+                <div class="item" @click="handleReportOpt('yl')" v-permission="reportManageBtn.reportManage_reportView">
                     <img src="@/assets/imgs/report/icon_preview.png" alt="">
                     <span>预览</span>
                 </div>
@@ -319,11 +348,11 @@ function onConfirmDSFBTime(time){
                     <img src="@/assets/imgs/report/icon_save2.png" alt="">
                     <span>保存</span>
                 </div>
-                <div class="item" @click="handleReportOpt('dsfb')">
+                <div class="item" @click="handleReportOpt('dsfb')" v-permission="reportManageBtn.reportManage_publish">
                     <img src="@/assets/imgs/report/icon_time.png" alt="">
                     <span>定时发布</span>
                 </div>
-                <div class="item" @click="handleReportOpt('fb')">
+                <div class="item" @click="handleReportOpt('fb')" v-permission="reportManageBtn.reportManage_publish">
                     <img src="@/assets/imgs/report/icon_publish3.png" alt="">
                     <span>发布</span>
                 </div>

+ 67 - 16
src/views/report/EditReport.vue

@@ -10,10 +10,16 @@ import moment from 'moment'
 import { showToast,showDialog } from 'vant'
 import { useRoute, useRouter } from 'vue-router'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
+import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
 const cachedViewsStore=useCachedViewsStore()
+const publicSettingStore = usePublicSettingStore()
 
 const router=useRouter()
 const route=useRoute()
+const {checkAuthBtn} = useAuthBtn()
+
+const isApprove = ref(false)
 
 
 const {lastFocusPosition,initFroalaEditor,imgUploadFlag,frolaEditorContentChange}=useInitFroalaEditor()
@@ -97,6 +103,9 @@ async function getReportDetail(){
         }
 
     }
+    //获取审批流
+    await publicSettingStore.getPublicSetting()
+    isApprove.value = ['1','3'].includes(publicSettingStore.publicSetting.ApprovalFlow)
 }
 
 
@@ -151,16 +160,18 @@ function handleInsert({list,type,chartType}){
         reportContentEditorIns.selection.get().addRange(lastFocusPosition.value)
     }
     if(type==='iframe'){
-        let link;
+        let link=publicSettingStore.publicSetting.ChartViewUrl;
         if(chartType==='chart'){
-            link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
+            // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
+            link=link+'/chartshow'
             list.forEach(item => {
                 reportContentEditorIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
 						<iframe src='${link}?code=${item}&fromPage=' width='100%' height='350' style='border-width:0px; min-height:350px;'></iframe>
 					</p>`,false)
             });
         }else if(chartType==='sheet'){
-            link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
+            // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
+            link=link+'/sheetshow'
             list.forEach(item => {
                 reportContentEditorIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
 						<iframe src='${link}?code=${item}' class='iframe${item}'  width='100%' style='border-width:0px;'></iframe>
@@ -203,12 +214,23 @@ async function handleRefreshAllChart(){
         }
     });
     if(!code_arr.length) return showToast('请插入图表');
+
+    if(route.query.id) {
+        let res = await apiChart.getReportrefreshStatus({
+            Source: 'report',
+            ReportId: Number(route.query.id),
+            ReportChapterId: 0
+        });
+        
+        if(!res.Data.RefreshResult) return showToast('图表正在刷新中,请勿重复操作')
+    }
+
     const res=await apiChart.refreshChartMultiple({ChartInfoCode:code_arr})
     if(res.Ret===200){
         $('iframe').each((k,i) => {
           $(i).attr('src',$(i).attr('src'))
         });
-        showToast('刷新成功')
+        showToast(res.Msg)
     }
 }
 
@@ -275,6 +297,10 @@ async function handleReportOpt(type){
         //     // 显示发布提示弹窗,提示推送客群
         //     showPublishPop.value=true
         // }
+        if(isApprove.value||!checkAuthBtn(reportManageBtn.reportManage_sendMsg)){
+            handleConfirmPublish(1)
+            return
+        }
         // 没有客群了发布都有二次提示弹窗
         showPublishPop.value=true
     }
@@ -341,13 +367,29 @@ function onConfirmDSFBTime(time){
         })
         return
     }
+    const isAuthPushMsg=checkAuthBtn(reportManageBtn.reportManage_sendMsg)
     showDialog({
         title: '提示',
-        message:'是否发布定时报告,并推送模板消息?',
-        confirmButtonText:'推送',
-        cancelButtonText:'不推送',
+        message:isAuthPushMsg?'是否发布定时报告,并推送模板消息?':'是否发布定时报告',
+        confirmButtonText:isAuthPushMsg?'推送':'确定',
+        cancelButtonText:isAuthPushMsg?'不推送':'取消',
         showCancelButton:true
     }).then(()=>{
+        if(!isAuthPushMsg){
+            apiReport.reportPublishTimeSet({
+                ReportId:reportData.value.Id,
+                PrePublishTime:time,
+                PreMsgSend:0
+            }).then(res=>{
+                if(res.Ret===200){
+                    showToast('定时发布成功!')
+                    setTimeout(() => {
+                        router.back()
+                    }, 1000);
+                }
+            })
+            return
+        }
         //推送
         apiReport.reportPublishTimeSet({
             ReportId:reportData.value.Id,
@@ -362,6 +404,7 @@ function onConfirmDSFBTime(time){
             }
         })
     }).catch(()=>{
+        if(!isAuthPushMsg) return
         //不推送
         apiReport.reportPublishTimeSet({
             ReportId:reportData.value.Id,
@@ -394,7 +437,7 @@ function onConfirmDSFBTime(time){
                     <img src="@/assets/imgs/report/icon_refresh.png" alt="">
                     <span>刷新</span>
                 </div>
-                <div class="item" @click="handleReportOpt('yl')">
+                <div class="item" @click="handleReportOpt('yl')" v-permission="reportManageBtn.reportManage_reportView">
                     <img src="@/assets/imgs/report/icon_preview.png" alt="">
                     <span>预览</span>
                 </div>
@@ -402,14 +445,22 @@ function onConfirmDSFBTime(time){
                     <img src="@/assets/imgs/report/icon_save2.png" alt="">
                     <span>保存</span>
                 </div>
-                <div class="item" @click="handleReportOpt('dsfb')">
-                    <img src="@/assets/imgs/report/icon_time.png" alt="">
-                    <span>定时发布</span>
-                </div>
-                <div class="item" @click="handleReportOpt('fb')">
-                    <img src="@/assets/imgs/report/icon_publish3.png" alt="">
-                    <span>发布</span>
-                </div>
+                <template v-if="!isApprove">
+                    <div class="item" @click="handleReportOpt('dsfb')" v-permission="reportManageBtn.reportManage_publish">
+                        <img src="@/assets/imgs/report/icon_time.png" alt="">
+                        <span>定时发布</span>
+                    </div>
+                    <div class="item" @click="handleReportOpt('fb')" v-permission="reportManageBtn.reportManage_publish">
+                        <img src="@/assets/imgs/report/icon_publish3.png" alt="">
+                        <span>发布</span>
+                    </div>
+                </template>
+                <template v-if="isApprove">
+                    <div class="item" @click="handleReportOpt('fb')" v-permission="reportManageBtn.reportManage_publish">
+                        <img src="@/assets/imgs/report/icon_publish3.png" alt="">
+                        <span>提交</span>
+                    </div>
+                </template>
             </div>
             <div class="right-btn" @click="showReportInsertPop=true">
                 <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">

+ 113 - 24
src/views/report/List.vue

@@ -1,5 +1,5 @@
 <script setup name="ReportList">
-import {computed, nextTick, reactive,ref} from 'vue'
+import {computed, nextTick, onMounted, reactive,ref} from 'vue'
 import apiReport from '@/api/report'
 import moment from 'moment'
 import ListClassify from './components/ListClassify.vue'
@@ -8,8 +8,31 @@ import { showToast,showDialog,Dialog } from 'vant';
 import { useRouter } from 'vue-router';
 import { useWindowSize } from '@vueuse/core'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
 import {reportFrequencyOpts} from './utils/config'
+import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
 const cachedViewsStore=useCachedViewsStore()
+const publicSettingStore = usePublicSettingStore()
+
+const isApprove = ref(false)
+
+const {checkAuthBtn} = useAuthBtn()
+
+//添加按钮和添加选项
+const isAddReportBtnShow = computed(()=>{
+    return checkAuthBtn(reportManageBtn.reportManage_reportAdd)||checkAuthBtn(reportManageBtn.reportManage_dayWeekReportAdd)
+})
+
+const addReportActions = computed(()=>{
+    let arr = []
+    if(checkAuthBtn(reportManageBtn.reportManage_reportAdd)){
+        arr.push({name:'研报'})
+    }
+    if(checkAuthBtn(reportManageBtn.reportManage_dayWeekReportAdd)){
+        arr.push({name:'晨报/周报'})
+    }
+    return arr.reverse()
+})
 
 const { width, height } = useWindowSize()
 
@@ -136,10 +159,32 @@ async function handleReportPublish(item){
         }
         return
     }
+    //如果走审批流 直接发布
+    if(isApprove.value){
+        publishReportApprove()
+        showReportItemOpt.value=false
+        return
+    }
+    // 判断是否有推送权限
+    if(!checkAuthBtn(reportManageBtn.reportManage_sendMsg)){
+        publishReportApprove()
+        showReportItemOpt.value=false
+        return
+    }
 
     showPublishPop.value=true
     showReportItemOpt.value=false
 }
+function publishReportApprove(){
+    apiReport.reportPublish({
+        ReportIds:activeReportData.value.Id.toString()
+    }).then(res=>{
+        if(res.Ret!==200) return 
+        showToast('发布成功')
+        handlePublishPopClose('refresh')
+    })
+}
+
 
 // 发布弹窗关闭
 function handlePublishPopClose(refresh){
@@ -154,7 +199,7 @@ function handlePublishPopClose(refresh){
 function handleReportPublishCancle(item){
     showDialog({
         title: '提示',
-        message: `是否确认取消发布?`,
+        message: `是否确认取消${isApprove.value?'提交':'发布'}?`,
         showCancelButton:true
     }).then(()=>{
         apiReport.reportPublishCancle({ReportIds:Number(item.Id)}).then(res=>{
@@ -210,6 +255,8 @@ function handleConfirmClassify({firstClassify,secondClassify}){
 // 跳转详情
 function goDetail(item){
     console.log(item);
+    //若没有预览权限,则不跳转
+    if(!checkAuthBtn(reportManageBtn.reportManage_reportView)) return 
     router.push({
         path:"/report/preview",
         query:{
@@ -227,6 +274,22 @@ async function onLongPressItem(e){
         showToast(`${e.Editor}正在编辑中,不可操作!`)
         return
     }
+    //检验权限,如果该状态下无可操作项,则长按不弹出
+    let checkState = false
+    if(e.State===1){ //编辑、发布、删除
+        checkState = checkAuthBtn(reportManageBtn.reportManage_reportEdit)
+            ||checkAuthBtn(reportManageBtn.reportManage_publish)
+            ||checkAuthBtn(reportManageBtn.reportManage_reportDel)
+    }
+    if((e.State===2&&!isApprove)||e.State===4){ //推送消息、取消发布
+        checkState = checkAuthBtn(reportManageBtn.reportManage_sendMsg)
+            ||checkAuthBtn(reportManageBtn.reportManage_cancelPublish)
+    }
+    if((e.State===2&&isApprove)||e.State===3){ //撤销
+        checkState = checkAuthBtn(reportManageBtn.reportManage_cancelPublish)
+    }
+    
+    if(!checkState) return
     activeItem.value=e
     showReportItemOpt.value=true
 }
@@ -259,6 +322,25 @@ const publishStatusOpt=[
         value:1
     }
 ]
+const approveStatusOpt=[
+    {
+        label:'待提交',
+        value:1
+    },
+    {
+        label:'待审批',
+        value:2
+    },
+    {
+        label:'已审批',
+        value:4
+    },
+    {
+        label:'已驳回',
+        value:3
+    }
+]
+let statusOpt = publishStatusOpt
 function handleSelectReportStatus(item){
     if(temMsgIsSendVal.value==item.value){
         temMsgIsSendVal.value=''
@@ -373,6 +455,13 @@ async function handleReportEdit(e){
     })
 }
 
+onMounted(async ()=>{
+    await publicSettingStore.getPublicSetting()
+    isApprove.value = ['1','3'].includes(publicSettingStore.publicSetting.ApprovalFlow)
+    console.log('isApprove',isApprove.value)
+    statusOpt = isApprove.value?approveStatusOpt:publishStatusOpt
+})
+
 </script>
 
 <template>
@@ -438,7 +527,7 @@ async function handleReportEdit(e){
                         </div>
                     </div>
                 </van-dropdown-item>
-                <van-dropdown-item title="推送状态" ref="statusDropMenuIns">
+                <van-dropdown-item title="推送状态" ref="statusDropMenuIns" v-if="checkAuthBtn(reportManageBtn.reportManage_sendMsg)">
                     <div class="report-status-box">
                         <ul>
                             <li 
@@ -455,12 +544,12 @@ async function handleReportEdit(e){
                         </div>
                     </div>
                 </van-dropdown-item>
-                <van-dropdown-item title="发布状态" ref="publishStatusDropMenuIns">
+                <van-dropdown-item :title="isApprove?'状态':'发布状态'" ref="publishStatusDropMenuIns">
                     <div class="report-status-box">
                         <ul>
                             <li 
                                 :class="['status-item',temPublishStatusVal===item.value?'active':'']" 
-                                v-for="item in publishStatusOpt" 
+                                v-for="item in statusOpt" 
                                 :key="item.value"
                                 @click="handleSelectReportPublishStatus(item)"
                             >{{item.label}}</li>
@@ -507,11 +596,13 @@ async function handleReportEdit(e){
                         </div>
                         <div class="read-count">
                             <span>PV:{{item.Pv}}</span>
-                            <span>UV:{{item.Uv}}</span>
+                            <span v-permission="reportManageBtn.reportManage_reportList_uv">UV:{{item.Uv}}</span>
                         </div>
                         <div class="status">
-                            <span v-if="item.State===1">未发布</span>
-                            <span v-if="item.State===2" class="active-status">已发布</span>
+                            <span v-if="item.State===1">{{isApprove?'待提交':'未发布'}}</span>
+                            <span v-if="item.State===2" class="active-status">{{isApprove?'待审批':'已发布'}}</span>
+                            <span v-if="item.State===3">已驳回</span>
+                            <span v-if="item.State===4">已审批</span>
                         </div>
                     </div>
                 </li>
@@ -520,7 +611,7 @@ async function handleReportEdit(e){
     </div>
 
     <!-- 添加报告按钮 -->
-    <div class="add-report-btn" @click="showAddReportPop=true">
+    <div class="add-report-btn" @click="showAddReportPop=true" v-if="isAddReportBtnShow">
         <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
             <path d="M12.0499 15.9499V27.5H15.9499V15.9499H27.5V12.0499H15.9499V0.5H12.0499V12.0499H0.5V15.9499H12.0499Z" fill="white"/>
         </svg>
@@ -532,14 +623,7 @@ async function handleReportEdit(e){
         v-model:show="showAddReportPop"
         cancel-text="取消"
         close-on-click-action
-        :actions="[
-            {
-                name:'研报'
-            },
-            {
-                name:'晨报/周报'
-            }
-        ]"
+        :actions="addReportActions"
         @select="handleSelectAddReportType"
     >
     </van-action-sheet>
@@ -554,13 +638,18 @@ async function handleReportEdit(e){
         <div class="report-item-action-box" v-if="activeItem">
             <!-- <div class="title">{{activeItem.Title}}</div> -->
             <template v-if="activeItem.State==1">
-                <div class="item" @click="handleReportEdit(activeItem)">编辑</div>
-                <div class="item" @click="handleReportPublish(activeItem)">发布</div>
-                <div class="item" @click="handleReportDel(activeItem)">删除</div>
+                <div class="item" @click="handleReportEdit(activeItem)" v-permission="reportManageBtn.reportManage_reportEdit">编辑</div>
+                <div class="item" @click="handleReportPublish(activeItem)" v-permission="reportManageBtn.reportManage_publish">{{isApprove?'提交':'发布'}}</div>
+                <div class="item" @click="handleReportDel(activeItem)" v-permission="reportManageBtn.reportManage_reportDel">删除</div>
+            </template>
+            <template v-if="[2,4].includes(activeItem.State)">
+                <div class="item" @click="handleReportPublishCancle(activeItem)" v-permission="reportManageBtn.reportManage_cancelPublish">{{isApprove?'撤销':'取消发布'}}</div>
+            </template>
+            <template v-if="activeItem.State==4||(activeItem.State==2&&!isApprove)">
+                <div class="item" @click="handldReportMsgSend(activeItem)" v-if="activeItem.MsgIsSend==0&&checkAuthBtn(reportManageBtn.reportManage_sendMsg)">推送消息</div>
             </template>
-            <template v-if="activeItem.State==2">
-                <div class="item" @click="handldReportMsgSend(activeItem)" v-if="activeItem.MsgIsSend==0">推送消息</div>
-                <div class="item" @click="handleReportPublishCancle(activeItem)">取消发布</div>
+            <template v-if="activeItem.State==3">
+                <div class="item" @click="handleReportPublishCancle(activeItem)" v-permission="reportManageBtn.reportManage_cancelPublish">撤销</div>
             </template>
         </div>
     </van-action-sheet>
@@ -607,7 +696,7 @@ async function handleReportEdit(e){
                     <span>日期选择</span>
                 </div>
                 <div class="time-type-box">
-                    <span @click="dateType=1" :class="['item',dateType===1?'active':'']">发布时间</span>
+                    <span @click="dateType=1" :class="['item',dateType===1?'active':'']">{{isApprove?'审批时间':'发布时间'}}</span>
                     <span @click="dateType=2" :class="['item',dateType===2?'active':'']">更新时间</span>
                 </div>
             </template>

+ 3 - 1
src/views/report/PreviewDetail.vue

@@ -4,6 +4,8 @@ import { useRoute, useRouter } from "vue-router";
 import apiReport from '@/api/report'
 import AudioBox from './components/AudioBox.vue'
 import {showToast} from 'vant'
+import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
 
 const route=useRoute()
 const router=useRouter()
@@ -69,7 +71,7 @@ async function goEdit(){
     <div class="report-detail-page" v-if="reportInfo">
         <div class="top-stage-box" v-if="$route.query.id!=-1">
             <span class="stage">第{{reportInfo.Stage}}期 / {{reportInfo.Frequency}}</span>
-            <img v-if="reportInfo.State==1" class="edit-icon" src="@/assets/imgs/report/icon_edit2.png" alt="" @click="goEdit">
+            <img v-if="reportInfo.State==1&&checkAuthBtn(reportManageBtn.reportManage_reportEdit)" class="edit-icon" src="@/assets/imgs/report/icon_edit2.png" alt="" @click="goEdit">
         </div>
         <h1 class="report-title">{{reportInfo.Title}}</h1>
         <div class="auth-box">

+ 20 - 5
src/views/report/chapter/Detail.vue

@@ -12,11 +12,14 @@ import {useUserInfo,isWeiXin} from '@/hooks/common'
 import { showToast,showDialog } from 'vant'
 import {useUploadFileToOSS} from '@/hooks/useUploadFileToOSS'
 import MD5 from 'js-md5'
+import {reportManageBtn} from '@/hooks/useAuthBtn'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
 
 const route=useRoute()
 const router=useRouter()
 
 const userInfo=useUserInfo()
+const publicSettingStore = usePublicSettingStore()
 
 const {lastFocusPosition,initFroalaEditor,imgUploadFlag,frolaEditorContentChange}=useInitFroalaEditor()
 let reportContentEditorIns=null//报告内容编辑器实例
@@ -261,16 +264,18 @@ function handleInsert({list,type,chartType}){
         reportContentEditorIns.selection.get().addRange(lastFocusPosition.value)
     }
     if(type==='iframe'){
-        let link;
+        let link=publicSettingStore.publicSetting.ChartViewUrl;
         if(chartType==='chart'){
-            link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
+            // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
+            link=link+'/chartshow'
             list.forEach(item => {
                 reportContentEditorIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
 						<iframe src='${link}?code=${item}&fromPage=' width='100%' height='350' style='border-width:0px; min-height:350px;'></iframe>
 					</p>`)
             });
         }else if(chartType==='sheet'){
-            link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
+            // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
+            link=link+'/sheetshow'
             list.forEach(item => {
                 reportContentEditorIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
 						<iframe src='${link}?code=${item}' class='iframe${item}'  width='100%' style='border-width:0px;'></iframe>
@@ -312,6 +317,16 @@ async function handleRefreshAllChart(){
         }
     });
     if(!code_arr.length) return showToast('请插入图表');
+    
+    if(route.query.id) {
+        let res = await apiChart.getReportrefreshStatus({
+            Source: 'report',
+            ReportId: Number(route.query.id),
+            ReportChapterId: 0
+        });
+        
+        if(!res.Data.RefreshResult) return showToast('图表正在刷新中,请勿重复操作')
+    }
     const res=await apiChart.refreshChartMultiple({ChartInfoCode:code_arr})
     if(res.Ret===200){
         $('iframe').each((k,i) => {
@@ -444,7 +459,7 @@ async function handleReportOpt(type){
                     <img src="@/assets/imgs/report/icon_refresh.png" alt="">
                     <span>刷新</span>
                 </div>
-                <div class="item" @click="handleReportOpt('yl')">
+                <div class="item" @click="handleReportOpt('yl')" v-permission="reportManageBtn.reportManage_reportView">
                     <img src="@/assets/imgs/report/icon_preview.png" alt="">
                     <span>预览</span>
                 </div>
@@ -452,7 +467,7 @@ async function handleReportOpt(type){
                     <img src="@/assets/imgs/report/icon_save2.png" alt="">
                     <span>保存</span>
                 </div>
-                <div class="item" @click="handleReportOpt('fb')">
+                <div class="item" @click="handleReportOpt('fb')" v-permission="reportManageBtn.reportManage_publish">
                     <img src="@/assets/imgs/report/icon_publish3.png" alt="">
                     <span>发布</span>
                 </div>

+ 19 - 14
src/views/report/components/reportInsert/ETAChart.vue

@@ -4,13 +4,15 @@ import apiChart from '@/api/chart'
 import apiFutureChart from '@/api/futureChart'
 import apiCorrelationChart from '@/api/correlationChart'
 import apiLineEquationChart from '@/api/lineEquationChart'
+import apiStatisticFeatureChart from '@/api/statisticFeatureChart'
+import apiCrossVarietyChart from '@/api/crossVarietyChart'
 import { showToast } from 'vant'
 import { vInfiniteScroll } from '@vueuse/components'
 
 const emits=defineEmits(['update'])
 
 const searchVal=ref('')
-const typeActive=ref('ETA图库')
+const typeActive=ref(1)
 const onlyMe=ref(false)
 
 const listState=reactive({
@@ -29,17 +31,18 @@ async function getChartList(){
         IsShowMe:onlyMe.value
     }
     listState.loading=true
-    let res=null
-    if(typeActive.value==='ETA图库'){
-        res=await apiChart.ETAChartListByES(params)
-    }else if(typeActive.value==='商品价格曲线'){
-        res=await apiFutureChart.searchChartList(params)
-    }else if(typeActive.value==='相关性图表'){
-        res=await apiCorrelationChart.searchChartList(params)
-    }else if(typeActive.value==='拟合方程曲线'){
-        res=await apiLineEquationChart.searchChartList(params)
+
+    const apiMap = {
+        1: apiChart.ETAChartListByES,
+        2: apiFutureChart.searchChartList,
+        3: apiCorrelationChart.searchChartList,
+        6: apiLineEquationChart.searchChartList,
+        7: apiStatisticFeatureChart.searchChart,
+        10: apiCrossVarietyChart.searchChart,
     }
     
+    let res = await apiMap[typeActive.value](params)
+    
     listState.loading=false
     if(res.Ret===200){
         const arr=res.Data.List||[]
@@ -96,10 +99,12 @@ watch(
         <div class="sticky-box">
             <van-search v-model="searchVal" shape="round" placeholder="请输入图表名称" @search="handleRefreshList" @clear="handleRefreshList" />
             <van-tabs v-model:active="typeActive" line-width="16px" title-active-color="#0052D9" @change="handleRefreshList">
-                <van-tab title="ETA图库" name="ETA图库"></van-tab>
-                <van-tab title="商品价格曲线" name="商品价格曲线"></van-tab>
-                <van-tab title="相关性图表" name="相关性图表"></van-tab>
-                <van-tab title="拟合方程曲线" name="拟合方程曲线"></van-tab>
+                <van-tab title="ETA图库" :name="1"></van-tab>
+                <van-tab title="商品价格曲线" :name="2"></van-tab>
+                <van-tab title="相关性图表" :name="3"></van-tab>
+                <van-tab title="拟合方程曲线" :name="6"></van-tab>
+                <van-tab title="统计特征" :name="7"></van-tab>
+                <van-tab title="跨品种分析" :name="10"></van-tab>
             </van-tabs>
             <van-checkbox v-model="onlyMe" @change="handleRefreshList">只看我的</van-checkbox>
         </div>

+ 21 - 6
src/views/reportEn/AddReport.vue

@@ -10,10 +10,13 @@ import { showToast,showDialog } from 'vant'
 import { useRoute, useRouter } from 'vue-router'
 import {useInitFroalaEditor} from '@/hooks/useFroalaEditor'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
+import {enReportManageBtn} from '@/hooks/useAuthBtn'
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
 
 const cachedViewsStore=useCachedViewsStore()
 const router=useRouter()
 const route=useRoute()
+const publicSettingStore = usePublicSettingStore()
 
 const {lastFocusPosition,frolaEditorContentChange,imgUploadFlag,initFroalaEditor}=useInitFroalaEditor()
 
@@ -158,16 +161,18 @@ function handleInsert({list,type,chartType}){
         reportContentIns.selection.get().addRange(lastFocusPosition.value)
     }
     if(type==='iframe'){
-        let link;
+        let link=publicSettingStore.publicSetting.ChartViewUrl;
         if(chartType==='chart'){
-            link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
+            // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/chartshow':'https://charttest.hzinsights.com/chartshow'
+            link=link+'/chartshow'
             list.forEach(item => {
                 reportContentIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
 						<iframe src='${link}?code=${item}&fromPage=en' width='100%' height='350' style='border-width:0px; min-height:350px;'></iframe>
 					</p>`)
             });
         }else if(chartType==='sheet'){
-            link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
+            // link=import.meta.env.MODE==='production'?'https://chartlib.hzinsights.com/sheetshow':'https://charttest.hzinsights.com/sheetshow'
+            link=link+'/sheetshow'
             list.forEach(item => {
                 reportContentIns.html.insert(`<p style='text-align:left; margin-top:10px;'>
 						<iframe src='${link}?code=${item}' class='iframe${item}'  width='100%' style='border-width:0px;'></iframe>
@@ -210,6 +215,16 @@ async function handleRefreshAllChart(){
         }
     });
     if(!code_arr.length) return showToast('请插入图表');
+
+    if(route.query.id) {
+        let res = await apiChart.getReportrefreshStatus({
+            Source: 'report',
+            ReportId: Number(route.query.id),
+            ReportChapterId: 0
+        });
+        
+        if(!res.Data.RefreshResult) return showToast('图表正在刷新中,请勿重复操作')
+    }
     const res=await apiChart.refreshChartMultiple({ChartInfoCode:code_arr})
     if(res.Ret===200){
         $('iframe').each((k,i) => {
@@ -352,7 +367,7 @@ function onConfirmDSFBTime(time){
                     <img src="@/assets/imgs/report/icon_refresh.png" alt="">
                     <span>刷新</span>
                 </div>
-                <div class="item" @click="handleReportOpt('yl')">
+                <div class="item" @click="handleReportOpt('yl')" v-permission="enReportManageBtn.enReport_reportView">
                     <img src="@/assets/imgs/report/icon_preview.png" alt="">
                     <span>预览</span>
                 </div>
@@ -360,11 +375,11 @@ function onConfirmDSFBTime(time){
                     <img src="@/assets/imgs/report/icon_save2.png" alt="">
                     <span>保存</span>
                 </div>
-                <div class="item" @click="handleReportOpt('dsfb')">
+                <div class="item" @click="handleReportOpt('dsfb')" v-permission="enReportManageBtn.enReport_publish">
                     <img src="@/assets/imgs/report/icon_time.png" alt="">
                     <span>定时发布</span>
                 </div>
-                <div class="item" @click="handleReportOpt('fb')">
+                <div class="item" @click="handleReportOpt('fb')" v-permission="enReportManageBtn.enReport_publish">
                     <img src="@/assets/imgs/report/icon_publish3.png" alt="">
                     <span>发布</span>
                 </div>

+ 3 - 1
src/views/reportEn/Detail.vue

@@ -3,6 +3,8 @@ import {ref} from 'vue'
 import { useRoute, useRouter } from "vue-router";
 import apiReportEn from '@/api/reportEn'
 import { showToast,showDialog } from 'vant';
+import {enReportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
 
 const route=useRoute()
 const router=useRouter()
@@ -62,7 +64,7 @@ async function goEdit(){
     <div class="report-detail-page" v-if="reportInfo">
         <div class="top-stage-box" v-if="route.query.id>0">
             <span class="stage">第{{reportInfo.Stage}}期 / {{reportInfo.Frequency}}</span>
-            <img v-if="reportInfo.State==1" class="edit-icon" src="@/assets/imgs/report/icon_edit2.png" alt="" @click="goEdit">
+            <img v-if="reportInfo.State==1&&checkAuthBtn(enReportManageBtn.enReport_reportEdit)" class="edit-icon" src="@/assets/imgs/report/icon_edit2.png" alt="" @click="goEdit">
         </div>
         <h1 class="report-title">{{reportInfo.Title}}</h1>
         <div class="auth-box">

+ 24 - 9
src/views/reportEn/List.vue

@@ -9,8 +9,9 @@ import { showToast,showDialog,Dialog } from 'vant';
 import { useRouter } from 'vue-router';
 import { useWindowSize } from '@vueuse/core'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
-
+import {enReportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
 const cachedViewsStore=useCachedViewsStore()
+const {checkAuthBtn} = useAuthBtn()
 
 const { width, height } = useWindowSize()
 
@@ -188,6 +189,8 @@ function handleConfirmClassify({rootClassify,firstClassify,secondClassify}){
 
 // 跳转详情
 function goDetail(item){
+    //若没有预览权限,则不跳转
+    if(!checkAuthBtn(enReportManageBtn.enReport_reportView)) return 
     router.push({
         path:"/reportEn/detail",
         query:{
@@ -205,6 +208,18 @@ function onLongPressItem(e){
         showToast(`${e.Editor}正在编辑中,不可操作!`)
         return
     }
+    //检验权限,如果该状态下无可操作项,则长按不弹出
+    let checkState = false
+    if(e.State===1){ //编辑、发布、删除
+        checkState = checkAuthBtn(enReportManageBtn.enReport_reportEdit)
+            ||checkAuthBtn(enReportManageBtn.enReport_publish)
+            ||checkAuthBtn(enReportManageBtn.enReport_reportDel)
+    }
+    if(e.State===2){ //群发邮件/群发日志、取消发布
+        checkState = checkAuthBtn(enReportManageBtn.enReport_sendEmail)
+            ||checkAuthBtn(enReportManageBtn.enReport_cancelPublish)
+    }
+    if(!checkState) return
     activeItem.value=e
     showReportItemOpt.value=true
 }
@@ -455,7 +470,7 @@ function handleGoEmailLog(e){
                         </div>
                     </div>
                 </van-dropdown-item>
-                <van-dropdown-item title="群发状态" ref="statusDropMenuIns">
+                <van-dropdown-item title="群发状态" ref="statusDropMenuIns" v-if="checkAuthBtn(enReportManageBtn.enReport_sendEmail)">
                     <div class="report-status-box">
                         <ul>
                             <li 
@@ -535,7 +550,7 @@ function handleGoEmailLog(e){
     </div>
 
     <!-- 添加报告按钮 -->
-    <div class="add-report-btn" @click="$router.push('/reportEn/add')">
+    <div class="add-report-btn" @click="$router.push('/reportEn/add')" v-if="checkAuthBtn(enReportManageBtn.enReport_reportAdd)">
         <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
             <path d="M12.0499 15.9499V27.5H15.9499V15.9499H27.5V12.0499H15.9499V0.5H12.0499V12.0499H0.5V15.9499H12.0499Z" fill="white"/>
         </svg>
@@ -551,14 +566,14 @@ function handleGoEmailLog(e){
         <div class="report-item-action-box" v-if="activeItem">
             <!-- <div class="title">{{activeItem.Title}}</div> -->
             <template v-if="activeItem.State==1">
-                <div class="item" @click="handleReportEdit(activeItem)" v-if="activeItem.CanEdit">编辑</div>
-                <div class="item" @click="handleReportPublish(activeItem)">发布</div>
-                <div class="item" @click="handleReportDel(activeItem)">删除</div>
+                <div class="item" @click="handleReportEdit(activeItem)" v-if="activeItem.CanEdit&&checkAuthBtn(enReportManageBtn.enReport_reportEdit)">编辑</div>
+                <div class="item" @click="handleReportPublish(activeItem)" v-permission="enReportManageBtn.enReport_publish">发布</div>
+                <div class="item" @click="handleReportDel(activeItem)" v-permission="enReportManageBtn.enReport_reportDel">删除</div>
             </template>
             <template v-if="activeItem.State==2">
-                <div class="item" @click="showSendEmail=true;showReportItemOpt=false" v-if="activeItem.EmailState===0&&activeItem.EmailAuth">群发邮件</div>
-                <div class="item" @click="handleGoEmailLog(activeItem)" v-if="activeItem.EmailState===1&&activeItem.EmailAuth">群发日志</div>
-                <div class="item" @click="handleReportPublishCancle(activeItem)">取消发布</div>
+                <div class="item" @click="showSendEmail=true;showReportItemOpt=false" v-if="activeItem.EmailState===0&&activeItem.EmailAuth&&checkAuthBtn(enReportManageBtn.enReport_sendEmail)">群发邮件</div>
+                <div class="item" @click="handleGoEmailLog(activeItem)" v-if="activeItem.EmailState===1&&activeItem.EmailAuth&&checkAuthBtn(enReportManageBtn.enReport_sendEmail)">群发日志</div>
+                <div class="item" @click="handleReportPublishCancle(activeItem)" v-permission="enReportManageBtn.enReport_cancelPublish">取消发布</div>
             </template>
         </div>
     </van-action-sheet>

+ 121 - 44
src/views/reportEn/components/SendEmail.vue

@@ -18,6 +18,7 @@ const emits=defineEmits(['close'])
 
 
 const emailOpt=ref([])
+const allEmailOpt = ref([])
 // 获取用户邮箱数据
 const showAllUser=ref(false)
 function getEmailList(){
@@ -30,6 +31,7 @@ function getEmailList(){
     }).then(res=>{
         if(res.Ret===200){
             emailOpt.value=res.Data?.List||[]
+            allEmailOpt.value = res.Data?.List||[]
             // handleCheckAllChange()
         }
     })
@@ -39,6 +41,7 @@ getEmailList()
 //获取报告对应的品种数据
 const showSelectVariety=ref(false)
 const varietyOpts=ref([])
+const isSlideCancelSend = ref(false);
 function getVarietyList(){
     apiReportVarietyEn.varietyList({
          ReportId:props.reportId
@@ -58,6 +61,16 @@ function getVarietyList(){
 }
 getVarietyList()
 
+function clearVariety(item) {
+    item.select=false
+
+    if(!selectVariety.value.length) {
+        isSlideCancelSend.value = false
+        searchCustomVal.value=''
+        checkedCustom.value=[]
+    }
+}
+
 //选中的品种
 const selectVariety=computed(()=>{
     return varietyOpts.value.filter(e=>e.select)
@@ -78,7 +91,12 @@ const disabledNum=computed(()=>{
 })
 const checked=ref([])//选择的用户
 const checkedAll=ref(false)
-
+const checkedSendOtherList = ref([]);
+const searchVal=ref('')
+function handleSearchFilter(){
+    checked.value=[]
+    emailOpt.value=allEmailOpt.value.filter(item=>item.Name.indexOf(searchVal.value)>-1)
+}
 function handleCheckAllChange(){
     if(checkedAll.value){
         checked.value=[]
@@ -90,33 +108,51 @@ function handleCheckAllChange(){
 watch(
     ()=>checked.value,
     (n)=>{
-        if(n.length===(emailOpt.value.length-disabledNum.value)){
+        if(n.length&&n.length===(emailOpt.value.length-disabledNum.value)){
             checkedAll.value=true
         }else{
             checkedAll.value=false
         }
+        checkedSendOtherList.value = emailOpt.value.filter(_ => n.includes(_.Id))
+    }
+)
+
+watch(
+    ()=>selectVariety.value,
+    (n)=>{
+        n.length&&getCustomListEnFun()
     }
 )
 
 
-const showSearch=ref(false)
-const searchVal=ref('')
-const searchFilterOpt=ref([])
-const checkedFilter=ref([])
-function handleShowSearch(){
-    checkedFilter.value=checked.value
-    searchVal.value=''
-    searchFilterOpt.value=[]
-    showSearch.value=true
-}
-function handleSearchFilter(){
-    searchFilterOpt.value=emailOpt.value.filter(item=>item.Name.indexOf(searchVal.value)>-1)
+const showCustomPop=ref(false)
+const searchCustomVal=ref('')
+const checkedCustom=ref([])
+const allCustomOptions=ref([])
+const customOptions = ref([])
+/* 品种关联的客户 */
+async function getCustomListEnFun(){
+    if(!selectVariety.value.length) return
+
+    const res = await apiReportEn.getCustomListEn({
+        PageSize:100000,
+        CurrentIndex:1,
+        EnPermissionIds:selectVariety.value.map(_ =>_.EnPermissionId).join(',')
+    })
+    if(res.Ret!==200) return 
+    customOptions.value = res.Data.List || []
+    allCustomOptions.value = res.Data.List || []
 }
-function handleConfirmSearch(){
-    checked.value=checkedFilter.value
-    showSearch.value=false
+
+function handleSearchFilterCustom() {
+    checkedCustom.value=[]
+    customOptions.value=allCustomOptions.value.filter(item=>item.CompanyName.indexOf(searchCustomVal.value)>-1)
 }
 
+const checkedCustomList = computed(() => {
+    return customOptions.value.filter(_ => checkedCustom.value.includes(_.CompanyId))
+})
+
 
 
 // 发送邮件
@@ -129,6 +165,10 @@ function handleConfirmSendEmail(){
         showToast('邮件主题请控制在100个字符以内')
         return
     }
+
+    if(!selectVariety.value.length && !checked.value.length) return showToast('收件人、抄送至少填写一个')
+
+
     const varietyIds=[]
     selectVariety.value.forEach(item=>{
         varietyIds.push(item.EnPermissionId)
@@ -137,7 +177,8 @@ function handleConfirmSendEmail(){
         ReportId:props.reportId,
         EmailIds:checked.value.join(','),
         Theme:themeVal.value,
-        EnPermissions:varietyIds
+        EnPermissions:varietyIds,
+        NoCompanyIds:checkedCustom.value,
     }).then(res=>{
         if(res.Ret===200){
             showToast('发送成功')
@@ -152,15 +193,48 @@ function handleConfirmSendEmail(){
 <template>
     <div class="send-email-wrap">
         <div class="top-box">
+            <label class="form-label">收件人</label>
             <ul class="left-variety-box">
                 <li v-for="item in selectVariety" :key="item.EnPermissionId">
                     <span>{{item.EnPermissionName}}</span>
-                    <img class="icon" src="@/assets/imgs/icon_close.png" alt="" @click="item.select=false">
+                    <img class="icon" src="@/assets/imgs/icon_close.png" alt="" @click="clearVariety(item)">
                 </li>
             </ul>
-            <van-search readonly placeholder="搜索" @click="handleShowSearch" />
+            <!-- <van-search readonly placeholder="搜索" @click="handleShowSearch" /> -->
             <img class="add-icon" src="@/assets/imgs/report/icon_add.png" alt="" @click="showSelectVariety=true">
-            <img class="add-icon" src="@/assets/imgs/icon_user2.png" alt="" @click="showAllUser=true">
+
+            <van-icon :name="isSlideCancelSend?'arrow-up':'arrow-down'" style="margin-left:10px;font-size:24px" v-if="selectVariety.length" @click="isSlideCancelSend=!isSlideCancelSend"/>
+        </div>
+        <div class="top-box" v-if="isSlideCancelSend">
+            <label class="form-label">取消发送</label>
+            <ul class="left-variety-box">
+                <li v-for="(item,index) in checkedCustomList" :key="item.CompanyId">
+                    <span>{{item.CompanyName}}</span>
+                    <img class="icon" src="@/assets/imgs/icon_close.png" alt="" @click="checkedCustom.splice(index,1)">
+                </li>
+            </ul>
+            <svg xmlns="http://www.w3.org/2000/svg" width="35" height="35" viewBox="0 0 48 48" fill="none" @click="showCustomPop=true;">
+                <rect width="48" height="48" rx="24" fill="#0052D9"/>
+                <path d="M16.75 17.3043C16.75 13.2703 19.94 10 23.875 10C27.81 10 31 13.2703 31 17.3043C31 21.3384 27.81 24.6087 23.875 24.6087C19.94 24.6087 16.75 21.3384 16.75 17.3043ZM28.625 17.3043C28.625 14.615 26.4984 12.4348 23.875 12.4348C21.2516 12.4348 19.125 14.615 19.125 17.3043C19.125 19.9937 21.2516 22.1739 23.875 22.1739C26.4984 22.1739 28.625 19.9937 28.625 17.3043Z" fill="white"/>
+                <path d="M23.875 25.8261C25.5152 25.8261 27.1068 26.0391 28.625 26.4397V28.9667C27.1199 28.5075 25.5256 28.2609 23.875 28.2609C20.3428 28.2609 17.0679 29.3902 14.375 31.3158L14.375 35.5652H28.625V38H14.375C13.0633 38 12 36.9099 12 35.5652V31.1249C12 30.2117 12.4984 29.3753 13.2909 28.9585L14.6644 28.2609H14.6695C17.3966 26.7095 20.5352 25.8261 23.875 25.8261Z" fill="white"/>
+                <rect x="27" y="30" width="10" height="3" fill="white"/>
+            </svg>
+        </div>
+        <div class="top-box">
+            <label class="form-label">抄送</label>
+            <ul class="left-variety-box">
+                <li v-for="(item,index) in checkedSendOtherList" :key="item.Id">
+                    <span>{{item.Name}}</span>
+                    <img class="icon" src="@/assets/imgs/icon_close.png" alt="" @click="checkedSendOtherList.splice(index,1)">
+                </li>
+            </ul>
+        
+            <svg xmlns="http://www.w3.org/2000/svg" width="35" height="35" viewBox="0 0 48 48" fill="none" @click="showAllUser=true;checked=checkedSendOtherList.map(_ =>_.Id)">
+                <rect width="48" height="48" rx="24" fill="#0052D9"/>
+                <path d="M16.7273 17.3043C16.7273 13.2703 19.902 10 23.8182 10C27.7344 10 30.9091 13.2703 30.9091 17.3043C30.9091 21.3384 27.7344 24.6087 23.8182 24.6087C19.902 24.6087 16.7273 21.3384 16.7273 17.3043ZM28.5455 17.3043C28.5455 14.615 26.429 12.4348 23.8182 12.4348C21.2074 12.4348 19.0909 14.615 19.0909 17.3043C19.0909 19.9937 21.2074 22.1739 23.8182 22.1739C26.429 22.1739 28.5455 19.9937 28.5455 17.3043Z" fill="white"/>
+                <path d="M23.8182 25.8261C25.4505 25.8261 27.0345 26.0391 28.5455 26.4397V28.9667C27.0476 28.5075 25.4608 28.2609 23.8182 28.2609C20.3029 28.2609 17.0436 29.3902 14.3636 31.3158L14.3636 35.5652H28.5455V38H14.3636C13.0582 38 12 36.9099 12 35.5652V31.1249C12 30.2117 12.496 29.3753 13.2847 28.9585L14.6517 28.2609H14.6567C17.3708 26.7095 20.4943 25.8261 23.8182 25.8261Z" fill="white"/>
+                <path d="M33.2727 25.8261V30.6957H38V33.1304H33.2727V38H30.9091V33.1304H26.1818V30.6957H30.9091V25.8261H33.2727Z" fill="white"/>
+            </svg>
         </div>
         <div class="theme-text-box">
             <van-field 
@@ -168,7 +242,7 @@ function handleConfirmSendEmail(){
                 label="主题" 
                 type="textarea"
                 placeholder="请输入主题"
-                rows="3"
+                rows="4"
                 maxlength="100"
                 show-word-limit
                 label-width="40px"
@@ -214,7 +288,7 @@ function handleConfirmSendEmail(){
             </div>
         </div>
     </van-popup>
-    <!-- 所有客户 -->
+    <!-- 所有邮箱 -->
     <van-popup 
         v-model:show="showAllUser"
         position="bottom"
@@ -223,6 +297,9 @@ function handleConfirmSendEmail(){
     >
         <div class="send-email-wrap search-wrap">
             <div class="head-box">选择联系人</div>
+            <div class="search-box">
+                <van-search v-model="searchVal" placeholder="搜索" @search="handleSearchFilter" @clear="handleSearchFilter"/>
+            </div>
             <div class="list-wrap">
                 <div class="check-all-btn">
                     <van-checkbox :modelValue="checkedAll" @click="handleCheckAllChange">全选</van-checkbox>
@@ -238,32 +315,29 @@ function handleConfirmSendEmail(){
             </div>
         </div>
     </van-popup>
-    <!-- 搜索客户 -->
+
+    <!-- 英文客户列表 -->
     <van-popup 
-        v-model:show="showSearch"
+        v-model:show="showCustomPop"
         position="bottom"
         round
+        closeable
     >
         <div class="send-email-wrap search-wrap">
-            <div class="top-box">
-                <van-search v-model="searchVal" placeholder="搜索" @search="handleSearchFilter" />
+            <div class="head-box">选择客户</div>
+            <div class="search-box">
+                <van-search v-model="searchCustomVal" placeholder="请输入客户名称" @search="handleSearchFilterCustom" @clear="handleSearchFilterCustom"/>
             </div>
             <div class="list-wrap">
-                <van-checkbox-group v-model="checkedFilter">
+                <van-checkbox-group v-model="checkedCustom">
                     <van-checkbox
-                        v-for="item in searchFilterOpt" 
-                        :key="item.Id"
-                        :name="item.Id" 
+                        v-for="item in customOptions" 
+                        :key="item.CompanyId"
+                        :name="item.CompanyId" 
                         :disabled="item.Enabled===0"
-                    >{{item.Name}}&lt;{{item.Email}}&gt;</van-checkbox>
+                    >{{item.CompanyName}}</van-checkbox>
                 </van-checkbox-group>
             </div>
-            <van-button 
-                class="submit-btn" 
-                block 
-                type="primary"
-                @click="handleConfirmSearch"
-            >完成</van-button>
         </div>
     </van-popup>
 </template>
@@ -275,11 +349,12 @@ function handleConfirmSendEmail(){
     display: flex;
     flex-direction: column;
     .top-box{
-        padding: 0 30px;
-        min-height: 128px;
+        padding: 20px 30px;
+        min-height: 100px;
         display: flex;
         align-items: center;
         border-bottom: 1px solid $border-color;
+        .form-label { margin-right: 10px;}
         .left-variety-box{
             flex: 1;
             display: flex;
@@ -288,7 +363,7 @@ function handleConfirmSendEmail(){
             position: relative;
             padding-right: 10px;
             margin-right: 10px;
-            &::after{
+            /* &::after{
                 content:'';
                 display: block;
                 width: 4px;
@@ -298,7 +373,7 @@ function handleConfirmSendEmail(){
                 top: 50%;
                 transform: translateY(-50%);
                 right: 0;
-            }
+            } */
             li{
                 padding: 8px 20px;
                 border-radius: 80px;
@@ -354,6 +429,7 @@ function handleConfirmSendEmail(){
 .search-wrap{
     padding-top: 0;
     .head-box{
+        position: relative;
         text-align: center;
         line-height: 100px;
         border-bottom: 1px solid $border-color;
@@ -370,13 +446,14 @@ function handleConfirmSendEmail(){
         .top-box{
             padding: 0 15px;
             min-height: 64px;
+            .form-label { margin-right: 10px;}
             .left-variety-box{
                 padding-right: 5px;
                 margin-right: 5px;
-                &::after{
+                /* &::after{
                     width: 2px;
                     height: 20px;
-                }
+                } */
                 li{
                     padding: 4px 10px;
                     border-radius: 40px;

+ 21 - 0
vite.config.js

@@ -5,6 +5,9 @@ import Components from "unplugin-vue-components/vite";
 import { VantResolver } from "unplugin-vue-components/resolvers";
 import VueSetupExtend from 'vite-plugin-vue-setup-extend'
 import {createSvgIconsPlugin} from 'vite-plugin-svg-icons'
+import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'
+import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill'
+import rollupNodePolyFill from 'rollup-plugin-node-polyfills'
 
 // https://vitejs.dev/config/
 export default ({ mode }) =>
@@ -47,10 +50,28 @@ export default ({ mode }) =>
     resolve: {
       alias: {
         "@": path.resolve(__dirname, "./src"),
+        events: 'rollup-plugin-node-polyfills/polyfills/events',
+        stream: 'rollup-plugin-node-polyfills/polyfills/stream',
+        buffer: 'rollup-plugin-node-polyfills/polyfills/buffer-es6',
+        process: 'rollup-plugin-node-polyfills/polyfills/process-es6'
       }
     },
+    optimizeDeps: {
+        esbuildOptions: {
+          // Enable esbuild polyfill plugins
+          plugins: [
+            NodeModulesPolyfillPlugin()
+          ]
+        }
+      },
     build: {
       outDir: loadEnv(mode, process.cwd()).VITE_APP_OUTDIR,
+      rollupOptions: {
+        plugins: [
+          // Enable rollup polyfills plugin used during production bundling
+          rollupNodePolyFill()
+        ]
+      }
     },
     server:{
       host:true