king
2022-05-17 2ae980243b7ad705dea575eadcfc4cf4e24073bd
Merge branch 'master' into positec
142个文件已修改
15个文件已添加
7361 ■■■■ 已修改文件
package-lock.json 366 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/admin.html 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/w4k.js 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/css/main.scss 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/header/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/dragaction/action.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/elementform/index.jsx 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/formconfig.jsx 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/index.jsx 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/data-card/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/prop-card/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/table-card/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/carousel/data-card/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/carousel/prop-card/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx 188 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/chartcompile/index.jsx 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/chartcompile/index.scss 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-dashboard/chartcompile/formconfig.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-dashboard/index.jsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-pie/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-scatter/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/chart-custom/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/form/formaction/actionform/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/form/formaction/formconfig.jsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/form/formaction/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/form/normal-form/index.jsx 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/form/tab-form/index.jsx 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/search/main-search/dragsearch/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/search/main-search/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/actionform/index.jsx 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/dragaction/card.jsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/formconfig.jsx 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/index.jsx 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/clockcomponent/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/clockcomponent/index.scss 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/logcomponent/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/logcomponent/index.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/normalform/index.jsx 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/normalform/index.scss 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/columns/index.jsx 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/columns/index.scss 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/index.jsx 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/options.jsx 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/normal-table/columns/index.jsx 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/normal-table/columns/index.scss 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/normal-table/index.jsx 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/normal-table/options.jsx 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/timeline/normal-timeline/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/tree/antd-tree/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modalconfig/index.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/picturecontroller/editform/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/picturecontroller/index.jsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/popview/index.jsx 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/popview/index.scss 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/stylecontroller/index.jsx 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/viewnodes/index.jsx 344 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/viewnodes/index.scss 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/common-menubar/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/menucomponent/options.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/search/single-search/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/modalconfig/index.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pc/components/login/normal-login/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/setupProxy.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/commontable/index.jsx 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/balcony/index.jsx 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/cardcellList/index.jsx 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/data-card/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/prop-card/index.jsx 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/table-card/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/carousel/data-card/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/carousel/prop-card/index.jsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/chart/antv-bar-line/index.jsx 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/chart/antv-dashboard/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/chart/antv-pie/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/chart/antv-scatter/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/chart/custom-chart/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/code/sand-box/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/editor/braft-editor/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/form/normal-form/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/form/tab-form/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/normalTable/index.jsx 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/normalTable/index.scss 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/edit-table/index.jsx 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/edit-table/normalTable/index.jsx 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/edit-table/normalTable/index.scss 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/normal-table/index.jsx 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/timeline/normal-timeline/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/tree/antd-tree/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/subtable/index.jsx 68 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/subtabtable/index.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/excelInbutton/excelin/index.jsx 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/excelInbutton/index.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/exceloutbutton/index.jsx 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/funcMegvii/index.jsx 546 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/funcMegvii/index.scss 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/funcMegvii/mock.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/index.jsx 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/normalbutton/index.jsx 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/popupbutton/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/automatic/index.jsx 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/fileupload-pice/index.jsx 498 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/fileupload-pice/index.scss 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/fileupload/index.jsx 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/fileupload/index.scss 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/normalTable/index.jsx 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/comtableconfig/index.jsx 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/formtabconfig/index.jsx 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/menuconfig/editthdmenu/index.jsx 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/menuconfig/editthdmenu/index.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/menuconfig/menuelement/card.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/modalconfig/index.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/actionform/index.jsx 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/dragaction/card.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/index.jsx 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/index.scss 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelin/customscript/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelin/index.jsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelout/customscript/index.jsx 342 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelout/customscript/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelout/datasource/index.jsx 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelout/datasource/index.scss 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelout/index.jsx 612 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelout/index.scss 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelout/utils.jsx 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifymegvii/index.jsx 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifymegvii/index.scss 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/chartgroupcomponent/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcalcomponent/verifycard/index.jsx 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/index.jsx 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/settingform/datasource/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/settingform/index.jsx 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/index.jsx 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/createinterface/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/formconfig.jsx 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/unattended/settingform/index.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/option.js 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils-custom.js 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils-update.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils.js 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/billprint/index.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/design/header/index.jsx 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.jsx 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.scss 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobdesign/index.jsx 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobdesign/index.scss 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/pcdesign/index.jsx 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/pcdesign/index.scss 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/rolemanage/index.jsx 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -108,6 +108,22 @@
        "tslib": "^1.10.0"
      }
    },
    "@antv/algorithm": {
      "version": "0.1.23",
      "resolved": "https://registry.npmjs.org/@antv/algorithm/-/algorithm-0.1.23.tgz",
      "integrity": "sha512-ncAfXwpO9angkGlZ/4LYsFk+T2hvQI6A+mEEGMslj8wF7ddPdtZ+DK3KFbhfJvQ4iz9Z44sJfdJ/fybwt0g0kA==",
      "requires": {
        "@antv/util": "^2.0.13",
        "tslib": "^2.0.0"
      },
      "dependencies": {
        "tslib": {
          "version": "2.4.0",
          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
          "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
        }
      }
    },
    "@antv/attr": {
      "version": "0.3.2",
      "resolved": "https://registry.npmjs.org/@antv/attr/-/attr-0.3.2.tgz",
@@ -309,6 +325,55 @@
        }
      }
    },
    "@antv/g-webgpu": {
      "version": "0.5.5",
      "resolved": "https://registry.npmjs.org/@antv/g-webgpu/-/g-webgpu-0.5.5.tgz",
      "integrity": "sha512-TxtBniINFq1jFGEPo46xjJfrbJbUqkFd5wmsRs3tcg/7J7xoldOP1kEadpI3AJG9knMYdE92VpILw1VPd6DgzQ==",
      "requires": {
        "@antv/g-webgpu-core": "^0.5.5",
        "@antv/g-webgpu-engine": "^0.5.5",
        "@webgpu/types": "^0.0.31",
        "gl-matrix": "^3.1.0",
        "gl-vec2": "^1.3.0",
        "hammerjs": "^2.0.8",
        "inversify": "^5.0.1",
        "inversify-inject-decorators": "^3.1.0",
        "polyline-miter-util": "^1.0.1",
        "polyline-normals": "^2.0.2",
        "probe.gl": "^3.1.1",
        "reflect-metadata": "^0.1.13"
      }
    },
    "@antv/g-webgpu-core": {
      "version": "0.5.6",
      "resolved": "https://registry.npmjs.org/@antv/g-webgpu-core/-/g-webgpu-core-0.5.6.tgz",
      "integrity": "sha512-DPiH3GkAUiT0Q+LAKeImpI+IOQ/gP2w6HstYKivpFIpBPIvZ/9equM3icVrn1iDfDkZANVXQ1PppcO3xBv1ZTw==",
      "requires": {
        "eventemitter3": "^4.0.0",
        "gl-matrix": "^3.1.0",
        "inversify": "^5.0.1",
        "inversify-inject-decorators": "^3.1.0",
        "probe.gl": "^3.1.1",
        "reflect-metadata": "^0.1.13"
      }
    },
    "@antv/g-webgpu-engine": {
      "version": "0.5.6",
      "resolved": "https://registry.npmjs.org/@antv/g-webgpu-engine/-/g-webgpu-engine-0.5.6.tgz",
      "integrity": "sha512-D311qYUefdEFwLayutIHqucrAY3cAGH3BdnXS37nq+0nsglrHcNP0Ab1YTinn9RihLoY3yXFTLzrYkJHJbZXDg==",
      "requires": {
        "@antv/g-webgpu-core": "^0.5.6",
        "@webgpu/glslang": "^0.0.15",
        "@webgpu/types": "^0.0.31",
        "gl-matrix": "^3.1.0",
        "hammerjs": "^2.0.8",
        "inversify": "^5.0.1",
        "inversify-inject-decorators": "^3.1.0",
        "probe.gl": "^3.1.1",
        "reflect-metadata": "^0.1.13",
        "regl": "^1.3.11"
      }
    },
    "@antv/g2": {
      "version": "4.1.34",
      "resolved": "https://registry.npmjs.org/@antv/g2/-/g2-4.1.34.tgz",
@@ -338,12 +403,151 @@
        }
      }
    },
    "@antv/g6": {
      "version": "4.6.4",
      "resolved": "https://registry.npmjs.org/@antv/g6/-/g6-4.6.4.tgz",
      "integrity": "sha512-8xU96NISJ7G3Gwo6m1BTtqzfBeRd23BWcLDIpwX4v1KoE3NMHZlJkUaNvnKsoeoEoLNCDYBvTfBfEwV4C5gWfw==",
      "requires": {
        "@antv/g6-pc": "0.6.4"
      }
    },
    "@antv/g6-core": {
      "version": "0.6.4",
      "resolved": "https://registry.npmjs.org/@antv/g6-core/-/g6-core-0.6.4.tgz",
      "integrity": "sha512-8JYIZME7zaQJImlPOatmlTe/tKPxRaq8/wV2f61/7mfKON3LF9kEVvTrpJexMnXoCBuM+puxx3DbiPb64gL4LQ==",
      "requires": {
        "@antv/algorithm": "^0.1.8",
        "@antv/dom-util": "^2.0.1",
        "@antv/event-emitter": "~0.1.0",
        "@antv/g-base": "^0.5.1",
        "@antv/g-math": "^0.1.1",
        "@antv/matrix-util": "^3.1.0-beta.3",
        "@antv/path-util": "^2.0.3",
        "@antv/util": "~2.0.5",
        "ml-matrix": "^6.5.0",
        "tslib": "^2.1.0"
      },
      "dependencies": {
        "tslib": {
          "version": "2.4.0",
          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
          "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
        }
      }
    },
    "@antv/g6-element": {
      "version": "0.6.4",
      "resolved": "https://registry.npmjs.org/@antv/g6-element/-/g6-element-0.6.4.tgz",
      "integrity": "sha512-9wKogi6LnA4IoiVEWv8HmvHgHOXzbT73jDi4QrdSWjrttu7cyR2wQeKRYDp2lL2OPkHzzuNcI+ULQxbLKl3Oqg==",
      "requires": {
        "@antv/g-base": "^0.5.1",
        "@antv/g6-core": "0.6.4",
        "@antv/util": "~2.0.5"
      }
    },
    "@antv/g6-pc": {
      "version": "0.6.4",
      "resolved": "https://registry.npmjs.org/@antv/g6-pc/-/g6-pc-0.6.4.tgz",
      "integrity": "sha512-bEQ3pqHzkqDvk2aWRghj9cB73qXx0n4NRJFmL4cSJnB1SQyk7akfBHgLBzw5aqsYviL9WK7VR6uCCZRPK3PoXA==",
      "requires": {
        "@ant-design/colors": "^4.0.5",
        "@antv/algorithm": "^0.1.8",
        "@antv/dom-util": "^2.0.1",
        "@antv/event-emitter": "~0.1.0",
        "@antv/g-base": "^0.5.1",
        "@antv/g-canvas": "^0.5.2",
        "@antv/g-math": "^0.1.1",
        "@antv/g-svg": "^0.5.1",
        "@antv/g6-core": "0.6.4",
        "@antv/g6-element": "0.6.4",
        "@antv/g6-plugin": "0.6.4",
        "@antv/hierarchy": "^0.6.7",
        "@antv/layout": "^0.2.1",
        "@antv/matrix-util": "^3.1.0-beta.3",
        "@antv/path-util": "^2.0.3",
        "@antv/util": "~2.0.5",
        "color": "^3.1.3",
        "d3-force": "^2.0.1",
        "dagre": "^0.8.5",
        "insert-css": "^2.0.0",
        "ml-matrix": "^6.5.0"
      },
      "dependencies": {
        "@ant-design/colors": {
          "version": "4.0.5",
          "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-4.0.5.tgz",
          "integrity": "sha512-3mnuX2prnWOWvpFTS2WH2LoouWlOgtnIpc6IarWN6GOzzLF8dW/U8UctuvIPhoboETehZfJ61XP+CGakBEPJ3Q==",
          "requires": {
            "tinycolor2": "^1.4.1"
          }
        },
        "@antv/hierarchy": {
          "version": "0.6.8",
          "resolved": "https://registry.npmjs.org/@antv/hierarchy/-/hierarchy-0.6.8.tgz",
          "integrity": "sha512-wVzUl+pxny5gyGJ2mkWx8IiEypX6bnMHgr/NILgbxY6shoy0Vf4FhZpI3CY8Ez7bQT6js8fMkB2NymPW7d7i8A==",
          "requires": {
            "@antv/util": "^2.0.7"
          }
        },
        "color": {
          "version": "3.2.1",
          "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
          "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
          "requires": {
            "color-convert": "^1.9.3",
            "color-string": "^1.6.0"
          }
        },
        "color-string": {
          "version": "1.9.1",
          "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
          "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
          "requires": {
            "color-name": "^1.0.0",
            "simple-swizzle": "^0.2.2"
          }
        }
      }
    },
    "@antv/g6-plugin": {
      "version": "0.6.4",
      "resolved": "https://registry.npmjs.org/@antv/g6-plugin/-/g6-plugin-0.6.4.tgz",
      "integrity": "sha512-eStD6j3nB54owXdYcA6/vxVCxJfBiMUfc33p4cKnRJnomSvEHYG/7i21SbyzMQQq7dRc0RDIPubYVwSilKzLCA==",
      "requires": {
        "@antv/dom-util": "^2.0.2",
        "@antv/g-base": "^0.5.1",
        "@antv/g-canvas": "^0.5.2",
        "@antv/g-svg": "^0.5.2",
        "@antv/g6-core": "0.6.4",
        "@antv/matrix-util": "^3.1.0-beta.3",
        "@antv/scale": "^0.3.4",
        "@antv/util": "^2.0.9",
        "insert-css": "^2.0.0"
      }
    },
    "@antv/graphlib": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/@antv/graphlib/-/graphlib-1.2.0.tgz",
      "integrity": "sha512-hhJOMThec51nU4Fe5p/viLlNIL71uDEgYFzKPajWjr2715SFG1HAgiP6AVylIeqBcAZ04u3Lw7usjl/TuI5RuQ=="
    },
    "@antv/hierarchy": {
      "version": "0.6.3",
      "resolved": "https://registry.npmjs.org/@antv/hierarchy/-/hierarchy-0.6.3.tgz",
      "integrity": "sha512-91YZUiZFXK8zp2nC2C+4FEc1LDIPZ5Q4YQbzMnKhH+7nei4QCfIdXPrPh0EKRws78CVt2hxR5gHD7zq6UlokAQ==",
      "requires": {
        "@antv/util": "^2.0.7"
      }
    },
    "@antv/layout": {
      "version": "0.2.2",
      "resolved": "https://registry.npmjs.org/@antv/layout/-/layout-0.2.2.tgz",
      "integrity": "sha512-J2qRZwhA6NbR3gPrbw8Vw+6TpXtMmpW86q8LnwtCnNvdRNQQ7wSfvWAiphtFRfHqwL9ky44J51v4ysCENgTGDQ==",
      "requires": {
        "@antv/g-webgpu": "0.5.5",
        "@antv/graphlib": "^1.0.0",
        "d3-force": "^2.0.1",
        "dagre-compound": "^0.0.11",
        "ml-matrix": "^6.5.0"
      }
    },
    "@antv/matrix-util": {
@@ -1910,6 +2114,31 @@
      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
      "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
    },
    "@probe.gl/env": {
      "version": "3.5.0",
      "resolved": "https://registry.npmjs.org/@probe.gl/env/-/env-3.5.0.tgz",
      "integrity": "sha512-YdlpZZshhyYxvWDBmZ5RIW2pTR14Pw4p9czMlt/v7F6HbFzWfAdmH7q6xVwFRYxUpQLwhWensWyv4aFysiWl4g==",
      "requires": {
        "@babel/runtime": "^7.0.0"
      }
    },
    "@probe.gl/log": {
      "version": "3.5.0",
      "resolved": "https://registry.npmjs.org/@probe.gl/log/-/log-3.5.0.tgz",
      "integrity": "sha512-nW/qz2X1xY08WU/TsmJP6/6IPNcaY5fS/vLjpC4ahJuE2Mezga4hGM/R2X5JWE/nkPc+BsC5GnAnD13rwAxS7g==",
      "requires": {
        "@babel/runtime": "^7.0.0",
        "@probe.gl/env": "3.5.0"
      }
    },
    "@probe.gl/stats": {
      "version": "3.5.0",
      "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-3.5.0.tgz",
      "integrity": "sha512-IH2M+F3c8HR1DTroBARePUFG7wIewumtKA0UFqx51Z7S4hKrD60wFbpMmg0AcF4FvHAXMBoC+kYi1UKW9XbAOw==",
      "requires": {
        "@babel/runtime": "^7.0.0"
      }
    },
    "@react-native-community/cli-debugger-ui": {
      "version": "4.9.0",
      "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-4.9.0.tgz",
@@ -2954,6 +3183,16 @@
        "@webassemblyjs/wast-parser": "1.8.5",
        "@xtuc/long": "4.2.2"
      }
    },
    "@webgpu/glslang": {
      "version": "0.0.15",
      "resolved": "https://registry.npmjs.org/@webgpu/glslang/-/glslang-0.0.15.tgz",
      "integrity": "sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q=="
    },
    "@webgpu/types": {
      "version": "0.0.31",
      "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.0.31.tgz",
      "integrity": "sha512-cvvCMSZBT4VsRNtt0lI6XQqvOIIWw6+NRUtnPUMDVDgsI4pCZColz3qzF5QcP9wIYOHEc3jssIBse8UWONKhlQ=="
    },
    "@xmldom/xmldom": {
      "version": "0.7.5",
@@ -5649,6 +5888,11 @@
        "randomfill": "^1.0.3"
      }
    },
    "crypto-js": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
      "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
    },
    "css": {
      "version": "2.2.4",
      "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
@@ -5979,6 +6223,11 @@
        }
      }
    },
    "d3-dispatch": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz",
      "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA=="
    },
    "d3-dsv": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz",
@@ -5993,6 +6242,16 @@
      "version": "1.0.7",
      "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz",
      "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ=="
    },
    "d3-force": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz",
      "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==",
      "requires": {
        "d3-dispatch": "1 - 2",
        "d3-quadtree": "1 - 2",
        "d3-timer": "1 - 2"
      }
    },
    "d3-geo": {
      "version": "1.6.4",
@@ -6038,6 +6297,11 @@
      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
      "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
    },
    "d3-quadtree": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz",
      "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw=="
    },
    "d3-sankey": {
      "version": "0.9.1",
      "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.9.1.tgz",
@@ -6074,6 +6338,11 @@
        "graphlib": "^2.1.8",
        "lodash": "^4.17.15"
      }
    },
    "dagre-compound": {
      "version": "0.0.11",
      "resolved": "https://registry.npmjs.org/dagre-compound/-/dagre-compound-0.0.11.tgz",
      "integrity": "sha512-UrSgRP9LtOZCYb9e5doolZXpc7xayyszgyOs7uakTK4n4KsLegLVTRRtq01GpQd/iZjYw5fWMapx9ed+c80MAQ=="
    },
    "damerau-levenshtein": {
      "version": "1.0.5",
@@ -9858,6 +10127,11 @@
      "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
      "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
    },
    "gl-vec2": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/gl-vec2/-/gl-vec2-1.3.0.tgz",
      "integrity": "sha512-YiqaAuNsheWmUV0Sa8k94kBB0D6RWjwZztyO+trEYS8KzJ6OQB/4686gdrf59wld4hHFIvaxynO3nRxpk1Ij/A=="
    },
    "glob": {
      "version": "7.1.4",
      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
@@ -10681,6 +10955,11 @@
        }
      }
    },
    "insert-css": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/insert-css/-/insert-css-2.0.0.tgz",
      "integrity": "sha1-610Ql7dUL0x56jBg067gfQU4gPQ="
    },
    "internal-ip": {
      "version": "4.3.0",
      "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
@@ -10702,6 +10981,16 @@
      "requires": {
        "loose-envify": "^1.0.0"
      }
    },
    "inversify": {
      "version": "5.1.1",
      "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.1.1.tgz",
      "integrity": "sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ=="
    },
    "inversify-inject-decorators": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/inversify-inject-decorators/-/inversify-inject-decorators-3.1.0.tgz",
      "integrity": "sha512-/seBlVp5bXrLQS3DpKEmlgeZL6C7Tf/QITd+IMQrbBBGuCbxb7k3hRAWu9XSreNpFzLgSboz3sClLSEmGwHphw=="
    },
    "invert-kv": {
      "version": "1.0.0",
@@ -10735,6 +11024,11 @@
      "requires": {
        "kind-of": "^3.0.2"
      }
    },
    "is-any-array": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.0.tgz",
      "integrity": "sha512-WdPV58rT3aOWXvvyuBydnCq4S2BM1Yz8shKxlEpk/6x+GX202XRvXOycEFtNgnHVLoc46hpexPFx8Pz1/sMS0w=="
    },
    "is-arguments": {
      "version": "1.0.4",
@@ -13887,6 +14181,41 @@
        }
      }
    },
    "ml-array-max": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/ml-array-max/-/ml-array-max-1.2.4.tgz",
      "integrity": "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==",
      "requires": {
        "is-any-array": "^2.0.0"
      }
    },
    "ml-array-min": {
      "version": "1.2.3",
      "resolved": "https://registry.npmjs.org/ml-array-min/-/ml-array-min-1.2.3.tgz",
      "integrity": "sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q==",
      "requires": {
        "is-any-array": "^2.0.0"
      }
    },
    "ml-array-rescale": {
      "version": "1.3.7",
      "resolved": "https://registry.npmjs.org/ml-array-rescale/-/ml-array-rescale-1.3.7.tgz",
      "integrity": "sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ==",
      "requires": {
        "is-any-array": "^2.0.0",
        "ml-array-max": "^1.2.4",
        "ml-array-min": "^1.2.3"
      }
    },
    "ml-matrix": {
      "version": "6.10.0",
      "resolved": "https://registry.npmjs.org/ml-matrix/-/ml-matrix-6.10.0.tgz",
      "integrity": "sha512-wU+jacx1dcP1QArV1/Kv49Ah6y2fq+BiQl2BnNVBC+hoCW7KgBZ4YZrowPopeoY164TB6Kes5wMeDjY8ODHYDg==",
      "requires": {
        "is-any-array": "^2.0.0",
        "ml-array-rescale": "^1.3.7"
      }
    },
    "moment": {
      "version": "2.24.0",
      "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
@@ -15009,6 +15338,22 @@
        "parse-svg-path": "~0.1.1"
      }
    },
    "polyline-miter-util": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/polyline-miter-util/-/polyline-miter-util-1.0.1.tgz",
      "integrity": "sha1-tpPyOJ6g3tNqa89ezS7OS2kX2Vc=",
      "requires": {
        "gl-vec2": "^1.0.0"
      }
    },
    "polyline-normals": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/polyline-normals/-/polyline-normals-2.0.2.tgz",
      "integrity": "sha1-oXN+ddjA3MsaWR+csn8J7vS30TU=",
      "requires": {
        "polyline-miter-util": "^1.0.1"
      }
    },
    "portfinder": {
      "version": "1.0.24",
      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz",
@@ -16028,6 +16373,17 @@
      "version": "0.1.8",
      "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
      "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg=="
    },
    "probe.gl": {
      "version": "3.5.0",
      "resolved": "https://registry.npmjs.org/probe.gl/-/probe.gl-3.5.0.tgz",
      "integrity": "sha512-KWj8u0PNytr/rVwcQFcN7O8SK7n/ITOsUZ91l4fSX95oHhKvVCI7eadrzFUzFRlXkFfBWpMWZXFHITsHHHUctw==",
      "requires": {
        "@babel/runtime": "^7.0.0",
        "@probe.gl/env": "3.5.0",
        "@probe.gl/log": "3.5.0",
        "@probe.gl/stats": "3.5.0"
      }
    },
    "process": {
      "version": "0.11.10",
@@ -17833,6 +18189,11 @@
      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
      "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
    },
    "reflect-metadata": {
      "version": "0.1.13",
      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
      "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
    },
    "regenerate": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
@@ -17932,6 +18293,11 @@
        }
      }
    },
    "regl": {
      "version": "1.7.0",
      "resolved": "https://registry.npmjs.org/regl/-/regl-1.7.0.tgz",
      "integrity": "sha512-bEAtp/qrtKucxXSJkD4ebopFZYP0q1+3Vb2WECWv/T8yQEgKxDxJ7ztO285tAMaYZVR6mM1GgI6CCn8FROtL1w=="
    },
    "regression": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz",
package.json
@@ -6,6 +6,7 @@
    "@ant-design/icons": "^4.6.2",
    "@antv/data-set": "^0.11.4",
    "@antv/g2": "^4.1.34",
    "@antv/g6": "^4.6.4",
    "@antv/util": "^2.0.17",
    "@babel/core": "7.5.5",
    "@svgr/webpack": "4.3.2",
@@ -27,6 +28,7 @@
    "camelcase": "^5.2.0",
    "case-sensitive-paths-webpack-plugin": "2.2.0",
    "codemirror": "^5.52.2",
    "crypto-js": "^4.1.1",
    "css-loader": "2.1.1",
    "dotenv": "6.2.0",
    "dotenv-expand": "4.2.0",
public/admin.html
@@ -20,13 +20,14 @@
            if (!config) {
              document.body.innerHTML = '<div style="text-align: center; font-size: 30px; margin-top: 40vh;">系统配置信息获取失败,请联系管理员!</div>'
            } else {
              var url = window.location.href.split(/(index.html)+/ig)[0]
              var appPort = 'admin/index.html'
              var url = window.location.href.split(/index\.html|#/ig)[0].replace(/\/$/ig, '')
              var appPort = '/admin/index.html'
              if (config.defaultApp) {
                if ((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
                  appPort = 'mob/index.html#/index'
                  appPort = '/mob/index.html#/index'
                } else {
                  appPort = 'pc/index.html#/index'
                  appPort = '/pc/index.html#/index'
                }
              }
              window.location.replace(url + appPort)
src/api/index.js
@@ -66,16 +66,22 @@
    return Promise.resolve(response.data)
  }
}, (error) => {
  if (error && error.response) {
    notification.error({
      className: 'notification-custom-error',
      bottom: 0,
      message: '状态码-' + error.response.status + ',请联系管理员',
      placement: 'bottomRight',
      duration: 15
    })
  let response = error.response
  if (response) {
    if (!response.data || !response.data.errors) { // 过滤旷视报错信息
      notification.error({
        className: 'notification-custom-error',
        bottom: 0,
        message: '状态码-' + response.status + ',请联系管理员',
        placement: 'bottomRight',
        duration: 15
      })
    }
    return Promise.reject(response)
  } else {
    return Promise.reject()
  }
  return Promise.reject(error.response)
})
class Api {
src/api/w4k.js
New file
@@ -0,0 +1,113 @@
import axios from 'axios'
import jsSHA from 'jssha'
class W4kApi {
  /**
   * @description 鉴权挑战
   * @param {Object} param 查询及提交参数
   */
  login (ip, username = 'admin', password) {
    return new Promise((resolve, reject) => {
      let challurl = ip + '/api/auth/login/challenge?username=' + username
      challurl = '/trans/redirect?rd=' + challurl + '&method=get'
      let loginurl = ip + '/api/auth/login'
      loginurl = '/trans/redirect?rd=' + loginurl + '&method=post'
      axios({
        url: challurl,
        method: 'post' // get
      }).then(res => {
        if (res.errors) {
          reject(res)
        } else {
          const shaObj = new jsSHA('SHA-256', 'TEXT', { encoding: 'UTF8' })
          shaObj.update(password + res.salt + res.challenge)
          const hash = shaObj.getHash('HEX')
          axios({
            url: loginurl,
            method: 'post',
            data: {
              session_id: res.session_id,
              username: username,
              password: hash
            }
          }).then(result => {
            resolve(result)
          }, (err) => {
            reject(err)
          })
        }
      }, (err) => {
        reject(err)
      })
    })
  }
  queryUsers (ip) {
    let url = ip + '/api/persons/query'
    url = '/trans/redirect?rd=' + url + '&method=post'
    return axios({
      url: url,
      method: 'POST',
      withCredentials: true,
      headers: { 'Content-Type': 'application/json' },
      data: {
        limit: 100,
        offset: 0,
        sort: 'desc',
        query_id: '',
        query_string: ''
      }
    })
  }
  addUsers (ip, data) {
    return new Promise((resolve, reject) => {
      let delurl = ip + '/api/persons/item/' + data.id
      delurl = '/trans/redirect?rd=' + delurl + '&method=DELETE'
      let addurl = ip + '/api/persons/item'
      addurl = '/trans/redirect?rd=' + addurl + '&method=post'
      if (data.deleted) {
        delete data.deleted
        axios({
          url: delurl,
          method: 'post' // DELETE
        }).then(res => {
          if (res.errors) {
            reject(res)
          } else {
            axios({
              url: addurl,
              method: 'post',
              data: data
            }).then(result => {
              resolve(result)
            }, (err) => {
              reject(err)
            })
          }
        }, (err) => {
          reject(err)
        })
      } else {
        delete data.deleted
        axios({
          url: addurl,
          method: 'post',
          data: data
        }).then(result => {
          resolve(result)
        }, (err) => {
          reject(err)
        })
      }
    })
  }
}
export default new W4kApi()
src/assets/css/main.scss
@@ -441,12 +441,13 @@
    color: inherit!important;
  }
}
// .ant-table.ant-table-mini .ant-table-thead > tr > th {
//   padding: 8px 4px;
// }
// .ant-table.ant-table-mini .ant-table-tbody > tr > td {
//   padding: 4px 4px;
// }
.ant-table.ant-table-mini .ant-table-thead > tr > th {
  padding: 8px 8px!important;
}
.ant-table.ant-table-mini .ant-table-tbody > tr > td {
  padding: 2px 8px!important;
  line-height: 1.5;
}
.mk-date-picker.minute {
  .ant-calendar-time-picker-combobox {
    .ant-calendar-time-picker-select {
src/components/header/index.jsx
@@ -653,7 +653,7 @@
      let url = `${ssodomain}/doc/index.html#?appkey=${window.GLOB.appkey}&LoginUID=${sessionStorage.getItem('LoginUID')}`
      window.open(url)
    } else if (options.sysType === 'SSO' || options.sysType === 'cloud') {
      window.open(`${window.location.href.replace(/\/index.html(.*)|\/#(.*)/ig, '')}/doc/index.html#?appkey=${window.GLOB.appkey}&LoginUID=${sessionStorage.getItem('LoginUID')}`)
      window.open(`${window.location.href.replace(/\/admin(.*)|\/index.html(.*)|\/#(.*)/ig, '')}/doc/index.html#?appkey=${window.GLOB.appkey}&LoginUID=${sessionStorage.getItem('LoginUID')}`)
    }
  }
src/menu/components/card/balcony/index.jsx
@@ -230,6 +230,7 @@
        </Popover>
        {card.wrap.checkAll === 'show' ? <div className="check-all"><Checkbox>全选</Checkbox></div> : null}
        <CardCellComponent cards={card} cardCell={card} elements={card.elements} updateElement={this.updateCard}/>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/card/cardcellcomponent/dragaction/action.jsx
@@ -68,7 +68,7 @@
        {hasProfile ? <ProfileOutlined className="profile" title="setting" onClick={() => profileCard(id)} /> : null}
      </div>
    } trigger="hover">
      <div ref={node => drag(drop(node))} className={'ant-col card-button-cell ant-col-' + card.width} onDoubleClick={(e) => {e.stopPropagation(); doubleClickCard(id)}}>
      <div ref={node => drag(drop(node))} className={'ant-col card-button-cell ant-col-' + card.width + (card.hidden === 'true' ? ' mk-hidden' : '')} onDoubleClick={(e) => {e.stopPropagation(); doubleClickCard(id)}}>
        <div style={{opacity: isDragging ? 0 : 1, ...card.wrapStyle}}>
          {btnElement}
        </div>
src/menu/components/card/cardcellcomponent/elementform/index.jsx
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Input, Select, Radio, Tooltip, InputNumber } from 'antd'
import { Form, Row, Col, Input, Select, Radio, Tooltip, InputNumber, Cascader } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import { formRule } from '@/utils/option.js'
@@ -15,7 +15,7 @@
const cardTypeOptions = {
  sequence: ['eleType', 'width'],
  text: ['eleType', 'datatype', 'format', 'width', 'height', 'prefix', 'postfix', 'link', 'noValue', 'bgImage', 'fixStyle'],
  text: ['eleType', 'datatype', 'format', 'width', 'height', 'prefix', 'postfix', 'link', 'anchors', 'noValue', 'bgImage', 'fixStyle'],
  number: ['eleType', 'datatype', 'format', 'width', 'height', 'prefix', 'postfix', 'noValue', 'fixStyle'],
  picture: ['eleType', 'datatype', 'width', 'lenWidRadio', 'maxWidth', 'link', 'noValue'],
  video: ['eleType', 'datatype', 'width', 'aspectRatio', 'autoPlay', 'loop', 'noValue'],
@@ -374,7 +374,7 @@
      } else if (item.type === 'number') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
            <Form.Item help={item.help || null} label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
@@ -507,6 +507,29 @@
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'cascader') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" overlayClassName={item.tooltipClass} title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || [],
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Cascader options={item.options || []} expandTrigger="hover" placeholder=""/>
              )}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
src/menu/components/card/cardcellcomponent/formconfig.jsx
@@ -8,7 +8,7 @@
 * @param {*} card
 * @param {*} type
 */
export function getCardCellForm (card, type, subtype, cardCell) {
export function getCardCellForm (card, type, subtype, cardCell, anchors) {
  let _options = [
    { value: 'text', text: '文本'},
    { value: 'number', text: '数值'},
@@ -326,6 +326,14 @@
      options: []
    },
    {
      type: 'cascader',
      key: 'anchors',
      label: '跳转锚点',
      initVal: card.anchors || [],
      required: false,
      options: anchors
    },
    {
      type: 'number',
      key: 'barHeight',
      min: 5,
@@ -406,6 +414,7 @@
      label: '最大宽度',
      initVal: card.maxWidth || '',
      tooltip: '图片宽度的最大值。',
      help: '注:此值存在时,左右外边距为0居中显示。',
      required: false,
    },
    {
@@ -426,7 +435,6 @@
      initVal: card.link || '',
      tooltip: '动态地址为绑定字段值。',
      required: false,
      // forbid: isApp,
      options: [
        { value: '', text: '无' },
        { value: 'dynamic', text: '动态' },
@@ -439,7 +447,6 @@
      label: '链接类型',
      initVal: card.linkType || 'other',
      required: false,
      // forbid: isApp,
      options: [
        { value: 'tel', text: '电话' },
        { value: 'email', text: '邮箱' },
@@ -552,7 +559,7 @@
      type: 'number',
      key: 'fixSize',
      min: 10,
      max: 100,
      max: 300,
      label: '字体大小',
      initVal: card.fixSize || 14,
      tooltip: '前缀、后缀的字体大小。',
src/menu/components/card/cardcellcomponent/index.jsx
@@ -12,6 +12,7 @@
import MKEmitter from '@/utils/events.js'
import MenuUtils from '@/utils/utils-custom.js'
import ElementForm from './elementform'
import CreateFunc from '@/templates/zshare/createfunc'
import DragElement from './dragaction'
import './index.scss'
@@ -41,6 +42,7 @@
    visible: false,      // 模态框控制
    actvisible: false,   // 按钮编辑模态框
    profVisible: false,  // 验证信息编辑
    record: null
  }
  /**
@@ -267,10 +269,12 @@
    if (card.eleType === 'button') {
      this.handleAction(card)
    } else {
      let anchors = MenuUtils.getAnchors(window.GLOB.customMenu.components, cards.uuid) || []
      this.setState({
        visible: true,
        card: card,
        formlist: getCardCellForm(card, cards.type, cards.subtype, cardCell)
        formlist: getCardCellForm(card, cards.type, cards.subtype, cardCell, anchors)
      })
    }
  }
@@ -309,11 +313,12 @@
    }
    let modules = MenuUtils.getSubModules(window.GLOB.customMenu.components, cards.uuid) || []
    let anchors = MenuUtils.getAnchors(window.GLOB.customMenu.components, cards.uuid) || []
    this.setState({
      actvisible: true,
      card: card,
      formlist: getActionForm(card, functip, cards, usefulFields, 'card', menulist, modules)
      formlist: getActionForm(card, functip, cards, usefulFields, 'card', menulist, modules, anchors)
    })
  }
@@ -627,7 +632,7 @@
  render() {
    const { cards, cardCell } = this.props
    const { elements, visible, actvisible, profVisible, card, dict } = this.state
    const { elements, visible, actvisible, profVisible, card, dict, record } = this.state
    return (
      <div className="model-menu-card-cell-list">
@@ -672,6 +677,7 @@
            maskClosable={false}
            onCancel={this.editModalCancel}
            footer={[
              record && record.intertype === 'inner' ? <CreateFunc key="create" dict={dict} ref="btnCreatFunc" trigger={this.creatFunc}/> : null,
              <Button key="cancel" onClick={this.editModalCancel}>{dict['model.cancel']}</Button>,
              <Button key="confirm" type="primary" onClick={this.handleActionSubmit}>{dict['model.confirm']}</Button>
            ]}
@@ -684,6 +690,7 @@
              formlist={this.state.formlist}
              inputSubmit={this.handleActionSubmit}
              setting={cards.setting}
              updRecord={(record) => this.setState({record: fromJS(record).toJS()})}
              wrappedComponentRef={(inst) => this.actionFormRef = inst}
            />
          </Modal>
src/menu/components/card/data-card/index.jsx
@@ -674,6 +674,7 @@
        <div style={{clear: 'both'}}></div>
        {card.wrap.pagestyle === 'page' && card.setting.laypage === 'true' && appType !== 'mob' ? <Pagination total={85} size="small" showTotal={total => `共 ${total} 条`} pageSize={20} defaultCurrent={1}/> : null}
        {card.wrap.pagestyle === 'page' && card.setting.laypage === 'true' && appType === 'mob' ? <MobPagination /> : null}
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/card/prop-card/index.jsx
@@ -494,6 +494,7 @@
        <div className={(card.wrap.layout || 'grid') + '-layout float-' + (card.wrap.cardFloat || 'left')}>
          {card.subcards.map(subcard => (<CardComponent key={subcard.uuid} cards={card} card={subcard} move={this.move} updateElement={this.updateCard} deleteElement={this.deleteCard}/>))}
        </div>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/card/table-card/index.jsx
@@ -564,6 +564,7 @@
        </div>
        {card.setting.laypage === 'true' && card.wrap.pagestyle !== 'slide' && appType !== 'mob' ? <Pagination size="small" total={50} /> : null}
        {card.setting.laypage === 'true' && card.wrap.pagestyle !== 'slide' && appType === 'mob' ? <MobPagination /> : null}
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/carousel/data-card/index.jsx
@@ -291,6 +291,7 @@
          <ToolOutlined/>
        </Popover>
        <CardSimpleComponent cards={card} card={card.subcards[0]} updateElement={this.updateCard} deleteElement={this.deleteCard}/>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/carousel/prop-card/index.jsx
@@ -373,6 +373,7 @@
        {card.subcards.length > 0 ? <Carousel dotPosition={card.wrap.dotPosition || 'bottom'} effect={card.wrap.effect || 'scrollx'}>
          {card.subcards.map((subcard) => (<CardSimpleComponent key={subcard.uuid} cards={card} card={subcard} move={this.move} updateElement={this.updateCard} deleteElement={this.deleteCard}/>))}
        </Carousel> : null}
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx
@@ -7,7 +7,7 @@
 * @description 获取图表视图配置表单
 * @param {object} card       // 图表对象
 */
export function getBaseForm (card) {
export function getBaseForm (card, columns = []) {
  let appType = sessionStorage.getItem('appType')
  let roleList = sessionStorage.getItem('sysRoles')
  if (roleList) {
@@ -18,6 +18,32 @@
    }
  } else {
    roleList = []
  }
  let menulist = []
  if (appType === 'pc') {
    menulist = sessionStorage.getItem('appMenus')
    if (menulist) {
      try {
        menulist = JSON.parse(menulist)
      } catch (e) {
        menulist = []
      }
    } else {
      menulist = []
    }
  } else if (appType === '') {
    menulist = sessionStorage.getItem('fstMenuList')
    if (menulist) {
      try {
        menulist = JSON.parse(menulist)
      } catch (e) {
        menulist = []
      }
    } else {
      menulist = []
    }
  }
  return [
@@ -79,38 +105,100 @@
      options: roleList,
      forbid: !!appType
    },
    // {
    //   type: 'cascader',
    //   key: 'linkmenu',
    //   label: '关联菜单',
    //   initVal: card.linkmenu || [],
    //   tooltip: '在使用柱形图且未启用自定义设置时有效。',
    //   required: false,
    //   forbid: appType === 'pc' || appType === 'mob',
    //   options: menulist
    // },
    // {
    //   type: 'select',
    //   key: 'linkmenu',
    //   label: '关联菜单',
    //   initVal: card.linkmenu || '',
    //   tooltip: '双击柱状图,会打开关联的菜单。',
    //   required: false,
    //   forbid: appType !== 'pc',
    //   options: menulist
    // },
    // {
    //   type: 'radio',
    //   key: 'open',
    //   label: '打开方式',
    //   initVal: card.open || 'blank',
    //   required: false,
    //   forbid: appType !== 'pc',
    //   options: [
    //     { value: 'blank', text: '新窗口' },
    //     { value: 'self', text: '当前窗口' }
    //   ]
    // }
    {
      type: 'radio',
      key: 'click',
      label: '点击事件',
      initVal: card.click || '',
      tooltip: '当绑定点击事件时,柱形图的切换功能将失效。',
      required: false,
      forbid: appType === 'mob' || card.chartType !== 'bar',
      options: [
        {value: '', label: '无'},
        {value: 'menu', label: '菜单'},
        {value: 'menus', label: '菜单组'}
      ]
    },
    {
      type: appType === '' ? 'cascader' : 'select',
      key: 'menu',
      label: '关联菜单',
      initVal: card.menu || (appType === '' ? [] : ''),
      tooltip: '在使用柱形图且未启用自定义设置时有效。',
      required: true,
      forbid: appType === 'mob' || card.chartType !== 'bar',
      hidden: card.click !== 'menu',
      options: menulist
    },
    {
      type: 'select',
      key: 'menuType',
      label: '菜单类型',
      initVal: card.menuType || '',
      required: true,
      forbid: appType === 'mob' || card.chartType !== 'bar',
      hidden: card.click !== 'menus',
      options: columns,
    },
    {
      type: 'radio',
      key: 'open',
      label: '打开方式',
      initVal: card.open || 'blank',
      required: false,
      options: [
        {value: 'blank', label: '新窗口'},
        {value: 'self', label: '当前窗口'},
      ],
      forbid: appType !== 'pc' || card.chartType !== 'bar',
      hidden: card.click !== 'menu' && card.click !== 'menus'
    },
    {
      type: 'radio',
      key: 'joint',
      label: '参数拼接',
      initVal: card.joint || 'true',
      required: false,
      options: [
        {value: 'true', label: '是'},
        {value: 'false', label: '否'},
      ],
      forbid: appType === 'mob' || card.chartType !== 'bar',
      hidden: card.click !== 'menu' && card.click !== 'menus'
    },
    {
      type: 'table',
      key: 'menus',
      label: '菜单组',
      initVal: card.menus || [],
      required: true,
      span: 24,
      actions: ['view'],
      forbid: appType === 'mob' || card.chartType !== 'bar',
      hidden: card.click !== 'menus',
      columns: [
        {
          title: '标识',
          dataIndex: 'sign',
          inputType: 'input',
          editable: true,
          unique: true,
          required: false,
          width: '35%'
        },
        {
          title: '菜单',
          dataIndex: 'menu',
          inputType: !appType ? 'cascader' : 'select',
          editable: true,
          required: true,
          extends: !appType ? 'Menu' : [{key: 'label', value: 'label'}],
          width: '35%',
          render: (text, record) => record.label,
          options: menulist
        }
      ]
    }
  ]
}
@@ -125,23 +213,23 @@
  if (card.chartType === 'line') {
    shapes = [
      { field: 'smooth', label: 'smooth' },
      { field: 'line', label: 'line' },
      { field: 'dot', label: 'dot' },
      { field: 'dash', label: 'dash' },
      { field: 'hv', label: 'hv' },
      { field: 'vh', label: 'vh' },
      { field: 'hvh', label: 'hvh' },
      { field: 'vhv', label: 'vhv' }
      { field: 'smooth', label: 'smooth(平滑线)' },
      { field: 'line', label: 'line(直线)' },
      { field: 'dot', label: 'dot(点状线)' },
      { field: 'dash', label: 'dash(虚线)' },
      { field: 'hv', label: 'hv(水平-垂直线)' },
      { field: 'vh', label: 'vh(垂直-水平线)' },
      { field: 'hvh', label: 'hvh(水平-垂直-水平线)' },
      { field: 'vhv', label: 'vhv(垂直-水平-垂直线)' }
    ]
  } else if (card.chartType === 'bar') {
    shapes = [
      { field: 'rect', label: 'rect' },
      { field: 'hollow-rect', label: 'hollow-rect' },
      { field: 'line', label: 'line' },
      { field: 'tick', label: 'tick' },
      { field: 'funnel', label: 'funnel' },
      { field: 'pyramid', label: 'pyramid' }
      { field: 'rect', label: 'rect(矩形)' },
      { field: 'hollow-rect', label: 'hollow-rect(空心矩形)' },
      { field: 'line', label: 'line(线条)' },
      { field: 'tick', label: 'tick(波动)' },
      // { field: 'funnel', label: 'funnel' },
      { field: 'pyramid', label: 'pyramid(角锥)' }
    ]
  }
@@ -288,6 +376,7 @@
      key: 'show',
      label: '显示',
      initVal: card.show || 'value',
      tooltip: '当使用自定义设置时,可在显示(值/%)处单独设置显示类型。注:自定义为空时使用此处设置。',
      required: false,
      options: [{
        value: 'value',
@@ -299,14 +388,15 @@
    }, {
      type: labelOptions.length > 20 ? 'select' : 'radio',
      key: 'label',
      label: '标签',
      label: '标注',
      initVal: card.label || 'false',
      tooltip: '图形节点处的数值。',
      required: false,
      options: labelOptions
    }, {
      type: 'radio',
      key: 'labelColor',
      label: '标签颜色',
      label: '标注颜色',
      initVal: card.labelColor || 'system',
      tooltip: '使用系统色时,使用色系选项设置的系统颜色,使用自定义为颜色设置中定义的图形颜色。',
      required: false,
src/menu/components/chart/antv-bar/chartcompile/index.jsx
@@ -218,7 +218,7 @@
        }
      },
      {
        title: '显示',
        title: '显示(值/%)',
        dataIndex: 'show',
        inputType: 'select',
        editable: true,
@@ -274,7 +274,7 @@
      datatype: config.plot.datatype || 'query',
      fieldName: fieldName,
      plot: fromJS(config.plot).toJS(),
      baseFormlist: getBaseForm(config.plot),
      baseFormlist: getBaseForm(config.plot, config.columns),
      formlist: getOptionForm(config.plot, config.columns)
    })
  }
@@ -502,6 +502,40 @@
      this.baseRef.handleConfirm().then(res => {
        let _plot = {...plot, ...res}
        if (res.click === 'menu') {
          delete _plot.menus
        } else if (res.click === 'menus') {
          delete _plot.menu
        } else {
          delete _plot.menus
          delete _plot.menu
        }
        delete _plot.MenuID
        delete _plot.MenuName
        delete _plot.MenuNo
        delete _plot.tabType
        if (_plot.click === 'menu' && sessionStorage.getItem('appType') === '' && _plot.menu) {
          let list = null
          try {
            list = JSON.parse(sessionStorage.getItem('thdMenuList')) || []
          } catch (e) {
            list = []
          }
          let id = _plot.menu[_plot.menu.length - 1]
          list.forEach(item => {
            if (item.MenuID === id) {
              _plot.MenuID = id
              _plot.MenuName = item.MenuName
              _plot.MenuNo = item.MenuNo
              _plot.tabType = item.type
            }
          })
        }
        this.setState({
          plot: _plot,
          visible: false
@@ -579,8 +613,44 @@
      })
    } else if (view === 'base') {
      this.baseRef.handleConfirm().then(res => {
        let _plot = {...plot, ...res}
        if (res.click === 'menu') {
          delete _plot.menus
        } else if (res.click === 'menus') {
          delete _plot.menu
        } else {
          delete _plot.menus
          delete _plot.menu
        }
        delete _plot.MenuID
        delete _plot.MenuName
        delete _plot.MenuNo
        delete _plot.tabType
        if (_plot.click === 'menu' && sessionStorage.getItem('appType') === '' && _plot.menu) {
          let list = null
          try {
            list = JSON.parse(sessionStorage.getItem('thdMenuList')) || []
          } catch (e) {
            list = []
          }
          let id = _plot.menu[_plot.menu.length - 1]
          list.forEach(item => {
            if (item.MenuID === id) {
              _plot.MenuID = id
              _plot.MenuName = item.MenuName
              _plot.MenuNo = item.MenuNo
              _plot.tabType = item.type
            }
          })
        }
        this.setState({
          plot: {...plot, ...res},
          plot: _plot,
          view: tab
        })
      })
@@ -638,7 +708,7 @@
          wrapClassName="popview-modal menu-chart-edit-modal"
          title={config.type === 'bar' ? '柱状图编辑' : '折线图编辑'}
          visible={visible}
          width={950}
          width={1000}
          maskClosable={false}
          onOk={this.onSubmit}
          onCancel={() => { this.setState({ visible: false }) }}
src/menu/components/chart/antv-bar/chartcompile/index.scss
@@ -29,6 +29,9 @@
          position: relative;
          z-index: 1;
        }
        .ant-table-column-title {
          white-space: nowrap;
        }
      }
    }
  }
src/menu/components/chart/antv-bar/index.jsx
@@ -151,7 +151,7 @@
  handleTabsChange = (parentId) => {
    const { card } = this.state
    if (parentId === card.parentId) {
    if (parentId === card.parentId || parentId === 'all') {
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
@@ -1358,6 +1358,7 @@
          config={card}
          updateaction={this.updateComponent}
        /> : null}
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/chart/antv-dashboard/chartcompile/formconfig.jsx
@@ -239,7 +239,7 @@
      label: '字体大小',
      initVal: card.fontSize || 28,
      min: 12,
      max: 200,
      max: 300,
      decimal: 0,
      required: true
    },
src/menu/components/chart/antv-dashboard/index.jsx
@@ -186,8 +186,8 @@
  handleTabsChange = (parentId) => {
    const { card } = this.state
    if (parentId === card.parentId) {
      let _element = document.getElementById(card.uuid + 'canvas')
    if (parentId === card.parentId || parentId === 'all') {
      let _element = document.getElementById(card.uuid + 'dashboard')
      if (_element) {
        _element.innerHTML = ''
      }
@@ -534,6 +534,7 @@
        </Popover>
        <NormalHeader hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'dashboard'} ref={ref => this.wrap = ref}></div>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/chart/antv-pie/index.jsx
@@ -141,7 +141,7 @@
  handleTabsChange = (parentId) => {
    const { card } = this.state
    if (parentId === card.parentId) {
    if (parentId === card.parentId || parentId === 'all') {
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
@@ -686,6 +686,7 @@
        </Popover>
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/chart/antv-scatter/index.jsx
@@ -139,7 +139,7 @@
  handleTabsChange = (parentId) => {
    const { card } = this.state
    if (parentId === card.parentId) {
    if (parentId === card.parentId || parentId === 'all') {
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
@@ -392,6 +392,7 @@
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
        {appType !== 'mob' ? <ActionComponent type="chart" config={card} updateaction={this.updateComponent}/> : null}
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/chart/chart-custom/index.jsx
@@ -127,7 +127,7 @@
  handleTabsChange = (parentId) => {
    const { card } = this.state
    if (parentId === card.parentId) {
    if (parentId === card.parentId || parentId === 'all') {
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
@@ -263,6 +263,7 @@
        </Popover>
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/code/sandbox/index.jsx
@@ -188,6 +188,7 @@
          <ToolOutlined />
        </Popover>
        <CodeContent name={card.name} html={card.html} css={card.css} js={card.js}/>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/editor/braft-editor/index.jsx
@@ -186,6 +186,7 @@
          value={card.wrap.datatype !== 'static' ? '<p class="empty-content">富文本</p>' : card.html}
          encryption="false"
        />
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/form/formaction/actionform/index.jsx
@@ -54,7 +54,7 @@
    } else if (card.type === 'next') {
      return ['type', 'label', 'enable']
    }
    let _options = ['type', 'label', 'intertype', 'syncComponent', 'linkmenu', 'open', 'enable', 'output', 'reload'] // 选项列表
    let _options = ['type', 'label', 'intertype', 'syncComponent', 'anchors', 'linkmenu', 'open', 'enable', 'output', 'reload'] // 选项列表
    
    if (_intertype === 'custom') {
      _options.pop()
src/menu/components/form/formaction/formconfig.jsx
@@ -9,7 +9,7 @@
 * @param {*} type           按钮类型,用于区分可选的打开方式
 */
export function getActionForm (card, functip, tableName, usefulFields, modules) {
export function getActionForm (card, functip, tableName, usefulFields, modules, anchors) {
  const appType = sessionStorage.getItem('appType')
  let _type = '提交'
  if (card.type === 'prev') {
@@ -287,6 +287,15 @@
      options: modules
    },
    {
      type: 'cascader',
      key: 'anchors',
      label: '跳转锚点',
      initVal: card.anchors || [],
      tooltip: '执行成功后,需要跳转的锚点',
      required: false,
      options: anchors
    },
    {
      type: 'radio',
      key: 'reload',
      label: '上一页',
src/menu/components/form/formaction/index.jsx
@@ -102,11 +102,12 @@
    </div>
    let modules = MenuUtils.getSubModules(window.GLOB.customMenu.components, config.uuid) || []
    let anchors = MenuUtils.getAnchors(window.GLOB.customMenu.components, config.uuid) || []
    this.setState({
      visible: true,
      card: card,
      formlist: getActionForm(card, functip, config.setting.tableName, usefulFields, modules)
      formlist: getActionForm(card, functip, config.setting.tableName, usefulFields, modules, anchors)
    })
  }
src/menu/components/form/normal-form/index.jsx
@@ -461,35 +461,31 @@
        index = i
      }
      let label = item.label || ''
      if (item.field && item.field.toLowerCase() !== label.toLowerCase()) {
        label = label + ' (' + item.field + ')'
      }
      if (['text', 'number', 'textarea', 'color'].includes(item.type) && _item.field !== item.field) {
        _inputfields.push({
          field: item.field,
          label: _inputIndex + '、' + item.label
          label: _inputIndex + '、' + label
        })
        _inputIndex++
      }
      if (appType === 'mob') {
        if (_form.field !== item.field && item.hidden !== 'true' && ['text', 'number'].includes(item.type)) {
          _tabfields.push({
            field: item.field,
            label: _tabIndex + '、' + item.label
          })
          _tabIndex++
        }
      } else {
        if (_form.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type)) {
          _tabfields.push({
            field: item.field,
            label: _tabIndex + '、' + item.label
          })
          _tabIndex++
        }
      if (_item.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type)) {
        _tabfields.push({
          field: item.field,
          label: _tabIndex + '、' + label
        })
        _tabIndex++
      }
      if (item.type === 'switch') {
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
      }
      
@@ -504,7 +500,7 @@
        })
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
        _linkIndex++
      }
@@ -735,6 +731,7 @@
          />}
          <FormAction config={card} group={group} updateconfig={this.updateGroup}/>
        </div> : null}
        <div className="component-name"><div className="center">{card.name}</div></div>
        <Modal
          title={this.state.dict['model.edit']}
          visible={this.state.visible}
src/menu/components/form/tab-form/index.jsx
@@ -467,35 +467,30 @@
        index = i
      }
      let label = item.label || ''
      if (item.field && item.field.toLowerCase() !== label.toLowerCase()) {
        label = label + ' (' + item.field + ')'
      }
      if (['text', 'number', 'textarea', 'color'].includes(item.type) && _item.field !== item.field) {
        _inputfields.push({
          field: item.field,
          label: _inputIndex + '、' + item.label
          label: _inputIndex + '、' + label
        })
        _inputIndex++
      }
      if (appType === 'mob') {
        if (_form.field !== item.field && item.hidden !== 'true' && ['text', 'number'].includes(item.type)) {
          _tabfields.push({
            field: item.field,
            label: _tabIndex + '、' + item.label
          })
          _tabIndex++
        }
      } else {
        if (_form.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type)) {
          _tabfields.push({
            field: item.field,
            label: _tabIndex + '、' + item.label
          })
          _tabIndex++
        }
      if (_form.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type)) {
        _tabfields.push({
          field: item.field,
          label: _tabIndex + '、' + label
        })
        _tabIndex++
      }
      if (item.type === 'switch') {
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
      }
      
@@ -510,7 +505,7 @@
        })
        _linksupFields.push({
          value: item.field,
          text: _linkIndex + '、' + item.label
          text: _linkIndex + '、' + label
        })
        _linkIndex++
@@ -743,6 +738,7 @@
          />}
          <FormAction config={card} group={group} updateconfig={this.updateGroup}/>
        </div> : null}
        <div className="component-name"><div className="center">{card.name}</div></div>
        <Modal
          title={this.state.dict['model.edit']}
          visible={this.state.visible}
src/menu/components/search/main-search/dragsearch/index.jsx
@@ -92,7 +92,7 @@
        <div className="ant-row ant-form-item" style={{lineHeight: '40px', height: '55px', marginBottom: 0}}>
          <div className="ant-col ant-form-item-label ant-col-xs-24 ant-col-sm-8">
          </div>
          <div className="ant-col ant-form-item-control-wrapper ant-col-xs-24 ant-col-sm-16">
          <div className="ant-col ant-form-item-control-wrapper ant-col-xs-24 ant-col-sm-16" style={{whiteSpace: 'nowrap'}}>
            <Button type="primary">搜索</Button>
            {appType !== 'mob' ? <Button style={{ marginLeft: 8 }}>重置</Button> : null}
            <div style={{position: 'absolute', top: 0, bottom: 0, left: 0, right: 0}}></div>
@@ -117,7 +117,7 @@
        <div className="ant-row ant-form-item" style={{lineHeight: '40px', height: '55px', marginBottom: 0}}>
          <div className="ant-col ant-form-item-label ant-col-xs-24 ant-col-sm-8">
          </div>
          <div className="ant-col ant-form-item-control-wrapper ant-col-xs-24 ant-col-sm-16">
          <div className="ant-col ant-form-item-control-wrapper ant-col-xs-24 ant-col-sm-16" style={{whiteSpace: 'nowrap'}}>
            <Button type="primary">搜索</Button>
            {appType !== 'mob' ? <Button style={{ marginLeft: 8 }}>重置</Button> : null}
            <div style={{position: 'absolute', top: 0, bottom: 0, left: 0, right: 0}}></div>
src/menu/components/search/main-search/index.jsx
@@ -440,6 +440,7 @@
        } trigger="hover">
          <ToolOutlined />
        </Popover>
        <div className="component-name"><div className="center">{card.name}</div></div>
        {/* 编辑搜索条件 */}
        <Modal
          title="搜索条件-编辑"
src/menu/components/share/actioncomponent/actionform/index.jsx
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Input, Select, Radio, notification, Tooltip, InputNumber, Cascader } from 'antd'
import { Form, Row, Col, Input, Select, Radio, Tooltip, InputNumber, Cascader } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import { formRule } from '@/utils/option.js'
@@ -11,16 +11,16 @@
const { TextArea } = Input
const MkEditIcon = asyncComponent(() => import('@/components/mkIcon'))
const acTyOptions = {
  pop: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width', 'openmenu', 'open', 'refreshTab', 'position', 'tipTitle'],
  prompt: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width', 'openmenu', 'open', 'refreshTab', 'position', 'tipTitle'],
  exec: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width', 'openmenu', 'open', 'refreshTab'],
  excelIn: ['label', 'Ot', 'OpenType', 'intertype', 'show', 'icon', 'class', 'color', 'sheet', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width'],
  excelOut: ['label', 'OpenType', 'intertype', 'show', 'icon', 'class', 'color', 'execSuccess', 'execError', 'syncComponent', 'switchTab', 'resetPageIndex', 'pagination', 'search', 'width'],
  popview: ['label', 'Ot', 'OpenType', 'show', 'icon', 'class', 'color', 'popClose', 'resetPageIndex', 'width', 'display', 'ratio', 'syncComponent', 'clickouter'],
  tab: ['label', 'Ot', 'OpenType', 'show', 'icon', 'class', 'color', 'linkmenu', 'width'],
  innerpage: ['label', 'Ot', 'OpenType', 'pageTemplate', 'show', 'swipe', 'icon', 'class', 'color', 'width', 'open'],
  funcbutton: ['label', 'OpenType', 'funcType', 'show', 'swipe', 'icon', 'class', 'color', 'width'],
  form: ['label', 'OpenType', 'formType', 'intertype', 'Ot', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'width', 'open', 'refreshTab', 'title']
  pop: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'anchors', 'width', 'openmenu', 'open', 'refreshTab', 'position', 'tipTitle', 'hidden'],
  prompt: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'anchors', 'width', 'openmenu', 'open', 'refreshTab', 'position', 'tipTitle', 'hidden'],
  exec: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'anchors', 'width', 'openmenu', 'open', 'refreshTab', 'hidden'],
  excelIn: ['label', 'Ot', 'OpenType', 'intertype', 'show', 'icon', 'class', 'color', 'sheet', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width', 'hidden'],
  excelOut: ['label', 'OpenType', 'intertype', 'show', 'icon', 'class', 'color', 'execSuccess', 'execError', 'syncComponent', 'switchTab', 'resetPageIndex', 'pagination', 'search', 'width', 'hidden'],
  popview: ['label', 'Ot', 'OpenType', 'show', 'icon', 'class', 'color', 'popClose', 'resetPageIndex', 'width', 'display', 'ratio', 'syncComponent', 'clickouter', 'hidden'],
  tab: ['label', 'Ot', 'OpenType', 'show', 'icon', 'class', 'color', 'linkmenu', 'width', 'hidden'],
  innerpage: ['label', 'Ot', 'OpenType', 'pageTemplate', 'show', 'swipe', 'icon', 'class', 'color', 'width', 'open', 'hidden'],
  funcbutton: ['label', 'OpenType', 'funcType', 'show', 'swipe', 'icon', 'class', 'color', 'width', 'hidden'],
  form: ['label', 'OpenType', 'formType', 'intertype', 'Ot', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'width', 'open', 'refreshTab', 'title', 'hidden']
}
class ActionForm extends Component {
@@ -30,7 +30,8 @@
    setting: PropTypes.object,   // 页面设置
    formlist: PropTypes.any,     // 表单信息
    card: PropTypes.any,         // 按钮信息
    inputSubmit: PropTypes.any   // 回车提交事件
    inputSubmit: PropTypes.func, // 回车提交事件
    updRecord: PropTypes.func    // 更新信息
  }
  state = {
@@ -140,6 +141,7 @@
        }
      }, 100)
    }
    this.props.updRecord && this.props.updRecord(this.record)
  }
  getMutilOptions = (requireOptions) => {
@@ -344,8 +346,8 @@
        reTooltip.linkmenu = '使用扫码登录功能或菜单跳转功能时,需选择跳转的菜单。'
      } else if (_funcType === 'goBack') {
        shows.push('reload')
      // } else if (_funcType === 'megvii') {
      //   shows.push('subFunc')
      } else if (_funcType === 'megvii') {
        shows.push('subFunc')
      }
    }
    
@@ -489,6 +491,8 @@
        this.record.control = ''
        _fieldval.control = ''
      }
      this.props.updRecord && this.props.updRecord(this.record)
    } else if (key === 'funcType') {
      if (value === 'print') {
        _fieldval.label = '打印'
@@ -553,6 +557,8 @@
      }
    } else if (key === 'intertype') {
      this.record.sysInterface = 'false'
      this.props.updRecord && this.props.updRecord(this.record)
    } else if (key === 'sysInterface') {
      if (value === 'true') {
        _fieldval.interface = window.GLOB.mainSystemApi || ''
@@ -734,7 +740,7 @@
  }
  handleConfirm = () => {
    const { setting, card } = this.props
    const { card } = this.props
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
@@ -749,15 +755,6 @@
              values.closeVal = +values.closeVal
            }
          } else if (values.OpenType === 'excelOut') {
            if (values.intertype === 'system' && setting.interType !== 'system') {
              notification.warning({
                top: 92,
                message: '表格数据查询未使用系统函数,导出Excel不可使用系统函数!',
                duration: 5
              })
              return
            }
            values.Ot = 'notRequired'
          } else if (['pop', 'prompt', 'exec'].includes(values.OpenType) && values.verify) {
            if ((values.Ot === 'requiredOnce' || card.Ot === 'requiredOnce') && card.Ot !== values.Ot) {
src/menu/components/share/actioncomponent/dragaction/card.jsx
@@ -37,6 +37,8 @@
    hasProfile = true
  } else if (card.funcType === 'print') {
    hasProfile = true
  } else if (card.funcType === 'megvii') {
    hasProfile = true
  }
  let btnElement = null
@@ -48,6 +50,9 @@
    _class = 'swiper swiper-' + card.color
    show = 'button'
  }
  if (card.hidden === 'true') {
    _class += ' mk-hidden'
  }
  let warning = null
  if (card.OpenType === 'innerpage' && !card.pageTemplate) {
    warning = <WarningOutlined style={{color: 'orange', marginLeft: '5px'}}/>
src/menu/components/share/actioncomponent/formconfig.jsx
@@ -12,7 +12,7 @@
 * @param {*} usefulFields   存储过程可用的开始字段
 * @param {*} type           按钮类型,用于区分可选的打开方式
 */
export function getActionForm (card, functip, config, usefulFields, type, menulist = [], modules = []) {
export function getActionForm (card, functip, config, usefulFields, type, menulist = [], modules = [], anchors = []) {
  let appType = sessionStorage.getItem('appType')
  let viewType = sessionStorage.getItem('editMenuType') // 弹窗 popview
  let printTemps = sessionStorage.getItem('printTemps')
@@ -88,6 +88,7 @@
    { value: 'changeuser', text: '切换用户' },
    { value: 'print', text: '标签打印' },
    { value: 'closetab', text: '标签关闭' },
    { value: 'megvii', text: '旷视面板机' },
  ]
  
  if (isApp) {
@@ -112,7 +113,6 @@
      { value: 'mkUnsubscribe', text: '注销账户' },
      { value: 'reAuth', text: '切换系统(清空缓存-小程序)' },
      { value: 'goBack', text: '返回' },
      // { value: 'megvii', text: '旷视面板机' },
    ]
    pageTemps = [
      { value: 'linkpage', text: '关联菜单' },
@@ -179,16 +179,16 @@
      required: true,
      options: funTypes
    },
    // {
    //   type: 'select',
    //   key: 'subFunc',
    //   label: '接口名称',
    //   initVal: card.subFunc || '',
    //   required: true,
    //   options: [
    //     { value: 'login', text: '登录' }
    //   ]
    // },
    {
      type: 'radio',
      key: 'subFunc',
      label: '接口名称',
      initVal: card.subFunc || 'addUser',
      required: true,
      options: [
        { value: 'addUser', text: '添加用户' },
      ]
    },
    {
      type: 'radio',
      key: 'formType',
@@ -646,6 +646,15 @@
    },
    {
      type: 'cascader',
      key: 'anchors',
      label: '跳转锚点',
      initVal: card.anchors || [],
      tooltip: '执行成功后,需要跳转的锚点',
      required: false,
      options: anchors
    },
    {
      type: 'cascader',
      key: 'refreshTab',
      label: '刷新菜单',
      initVal: card.refreshTab || [],
@@ -719,7 +728,7 @@
      precision: 0,
      label: '比例',
      initVal: card.ratio || 85,
      tooltip: '小于100为宽度(或高度)百分比,大于100为像素值。',
      tooltip: '模态框或抽屉的宽度,小于100为窗口宽度(或高度)百分比,大于100为像素值。',
      required: true
    },
    {
@@ -891,6 +900,21 @@
      initVal: card.closeText || '',
      required: false,
    },
    {
      type: 'radio',
      key: 'hidden',
      label: '隐藏',
      initVal: card.hidden || 'false',
      tooltip: '隐藏后按钮在页面中不显示,且不参与权限分配。',
      required: false,
      options: [{
        value: 'false',
        text: '否'
      }, {
        value: 'true',
        text: '是'
      }]
    }
  ]
  return forms
src/menu/components/share/actioncomponent/index.jsx
@@ -21,6 +21,7 @@
const VerifyPrint = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyprint'))
const VerifyExcelIn = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyexcelin'))
const VerifyExcelOut = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyexcelout'))
const VerifyMegvii = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifymegvii'))
class ActionComponent extends Component {
  static propTpyes = {
@@ -37,7 +38,8 @@
    formlist: null,      // 表单信息
    actionlist: null,    // 按钮组
    visible: false,      // 模态框控制
    profVisible: false   // 验证信息模态框
    profVisible: false,  // 验证信息模态框
    record: null
  }
  /**
@@ -184,11 +186,12 @@
    }
    
    let modules = MenuUtils.getSubModules(window.GLOB.customMenu.components, config.uuid) || []
    let anchors = MenuUtils.getAnchors(window.GLOB.customMenu.components, config.uuid) || []
    this.setState({
      visible: true,
      card: card,
      formlist: getActionForm(card, functip, config, usefulFields, this.props.type, menulist, modules)
      formlist: getActionForm(card, functip, config, usefulFields, this.props.type, menulist, modules, anchors)
    })
  }
@@ -446,9 +449,54 @@
    })
  }
  getVerify = (card) => {
    const { config } = this.props
    const { dict } = this.state
    if (!card) return null
    if (['pop', 'prompt', 'exec'].includes(card.OpenType)) {
      return <VerifyCard
        card={card}
        dict={dict}
        config={config}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'excelIn') {
      return <VerifyExcelIn
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'excelOut') {
      return <VerifyExcelOut
        card={card}
        dict={dict}
        config={config}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'funcbutton' && card.funcType === 'print') {
      return <VerifyPrint
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'funcbutton' && card.funcType === 'megvii') {
      return <VerifyMegvii
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    }
  }
  render() {
    const { config, type } = this.props
    const { actionlist, visible, appType, card, dict, profVisible } = this.state
    const { actionlist, visible, appType, card, dict, profVisible, record } = this.state
    return (
      <div className={'model-menu-action-list'}>
@@ -465,14 +513,13 @@
        />
        {/* 编辑按钮:复制、编辑 */}
        <Modal
          // title={dict['model.action'] + '-' + (card && card.copyType === 'action' ? dict['model.copy'] : dict['model.edit'])}
          title="按钮·编辑"
          visible={visible}
          width={850}
          maskClosable={false}
          onCancel={this.editModalCancel}
          footer={[
            <CreateFunc key="create" dict={dict} ref="btnCreatFunc" trigger={this.creatFunc}/>,
            record && record.intertype === 'inner' ? <CreateFunc key="create" dict={dict} ref="btnCreatFunc" trigger={this.creatFunc}/> : null,
            <Button key="cancel" onClick={this.editModalCancel}>{dict['model.cancel']}</Button>,
            <Button key="confirm" type="primary" onClick={this.handleSubmit}>{dict['model.confirm']}</Button>
          ]}
@@ -484,6 +531,7 @@
            formlist={this.state.formlist}
            inputSubmit={this.handleSubmit}
            setting={config.setting}
            updRecord={(record) => this.setState({record: fromJS(record).toJS()})}
            wrappedComponentRef={(inst) => this.actionFormRef = inst}
          />
        </Modal>
@@ -507,39 +555,7 @@
          }}
          destroyOnClose
        >
          {card && !card.execMode && card.OpenType !== 'excelIn' && card.OpenType !== 'excelOut' ?
            <VerifyCard
              card={card}
              dict={dict}
              config={config}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.execMode ?
            <VerifyPrint
              card={card}
              dict={dict}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.OpenType === 'excelIn' ?
            <VerifyExcelIn
              card={card}
              dict={dict}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.OpenType === 'excelOut' ?
            <VerifyExcelOut
              card={card}
              dict={dict}
              config={config}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {this.getVerify(card)}
        </Modal>
      </div>
    )
src/menu/components/share/clockcomponent/index.jsx
@@ -42,7 +42,7 @@
    const { visible, loading } = this.state
    return (
      <div className="clock-component-wrap">
      <div className={'clock-component-wrap' + (config.timer ? ' tip-sign' : '')}>
        <ClockCircleOutlined title="定时器" onClick={this.trigger} />
        <Modal
          title="定时器设置"
src/menu/components/share/clockcomponent/index.scss
@@ -1,7 +1,20 @@
.clock-component-wrap {
  position: relative;
  display: inline-block;
  >.anticon-clock-circle {
    color: rgb(38, 194, 129);
  }
}
.clock-component-wrap.tip-sign::after {
  content: ' ';
  position: absolute;
  top: -2px;
  right: 2px;
  height: 8px;
  width: 8px;
  border-radius: 100%;
  background: #ff5b05;
  z-index: 1;
}
src/menu/components/share/logcomponent/index.jsx
@@ -103,10 +103,11 @@
  }
  render () {
    const { btnlog } = this.props
    const { visible, dict, data, columns } = this.state
    return (
      <div className="btn-log-wrap">
      <div className={'btn-log-wrap' + (btnlog && btnlog.length > 0 ? ' tip-sign' : '')}>
        <RollbackOutlined title="解除冻结" onClick={this.trigger} />
        <Modal
          wrapClassName="popview-modal"
src/menu/components/share/logcomponent/index.scss
@@ -1,4 +1,5 @@
.btn-log-wrap {
  position: relative;
  display: inline-block;
  >.anticon-rollback {
@@ -18,4 +19,15 @@
      padding: 5px;
    }
  }
}
.btn-log-wrap.tip-sign::after {
  content: ' ';
  position: absolute;
  top: -2px;
  right: 2px;
  height: 8px;
  width: 8px;
  border-radius: 100%;
  background: #ff5b05;
  z-index: 1;
}
src/menu/components/share/normalform/index.jsx
@@ -1,15 +1,29 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Input, Tooltip, InputNumber, Select, Radio, Cascader } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
// import './index.scss'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const MKTable = asyncComponent(() => import('@/components/normalform/modalform/mkTable'))
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,      // 字典项
    formlist: PropTypes.array,   // 表单
    inputSubmit: PropTypes.func  // 回车事件
  }
  state = {
    formlist: [],
  }
  UNSAFE_componentWillMount() {
    const { formlist } = this.props
    this.setState({formlist: fromJS(formlist).toJS()})
  }
  handleConfirm = () => {
@@ -33,141 +47,81 @@
    }
  }
  getFields() {
    const { formlist } = this.props
    const { getFieldDecorator } = this.props.form
    const fields = []
  optionChange = (item, val) => {
    if (item.key === 'click') {
      this.setState({formlist: fromJS(this.state.formlist).toJS().map(cell => {
        if (!['menu', 'menuType', 'open', 'joint', 'menus'].includes(cell.key)) return cell
        if (val === 'menu') {
          cell.hidden = !['menu', 'open', 'joint'].includes(cell.key)
        } else if (val === 'menus') {
          cell.hidden = !['menuType', 'open', 'joint', 'menus'].includes(cell.key)
        } else {
          cell.hidden = true
        }
    if (!formlist) return []
        return cell
      })})
    }
  }
  getFields() {
    const { formlist } = this.state
    const { getFieldDecorator } = this.props.form
    const fields = []
    formlist.forEach((item, index) => {
      if (item.hidden || item.forbid) return
      let content = null
      let label = item.tooltip ? <Tooltip placement="topLeft" title={item.tooltip}><QuestionCircleOutlined className="mk-form-tip" />{item.label}</Tooltip> : item.label
      if (item.type === 'text') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} onPressEnter={this.handleSubmit}/>)}
            </Form.Item>
          </Col>
        )
        content = (<Input placeholder="" autoComplete="off" onPressEnter={this.handleSubmit}/>)
      } else if (item.type === 'number') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<InputNumber min={item.min} max={item.max} precision={item.decimal} onPressEnter={this.handleSubmit}/>)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select') { // 下拉
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select mode={item.multi ? 'multiple' : ''}>
                  {item.options.map((option, index) =>
                    <Select.Option key={index} value={option.field || option.value}>
                      {option.label || option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
        content = (<InputNumber min={item.min} max={item.max} precision={item.decimal} onPressEnter={this.handleSubmit}/>)
      } else if (item.type === 'select') {
        content = (<Select mode={item.multi ? 'multiple' : ''}>
          {item.options.map((option, index) =>
            <Select.Option key={index} value={option.field || option.value}>
              {option.label || option.text}
            </Select.Option>
          )}
        </Select>)
      } else if (item.type === 'radio') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Radio.Group disabled={item.readonly}>
                  {item.options.map(option => {
                    return (
                      <Radio key={option.value} value={option.value}>{option.text}</Radio>
                    )
                  })}
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
        )
        content = (<Radio.Group onChange={(e) => this.optionChange(item, e.target.value)}>
          {item.options.map(option => {
            return (
              <Radio key={option.value} value={option.field || option.value}>{option.label || option.text}</Radio>
            )
          })}
        </Radio.Group>)
      } else if (item.type === 'cascader') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || [],
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Cascader options={item.options} placeholder=""/>
              )}
            </Form.Item>
          </Col>
        )
        content = (<Cascader options={item.options} placeholder=""/>)
      } else if (item.type === 'table') {
        content = (<MKTable columns={item.columns || []} actions={item.actions || []}/>)
      }
      if (!content) return
      fields.push(
        <Col span={item.span || 12} key={index}>
          <Form.Item label={label}>
            {getFieldDecorator(item.key, {
              initialValue: item.initVal,
              rules: [
                {
                  required: item.required,
                  message: (item.type === 'select' || item.type === 'cascader' ?  '请选择' : '请输入') + item.label + '!'
                }
              ]
            })(content)}
          </Form.Item>
        </Col>
      )
    })
    return fields
  }
@@ -184,7 +138,7 @@
    }
    return (
      <div>
      <div className="plot-base-wrap">
        <Form {...formItemLayout}><Row gutter={16}>{this.getFields()}</Row></Form>
      </div>
    )
src/menu/components/share/normalform/index.scss
@@ -0,0 +1,10 @@
.plot-base-wrap {
  .ant-col-24 {
    .ant-form-item-label {
      width: 16.5%;
    }
    .ant-form-item-control-wrapper {
      width: 83.5%;
    }
  }
}
src/menu/components/table/edit-table/columns/index.jsx
@@ -149,7 +149,7 @@
        val = column.formula
        if (column.eval === 'false') {
          val = val.replace(/\n/ig, '<br/>').replace(/\s/ig, '&nbsp;')
          val = <span dangerouslySetInnerHTML={{__html: val}}></span>
          val = <span style={{fontWeight: 'inherit'}} dangerouslySetInnerHTML={{__html: val}}></span>
        }
      }
      return (
@@ -199,18 +199,17 @@
      return uuid.join('')
    }) ()
    if (config.wrap && config.wrap.borderColor) { // 边框颜色
      let style = `#${tableId} table, #${tableId} tr, #${tableId} th, #${tableId} td {border-color: ${config.wrap.borderColor}}`
      let ele = document.createElement('style')
      ele.innerHTML = style
      document.getElementsByTagName('head')[0].appendChild(ele)
    }
    this.setState({
      tableId,
      columns: fromJS(config.cols).toJS(),
      fields: fromJS(config.columns).toJS(),
      lineMarks: config.lineMarks ? fromJS(config.lineMarks).toJS() : []
    }, () => {
      const element = document.getElementById(tableId)
      element && element.style.setProperty('--mk-table-border-color', config.wrap.borderColor || '#e8e8e8')
      element && element.style.setProperty('--mk-table-color', config.wrap.color || 'rgba(0, 0, 0, 0.65)')
      element && element.style.setProperty('--mk-table-font-size', config.wrap.fontSize || '14px')
      element && element.style.setProperty('--mk-table-font-weight', config.wrap.fontWeight || 'normal')
    })
  }
@@ -224,11 +223,12 @@
      }
    } else if (!is(fromJS(this.state.fields), fromJS(nextProps.config.columns))) {
      this.setState({fields: fromJS(nextProps.config.columns).toJS()})
    } else if (this.props.config.wrap.borderColor !== nextProps.config.wrap.borderColor) {
      let style = `#${this.state.tableId} table, #${this.state.tableId} tr, #${this.state.tableId} th, #${this.state.tableId} td {border-color: ${nextProps.config.wrap.borderColor}}`
      let ele = document.createElement('style')
      ele.innerHTML = style
      document.getElementsByTagName('head')[0].appendChild(ele)
    } else if (!is(fromJS(this.props.config.wrap), fromJS(nextProps.config.wrap))) {
      const element = document.getElementById(this.state.tableId)
      element && element.style.setProperty('--mk-table-border-color', nextProps.config.wrap.borderColor || '#e8e8e8')
      element && element.style.setProperty('--mk-table-color', nextProps.config.wrap.color || 'rgba(0, 0, 0, 0.65)')
      element && element.style.setProperty('--mk-table-font-size', nextProps.config.wrap.fontSize || '14px')
      element && element.style.setProperty('--mk-table-font-weight', nextProps.config.wrap.fontWeight || 'normal')
    }
  }
@@ -560,14 +560,6 @@
      }
    })
    let style = {}
    if (config.wrap.color) {
      style.color = config.wrap.color
    }
    if (config.wrap.fontSize) {
      style.fontSize = config.wrap.fontSize
    }
    return (
      <div className={`edit-table-columns ${config.setting.laypage} ${config.wrap.mode || ''}`} id={tableId}>
        <div className="col-control">
@@ -589,7 +581,6 @@
            rowKey="uuid"
            size={config.wrap.size || 'middle'}
            rowClassName="editable-row"
            style={style}
            bordered={config.wrap.bordered !== 'false'}
            rowSelection={rowSelection}
            components={components}
src/menu/components/table/edit-table/columns/index.scss
@@ -1,5 +1,10 @@
.edit-table-columns {
  position: relative;
  --mk-table-border-color: #e8e8e8;
  --mk-table-color: rgba(0, 0, 0, 0.65);
  --mk-table-font-size: 14px;
  --mk-table-font-weight: normal;
  .ant-table {
    color: inherit;
    font-size: inherit;
@@ -88,6 +93,22 @@
  .ant-table-small > .ant-table-content > .ant-table-body {
    margin: 0;
  }
  table, tr, th, td {
    border-color: var(--mk-table-border-color)!important;
  }
  table tr {
    th .ant-table-column-title, th .ant-table-column-title > span {
      // color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
    }
    td {
      color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
    }
  }
}
.edit-table-columns.false {
  .ant-pagination {
src/menu/components/table/edit-table/index.jsx
@@ -194,7 +194,12 @@
  changeStyle = () => {
    const { card } = this.state
    MKEmitter.emit('changeStyle', [card.uuid], ['background', 'border', 'padding', 'margin', 'shadow'], card.style)
    let style = {...card.style}
    style.color = card.wrap.color || 'rgba(0, 0, 0, 0.65)'
    style.fontSize = card.wrap.fontSize || 14
    style.fontWeight = card.wrap.fontWeight || 'normal'
    MKEmitter.emit('changeStyle', [card.uuid], ['font1', 'background', 'border', 'padding', 'margin', 'shadow'], style)
  }
  getStyle = (comIds, style) => {
@@ -202,7 +207,21 @@
    if (comIds[0] !== card.uuid || comIds.length !== 1) return
    let _card = {...card, style}
    let _card = fromJS(card).toJS()
    let _style = fromJS(style).toJS()
    let color = style.color
    let fontSize = style.fontSize
    let fontWeight = style.fontWeight
    delete _style.color
    delete _style.fontSize
    delete _style.fontWeight
    _card.style = _style
    _card.wrap.color = color
    _card.wrap.fontSize = fontSize
    _card.wrap.fontWeight = fontWeight
    this.setState({
      card: _card
@@ -310,13 +329,19 @@
  }
  updateWrap = (res) => {
    let card = {...this.state.card, wrap: res}
    const { card } = this.state
    res.color = card.wrap.color
    res.fontSize = card.wrap.fontSize
    res.fontWeight = card.wrap.fontWeight
    let _card = {...card, wrap: res}
    if (res.tableType) {
      card.switchable = true
      _card.switchable = true
    }
    
    this.updateComponent(card)
    this.updateComponent(_card)
  }
  clickComponent = (e) => {
@@ -354,6 +379,7 @@
        <SearchComponent config={card} updatesearch={this.updateconfig}/>
        <ActionComponent type="editable" config={card} setSubConfig={this.setSubConfig} updateaction={this.updateComponent}/>
        <ColumnComponent config={card} updatecolumn={this.updateconfig}/>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/table/edit-table/options.jsx
@@ -191,7 +191,7 @@
        {value: 'default', label: '大'},
        {value: 'middle', label: '中'},
        {value: 'small', label: '小'},
        // {value: 'mini', label: '微'},
        {value: 'mini', label: '迷你'},
      ]
    },
    {
@@ -225,24 +225,24 @@
      tooltip: '默认值 #e8e8e8。',
      required: false
    },
    {
      type: 'color',
      field: 'color',
      label: '字体颜色',
      initval: wrap.color || 'rgba(0, 0, 0, 0.65)',
      tooltip: '默认值 rgba(0, 0, 0, 0.65)。',
      required: false
    },
    {
      type: 'number',
      field: 'fontSize',
      label: '字体大小',
      initval: wrap.fontSize || 14,
      min: 12,
      max: 30,
      precision: 0,
      required: false
    },
    // {
    //   type: 'color',
    //   field: 'color',
    //   label: '字体颜色',
    //   initval: wrap.color || 'rgba(0, 0, 0, 0.65)',
    //   tooltip: '默认值 rgba(0, 0, 0, 0.65)。',
    //   required: false
    // },
    // {
    //   type: 'number',
    //   field: 'fontSize',
    //   label: '字体大小',
    //   initval: wrap.fontSize || 14,
    //   min: 12,
    //   max: 30,
    //   precision: 0,
    //   required: false
    // },
    {
      type: 'number',
      field: 'advanceWidth',
src/menu/components/table/normal-table/columns/index.jsx
@@ -164,7 +164,7 @@
        val = column.formula
        if (column.eval === 'false') {
          val = val.replace(/\n/ig, '<br/>').replace(/\s/ig, '&nbsp;')
          val = <span dangerouslySetInnerHTML={{__html: val}}></span>
          val = <span style={{fontWeight: 'inherit'}} dangerouslySetInnerHTML={{__html: val}}></span>
        }
      }
      return (
@@ -214,18 +214,17 @@
      return uuid.join('')
    }) ()
    if (config.wrap && config.wrap.borderColor) { // 边框颜色
      let style = `#${tableId} table, #${tableId} tr, #${tableId} th, #${tableId} td {border-color: ${config.wrap.borderColor}}`
      let ele = document.createElement('style')
      ele.innerHTML = style
      document.getElementsByTagName('head')[0].appendChild(ele)
    }
    this.setState({
      tableId,
      columns: fromJS(config.cols).toJS(),
      fields: fromJS(config.columns).toJS(),
      lineMarks: config.lineMarks ? fromJS(config.lineMarks).toJS() : []
    }, () => {
      const element = document.getElementById(tableId)
      element && element.style.setProperty('--mk-table-border-color', config.wrap.borderColor || '#e8e8e8')
      element && element.style.setProperty('--mk-table-color', config.wrap.color || 'rgba(0, 0, 0, 0.65)')
      element && element.style.setProperty('--mk-table-font-size', config.wrap.fontSize || '14px')
      element && element.style.setProperty('--mk-table-font-weight', config.wrap.fontWeight || 'normal')
    })
  }
@@ -239,11 +238,12 @@
      }
    } else if (!is(fromJS(this.state.fields), fromJS(nextProps.config.columns))) {
      this.setState({fields: fromJS(nextProps.config.columns).toJS()})
    } else if (this.props.config.wrap.borderColor !== nextProps.config.wrap.borderColor) {
      let style = `#${this.state.tableId} table, #${this.state.tableId} tr, #${this.state.tableId} th, #${this.state.tableId} td {border-color: ${nextProps.config.wrap.borderColor}}`
      let ele = document.createElement('style')
      ele.innerHTML = style
      document.getElementsByTagName('head')[0].appendChild(ele)
    } else if (!is(fromJS(this.props.config.wrap), fromJS(nextProps.config.wrap))) {
      const element = document.getElementById(this.state.tableId)
      element && element.style.setProperty('--mk-table-border-color', nextProps.config.wrap.borderColor || '#e8e8e8')
      element && element.style.setProperty('--mk-table-color', nextProps.config.wrap.color || 'rgba(0, 0, 0, 0.65)')
      element && element.style.setProperty('--mk-table-font-size', nextProps.config.wrap.fontSize || '14px')
      element && element.style.setProperty('--mk-table-font-weight', nextProps.config.wrap.fontWeight || 'normal')
    }
  }
@@ -573,14 +573,6 @@
    const columns = this.handlecolumns(this.state.columns, fields, config)
    let style = {}
    if (config.wrap.color) {
      style.color = config.wrap.color
    }
    if (config.wrap.fontSize) {
      style.fontSize = config.wrap.fontSize
    }
    return (
      <div className={`normal-table-columns ${config.setting.laypage} ${config.wrap.tableType} ${config.wrap.mode || ''}`} id={tableId}>
        <div className="col-control">
@@ -594,7 +586,6 @@
            rowKey="uuid"
            size={config.wrap.size || 'middle'}
            rowClassName="editable-row"
            style={style}
            bordered={config.wrap.bordered !== 'false'}
            components={components}
            dataSource={this.state.data}
src/menu/components/table/normal-table/columns/index.scss
@@ -1,8 +1,14 @@
.normal-table-columns {
  position: relative;
  --mk-table-border-color: #e8e8e8;
  --mk-table-color: rgba(0, 0, 0, 0.65);
  --mk-table-font-size: 14px;
  --mk-table-font-weight: normal;
  .ant-table {
    color: inherit;
    font-size: inherit;
    font-weight: inherit;
  }
  .ant-table-body {
    overflow-x: auto;
@@ -79,6 +85,22 @@
  .ant-table-small > .ant-table-content > .ant-table-body {
    margin: 0;
  }
  table, tr, th, td {
    border-color: var(--mk-table-border-color)!important;
  }
  table tr {
    th .ant-table-column-title {
      // color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
    }
    td {
      color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
    }
  }
}
.normal-table-columns.false {
  .ant-pagination {
src/menu/components/table/normal-table/index.jsx
@@ -248,20 +248,34 @@
  changeStyle = () => {
    const { card } = this.state
    MKEmitter.emit('changeStyle', [card.uuid], ['background', 'border', 'padding', 'margin', 'shadow'], card.style)
    let style = {...card.style}
    style.color = card.wrap.color || 'rgba(0, 0, 0, 0.65)'
    style.fontSize = card.wrap.fontSize || 14
    style.fontWeight = card.wrap.fontWeight || 'normal'
    MKEmitter.emit('changeStyle', [card.uuid], ['font1', 'background', 'border', 'padding', 'margin', 'shadow'], style)
  }
  getStyle = (comIds, style) => {
    const { card } = this.state
    if (comIds[0] !== card.uuid) return
    if (comIds[0] !== card.uuid || comIds.length !== 1) return
    let _card = {}
    if (comIds.length === 1) {
      _card = {...card, style}
    } else {
      return
    }
    let _card = fromJS(card).toJS()
    let _style = fromJS(style).toJS()
    let color = style.color
    let fontSize = style.fontSize
    let fontWeight = style.fontWeight
    delete _style.color
    delete _style.fontSize
    delete _style.fontWeight
    _card.style = _style
    _card.wrap.color = color
    _card.wrap.fontSize = fontSize
    _card.wrap.fontWeight = fontWeight
    this.setState({
      card: _card
@@ -416,7 +430,13 @@
  }
  updateWrap = (res) => {
    this.updateComponent({...this.state.card, wrap: res})
    const { card } = this.state
    res.color = card.wrap.color
    res.fontSize = card.wrap.fontSize
    res.fontWeight = card.wrap.fontWeight
    this.updateComponent({...card, wrap: res})
  }
  clickComponent = (e) => {
@@ -460,6 +480,7 @@
        <SearchComponent config={card} updatesearch={this.updateconfig}/>
        <ActionComponent config={card} setSubConfig={this.setSubConfig} updateaction={this.updateComponent}/>
        <ColumnComponent config={card} updatecolumn={this.updateconfig}/>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/table/normal-table/options.jsx
@@ -114,7 +114,7 @@
        {value: 'default', label: '大'},
        {value: 'middle', label: '中'},
        {value: 'small', label: '小'},
        // {value: 'mini', label: '微'},
        {value: 'mini', label: '迷你'},
      ]
    },
    {
@@ -161,24 +161,24 @@
      tooltip: '默认值 #e8e8e8。',
      required: false
    },
    {
      type: 'color',
      field: 'color',
      label: '字体颜色',
      initval: wrap.color || 'rgba(0, 0, 0, 0.65)',
      tooltip: '默认值 rgba(0, 0, 0, 0.65)。',
      required: false
    },
    {
      type: 'number',
      field: 'fontSize',
      label: '字体大小',
      initval: wrap.fontSize || 14,
      min: 12,
      max: 30,
      precision: 0,
      required: false
    },
    // {
    //   type: 'color',
    //   field: 'color',
    //   label: '字体颜色',
    //   initval: wrap.color || 'rgba(0, 0, 0, 0.65)',
    //   tooltip: '默认值 rgba(0, 0, 0, 0.65)。',
    //   required: false
    // },
    // {
    //   type: 'number',
    //   field: 'fontSize',
    //   label: '字体大小',
    //   initval: wrap.fontSize || 14,
    //   min: 12,
    //   max: 30,
    //   precision: 0,
    //   required: false
    // },
    {
      type: 'number',
      field: 'advanceWidth',
src/menu/components/timeline/normal-timeline/index.jsx
@@ -192,6 +192,7 @@
        </Popover>
        <NormalHeader hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        {card.subcards.map(subcard => (<CardSimpleComponent key={subcard.uuid} cards={card} card={subcard} updateElement={this.updateCard}/>))}
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/components/tree/antd-tree/index.jsx
@@ -194,6 +194,7 @@
            </TreeNode>
          </Tree>
        </div>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/menu/modalconfig/index.jsx
@@ -131,17 +131,22 @@
        index = i
      }
      let label = item.label || ''
      if (item.field && item.field.toLowerCase() !== label.toLowerCase()) {
        label = label + ' (' + item.field + ')'
      }
      if (['text', 'number', 'textarea', 'color'].includes(item.type) && card.field !== item.field) {
        _inputfields.push({
          field: item.field,
          label: _inputIndex + '、' + item.label
          label: _inputIndex + '、' + label
        })
        _inputIndex++
      }
      if (card.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type)) {
        _tabfields.push({
          field: item.field,
          label: _tabIndex + '、' + item.label
          label: _tabIndex + '、' + label
        })
        _tabIndex++
      }
@@ -149,7 +154,7 @@
      if (item.type === 'switch') {
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
      }
@@ -164,7 +169,7 @@
        })
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
        _linkIndex++
      }
src/menu/picturecontroller/editform/index.jsx
@@ -148,7 +148,7 @@
                    message: '色值最多100个字符!'
                  }
                ]
              })(<Input onPressEnter={() => this.props.inputSubmit()}/>)}
              })(<Input placeholder="例如:#1890ff、blue、rgba(0,0,0,0.65)" onPressEnter={() => this.props.inputSubmit()}/>)}
            </Form.Item>
          </Col> : null}
          <Col span={24}>
src/menu/picturecontroller/index.jsx
@@ -39,6 +39,15 @@
    let videos = sessionStorage.getItem('app_videos')
    let colorlist = sessionStorage.getItem('app_colors')
    if (!pictures) {
      notification.warning({
        top: 92,
        message: '未获取到资源信息,请稍后或刷新重试。',
        duration: 5
      })
      return
    }
    try {
      pictures = JSON.parse(pictures)
      videos = JSON.parse(videos)
src/menu/popview/index.jsx
@@ -5,6 +5,7 @@
import moment from 'moment'
import HTML5Backend from 'react-dnd-html5-backend'
import { notification, Modal, Collapse, Card, Switch, Button } from 'antd'
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'
import Api from '@/api'
import Utils from '@/utils/utils.js'
@@ -47,7 +48,8 @@
    oriConfig: null,
    config: null,
    customComponents: [],
    comloading: false
    comloading: false,
    eyeopen: false
  }
  UNSAFE_componentWillMount() {
@@ -237,7 +239,7 @@
    })
  }
  getMenuMessage = () => {
  getMenuMessage = (delButtons) => {
    const { config } = this.state
    let buttons = []
    let _sort = 1
@@ -252,6 +254,10 @@
          traversal(item.components)
        } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') {
              delButtons.push(btn.uuid)
              return
            }
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
@@ -259,12 +265,20 @@
          item.subcards && item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') {
                delButtons.push(cell.uuid)
                return
              }
              this.checkBtn(cell)
              buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
            })
            card.backElements && card.backElements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') {
                delButtons.push(cell.uuid)
                return
              }
              this.checkBtn(cell)
              buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
@@ -274,6 +288,10 @@
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') {
                delButtons.push(cell.uuid)
                return
              }
              this.checkBtn(cell)
              buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
@@ -282,18 +300,30 @@
        } else if (item.type === 'balcony') {
          item.elements && item.elements.forEach(cell => {
            if (cell.eleType !== 'button') return
            if (cell.hidden === 'true') {
              delButtons.push(cell.uuid)
              return
            }
            this.checkBtn(cell)
            buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
        } else if (item.type === 'line' || item.type === 'bar' || item.type === 'chart') {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') {
              delButtons.push(btn.uuid)
              return
            }
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
        } else if (item.type === 'table' && (item.subtype === 'normaltable' || item.subtype === 'editable')) {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') {
              delButtons.push(btn.uuid)
              return
            }
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
@@ -301,6 +331,10 @@
          item.cols && item.cols.forEach(col => {
            if (col.type !== 'action') return
            col.elements.forEach(btn => {
              if (btn.hidden === 'true') {
                delButtons.push(btn.uuid)
                return
              }
              this.checkBtn(btn)
              buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
@@ -354,7 +388,6 @@
  submitConfig = () => {
    const { btn } = this.props
    const { delButtons } = this.state
    let config = fromJS(this.state.config).toJS()
    if ((config.cacheUseful === 'true' && !config.cacheTime) || !config.MenuNo || !config.MenuName) {
@@ -402,13 +435,14 @@
        LText: []
      }
      let delButtons = fromJS(this.state.delButtons).toJS()
      if (sessionStorage.getItem('appType') === 'pc') {
        param.TypeCharOne = sessionStorage.getItem('kei_no')
        param.Typename = 'pc'
        btnParam.LText = ''
      } else {
        btnParam.LText = this.getMenuMessage()
        btnParam.LText = this.getMenuMessage(delButtons)
        btnParam.LText = btnParam.LText.join(' union all ')
        btnParam.LText = Utils.formatOptions(btnParam.LText)
        btnParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -628,7 +662,7 @@
  render () {
    const { btn } = this.props
    const { activeKey, comloading, dict, config, menuloading, customComponents, MenuId } = this.state
    const { activeKey, comloading, dict, config, menuloading, customComponents, MenuId, eyeopen } = this.state
    return (
      <div className="pc-poper-view">
@@ -659,11 +693,12 @@
                </Panel>
              </Collapse>
            </div>
            <div className={'menu-view ' + (menuloading ? 'saving' : '')}>
            <div className={'menu-view' + (menuloading ? ' saving' : '') + (eyeopen ? ' eye-open' : '')}>
              <Card title={
                <div> {config && config.MenuName} </div>
              } bordered={false} extra={
                <div>
                  <Button className="mk-border-purple" onClick={() => this.setState({eyeopen: !eyeopen})}>{!eyeopen ? <EyeOutlined /> : <EyeInvisibleOutlined />} 组件名</Button>
                  <Versions MenuId={MenuId} open_edition={config ? config.open_edition : ''} updateConfig={this.refreshConfig}/>
                  <ReplaceField type="custom" config={config} updateConfig={this.resetConfig}/>
                  <StyleCombControlButton menu={config} />
src/menu/popview/index.scss
@@ -1,6 +1,16 @@
.pc-poper-view {
  background: #000;
  min-height: 100vh;
  .eye-open {
    .component-name {
      display: block;
    }
    .anticon-tool {
      display: none;
    }
  }
  >.menu-body {
    width: 100vw;
    height: 100vh;
src/menu/stylecontroller/index.jsx
@@ -149,7 +149,13 @@
  changeFontSize = (val) => {
    let value = parseInt(val)
    if (isNaN(value) || value < 12 || value > 100) return
    if (isNaN(value)) return
    if (value < 12) {
      value = 12
    } else if (value > 300) {
      value = 300
    }
    this.updateStyle({fontSize: `${value}px`})
  }
@@ -430,7 +436,7 @@
              {options.includes('font') ? <Panel header="字体" key="font">
                <Col span={12}>
                  <Form.Item colon={false} label={<FontSizeOutlined title="字体大小"/>}>
                    <InputNumber defaultValue={card.fontSize || 14} min={12} max={100} precision={0} onChange={this.changeFontSize} />
                    <InputNumber defaultValue={card.fontSize || 14} min={12} max={300} precision={0} onChange={this.changeFontSize} />
                  </Form.Item>
                </Col>
                <Col span={12}>
@@ -512,6 +518,41 @@
                  </Form.Item>
                </Col>
              </Panel> : null}
              {options.includes('font1') ? <Panel header="字体" key="font1">
                <Col span={12}>
                  <Form.Item colon={false} label={<FontSizeOutlined title="字体大小"/>}>
                    <InputNumber defaultValue={card.fontSize || 14} min={12} max={300} precision={0} onChange={this.changeFontSize} />
                  </Form.Item>
                </Col>
                <Col span={12}>
                  <Form.Item colon={false} label={<BoldOutlined title="字体粗细"/>}>
                    <Select defaultValue={card.fontWeight || 'normal'} onChange={this.boldChange}>
                      <Option value="normal">normal</Option>
                      <Option value="bold">bold</Option>
                      <Option value="bolder">bolder</Option>
                      <Option value="lighter">lighter</Option>
                      <Option value="100">100</Option>
                      <Option value="200">200</Option>
                      <Option value="300">300</Option>
                      <Option value="400">400</Option>
                      <Option value="500">500</Option>
                      <Option value="600">600</Option>
                      <Option value="700">700</Option>
                      <Option value="800">800</Option>
                      <Option value="900">900</Option>
                    </Select>
                  </Form.Item>
                </Col>
                <Col span={24}>
                  <Form.Item
                    colon={false}
                    label={<FontColorsOutlined title="字体颜色"/>}
                    labelCol={{xs: { span: 24 }, sm: { span: 4 }}} wrapperCol={ {xs: { span: 24 }, sm: { span: 20 }} }
                  >
                    <ColorSketch value={card.color || 'rgba(0, 0, 0, 0.85)'} onChange={this.changeFontColor} />
                  </Form.Item>
                </Col>
              </Panel> : null}
              {options.includes('background') || options.includes('backgroundColor') ? <Panel header="背景" key="background">
                <Col span={24}>
                  <Form.Item
src/menu/viewnodes/index.jsx
New file
@@ -0,0 +1,344 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Modal, Button, notification, Spin } from 'antd'
import { ForkOutlined } from '@ant-design/icons'
import Api from '@/api'
import G6 from "@antv/g6"
import MKEmitter from '@/utils/events.js'
import './index.scss'
class Versions extends Component {
  static propTpyes = {
    config: PropTypes.object,
    MenuId: PropTypes.string
  }
  state = {
    visible: false,
    loading: false,
    empty: false
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  trigger = () => {
    const { MenuId, config } = this.props
    this.setState({visible: true, loading: true, empty: false}, () => {
      let param = {
        func: 's_get_menu_used_list',
        TypeCharOne: sessionStorage.getItem('kei_no'),
        typename: sessionStorage.getItem('typename'),
        par_menuid: MenuId
      }
      Api.getSystemConfig(param).then(result => {
        if (!result.status) {
          notification.warning({
            top: 92,
            message: result.message,
            duration: 5
          })
          this.setState({empty: true, loading: false})
          return
        }
        let data = {
          label: config.MenuName || '空',
          id: MenuId,
          MenuID: config.MenuID,
          MenuName: config.MenuName,
          MenuNo: config.MenuNo,
          children: []
        }
        let allMenus = JSON.parse(sessionStorage.getItem('allMenus'))
        let menuObj = {}
        allMenus.forEach(item => {
          menuObj[item.MenuID] = item
        })
        if (result.par_data) {
          result.par_data.forEach((item, i) => {
            let cell = {
              label: item.par_menuname,
              id: 'par' + i,
              MenuID: item.par_menuid,
              MenuName: item.par_menuname,
              MenuNo: item.par_menuno,
              deleted: false,
              direction: 'left'
            }
            if (menuObj[item.par_menuid]) {
              cell.color = '#5AD8A6'
              cell.label = menuObj[item.par_menuid].MenuName || item.par_menuname
              cell.MenuName = menuObj[item.par_menuid].MenuName || item.par_menuname
              cell.MenuNo = menuObj[item.par_menuid].MenuNo || item.par_menuno
            } else {
              cell.color = '#dddddd'
              cell.deleted = true
            }
            data.children.push(cell)
          })
        }
        if (result.used_data) {
          result.used_data.forEach((item, i) => {
            let cell = {
              label: item.used_menuname,
              id: 'sub' + i,
              MenuID: item.used_menuid,
              MenuName: item.used_menuname,
              MenuNo: '',
              deleted: false,
              direction: 'right'
            }
            if (menuObj[item.used_menuid]) {
              cell.color = '#5AD8A6'
              cell.label = menuObj[item.used_menuid].MenuName || item.used_menuname
              cell.MenuName = menuObj[item.used_menuid].MenuName || item.used_menuname
              cell.MenuNo = menuObj[item.used_menuid].MenuNo
            } else {
              cell.color = '#dddddd'
              cell.deleted = true
            }
            data.children.push(cell)
          })
        }
        if (data.children.length === 0) {
          this.setState({empty: true, loading: false})
        } else {
          this.setState({loading: false})
          this.getForks(data)
        }
      })
    })
  }
  changeMenu = (menu) => {
    if (menu.depth === 0) return
    MKEmitter.emit('changeEditMenu', menu)
  }
  getForks = (data) => {
    const { Util } = G6
    const that = this
    G6.registerNode(
      'dice-mind-map-root', {
        jsx: (cfg) => {
          const width = Util.getTextSize(cfg.label, 14)[0] + 12;
          const stroke = cfg.style.stroke || '#096dd9';
          return `
          <group>
            <rect draggable="true" style={{width: ${width}, height: 30, stroke: ${stroke}, radius: 4}} keyshape>
              <text style={{ fontSize: 14, marginLeft: 6, marginTop: 6 }}>${cfg.label}</text>
            </rect>
          </group>
        `;
        },
        getAnchorPoints() {
          return [
            [0, 0.5],
            [1, 0.5],
          ];
        },
      },
      'single-node',
    );
    G6.registerNode(
      'dice-mind-map-leaf', {
        jsx: (cfg) => {
          const width = Util.getTextSize(cfg.label, 12)[0] + 24;
          const color = cfg.color;
          return `
          <group>
            <rect draggable="true" style={{width: ${width}, height: 26, cursor: 'pointer', fill: 'transparent' }}>
              <text style={{ fontSize: 12, fill: ${cfg.deleted ? '#959595' : 'black'}, marginLeft: 12, marginTop: 6 }}>${cfg.label}</text>
            </rect>
            <rect style={{ fill: ${color}, width: ${width}, cursor: 'pointer', height: 2, x: 0, y: 32 }} />
          </group>
        `;
        },
        getAnchorPoints() {
          return [
            [0, 0.965],
            [1, 0.965],
          ];
        },
      },
      'single-node',
    );
    G6.registerBehavior('dice-mindmap', {
      getEvents() {
        return {
          'node:dblclick': 'editNode',
        };
      },
      editNode(evt) {
        const item = evt.item;
        const model = item.get('model');
        that.changeMenu(model)
      }
    });
    G6.registerBehavior('scroll-canvas', {
      getEvents: function getEvents() {
        return {
          wheel: 'onWheel',
        };
      },
      onWheel: function onWheel(ev) {
        const {
          graph
        } = this;
        if (!graph) {
          return;
        }
        if (ev.ctrlKey) {
          const canvas = graph.get('canvas');
          const point = canvas.getPointByClient(ev.clientX, ev.clientY);
          let ratio = graph.getZoom();
          if (ev.wheelDelta > 0) {
            ratio += ratio * 0.05;
          } else {
            ratio *= ratio * 0.05;
          }
          graph.zoomTo(ratio, {
            x: point.x,
            y: point.y,
          });
        } else {
          const x = ev.deltaX || ev.movementX;
          const y = ev.deltaY || ev.movementY || (-ev.wheelDelta * 125) / 3;
          graph.translate(-x, -y);
        }
        ev.preventDefault();
      },
    });
    const dataTransform = (data) => {
      const changeData = (d, level = 0, color) => {
        const data = {
          ...d,
        };
        switch (level) {
          case 0:
            data.type = 'dice-mind-map-root';
            break;
          default:
            data.type = 'dice-mind-map-leaf';
            break;
        }
        data.hover = false;
        if (color) {
          data.color = color;
        }
        if (d.children) {
          data.children = d.children.map((child) => changeData(child, level + 1, data.color));
        }
        return data;
      };
      return changeData(data);
    };
    const tree = new G6.TreeGraph({
      container: 'mountNode',
      width: 600,
      height: 350,
      fitView: true,
      fitViewPadding: [10, 20],
      layout: {
        type: 'mindmap',
        direction: 'H',
        getHeight: () => {
          return 16;
        },
        getWidth: (node) => {
          return node.level === 0 ?
            Util.getTextSize(node.label, 16)[0] + 12 :
            Util.getTextSize(node.label, 12)[0];
        },
        getVGap: () => {
          return 10;
        },
        getHGap: () => {
          return 60;
        },
        getSide: (node) => {
          return node.data.direction;
        },
      },
      defaultEdge: {
        type: 'cubic-horizontal',
        style: {
          lineWidth: 2,
        },
      },
      minZoom: 0.5,
      modes: {
        default: ['drag-canvas', 'zoom-canvas', 'dice-mindmap'],
      },
    });
    tree.data(dataTransform(data));
    tree.render();
  }
  render() {
    const { visible, loading, empty } = this.state
    return (
      <div style={{display: 'inline-block'}}>
        <Button style={{borderColor: 'orange', color: 'orange'}} onClick={this.trigger}><ForkOutlined /> 页面关系图</Button>
        <Modal
          title=""
          wrapClassName="view-nodes-modal"
          visible={visible}
          width={700}
          closable={false}
          maskClosable={false}
          footer={[]}
          destroyOnClose
        >
          <div className="header">页面关系图</div>
          <div className="wrap">
            {loading ? <Spin size="large" /> : null}
            {empty ? <div className="empty">未查询到页面关联菜单。</div> : null}
            <div className="mountNode" id="mountNode"></div>
          </div>
          <div className="footer">
            <Button key="cancel" onClick={() => { this.setState({ visible: false })}}>关闭</Button>
          </div>
        </Modal>
      </div>
    )
  }
}
export default Versions
src/menu/viewnodes/index.scss
New file
@@ -0,0 +1,40 @@
.view-nodes-modal {
  .ant-modal-body {
    padding: 20px 50px 20px;
    user-select: none;
  }
  .ant-modal-footer {
    display: none;
  }
  .wrap {
    position: relative;
    height: 350px;
    margin: 10px 0px;
    overflow: hidden;
    .ant-spin {
      position: absolute;
      top: calc(50% - 16px);
      left: calc(50% - 16px);
    }
    .empty {
      position: relative;
      text-align: center;
      color: #959595;
      padding-top: 200px;
    }
    .empty + .mountNode {
      opacity: 0;
    }
  }
  .header {
    color: #1890ff;
    font-weight: 500;
    text-align: center;
    font-size: 18px;
  }
  .footer {
    text-align: center;
  }
}
src/mob/components/menubar/common-menubar/index.jsx
@@ -182,6 +182,7 @@
            </div>
          </Col>
        </div>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/mob/components/menubar/normal-menubar/index.jsx
@@ -299,6 +299,7 @@
        <div className={(card.wrap.layout || 'grid') + '-layout'}>
          {card.subMenus.map((menu, index) => (<MenuComponent key={menu.uuid} cards={card} card={menu} move={this.move} updateElement={this.updateCard} deleteElement={this.deleteCard}/>))}
        </div>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/mob/components/menubar/normal-menubar/menucomponent/options.jsx
@@ -136,7 +136,7 @@
      label: '字体大小',
      initval: setting.iconFont || 20,
      min: 12,
      max: 200,
      max: 300,
      precision: 0,
      required: true
    },
src/mob/components/search/single-search/index.jsx
@@ -152,6 +152,7 @@
        } trigger="hover">
          <ToolOutlined />
        </Popover>
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/mob/modalconfig/index.jsx
@@ -152,17 +152,22 @@
        index = i
      }
      let label = item.label || ''
      if (item.field && item.field.toLowerCase() !== label.toLowerCase()) {
        label = label + ' (' + item.field + ')'
      }
      if (['text', 'number', 'textarea', 'color'].includes(item.type) && card.field !== item.field) {
        _inputfields.push({
          field: item.field,
          label: _inputIndex + '、' + item.label
          label: _inputIndex + '、' + label
        })
        _inputIndex++
      }
      if (card.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type)) {
        _tabfields.push({
          field: item.field,
          label: _tabIndex + '、' + item.label
          label: _tabIndex + '、' + label
        })
        _tabIndex++
      }
@@ -170,7 +175,7 @@
      if (item.type === 'switch') {
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
      }
@@ -185,7 +190,7 @@
        })
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
        _linkIndex++
      }
src/pc/components/login/normal-login/index.jsx
@@ -188,6 +188,7 @@
          <ToolOutlined />
        </Popover>
        <LoginForm loginWays={card.loginWays} wrap={card.wrap} />
        <div className="component-name"><div className="center">{card.name}</div></div>
      </div>
    )
  }
src/setupProxy.js
@@ -2,7 +2,7 @@
// const host = 'http://qingqiumarket.cn'
// const service = 'mkwms/'
module.exports = function() {}
module.exports = function(app) {}
// module.exports = function(app) {
//   app.use(proxy('/webapi', { 
//     target: `${host}/${service}webapi`,
@@ -42,11 +42,14 @@
//   }))
//   app.use(proxy('/trans', {
//     target: `${host}/${service}trans`,
//     target: `${host}/${service}`,
//     secure: false,
//     changeOrigin: true,
//     pathRewrite: {
//     '^/trans': '/'
//     }
//     changeOrigin: true
//   }))
//   app.use(proxy('/api', { // 旷视面板机接口测试
//     target: `http://192.168.1.66:80`,
//     secure: false,
//     changeOrigin: true
//   }))
// }
src/tabviews/commontable/index.jsx
@@ -159,7 +159,7 @@
      // 权限过滤
      if (this.props.menuType !== 'HS') {
        config.action = config.action.filter(item => permAction[item.uuid])
        config.action = config.action.filter(item => item.hidden !== 'true' && permAction[item.uuid])
        config.tabgroups.forEach(group => {
          group.sublist = group.sublist.filter(tab => {
            if (tab.supMenu === 'mainTable') {
@@ -169,6 +169,7 @@
          })
        })
      } else {
        config.action = config.action.filter(item => item.hidden !== 'true')
        config.tabgroups.forEach(group => {
          group.sublist = group.sublist.map(tab => {
            if (tab.supMenu === 'mainTable') {
@@ -182,17 +183,22 @@
      config.tabgroups = config.tabgroups.filter(group => group.sublist.length > 0)
      let roleId = sessionStorage.getItem('role_id') || '' // 角色ID
      // 视图权限
      config.charts = config.charts.filter(item => {
        if (item.Hide === 'true') return false
        if (!item.blacklist || item.blacklist.length === 0) return true
        return item.blacklist.filter(v => roleId.indexOf(v) > -1).length === 0
      })
      if (config.charts.length <= 1) {
        config.expand = true
      let chartId = ''
      if (config.charts) {
        // 视图权限
        config.charts = config.charts.filter(item => {
          if (item.Hide === 'true') return false
          if (!item.blacklist || item.blacklist.length === 0) return true
          return item.blacklist.filter(v => roleId.indexOf(v) > -1).length === 0
        })
        if (config.charts.length <= 1) {
          config.expand = true
        }
        chartId = config.charts[0] ? config.charts[0].uuid : ''
      }
      let chartId = config.charts[0] ? config.charts[0].uuid : ''
      config.search = Utils.initSearchVal(config.search)
@@ -1128,7 +1134,7 @@
        {searchlist && searchlist.length ?
          <MainSearch BID={BID} searchlist={searchlist} setting={setting} menuType={this.props.menuType} refreshdata={this.refreshbysearch}/> : null
        }
        {setting ? <Row className="chart-view" gutter={16}>
        {setting && config.charts ? <Row className="chart-view" gutter={16}>
          {/* 视图组 */}
          {!config.expand ? <Tabs activeKey={chartId} onChange={this.changeChart}>
            {config.charts.map(item => (
@@ -1208,6 +1214,41 @@
            }
          })}
        </Row> : null }
        {setting && !config.charts ? <div className="chart-view">
          <div className="commontable-main-action">
            <MainAction
              BID={BID}
              setting={setting}
              actions={actions}
              columns={columns}
              dict={this.state.dict}
              MenuID={MenuID}
              selectedData={selectedData}
              ContainerId={this.state.ContainerId}
            />
          </div>
          <div className="main-table-box">
            {(setting.tableType === 'radio' || setting.tableType === 'checkbox') && this.state.data && this.state.data.length > 0 ?
              <Switch title="收起" className="main-pickup" checkedChildren="开" unCheckedChildren="关" checked={pickup} onChange={this.pickupChange} /> : null
            }
            <MainTable
              MenuID={MenuID}
              tableId={MenuID}
              pickup={pickup}
              setting={setting}
              columns={columns}
              pageSize={pageSize}
              dict={this.state.dict}
              data={this.state.data}
              total={this.state.total}
              loading={this.state.loading}
              statFValue={this.state.statFValue}
              ContainerId={this.state.ContainerId}
              refreshdata={this.refreshbytable}
              chgSelectData={this.changeSelectedData}
            />
          </div>
        </div> : null }
        {setting && config.tabgroups.map(group => (
          <Tabs key={group.uuid}>
            {group.sublist.map(_tab => {
src/tabviews/custom/components/card/balcony/index.jsx
@@ -119,6 +119,7 @@
    MKEmitter.addListener('reloadData', this.reloadData)
    MKEmitter.addListener('syncBalconyData', this.syncBalconyData)
    MKEmitter.addListener('resetSelectLine', this.resetParentParam)
    MKEmitter.addListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.addListener('refreshByButtonResult', this.refreshByButtonResult)
    this.handleTimer()
  }
@@ -135,6 +136,7 @@
    MKEmitter.removeListener('reloadData', this.reloadData)
    MKEmitter.removeListener('syncBalconyData', this.syncBalconyData)
    MKEmitter.removeListener('resetSelectLine', this.resetParentParam)
    MKEmitter.removeListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult)
  }
@@ -283,6 +285,22 @@
    }
  }
  /**
   * @description 导出Excel时,获取页面搜索排序等参数
   */
  queryModuleParam = (menuId, btnId) => {
    const { config } = this.state
    if (config.uuid !== menuId) return
    MKEmitter.emit('returnModuleParam', config.uuid, btnId, {
      arr_field: '',
      orderBy: '',
      search: [],
      menuName: config.name
    })
  }
  reloadData = (menuId) => {
    const { config } = this.state
src/tabviews/custom/components/card/cardcellList/index.jsx
@@ -21,6 +21,7 @@
const NewPageButton = asyncComponent(() => import('@/tabviews/zshare/actionList/newpagebutton'))
const ChangeUserButton = asyncComponent(() => import('@/tabviews/zshare/actionList/changeuserbutton'))
const PrintButton = asyncComponent(() => import('@/tabviews/zshare/actionList/printbutton'))
const FuncMegvii = asyncComponent(() => import('@/tabviews/zshare/actionList/funcMegvii'))
const BarCode = asyncElementComponent(() => import('@/components/barcode'))
const QrCode = asyncElementComponent(() => import('@/components/qrcode'))
const MkProgress = asyncElementComponent(() => import('@/components/mkProgress'))
@@ -71,6 +72,14 @@
  openNewView = (e, card) => {
    const { cardCell, data, cards } = this.props
    if (data.$disabled) return
    if (card.anchors && card.anchors.length > 0) {
      let id = card.anchors[card.anchors.length - 1]
      let node = document.getElementById('anchor' + id)
      node && node.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'})
    }
    if (!card.link) return
    e.stopPropagation()
@@ -348,10 +357,10 @@
        }
      }
      if (card.link) {
      if (card.link || (card.anchors && card.anchors.length > 0)) {
        _style.cursor = 'pointer'
      }
      if (card.bgImage && data[card.bgImage]) {
        _style.backgroundImage = `url('${data[card.bgImage]}')`
      }
@@ -873,6 +882,21 @@
              />
            </Col>
          )
        } else if (card.funcType === 'megvii') {
          return (
            <Col key={card.uuid} className="mk-cell-btn" style={card.wrapStyle} span={card.width}>
              <FuncMegvii
                BID={data.$$BID}
                disabled={_disabled}
                lineId={data.$$key || ''}
                btn={card}
                show={card.show}
                style={card.style}
                setting={cards.setting}
                selectedData={_data}
              />
            </Col>
          )
        }
      }
    }
src/tabviews/custom/components/card/data-card/index.jsx
@@ -744,7 +744,7 @@
    }
    return (
      <div className="custom-data-card-box" style={config.style}>
      <div className="custom-data-card-box" id={'anchor' + config.uuid} style={config.style}>
        {loading ?
          <div className="loading-mask">
            {data ? <div className="ant-spin-blur"></div> : null}
src/tabviews/custom/components/card/prop-card/index.jsx
@@ -129,6 +129,7 @@
  componentDidMount () {
    MKEmitter.addListener('reloadData', this.reloadData)
    MKEmitter.addListener('resetSelectLine', this.resetParentParam)
    MKEmitter.addListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.addListener('refreshByButtonResult', this.refreshByButtonResult)
    this.handleTimer()
  }
@@ -144,6 +145,7 @@
    }
    MKEmitter.removeListener('reloadData', this.reloadData)
    MKEmitter.removeListener('resetSelectLine', this.resetParentParam)
    MKEmitter.removeListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult)
  }
@@ -292,6 +294,25 @@
    }
  }
  /**
   * @description 导出Excel时,获取页面搜索排序等参数
   */
  queryModuleParam = (menuId, btnId) => {
    const { mainSearch } = this.props
    const { config } = this.state
    if (config.uuid !== menuId) return
    let searches = config.setting.useMSearch && mainSearch ? mainSearch : []
    MKEmitter.emit('returnModuleParam', config.uuid, btnId, {
      arr_field: '',
      orderBy: '',
      search: searches,
      menuName: config.name
    })
  }
  reloadData = (menuId) => {
    const { config } = this.state
@@ -399,7 +420,7 @@
    if (config.wrap.empty === 'hidden' && (!data || data.$$empty)) return null
    return (
      <div className="custom-prop-card-box" style={config.style}>
      <div className="custom-prop-card-box" id={'anchor' + config.uuid} style={config.style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/card/table-card/index.jsx
@@ -422,7 +422,7 @@
    if (config.wrap.empty === 'hidden' && (!data || data.length === 0)) return null
    
    return (
      <div className="custom-table-card-box" style={{...config.style}}>
      <div className="custom-table-card-box" id={'anchor' + config.uuid} style={{...config.style}}>
        {loading ?
          <div className="loading-mask">
            {data ? <div className="ant-spin-blur"></div> : null}
src/tabviews/custom/components/carousel/data-card/index.jsx
@@ -258,7 +258,7 @@
    if (config.wrap.empty === 'hidden' && (!data || data.length === 0)) return null
    
    return (
      <div className="custom-data-carousel-box" style={config.style}>
      <div className="custom-data-carousel-box" id={'anchor' + config.uuid} style={config.style}>
        {loading ?
          <div className="loading-mask">
            {data ? <div className="ant-spin-blur"></div> : null}
src/tabviews/custom/components/carousel/prop-card/index.jsx
@@ -97,7 +97,8 @@
  componentDidMount () {
    MKEmitter.addListener('reloadData', this.reloadData)
    MKEmitter.addListener('resetSelectLine', this.resetParentParam)
    MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult)
    MKEmitter.addListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.addListener('refreshByButtonResult', this.refreshByButtonResult)
  }
  shouldComponentUpdate (nextProps, nextState) {
@@ -110,6 +111,7 @@
    }
    MKEmitter.removeListener('reloadData', this.reloadData)
    MKEmitter.removeListener('resetSelectLine', this.resetParentParam)
    MKEmitter.removeListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult)
  }
@@ -180,6 +182,25 @@
        this.loadData()
      })
    }
  }
  /**
   * @description 导出Excel时,获取页面搜索排序等参数
   */
  queryModuleParam = (menuId, btnId) => {
    const { mainSearch } = this.props
    const { config } = this.state
    if (config.uuid !== menuId) return
    let searches = config.setting.useMSearch && mainSearch ? mainSearch : []
    MKEmitter.emit('returnModuleParam', config.uuid, btnId, {
      arr_field: '',
      orderBy: '',
      search: searches,
      menuName: config.name
    })
  }
  reloadData = (menuId) => {
@@ -255,7 +276,7 @@
    if (config.wrap.empty === 'hidden' && (!data || data.$$empty)) return null
    return (
      <div className="custom-prop-carousel-box" style={config.style}>
      <div className="custom-prop-carousel-box" id={'anchor' + config.uuid} style={config.style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/chart/antv-bar-line/index.jsx
@@ -296,8 +296,12 @@
          marker: { symbol: item.chartType === 'bar' ? 'square' : 'hyphen', style: { stroke: item.color,fill: item.color, r: 5, lineWidth: 2 } }
        })
        if ((!_config.plot.Bar_axis || item.chartType !== 'bar') && item.show) { // 折线图或重叠下的柱状图可单独设置显示类型
          vFieldsShow[item.type] = item.show
        if (!_config.plot.Bar_axis || item.chartType !== 'bar') { // 折线图或重叠下的柱状图可单独设置显示类型
          if (item.show) {
            vFieldsShow[item.type] = item.show
          } else {
            item.show = _config.plot.show
          }
        }
      })
      _config.plot.customs = fields
@@ -1712,7 +1716,60 @@
    chart.on('element:click', (ev) => {
      let data = ev.data.data
      MKEmitter.emit('resetSelectLine', config.uuid, (data ? data.$$uuid : ''), data)
      if (plot.click === 'menus') {
        let menu = null
        if (plot.menus && plot.menus.length > 0) {
          let s = data[plot.menuType] || ''
          plot.menus.forEach(m => {
            if (s !== m.sign) return
            menu = m
          })
        }
        if (!menu || !menu.MenuID) return
        menu.type = menu.tabType
        let newtab = {
          ...menu,
          param: {}
        }
        if (plot.joint === 'true') {
          newtab.param.$BID = data.$$uuid || ''
        }
        if (['linkage_navigation', 'linkage', 'menu_board'].includes(window.GLOB.navBar)) {
          MKEmitter.emit('modifyTabs', newtab, 'replace')
        } else {
          MKEmitter.emit('modifyTabs', newtab, 'plus', true)
        }
      } else if (plot.click === 'menu' && plot.MenuID) {
        let menu = {
          MenuID: plot.MenuID,
          MenuName: plot.MenuName,
          MenuNo: plot.MenuNo,
          type: plot.tabType
        }
        let newtab = {
          ...menu,
          param: {}
        }
        if (plot.joint === 'true') {
          newtab.param.$BID = data.$$uuid || ''
        }
        if (['linkage_navigation', 'linkage', 'menu_board'].includes(window.GLOB.navBar)) {
          MKEmitter.emit('modifyTabs', newtab, 'replace')
        } else {
          MKEmitter.emit('modifyTabs', newtab, 'plus', true)
        }
      }
    })
    if (plot.interaction && plot.interaction.length) {
@@ -1781,7 +1838,7 @@
    }
    return (
      <div className="custom-line-chart-plot-box" style={style}>
      <div className="custom-line-chart-plot-box" id={'anchor' + config.uuid} style={style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/chart/antv-dashboard/index.jsx
@@ -642,7 +642,7 @@
    const { config, loading } = this.state
    return (
      <div className="custom-dashboard-plot-box" style={config.style}>
      <div className="custom-dashboard-plot-box" id={'anchor' + config.uuid} style={config.style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/chart/antv-pie/index.jsx
@@ -1054,7 +1054,7 @@
    }
    return (
      <div className="custom-pie-chart-plot-box" style={style}>
      <div className="custom-pie-chart-plot-box" id={'anchor' + config.uuid} style={style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/chart/antv-scatter/index.jsx
@@ -447,7 +447,7 @@
    }
    return (
      <div className="custom-scatter-plot-box" style={style}>
      <div className="custom-scatter-plot-box" id={'anchor' + config.uuid} style={style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/chart/custom-chart/index.jsx
@@ -380,7 +380,7 @@
    const { config, loading, empty, BID } = this.state
    return (
      <div className="custom-chart-plot-box" style={config.style}>
      <div className="custom-chart-plot-box" id={'anchor' + config.uuid} style={config.style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/code/sand-box/index.jsx
@@ -208,7 +208,7 @@
    const { config, loading, html } = this.state
    return (
      <div className="custom-sand-box" style={{...config.style}}>
      <div className="custom-sand-box" id={'anchor' + config.uuid} style={{...config.style}}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/editor/braft-editor/index.jsx
@@ -177,7 +177,7 @@
    const { config, loading, data } = this.state
    return (
      <div className="custom-braft-editor-box" style={config.style}>
      <div className="custom-braft-editor-box" id={'anchor' + config.uuid} style={config.style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/form/normal-form/index.jsx
@@ -359,7 +359,7 @@
    const { config, loading, BID, data, group, dict, step } = this.state
    return (
      <div className="custom-normal-form-box" style={{...config.style}}>
      <div className="custom-normal-form-box" id={'anchor' + config.uuid} style={{...config.style}}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/form/tab-form/index.jsx
@@ -307,7 +307,7 @@
    const { config, loading, BID, data, group, dict } = this.state
    return (
      <div className="custom-tab-form-box" style={{...config.style}}>
      <div className="custom-tab-form-box" id={'anchor' + config.uuid} style={{...config.style}}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/components/share/normalTable/index.jsx
@@ -185,7 +185,7 @@
          content = content.replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,')
        }
  
        content = col.prefix + content + col.postfix
        content = (col.prefix || '') + content + (col.postfix || '')
      }
      if (col.marks) {
@@ -224,7 +224,12 @@
      let photos = ''
      if (record[col.field]) {
        photos = `${record[col.field]}`
        photos = photos.split(',')
      }
      if (/^data:image/.test(photos)) {
        photos = [photos]
      } else {
        photos = photos.split(',').filter(Boolean)
      }
      let cols = 24 / (col.picSort || 1)
@@ -254,7 +259,7 @@
      }
      if (content) {
        content = col.prefix + content + col.postfix
        content = (col.prefix || '') + content + (col.postfix || '')
      }
      if (col.blur) {
@@ -480,12 +485,12 @@
      return uuid.join('')
    }) ()
    if (setting.borderColor) { // 边框颜色
      let style = `#${tableId} table, #${tableId} tr, #${tableId} th, #${tableId} td {border-color: ${setting.borderColor}}`
      let ele = document.createElement('style')
      ele.innerHTML = style
      document.getElementsByTagName('head')[0].appendChild(ele)
    }
    // if (setting.borderColor) { // 边框颜色
    //   let style = `#${tableId} table, #${tableId} tr, #${tableId} th, #${tableId} td {border-color: ${setting.borderColor}}`
    //   let ele = document.createElement('style')
    //   ele.innerHTML = style
    //   document.getElementsByTagName('head')[0].appendChild(ele)
    // }
    let size = (setting.pageSize || 10) + ''
    let pageOptions = ['10', '25', '50', '100', '500', '1000']
@@ -502,6 +507,12 @@
      rowspans,
      tableId,
      orderfields
    }, () => {
      const element = document.getElementById(tableId)
      element && element.style.setProperty('--mk-table-border-color', setting.borderColor || '#e8e8e8')
      element && element.style.setProperty('--mk-table-color', setting.color || 'rgba(0, 0, 0, 0.65)')
      element && element.style.setProperty('--mk-table-font-size', setting.fontSize || '14px')
      element && element.style.setProperty('--mk-table-font-weight', setting.fontWeight || 'normal')
    })
  }
@@ -892,7 +903,7 @@
        }
        <Table
          components={components}
          style={setting.style}
          // style={setting.style}
          size={setting.size || 'middle'}
          bordered={setting.bordered !== 'false'}
          rowSelection={rowSelection}
src/tabviews/custom/components/share/normalTable/index.scss
@@ -1,6 +1,10 @@
.normal-custom-table {
  position: relative;
  padding: 0px;
  --mk-table-border-color: #e8e8e8;
  --mk-table-color: rgba(0, 0, 0, 0.65);
  --mk-table-font-size: 14px;
  --mk-table-font-weight: normal;
  .normal-table-footer {
    padding: 10px 0px;
@@ -188,6 +192,26 @@
      display: block;
    }
  }
  table, tr, th, td {
    border-color: var(--mk-table-border-color)!important;
  }
  table tr {
    th .ant-table-column-title {
      // color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
    }
    td {
      color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
      >span, div:not(.card-cell-list), div:not(.card-cell-list) div, div:not(.card-cell-list) span {
        font-weight: var(--mk-table-font-weight)!important;
      }
    }
  }
}
.normal-custom-table:not(.fixed-height) {
  .ant-table-body::-webkit-scrollbar {
src/tabviews/custom/components/table/edit-table/index.jsx
@@ -54,7 +54,7 @@
    const { BID, BData } = this.props
    let _config = fromJS(this.props.config).toJS()
    let _cols = new Map()
    let setting = {..._config.setting, ..._config.wrap, style: {}}
    let setting = {..._config.setting, ..._config.wrap}
    setting.tableId = Utils.getuuid()
    _config.submit.style = _config.submit.style || {}
@@ -132,12 +132,12 @@
      }
    }
    if (setting.color) {
      setting.style.color = setting.color
    }
    if (setting.fontSize) {
      setting.style.fontSize = setting.fontSize
    }
    // if (setting.color) {
    //   setting.style.color = setting.color
    // }
    // if (setting.fontSize) {
    //   setting.style.fontSize = setting.fontSize
    // }
    if (!_config.lineMarks || _config.lineMarks.length === 0) {
      _config.lineMarks = null
@@ -589,7 +589,7 @@
    const { BID, setting, searchlist, actions, config, columns, BData, selectedData, lock } = this.state
    return (
      <div className="custom-edit-table" style={config.style}>
      <div className="custom-edit-table" id={'anchor' + config.uuid} style={config.style}>
        <NormalHeader config={config}/>
        {searchlist && searchlist.length ?
          <MainSearch BID={BID} setting={config.wrap} searchlist={searchlist} menuType={this.props.menuType} refreshdata={this.refreshbysearch}/> : null
src/tabviews/custom/components/table/edit-table/normalTable/index.jsx
@@ -395,7 +395,7 @@
          content = content.replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,')
        }
  
        content = col.prefix + content + col.postfix
        content = (col.prefix || '') + content + (col.postfix || '')
      }
      if (col.marks) {
@@ -435,7 +435,7 @@
      }
      if (content) {
        content = col.prefix + content + col.postfix
        content = (col.prefix || '') + content + (col.postfix || '')
      }
      children = (
@@ -769,7 +769,7 @@
            content = content.replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,')
          }
    
          content = col.prefix + content + col.postfix
          content = (col.prefix || '') + content + (col.postfix || '')
        }
        if (col.marks) {
@@ -794,7 +794,7 @@
      }
      if (content) {
        content = col.prefix + content + col.postfix
        content = (col.prefix || '') + content + (col.postfix || '')
      }
      children = (
@@ -963,12 +963,12 @@
      })
    }
    if (setting.borderColor) { // 边框颜色
      let style = `#${setting.tableId} table, #${setting.tableId} tr, #${setting.tableId} th, #${setting.tableId} td {border-color: ${setting.borderColor}}`
      let ele = document.createElement('style')
      ele.innerHTML = style
      document.getElementsByTagName('head')[0].appendChild(ele)
    }
    // if (setting.borderColor) { // 边框颜色
    //   let style = `#${setting.tableId} table, #${setting.tableId} tr, #${setting.tableId} th, #${setting.tableId} td {border-color: ${setting.borderColor}}`
    //   let ele = document.createElement('style')
    //   ele.innerHTML = style
    //   document.getElementsByTagName('head')[0].appendChild(ele)
    // }
    let size = (setting.pageSize || 10) + ''
    let pageOptions = ['10', '25', '50', '100', '500', '1000']
@@ -991,6 +991,12 @@
      if (deForms.length > 0) {
        this.improveActionForm(deForms)
      }
      const element = document.getElementById(setting.tableId)
      element && element.style.setProperty('--mk-table-border-color', setting.borderColor || '#e8e8e8')
      element && element.style.setProperty('--mk-table-color', setting.color || 'rgba(0, 0, 0, 0.65)')
      element && element.style.setProperty('--mk-table-font-size', setting.fontSize || '14px')
      element && element.style.setProperty('--mk-table-font-weight', setting.fontWeight || 'normal')
    })
  }
@@ -1963,7 +1969,7 @@
          <Table
            rowKey="$$uuid"
            components={components}
            style={setting.style}
            // style={setting.style}
            size={setting.size || 'middle'}
            bordered={setting.bordered !== 'false'}
            rowSelection={rowSelection}
src/tabviews/custom/components/table/edit-table/normalTable/index.scss
@@ -2,6 +2,10 @@
  position: relative;
  min-height: 150px;
  padding: 0px;
  --mk-table-border-color: #e8e8e8;
  --mk-table-color: rgba(0, 0, 0, 0.65);
  --mk-table-font-size: 14px;
  --mk-table-font-weight: normal;
  .normal-table-footer {
    padding: 10px 0px;
@@ -24,6 +28,7 @@
    bottom: 2px;
    right: 5px;
    opacity: 0.7;
    font-size: 14px;
  }
  table {
@@ -290,6 +295,26 @@
    margin-top: 10px!important;
    margin-right: 10px!important;
  }
  table, tr, th, td {
    border-color: var(--mk-table-border-color)!important;
  }
  table tr {
    th .ant-table-column-title, th .ant-table-column-title span:not(.anticon) {
      // color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
    }
    td {
      color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
      >span, div:not(.card-cell-list), div:not(.card-cell-list) div, div:not(.card-cell-list) span {
        font-weight: var(--mk-table-font-weight)!important;
      }
    }
  }
}
.edit-custom-table.buoyMode {
  .ant-table-scroll {
src/tabviews/custom/components/table/normal-table/index.jsx
@@ -67,7 +67,7 @@
      }
    }
    let setting = {..._config.setting, ..._config.wrap, style: {}}
    let setting = {..._config.setting, ..._config.wrap}
    if (setting.selected !== 'always' && setting.selected !== 'init') {
      setting.selected = 'false'
@@ -129,12 +129,12 @@
      }
    })
    if (setting.color) {
      setting.style.color = setting.color
    }
    if (setting.fontSize) {
      setting.style.fontSize = setting.fontSize
    }
    // if (setting.color) {
    //   setting.style.color = setting.color
    // }
    // if (setting.fontSize) {
    //   setting.style.fontSize = setting.fontSize
    // }
    if (_config.wrap.collapse === 'true') {
      _config.wrap.title = _config.wrap.title || ' '
@@ -643,7 +643,7 @@
    }
    return (
      <div className="custom-normal-table" style={style}>
      <div className="custom-normal-table" id={'anchor' + config.uuid} style={style}>
        {config.wrap.collapse === 'true' ? <Collapse bordered={false} defaultActiveKey="1" expandIconPosition="right">
          <Panel forceRender={true} header={<NormalHeader config={config}/>} key="1">
            {searchlist && searchlist.length ?
src/tabviews/custom/components/timeline/normal-timeline/index.jsx
@@ -318,7 +318,7 @@
    const { config, loading, data } = this.state
    return (
      <div className="normal-timeline-box" style={{...config.style}}>
      <div className="normal-timeline-box" id={'anchor' + config.uuid} style={{...config.style}}>
        {loading ?
          <div className="loading-mask">
            {data ? <div className="ant-spin-blur"></div> : null}
src/tabviews/custom/components/tree/antd-tree/index.jsx
@@ -462,7 +462,7 @@
    const { config, loading, treeNodes, expandedKeys, selectedKeys } = this.state
    return (
      <div className="custom-tree-box" style={config.style}>
      <div className="custom-tree-box" id={'anchor' + config.uuid} style={config.style}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
src/tabviews/custom/index.jsx
@@ -460,7 +460,7 @@
        delete item.style.shadowColor
      }
      item.$menuname = this.props.MenuName + '-' + (item.name || '')
      item.$menuname = (this.props.MenuName || '') + '-' + (item.name || '')
      if (item.type === 'tabs') {
        if (
@@ -593,6 +593,8 @@
      let tabId = this.props.Tab ? this.props.Tab.uuid : '' // 弹窗标签按钮Id
      if (item.action && item.action.length > 0) {
        item.action = item.action.filter(cell => {
          if (item.hidden === 'true') return false
          cell.logLabel = item.$menuname + '-' + cell.label
          cell.ContainerId = this.state.ContainerId
          cell.syncComponentId = cell.syncComponent ? (cell.syncComponent.pop() || '') : ''
@@ -631,6 +633,8 @@
          card.elements = card.elements.filter(cell => {
            if (cell.eleType === 'button') {
              if (cell.hidden === 'true') return false
              cell.logLabel = item.$menuname + '-' + cell.label
              cell.Ot = cell.Ot || 'requiredSgl'
              cell.ContainerId = this.state.ContainerId
@@ -659,6 +663,8 @@
          })
          card.backElements = card.backElements.filter(cell => {
            if (cell.eleType === 'button') {
              if (cell.hidden === 'true') return false
              cell.logLabel = item.$menuname + '-' + cell.label
              cell.Ot = cell.Ot || 'requiredSgl'
              cell.ContainerId = this.state.ContainerId
@@ -692,6 +698,8 @@
        }
        item.elements = item.elements.filter(cell => {
          if (cell.eleType === 'button') {
            if (cell.hidden === 'true') return false
            cell.logLabel = item.$menuname + '-' + cell.label
            cell.ContainerId = this.state.ContainerId
            cell.syncComponentId = cell.syncComponent ? (cell.syncComponent.pop() || '') : ''
@@ -718,6 +726,8 @@
          let _hasheight = card.style.height && card.style.height !== 'auto'
          card.elements = card.elements.filter(cell => {
            if (cell.eleType === 'button') {
              if (cell.hidden === 'true') return false
              cell.logLabel = item.$menuname + '-' + cell.label
              cell.Ot = cell.Ot || 'requiredSgl'
              cell.ContainerId = this.state.ContainerId
@@ -748,6 +758,8 @@
        item.cols = item.cols.filter(col => {
          if (col.type !== 'action') return true
          col.elements = col.elements.filter(cell => {
            if (cell.hidden === 'true') return false
            cell.logLabel = item.$menuname + '-' + cell.label
            cell.Ot = cell.Ot || 'requiredSgl'
            cell.ContainerId = this.state.ContainerId
src/tabviews/subtable/index.jsx
@@ -134,7 +134,9 @@
      // 权限过滤
      if (this.props.menuType !== 'HS') {
        config.action = config.action.filter(item => permAction[item.uuid])
        config.action = config.action.filter(item => item.hidden !== 'true' && permAction[item.uuid])
      } else {
        config.action = config.action.filter(item => item.hidden !== 'true')
      }
      config.search = Utils.initSearchVal(config.search)
@@ -158,18 +160,21 @@
        return col
      })
      // 视图权限
      config.charts = config.charts.filter(item => {
        if (item.Hide === 'true') return false
        if (!item.blacklist || item.blacklist.length === 0) return true
        return item.blacklist.filter(v => roleId.indexOf(v) > -1).length === 0
      })
      if (config.charts.length <= 1) {
        config.expand = true
      let chartId = ''
      if (config.charts) {
        // 视图权限
        config.charts = config.charts.filter(item => {
          if (item.Hide === 'true') return false
          if (!item.blacklist || item.blacklist.length === 0) return true
          return item.blacklist.filter(v => roleId.indexOf(v) > -1).length === 0
        })
        if (config.charts.length <= 1) {
          config.expand = true
        }
        chartId = config.charts[0] ? config.charts[0].uuid : ''
      }
      let chartId = config.charts[0] ? config.charts[0].uuid : ''
      // 1、筛选字段集,2、过滤隐藏列及合并列中的字段uuid
      config.columns.forEach(col => {
@@ -997,7 +1002,7 @@
        {searchlist && searchlist.length ?
          <SubSearch BID={BID} setting={setting} searchlist={searchlist} menuType={this.props.menuType} refreshdata={this.refreshbysearch}/> : null
        }
        {config ? <Row className="chart-view" gutter={16}>
        {config && config.charts ? <Row className="chart-view" gutter={16}>
          {/* 视图组 */}
          {!config.expand ? <Tabs activeKey={chartId} onChange={this.changeChart}>
            {config.charts.map(item => (
@@ -1081,6 +1086,43 @@
            }
          })}
        </Row> : null }
        {config && !config.charts ? <>
          <div className="sub-action">
            <SubAction
              BID={BID}
              BData={BData}
              setting={setting}
              actions={actions}
              columns={columns}
              Tab={this.props.Tab}
              MenuID={this.props.MenuID}
              selectedData={selectedData}
              ContainerId={this.props.ContainerId}
            />
          </div>
          <div className="subtable-box">
            {(setting.tableType === 'radio' || setting.tableType === 'checkbox') && this.state.data && this.state.data.length > 0 ?
              <Switch title="收起" className="subtable-pickup" checkedChildren="开" unCheckedChildren="关" checked={pickup} onChange={this.pickupChange} /> : null
            }
            <SubTable
              BData={BData}
              tableId={this.props.Tab.uuid}
              pickup={pickup}
              setting={setting}
              columns={columns}
              pageSize={pageSize}
              dict={this.state.dict}
              data={this.state.data}
              total={this.state.total}
              MenuID={this.props.MenuID}
              loading={this.state.loading}
              statFValue={this.state.statFValue}
              ContainerId={this.props.ContainerId}
              refreshdata={this.refreshbytable}
              chgSelectData={this.changeSelectedData}
            />
          </div>
        </> : null }
        {viewlost ? <NotFount msg={this.state.lostmsg} /> : null}
      </div>
    )
src/tabviews/subtabtable/index.jsx
@@ -129,7 +129,9 @@
      // 权限过滤
      if (this.props.menuType !== 'HS') {
        config.action = config.action.filter(item => permAction[item.uuid])
        config.action = config.action.filter(item => item.hidden !== 'true' && permAction[item.uuid])
      } else {
        config.action = config.action.filter(item => item.hidden !== 'true')
      }
      config.search = Utils.initSearchVal(config.search)
src/tabviews/zshare/actionList/excelInbutton/excelin/index.jsx
@@ -61,8 +61,12 @@
            btn.verify.columns.forEach(op => {
              let _name = typeof(header[op.Column]) === 'string' ? header[op.Column].replace(/(^\s*|\s*$)/g, '') : header[op.Column]
              let _text = op.Text ? op.Text.replace(/(^\s*|\s*$)/g, '') : op.Text
              if (_name !== _text && !iserror) {
              if (!_name && !iserror) {
                iserror = true
                errors = 'headerError'
                errDetail = `Excel中不存在(${_text})列!`
              } else if (_name !== _text && !iserror) {
                iserror = true
                errors = 'headerError'
                errDetail = `Excel中(${_name})与按钮列信息(${_text})不一致!`
src/tabviews/zshare/actionList/excelInbutton/index.jsx
@@ -213,7 +213,10 @@
      MKEmitter.emit('popclose')
    } else if (btn.execSuccess !== 'never') {
      MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execSuccess, btn, '', this.state.selines)
    } else {
      btn.syncComponentId && MKEmitter.emit('reloadData', btn.syncComponentId)
    }
    if (btn.switchTab && btn.switchTab.length > 0) {
      let id = btn.switchTab[btn.switchTab.length - 1]
      let node = document.getElementById('tab' + id)
src/tabviews/zshare/actionList/exceloutbutton/index.jsx
@@ -138,24 +138,17 @@
      return
    }
    if (btn.intertype === 'system' && setting.interType !== 'system') {
      notification.warning({
        top: 92,
        message: '导出按钮配置错误!',
        duration: 5
      })
      return
    } else if (btn.intertype === 'inner' && !btn.innerFunc) {
      notification.warning({
        top: 92,
        message: '导出按钮配置错误!',
        duration: 5
      })
      return
    } else if (!btn.verify || !btn.verify.columns || btn.verify.columns.length === 0) {
    if (!btn.verify || !btn.verify.columns || btn.verify.columns.length === 0) {
      notification.warning({
        top: 92,
        message: '请设置导出列!',
        duration: 5
      })
      return
    } else if (btn.intertype === 'system' && setting.interType !== 'system' && btn.verify.dataType !== 'custom') {
      notification.warning({
        top: 92,
        message: '导出按钮配置错误!',
        duration: 5
      })
      return
@@ -178,7 +171,7 @@
    let name = `${viewParam.menuName}${moment().format('YYYYMMDDHHmmss')}.xlsx`
    let pageSize = 1000
    if (btn.search === 'true' && viewParam.search && viewParam.search.length > 0) {
    if (((btn.intertype === 'system' && btn.verify.dataType === 'custom' && btn.verify.useSearch === 'true') || btn.intertype !== 'system' || btn.verify.dataType !== 'custom') && btn.search === 'true' && viewParam.search && viewParam.search.length > 0) {
      let valid = false
      viewParam.search.forEach(item => {
        if (item.value || item.value === 0) {
@@ -196,16 +189,34 @@
      }
    }
    if (btn.intertype === 'system' && !viewParam.arr_field) { // 使用系统函数
      notification.warning({
        top: 92,
        message: '未设置显示列!',
        duration: 5
      })
      return
    } else if (btn.intertype === 'system' && btn.verify && btn.verify.enable === 'true') {
    if (btn.intertype === 'system') { // 使用系统函数
      if (btn.verify.dataType !== 'custom' && !viewParam.arr_field) {
        notification.warning({
          top: 92,
          message: '按钮需自定义导出数据!',
          duration: 5
        })
        return
      } else if (btn.verify.dataType === 'custom') {
        viewParam.arr_field = []
        btn.verify.columns.forEach(col => {
          if (col.Column && col.Column !== '$Index') {
            viewParam.arr_field.push(col.Column)
          }
        })
        viewParam.arr_field = viewParam.arr_field.join(',')
        viewParam.orderBy = btn.verify.order || viewParam.orderBy
      }
    }
    if (btn.intertype === 'system' && btn.verify.enable === 'true') {
      this.setState({search: fromJS(viewParam.search).toJS()})
    }
    if (btn.intertype === 'system' && btn.verify.dataType === 'custom' && btn.verify.useSearch === 'false') {
      viewParam.search = []
    }
    viewParam.orderBy = viewParam.orderBy || viewParam.arr_field.split(',')[0]
    
    this.updateStatus('start', name)
@@ -718,6 +729,41 @@
  getExcelDefaultParam = (arr_field, orderBy, search, pagination = false, pageIndex = 1, pageSize = 100) => {
    const { setting, btn } = this.props
    let defaultSql = setting.execute || setting.default || 'true'
    let customScript = setting.customScript || ''
    let _dataresource = setting.dataresource || ''
    let queryType = setting.queryType
    let transaction = setting.transaction
    if (btn.verify.dataType === 'custom') {
      defaultSql = btn.verify.defaultSql || 'true'
      _dataresource = btn.verify.dataresource || ''
      queryType = btn.verify.queryType
      transaction = btn.verify.transaction
      if (/\s/.test(_dataresource)) {
        _dataresource = '(' + _dataresource + ') tb'
      }
      customScript = ''
      btn.verify.scripts && btn.verify.scripts.forEach(script => {
        if (script.status !== 'false') {
          customScript += `
          ${script.sql}
          `
        }
      })
      if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
        _dataresource = _dataresource.replace(/\$@/ig, '/*')
        _dataresource = _dataresource.replace(/@\$/ig, '*/')
        customScript = customScript.replace(/\$@/ig, '/*')
        customScript = customScript.replace(/@\$/ig, '*/')
      } else {
        _dataresource = _dataresource.replace(/@\$|\$@/ig, '')
        customScript = customScript.replace(/@\$|\$@/ig, '')
      }
    }
    let _search = Utils.joinMainSearchkey(search)
    _search = _search ? 'where ' + _search : ''
@@ -726,8 +772,8 @@
      func: 'sPC_Get_TableData',
      obj_name: 'data',
      arr_field: arr_field,
      custom_script: setting.customScript,
      default_sql: setting.default || 'true',
      custom_script: customScript,
      default_sql: defaultSql,
      menuname: btn.logLabel
    }
@@ -751,11 +797,9 @@
      userName = sessionStorage.getItem('CloudUserName') || ''
      fullName = sessionStorage.getItem('CloudFullName') || ''
    }
    let _dataresource = setting.dataresource
    let regoptions = null
    if (setting.queryType === 'statistics' || param.custom_script) {
    if (queryType === 'statistics' || param.custom_script) {
      let allSearch = Utils.getAllSearchOptions(search)
      regoptions = allSearch.map(item => {
@@ -776,7 +820,7 @@
        value: fullName
      }, {
        reg: new RegExp('@orderBy@', 'ig'),
        value: orderBy
        value: orderBy || ''
      }, {
        reg: new RegExp('@pageSize@', 'ig'),
        value: pageSize
@@ -786,7 +830,7 @@
      })
    }
    if (setting.queryType === 'statistics') { // 统计数据源,内容替换
    if (queryType === 'statistics') { // 统计数据源,内容替换
      regoptions.forEach(item => {
        _dataresource = _dataresource.replace(item.reg, item.value)
      })
@@ -795,9 +839,9 @@
    let LText = ''
    if (setting.default !== 'false' && !pagination) {
    if (defaultSql !== 'false' && !pagination) {
      LText = ` select ${arr_field} from (select ${arr_field} ,ROW_NUMBER() over(order by ${orderBy}) as rows from ${_dataresource} ${_search}) tmptable order by tmptable.rows `
    } else if (setting.default !== 'false') {
    } else if (defaultSql !== 'false') {
      LText = ` select top ${pageSize} ${arr_field} from (select ${arr_field} ,ROW_NUMBER() over(order by ${orderBy}) as rows from ${_dataresource} ${_search}) tmptable where rows > ${pageSize * (pageIndex - 1)} order by tmptable.rows `
    }
@@ -822,6 +866,10 @@
            insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
        `
      }
    } else if (LText) {
      LText = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000),@UserName nvarchar(50),@FullName nvarchar(50),@RoleID nvarchar(512),@mk_departmentcode nvarchar(50),@mk_organization nvarchar(50),@login_city nvarchar(50) select @ErrorCode='',@retmsg ='',@UserName='${userName}', @FullName='${fullName}', @RoleID='${RoleID}', @mk_departmentcode='${departmentcode}', @mk_organization='${organization}', @login_city='${city}'
        ${LText}
      `
    }
    // 测试系统打印查询语句
@@ -840,7 +888,7 @@
      param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
    }
    if (setting.transaction === 'true') {
    if (transaction === 'true') {
      param.func = 'sPC_Get_TableData_try'
    }
@@ -880,7 +928,10 @@
      MKEmitter.emit('popclose')
    } else if (btn.execSuccess !== 'never') {
      MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execSuccess, btn, '', [])
    } else {
      btn.syncComponentId && MKEmitter.emit('reloadData', btn.syncComponentId)
    }
    if (btn.switchTab && btn.switchTab.length > 0) {
      let id = btn.switchTab[btn.switchTab.length - 1]
      let node = document.getElementById('tab' + id)
src/tabviews/zshare/actionList/funcMegvii/index.jsx
New file
@@ -0,0 +1,546 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router'
import { is, fromJS } from 'immutable'
import { Button, Modal, notification, message } from 'antd'
import CryptoJS from 'crypto-js'
import moment from 'moment'
import NApi from '@/api'
import Utils from '@/utils/utils.js'
import Api from '@/api/w4k.js'
import MKEmitter from '@/utils/events.js'
import MkIcon from '@/components/mk-icon'
// import { mockdata } from './mock'
import './index.scss'
class FuncButton extends Component {
  static propTpyes = {
    btn: PropTypes.object,            // 按钮
    disabled: PropTypes.any,          // 行按钮禁用
  }
  state = {
    loading: false,
    disabled: false,
    loadingNumber: '',
    hidden: false,
    IpList: [],
    lines: [],
    selectIp: {},
    visible: false
  }
  UNSAFE_componentWillMount () {
    const { btn, selectedData } = this.props
    let disabled = false
    if (btn.controlField && selectedData && selectedData.length > 0) { // 表格中按钮隐藏控制
      selectedData.forEach(item => {
        let s = item[btn.controlField] !== undefined ? item[btn.controlField] + '' : ''
        if (btn.controlVals.includes(s)) {
          disabled = true
        }
      })
      this.setState({hidden: disabled && btn.control === 'hidden'})
    }
    if (this.props.disabled || disabled) {
      this.setState({disabled: true})
    }
  }
  componentDidMount () {
    MKEmitter.addListener('triggerBtnId', this.actionTrigger)
  }
  UNSAFE_componentWillReceiveProps (nextProps) {
    const { btn, selectedData } = this.props
    let disabled = false
    if (btn.controlField && !is(fromJS(nextProps.selectedData || []), fromJS(selectedData || []))) {
      if (nextProps.selectedData && nextProps.selectedData.length > 0) { // 表格中按钮隐藏控制
        nextProps.selectedData.forEach(item => {
          let s = item[btn.controlField] !== undefined ? item[btn.controlField] + '' : ''
          if (btn.controlVals.includes(s)) {
            disabled = true
          }
        })
      }
      this.setState({hidden: disabled && btn.control === 'hidden'})
    }
    if (nextProps.disabled || disabled) {
      this.setState({disabled: true})
    } else {
      this.setState({disabled: false})
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('triggerBtnId', this.actionTrigger)
  }
  /**
   * @description 触发按钮操作
   */
  actionTrigger = (triggerId, record, type) => {
    const { Tab, BID, btn, selectedData, setting } = this.props
    const { loading, disabled } = this.state
    if (loading || disabled) return
    if (triggerId) {
      if (btn.uuid !== triggerId) return
      if (this.props.lineId && record && record[0] && this.props.lineId !== record[0].$$key) {
        return
      }
    }
    if (((Tab && Tab.supMenu) || setting.supModule) && !BID) {
      notification.warning({
        top: 92,
        message: '需要上级主键值!',
        duration: 5
      })
      return
    } else if (type === 'linkbtn' && selectedData && selectedData.length === 1) {
      if (record[0].$Index !== selectedData[0].$Index) {
        return
      }
    }
    let data = record || selectedData || []
    // let data = fromJS(mockdata.data).toJS()
    if (data.length === 0) {
      // 需要选择行时,校验数据
      notification.warning({
        top: 92,
        message: '请选择行!',
        duration: 5
      })
      return
    }
    this.setState({
      loading: true,
      lines: data
    })
    this.getIpList()
  }
  getIpList = () => {
    let _scriptSql = `select ID,data_code,data_name,Remark,face_ip,face_uname,face_pwd from bd_data where typecharone='face_device'  and deleted=0`
    _scriptSql = Utils.formatOptions(_scriptSql)
    let _sParam = {
      func: 'sPC_Get_SelectedList',
      LText: _scriptSql,
      obj_name: 'data',
      arr_field: 'ID,data_code,data_name,Remark,face_ip,face_uname,face_pwd'
    }
    _sParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    _sParam.secretkey = Utils.encrypt(_sParam.LText, _sParam.timestamp)
    NApi.getSystemCacheConfig(_sParam).then(res => {
      if (res.status) {
        let IpList = res.data || []
        if (IpList.length === 0) {
          notification.warning({
            top: 92,
            message: '未获取到设备信息!',
            duration: 5
          })
          this.setState({
            loading: false
          })
        } else {
          this.setState({
            IpList: IpList.map(item => {
              if (item.face_ip) {
                if (!/^http:/.test(item.face_ip)) {
                  item.face_ip = 'http://' + item.face_ip
                }
                if (!/:\d{2,4}$/.test(item.face_ip)) {
                  item.face_ip = item.face_ip + ':80'
                }
              }
              return item
            }),
            selectIp: {},
            visible: true
          })
        }
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  loginDevice = () => {
    const { lines, selectIp } = this.state
    // 上报接口设置:工程模式-场景模式-开放接口设置-数据上报设置-数据上报服务器地址(开启服务器二次鉴权)
    // let ip = 'http://localhost:3001'
    let ip = selectIp.face_ip
    Api.login(ip, selectIp.face_uname, selectIp.face_pwd).then(result => {
      if (result.errors) {
        this.execPreError(result)
        return
      }
      document.cookie = 'sessionID=' + result.session_id
      Api.queryUsers(ip).then(res => {
        if (res.errors) {
          this.execPreError(res)
          return
        }
        let persons = {}
        res.data.forEach(item => {
          persons[item.id] = true
        })
        let data = lines.map(cell => {
          let item = {}
          Object.keys(cell).forEach(key => {
            item[key.toLowerCase()] = cell[key]
          })
          return {
            deleted: persons[item.id] || false,
            recognition_type: item.recognition_type || 'staff',
            is_admin: item.is_admin === 'true',
            person_name: item.person_name || '',
            id: item.id || '',
            card_number: item.card_number || '',
            person_code: item.person_code || '',
            group_list: item.group_list ? item.group_list.split(',') : [1],
            password: item.password || '',
            id_number: item.id_number || '',
            face_list: [{
              idx: item.face_idx || 0,
              data: item.face_data ? item.face_data.replace(/^data:image\/(jpeg|png|jpg|gif);base64,/, '') : '',
            }]
          }
        })
        this.addUser(ip, data, result.session_id)
      })
    }, err => {
      this.execPreError(err)
    })
  }
  addUser = (ip, datas, sessionId) => {
    let data = datas.shift()
    this.setState({
      loadingNumber: datas.length || ''
    })
    let error = ''
    if (!data.person_name) {
      error = '人员名称不可为空'
    } else if (!data.id) {
      error = '人员id不可为空'
    } else if (!data.face_list[0].data) {
      error = '人员图片不可为空'
    } else if (data.password) {
      if (/^\d{6}$/.test(data.password)) {
        let key = CryptoJS.enc.Utf8.parse(sessionId)
        let srcs = CryptoJS.enc.Utf8.parse(data.password)
        let encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7})
        data.password = CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
      } else {
        error = '密码使用6位数字'
      }
    }
    if (error) {
      this.execError({ErrCode: 'E', ErrMesg: error})
      return
    }
    if (data.id_number) {
      let key = CryptoJS.enc.Utf8.parse(sessionId)
      let srcs = CryptoJS.enc.Utf8.parse(data.id_number)
      let encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7})
      data.id_number = CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
    }
    Api.addUsers(ip, data).then(res => {
      if (res.errors) {
        this.execPreError(res, data)
        return
      }
      if (datas.length > 0) {
        this.addUser(ip, datas, sessionId)
      } else {
        this.execSuccess({ErrCode: 'S', ErrMesg: '执行成功。'})
      }
    }, (err) => {
      this.execPreError(err, data)
    })
  }
  /**
   * @description 操作成功后处理
   * 1、excel导出,成功后取消导出按钮加载中状态
   * 2、状态码为 S 时,显示成功信息后系统默认信息
   * 3、状态码为 -1 时,不显示任何信息
   * 4、模态框执行成功后是否关闭
   * 5、通知主列表刷新
   */
  execSuccess = (res) => {
    const { btn } = this.props
    if (res && (res.ErrCode === 'S' || !res.ErrCode)) { // 执行成功
      notification.success({
        top: 92,
        message: res.ErrMesg || '执行成功',
        duration: btn.verify && btn.verify.stime ? btn.verify.stime : 2
      })
    } else if (res && res.ErrCode === 'Y') { // 执行成功
      Modal.success({
        title: res.ErrMesg || '执行成功'
      })
    } else if (res && res.ErrCode === '-1') { // 完成后不提示
    }
    this.setState({
      loading: false,
      loadingNumber: ''
    })
    // if (btn.execSuccess !== 'never') {
    //   MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execSuccess, btn)
    // }
  }
  execPreError = (err, data) => {
    if (err.data && err.data.errors && err.data.errors[0]) {
      let error = err.data.errors[0]
      let message = error.detail
      if (error.source.pointer === '/api/persons/item' && error.error_code) {
        let list = [
          {val: 1, msg: '图片格式错误'},
          {val: 2, msg: '图片大小超出限制'},
          {val: 3, msg: '图片分辨率超出限制'},
          {val: 16, msg: '人脸无特征值'},
          {val: 17, msg: '入库无图片'},
          {val: 18, msg: '未检测到人脸'},
          {val: 19, msg: '检测到多个人脸'},
          {val: 20, msg: '人脸太小'},
          {val: 21, msg: '图片太亮'},
          {val: 22, msg: '图片太暗'},
          {val: 23, msg: '人脸太模糊'},
          {val: 24, msg: '人脸方向错误'},
          {val: 32, msg: '数据库满'},
          {val: 33, msg: '人脸图片重复'},
          {val: 34, msg: '人员编号重复'},
          {val: 35, msg: '卡号重复'},
          {val: 48, msg: '人员名称重复'},
          {val: -1, msg: '未知错误'},
          {val: -2141716476, msg: '系统参数错误,有可能是人员组不存在。'},
        ]
        list.forEach(item => {
          if (item.val === error.error_code) {
            message = (data ? data.person_name + ' - ' : '') + item.msg
          }
        })
      }
      this.execError({
        ErrCode: 'E',
        message: message
      })
    } else {
      this.execError({
        ErrCode: 'E',
        message: '请检查设备状态!'
      })
    }
  }
  /**
   * @description 操作失败后处理
   */
  execError = (res) => {
    const { btn } = this.props
    if (res.ErrCode === 'E') {
      Modal.error({
        title: res.message || res.ErrMesg,
      })
    } else if (res.ErrCode === 'N') {
      notification.error({
        top: 92,
        message: res.message || res.ErrMesg,
        duration: btn.verify && btn.verify.ntime ? btn.verify.ntime : 10
      })
    } else if (res.ErrCode === 'F') {
      notification.error({
        className: 'notification-custom-error',
        top: 92,
        message: res.message || res.ErrMesg,
        duration: btn.verify && btn.verify.ftime ? btn.verify.ftime : 10
      })
    } else if (res.ErrCode === 'NM') {
      message.error(res.message || res.ErrMesg)
    }
    this.setState({
      loading: false,
      loadingNumber: ''
    })
    // if (btn.execError !== 'never') {
    //   MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execError, btn)
    // }
  }
  handleOk = () => {
    const { selectIp } = this.state
    if (!selectIp.ID) {
      notification.warning({
        top: 92,
        message: '请选择面板机!',
        duration: 5
      })
      return
    } else if (!selectIp.face_ip) {
      notification.warning({
        top: 92,
        message: '面板机IP缺失!',
        duration: 5
      })
      return
    } else if (!selectIp.face_uname) {
      notification.warning({
        top: 92,
        message: '面板机用户名缺失!',
        duration: 5
      })
      return
    } else if (!selectIp.face_pwd) {
      notification.warning({
        top: 92,
        message: '面板机密码缺失!',
        duration: 5
      })
      return
    }
    this.setState({
      visible: false
    })
    this.loginDevice()
  }
  getModels = () => {
    const { IpList, visible, selectIp } = this.state
    return (
      <Modal
        title="请选择面板机"
        maskClosable={false}
        getContainer={document.body}
        wrapClassName='ip-list-modal'
        visible={visible}
        width={700}
        onOk={this.handleOk}
        onCancel={() => this.setState({visible: false, loading: false})}
        destroyOnClose
      >
        {IpList.map(item => {
          return <div className={'ip-item' + (selectIp.ID === item.ID ? ' active' : '')} key={item.ID} onClick={() => {this.setState({selectIp: item})}}>
            <div className="ip">IP:{item.face_ip}</div>
            <div className="user">用户:{item.face_uname}</div>
            <div className="remark">备注:{item.Remark}</div>
          </div>
        })}
      </Modal>
    )
  }
  render() {
    const { btn, show } = this.props
    const { loading, disabled, hidden, loadingNumber } = this.state
    if (hidden) return null
    if (show === 'actionList') {
      return (
        <>
          <Button
            icon={btn.icon}
            loading={loading}
            disabled={disabled}
            title={disabled ? (btn.reason || '') : ''}
            className={'mk-btn mk-' + btn.class}
            onClick={(e) => {e.stopPropagation(); this.actionTrigger()}}
          >{(loadingNumber ? `(${loadingNumber})` : '') + btn.label}</Button>
          {this.getModels()}
        </>
      )
    } else { // icon、text、 all 卡片
      let label = ''
      let icon = ''
      if (show === 'button') {
        label = btn.label
        icon = btn.icon || ''
      } else if (show === 'link') {
        label = <span>{btn.label}{btn.icon ? <MkIcon style={{marginLeft: '8px'}} type={btn.icon}/> : ''}</span>
        icon = ''
      } else if (show === 'icon') {
        icon = btn.icon || ''
      } else {
        label = btn.label
      }
      return (
        <>
          <Button
            type="link"
            title={disabled ? (btn.reason || '') : (show === 'icon' ? btn.label : '')}
            loading={loading}
            disabled={disabled}
            style={btn.style}
            icon={icon}
            onClick={(e) => {e.stopPropagation(); this.actionTrigger()}}
          >{label}</Button>
          {this.getModels()}
        </>
      )
    }
  }
}
export default withRouter(FuncButton)
src/tabviews/zshare/actionList/funcMegvii/index.scss
New file
@@ -0,0 +1,26 @@
.ip-list-modal {
  .ip-item {
    display: inline-block;
    border: 1px solid #d9d9d9;
    padding: 10px;
    height: 100px;
    width: calc(33% - 20px);
    margin: 10px;
    cursor: pointer;
    .ip {
      color: #000000;
    }
    .remark {
      word-break: break-all;
      text-overflow: ellipsis;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      overflow: hidden;
    }
  }
  .ip-item.active {
    border-color: var(--antd-wave-shadow-color);
    box-shadow: 0 0 2px var(--antd-wave-shadow-color);
  }
}
src/tabviews/zshare/actionList/funcMegvii/mock.js
New file
@@ -0,0 +1,27 @@
export const mockdata = {
  data: [
    {
      "recognition_type": "staff",
      "id": "2226169",
      "type": "persons",
      "is_admin": "true",
      "person_name": "king",
      "card_number": "mk001",
      "person_code": "2018",
      "id_number": "130982193002054729",
      "group_list": "1",
      "face_data":""
    },
    {
      "recognition_type": "staff",
      "id": "3272487",
      "is_admin": 'false',
      "person_name": "jinfei",
      "card_number": "mk002",
      "person_code": "02",
      "id_number": "",
      "group_list": "1",
      "face_data": ""
    }
  ]
}
src/tabviews/zshare/actionList/index.jsx
@@ -14,6 +14,7 @@
const NewPageButton = asyncComponent(() => import('./newpagebutton'))
const ChangeUserButton = asyncComponent(() => import('./changeuserbutton'))
const PrintButton = asyncComponent(() => import('./printbutton'))
const FuncMegvii = asyncComponent(() => import('./funcMegvii'))
class ActionList extends Component {
  static propTpyes = {
@@ -37,7 +38,6 @@
  getButtonList = (actions) => {
    const { BID, BData, MenuID, Tab, columns, setting, ContainerId, selectedData, lock } = this.props
    return actions.map(item => {
      if (['exec', 'prompt', 'pop'].includes(item.OpenType)) {
        return (
@@ -113,7 +113,7 @@
        return (
          <NewPageButton
            key={item.uuid}
            show="actionList"
            show={item.show || 'actionList'}
            disabled={lock || false}
            btn={item}
            BData={BData}
@@ -151,6 +151,19 @@
              selectedData={selectedData}
            />
          )
        } else if (item.funcType === 'megvii') {
          return (
            <FuncMegvii
              key={item.uuid}
              show={item.show || 'actionList'}
              disabled={lock || false}
              BID={BID}
              Tab={Tab}
              btn={item}
              setting={setting}
              selectedData={selectedData}
            />
          )
        }
      }
      return null
src/tabviews/zshare/actionList/normalbutton/index.jsx
@@ -1543,6 +1543,8 @@
      MKEmitter.emit('popclose')
    } else if (btn.execSuccess !== 'never') {
      MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execSuccess, btn, id, this.state.selines)
    } else {
      btn.syncComponentId && MKEmitter.emit('reloadData', btn.syncComponentId)
    }
    if (tabId) {
@@ -1553,6 +1555,11 @@
      let id = btn.switchTab[btn.switchTab.length - 1]
      let node = document.getElementById('tab' + id)
      node && node.click()
    }
    if (btn.anchors && btn.anchors.length > 0) {
      let id = btn.anchors[btn.anchors.length - 1]
      let node = document.getElementById('anchor' + id)
      node && node.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'})
    }
    if (btn.openmenu && btn.openmenu.length > 0 && btn.MenuID) {
@@ -1823,10 +1830,10 @@
   */
  improveAction = () => {
    const { btn } = this.props
    const { btnconfig, autoMatic } = this.state
    const { btnconfig } = this.state
    if (btnconfig) {
      if (!autoMatic && (btnconfig.setting.display === 'prompt' || btnconfig.setting.display === 'exec')) { // 如果表单以是否框展示
      if (btnconfig.setting.display === 'prompt' || btnconfig.setting.display === 'exec') { // 如果表单以是否框展示
        this.modelconfirm()
      } else {
        this.setState({
@@ -1870,7 +1877,7 @@
          this.setState({
            btnconfig: _LongParam
          }, () => {
            if (!autoMatic && (_LongParam.setting.display === 'prompt' || _LongParam.setting.display === 'exec')) { // 如果表单以是否框展示
            if (_LongParam.setting.display === 'prompt' || _LongParam.setting.display === 'exec') { // 如果表单以是否框展示
              this.modelconfirm()
            } else {
              this.setState({
@@ -1888,6 +1895,7 @@
   */
  handleOk = () => {
    if (!this.formRef) return
    this.formRef.handleConfirm().then(res => {
      this.setState({ confirmLoading: true })
src/tabviews/zshare/actionList/popupbutton/index.jsx
@@ -270,7 +270,7 @@
          BID={popData ? primaryId : this.props.BID}
          BData={popData || this.props.BData}
        /> : null}
        {btn.$view === 'CustomPage' ? <CustomPage Tab={btn} MenuID={btn.uuid} param={{$BID: (popData ? primaryId : this.props.BID), ...(popData || this.props.BData || {})}} /> : null}
        {btn.$view === 'CustomPage' ? <CustomPage Tab={btn} MenuID={btn.uuid} MenuName={btn.label} param={{$BID: (popData ? primaryId : this.props.BID), ...(popData || this.props.BData || {})}} /> : null}
      </Modal>
    } else {
      let height = '100vh'
src/tabviews/zshare/automatic/index.jsx
@@ -129,7 +129,13 @@
            let node = document.querySelector('.ant-modal-confirm-btns >.ant-btn-primary')
            node && node.click()
          } else if (autoMatic.OpenType === 'pop') {
            MKEmitter.emit('triggerBtnPopSubmit', autoMatic.action)
            let node = document.querySelector('.ant-modal-confirm-btns >.ant-btn-primary')
            if (node) {
              node.click()
            } else {
              MKEmitter.emit('triggerBtnPopSubmit', autoMatic.action)
            }
          }
        }, delay)
      }
src/tabviews/zshare/fileupload-pice/index.jsx
New file
@@ -0,0 +1,498 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import moment from 'moment'
import { Upload, Button, Progress, notification } from 'antd'
import { UploadOutlined } from '@ant-design/icons'
import SparkMD5 from 'spark-md5'
import Api from '@/api'
import './index.scss'
let service = ''
if (process.env.NODE_ENV === 'production') {
  service = document.location.origin + '/' + window.GLOB.service
} else {
  service = window.GLOB.location + '/' + window.GLOB.service
}
class FileUpload extends Component {
  static propTpyes = {
    config: PropTypes.object,  // 表单信息
    onChange: PropTypes.func,  // 表单变化
  }
  state = {
    percent: 0,
    accept: '',
    accepts: null,
    maxFile: null,
    rduri: '',
    limit: 2,
    compress: false,
    fileType: 'text',
    showprogress: false,
    filelist: []
  }
  UNSAFE_componentWillMount () {
    const { config } = this.props
    let filelist = []
    if (config.initval) {
      if (/^data:image/.test(config.initval)) {
        filelist = [{
          uid: '0',
          name: 'data:image/jpeg;base64',
          status: 'done',
          url: config.initval,
          origin: true
        }]
      } else {
        try {
          filelist = config.initval.split(',').map((url, index) => {
            return {
              uid: `${index}`,
              name: url.slice(url.lastIndexOf('/') + 1),
              status: 'done',
              url: url,
              origin: true
            }
          })
        } catch (e) {
          filelist = []
        }
      }
    }
    let accept = ''
    let accepts = null
    let compress = false
    let maxFile = config.maxfile && config.maxfile > 0 ? config.maxfile : null
    if (config.compress === 'true' || config.compress === 'base64') {
      compress = true
      accepts = ['.jpg', '.png', '.gif', '.jpeg']
      accept = accepts.join(',')
      if (config.compress === 'base64') {
        maxFile = 1
      }
    } else if (config.suffix) {
      accepts = config.suffix.split(',').map(item => {
        if (!/^\./ig.test(item)) {
          item = '.' + item
        }
        return item
      })
      accept = accepts.join(',')
    }
    let rduri = config.rduri || ''
    if (window.GLOB.systemType === 'production') {
      rduri = config.proRduri || ''
    }
    this.setState({
      rduri,
      accept,
      accepts,
      filelist,
      compress,
      limit: config.limit || 2,
      maxFile: maxFile,
      fileType: config.fileType || 'text'
    })
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  onChange = ({ fileList }) => {
    fileList = fileList.map(item => {
      if (item.status === 'error' && /^<!DOCTYPE html>/.test(item.response)) {
        item.response = ''
      }
      return item
    })
    this.setState({filelist: fileList})
  }
  onRemove = file => {
    const files = this.state.filelist.filter(v => v.uid !== file.uid)
    this.setState({filelist: files})
    let vals = []
    files.forEach(item => {
      if (item.origin && item.url) {
        vals.push(item.url)
      } else if (!item.origin && item.status === 'done' && item.response) {
        vals.push(item.response)
      }
    })
    this.props.onChange(vals.join(','))
  }
  onUpdate = (url) => {
    let filelist = fromJS(this.state.filelist).toJS()
    if (filelist[filelist.length -1]) {
      filelist[filelist.length -1].status = 'done'
      filelist[filelist.length -1].response = url
      filelist[filelist.length -1].origin = false
    }
    filelist = filelist.filter(item => !!(item.url || item.response))
    let vals = []
    filelist.forEach(item => {
      if (item.origin && item.url) {
        vals.push(item.url)
      } else if (!item.origin && item.status === 'done' && item.response) {
        vals.push(item.response)
      }
    })
    this.setState({filelist})
    this.props.onChange(vals.join(','))
  }
  onFail = (msg) => {
    let filelist = this.state.filelist.map(item => {
      if (!item.url && !item.response && !item.status) {
        item.status = 'error'
      }
      return item
    })
    this.setState({filelist, showprogress: false, percent: 0})
    notification.warning({
      top: 92,
      message: msg || '文件上传失败!',
      duration: 5
    })
  }
  shardupload = (params) => {
    let param = params.chunks.shift()
    let form = new FormData()
    form.append('file', param.binary)
    form.append('fileMd5', params.file.fileMd5)
    form.append('shardingMd5', param.chunkMd5)
    form.append('baseDomain', service)
    form.append('rootPath', 'Content/images/upload/')
    form.append('fileName', params.file.fileName)
    form.append('fileExt', params.file.fileType)
    form.append('shardingCnt', param.chunks)
    form.append('shardingNo', param.chunk)
    form.append('LoginUID', sessionStorage.getItem('LoginUID') || '')
    form.append('UserID', sessionStorage.getItem('UserID') || '')
    Api.getLargeFileUpload(form).then(res => {
      if (res.status) {
        if (params.chunks.length > 0) {
          this.setState({
            percent: Math.floor(100 * (param.chunk / param.chunks))
          })
          this.shardupload(params)
        } else {
          if (res.urlPath) {
            this.onUpdate(res.urlPath)
          } else {
            this.onFail()
          }
          this.setState({
            percent: 100
          }, () => {
            setTimeout(() => {
              this.setState({
                showprogress: false,
                percent: 0
              })
            }, 200)
          })
        }
      } else {
        this.onFail(res.message)
      }
    })
  }
  getuuid = () => {
    let uuid = []
    let _options = '0123456789abcdefghigklmnopqrstuv'
    for (let i = 0; i < 19; i++) {
      uuid.push(_options.substr(Math.floor(Math.random() * 0x20), 1))
    }
    uuid = uuid.join('')
    return uuid
  }
  beforeUpload = (file) => {
    const { accepts, compress, limit, rduri } = this.state
    if (accepts && file.name) {
      let pass = false
      accepts.forEach(type => {
        if (new RegExp(type + '$', 'ig').test(file.name)) {
          pass = true
        }
      })
      if (!pass) {
        setTimeout(() => {
          this.onFail('文件格式错误!')
        }, 10)
        return false
      }
    }
    this.setState({
      showprogress: true,
      percent: 0
    })
    if (compress) {
      let reader = new FileReader()
      let fileSize = file.size / 1024 / 1024
      let compressRate = 0.9
      if (fileSize / limit > 5) {
        compressRate = 0.4
      } else if (fileSize / limit > 4) {
        compressRate = 0.5
      } else if (fileSize / limit > 3) {
        compressRate = 0.6
      } else if (fileSize / limit > 2) {
        compressRate = 0.7
      } else if (fileSize > limit) {
        compressRate = 0.8
      }
      reader.onload = (e) => {
        let img = new Image()
        let maxW = 640
        img.onload = () => {
          let cvs = document.createElement( 'canvas')
          let ctx = cvs.getContext( '2d')
          if (img.width > maxW) {
            img.height *= maxW / img.width
            img.width = maxW
          }
          cvs.width = img.width
          cvs.height = img.height
          ctx.clearRect(0, 0, cvs.width, cvs.height)
          ctx.drawImage(img, 0, 0, img.width, img.height)
          let param = {Base64Img: cvs.toDataURL('image/jpeg', compressRate)}
          if (this.props.config.compress === 'base64') {
            this.onUpdate(param.Base64Img)
            this.setState({
              percent: 100
            }, () => {
              setTimeout(() => {
                this.setState({
                  showprogress: false,
                  percent: 0
                })
              }, 200)
            })
          } else {
            if (rduri) {
              param.rduri = rduri
            }
            Api.fileuploadbase64(param).then(result => {
              if (result.status && result.Images) {
                let url = service + result.Images
                if (rduri) {
                  url = rduri.replace(/webapi(.*)$/, '') + result.Images
                }
                this.onUpdate(url)
                this.setState({
                  percent: 100
                }, () => {
                  setTimeout(() => {
                    this.setState({
                      showprogress: false,
                      percent: 0
                    })
                  }, 200)
                })
              } else {
                this.onFail(result.message)
              }
            })
          }
        }
        img.onerror = () => {
          this.onFail('图片读取失败!')
        }
        img.src = e.target.result
      }
      reader.onerror = () => {
        this.onFail('文件读取失败!')
      }
      reader.readAsDataURL(file)
      return false
    }
    // 兼容性的处理
    let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
    let chunkSize = 1024 * 1024 * 2                // 切片每次2M
    let chunks = Math.ceil(file.size / chunkSize)  // 切片总数
    let currentChunk = 0                           // 当前上传的chunk
    let spark = new SparkMD5.ArrayBuffer()         // 对arrayBuffer数据进行md5加密,产生一个md5字符串
    let chunkFileReader = new FileReader()         // 用于计算出每个chunkMd5
    let totalFileReader = new FileReader()         // 用于计算出总文件的fileMd5
    let params = {chunks: [], file: {}}            // 用于上传所有分片的md5信息
    params.file.fileName = file.name.replace(/\.{1}[^.]*$/ig, '')  // 文件名(去除后缀名)
    params.file.fileType = file.name.replace(/^.*\.{1}/ig, '')     // 文件类型
    params.file.fileSize = file.size                               // 文件大小
    params.file.fileChunks = chunks                                // 记录所有chunks的长度
    if (!/^[A-Za-z0-9]+$/.test(params.file.fileName)) {            // 文件名称含有英文及数字之外字符时,名称系统生成
      params.file.fileName = moment().format('YYYYMMDDHHmmss') + this.getuuid()
    }
    totalFileReader.readAsArrayBuffer(file)
    totalFileReader.onload = (e) => {   // 对整个totalFile生成md5
      spark.append(e.target.result)
      params.file.fileMd5 = spark.end() // 计算整个文件的fileMd5
      let _param = new FormData()
      _param.append('fileMd5', params.file.fileMd5)
      Api.getFilePreUpload(_param).then(res => {
        if (res.status && res.urlPath) {
          this.onUpdate(res.urlPath)
          this.setState({
            percent: 100
          }, () => {
            setTimeout(() => {
              this.setState({
                showprogress: false,
                percent: 0
              })
            }, 200)
          })
        } else if (res.shardings && res.shardings.length > 0) {
          res.shardings.forEach(shard => {
            if (shard.shardingNo && parseInt(shard.shardingNo) > currentChunk) {
              currentChunk = parseInt(shard.shardingNo)
            }
          })
          loadNext()
        } else {
          loadNext()
        }
      })
    }
    chunkFileReader.onload = (e) => {
      spark.append(e.target.result)      // 对每一片分片进行md5加密
      params.chunks[params.chunks.length - 1].chunkMd5 = spark.end() // 添加切片md5
      currentChunk++  // 每一次分片onload,currentChunk都需要增加,以便来计算分片的次数
      if (currentChunk < chunks) { // 当前切片总数没有达到总数时
        loadNext()
      } else {
        this.shardupload(params)
      }
    }
    chunkFileReader.onerror = () => {
      this.onFail('文件读取失败!')
    }
    totalFileReader.onerror = () => {
      this.onFail('文件读取失败!')
    }
    let loadNext = () => {
      let start = currentChunk * chunkSize              // 计算分片的起始位置
      let end = Math.min(file.size, start + chunkSize)  // 计算分片的结束位置
      let obj = {                                       // 每一个分片需要包含的信息
        chunk: currentChunk + 1,
        binary: file.slice(start, end),
        start: start,
        end: end,
        chunks
      }
      params.chunks.push(obj)
      chunkFileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
    }
    return false
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  render() {
    const { showprogress, percent, filelist, maxFile, fileType, accept } = this.state
    let uploadable = 'fileupload-form-container '
    if (maxFile && filelist.length >= maxFile) {
      uploadable += 'limit-fileupload'
    }
    const props = {
      name: 'file',
      disabled: showprogress,
      listType: fileType,
      fileList: filelist,
      action: null,
      accept: accept,
      method: 'post',
      multiple: false,
      onChange: this.onChange,
      onRemove: this.onRemove,
      beforeUpload: this.beforeUpload,
      className: uploadable
    }
    return (
      <Upload {...props}>
        {fileType !== 'picture-card' ? <Button>
          <UploadOutlined /> 点击上传
        </Button> : null}
        {fileType === 'picture-card' ? <span style={{whiteSpace: 'nowrap'}}>
          <UploadOutlined /> 点击上传
        </span> : null}
        {showprogress ? <Progress percent={percent} size="small" /> : null}
      </Upload>
    )
  }
}
export default FileUpload
src/tabviews/zshare/fileupload-pice/index.scss
New file
@@ -0,0 +1,53 @@
.fileupload-form-container {
  .ant-progress-small.ant-progress-line {
    position: absolute;
    bottom: -20px;
    left: 0px;
  }
  .ant-upload-select-picture-card {
    .ant-progress-small.ant-progress-line {
      bottom: 0px;
    }
  }
  .ant-upload-list-picture-card-container {
    width: 90px;
    height: 90px;
  }
  // .ant-upload-list-picture-card .ant-upload-list-item-undefined {
  //   border-color: #f5222d;
  //   .ant-upload-list-item-name {
  //     color: #f5222d;
  //   }
  // }
  .ant-upload-list-picture-card .ant-upload-list-item {
    width: 90px;
    height: 90px;
  }
  .ant-upload.ant-upload-select-picture-card {
    width: 90px;
    height: 90px;
  }
  a[href^="data"] {
    pointer-events: none;
    .anticon-eye-o {
      display: none;
    }
  }
}
.fileupload-form-container.limit-fileupload {
  > .ant-upload {
    display: inline;
    >.ant-upload {
      >input {
        display: none;
      }
      >button {
        display: none;
      }
    }
  }
  > .ant-upload-select-picture-card {
    display: none;
  }
}
src/tabviews/zshare/fileupload/index.jsx
@@ -39,28 +39,42 @@
    let filelist = []
    if (config.initval) {
      try {
        filelist = config.initval.split(',').map((url, index) => {
          return {
            uid: `${index}`,
            name: url.slice(url.lastIndexOf('/') + 1),
            status: 'done',
            url: url,
            origin: true
          }
        })
      } catch (e) {
        filelist = []
      if (/^data:image/.test(config.initval)) {
        filelist = [{
          uid: '0',
          name: 'data:image/jpeg;base64',
          status: 'done',
          url: config.initval,
          origin: true
        }]
      } else {
        try {
          filelist = config.initval.split(',').map((url, index) => {
            return {
              uid: `${index}`,
              name: url.slice(url.lastIndexOf('/') + 1),
              status: 'done',
              url: url,
              origin: true
            }
          })
        } catch (e) {
          filelist = []
        }
      }
    }
    let accept = ''
    let accepts = null
    let compress = false
    if (config.compress === 'true') {
    let maxFile = config.maxfile && config.maxfile > 0 ? config.maxfile : null
    if (config.compress === 'true' || config.compress === 'base64') {
      compress = true
      accepts = ['.jpg', '.png', '.gif', '.jpeg']
      accept = accepts.join(',')
      if (config.compress === 'base64') {
        maxFile = 1
      }
    } else if (config.suffix) {
      accepts = config.suffix.split(',').map(item => {
        if (!/^\./ig.test(item)) {
@@ -83,7 +97,7 @@
      filelist,
      compress,
      limit: config.limit || 2,
      maxFile: config.maxfile && config.maxfile > 0 ? config.maxfile : null,
      maxFile: maxFile,
      fileType: config.fileType || 'text'
    })
  }
@@ -163,46 +177,38 @@
    })
  }
  shardupload = (params) => {
    let param = params.chunks.shift()
  shardupload = (param) => {
    let form = new FormData()
    form.append('file', param.binary)
    form.append('fileMd5', params.file.fileMd5)
    form.append('shardingMd5', param.chunkMd5)
    form.append('fileMd5', param.fileMd5)
    form.append('shardingMd5', param.fileMd5)
    form.append('baseDomain', service)
    form.append('rootPath', 'Content/images/upload/')
    form.append('fileName', params.file.fileName)
    form.append('fileExt', params.file.fileType)
    form.append('shardingCnt', param.chunks)
    form.append('shardingNo', param.chunk)
    form.append('fileName', param.fileName)
    form.append('fileExt', param.fileType)
    form.append('shardingCnt', 1)
    form.append('shardingNo', 1)
    form.append('LoginUID', sessionStorage.getItem('LoginUID') || '')
    form.append('UserID', sessionStorage.getItem('UserID') || '')
    Api.getLargeFileUpload(form).then(res => {
      if (res.status) {
        if (params.chunks.length > 0) {
          this.setState({
            percent: Math.floor(100 * (param.chunk / param.chunks))
          })
          this.shardupload(params)
        if (res.urlPath) {
          this.onUpdate(res.urlPath)
        } else {
          if (res.urlPath) {
            this.onUpdate(res.urlPath)
          } else {
            this.onFail()
          }
          this.setState({
            percent: 100
          }, () => {
            setTimeout(() => {
              this.setState({
                showprogress: false,
                percent: 0
              })
            }, 200)
          })
          this.onFail()
        }
        this.setState({
          percent: 100
        }, () => {
          setTimeout(() => {
            this.setState({
              showprogress: false,
              percent: 0
            })
          }, 200)
        })
      } else {
        this.onFail(res.message)
      }
@@ -281,34 +287,49 @@
          let param = {Base64Img: cvs.toDataURL('image/jpeg', compressRate)}
          if (rduri) {
            param.rduri = rduri
          }
          Api.fileuploadbase64(param).then(result => {
            if (result.status && result.Images) {
              let url = service + result.Images
              if (rduri) {
                url = rduri.replace(/webapi(.*)$/, '') + result.Images
              }
              this.onUpdate(url)
              this.setState({
                percent: 100
              }, () => {
                setTimeout(() => {
                  this.setState({
                    showprogress: false,
                    percent: 0
                  })
                }, 200)
              })
            } else {
              this.onFail(result.message)
          if (this.props.config.compress === 'base64') {
            this.onUpdate(param.Base64Img)
            this.setState({
              percent: 100
            }, () => {
              setTimeout(() => {
                this.setState({
                  showprogress: false,
                  percent: 0
                })
              }, 200)
            })
          } else {
            if (rduri) {
              param.rduri = rduri
            }
          })
            Api.fileuploadbase64(param).then(result => {
              if (result.status && result.Images) {
                let url = service + result.Images
                if (rduri) {
                  url = rduri.replace(/webapi(.*)$/, '') + result.Images
                }
                this.onUpdate(url)
                this.setState({
                  percent: 100
                }, () => {
                  setTimeout(() => {
                    this.setState({
                      showprogress: false,
                      percent: 0
                    })
                  }, 200)
                })
              } else {
                this.onFail(result.message)
              }
            })
          }
        }
        img.onerror = () => {
@@ -327,31 +348,25 @@
    }
    // 兼容性的处理
    let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
    let chunkSize = 1024 * 1024 * 2                // 切片每次2M
    let chunks = Math.ceil(file.size / chunkSize)  // 切片总数
    let currentChunk = 0                           // 当前上传的chunk
    let spark = new SparkMD5.ArrayBuffer()         // 对arrayBuffer数据进行md5加密,产生一个md5字符串
    let chunkFileReader = new FileReader()         // 用于计算出每个chunkMd5
    let totalFileReader = new FileReader()         // 用于计算出总文件的fileMd5
    let params = {chunks: [], file: {}}            // 用于上传所有分片的md5信息
    let param = {}
    params.file.fileName = file.name.replace(/\.{1}[^.]*$/ig, '')  // 文件名(去除后缀名)
    params.file.fileType = file.name.replace(/^.*\.{1}/ig, '')     // 文件类型
    params.file.fileSize = file.size                               // 文件大小
    params.file.fileChunks = chunks                                // 记录所有chunks的长度
    param.fileName = file.name.replace(/\.{1}[^.]*$/ig, '')  // 文件名(去除后缀名)
    param.fileType = file.name.replace(/^.*\.{1}/ig, '')     // 文件类型
    if (!/^[A-Za-z0-9]+$/.test(params.file.fileName)) {            // 文件名称含有英文及数字之外字符时,名称系统生成
      params.file.fileName = moment().format('YYYYMMDDHHmmss') + this.getuuid()
    if (!/^[A-Za-z0-9]+$/.test(param.fileName)) {            // 文件名称含有英文及数字之外字符时,名称系统生成
      param.fileName = moment().format('YYYYMMDDHHmmss') + this.getuuid()
    }
    totalFileReader.readAsArrayBuffer(file)
    totalFileReader.onload = (e) => {   // 对整个totalFile生成md5
      spark.append(e.target.result)
      params.file.fileMd5 = spark.end() // 计算整个文件的fileMd5
      param.fileMd5 = spark.end()       // 计算整个文件的fileMd5
      param.binary = file
      let _param = new FormData()
      _param.append('fileMd5', params.file.fileMd5)
      _param.append('fileMd5', param.fileMd5)
      
      Api.getFilePreUpload(_param).then(res => {
        if (res.status && res.urlPath) {
@@ -366,54 +381,14 @@
              })
            }, 200)
          })
        } else if (res.shardings && res.shardings.length > 0) {
          res.shardings.forEach(shard => {
            if (shard.shardingNo && parseInt(shard.shardingNo) > currentChunk) {
              currentChunk = parseInt(shard.shardingNo)
            }
          })
          loadNext()
        } else {
          loadNext()
          this.shardupload(param)
        }
      })
    }
    chunkFileReader.onload = (e) => {
      spark.append(e.target.result)      // 对每一片分片进行md5加密
      params.chunks[params.chunks.length - 1].chunkMd5 = spark.end() // 添加切片md5
      currentChunk++  // 每一次分片onload,currentChunk都需要增加,以便来计算分片的次数
      if (currentChunk < chunks) { // 当前切片总数没有达到总数时
        loadNext()
      } else {
        this.shardupload(params)
      }
    }
    chunkFileReader.onerror = () => {
      this.onFail('文件读取失败!')
    }
    totalFileReader.onerror = () => {
      this.onFail('文件读取失败!')
    }
    let loadNext = () => {
      let start = currentChunk * chunkSize              // 计算分片的起始位置
      let end = Math.min(file.size, start + chunkSize)  // 计算分片的结束位置
      let obj = {                                       // 每一个分片需要包含的信息
        chunk: currentChunk + 1,
        binary: file.slice(start, end),
        start: start,
        end: end,
        chunks
      }
      params.chunks.push(obj)
      chunkFileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
    }
    return false
src/tabviews/zshare/fileupload/index.scss
@@ -14,12 +14,12 @@
    width: 90px;
    height: 90px;
  }
  .ant-upload-list-picture-card .ant-upload-list-item-undefined {
    border-color: #f5222d;
    .ant-upload-list-item-name {
      color: #f5222d;
    }
  }
  // .ant-upload-list-picture-card .ant-upload-list-item-undefined {
  //   border-color: #f5222d;
  //   .ant-upload-list-item-name {
  //     color: #f5222d;
  //   }
  // }
  .ant-upload-list-picture-card .ant-upload-list-item {
    width: 90px;
    height: 90px;
src/tabviews/zshare/normalTable/index.jsx
@@ -654,9 +654,11 @@
      let photos = ''
      if (item.field && record.hasOwnProperty(item.field)) {
        photos = record[item.field] + ''
        photos = photos.split(',').filter(Boolean)
      }
      if (/^data:image/.test(photos)) {
        photos = [photos]
      } else {
        photos = ''
        photos = photos.split(',').filter(Boolean)
      }
      let maxHeight = item.maxHeight || 128
@@ -857,7 +859,12 @@
          let photos = []
          try {
            photos = record[col.field] + ''
            photos = photos.split(',').filter(Boolean)
            if (/^data:image/.test(photos)) {
              photos = [photos]
            } else {
              photos = photos.split(',').filter(Boolean)
            }
          } catch (e) {
            photos = []
          }
src/templates/comtableconfig/index.jsx
@@ -135,7 +135,7 @@
    }
    this.setState({
      chartview: _config.charts[0].uuid,
      chartview: _config.charts ? _config.charts[0].uuid : '',
      config: _config,
      openEdition: menu.open_edition || '',
      activeKey: menu.activeKey || '0',
@@ -378,6 +378,7 @@
    }
    _config.action.forEach(item => {
      if (item.hidden === 'true') return
      _sort++
      if (item.OpenType === 'popview') {
        btntabs.push({
@@ -1111,7 +1112,7 @@
          config: null
        }, () => {
          this.setState({
            chartview: _config.charts[0].uuid,
            chartview: _config.charts ? _config.charts[0].uuid : '',
            config: _config,
            openEdition: res.open_edition || '',
            activeKey: menu.activeKey || '0',
@@ -1264,12 +1265,12 @@
                config={config}
                updatesearch={this.updatesearch}
              />
              <div className="chart-view" style={{position: 'relative'}}>
                {/* 视图组 权限 会员等级20+ */}
                {this.props.memberLevel >= 20 ? <ChartGroupComponent
              {config.charts ? <div className="chart-view" style={{position: 'relative'}}>
                {/* 视图组 已弃用 */}
                <ChartGroupComponent
                  config={config}
                  updatechartgroup={this.updatechartgroup}
                /> : null}
                />
                {config.charts.map(item => {
                  if (!config.expand && chartview !== item.uuid) return ''
@@ -1314,7 +1315,21 @@
                    )
                  }
                })}
              </div>
              </div> : <>
                <ActionComponent
                  type="main"
                  menu={{ MenuID: this.props.menu.MenuID, MenuName: config.MenuName, MenuNo: config.MenuNo, fstMenuList: this.props.menu.fstMenuList }}
                  config={config}
                  tabs={this.state.tabviews}
                  setSubConfig={(_btn) => this.setSubConfig(_btn, 'button')}
                  updateaction={this.updateaction}
                />
                <ColumnComponent
                  config={config}
                  menu={this.props.menu}
                  updatecolumn={this.updateconfig}
                />
              </>}
              {/* 标签组 */}
              <TabsComponent
                config={config}
src/templates/formtabconfig/index.jsx
@@ -756,13 +756,7 @@
      let newLText = Utils.formatOptions(FuncUtils.getTableFunc(setting, menu, _config)) // 创建存储过程sql
      let DelText = Utils.formatOptions(FuncUtils.dropfunc(setting.innerFunc))          // 删除存储过程sql
      this.refs.tableCreatFunc.exec(setting.innerFunc, newLText, DelText).then(result => {
        if (result === 'success') {
          this.setState({
            config: _config
          })
        }
      })
      this.refs.tableCreatFunc.exec(setting.innerFunc, newLText, DelText)
    })
  }
src/templates/menuconfig/editthdmenu/index.jsx
@@ -407,6 +407,7 @@
  }
  previewPicture = (template) => {
    if (template.disabled) return
    // 图片预览
    this.setState({
      preview: template.url,
@@ -555,31 +556,29 @@
    Api.getSystemConfig({func: 'sPC_Get_UserTemp', TypeCharTwo: 'menu'}).then(res => {
      let _templates = []
      let hasRolePermission = false
      res.UserTemp.forEach(temp => {
        if (temp.Template === 'NewPage') {
          return
        } else if (temp.Template === 'RolePermission') {
          hasRolePermission = true
        if (temp.Template === 'NewPage' || temp.Template === 'RolePermission') {
          return
        } else if (temp.Template === 'CustomPage' && memberLevel < 20) {
          return
          temp.disabled = true
          temp.disTitle = '会员等级不够,无开发权限。'
        }
        
        _templates.push({
          uuid: temp.MenuID,
          title: temp.MenuName,
          type: temp.Template,
          url: illust[temp.Template]
          url: illust[temp.Template],
          disabled: temp.disabled || false,
          disTitle: temp.disTitle || ''
        })
      })
      sysTemplates = sysTemplates.map(temp => {
        if (temp.type === 'RolePermission' && !hasRolePermission) {
          temp.hidden = false
        } else if (temp.type === 'CustomPage' && memberLevel < 20) {
          temp.hidden = true
        if (temp.type === 'CustomPage' && memberLevel < 20) {
          temp.disabled = true
          temp.disTitle = '会员等级不够,无开发权限。'
        }
        return temp
@@ -618,15 +617,15 @@
    let sysTemplates = fromJS(this.state.sysTemplates).toJS()
    // 角色权限分配模板,只可以添加一次
    if (sysMenu.isSystem && (sysMenu.Template === 'RolePermission')) {
      sysTemplates = sysTemplates.map(temp => {
        if (temp.type === sysMenu.type) {
          temp.hidden = true
        }
    // if (sysMenu.isSystem && (sysMenu.Template === 'RolePermission')) {
    //   sysTemplates = sysTemplates.map(temp => {
    //     if (temp.type === sysMenu.type) {
    //       temp.hidden = true
    //     }
        return temp
      })
    }
    //     return temp
    //   })
    // }
    this.menuFormRef.handleConfirm().then(res => {
      let PageParam = {
@@ -827,10 +826,10 @@
                <TabPane tab="系统模板" key="1">
                  <Row>
                    {this.state.sysTemplates.map((template, index) => {
                      if (template.hidden) return null
                      // if (template.hidden) return null
                      return (
                        <Col key={`${index}`} span={8}>
                        <Col key={`${index}`} className={template.disabled ? 'disabled' : ''} title={template.disTitle || ''} span={8}>
                          <Card
                            title={template.title}>
                            <img onClick={() => {this.previewPicture(template)}} src={template.url} alt=""/>
@@ -853,7 +852,7 @@
                    {this.state.usedTemplates && this.state.usedTemplates.map((template, index) => {
                      if (template.title.toLowerCase().indexOf(this.state.tempSearchKey.toLowerCase()) >= 0) {
                        return (
                          <Col key={template.type + index} span={8}>
                          <Col key={template.type + index} className={template.disabled ? 'disabled' : ''} title={template.disTitle || ''} span={8}>
                            <Card
                              title={template.title}>
                              <img onClick={() => {this.previewPicture(template)}} src={template.url} alt=""/>
src/templates/menuconfig/editthdmenu/index.scss
@@ -123,6 +123,18 @@
      .ant-col {
        padding: 10px;
      }
      .ant-col.disabled {
        cursor: not-allowed;
        .ant-card-head-title {
          color: #959595;
        }
        .card-operation {
          display: none;
        }
        img {
          cursor: not-allowed;
        }
      }
      .ant-card-head-title {
        text-align: center;
      }
src/templates/menuconfig/menuelement/card.jsx
@@ -53,7 +53,8 @@
      </div>
      {/* 自定义模板,在新页面编辑 */}
      {!card.forbidden && card.type !== 'CustomPage' ? <EditOutlined className="edit" onClick={edit} /> : null}
      {card.type === 'CustomPage' ? <EditOutlined className="edit" onClick={() => {window.open(`#/menudesign/${_param}`)}}/> : null}
      {!card.forbidden && card.type === 'CustomPage' ? <EditOutlined className="edit" onClick={() => {window.open(`#/menudesign/${_param}`)}}/> : null}
      {card.forbidden && card.type === 'CustomPage' ? <EditOutlined className="edit" style={{color: '#959595', cursor: 'not-allowed'}} title="会员等级不够,无开发权限。"/> : null}
      <CloseOutlined className="close" onClick={close} />
    </div>
  )
src/templates/modalconfig/index.jsx
@@ -262,17 +262,22 @@
        index = i
      }
      let label = item.label || ''
      if (item.field && item.field.toLowerCase() !== label.toLowerCase()) {
        label = label + ' (' + item.field + ')'
      }
      if (['text', 'number', 'textarea', 'color'].includes(item.type) && card.field !== item.field) {
        _inputfields.push({
          field: item.field,
          label: _inputIndex + '、' + item.label
          label: _inputIndex + '、' + label
        })
        _inputIndex++
      }
      if (card.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type)) {
        _tabfields.push({
          field: item.field,
          label: _tabIndex + '、' + item.label
          label: _tabIndex + '、' + label
        })
        _tabIndex++
      }
@@ -280,7 +285,7 @@
      if (item.type === 'switch') {
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
      }
@@ -295,7 +300,7 @@
        })
        _linksupFields.push({
          field: item.field,
          label: _linkIndex + '、' + item.label
          label: _linkIndex + '、' + label
        })
        _linkIndex++
      }
src/templates/sharecomponent/actioncomponent/actionform/index.jsx
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Input, Select, Radio, notification, Tooltip, InputNumber, Cascader } from 'antd'
import { Form, Row, Col, Input, Select, Radio, Tooltip, InputNumber, Cascader } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import { formRule } from '@/utils/option.js'
@@ -13,15 +13,15 @@
const { TextArea } = Input
const actionTypeOptions = {
  pop: ['label', 'position', 'OpenType', 'intertype', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'openmenu', 'output', 'tipTitle'],
  prompt: ['label', 'position', 'OpenType', 'intertype', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'openmenu', 'output', 'tipTitle'],
  exec: ['label', 'position', 'OpenType', 'intertype', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'openmenu', 'output'],
  excelIn: ['label', 'Ot', 'OpenType', 'intertype', 'icon', 'class', 'sheet', 'execSuccess', 'execError'],
  excelOut: ['label', 'OpenType', 'intertype', 'icon', 'class', 'execSuccess', 'execError', 'pagination', 'search'],
  popview: ['label', 'Ot', 'OpenType', 'icon', 'class', 'position', 'tabType', 'linkTab', 'popClose', 'display', 'ratio', 'clickouter'],
  tab: ['label', 'Ot', 'OpenType', 'icon', 'class', 'position', 'linkmenu'],
  innerpage: ['label', 'Ot', 'OpenType', 'pageTemplate', 'icon', 'class', 'position'],
  funcbutton: ['label', 'OpenType', 'funcType', 'icon', 'class']
  pop: ['label', 'position', 'OpenType', 'intertype', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'openmenu', 'output', 'tipTitle', 'hidden'],
  prompt: ['label', 'position', 'OpenType', 'intertype', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'openmenu', 'output', 'tipTitle', 'hidden'],
  exec: ['label', 'position', 'OpenType', 'intertype', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'openmenu', 'output', 'hidden'],
  excelIn: ['label', 'Ot', 'OpenType', 'intertype', 'icon', 'class', 'sheet', 'execSuccess', 'execError', 'hidden'],
  excelOut: ['label', 'OpenType', 'intertype', 'icon', 'class', 'execSuccess', 'execError', 'pagination', 'search', 'hidden'],
  popview: ['label', 'Ot', 'OpenType', 'icon', 'class', 'position', 'tabType', 'linkTab', 'popClose', 'display', 'ratio', 'clickouter', 'hidden'],
  tab: ['label', 'Ot', 'OpenType', 'icon', 'class', 'position', 'linkmenu', 'hidden'],
  innerpage: ['label', 'Ot', 'OpenType', 'pageTemplate', 'icon', 'class', 'position', 'hidden'],
  funcbutton: ['label', 'OpenType', 'funcType', 'icon', 'class', 'hidden']
}
class MainSearch extends Component {
@@ -30,7 +30,8 @@
    setting: PropTypes.object,   // 页面设置
    formlist: PropTypes.any,     // 表单信息
    card: PropTypes.any,         // 按钮信息
    inputSubmit: PropTypes.any   // 回车提交事件
    inputSubmit: PropTypes.func, // 回车提交事件
    updRecord: PropTypes.func
  }
  state = {
@@ -126,6 +127,8 @@
        console.warn('表单focus失败!')
      }
    }
    this.props.updRecord && this.props.updRecord(this.record)
  }
  getMutilOptions = () => {
@@ -266,6 +269,8 @@
        }
      } else if (_funcType === 'closetab') {
        shows.push('refreshTab')
      } else if (_funcType === 'megvii') {
        shows.push('subFunc')
      }
    }
@@ -383,6 +388,8 @@
      if (!['funcbutton', 'excelIn', 'excelOut'].includes(value)) {
        _fieldval.position = 'toolbar'
      }
      this.props.updRecord && this.props.updRecord(this.record)
    } else if (key === 'position') {
      if (value === 'grid') {
        _fieldval.Ot = 'requiredSgl'
@@ -437,6 +444,8 @@
      }
    } else if (key === 'intertype') {
      this.record.sysInterface = 'false'
      this.props.updRecord && this.props.updRecord(this.record)
    } else if (key === 'sysInterface') {
      if (value === 'true') {
        _fieldval.interface = window.GLOB.mainSystemApi || ''
@@ -590,7 +599,7 @@
  }
  handleConfirm = () => {
    const { setting, card } = this.props
    const { card } = this.props
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
@@ -600,15 +609,6 @@
          values.position = values.position || 'toolbar'
          if (values.OpenType === 'excelOut') {
            if (values.intertype === 'system' && setting.interType !== 'system') { // 导出excel需使用查询数据源
              notification.warning({
                top: 92,
                message: '表格数据查询未使用系统函数,导出Excel不可使用系统函数!',
                duration: 5
              })
              return
            }
            values.Ot = 'notRequired'
          } else if (values.OpenType === 'popview' && !values.linkTab) { // 没有关联标签(新建时),创建新标签Id
            values.linkTab = Utils.getuuid()
src/templates/sharecomponent/actioncomponent/dragaction/card.jsx
@@ -42,6 +42,8 @@
    }
  } else if (card.funcType === 'print') {
    hasProfile = true
  } else if (card.funcType === 'megvii') {
    hasProfile = true
  }
  return (
@@ -56,7 +58,7 @@
      <div className="page-card" style={{ opacity: opacity}}>
        <div ref={node => drag(drop(node))}>
          <Button
            className={'mk-btn mk-' + card.class}
            className={'mk-btn mk-' + card.class + (card.hidden === 'true' ? ' mk-hidden' : '')}
            icon={card.icon}
            key={card.uuid}
            onDoubleClick={() => doubleClickCard(id)}
src/templates/sharecomponent/actioncomponent/index.jsx
@@ -24,6 +24,7 @@
const VerifyCard = asyncSpinComponent(() => import('@/templates/zshare/verifycard'))
const VerifyPrint = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyprint'))
const VerifyExcelIn = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyexcelin'))
const VerifyMegvii = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifymegvii'))
const VerifyExcelOut = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyexcelout'))
class ActionComponent extends Component {
@@ -44,7 +45,8 @@
    copying: false,      // 按钮复制中
    visible: false,      // 模态框控制
    printTemps: [],      // 单据打印模板
    profVisible: false   // 验证信息模态框
    profVisible: false,  // 验证信息模态框
    record: null
  }
  /**
@@ -858,12 +860,57 @@
    MKEmitter.removeListener('pasteButton', this.pasteButton)
  }
  getVerify = (card) => {
    const { config } = this.props
    const { dict } = this.state
    if (!card) return null
    if (['pop', 'prompt', 'exec'].includes(card.OpenType)) {
      return <VerifyCard
        card={card}
        dict={dict}
        config={config}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'excelIn') {
      return <VerifyExcelIn
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'excelOut') {
      return <VerifyExcelOut
        card={card}
        dict={dict}
        config={config}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'funcbutton' && card.funcType === 'print') {
      return <VerifyPrint
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'funcbutton' && card.funcType === 'megvii') {
      return <VerifyMegvii
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    }
  }
  render() {
    const { config } = this.props
    const { actionlist, visible, card, dict, copying, profVisible } = this.state
    const { actionlist, visible, card, dict, copying, profVisible, record } = this.state
    return (
      <div className="model-table-action-list" style={config.charts.length > 1 ? {paddingTop: 25} : null}>
      <div className="model-table-action-list" style={config.charts && config.charts.length > 1 ? {paddingTop: 25} : null}>
        <Tooltip placement="bottomLeft" overlayClassName="middle" title="在左侧工具栏《按钮》中,选择对应类型的按钮拖至此处添加,如选择按钮类型为表单、新标签页等含有配置页面的按钮,可在左侧工具栏-按钮-可配置按钮处,点击按钮完成相关配置。注:当设置按钮显示位置为表格时,显示列会增加操作列。">
          <QuestionCircleOutlined style={{color: '#c49f47', position: 'absolute', left: '5px', top: '5px'}} />
        </Tooltip>
@@ -885,8 +932,8 @@
          maskClosable={false}
          onCancel={this.editModalCancel}
          footer={[
            <CreateInterface key="interface" dict={dict} ref="btnCreatInterface" trigger={this.btnCreatInterface}/>,
            <CreateFunc key="create" dict={dict} ref="btnCreatFunc" trigger={this.creatFunc}/>,
            record && ['pop', 'exec', 'prompt'].includes(record.OpenType) && record.intertype === 'system' ? <CreateInterface key="interface" dict={dict} ref="btnCreatInterface" trigger={this.btnCreatInterface}/> : null,
            record && record.intertype === 'inner' ? <CreateFunc key="create" dict={dict} ref="btnCreatFunc" trigger={this.creatFunc}/> : null,
            <Button key="cancel" onClick={this.editModalCancel}>{dict['model.cancel']}</Button>,
            <Button key="confirm" type="primary" loading={copying} onClick={this.handleSubmit}>{dict['model.confirm']}</Button>
          ]}
@@ -898,6 +945,7 @@
            formlist={this.state.formlist}
            inputSubmit={this.handleSubmit}
            setting={config.setting}
            updRecord={(record) => this.setState({record: fromJS(record).toJS()})}
            wrappedComponentRef={(inst) => this.actionFormRef = inst}
          />
        </Modal>
@@ -921,39 +969,7 @@
          }}
          destroyOnClose
        >
          {card && !card.execMode && card.OpenType !== 'excelIn' && card.OpenType !== 'excelOut' ?
            <VerifyCard
              card={card}
              dict={dict}
              config={config}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.execMode ?
            <VerifyPrint
              card={card}
              dict={dict}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.OpenType === 'excelIn' ?
            <VerifyExcelIn
              card={card}
              dict={dict}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.OpenType === 'excelOut' ?
            <VerifyExcelOut
              card={card}
              dict={dict}
              config={config}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {this.getVerify(card)}
        </Modal>
      </div>
    )
src/templates/sharecomponent/actioncomponent/index.scss
@@ -6,6 +6,12 @@
  > .ant-row {
    min-height: 65px;
  }
  .mk-hidden {
    text-decoration: line-through!important;
    span {
      text-decoration: line-through!important;
    }
  }
  .page-card {
    display: inline-block;
    margin: 0px 0px 0px 0px;
src/templates/sharecomponent/actioncomponent/verifyexcelin/customscript/index.jsx
@@ -12,7 +12,6 @@
class CustomForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,         // 字典项
    btn: PropTypes.object,          // 按钮信息
    scripts: PropTypes.array,       // 自定义脚本列表
    usefulfields: PropTypes.any,    // 可用字段
@@ -347,7 +346,7 @@
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.input'] + 'sql!'
                    message: '请输入sql!'
                  }
                ]
              })(<CodeMirror />)}
src/templates/sharecomponent/actioncomponent/verifyexcelin/index.jsx
@@ -809,7 +809,6 @@
            </span>
          } key="scripts">
            <CustomScript
              dict={this.props.dict}
              btn={this.props.card}
              usefulfields={verify.columns}
              scripts={verify.scripts}
@@ -818,14 +817,6 @@
              wrappedComponentRef={(inst) => this.scriptsForm = inst}
            />
            <EditTable actions={['move']} data={verify.scripts} columns={scriptsColumns} onChange={(scripts) => {this.setState({verify: {...verify, scripts}})}}/>
            {/* <Table
              bordered
              rowKey="uuid"
              className="custom-table"
              dataSource={verify.scripts}
              columns={scriptsColumns}
              pagination={false}
            /> */}
          </TabPane> : null}
          <TabPane tab="信息提示" key="tip">
            <Form {...formItemLayout}>
src/templates/sharecomponent/actioncomponent/verifyexcelout/customscript/index.jsx
New file
@@ -0,0 +1,342 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Form, Row, Col, Button, notification, Modal, Tooltip, Radio, Select } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import CodeMirror from '@/templates/zshare/codemirror'
import './index.scss'
class CustomForm extends Component {
  static propTpyes = {
    scripts: PropTypes.array,       // 自定义脚本列表
    usefulfields: PropTypes.any,    // 可用字段
    systemScripts: PropTypes.array, // 系统脚本
    scriptsChange: PropTypes.func   // 表单
  }
  state = {
    editItem: null,
    usefulfields: null,
    loading: false,
    verifySql: ''
  }
  UNSAFE_componentWillMount () {
    this.resetfield(this.props.usefulfields)
  }
  UNSAFE_componentWillReceiveProps (nextProps) {
    if (nextProps.usefulfields && !is(fromJS(this.props.usefulfields), fromJS(nextProps.usefulfields))) {
      this.resetfield(nextProps.usefulfields)
    }
  }
  resetfield = (columns) => {
    columns = columns.filter(item => item.Column !== '$Index')
    let fields = columns.map(item => item.Column)
    let _sql = `Declare @UserName nvarchar(50),@FullName nvarchar(50),@RoleID nvarchar(512),@mk_departmentcode nvarchar(50),@mk_organization nvarchar(50),@login_city nvarchar(50),@ErrorCode nvarchar(50), @retmsg nvarchar(4000)
    `
    this.setState({
      verifySql: _sql,
      usefulfields: fields.join(', ')
    })
  }
  edit = (record) => {
    this.setState({
      editItem: record
    })
    this.props.form.setFieldsValue({
      sql: record.sql,
      position: record.position || 'back'
    })
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        values.uuid = this.state.editItem ? this.state.editItem.uuid : ''
        let _quot = values.sql.match(/'{1}/g)
        let _lparen = values.sql.match(/\({1}/g)
        let _rparen = values.sql.match(/\){1}/g)
        _quot = _quot ? _quot.length : 0
        _lparen = _lparen ? _lparen.length : 0
        _rparen = _rparen ? _rparen.length : 0
        if (_quot % 2 !== 0) {
          notification.warning({
            top: 92,
            message: 'sql中\'必须成对出现',
            duration: 5
          })
          return
        } else if (_lparen !== _rparen) {
          notification.warning({
            top: 92,
            message: 'sql中()必须成对出现',
            duration: 5
          })
          return
        } else if (/--/ig.test(values.sql)) {
          notification.warning({
            top: 92,
            message: '自定义sql语句中,不可出现字符 -- ,注释请用 /*内容*/',
            duration: 5
          })
          return
        }
        let error = Utils.verifySql(values.sql, 'customscript')
        if (error) {
          notification.warning({
            top: 92,
            message: 'sql中不可使用' + error,
            duration: 5
          })
          return
        }
        let tail = `
          aaa:
        `
        let _initCustomScript = '' // 初始化脚本
        let _prevCustomScript = '' // 默认sql前执行脚本
        let _backCustomScript = '' // 默认sql后执行脚本
        this.props.scripts.forEach(item => {
          if (item.status === 'false') return
          if (item.position === 'init') {
            _initCustomScript += `
            /* 初始化脚本 */
            ${values.uuid === item.uuid ? values.sql : item.sql}
            `
          } else if (item.position === 'front') {
            _prevCustomScript += `
            /* 默认sql前脚本 */
            ${values.uuid === item.uuid ? values.sql : item.sql}
            `
          } else {
            _backCustomScript += `
            /* 默认sql后脚本 */
            ${values.uuid === item.uuid ? values.sql : item.sql}
            `
          }
        })
        if (!values.uuid) {
          if (values.position === 'init') {
            _initCustomScript += `
            /* 初始化脚本 */
            ${values.sql}
            `
          } else if (values.position === 'front') {
            _prevCustomScript += `
            /* 默认sql前脚本 */
            ${values.sql}
            `
          } else {
            _backCustomScript += `
            /* 默认sql后脚本 */
            ${values.sql}
            `
          }
        }
        let param = {
          func: 's_debug_sql',
          exec_type: 'y',
          LText: this.state.verifySql + _initCustomScript + _prevCustomScript + _backCustomScript + tail
        }
        param.LText = param.LText.replace(/@\$|\$@/ig, '')
        // 外联数据库替换
        if (window.GLOB.externalDatabase !== null) {
          param.LText = param.LText.replace(/@db@/ig, window.GLOB.externalDatabase)
        }
        param.LText = Utils.formatOptions(param.LText)
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        param.secretkey = Utils.encrypt('', param.timestamp)
        this.setState({loading: true})
        Api.getLocalConfig(param).then(res => {
          if (res.status) {
            this.setState({
              loading: false,
              editItem: null
            }, () => {
              this.props.scriptsChange(values)
            })
            this.props.form.setFieldsValue({
              sql: ''
            })
          } else {
            this.setState({loading: false})
            Modal.error({
              title: res.message
            })
          }
        })
      }
    })
  }
  handleCancel = () => {
    this.setState({
      editItem: null
    })
    this.props.form.setFieldsValue({
      sql: ''
    })
  }
  selectScript = (value, option) => {
    const { usefulfields, sheet } = this.props
    let _value = ''
    if (value === 'default') {
      let fields = usefulfields.filter(item => item.import !== 'false')
      fields = fields.map(col => col.Column).join(',')
      let _sheet = sheet.replace(/(.*)\.(.*)\.|@db@/ig, '')
      _value = `Select ${fields} From ${_sheet}`
    } else {
      _value = value
    }
    let _sql = this.props.form.getFieldValue('sql')
    if (_sql) {
      _sql = _sql + `
      `
    }
    _sql = _sql.replace(/\s{6}$/, '')
    _sql = _sql + `/*${option.props.children}*/
    `
    _sql = _sql.replace(/\s{4}$/, '')
    _sql = _sql + _value
    this.props.form.setFieldsValue({
      sql: _sql
    })
  }
  render() {
    const { systemScripts, sheet } = this.props
    const { usefulfields } = this.state
    const { getFieldDecorator } = this.props.form
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <Form {...formItemLayout} className="verify-form" id="verify-excelin-custom-scripts">
        <Row gutter={24}>
          <Col span={8}>
            <Form.Item label={'表名'} style={{whiteSpace: 'nowrap', margin: 0}}>
              {sheet}
            </Form.Item>
          </Col>
          <Col span={10}>
            <Form.Item label={'报错字段'} style={{margin: 0, whiteSpace: 'nowrap'}}>
              ErrorCode(增加后缀NT表示数据不回滚,如ENT、NNT、FNT、NMNT), retmsg
            </Form.Item>
          </Col>
          {usefulfields ? <Col span={24} className="sqlfield">
            <Form.Item label={'可用字段'}>
            BID, ID, LoginUID, SessionUid, UserID, Appkey, UserName, FullName, RoleID, mk_departmentcode, mk_organization, login_city, {usefulfields}
            </Form.Item>
          </Col> : null}
          <Col span={8} style={{whiteSpace: 'nowrap'}}>
            <Form.Item style={{marginBottom: 0}} label={
              <Tooltip placement="bottomLeft" title={'自定义脚本与默认sql位置关系。'}>
                <QuestionCircleOutlined className="mk-form-tip" />
                执行位置
              </Tooltip>
            }>
              {getFieldDecorator('position', {
                initialValue: 'front'
              })(
                <Radio.Group>
                  <Radio value="init">初始化</Radio>
                  <Radio value="front">sql前</Radio>
                  <Radio value="back">sql后</Radio>
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
          <Col span={10}>
            <Form.Item style={{marginBottom: 0}} label={'快捷添加'}>
              <Select
                showSearch
                filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                onSelect={this.selectScript}
                getPopupContainer={() => document.getElementById('verify-excelin-custom-scripts')}
              >
                <Select.Option key="default" value={'default'}>
                  默认sql
                </Select.Option>
                <Select.Option key="debugger" value={`z_debug: select @ErrorCode='E',@retmsg='测试断点' goto aaa`}>
                  测试断点
                </Select.Option>
                {systemScripts.map((option, i) =>
                  <Select.Option key={i} value={option.value}>
                    {option.name}
                  </Select.Option>
                )}
              </Select>
            </Form.Item>
          </Col>
          <Col span={6} className="add">
            <Button onClick={this.handleConfirm} loading={this.state.loading} className="mk-green" style={{marginBottom: 15, marginLeft: 40}}>
              保存
            </Button>
            <Button onClick={this.handleCancel} style={{marginBottom: 15, marginLeft: 10}}>
              取消
            </Button>
          </Col>
          <Col span={24} className="sql">
            <Form.Item label={'sql'}>
              {getFieldDecorator('sql', {
                initialValue: '',
                rules: [
                  {
                    required: true,
                    message: '请输入sql!'
                  }
                ]
              })(<CodeMirror />)}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(CustomForm)
src/templates/sharecomponent/actioncomponent/verifyexcelout/customscript/index.scss
src/templates/sharecomponent/actioncomponent/verifyexcelout/datasource/index.jsx
New file
@@ -0,0 +1,246 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, Tooltip, notification } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import Utils from '@/utils/utils.js'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const CodeMirror = asyncComponent(() => import('@/templates/zshare/codemirror'))
class SettingForm extends Component {
  static propTpyes = {
    setting: PropTypes.object,    // 数据源配置
  }
  state = {
    dataType: '',
    defaultSql: 'true'
  }
  UNSAFE_componentWillMount () {
    const { setting } = this.props
    this.setState({
      dataType: setting.dataType,
      defaultSql: setting.defaultSql || 'true'
    })
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          // 数据源前端验证
          if (values.defaultSql === 'true' && !values.dataresource) {
            notification.warning({
              top: 92,
              message: '请填写数据源!',
              duration: 5
            })
            reject()
            return
          } else if (values.defaultSql === 'true' && values.dataresource) {
            let _quot = values.dataresource.match(/'{1}/g)
            let _lparen = values.dataresource.match(/\({1}/g)
            let _rparen = values.dataresource.match(/\){1}/g)
            _quot = _quot ? _quot.length : 0
            _lparen = _lparen ? _lparen.length : 0
            _rparen = _rparen ? _rparen.length : 0
            if (_quot % 2 !== 0) {
              notification.warning({
                top: 92,
                message: '数据源中\'必须成对出现',
                duration: 5
              })
              reject()
              return
            } else if (_lparen !== _rparen) {
              notification.warning({
                top: 92,
                message: '数据源中()必须成对出现',
                duration: 5
              })
              reject()
              return
            } else if (/--/ig.test(values.dataresource)) {
              notification.warning({
                top: 92,
                message: '数据源中,不可出现字符 -- ,注释请用 /*内容*/',
                duration: 5
              })
              reject()
              return
            }
            let error = Utils.verifySql(values.dataresource)
            if (error) {
              notification.warning({
                top: 92,
                message: '数据源中不可使用' + error,
                duration: 5
              })
              reject()
              return
            }
          }
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  updateDataType = (e) => {
    this.setState({dataType: e.target.value})
    this.props.updateDataType(e.target.value)
  }
  render() {
    const { setting } = this.props
    const { getFieldDecorator } = this.props.form
    const { dataType, defaultSql } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="excelout-datasource-wrap">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={8}>
              <Form.Item label="导出数据">
                {getFieldDecorator('dataType', {
                  initialValue: setting.dataType
                })(
                  <Radio.Group onChange={this.updateDataType}>
                    <Radio value="default">默认</Radio>
                    <Radio value="custom">自定义</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            {dataType === 'custom' ? <Col className="short-label" span={8}>
              <Form.Item label="表名">
                {getFieldDecorator('tableName', {
                  initialValue: setting.tableName || '',
                  rules: [
                    {
                      required: true,
                      message: '请输入表名!'
                    },
                    {
                      max: 50,
                      message: '表名最长为50个字符!'
                    }
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {dataType === 'custom' ? <Col span={8}>
              <Form.Item label="默认sql">
                {getFieldDecorator('defaultSql', {
                  initialValue: setting.defaultSql || 'true'
                })(
                <Radio.Group onChange={(e) => this.setState({defaultSql: e.target.value})}>
                  <Radio value="true">执行</Radio>
                  <Radio value="false">不执行</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {dataType === 'custom' && defaultSql === 'true' ? <Col span={24} className="data-source" style={{paddingLeft: '7px'}}>
              <Form.Item labelCol={{xs: { span: 24 }, sm: { span: 4 }}} wrapperCol={ {xs: { span: 24 }, sm: { span: 20 }} } label={
                <Tooltip placement="topLeft" title={`注:数据权限替换符 $@ -> /* 或 ''、 @$ -> */ 或 ''。`}>
                  <QuestionCircleOutlined className="mk-form-tip" />
                  数据源
                </Tooltip>
              }>
                {getFieldDecorator('dataresource', {
                  initialValue: setting.dataresource || '',
                  rules: [
                    {
                      required: true,
                      message: '请填写数据源!'
                    }
                  ]
                })(<CodeMirror />)}
              </Form.Item>
            </Col> : null}
            {dataType === 'custom' ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'查询时,搜索条件以where条件拼接进入sql,统计时,将数据源中以“@+搜索字段+@”的内容,以搜索条件中的值进行替换后,提交查询,注:查询类型仅在使用系统函数时有效。'}>
                  <QuestionCircleOutlined className="mk-form-tip" />
                  查询类型
                </Tooltip>
              }>
                {getFieldDecorator('queryType', {
                  initialValue: setting.queryType || 'query'
                })(
                <Radio.Group>
                  <Radio value="query">查询</Radio>
                  <Radio value="statistics">统计</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {dataType === 'custom' && defaultSql === 'true' ? <Col className="short-label" span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="排序方式为空时,使用表格或组件中的排序方式。">
                  <QuestionCircleOutlined className="mk-form-tip" />
                  排序方式
                </Tooltip>
              }>
                {getFieldDecorator('order', {
                  initialValue: setting.order || ''
                })(<Input placeholder={'ID asc, UID desc'} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {dataType === 'custom' ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="不使用搜索条件时,不会进行搜索条件的拼接与相关统计字段的替换。注:自定义数据来源时,只使用内部搜索。">
                  <QuestionCircleOutlined className="mk-form-tip" />
                  搜索条件
                </Tooltip>
              }>
                {getFieldDecorator('useSearch', {
                  initialValue: setting.useSearch || 'true'
                })(
                <Radio.Group>
                  <Radio value="true">使用</Radio>
                  <Radio value="false">不使用</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {dataType === 'custom' ? <Col span={8}>
              <Form.Item label="事务">
                {getFieldDecorator('transaction', {
                  initialValue: setting.transaction || 'false'
                })(
                <Radio.Group>
                  <Radio value="true">使用</Radio>
                  <Radio value="false">不使用</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/templates/sharecomponent/actioncomponent/verifyexcelout/datasource/index.scss
New file
@@ -0,0 +1,20 @@
.excelout-datasource-wrap {
  position: relative;
  .data-source {
    .ant-form-item-label {
      width: 11%;
    }
    .ant-form-item-control-wrapper {
      width: 83%;
    }
    .CodeMirror {
      height: 270px;
    }
  }
  .short-label {
    .ant-form-item-control-wrapper {
      width: 55%;
    }
  }
}
src/templates/sharecomponent/actioncomponent/verifyexcelout/index.jsx
@@ -1,19 +1,23 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Tabs, Row, Col, Button, notification, Modal, message, InputNumber, Radio } from 'antd'
import { Form, Tabs, Row, Col, Button, notification, Modal, message, InputNumber, Radio, Spin, Typography, Popconfirm } from 'antd'
import { EditOutlined, StopOutlined, CheckCircleOutlined, SwapOutlined, DeleteOutlined } from '@ant-design/icons'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import SettingUtils from './utils.jsx'
import ColumnForm from './columnform'
import DataSource from './datasource'
import CustomScript from './customscript'
import CodeMirror from '@/templates/zshare/codemirror'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { TabPane } = Tabs
const { confirm } = Modal
const { Paragraph } = Typography
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
class VerifyCard extends Component {
@@ -25,6 +29,8 @@
  state = {
    verify: {},
    activeKey: 'setting',
    systemScripts: [],
    defaultscript: '', // 自定义脚本
    excelColumns: [
      {
@@ -109,6 +115,75 @@
          {value: 'false', text: '否'}
        ]
      }
    ],
    scriptsColumns: [
      {
        title: 'SQL',
        dataIndex: 'sql',
        width: '60%',
        render: (text) => {
          let title = text.match(/^\s*\/\*.+\*\//)
          title = title && title[0] ? title[0] : ''
          let _text = title ? text.replace(title, '') : text
          return (
            <div>
              {title ? <span style={{color: '#a50'}}>{title}<span style={{fontSize: '12px', marginLeft: '5px'}}>{_text.length}</span></span> : null}
              <Paragraph copyable={{ text: text }} ellipsis={{ rows: 4, expandable: true }}>{_text}</Paragraph>
            </div>
          )
        }
      },
      {
        title: '执行位置',
        dataIndex: 'position',
        width: '10%',
        render: (text, record) => {
          if (record.position === 'init') {
            return <span style={{color: 'orange'}}>初始化</span>
          } else if (record.position === 'front') {
            return <span style={{color: '#26C281'}}>sql前</span>
          } else {
            return <span style={{color: '#1890ff'}}>sql后</span>
          }
        }
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '10%',
        render: (text, record) => record.status === 'false' ?
          (
            <div style={{color: '#ff4d4f'}}>
              {this.props.dict['model.status.forbidden']}
              <StopOutlined style={{marginLeft: '5px'}} />
            </div>
          ) :
          (
            <div style={{color: '#26C281'}}>
              {this.props.dict['model.status.open']}
              <CheckCircleOutlined style={{marginLeft: '5px'}}/>
            </div>
          )
      },
      {
        title: '操作',
        align: 'center',
        width: '140px',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div style={{textAlign: 'center'}}>
            <span className="operation-btn" title={this.props.dict['model.edit']} onClick={() => this.handleEdit(record, 'scripts')} style={{color: '#1890ff'}}><EditOutlined /></span>
            <span className="operation-btn" title={this.props.dict['header.form.status.change']} onClick={() => this.handleStatus(record, 'scripts')} style={{color: '#8E44AD'}}><SwapOutlined /></span>
            <Popconfirm
              overlayClassName="popover-confirm"
              title={this.props.dict['model.query.delete']}
              onConfirm={() => this.handleDelete(record, 'scripts')
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><DeleteOutlined /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
@@ -121,7 +196,9 @@
    }
    _verify.enable = _verify.enable || 'false'
    _verify.dataType = _verify.dataType || 'default'
    _verify.columns = _verify.columns || []
    _verify.scripts = _verify.scripts || []
    if (card.intertype !== 'system') {
      _verify.enable = 'false'
@@ -144,10 +221,138 @@
      defaultscript = `update ${config.setting.tableName || ''} set idefine5= idefine5+1 ,modifydate=getdate(),cdefine5='已导出',modifyuserid=@userid@ ${search}`
    }
    let search = []
    if (config.setting && config.setting.useMSearch === 'true' && window.GLOB.customMenu) {
      let menu = fromJS(window.GLOB.customMenu).toJS()
      let _search = null
      let filterComponent = (box) => {
        box.components.forEach(item => {
          if (_search) return
          if (item.type === 'search') {
            box.slist = [...box.slist, item.search]
          } else if (item.uuid === config.uuid) {
            _search = box.slist.pop()
          } else if (item.type === 'group') {
            item.components.forEach(m => {
              if (m.uuid !== config.uuid) return
              _search = box.slist.pop()
            })
          } else if (item.type === 'tabs') {
            item.subtabs.forEach(tab => {
              tab.slist = [...box.slist]
              filterComponent(tab)
            })
          }
        })
      }
      menu.slist = []
      filterComponent(menu)
      if (_search) {
        search = _search
      } else {
        menu.components.forEach(item => {
          if (item.type !== 'search') return
          search = item.search
        })
      }
    }
    let searches = fromJS(config.search || []).toJS()
    if (search.length > 0) {
      let keys = searches.map(item => (item.field ? item.field.toLowerCase() : ''))
      search.forEach(item => {
        if (item.field && !keys.includes(item.field.toLowerCase())) {
          searches.push(item)
        }
      })
    }
    this.setState({
      verify: _verify,
      searches: searches,
      activeKey: card.intertype === 'system' && _verify.dataType === 'custom' ? 'setting' : 'columns',
      defaultscript: defaultscript
    })
  }
  componentDidMount () {
    let _scriptSql = `Select distinct func+Remark as funcname,longparam, s.Sort from  s_custom_script s inner join (select OpenID from sapp where ID=@Appkey@) p on s.openid = case when s.appkey='' then s.openid else p.OpenID end order by s.Sort`
    _scriptSql = Utils.formatOptions(_scriptSql)
    let _sParam = {
      func: 'sPC_Get_SelectedList',
      LText: _scriptSql,
      obj_name: 'data',
      arr_field: 'funcname,longparam'
    }
    _sParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    _sParam.secretkey = Utils.encrypt(_sParam.LText, _sParam.timestamp)
    _sParam.open_key = Utils.encryptOpenKey(_sParam.secretkey, _sParam.timestamp) // 云端数据验证
    Api.getSystemConfig(_sParam).then(res => {
      if (res.status) {
        this.setState({
          systemScripts: res.data.map(item => {
            return {
              name: item.funcname,
              value: window.decodeURIComponent(window.atob(item.longparam))
            }
          })
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  handleEdit = (record, type) => {
    if (type === 'scripts') {
      this.scriptsForm.edit(record)
    }
    let node = document.getElementById('verify-excelout-box-tab').parentNode
    if (node && node.scrollTop) {
      let inter = Math.ceil(node.scrollTop / 10)
      let timer = setInterval(() => {
        if (node.scrollTop - inter > 0) {
          node.scrollTop = node.scrollTop - inter
        } else {
          node.scrollTop = 0
          clearInterval(timer)
        }
      }, 10)
    }
  }
  handleStatus = (record, type) => {
    let verify = JSON.parse(JSON.stringify(this.state.verify))
    record.status = record.status === 'false' ? 'true' : 'false'
    if (type === 'scripts') {
      verify.scripts = verify.scripts.map(item => {
        if (item.uuid === record.uuid) {
          return record
        } else {
          return item
        }
      })
    }
    this.setState({
      verify: verify
    })
  }
@@ -296,7 +501,7 @@
  }
  handleConfirm = () => {
    let verify = fromJS(this.state.verify).toJS()
    const { activeKey, verify } = this.state
    
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
@@ -313,84 +518,168 @@
        return
      }
      if (verify.enable === 'true') {
        this.props.form.validateFieldsAndScroll((err, values) => {
          if (!err) {
            values.sql = values.sql || ''
      if (activeKey === 'backscript' && verify.enable === 'true') {
        this.checkScript(resolve, reject)
      } else if (activeKey === 'setting') {
        this.settingForm.handleConfirm().then(res => {
          let _verify = {...verify, ...res}
          this.setState({
            verify: _verify
          }, () => {
            this.setState({loading: true})
            this.sqlverify(() => { // 验证成功
              this.setState({
                loading: false
              })
              resolve(_verify)
            }, () => {             // 验证失败
              this.setState({
                loading: false
              })
              reject()
            }, verify.scripts)
          })
        })
      } else if (activeKey === 'columns') {
        if (this.columnRef && this.columnRef.state.editingKey) {
          notification.warning({
            top: 92,
            message: '字段未保存!',
            duration: 5
          })
          return
        }
            let _quot = values.sql.match(/'{1}/g)
            let _lparen = values.sql.match(/\({1}/g)
            let _rparen = values.sql.match(/\){1}/g)
        if (this.props.card.intertype !== 'system' || verify.dataType !== 'custom') {
          resolve(verify)
        } else {
          this.setState({loading: true})
    
            _quot = _quot ? _quot.length : 0
            _lparen = _lparen ? _lparen.length : 0
            _rparen = _rparen ? _rparen.length : 0
            if (_quot % 2 !== 0) {
              notification.warning({
                top: 92,
                message: 'sql中\'必须成对出现',
                duration: 5
              })
              return
            } else if (_lparen !== _rparen) {
              notification.warning({
                top: 92,
                message: 'sql中()必须成对出现',
                duration: 5
              })
              return
            } else if (/--/ig.test(values.sql)) {
              notification.warning({
                top: 92,
                message: '自定义sql语句中,不可出现字符 -- ,注释请用 /*内容*/',
                duration: 5
              })
              return
            }
            let error = Utils.verifySql(values.sql, 'customscript')
            if (error) {
              notification.warning({
                top: 92,
                message: 'sql中不可使用' + error,
                duration: 5
              })
              return
            }
            let param = {
              func: 's_debug_sql',
              exec_type: 'y',
              LText: values.sql
            }
            param.LText = param.LText.replace(/@\$|\$@/ig, '')
            param.LText = Utils.formatOptions(param.LText)
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
            param.secretkey = Utils.encrypt('', param.timestamp)
            Api.getLocalConfig(param).then(res => {
              if (res.status) {
                resolve({...verify, script: values.sql})
              } else {
                Modal.error({
                  title: res.message
                })
              }
          this.sqlverify(() => { // 验证成功
            this.setState({
              loading: false
            })
            resolve(verify)
          }, () => {             // 验证失败
            this.setState({
              loading: false
            })
            reject()
          }, verify.scripts)
        }
      } else if (activeKey === 'scripts') {
        if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql') && !/^\s+$/.test(this.scriptsForm.props.form.getFieldValue('sql'))) {
          notification.warning({
            top: 92,
            message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
            duration: 5
          })
          return
        }
        this.setState({loading: true})
        this.sqlverify(() => { // 验证成功
          this.setState({
            loading: false
          })
          resolve(verify)
        }, () => {             // 验证失败
          this.setState({
            loading: false
          })
          reject()
        }, verify.scripts)
      } else {
        resolve(verify)
      }
    })
  }
  checkScript = (_resolve, _reject) => {
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        values.sql = values.sql || ''
        let _quot = values.sql.match(/'{1}/g)
        let _lparen = values.sql.match(/\({1}/g)
        let _rparen = values.sql.match(/\){1}/g)
        _quot = _quot ? _quot.length : 0
        _lparen = _lparen ? _lparen.length : 0
        _rparen = _rparen ? _rparen.length : 0
        if (_quot % 2 !== 0) {
          notification.warning({
            top: 92,
            message: 'sql中\'必须成对出现',
            duration: 5
          })
          return
        } else if (_lparen !== _rparen) {
          notification.warning({
            top: 92,
            message: 'sql中()必须成对出现',
            duration: 5
          })
          return
        } else if (/--/ig.test(values.sql)) {
          notification.warning({
            top: 92,
            message: '自定义sql语句中,不可出现字符 -- ,注释请用 /*内容*/',
            duration: 5
          })
          return
        }
        let error = Utils.verifySql(values.sql, 'customscript')
        if (error) {
          notification.warning({
            top: 92,
            message: 'sql中不可使用' + error,
            duration: 5
          })
          return
        }
        let param = {
          func: 's_debug_sql',
          exec_type: 'y',
          LText: values.sql
        }
        param.LText = param.LText.replace(/@\$|\$@/ig, '')
        param.LText = Utils.formatOptions(param.LText)
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        param.secretkey = Utils.encrypt('', param.timestamp)
        this.setState({
          loading: true
        })
        Api.getLocalConfig(param).then(res => {
          this.setState({
            loading: false
          })
          if (res.status) {
            let verify = {...this.state.verify, script: values.sql}
            this.setState({verify: verify}, () => {
              _resolve(verify)
            })
          } else {
            notification.warning({
              top: 92,
              message: '自定义脚本不可为空!',
              duration: 5
            Modal.error({
              title: res.message
            })
          }
        })
      } else {
        resolve(verify)
        notification.warning({
          top: 92,
          message: '自定义脚本不可为空!',
          duration: 5
        })
        _reject()
      }
    })
  }
@@ -470,9 +759,153 @@
    this.setState({verify: {...verify, columns}})
  }
  // 标签切换
  tabchange = (val) => {
    const { card } = this.props
    const { activeKey, verify } = this.state
    if (activeKey === 'backscript' && verify.enable === 'true') {
      this.checkScript(() => {
        this.setState({
          activeKey: val
        })
      }, () => {})
    } else if (card.intertype !== 'system' || verify.dataType !== 'custom') {
      this.setState({activeKey: val})
      return
    } else if (activeKey === 'setting') {
      this.settingForm.handleConfirm().then(res => {
        this.setState({
          verify: {...verify, ...res}
        }, () => {
          this.setState({loading: true})
          this.sqlverify(() => { // 验证成功
            this.setState({
              activeKey: val,
              loading: false
            })
          }, () => {             // 验证失败
            this.setState({
              activeKey: val,
              loading: false
            })
          }, verify.scripts)
        })
      })
    } else if (activeKey === 'columns') {
      if (this.columnRef && this.columnRef.state.editingKey) {
        notification.warning({
          top: 92,
          message: '字段未保存!',
          duration: 5
        })
        return
      }
      this.setState({loading: true})
      this.sqlverify(() => { // 验证成功
        this.setState({
          activeKey: val,
          loading: false
        })
      }, () => {             // 验证失败
        this.setState({
          activeKey: val,
          loading: false
        })
      }, verify.scripts)
    } else if (activeKey === 'scripts') {
      if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql') && !/^\s+$/.test(this.scriptsForm.props.form.getFieldValue('sql'))) {
        notification.warning({
          top: 92,
          message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
          duration: 5
        })
        return
      }
      this.setState({loading: true})
      this.sqlverify(() => { // 验证成功
        this.setState({
          activeKey: val,
          loading: false
        })
      }, () => {             // 验证失败
        this.setState({
          activeKey: val,
          loading: false
        })
      }, verify.scripts)
    } else {
      this.setState({
        activeKey: val
      })
    }
  }
  scriptsChange = (values) => {
    let verify = JSON.parse(JSON.stringify(this.state.verify))
    if (values.uuid) {
      verify.scripts = verify.scripts.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      values.uuid = Utils.getuuid()
      verify.scripts.push(values)
    }
    this.setState({loading: true})
    this.sqlverify(() => { // 验证成功
      this.setState({
        loading: false,
        verify: verify
      })
    }, () => {             // 验证失败
      this.setState({
        loading: false
      })
    }, verify.scripts)
  }
  sqlverify = (_resolve, _reject, scripts) => {
    const { searches, verify } = this.state
    let sql = SettingUtils.getDebugSql(verify, scripts, searches, Utils)
    let param = {
      func: 's_debug_sql',
      exec_type: 'y',
      LText: sql
    }
    param.LText = Utils.formatOptions(param.LText)
    param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    param.secretkey = Utils.encrypt('', param.timestamp)
    Api.getLocalConfig(param).then(result => {
      if (result.status) {
        _resolve()
      } else {
        _reject()
        Modal.error({
          title: result.message
        })
      }
    })
  }
  updateDataType = (val) => {
    this.setState({verify: {...this.state.verify, dataType: val}})
  }
  render() {
    const { card } = this.props
    const { verify, excelColumns, defaultscript } = this.state
    const { verify, excelColumns, defaultscript, scriptsColumns, activeKey, loading } = this.state
    const { getFieldDecorator } = this.props.form
    const formItemLayout = {
      labelCol: {
@@ -487,13 +920,17 @@
    return (
      <div id="verify-excelout-box-tab">
        <Tabs defaultActiveKey="1" className="excelout-verify-card-box" onChange={this.tabchange}>
        {loading && <Spin size="large" />}
        <Tabs activeKey={activeKey} className="excelout-verify-card-box" onChange={this.tabchange}>
          {card.intertype === 'system' ? <TabPane tab="基础验证" key="setting">
            <DataSource setting={verify} updateDataType={this.updateDataType} wrappedComponentRef={(inst) => this.settingForm = inst}/>
          </TabPane> : null}
          <TabPane tab={
            <span>
              Excel导出列
              {verify.columns.length ? <span className="count-tip">{verify.columns.length}</span> : null}
            </span>
          } key="1">
          } key="columns">
            <ColumnForm dict={this.props.dict} columnChange={this.columnChange}/>
            <Button className="excel-col-add mk-green" title="添加显示列字段" onClick={this.columnFieldInput}>
              同步显示列
@@ -502,14 +939,31 @@
              清空Excel列
            </Button>
            <div style={{color: '#959595', fontSize: '13px', paddingLeft: '10px'}}>如需导出序号,请使用字段 $Index。</div>
            <EditTable actions={['edit', 'move', 'copy', 'del']} type="excelcolumn" data={verify.columns} columns={excelColumns} onChange={this.changeColumns}/>
            <EditTable actions={['edit', 'move', 'copy', 'del']} type="excelcolumn" wrappedComponentRef={(inst) => this.columnRef = inst} data={verify.columns} columns={excelColumns} onChange={this.changeColumns}/>
          </TabPane>
          {card.intertype === 'system' ? <TabPane tab={
            <span>
              自定义脚本
              {verify.scripts.length ? <span className="count-tip">{verify.scripts.length}</span> : null}
            </span>
          } key="scripts" disabled={verify.dataType !== 'custom'}>
            <CustomScript
              btn={card}
              sheet={verify.tableName}
              usefulfields={verify.columns}
              scripts={verify.scripts}
              systemScripts={this.state.systemScripts}
              scriptsChange={this.scriptsChange}
              wrappedComponentRef={(inst) => this.scriptsForm = inst}
            />
            <EditTable actions={['move']} data={verify.scripts} columns={scriptsColumns} onChange={(scripts) => {this.setState({verify: {...verify, scripts}})}}/>
          </TabPane> : null}
          {card.intertype === 'system' ? <TabPane tab={
            <span>
              回调脚本
              {verify.enable === 'true' ? <span className="count-tip">1</span> : null}
            </span>
          } key="6">
          } key="backscript">
            <Form {...formItemLayout} className="verify-form">
              <Row gutter={24}>
                <Col span={8}>
@@ -537,7 +991,7 @@
              </Row>
            </Form>
          </TabPane> : null}
          <TabPane tab="信息提示" key="7">
          <TabPane tab="信息提示" key="message">
            <Form {...formItemLayout}>
              <Row gutter={24}>
                <Col offset={6} span={6}>
src/templates/sharecomponent/actioncomponent/verifyexcelout/index.scss
@@ -67,6 +67,12 @@
  }
}
#verify-excelout-box-tab {
  >.ant-spin {
    position: absolute;
    top: calc(50% - 16px);
    left: calc(50% - 16px);
    z-index: 1;
  }
  .ant-tabs-tabpane {
    position: relative;
    .excel-col-add {
src/templates/sharecomponent/actioncomponent/verifyexcelout/utils.jsx
New file
@@ -0,0 +1,244 @@
import moment from 'moment'
export default class SettingUtils {
  /**
   * @description 生成页面查询语句
   */
  static getDebugSql (verify, scripts, searches, Utils) {
    let sql = ''
    let _dataresource = verify.dataresource || ''
    let regoptions = this.getRegOptions(searches)
    let _search = this.formatSearch(searches)
    _search = Utils.joinMainSearchkey(_search)
    _search = _search.replace(/@\$@/ig, '')
    _search = _search ? 'where ' + _search : ''
    let arr_field = []
    verify.columns.forEach(item => {
      if (item.Column !== '$Index') {
        arr_field.push(item.Column)
      }
    })
    arr_field = arr_field.join(',')
    let _customScript = ''
    scripts && scripts.forEach(script => {
      if (script.status === 'false') return
      _customScript += `
      ${script.sql}
      `
    })
    if (_customScript) {
      _customScript = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000),@UserName nvarchar(50),@FullName nvarchar(50),@RoleID nvarchar(512),@mk_departmentcode nvarchar(50),@mk_organization nvarchar(50),@login_city nvarchar(50) select @ErrorCode='',@retmsg =''
        ${_customScript}
      `
    }
    if (verify.defaultSql === 'false') {
      _dataresource = ''
    }
    if (window.GLOB.funcs && window.GLOB.funcs.length > 0) {
      window.GLOB.funcs.forEach(item => {
        let reg = new RegExp('\\$ex@' + item.func_code + '@ex\\$', 'ig')
        _dataresource = _dataresource.replace(reg, `/*$ex@${item.func_code}-begin*/\n${item.key_sql}\n/*@ex$-end*/`)
        _customScript = _customScript.replace(reg, `/*$ex@${item.func_code}-begin*/\n${item.key_sql}\n/*@ex$-end*/`)
      })
    }
    _dataresource = _dataresource.replace(/@\$|\$@/ig, '')
    _customScript = _customScript.replace(/@\$|\$@/ig, '')
    // 外联数据库替换
    if (window.GLOB.externalDatabase !== null) {
      _dataresource = _dataresource.replace(/@db@/ig, window.GLOB.externalDatabase)
      _customScript = _customScript.replace(/@db@/ig, window.GLOB.externalDatabase)
    }
    // 正则替换
    let _regoptions = regoptions.map(item => {
      return {
        reg: new RegExp('@' + item.key + '@', 'ig'),
        value: `'0'`,
        type: item.type || ''
      }
    })
    _regoptions.push({
      reg: new RegExp('@login_city@', 'ig'),
      value: `''`
    }, {
      reg: new RegExp('@userName@', 'ig'),
      value: `''`
    }, {
      reg: new RegExp('@fullName@', 'ig'),
      value: `''`
    }, {
      reg: new RegExp('@orderBy@', 'ig'),
      value: verify.order
    }, {
      reg: new RegExp('@pageSize@', 'ig'),
      value: 10
    }, {
      reg: new RegExp('@pageIndex@', 'ig'),
      value: 1
    })
    if (verify.queryType === 'statistics' && _dataresource) {
      _regoptions.forEach(item => {
        _dataresource = _dataresource.replace(item.reg, item.value)
      })
      _search = ''
    } else if (_dataresource) {
      _regoptions.forEach(item => {
        if (item.type !== 'url') return
        _dataresource = _dataresource.replace(item.reg, item.value)
      })
    }
    if (_customScript) {
      _regoptions.forEach(item => {
        _customScript = _customScript.replace(item.reg, item.value)
      })
    }
    // 数据源处理, 存在显示列时
    if (arr_field && _dataresource) {
      if (/\s/.test(_dataresource)) {
        _dataresource = '(' + _dataresource + ') tb'
      }
      if (verify.order) {
        _dataresource = `select ${arr_field} from (select ${arr_field} ,ROW_NUMBER() over(order by ${verify.order}) as rows from ${_dataresource} ${_search}) tmptable order by tmptable.rows`
      } else {
        _dataresource = `select ${arr_field} from ${_dataresource} ${_search}`
      }
    }
    if (_customScript) {
      sql = `/* sql 验证 */
        ${_customScript}
        ${_dataresource}
        aaa:
        if @ErrorCode!=''
          insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
      `
    } else {
      sql = `/* sql 验证 */
        declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000),@UserName nvarchar(50),@FullName nvarchar(50),@RoleID nvarchar(512),@mk_departmentcode nvarchar(50),@mk_organization nvarchar(50),@login_city nvarchar(50) select @ErrorCode='',@retmsg =''
        ${_dataresource}`
    }
    sql = sql.replace(/\n\s{8}/ig, '\n')
    console.info(sql)
    return sql
  }
  /**
   * @description 获取全部搜索条件
   * @param {Array} searches 搜索条件数组
   */
  static formatSearch (searches) {
    if (!searches || searches.length === 0) return []
    let newsearches = []
    searches.forEach(search => {
      if (!search.field) return
      let item = {
        key: search.field,
        match: search.match,
        type: search.type,
        label: search.label,
        value: search.initval,
        required: search.required === 'true'
      }
      if (item.type === 'group') {
        item.key = search.datefield
        item.type = 'daterange'
        item.match = 'between'
        item.value = [moment().format('YYYY-MM-DD'), moment().format('YYYY-MM-DD')].join(',')
        newsearches.push(item)
        return
      } else if (item.type === 'date') {
        item.value = moment().format('YYYY-MM-DD')
      } else if (item.type === 'datemonth') {
        item.value = moment().format('YYYY-MM')
      } else if (item.type === 'dateweek') {
        item.value = moment().format('YYYY-MM-DD')
      } else if (item.type === 'daterange') {
        item.value = [moment().format('YYYY-MM-DD'), moment().format('YYYY-MM-DD')].join(',')
      } else if (item.type === 'multiselect' || (item.type === 'checkcard' && search.multiple === 'true')) {
        item.type = 'multi'
        item.value = '@$@'
      } else {
        item.value = '@$@'
      }
      newsearches.push(item)
    })
    return newsearches
  }
  /**
   * @description 获取搜索用于正则替换
   * @param {Array}   searches     搜索条件
   * @return {String}  searchText  拼接结果
   */
  static getRegOptions (searches) {
    if (!searches || searches.length === 0) return []
    let options = []
    let fieldmap = new Map()
    searches.forEach(search => {
      let item = {
        key: search.field,
        value: '0'
      }
      if (fieldmap.has(item.key)) {
        item.key = item.key + '1'
      }
      fieldmap.set(item.key, true)
      if (search.type === 'group') {
        options.push({
          key: search.field,
          value: '0'
        })
        options.push({
          key: search.datefield,
          value: '0'
        })
        options.push({
          key: search.datefield + '1',
          value: '0'
        })
        options.push(item)
      } else if (['datemonth', 'dateweek', 'daterange'].includes(search.type)) {
        options.push(item)
        options.push({
          key: item.key + '1',
          value: '0'
        })
      } else if (search.type === 'text' || search.type === 'select') {
        item.key.split(',').forEach(field => {
          let cell = JSON.parse(JSON.stringify(item))
          cell.key = field
          options.push(cell)
        })
      } else {
        options.push(item)
      }
    })
    return options
  }
}
src/templates/sharecomponent/actioncomponent/verifymegvii/index.jsx
New file
@@ -0,0 +1,249 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Tabs, Table, Row, Col, Button, notification, Modal, message, InputNumber } from 'antd'
import './index.scss'
const { TabPane } = Tabs
class MegviiCard extends Component {
  static propTpyes = {
    columns: PropTypes.array,  // 显示列
    dict: PropTypes.object,    // 字典项
    card: PropTypes.object,
  }
  state = {
    verify: {},
    activeKey: 'excelcolumn',
    excelColumns: [
      {
        title: '名称',
        dataIndex: 'field',
        width: '20%'
      },
      {
        title: '数据类型',
        dataIndex: 'datatype',
        width: '20%'
      },
      {
        title: '变量类型',
        dataIndex: 'type',
        width: '20%'
      },
      {
        title: '是否必须',
        dataIndex: 'required',
        width: '15%'
      },
      {
        title: '备注',
        dataIndex: 'remark',
        width: '25%',
      }
    ],
    excelData: [
      {field: 'id', datatype: 'string', type: 'nvarchar(50)', required: '必须', remark: '人员id'},
      {field: 'person_name', datatype: 'string', type: 'nvarchar(50)', required: '必须', remark: '人员名称'},
      {field: 'face_data', datatype: 'string', type: 'text', required: '必须', remark: 'Base64编码的照片数据(人脸信息)'},
      {field: 'group_list', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '绑定人员组的列表,多个使用逗号分隔,示例:\'1,2\''},
      {field: 'face_idx', datatype: 'integer', type: 'int', required: '非必须', remark: '照片索引,默认为0'},
      {field: 'is_admin', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '是否启用管理员权限,默认为\'false\''},
      {field: 'recognition_type', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '人员类型,staff – 普通人员(默认),visitor – 访客,blacklist – 黑名单'},
      {field: 'password', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '密码使用6位数字(AES加密)'},
      {field: 'id_number', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '身份证号码(AES加密)'},
      {field: 'person_code', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '人员编号'},
      {field: 'card_number', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '卡号,最大20位数字'},
    ]
  }
  UNSAFE_componentWillMount() {
    const { card } = this.props
    let _verify = fromJS(card.verify || {}).toJS()
    this.setState({
      verify: _verify
    })
  }
  componentDidMount () {
  }
  handleConfirm = () => {
    const { verify } = this.state
    return Promise.resolve(verify)
  }
  showError = (errorType) => {
    if (errorType === 'S') {
      notification.success({
        top: 92,
        message: '执行成功!',
        duration: 2
      })
    } else if (errorType === 'Y') {
      Modal.success({
        title: '执行成功!'
      })
    } else if (errorType === 'F') {
      notification.error({
        className: 'notification-custom-error',
        top: 92,
        message: '执行失败!',
        duration: 10
      })
    } else if (errorType === 'N') {
      notification.error({
        top: 92,
        message: '执行失败!',
        duration: 10
      })
    } else if (errorType === 'E') {
      Modal.error({
        title: '执行失败!'
      })
    } else if (errorType === 'NM') {
      message.error('执行失败!')
    }
  }
  timeChange = (val, type) => {
    const { verify } = this.state
    this.setState({
      verify: {...verify, [type]: val}
    })
  }
  tabchange = (val) => {
    this.setState({activeKey: val})
  }
  render() {
    const { verify, excelColumns, excelData, activeKey } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div>
        <Tabs activeKey={activeKey} className="megvii-verify-card-box" onChange={this.tabchange}>
          <TabPane tab={
            <span>
              列设置
            </span>
          } key="excelcolumn">
            <Table
              bordered
              rowKey="field"
              className="custom-table"
              dataSource={excelData}
              columns={excelColumns}
              pagination={false}
            />
          </TabPane>
          <TabPane tab="信息提示" key="tip">
            <Form {...formItemLayout}>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> S </span>
                    <Button onClick={() => {this.showError('S')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
                <Col span={8}>
                  <Form.Item label={'停留时间'}>
                    <InputNumber defaultValue={verify.stime || 2} min={1} max={10000} precision={0} onChange={(val) => {this.timeChange(val, 'stime')}} />
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> Y </span>
                    <Button onClick={() => {this.showError('Y')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> N </span>
                    <Button onClick={() => {this.showError('N')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
                <Col span={8}>
                  <Form.Item label={'停留时间'}>
                    <InputNumber defaultValue={verify.ntime || 10} min={1} max={10000} precision={0} onChange={(val) => {this.timeChange(val, 'ntime')}} />
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> F </span>
                    <Button onClick={() => {this.showError('F')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
                <Col span={8}>
                  <Form.Item label={'停留时间'}>
                    <InputNumber defaultValue={verify.ftime || 10} min={1} max={10000} precision={0} onChange={(val) => {this.timeChange(val, 'ftime')}} />
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> E </span>
                    <Button onClick={() => {this.showError('E')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> NM </span>
                    <Button onClick={() => {this.showError('NM')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> -1 </span>
                    不提示
                  </Form.Item>
                </Col>
              </Row>
            </Form>
          </TabPane>
        </Tabs>
      </div>
    )
  }
}
export default Form.create()(MegviiCard)
src/templates/sharecomponent/actioncomponent/verifymegvii/index.scss
New file
@@ -0,0 +1,38 @@
.megvii-verify-card-box {
  .ant-tabs-nav-scroll {
    text-align: center;
  }
  .ant-tabs-content {
    min-height: 40vh;
  }
  table tr td {
    word-wrap: break-word;
    word-break: break-word;
  }
  .excel-custom-table {
    position: relative;
    top: -25px;
  }
  .errorval {
    display: inline-block;
    width: 30px;
  }
  .operation-btn {
    display: inline-block;
    font-size: 16px;
    padding: 0 5px;
    cursor: pointer;
  }
  .ant-tabs-tabpane {
    position: relative;
    .excel-col-add {
      position: relative;
      float: right;
      right: -9px;
      margin-right: 10px;
      top: 10px;
      z-index: 1;
    }
  }
}
src/templates/sharecomponent/chartgroupcomponent/index.jsx
@@ -254,6 +254,7 @@
    }
  }
  // 已弃用,不在开放添加通道
  render() {
    const { config } = this.props
    const { dict, chartlist, modaltype, card, chartview } = this.state
src/templates/sharecomponent/settingcalcomponent/verifycard/index.jsx
@@ -294,7 +294,7 @@
      let param = {
        func: 's_debug_sql',
        exec_type: 'y',
        LText: SettingUtils.getDebugSql(setting, _scripts, columns, Utils.getRegOptions(searches), config.calendar, config.urlFields)
        LText: SettingUtils.getDebugSql(setting, _scripts, columns, this.getRegOptions(searches), config.calendar, config.urlFields)
      }
      param.LText = Utils.formatOptions(param.LText)
      param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -315,6 +315,58 @@
    }
  }
  getRegOptions = (searches) => {
    if (!searches || searches.length === 0) return []
    let options = []
    let fieldmap = new Map()
    searches.forEach(search => {
      let item = {
        key: search.field,
        value: '0'
      }
      if (fieldmap.has(item.key)) {
        item.key = item.key + '1'
      }
      fieldmap.set(item.key, true)
      if (search.type === 'group') {
        options.push({
          key: search.field,
          value: '0'
        })
        options.push({
          key: search.datefield,
          value: '0'
        })
        options.push({
          key: search.datefield + '1',
          value: '0'
        })
        options.push(item)
      } else if (['datemonth', 'dateweek', 'daterange'].includes(search.type)) {
        options.push(item)
        options.push({
          key: item.key + '1',
          value: '0'
        })
      } else if (search.type === 'text' || search.type === 'select') {
        item.key.split(',').forEach(field => {
          let cell = JSON.parse(JSON.stringify(item))
          cell.key = field
          options.push(cell)
        })
      } else {
        options.push(item)
      }
    })
    return options
  }
  updatefields = (columns) => {
    this.setState({
      columns: columns
src/templates/sharecomponent/settingcomponent/index.jsx
@@ -28,7 +28,9 @@
    search: null,        // 搜索条件,包括主表搜索
    formlist: null,      // 表单信息
    visible: false,      // 模态框控制
    loading: false       // 设置信息验证保存中
    loading: false,      // 设置信息验证保存中
    record: null
  }
  /**
@@ -84,17 +86,13 @@
    const { config } = this.props
    const { menu } = this.state
    this.settingRef.handleConfirm('func').then(setting => {
    this.settingRef.handleConfirm('loading').then(setting => {
      let res = this.resetSetting(setting)
      let _config = {...config, setting: res}
      let newLText = Utils.formatOptions(FuncUtils.getTableFunc(setting, menu, _config)) // 创建存储过程sql
      let DelText = Utils.formatOptions(FuncUtils.dropfunc(setting.innerFunc))          // 删除存储过程sql
      this.refs.funcCreatComponent.exec(setting.innerFunc, newLText, DelText).then(result => {
        if (result === 'success') {
          this.props.updatesetting(_config)
        }
      })
      this.refs.funcCreatComponent.exec(setting.innerFunc, newLText, DelText)
    })
  }
@@ -105,7 +103,7 @@
    const { config } = this.props
    const { menu } = this.state
    this.settingRef.handleConfirm('interface').then(setting => {
    this.settingRef.handleConfirm('loading').then(setting => {
      let res = this.resetSetting(setting)
      let _config = {...config, setting: res}
      let _menu = {
@@ -169,7 +167,7 @@
  render() {
    const { config } = this.props
    const { dict, visible, search, menu } = this.state
    const { dict, visible, search, menu, record } = this.state
    return (
      <div className="model-menu-setting">
@@ -183,8 +181,8 @@
          maskClosable={false}
          onCancel={() => { this.setState({ visible: false, loading: false })}}
          footer={[
            <CreateInterface key="interface" loading={this.state.interloading} dict={dict} ref="tableCreatInterface" trigger={this.tableCreatInterface}/>,
            <CreateFunc key="create" dict={dict} ref="funcCreatComponent" trigger={this.tableCreatFunc}/>,
            record && record.interType === 'system' ? <CreateInterface key="interface" loading={this.state.interloading} dict={dict} ref="tableCreatInterface" trigger={this.tableCreatInterface}/> : null,
            record && record.interType === 'inner' ? <CreateFunc key="create" dict={dict} ref="funcCreatComponent" trigger={this.tableCreatFunc}/> : null,
            <Button key="cancel" onClick={() => { this.setState({ visible: false, loading: false }) }}>{this.state.dict['model.cancel']}</Button>,
            <Button key="confirm" type="primary" loading={this.state.loading} onClick={this.settingSave}>{this.state.dict['model.confirm']}</Button>
          ]}
@@ -195,6 +193,7 @@
            menu={menu}
            config={config}
            search={search}
            updRecord={(record) => this.setState({record: fromJS(record).toJS()})}
            wrappedComponentRef={(inst) => this.settingRef = inst}
          />
        </Modal>
src/templates/sharecomponent/settingcomponent/settingform/datasource/index.jsx
@@ -635,7 +635,7 @@
                  <Radio value="default">大</Radio>
                  <Radio value="middle">中</Radio>
                  <Radio value="small">小</Radio>
                  {/* <Radio value="mini">微</Radio> */}
                  <Radio value="mini">迷你</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
src/templates/sharecomponent/settingcomponent/settingform/index.jsx
@@ -20,7 +20,8 @@
    dict: PropTypes.object,         // 字典项
    menu: PropTypes.object,         // 菜单信息
    config: PropTypes.object,       // 页面配置信息
    search: PropTypes.array         // 搜索条件
    search: PropTypes.array,        // 搜索条件
    updRecord: PropTypes.func
  }
  state = {
@@ -85,7 +86,7 @@
    status.requestMode = status.requestMode || 'system'
    status.procMode = status.procMode || 'script'
    status.callbackType = status.callbackType || 'script'
    let regoptions = Utils.getRegOptions(search)
    let regoptions = this.getRegOptions(search)
    if (config.urlFields && config.urlFields.length > 0) {
      config.urlFields.forEach(field => {
@@ -126,6 +127,62 @@
      cbScripts: _cbScripts,
      status
    })
  }
  componentDidMount () {
    this.props.updRecord(this.state.status)
  }
  getRegOptions = (searches) => {
    if (!searches || searches.length === 0) return []
    let options = []
    let fieldmap = new Map()
    searches.forEach(search => {
      let item = {
        key: search.field,
        value: '0'
      }
      if (fieldmap.has(item.key)) {
        item.key = item.key + '1'
      }
      fieldmap.set(item.key, true)
      if (search.type === 'group') {
        options.push({
          key: search.field,
          value: '0'
        })
        options.push({
          key: search.datefield,
          value: '0'
        })
        options.push({
          key: search.datefield + '1',
          value: '0'
        })
        options.push(item)
      } else if (['datemonth', 'dateweek', 'daterange'].includes(search.type)) {
        options.push(item)
        options.push({
          key: item.key + '1',
          value: '0'
        })
      } else if (search.type === 'text' || search.type === 'select') {
        item.key.split(',').forEach(field => {
          let cell = JSON.parse(JSON.stringify(item))
          cell.key = field
          options.push(cell)
        })
      } else {
        options.push(item)
      }
    })
    return options
  }
  /**
@@ -205,26 +262,6 @@
    if (activeKey === 'setting') {
      return new Promise((resolve, reject) => {
        this.settingForm.handleConfirm().then(res => {
          if (trigger === 'func' && res.interType !== 'inner') {
            notification.warning({
              top: 92,
              message: '使用内部接口,才可以创建存储过程!',
              duration: 5
            })
            this.setState({loading: false})
            reject()
            return
          } else if (trigger === 'interface' && res.interType !== 'system') {
            notification.warning({
              top: 92,
              message: '使用系统接口时,才可以创建接口!',
              duration: 5
            })
            this.setState({loading: false})
            reject()
            return
          }
          this.setState({
            setting: res
          }, () => {
@@ -243,23 +280,13 @@
      })
    } else if (activeKey === 'scripts') {
      return new Promise((resolve, reject) => {
        if (trigger === 'func' && setting.interType !== 'inner') {
          notification.warning({
            top: 92,
            message: '使用内部接口,才可以创建存储过程!',
            duration: 5
          })
        this.sqlverify(() => {
          this.setState({loading: false})
          resolve({...setting, scripts, preScripts, cbScripts})
        }, () => {
          this.setState({loading: false})
          reject()
        } else {
          this.sqlverify(() => {
            this.setState({loading: false})
            resolve({...setting, scripts, preScripts, cbScripts})
          }, () => {
            this.setState({loading: false})
            reject()
          }, 'submit')
        }
        }, 'submit')
      })
    } else {
      this.setState({loading: false})
@@ -467,7 +494,9 @@
  }
  updateStatus = (status) => {
    this.setState({status: {...this.state.status, ...status}})
    let _status = {...this.state.status, ...status}
    this.setState({status: _status})
    this.props.updRecord(_status)
  }
  render() {
src/templates/subtableconfig/index.jsx
@@ -118,7 +118,7 @@
    this.setState({
      openEdition: editSubTab ? (editSubTab.open_edition || '') : (editTab.open_edition || ''),
      chartview: _config.charts[0].uuid,
      chartview: _config.charts ? _config.charts[0].uuid : '',
      originActions: _oriActions,
      config: _config,
      activeKey: _activeKey || '0',
@@ -361,6 +361,7 @@
    let btntabs = []
    _config.action.forEach((item, index) => {
      if (item.hidden === 'true') return
      if (item.OpenType === 'popview') {
        btntabs.push(`select '${item.uuid}' as MenuID ,'${item.linkTab}' as Tabid,'${item.label}' as TabName ,'${(index + 1) * 10}' as Sort`)
      }
@@ -986,7 +987,7 @@
          config: null
        }, () => {
          this.setState({
            chartview: _config.charts[0].uuid,
            chartview: _config.charts ? _config.charts[0].uuid : '',
            config: _config,
            openEdition: res.open_edition || '',
            activeKey: '0',
@@ -1108,12 +1109,12 @@
                config={config}
                updatesearch={this.updatesearch}
              />
              <div className="chart-view" style={{position: 'relative'}}>
                {/* 视图组 权限 会员等级20+ */}
                {this.props.memberLevel >= 20 ? <ChartGroupComponent
              {config.charts ? <div className="chart-view" style={{position: 'relative'}}>
                {/* 视图组 已弃用 */}
                <ChartGroupComponent
                  config={config}
                  updatechartgroup={this.updatechartgroup}
                /> : null}
                />
                {config.charts.map(item => {
                  if (!config.expand && chartview !== item.uuid) return ''
@@ -1158,7 +1159,21 @@
                    )
                  }
                })}
              </div>
              </div> : <>
                <ActionComponent
                  type="subtable"
                  menu={{MenuID: config.uuid, MenuName: config.tabName, MenuNo: config.tabNo, fstMenuList: this.props.menu.fstMenuList}}
                  config={config}
                  tabs={this.state.tabviews}
                  setSubConfig={this.setSubConfig}
                  updateaction={this.updateaction}
                />
                <ColumnComponent
                  config={config}
                  menu={this.props.menu}
                  updatecolumn={this.updateconfig}
                />
              </>}
            </Card>
          </div>
        </DndProvider>
src/templates/zshare/createinterface/index.jsx
@@ -11,7 +11,7 @@
import Api from '@/api'
import './index.scss'
class CreateFunc extends Component {
class CreateInterface extends Component {
  static propTypes = {
    dict: PropTypes.object,  // 字典项
    trigger: PropTypes.func
@@ -1135,4 +1135,4 @@
  }
}
export default CreateFunc
export default CreateInterface
src/templates/zshare/formconfig.jsx
@@ -953,7 +953,20 @@
      }, {
        value: 'closetab',
        text: '标签关闭'
      }, {
        value: 'megvii',
        text: '旷视面板机'
      }]
    },
    { // 旷视面板机接口 待扩展
      type: 'radio',
      key: 'subFunc',
      label: '接口名称',
      initVal: card.subFunc || 'addUser',
      required: true,
      options: [
        { value: 'addUser', text: '添加用户' },
      ]
    },
    {
      type: 'select',
@@ -1392,7 +1405,7 @@
      precision: 0,
      label: '比例',
      initVal: card.ratio || 85,
      tooltip: '小于100为宽度(或高度)百分比,大于100为像素值。',
      tooltip: '模态框或抽屉的宽度,小于100为窗口宽度(或高度)百分比,大于100为像素值。',
      required: true
    },
    {
@@ -1480,6 +1493,21 @@
      initVal: card.reason || '',
      required: false
    },
    {
      type: 'radio',
      key: 'hidden',
      label: '隐藏',
      initVal: card.hidden || 'false',
      tooltip: '隐藏后按钮在页面中不显示,且不参与权限分配。',
      required: false,
      options: [{
        value: 'false',
        text: '否'
      }, {
        value: 'true',
        text: '是'
      }]
    }
  ]
}
@@ -3205,15 +3233,18 @@
    {
      type: 'radio',
      key: 'compress',
      label: '压缩',
      label: '文件处理',
      initVal: card.compress || 'false',
      tooltip: '文件压缩必须为图片,图片格式为jpg、png、gif 或 jpeg',
      tooltip: '文件压缩或base64必须为图片,图片格式为jpg、png、gif 或 jpeg。注:base64只可上传一张图片。',
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
        text: '无'
      }, {
        value: 'true',
        text: '压缩'
      }, {
        value: 'base64',
        text: 'base64'
      }]
    },
    {
src/templates/zshare/unattended/settingform/index.jsx
@@ -13,7 +13,7 @@
  state = {
    enable: this.props.autoMatic.enable,
    onFinish: this.props.autoMatic.onFinish || 'over'
    onFinish: this.props.autoMatic.onFinish || 'restart'
  }
  handleConfirm = () => {
@@ -134,11 +134,11 @@
          {enable === 'true' ? <Col span={12}>
            <Form.Item label="完成后">
              {getFieldDecorator('onFinish', {
                initialValue: autoMatic.onFinish || 'over',
                initialValue: autoMatic.onFinish || 'restart',
              })(
                <Radio.Group onChange={(e) => this.setState({onFinish: e.target.value})}>
                  <Radio value="over">结束</Radio>
                  <Radio value="restart">重新启动</Radio>
                  <Radio value="over">结束</Radio>
                </Radio.Group>
              )}
            </Form.Item>
src/utils/option.js
@@ -66,6 +66,13 @@
    isSubtable: true
  },
  {
    title: '自定义',
    type: 'CustomPage',
    url: customImg,
    baseconfig: '',
    isSystem: true
  },
  {
    title: '树形页面',
    type: 'TreePage',
    url: treepage,
@@ -80,13 +87,6 @@
    isSystem: true
  },
  {
    title: '自定义',
    type: 'CustomPage',
    url: customImg,
    baseconfig: '',
    isSystem: true
  },
  {
    title: '外部页面',
    type: 'NewPage',
    url: customImg,
@@ -97,8 +97,7 @@
    title: '角色权限分配',
    type: 'RolePermission',
    url: rolemanage,
    isSystem: true,
    hidden: true
    isSystem: true
  }
]
src/utils/utils-custom.js
@@ -85,6 +85,91 @@
  }
  /**
   * @description 获取下级模块
   * @return {String}  selfId  当前组件id
   */
  static getAnchors (components, selfId) {
    let modules = components.map(item => {
      if (item.uuid === selfId) {
        return {
          children: null
        }
      } else if (item.type === 'tabs') {
        let _item = {
          type: 'tabs',
          value: item.uuid,
          label: item.name,
          children: item.subtabs.map(f_tab => {
            let subItem = {
              type: 'tab',
              value: f_tab.uuid,
              label: f_tab.label,
              children: this.getSubModules(f_tab.components, selfId)
            }
            if (!subItem.children || subItem.children.length === 0) {
              return {children: null}
            }
            return subItem
          })
        }
        _item.children = _item.children.filter(t => t.children !== null)
        if (_item.children.length === 0) {
          return {children: null}
        }
        return _item
      } else if (item.type === 'group') {
        let _item = {
          value: item.uuid,
          label: item.name,
          children: item.components.map(f_tab => {
            if (f_tab.uuid === selfId) {
              return {
                children: null
              }
            } else if (f_tab.format) {
              return {
                value: f_tab.uuid,
                label: f_tab.name
              }
            }
            return {
              children: null
            }
          })
        }
        _item.children = _item.children.filter(t => t.children !== null)
        if (_item.children.length === 0) {
          return {children: null}
        }
        return _item
      } else if (!['login', 'navbar', 'topbar', 'tabs', 'search', 'group', 'balcony'].includes(item.type)) { // 数据格式,存在数据源
        return {
          value: item.uuid,
          label: item.name
        }
      } else {
        return {
          children: null
        }
      }
    })
    modules = modules.filter(mod => mod.children !== null)
    if (modules.length === 0) {
      return null
    }
    return modules
  }
  /**
   * @description 获取上级模块
   * @return {String}  selfId  当前组件id
   */
src/utils/utils-update.js
@@ -135,6 +135,14 @@
    })
  }
  if (config.version < '2.0') {
    config.version = '2.0'
    // 去除传统table中的标签,调整数据结构
    if (config.charts && config.charts.length === 1) {
      config.charts = null
    }
  }
  config.Template = 'CommonTable'
  return config
@@ -216,6 +224,14 @@
      return item
    })
  }
  if (config.version < '2.0') {
    config.version = '2.0'
    // 去除传统table中的标签,调整数据结构
    if (config.charts && config.charts.length === 1) {
      config.charts = null
    }
  }
  
  config.Template = 'SubTable'
src/utils/utils.js
@@ -705,63 +705,6 @@
  }
  /**
   * @description 获取搜索用于正则替换
   * @param {Array}   searches     搜索条件
   * @return {String}  searchText  拼接结果
   */
  static getRegOptions (searches) {
    if (!searches || searches.length === 0) return []
    let options = []
    let fieldmap = new Map()
    searches.forEach(search => {
      let item = {
        key: search.field,
        value: '0'
      }
      if (fieldmap.has(item.key)) {
        item.key = item.key + '1'
      }
      fieldmap.set(item.key, true)
      if (search.type === 'group') {
        options.push({
          key: search.field,
          value: '0'
        })
        options.push({
          key: search.datefield,
          value: '0'
        })
        options.push({
          key: search.datefield + '1',
          value: '0'
        })
        options.push(item)
      } else if (['datemonth', 'dateweek', 'daterange'].includes(search.type)) {
        options.push(item)
        options.push({
          key: item.key + '1',
          value: '0'
        })
      } else if (search.type === 'text' || search.type === 'select') {
        item.key.split(',').forEach(field => {
          let cell = JSON.parse(JSON.stringify(item))
          cell.key = field
          options.push(cell)
        })
      } else {
        options.push(item)
      }
    })
    return options
  }
  /**
   * @description 获取图片真实路径
   * @return {String}    url 图片路径
   */
@@ -1571,6 +1514,24 @@
      Declare @tbid nvarchar(50),@ErrorCode nvarchar(50),@retmsg nvarchar(4000),@BillCode nvarchar(50),@BVoucher nvarchar(50),@FIBVoucherDate nvarchar(50), @FiYear nvarchar(50), @UserName nvarchar(50),@FullName nvarchar(50),@RoleID nvarchar(512),@mk_departmentcode nvarchar(50),@mk_organization nvarchar(50),@login_city nvarchar(50),@bid nvarchar(50),@ModularDetailCode nvarchar(50)${_declarefields}
    `
  let userName = sessionStorage.getItem('User_Name') || ''
  let fullName = sessionStorage.getItem('Full_Name') || ''
  let RoleID = sessionStorage.getItem('role_id') || ''
  let departmentcode = sessionStorage.getItem('departmentcode') || ''
  let organization = sessionStorage.getItem('organization') || ''
  let city = sessionStorage.getItem('city') || ''
  if (sessionStorage.getItem('isEditState') === 'true') {
    userName = sessionStorage.getItem('CloudUserName') || ''
    fullName = sessionStorage.getItem('CloudFullName') || ''
  }
  // 初始化凭证及用户信息字段
  _sql += `
      /* 凭证及用户信息初始化赋值 */
      select @BVoucher='',@FIBVoucherDate='',@FiYear='',@ErrorCode='',@retmsg='',@UserName='${userName}', @FullName='${fullName}', @RoleID='${RoleID}', @mk_departmentcode='${departmentcode}', @mk_organization='${organization}', @login_city='${city}', @BillCode='', @ModularDetailCode=''
      `
  // 表单变量赋值
  if (_initFormfields.length > 0) {
    _sql += `
@@ -1599,24 +1560,6 @@
  if (verify.billcodes) {
    verify.billcodes = verify.billcodes.filter(item => item.status !== 'false')
  }
  let userName = sessionStorage.getItem('User_Name') || ''
  let fullName = sessionStorage.getItem('Full_Name') || ''
  let RoleID = sessionStorage.getItem('role_id') || ''
  let departmentcode = sessionStorage.getItem('departmentcode') || ''
  let organization = sessionStorage.getItem('organization') || ''
  let city = sessionStorage.getItem('city') || ''
  if (sessionStorage.getItem('isEditState') === 'true') {
    userName = sessionStorage.getItem('CloudUserName') || ''
    fullName = sessionStorage.getItem('CloudFullName') || ''
  }
  // 初始化凭证及用户信息字段
  _sql += `
      /* 凭证及用户信息初始化赋值 */
      select @BVoucher='',@FIBVoucherDate='',@FiYear='',@ErrorCode='',@retmsg='',@UserName='${userName}', @FullName='${fullName}', @RoleID='${RoleID}', @mk_departmentcode='${departmentcode}', @mk_organization='${organization}', @login_city='${city}', @BillCode='', @ModularDetailCode=''
      `
  if (retmsg) {
    _callbacksql = _sql
src/views/billprint/index.scss
@@ -51,6 +51,13 @@
    background: transparent!important;
    border-radius: 0!important;
  }
  .ant-table.ant-table-mini .ant-table-thead > tr > th {
    padding: 8px 8px!important;
  }
  .ant-table.ant-table-mini .ant-table-tbody > tr > td {
    padding: 2px 8px!important;
    line-height: 1.5;
  }
}
.print-page.auto:last-child {
  height: auto!important;
src/views/design/header/index.jsx
@@ -333,43 +333,43 @@
        {editLevel === 'HS' ? <Button className="level4-close" type="primary" onClick={this.exitManage}>退出</Button> : null}
        {/* 进入编辑按钮 */}
        {!editLevel && menulist ? <EditOutlined onClick={this.enterEdit} className="edit-check" /> : null}
        {!editLevel && options.sysType === 'local' && window.GLOB.systemType !== 'production' && this.props.memberLevel >= 20 ?
        {!editLevel && window.GLOB.systemType !== 'production' ?
          <div className="app-entrance entrance">
            <div className="icon"><AppstoreOutlined /></div>
            <div className="title">应用管理</div>
            <div className="detail">可创建及管理PC、pad及移动端等不同设备的应用,实现明科云APP、微信公众号、小程序等多平台的应用共享。</div>
            <Button type="primary" onClick={() => {window.open('#/appmanage')}}>
            <Button type="primary" disabled={!(this.props.memberLevel >= 20)} title={this.props.memberLevel >= 20 ? '' : '会员等级不够,无开发权限。'} onClick={() => {window.open('#/appmanage')}}>
              编辑
            </Button>
          </div> : null
        }
        {editLevel === 'HS' && options.sysType === 'local' && window.GLOB.systemType === 'production' && this.props.memberLevel >= 20 ?
        {editLevel === 'HS' && window.GLOB.systemType === 'production' ?
          <div className="app-prod-entrance entrance">
            <div className="icon"><AppstoreOutlined /></div>
            <div className="title">应用管理</div>
            <div className="detail">可创建及管理PC、pad及移动端等不同设备的应用,实现明科云APP、微信公众号、小程序等多平台的应用共享。</div>
            <Button type="primary" onClick={() => {window.open('#/appcheck')}}>
            <Button type="primary" disabled={!(this.props.memberLevel >= 20)} title={this.props.memberLevel >= 20 ? '' : '会员等级不够,无开发权限。'} onClick={() => {window.open('#/appcheck')}}>
              查看
            </Button>
          </div> : null
        }
        {!editLevel && options.sysType === 'local' && this.props.memberLevel >= 20 ?
        {!editLevel ?
          <div className="api-entrance entrance">
            <div className="icon"><ApiOutlined /></div>
            <div className="title">接口调试</div>
            <div className="detail">可自动处理登录接口的参数加密,以及业务接口的签名计算,方便开发人员的接口测试工作。</div>
            <Button type="primary" onClick={() => {window.open('#/interface')}}>
            <Button type="primary" disabled={!(this.props.memberLevel >= 20)} title={this.props.memberLevel >= 20 ? '' : '会员等级不够,无开发权限。'} onClick={() => {window.open('#/interface')}}>
              编辑
            </Button>
          </div> : null
        }
        {/* window.btoa(window.encodeURIComponent(JSON.stringify({ MenuType: 'home', MenuId: 'home_page_id', MenuName: '首页' }))) */}
        {!editLevel && window.GLOB.systemType !== 'production' && this.props.memberLevel >= 20 ?
        {!editLevel && window.GLOB.systemType !== 'production' ?
          <div className="home-entrance entrance">
            <div className="icon"><HomeOutlined /></div>
            <div className="title">首页</div>
            <div className="detail">基于自定义页面的首页设计,可实现灵活的元素配置及样式调整,展现当前系统的风格。</div>
            <Button type="primary" onClick={() => {window.open('#/menudesign/JTdCJTIyTWVudVR5cGUlMjIlM0ElMjJob21lJTIyJTJDJTIyTWVudUlkJTIyJTNBJTIyaG9tZV9wYWdlX2lkJTIyJTJDJTIyTWVudU5hbWUlMjIlM0ElMjIlRTklQTYlOTYlRTklQTElQjUlMjIlN0Q=')}}>
            <Button type="primary" disabled={!(this.props.memberLevel >= 20)} title={this.props.memberLevel >= 20 ? '' : '会员等级不够,无开发权限。'} onClick={() => {window.open('#/menudesign/JTdCJTIyTWVudVR5cGUlMjIlM0ElMjJob21lJTIyJTJDJTIyTWVudUlkJTIyJTNBJTIyaG9tZV9wYWdlX2lkJTIyJTJDJTIyTWVudU5hbWUlMjIlM0ElMjIlRTklQTYlOTYlRTklQTElQjUlMjIlN0Q=')}}>
              编辑
            </Button>
          </div> : null
src/views/login/index.jsx
@@ -542,7 +542,7 @@
              style: res.CSS || '',
              showline: res.split_line_show || 'true',
              webSite: res.WebSite || '',
              navBar: res.menu_type,
              navBar: res.menu_type, // shutter 百叶窗、linkage_navigation 联动菜单、linkage 联动菜单_无导航栏、menu_board 菜单面板、menu_board_navigation 菜单面板_标签页
              app_version: res.app_version
            }
src/views/menudesign/index.jsx
@@ -4,6 +4,7 @@
import moment from 'moment'
import HTML5Backend from 'react-dnd-html5-backend'
import { ConfigProvider, notification, Modal, Collapse, Card, Switch, Button, Typography } from 'antd'
import { DoubleLeftOutlined, DoubleRightOutlined, EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'
import html2canvas from 'html2canvas'
import Api from '@/api'
@@ -72,7 +73,9 @@
    popBtn: null,             // 弹窗标签页
    visible: false,
    customComponents: [],
    comloading: false
    comloading: false,
    settingshow: true,
    eyeopen: false
  }
  UNSAFE_componentWillMount() {
@@ -456,7 +459,7 @@
    })
  }
  getMenuMessage = () => {
  getMenuMessage = (delButtons) => {
    const { config } = this.state
    let buttons = []
    let _sort = 1
@@ -471,6 +474,10 @@
          traversal(item.components)
        } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') {
              delButtons.push(btn.uuid)
              return
            }
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
@@ -478,12 +485,20 @@
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') {
                delButtons.push(cell.uuid)
                return
              }
              this.checkBtn(cell)
              buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
            })
            card.backElements && card.backElements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') {
                delButtons.push(cell.uuid)
                return
              }
              this.checkBtn(cell)
              buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
@@ -493,6 +508,10 @@
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') {
                delButtons.push(cell.uuid)
                return
              }
              this.checkBtn(cell)
              buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
@@ -501,18 +520,30 @@
        } else if (item.type === 'balcony') {
          item.elements && item.elements.forEach(cell => {
            if (cell.eleType !== 'button') return
            if (cell.hidden === 'true') {
              delButtons.push(cell.uuid)
              return
            }
            this.checkBtn(cell)
            buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
        } else if (item.type === 'line' || item.type === 'bar' || item.type === 'chart') {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') {
              delButtons.push(btn.uuid)
              return
            }
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
        } else if (item.type === 'table' && (item.subtype === 'normaltable' || item.subtype === 'editable')) {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') {
              delButtons.push(btn.uuid)
              return
            }
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
@@ -520,6 +551,10 @@
          item.cols && item.cols.forEach(col => {
            if (col.type !== 'action') return
            col.elements.forEach(btn => {
              if (btn.hidden === 'true') {
                delButtons.push(btn.uuid)
                return
              }
              this.checkBtn(btn)
              buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
@@ -555,7 +590,7 @@
  }
  submitConfig = () => {
    const { MenuType, delButtons, copyButtons, thawButtons } = this.state
    const { MenuType, copyButtons, thawButtons } = this.state
    let config = fromJS(this.state.config).toJS()
    if (MenuType === 'billPrint' && (!config.firstCount || !config.everyPCount)) {
@@ -622,9 +657,10 @@
        LText: []
      }
      let delButtons = fromJS(this.state.delButtons).toJS()
      let btnIds = '' // 用于复制按钮的过滤
      if (MenuType !== 'billPrint') {
        btnParam.LText = this.getMenuMessage()
        btnParam.LText = this.getMenuMessage(delButtons)
        btnParam.LText = btnParam.LText.join(' union all ')
        btnIds = btnParam.LText
@@ -1055,8 +1091,16 @@
    })
  }
  changeSetting = () => {
    this.setState({settingshow: !this.state.settingshow})
    setTimeout(() => {
      MKEmitter.emit('tabsChange', 'all')
    }, 400)
  }
  render () {
    const { activeKey, comloading, MenuType, popBtn, visible, dict, MenuId, config, ParentId, MenuName, MenuNo, menuloading, customComponents } = this.state
    const { activeKey, comloading, MenuType, popBtn, visible, dict, MenuId, config, settingshow, ParentId, MenuName, MenuNo, menuloading, customComponents, eyeopen } = this.state
    return (
      <ConfigProvider locale={_locale}>
@@ -1064,7 +1108,10 @@
          <Header />
          {!popBtn && !visible ? <DndProvider backend={HTML5Backend}>
            <div className="menu-body">
              <div className="menu-setting">
              <div className={'menu-setting ' + (!settingshow ? 'hidden' : '')}>
                <div className="draw">
                  {settingshow ? <DoubleLeftOutlined onClick={this.changeSetting}/> : <DoubleRightOutlined onClick={this.changeSetting}/>}
                </div>
                <Collapse accordion activeKey={activeKey} bordered={false} onChange={(key) => this.setState({activeKey: key})}>
                  {/* 基本信息 */}
                  <Panel header={dict['mob.basemsg']} key="basedata">
@@ -1111,11 +1158,12 @@
                  </Panel>
                </Collapse>
              </div>
              <div className={'menu-view ' + (menuloading ? 'saving' : '')}>
              <div className={'menu-view' + (menuloading ? ' saving' : '') + (eyeopen ? ' eye-open' : '')}>
                <Card title={
                  <div> {config && config.MenuName} </div>
                  <div style={{paddingLeft: '15px'}}> {config && config.MenuName} </div>
                } bordered={false} extra={
                  <div>
                    <Button className="mk-border-purple" onClick={() => this.setState({eyeopen: !eyeopen})}>{!eyeopen ? <EyeOutlined /> : <EyeInvisibleOutlined />} 组件名</Button>
                    <Versions MenuId={MenuId} open_edition={config ? config.open_edition : ''}/>
                    <ReplaceField type="custom" config={config} updateConfig={this.resetConfig}/>
                    <SysInterface config={config} updateConfig={this.updateConfig}/>
src/views/menudesign/index.scss
@@ -4,6 +4,39 @@
.pc-menu-view {
  background: #000;
  min-height: 100vh;
  .mk-hidden {
    text-decoration: line-through!important;
    span {
      text-decoration: line-through!important;
    }
  }
  .eye-open {
    .component-name {
      display: block;
    }
    .anticon-tool {
      display: none;
    }
  }
  .component-name {
    position: absolute;
    z-index: 9;
    display: none;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background: rgba(255, 255, 255, 0.8);
    border: 1px solid #1890ff;
    .center {
      position: absolute;
      font-size: 16px;
      left: 50%;
      top: 50%;
      color: #1890ff;
      transform: translate(-50%, -50%);
    }
  }
  >.menu-body {
    width: 100vw;
    height: 100vh;
@@ -21,11 +54,28 @@
      width: 300px;
      background: #ffffff;
      box-shadow: 0px 2px 5px #bcbcbc;
      overflow-y: auto;
      overflow-x: hidden;
      transition: left 0.3s;
      .draw {
        position: absolute;
        z-index: 11;
        background: #ffffff;
        right: -20px;
        top: 0px;
        box-shadow: 0 0 1px #959595;
        border-radius: 0 2px 2px 0px;
        .anticon {
          padding: 12px 3px;
        }
      }
      > .ant-collapse {
        height: 100%;
        overflow-y: auto;
        overflow-x: hidden;
        background-color: #ffffff;
        border-radius: 0px;
        padding-bottom: 30px;
        .ant-collapse-item.ant-collapse-item-active {
          border-bottom: 1px solid #d9d9d9;
        }
@@ -87,19 +137,27 @@
        }
      }
    }
    .menu-setting::-webkit-scrollbar {
    .menu-setting >.ant-collapse::-webkit-scrollbar {
      width: 4px;
    }
    .menu-setting::-webkit-scrollbar-thumb {
    .menu-setting >.ant-collapse::-webkit-scrollbar-thumb {
      border-radius: 5px;
      box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.08);
      background: rgba(0, 0, 0, 0.08);
    }
    .menu-setting::-webkit-scrollbar-track {
    .menu-setting >.ant-collapse::-webkit-scrollbar-track {
      box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
      border-radius: 3px;
      border: 1px solid rgba(0, 0, 0, 0.07);
      background: rgba(0, 0, 0, 0);
    }
    .menu-setting.hidden {
      left: -300px;
    }
    .menu-setting.hidden + .menu-view {
      width: 100vw;
      margin-left: 0px;
    }
    .menu-view {
@@ -108,6 +166,7 @@
      margin-left: 300px;
      height: calc(100vh - 50px);
      overflow-y: auto;
      transition: all 0.3s;
      > .ant-card {
        >.ant-card-head {
src/views/mobdesign/index.jsx
@@ -6,7 +6,7 @@
import moment from 'moment'
import HTML5Backend from 'react-dnd-html5-backend'
import { ConfigProvider, notification, Modal, Collapse, Switch, Button, message, Spin, Typography } from 'antd'
import { DoubleLeftOutlined, DoubleRightOutlined, HomeOutlined, LoginOutlined, RedoOutlined, ArrowLeftOutlined } from '@ant-design/icons'
import { DoubleLeftOutlined, DoubleRightOutlined, HomeOutlined, LoginOutlined, RedoOutlined, ArrowLeftOutlined, EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'
import Api from '@/api'
import Utils, { setGLOBFuncs } from '@/utils/utils.js'
@@ -30,6 +30,7 @@
const CreateView = asyncComponent(() => import('@/pc/createview'))
const Transfer = asyncComponent(() => import('@/pc/transfer'))
const Versions = asyncComponent(() => import('@/menu/versions'))
const ViewNodes = asyncComponent(() => import('@/menu/viewnodes'))
const SourceWrap = asyncComponent(() => import('@/mob/modulesource'))
const BgController = asyncComponent(() => import('@/pc/bgcontroller'))
const ReplaceField = asyncComponent(() => import('@/menu/replaceField'))
@@ -73,7 +74,8 @@
    controlshow: true,
    comloading: false,
    adapters: [],
    viewType: 'menu'
    viewType: 'menu',
    eyeopen: false
  }
  UNSAFE_componentWillMount() {
@@ -124,6 +126,7 @@
          viewType: /^userbind/.test(param.MenuID) ? 'userbind' : 'menu'
        }, () => {
          this.getMenuParam(param)
          this.getRelationMenus()
        })
      }
    } catch (e) {
@@ -570,6 +573,29 @@
    this.getAppMenus()
  }
  getRelationMenus = () => {
    const { MenuId } = this.state
    let param = {
      func: 's_get_menu_used_list',
      TypeCharOne: sessionStorage.getItem('kei_no'),
      typename: sessionStorage.getItem('typename'),
      par_menuid: MenuId,
      // used_menuid: MenuId
    }
    Api.getSystemConfig(param).then(result => {
      if (!result.status) {
        notification.warning({
          top: 92,
          message: result.message,
          duration: 5
        })
        return
      }
    })
  }
  setUserBindMenu = (config, result) => {
    const { MenuId } = this.state
    let isCreate = !config
@@ -691,6 +717,7 @@
        return item
      })
      sessionStorage.setItem('appMenus', JSON.stringify(menus))
      sessionStorage.setItem('allMenus', JSON.stringify(res.menus || []))
    })
  }
@@ -911,6 +938,8 @@
          return null
        } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') return
            this.checkBtn(btn)
            m.children.push({
              key: btn.uuid,
@@ -920,6 +949,8 @@
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') return
              this.checkBtn(cell)
              m.children.push({
                key: cell.uuid,
@@ -931,6 +962,8 @@
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') return
              this.checkBtn(cell)
              m.children.push({
                key: cell.uuid,
@@ -941,6 +974,8 @@
        } else if (item.type === 'balcony') {
          item.elements && item.elements.forEach(cell => {
            if (cell.eleType !== 'button') return
            if (cell.hidden === 'true') return
            this.checkBtn(cell)
            m.children.push({
              key: cell.uuid,
@@ -967,6 +1002,8 @@
          })
        } else if (item.type === 'table' && item.subtype === 'normaltable') {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') return
            this.checkBtn(btn)
            m.children.push({
              key: btn.uuid,
@@ -976,6 +1013,8 @@
          item.cols && item.cols.forEach(col => {
            if (col.type !== 'action') return
            col.elements.forEach(btn => {
              if (btn.hidden === 'true') return
              this.checkBtn(btn)
              m.children.push({
                key: btn.uuid,
@@ -994,6 +1033,148 @@
    let trees = traversal(config.components)
    return trees
  }
  getSubMenus = () => {
    const { config } = this.state
    let menus = []
    let menuObj = {}
    let allMenus = JSON.parse(sessionStorage.getItem('allMenus'))
    allMenus.forEach(item => {
      menuObj[item.MenuID] = item
    })
    let traversal = (components) => {
      components.forEach(item => {
        if (item.type === 'topbar') {
          if (item.wrap.linkmenu && menuObj[item.wrap.linkmenu]) {
            menus.push(menuObj[item.wrap.linkmenu])
          }
          if (item.wrap.menus) {
            item.wrap.menus.forEach(m => {
              if (!menuObj[m.menu]) return
              menus.push(menuObj[m.menu])
            })
          }
        } else if (item.type === 'login') {
          if (item.wrap.linkmenu && menuObj[item.wrap.linkmenu]) {
            menus.push(menuObj[item.wrap.linkmenu])
          }
        } else if (item.type === 'menubar' && item.subtype !== 'commonbar') {
          item.subMenus.forEach(m => {
            if (m.setting.type === 'menu') {
              if (menuObj[m.uuid]) {
                menus.push(menuObj[m.uuid])
              } else {
                menus.push({
                  MenuID: m.uuid,
                  MenuName: m.setting.name,
                  MenuNo: m.setting.MenuNo
                })
              }
            } else if (m.setting.type === 'linkmenu' && menuObj[m.setting.linkMenuId]) {
              menus.push(menuObj[m.setting.linkMenuId])
            }
          })
        } else if (item.type === 'tabs') {
          item.subtabs.forEach(tab => {
            traversal(tab.components)
          })
        } else if (item.type === 'group') {
          traversal(item.components)
        } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.action && item.action.forEach(btn => {
            if (btn.linkmenu && menuObj[btn.linkmenu]) {
              menus.push(menuObj[btn.linkmenu])
            } else if (btn.openmenu && menuObj[btn.openmenu]) {
              menus.push(menuObj[btn.openmenu])
            }
          })
          item.subcards.forEach(card => {
            if (card.setting.click === 'menu' && menuObj[card.setting.menu]) {
              menus.push(menuObj[card.setting.menu])
            } else if (card.setting.click === 'menus' && card.menus) {
              card.menus.forEach(m => {
                if (menuObj[m.menu]) {
                  menus.push(menuObj[m.menu])
                }
              })
            }
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.linkmenu && menuObj[cell.linkmenu]) {
                menus.push(menuObj[cell.linkmenu])
              } else if (cell.openmenu && menuObj[cell.openmenu]) {
                menus.push(menuObj[cell.openmenu])
              }
            })
          })
        } else if (item.type === 'carousel' || item.type === 'timeline') {
          item.subcards.forEach(card => {
            if (card.setting.click === 'menu' && menuObj[card.setting.menu]) {
              menus.push(menuObj[card.setting.menu])
            }
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.linkmenu && menuObj[cell.linkmenu]) {
                menus.push(menuObj[cell.linkmenu])
              } else if (cell.openmenu && menuObj[cell.openmenu]) {
                menus.push(menuObj[cell.openmenu])
              }
            })
          })
        } else if (item.type === 'balcony') {
          item.elements && item.elements.forEach(cell => {
            if (cell.eleType !== 'button') return
            if (cell.linkmenu && menuObj[cell.linkmenu]) {
                menus.push(menuObj[cell.linkmenu])
              } else if (cell.openmenu && menuObj[cell.openmenu]) {
                menus.push(menuObj[cell.openmenu])
              }
          })
        } else if (item.type === 'form') {
          item.subcards.forEach(m => {
            if (m.subButton && m.subButton.linkmenu && menuObj[m.subButton.linkmenu]) {
              menus.push(menuObj[m.subButton.linkmenu])
            }
          })
        } else if (item.type === 'table' && item.subtype === 'normaltable') {
          item.action && item.action.forEach(btn => {
            if (btn.linkmenu && menuObj[btn.linkmenu]) {
              menus.push(menuObj[btn.linkmenu])
            } else if (btn.openmenu && menuObj[btn.openmenu]) {
              menus.push(menuObj[btn.openmenu])
            }
          })
          item.cols && item.cols.forEach(col => {
            if (col.type !== 'action') return
            col.elements.forEach(btn => {
              if (btn.linkmenu && menuObj[btn.linkmenu]) {
                menus.push(menuObj[btn.linkmenu])
              } else if (btn.openmenu && menuObj[btn.openmenu]) {
                menus.push(menuObj[btn.openmenu])
              }
            })
          })
        }
      })
    }
    traversal(config.components)
    let map = new Map()
    menus = menus.filter(m => {
      if (map.has(m.MenuID)) return false
      map.set(m.MenuID, true)
      return true
    })
    return menus
  }
  checkBtn = (btn) => {
@@ -1147,6 +1328,10 @@
        config = this.getMiniStyle(config)
      }
      let subMenus = this.getSubMenus()
      let menus_used_list = subMenus.map(m => `'${config.uuid}','${config.MenuName || ''}','${config.MenuNo || ''}','${m.MenuID}','${m.MenuName}'`).join(';')
      menus_used_list = window.btoa(window.encodeURIComponent(menus_used_list || 'del'))
      let param = {
        func: 'sPC_TrdMenu_AddUpt',
        FstID: 'mk_app',
@@ -1163,7 +1348,8 @@
        open_edition: config.open_edition,
        menus_rolelist: window.btoa(window.encodeURIComponent(JSON.stringify(roleParam))),
        LText: '',
        LTexttb: ''
        LTexttb: '',
        menus_used_list
      }
      param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -1714,7 +1900,7 @@
  render () {
    const { viewType, localedict, comloading, loading, settingshow, controlshow, activeKey, dict, MenuId, config, menuloading, customComponents, adapters } = this.state
    const { viewType, localedict, comloading, loading, settingshow, controlshow, activeKey, dict, MenuId, config, menuloading, customComponents, adapters, eyeopen } = this.state
    return (
      <ConfigProvider locale={localedict}>
@@ -1757,7 +1943,7 @@
                </Collapse>
              </div>
            </div>
            <div className={'menu-control ' + (!controlshow ? 'hidden' : '')}>
            <div className={'menu-control' + (!controlshow ? ' hidden' : '')}>
              <div className="draw">
                {controlshow ? <DoubleRightOutlined onClick={() => {this.setState({controlshow: false})}}/> : null}
                {!controlshow ? <DoubleLeftOutlined onClick={() => {this.setState({controlshow: true})}}/> : null}
@@ -1766,9 +1952,10 @@
                <Button type="primary" onClick={this.submitConfig} id="save-config" loading={menuloading}>{dict['mob.save']}</Button>
                <Switch className="big" checkedChildren={dict['mob.enable']} unCheckedChildren={dict['mob.disable']} checked={config && config.enabled} onChange={this.onEnabledChange} />
                <ArrowLeftOutlined title="后退" className="back-view" onClick={this.backView}/>
                <Button className="mk-border-purple" onClick={() => this.setState({eyeopen: !eyeopen})}>{!eyeopen ? <EyeOutlined /> : <EyeInvisibleOutlined />} 组件名</Button>
                <CreateView resetmenu={this.getAppMenus} />
                <PasteController insert={this.insert} />
                <StyleCombControlButton menu={config} />
                <ViewNodes config={config} MenuId={MenuId}/>
                <SysInterface config={config} updateConfig={this.updateConfig}/>
                <PictureController/>
                <Quotecomponent config={config} updateConfig={this.updateConfig}/>
@@ -1777,11 +1964,12 @@
                <ReplaceField type="custom" config={config} updateConfig={this.resetConfig}/>
                <Transfer MenuID={MenuId} />
                <Versions MenuId={MenuId} open_edition={config ? config.open_edition : ''}/>
                <StyleCombControlButton menu={config} />
                <Button className="mk-border-danger" onClick={this.refreshView}><RedoOutlined /> 强制刷新</Button>
                <Button type="default" onClick={this.closeView}>关闭</Button>
              </div>
            </div>
            <div className={'menu-body menu-view' + (menuloading ? 'saving' : '')}>
            <div className={'menu-body menu-view' + (menuloading ? ' saving' : '') + (eyeopen ? ' eye-open' : '')}>
              {config && !comloading ? <div className="mob-shell" style={{width: window.GLOB.shellWidth, height: window.GLOB.shellHeight}}>
                <MobShell menu={config} handleList={this.updateConfig} />
              </div> : null}
src/views/mobdesign/index.scss
@@ -1,6 +1,40 @@
.mk-mob-view {
  min-height: 100vh;
  .eye-open {
    .component-name {
      display: block;
    }
    .anticon-tool {
      display: none;
    }
  }
  .component-name {
    position: absolute;
    z-index: 9;
    display: none;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background: rgba(255, 255, 255, 0.8);
    border: 1px solid #1890ff;
    .center {
      position: absolute;
      font-size: 16px;
      left: 50%;
      top: 50%;
      color: #1890ff;
      transform: translate(-50%, -50%);
    }
  }
  .mk-hidden {
    text-decoration: line-through!important;
    span {
      text-decoration: line-through!important;
    }
  }
  >.view-spin {
    position: absolute;
    z-index: 3;
src/views/pcdesign/index.jsx
@@ -6,7 +6,7 @@
import moment from 'moment'
import HTML5Backend from 'react-dnd-html5-backend'
import { ConfigProvider, notification, Modal, Collapse, Switch, Button, message, Spin, Typography } from 'antd'
import { DoubleLeftOutlined, DoubleRightOutlined, HomeOutlined, LoginOutlined, RedoOutlined, ArrowLeftOutlined } from '@ant-design/icons'
import { DoubleLeftOutlined, DoubleRightOutlined, HomeOutlined, LoginOutlined, RedoOutlined, ArrowLeftOutlined, EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'
import Api from '@/api'
import Utils, { setGLOBFuncs } from '@/utils/utils.js'
@@ -29,6 +29,7 @@
const PopviewController = asyncComponent(() => import('@/menu/popview'))
const Versions = asyncComponent(() => import('@/menu/versions'))
const MenuShell = asyncComponent(() => import('@/pc/menushell'))
const ViewNodes = asyncComponent(() => import('@/menu/viewnodes'))
const SourceWrap = asyncComponent(() => import('@/pc/modulesource'))
const CreateView = asyncComponent(() => import('@/pc/createview'))
const BgController = asyncComponent(() => import('@/pc/bgcontroller'))
@@ -75,7 +76,8 @@
    customComponents: [],
    settingshow: sessionStorage.getItem('settingshow') !== 'false',
    controlshow: sessionStorage.getItem('controlshow') !== 'false',
    comloading: false
    comloading: false,
    eyeopen: false
  }
  UNSAFE_componentWillMount() {
@@ -691,6 +693,7 @@
        return item
      })
      sessionStorage.setItem('appMenus', JSON.stringify(menus))
      sessionStorage.setItem('allMenus', JSON.stringify(res.menus || []))
    })
  }
@@ -911,6 +914,8 @@
          return null
        } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') return
            this.checkBtn(btn)
            m.children.push({
              key: btn.uuid,
@@ -923,6 +928,8 @@
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') return
              this.checkBtn(cell)
              m.children.push({
                key: cell.uuid,
@@ -934,6 +941,8 @@
            })
            card.backElements && card.backElements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') return
              this.checkBtn(cell)
              m.children.push({
                key: cell.uuid,
@@ -948,6 +957,8 @@
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.hidden === 'true') return
              this.checkBtn(cell)
              m.children.push({
                key: cell.uuid,
@@ -961,6 +972,8 @@
        } else if (item.type === 'balcony') {
          item.elements && item.elements.forEach(cell => {
            if (cell.eleType !== 'button') return
            if (cell.hidden === 'true') return
            this.checkBtn(cell)
            m.children.push({
              key: cell.uuid,
@@ -990,6 +1003,8 @@
          })
        } else if (item.type === 'table' && (item.subtype === 'normaltable' || item.subtype === 'editable')) {
          item.action && item.action.forEach(btn => {
            if (btn.hidden === 'true') return
            this.checkBtn(btn)
            m.children.push({
              key: btn.uuid,
@@ -1002,6 +1017,8 @@
          item.cols && item.cols.forEach(col => {
            if (col.type !== 'action') return
            col.elements.forEach(btn => {
              if (btn.hidden === 'true') return
              this.checkBtn(btn)
              m.children.push({
                key: btn.uuid,
@@ -1057,6 +1074,121 @@
    }
  }
  getSubMenus = () => {
    const { config } = this.state
    let menus = []
    let menuObj = {}
    let allMenus = JSON.parse(sessionStorage.getItem('allMenus'))
    allMenus.forEach(item => {
      menuObj[item.MenuID] = item
    })
    let traversal = (components) => {
      components.forEach(item => {
        if (item.type === 'login') {
          if (item.wrap.linkmenu && menuObj[item.wrap.linkmenu]) {
            menus.push(menuObj[item.wrap.linkmenu])
          }
        } else if (item.type === 'tabs') {
          item.subtabs.forEach(tab => {
            traversal(tab.components)
          })
        } else if (item.type === 'group') {
          traversal(item.components)
        } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.action && item.action.forEach(btn => {
            if (btn.linkmenu && menuObj[btn.linkmenu]) {
              menus.push(menuObj[btn.linkmenu])
            } else if (btn.openmenu && menuObj[btn.openmenu]) {
              menus.push(menuObj[btn.openmenu])
            }
          })
          item.subcards.forEach(card => {
            if (card.setting.click === 'menu' && menuObj[card.setting.menu]) {
              menus.push(menuObj[card.setting.menu])
            } else if (card.setting.click === 'menus' && card.menus) {
              card.menus.forEach(m => {
                if (menuObj[m.menu]) {
                  menus.push(menuObj[m.menu])
                }
              })
            }
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.linkmenu && menuObj[cell.linkmenu]) {
                menus.push(menuObj[cell.linkmenu])
              } else if (cell.openmenu && menuObj[cell.openmenu]) {
                menus.push(menuObj[cell.openmenu])
              }
            })
          })
        } else if (item.type === 'carousel' || item.type === 'timeline') {
          item.subcards.forEach(card => {
            if (card.setting.click === 'menu' && menuObj[card.setting.menu]) {
              menus.push(menuObj[card.setting.menu])
            }
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              if (cell.linkmenu && menuObj[cell.linkmenu]) {
                menus.push(menuObj[cell.linkmenu])
              } else if (cell.openmenu && menuObj[cell.openmenu]) {
                menus.push(menuObj[cell.openmenu])
              }
            })
          })
        } else if (item.type === 'balcony') {
          item.elements && item.elements.forEach(cell => {
            if (cell.eleType !== 'button') return
            if (cell.linkmenu && menuObj[cell.linkmenu]) {
                menus.push(menuObj[cell.linkmenu])
              } else if (cell.openmenu && menuObj[cell.openmenu]) {
                menus.push(menuObj[cell.openmenu])
              }
          })
        } else if (item.type === 'form') {
          item.subcards.forEach(m => {
            if (m.subButton && m.subButton.linkmenu && menuObj[m.subButton.linkmenu]) {
              menus.push(menuObj[m.subButton.linkmenu])
            }
          })
        } else if (item.type === 'table' && item.subtype === 'normaltable') {
          item.action && item.action.forEach(btn => {
            if (btn.linkmenu && menuObj[btn.linkmenu]) {
              menus.push(menuObj[btn.linkmenu])
            } else if (btn.openmenu && menuObj[btn.openmenu]) {
              menus.push(menuObj[btn.openmenu])
            }
          })
          item.cols && item.cols.forEach(col => {
            if (col.type !== 'action') return
            col.elements.forEach(btn => {
              if (btn.linkmenu && menuObj[btn.linkmenu]) {
                menus.push(menuObj[btn.linkmenu])
              } else if (btn.openmenu && menuObj[btn.openmenu]) {
                menus.push(menuObj[btn.openmenu])
              }
            })
          })
        }
      })
    }
    traversal(config.components)
    let map = new Map()
    menus = menus.filter(m => {
      if (map.has(m.MenuID)) return false
      map.set(m.MenuID, true)
      return true
    })
    return menus
  }
  submitConfig = () => {
    const { delButtons, copyButtons, thawButtons } = this.state
    let config = fromJS(this.state.config).toJS()
@@ -1086,6 +1218,10 @@
      let roleParam = this.getMenuMessage()
      let subMenus = this.getSubMenus()
      let menus_used_list = subMenus.map(m => `'${config.uuid}','${config.MenuName || ''}','${config.MenuNo || ''}','${m.MenuID}','${m.MenuName}'`).join(';')
      menus_used_list = window.btoa(window.encodeURIComponent(menus_used_list || 'del'))
      let param = {
        func: 'sPC_TrdMenu_AddUpt',
        FstID: 'mk_app',
@@ -1102,7 +1238,8 @@
        menus_rolelist: window.btoa(window.encodeURIComponent(JSON.stringify(roleParam))),
        open_edition: config.open_edition,
        LText: '',
        LTexttb: ''
        LTexttb: '',
        menus_used_list
      }
      param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -1762,7 +1899,7 @@
  }
  render () {
    const { localedict, loading, visible, popBtn, comloading, activeKey, settingshow, controlshow, dict, MenuId, config, menuloading, customComponents } = this.state
    const { localedict, loading, visible, popBtn, comloading, activeKey, settingshow, controlshow, dict, MenuId, config, menuloading, customComponents, eyeopen } = this.state
    return (
      <ConfigProvider locale={localedict}>
@@ -1812,9 +1949,10 @@
                <Button type="primary" id="save-config" onClick={this.submitConfig} loading={menuloading}>{dict['mob.save']}</Button>
                <Switch className="big" checkedChildren={dict['mob.enable']} unCheckedChildren={dict['mob.disable']} checked={config && config.enabled} onChange={this.onEnabledChange} />
                <ArrowLeftOutlined title="后退" className="back-view" onClick={this.backView}/>
                <Button className="mk-border-purple" onClick={() => this.setState({eyeopen: !eyeopen})}>{!eyeopen ? <EyeOutlined /> : <EyeInvisibleOutlined />} 组件名</Button>
                <CreateView resetmenu={this.getAppMenus} />
                <PasteController insert={this.insert} />
                <StyleCombControlButton menu={config} />
                <ViewNodes config={config} MenuId={MenuId}/>
                <SysInterface config={config} updateConfig={this.updateConfig}/>
                <PictureController/>
                <Quotecomponent config={config} updateConfig={this.updateConfig}/>
@@ -1823,11 +1961,12 @@
                <ReplaceField type="custom" config={config} updateConfig={this.resetConfig}/>
                <Transfer MenuID={MenuId} />
                <Versions MenuId={MenuId} open_edition={config ? config.open_edition : ''}/>
                <StyleCombControlButton menu={config} />
                <Button className="mk-border-danger" onClick={this.refreshView}><RedoOutlined /> 强制刷新</Button>
                <Button type="default" onClick={this.closeView}>关闭</Button>
              </div>
            </div>
            <div className={'menu-body menu-view' + (menuloading ? 'saving' : '')}>
            <div className={'menu-body menu-view' + (menuloading ? ' saving' : '') + (eyeopen ? ' eye-open' : '')}>
              {config && !comloading ? <MenuShell menu={config} handleList={this.updateConfig} /> : null}
            </div>
          </DndProvider> : null}
src/views/pcdesign/index.scss
@@ -5,6 +5,40 @@
  background: #000;
  min-height: 100vh;
  .eye-open {
    .component-name {
      display: block;
    }
    .anticon-tool {
      display: none;
    }
  }
  .component-name {
    position: absolute;
    z-index: 9;
    display: none;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background: rgba(255, 255, 255, 0.8);
    border: 1px solid #1890ff;
    .center {
      position: absolute;
      font-size: 16px;
      left: 50%;
      top: 50%;
      color: #1890ff;
      transform: translate(-50%, -50%);
    }
  }
  .mk-hidden {
    text-decoration: line-through!important;
    span {
      text-decoration: line-through!important;
    }
  }
  >.view-spin {
    position: absolute;
    z-index: 3;
src/views/rolemanage/index.jsx
@@ -689,17 +689,17 @@
  }
  saveTree = () => {
    const { trees } = this.state
    // const { trees } = this.state
    const _this = this
    if (!trees || trees.length === 0) {
      notification.warning({
        top: 92,
        message: '未获取到权限信息!',
        duration: 5
      })
      return
    }
    // if (!trees || trees.length === 0) {
    //   notification.warning({
    //     top: 92,
    //     message: '未获取到权限信息!',
    //     duration: 5
    //   })
    //   return
    // }
    confirm({
      content: '确定执行吗?',