king
2021-02-02 316877c1d9e5b6d92334f30b03d97d7e833cd934
2021-02-02
5 文件已重命名
116个文件已修改
54个文件已添加
3个文件已删除
13233 ■■■■ 已修改文件
src/api/index.js 135 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/css/main.scss 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/css/viewstyle.scss 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/mobimg/editor.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/mobimg/sandbox.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Image/index.jsx 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Image/index.scss 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/editor/index.jsx 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/editor/index.scss 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/querylog/index.jsx 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/sidemenu/index.jsx 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/sidemenu/index.scss 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/tabview/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/tabview/index.scss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/video/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/en-US/model.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/zh-CN/model.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/bgcontroller/index.jsx 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/bgcontroller/index.scss 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/elementform/index.jsx 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/elementform/index.scss 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/formconfig.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/codecontent/index.jsx 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/codecontent/index.scss 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/editorcode/index.jsx 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/editorcode/index.scss 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/index.jsx 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/index.scss 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/wrapsetting/index.jsx 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/wrapsetting/index.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/wrapsetting/settingform/index.jsx 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/code/sandbox/wrapsetting/settingform/index.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/editorcontent/index.jsx 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/editorcontent/index.scss 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/index.jsx 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/index.scss 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/wrapsetting/index.jsx 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/wrapsetting/index.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/wrapsetting/settingform/index.jsx 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/wrapsetting/settingform/index.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/group/groupcomponents/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/group/groupcomponents/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/actionform/index.jsx 106 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/actionform/index.scss 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/formconfig.jsx 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/sourcecomponent/index.jsx 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/sourcecomponent/index.scss 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/sourcecomponent/inputform/index.jsx 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/sourcecomponent/inputform/index.scss 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/usercomponent/index.jsx 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/normal-table/columns/editColumn/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/tabs/tabcomponents/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/tabs/tabcomponents/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/datasource/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/datasource/verifycard/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/card.jsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/index.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/dragsource/index.jsx 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/dragsource/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/index.jsx 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/option.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/pastecontroller/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/pastecontroller/index.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/picturecontroller/editform/index.jsx 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/picturecontroller/editform/index.scss 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/picturecontroller/index.jsx 320 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/picturecontroller/index.scss 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/picturecontroller/video/index.jsx 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/picturecontroller/video/index.scss 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/popview/index.jsx 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/stylecombcontrolbutton/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/stylecombcontrolbutton/index.scss 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/stylecontroller/index.jsx 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/stylecontroller/index.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/index.jsx 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/index.scss 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/baseform/index.jsx 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/baseform/index.scss 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/index.jsx 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/index.scss 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/simplescript/index.jsx 450 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/simplescript/index.scss 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/utils.jsx 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/datasource/verifycard/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/setupProxy.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/commontable/index.jsx 254 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/data-card/asyncButtonComponent.jsx 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/prop-card/asyncButtonComponent.jsx 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/table-card/asyncButtonComponent.jsx 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/code/sand-box/index.jsx 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/code/sand-box/index.scss 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/editor/braft-editor/index.jsx 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/editor/braft-editor/index.scss 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/group/normal-group/index.jsx 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/braftContent/index.jsx 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/braftContent/index.scss 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/normalTable/index.jsx 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/tabtransfer/index.jsx 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/formtab/actionList/index.jsx 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/formtab/index.jsx 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/scriptmanage/actionList/index.jsx 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/scriptmanage/actionList/index.scss 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/scriptmanage/config.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/scriptmanage/index.jsx 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/subtable/index.jsx 333 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/subtable/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/subtabtable/index.jsx 285 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/verupmanage/actionList/index.jsx 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/verupmanage/actionList/index.scss 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/verupmanage/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/verupmanage/subtabtable/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/excelInbutton/index.jsx 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/exceloutbutton/index.jsx 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/index.scss 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/newpagebutton/index.jsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/normalbutton/index.jsx 957 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/popupbutton/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/printbutton/index.jsx 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/tabbutton/index.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/fileupload/index.jsx 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/mutilform/customTextArea/index.jsx 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/mutilform/customTextArea/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/mutilform/index.jsx 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/mutilform/index.scss 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/normalTable/index.jsx 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/settingcomponent/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/tabcomponent/tabform/index.jsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/comtableconfig/index.jsx 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/formtabconfig/actionform/index.jsx 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/formtabconfig/actionform/index.scss 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/formtabconfig/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/modalconfig/dragelement/card.jsx 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/modalconfig/dragelement/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/modalconfig/index.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/modalconfig/source.jsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/actionform/index.jsx 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/actionform/index.scss 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/index.scss 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelin/customscript/index.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelin/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/columncomponent/columnform/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcalcomponent/verifycard/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/index.scss 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/settingform/datasource/index.jsx 206 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/settingform/index.jsx 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/settingform/simplescript/index.jsx 488 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/settingform/simplescript/index.scss 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/settingform/utils.jsx 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/tabscomponent/tabform/index.jsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/treesettingcomponent/settingform/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/index.jsx 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/codemirror/index.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/customscript/index.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/editTable/index.jsx 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/formconfig.jsx 153 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/modalform/index.jsx 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/verifycard/callbackcustomscript/index.jsx 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/verifycard/callbackcustomscript/index.scss 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/verifycard/customscript/index.jsx 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/verifycard/index.jsx 259 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils-datamanage.js 248 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils-update.js 311 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils.js 1796 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/billprint/index.jsx 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/billprint/index.scss 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login/index.jsx 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/main/index.jsx 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/main/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.jsx 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/printmenuform/index.jsx 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobdesign/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/printTemplate/index.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.js
@@ -41,13 +41,11 @@
axios.defaults.withCredentials = true
axios.interceptors.request.use((config) => {
  config.method = 'post'
  if (config.url.includes('LoginAndRedirect') || config.url.includes('getjsonresult') || config.url.includes('wxNativePay')) {
    config.data = qs.stringify(config.data)
  } else if (config.url.includes('Upload') || config.url.includes('doupload') || config.url.includes('dopreload')) {
    config.headers = { 'Content-Type': 'multipart/form-data' }
  } else {
    // config.headers.token = sessionStorage.getItem('TOKEN') || ''
  } else if (config.method === 'post') {
    config.data = JSON.stringify(config.data)
  }
@@ -101,7 +99,7 @@
      duration: 15
    })
  }
  return Promise.reject(error)
  return Promise.reject(error.response)
})
class Api {
@@ -122,6 +120,7 @@
    return axios({
      url: '/webapi/dostar',
      method: 'post',
      data: param
    })
  }
@@ -144,6 +143,30 @@
    })
  }
  /* @description 直接请求
   * @param {Object} param 查询及提交参数
   */
  directRequest (url, method = 'post', param) {
    let params = { method: 'post' }
    let _url = url
    if (method === 'get' && param) {
      let keys = Object.keys(param).map(key => `${key}=${param[key]}`)
      keys = keys.join('&')
      if (keys) {
        _url = _url + '?' + keys
      }
    } else if (method === 'post' && param) {
      params.data = param
    }
    _url = _url.replace(/&/ig, '%26')
    // _url = window.btoa(_url)
    params.url = '/trans/redirect?rd=' + _url + '&method=' + method
    return axios(params)
  }
  /**
   * @description 使用dostar接口,跳转至dostars
   * @param {Object} param 查询及提交参数
@@ -156,6 +179,7 @@
    return axios({
      url: '/webapi/dostar',
      method: 'post',
      data: param
    })
  }
@@ -189,6 +213,7 @@
    return axios({
      url: '/webapi/dologon',
      method: 'post',
      data: param
    })
  }
@@ -220,6 +245,7 @@
    return axios({
      url: '/webapi/dologon',
      method: 'post',
      data: param
    })
  }
@@ -250,6 +276,7 @@
    return axios({
      url: '/webapi/dologon',
      method: 'post',
      data: param
    })
  }
@@ -312,6 +339,7 @@
      return new Promise((resolve, reject) => {
        axios({
          url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
          method: 'post',
          data: param
        }).then(res => {
          if (!res.status) {
@@ -442,6 +470,7 @@
    return axios({
      url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
      method: 'post',
      data: param
    })
  }
@@ -482,6 +511,7 @@
      return new Promise(resolve => {
        axios({
          url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
          method: 'post',
          data: param
        }).then(res => {
          if (res.status) {
@@ -515,6 +545,7 @@
    return axios({
      url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
      method: 'post',
      data: param
    })
  }
@@ -533,6 +564,7 @@
    return axios({
      url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
      method: 'post',
      data: param
    })
  }
@@ -586,6 +618,7 @@
            } else {
              axios({
                url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
                method: 'post',
                data: param
              }).then(res => {
                if (res.status) {
@@ -600,6 +633,7 @@
            axios({
              url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
              method: 'post',
              data: param
            }).then(res => {
              if (res.status) {
@@ -618,6 +652,7 @@
      return new Promise(resolve => {
        axios({
          url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
          method: 'post',
          data: param
        }).then(res => {
          if (res.status) {
@@ -730,6 +765,7 @@
      return new Promise(resolve => {
        axios({
          url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
          method: 'post',
          data: param
        }).then(res => {
          if (res.status) {
@@ -751,8 +787,8 @@
    param.LoginUID = sessionStorage.getItem('LoginUID') || ''
    param.appkey = window.GLOB.appkey || ''
    if (sessionStorage.getItem('isEditState') === 'true') { // HS下菜单
      param.userid = sessionStorage.getItem('CloudUserID')
    if (options.cloudServiceApi && param.rduri === options.cloudServiceApi) { // HS下菜单
      param.userid = sessionStorage.getItem('CloudUserID') || ''
      param.LoginUID = sessionStorage.getItem('CloudLoginUID') || ''
    }
@@ -760,6 +796,7 @@
    return axios({
      url: `/webapi/dostars${param.func ? '/' + param.func : ''}`,
      method: 'post',
      data: param
    })
  }
@@ -778,6 +815,7 @@
      axios({
        url: '/webapi/doexcel',
        responseType: 'blob',
        method: 'post',
        data: param
      }).then(res => {
@@ -819,28 +857,15 @@
   * @description 上传base64
   * @param {String} base64 base64图片编码
   */
  fileuploadbase64 (base64, service = 'local') {
    let param = {
      func: '',
      BasePath: 'Content/Upload',
      lang: localStorage.getItem('lang') || '',
      appkey: window.GLOB.appkey || '',
      Base64Img: base64
    }
  fileuploadbase64 (param) {
    param.func = ''
    param.BasePath = 'Content/Upload'
    param.lang = localStorage.getItem('lang') || ''
    param.appkey = window.GLOB.appkey || ''
    param.SessionUid = localStorage.getItem('SessionUid') || ''
    if (service === 'sso' && window.GLOB.mainSystemApi) {
      param.rduri = window.GLOB.mainSystemApi
      param.userid = sessionStorage.getItem('UserID')
      param.LoginUID = sessionStorage.getItem('LoginUID') || ''
    } else if (service === 'cloud' && options.cloudServiceApi) {
      param.rduri = options.cloudServiceApi
      param.userid = sessionStorage.getItem('CloudUserID')
      param.LoginUID = sessionStorage.getItem('CloudLoginUID') || ''
    } else {
      param.userid = sessionStorage.getItem('UserID')
      param.LoginUID = sessionStorage.getItem('LoginUID') || ''
    }
    param.userid = param.userid || sessionStorage.getItem('UserID') || ''
    param.LoginUID = param.LoginUID || sessionStorage.getItem('LoginUID') || ''
    param = this.encryptParam(param)
@@ -849,11 +874,13 @@
      return axios({
        url: '/webapi/dostars',
        method: 'post',
        data: param
      })
    } else {
      return axios({
        url: '/webapi/SaveBase64Image',
        method: 'post',
        data: param
      })
    }
@@ -865,6 +892,7 @@
  getLargeFileUpload (param) {
    return axios({
      url: '/webapi/doupload',
      method: 'post',
      data: param
    })
  }
@@ -875,6 +903,7 @@
  getFilePreUpload (param) {
    return axios({
      url: '/webapi/dopreload',
      method: 'post',
      data: param
    })
  }
@@ -885,53 +914,21 @@
  getWxNativePay (param) {
    return axios({
      url: '/wxpay/wxNativePay',
      method: 'post',
      data: param
    })
  }
  /**
   * @description 文件上传
   */
  getFileUpload (param) {
    return axios({
      url: '/zh-CN/Home/Upload',
      data: param
    })
  }
  /**
   * @description 通用接口(数据管理)
   * @param {Object} param 查询及提交参数
   */
  commonInterface (param) {
    param.userid = sessionStorage.getItem('UserID')
    param.lang = localStorage.getItem('lang') || ''
    param.SessionUid = localStorage.getItem('SessionUid') || ''
    param.LoginUID = sessionStorage.getItem('LoginUID') || ''
    param.BID = param.BID || ''
    param.debug = param.debug || ''
    return axios({
      url: '/webapi/dostar',
      data: param
    })
  }
  /**
   * @description 通用接口(提交)(数据管理)
   * @param {Object} param 查询及提交参数
   */
  submitInterface (param) {
    param.userid = sessionStorage.getItem('UserID')
    param.lang = localStorage.getItem('lang') || ''
    param.SessionUid = localStorage.getItem('SessionUid') || ''
    param.LoginUID = sessionStorage.getItem('LoginUID') || ''
    return axios({
      url: '/webapi/dostar',
      data: param
    })
  }
  // /**
  //  * @description 文件上传
  //  */
  // getFileUpload (param) {
  //   return axios({
  //     url: '/zh-CN/Home/Upload',
  //     method: 'post',
  //     data: param
  //   })
  // }
}
export default new Api()
src/assets/css/main.scss
@@ -71,7 +71,7 @@
  background-color: unset;
  color: unset;
}
.side-menu.ant-menu-dark .ant-menu-item.ant-menu-item-selected {
.mk-side-menu.ant-menu-dark .ant-menu-item.ant-menu-item-selected {
  background-color: unset;
  color: unset;
}
@@ -211,6 +211,9 @@
.ant-popover.popover-confirm {
  z-index: 1080!important;
}
.ant-message {
  z-index: 1110!important;
}
.ant-notification-notice.notification-custom-error {
  background: #f5222d;
@@ -258,7 +261,7 @@
.ant-modal.popview-modal {
  top: 70px;
  .ant-modal-body {
    min-height: 250px;
    min-height: 200px;
    max-height: calc(100vh - 210px);
    overflow-y: auto;
  }
@@ -282,7 +285,7 @@
    top: 70px;
  }
  .ant-modal-body {
    min-height: 250px;
    min-height: 200px;
    max-height: calc(100vh - 210px);
    overflow-y: auto;
  }
@@ -301,6 +304,34 @@
    background: rgba(0, 0, 0, 0);
  }
}
// 设置模态框样式,规定最大最小高度,重置滚动条
.action-modal {
  .ant-modal {
    max-width: 95vw;
    top: 70px;
  }
  .ant-modal-body {
    max-height: calc(100vh - 205px);
    min-height: 150px;
    overflow-y: auto;
    padding-bottom: 35px;
  }
  .ant-modal-body::-webkit-scrollbar {
    width: 7px;
    height: 7px;
  }
  .ant-modal-body::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .ant-modal-body::-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);
  }
}
.ant-drawer {
  z-index: 1080!important;
src/assets/css/viewstyle.scss
@@ -53,7 +53,7 @@
    }
  }
  #root > .main-view {
  #root > .mk-main-view {
    > .header-container {
      background: $bg1;
      color: $font1;
@@ -86,7 +86,7 @@
        }
      }
    }
    > .side-menu {
    > .mk-side-menu {
      border-right: 1px solid #d9d9d9;
      background: $bg1;
      > .ant-menu {
@@ -122,7 +122,7 @@
        }
      }
    }
    > .side-menu:not(.edit) {
    > .mk-side-menu:not(.edit) {
      > .ant-menu {
        > .ant-menu-submenu {
          > .ant-menu-sub {
@@ -137,7 +137,7 @@
        }
      }
    }
    >.content-box {
    >.mk-tabview-wrap {
      >.content-header {
        >.ant-tabs {
          >.ant-tabs-bar {
@@ -321,7 +321,7 @@
}
@mixin bgblack() {
  #root > .main-view {
  #root > .mk-main-view {
    > .header-container {
      box-shadow: none;
      > .header-menu {
src/assets/mobimg/editor.png
src/assets/mobimg/sandbox.png
src/components/Image/index.jsx
New file
@@ -0,0 +1,53 @@
import React, {Component} from 'react'
import './index.scss'
class ImageWrap extends Component {
  componentDidMount () {
    let Img = new Image()
    Img.src = this.props.url
    if (Img.complete) {
      this.setSize(Img.width, Img.height)
    } else {
      Img.onload = () => {
        this.setSize(Img.width, Img.height)
      }
    }
  }
  shouldComponentUpdate () {
    return false
  }
  setSize = (width, height) => {
    const { clientWidth, clientHeight } = this.ImageWrapDom
    if (!clientWidth || !clientHeight || !width || !height) return
    let ratio = (width / height) / (clientWidth / clientHeight)
    if (ratio > 1.5) {
      let _height = Math.floor(height * (clientWidth / width))
      this.ImageDom.style.height = 'auto'
      this.ImageDom.style.top = ((clientHeight - _height) / 2) + 'px'
    } else if (ratio > 1 && ratio < 1.5) {
      let _width = Math.floor(width / (height / clientHeight))
      this.ImageDom.style.width = _width + 'px'
      this.ImageDom.style.left = '-' + ((_width - clientWidth) / 2) + 'px'
    } else if (ratio < 1) {
      let _height = Math.floor(height / (width / clientWidth))
      this.ImageDom.style.height = _height + 'px'
      this.ImageDom.style.top = '-' + ((_height - clientHeight) / 2) + 'px'
    }
  }
  render() {
    return (
      <div className="mk_image-wrap" ref={dom => { this.ImageWrapDom = dom }}>
        <img src={this.props.url} ref={dom => { this.ImageDom = dom }} alt=""/>
      </div>
    )
  }
}
export default ImageWrap
src/components/Image/index.scss
New file
@@ -0,0 +1,13 @@
.mk_image-wrap {
  display: inline-block;
  position: relative;
  overflow: hidden;
  width: 100%;
  height: 100%;
  img {
    position: absolute;
    width: 100%;
    height: 100%;
  }
}
src/components/editor/index.jsx
@@ -1,38 +1,242 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import BraftEditor from 'braft-editor'
import 'braft-editor/dist/index.css'
import 'braft-extensions/dist/table.css'
import BraftEditor from 'braft-editor'
// import 'braft-editor/dist/output.css'
import Table from 'braft-extensions/dist/table'
import SparkMD5 from 'spark-md5'
import moment from 'moment'
import Api from '@/api'
import './index.scss'
BraftEditor.use(Table())
let service = ''
if (process.env.NODE_ENV === 'production') {
  service = document.location.origin + '/' + window.GLOB.service
} else {
  service = window.GLOB.location + '/' + window.GLOB.service
}
class NormalEditor extends Component {
  static propTpyes = {
    card: PropTypes.object,  // 条码设置
    value: PropTypes.any,    // 条码值
    Item: PropTypes.object,     // 表单元素
    onChange: PropTypes.func,   // 表单更新
  }
  state = {
    editorState: '',
    encryption: 'false',
  }
  UNSAFE_componentWillMount () {
    let initVal = null
    let encryption = 'false'
    if (this.props['data-__meta']) {
      initVal = this.props['data-__meta'].initialValue || null
    } else if (this.props.defaultValue) {
      initVal = this.props.defaultValue || null
    }
    if (this.props.Item && this.props.Item.encryption === 'true') {
      encryption = 'true'
      if (initVal) {
        try {
          initVal = window.decodeURIComponent(window.atob(initVal))
        } catch {
          initVal = this.props['data-__meta'].initialValue || null
        }
      }
    }
    this.setState({
      editorState: BraftEditor.createEditorState(initVal),
      encryption
    })
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps))
  }
  handleEditorChange = () => {
  handleEditorChange = (editorState) => {
    const { encryption } = this.state
    this.setState({ editorState })
    if (this.props.onChange) {
      let val = editorState.toHTML()
      if (encryption === 'true') {
        try {
          val = window.btoa(window.encodeURIComponent(val))
        } catch {
          val = editorState.toHTML()
        }
      }
      this.props.onChange(val)
    }
  }
  submitContent = () => {
  shardupload = (params, param) => {
    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)
    Api.getLargeFileUpload(form).then(res => {
      if (res.status) {
        if (params.chunks.length > 0) {
          param.progress(Math.floor(100 * (_param.chunk / _param.chunks)))
          this.shardupload(params, param)
        } else {
          if (res.urlPath) {
            param.success({
              url: res.urlPath
            })
          } else {
            param.error({
              url: '上传失败!'
            })
          }
        }
      } else {
        param.error({
          url: '上传失败!'
        })
      }
    })
  }
  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
  }
  handleUpload(param) {
    const file = param.file
    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) {
          param.success({
            url: res.urlPath
          })
        } 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, param)
      }
    }
    chunkFileReader.onerror = () => {
      param.error({
        url: '上传失败!'
      })
      console.warn('File reading failed.')
    }
    totalFileReader.onerror = () => {
      param.error({
        url: '上传失败!'
      })
    }
    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))
    }
  }
  render() {
    const { editorState } = this.state
    return (
      <div className="normal-braft-editor">
        <BraftEditor value={'<p></p>'} onChange={this.handleEditorChange} onSave={this.submitContent}/>
        <BraftEditor
          value={editorState}
          onChange={this.handleEditorChange}
          media={{
            uploadFn: (param) => {
              this.handleUpload(param)
            },
            validate: () => {
              return true
            },
            onInsert: () => {
            }
          }}
        />
      </div>
    )
  }
src/components/editor/index.scss
@@ -0,0 +1,29 @@
.normal-braft-editor {
  .bf-content {
    overflow-x: hidden;
    overflow-y: auto;
    height: auto;
    min-height: 500px;
    padding-bottom: 0px;
    .public-DraftEditor-content {
      padding-bottom: 0;
    }
  }
  .bf-content::-webkit-scrollbar {
    width: 7px;
  }
  .bf-content::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .bf-content::-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);
  }
  .mk-braft-delete {
    font-size: 16px;
  }
}
src/components/querylog/index.jsx
New file
@@ -0,0 +1,80 @@
import { Component } from 'react'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import MKEmitter from '@/utils/events.js'
class QueryLog extends Component {
  state = {
    logs: []
  }
  componentDidMount () {
    if (window.GLOB.systemType === 'production') {
      MKEmitter.addListener('queryTrigger', this.queryTrigger)
      setTimeout(() => {
        this.sendLog()
      }, 300000)
    }
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('queryTrigger', this.queryTrigger)
  }
  sendLog = () => {
    const { logs } = this.state
    if (logs && logs.length > 0 && sessionStorage.getItem('isEditState') !== 'true') {
      let logMap = new Map()
      logs.forEach(item => {
        if (logMap.has(item.menuId)) {
          let _item = logMap.get(item.menuId)
          _item.times++
          logMap.set(item.menuId, _item)
        } else {
          item.times = 1
          logMap.set(item.menuId, item)
        }
      })
      let userid = sessionStorage.getItem('UserID') || ''
      let LText = [...logMap.values()].map(item => `select '${item.menuId}','${item.times}','${item.name}','${window.GLOB.appkey}','${userid}'`)
      let param = {
        func: 's_get_users_operation_log',
        exec_type: 'y', // 后台解码
        LText: LText.join(' union all ')
      }
      param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
      param.secretkey = Utils.encrypt('', param.timestamp)
      param.LText = Utils.formatOptions(param.LText)
      Api.getSystemConfig(param)
      this.setState({logs: []})
    }
    setTimeout(() => {
      this.sendLog()
    }, 300000)
  }
  queryTrigger = (item) => {
    this.setState({logs: [...this.state.logs, item]})
  }
  render () {
    return null
  }
}
export default QueryLog
src/components/sidemenu/index.jsx
@@ -9,6 +9,7 @@
import { modifyTabview, resetEditLevel, modifyMenuTree, modifyMainMenu } from '@/store/action'
import { SySMenuList } from './config'
import options from '@/store/options.js'
import MKEmitter from '@/utils/events.js'
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import Api from '@/api'
@@ -83,21 +84,17 @@
    })
  }
  changemenu(e) {
  changemenu(e, menu) {
    e.preventDefault()
    if (this.props.editState && this.props.editLevel !== 'HS') {
      e.preventDefault()
      return
    }
    let menu = JSON.parse(e.target.dataset.item)
    if (menu.OpenType === 'newpage' || menu.OpenType === 'NewPage') {
      e.preventDefault()
      window.open(menu.src)
    } else if (menu.OpenType === 'blank') {
      menu.selected = true
      this.props.modifyTabview([menu])
      e.preventDefault()
    } else {
      let tabs = fromJS(this.props.tabviews).toJS()
      tabs = tabs.filter(tab => {
@@ -114,8 +111,10 @@
        tabs.push(menu)
        this.props.modifyTabview(tabs)
      })
      e.preventDefault()
    }
    if (window.GLOB.systemType === 'production') {
      MKEmitter.emit('queryTrigger', {menuId: menu.MenuID, name: '菜单'})
    }
  }
@@ -282,10 +281,10 @@
    const { mainMenu } = this.props
    const editShow = (this.props.editState && !this.props.editLevel) || false
    if (mainMenu === '') return (<span className="side-menu-hidden"></span>)
    if (mainMenu === '') return (<span className="mk-side-menu-hidden"></span>)
    return (
      <aside className={"side-menu ant-menu-dark" + (this.props.collapse ? ' side-menu-collapsed' : '') + (this.props.isiframe ? ' iframe' : '') + (this.props.editState ? ' edit' : '')}>
      <aside id="mk-sidemenu-wrap" className={'mk-side-menu ant-menu-dark' + (this.props.collapse ? ' collapsed' : '') + (this.props.isiframe ? ' mk-iframe' : '') + (this.props.editState ? ' mk-edit' : '')}>
        {!(this.props.editLevel === 'level2' || this.props.editLevel === 'level3') &&
          <Menu openKeys={this.state.openKeys} onOpenChange={this.onOpenChange} mode="inline" theme="dark" inlineCollapsed={this.props.collapse}>
          {editShow && <li className="sup-menu"><Icon onClick={this.enterSubEdit} className="edit-check" type="edit" /></li>}
@@ -306,7 +305,7 @@
                {item.children.map(cell => {
                  return (
                    <Menu.Item key={cell.MenuID}>
                      <a href={cell.src} id={cell.MenuID} data-item={JSON.stringify(cell)} onClick={this.changemenu.bind(this)}>{cell.MenuName}</a>
                      <a href={cell.src} id={cell.MenuID} onClick={(e) => this.changemenu(e, cell)}>{cell.MenuName}</a>
                    </Menu.Item>
                  )
                })}
src/components/sidemenu/index.scss
@@ -1,7 +1,7 @@
@import '../../assets/css/iconfont.css';
@import '../../assets/css/global.scss';
.side-menu {
.mk-side-menu {
  flex: 0 0 235px;
  width: 235px;
  padding: 48px 0 40px;
@@ -94,7 +94,7 @@
    left: 187px;
  }
}
.side-menu.edit { // 编辑时控制菜单底色
.mk-side-menu.mk-edit { // 编辑时控制菜单底色
  .ant-menu-sub.ant-menu-inline {
    > .ant-menu-item.ant-menu-item-selected {
      background: unset;
@@ -104,14 +104,14 @@
    }
  }
}
.side-menu.iframe { // tab页中为iframe时
.mk-side-menu.mk-iframe { // tab页中为iframe时
  max-height: 100vh;
  overflow-y: scroll;
  &::-webkit-scrollbar {
    display: none;
  }
}
.side-menu.side-menu-collapsed { // 左侧菜单合并时
.mk-side-menu.collapsed { // 左侧菜单合并时
  flex: 0 0 80px;
  width: 80px;
}
src/components/tabview/index.jsx
@@ -213,7 +213,7 @@
    const { tabviews, activeId } = this.state
    return (
      <section className={'flex-container content-box' + (this.props.collapse ? ' collapsed' : '')}>
      <section id="mk-tabview-wrap" className={'mk-tabview-wrap' + (this.props.collapse ? ' collapsed' : '')}>
        <div className="content-header">
          {tabviews && tabviews.length > 0 &&
            <Tabs activeKey={activeId}>
src/components/tabview/index.scss
@@ -1,4 +1,7 @@
.content-box {
.mk-tabview-wrap {
  display: flex;
  flex: auto;
  min-height: 100%;
  padding-top: 48px;
  max-width: calc(100% - 235px);
  transition: max-width 0.2s;
@@ -105,10 +108,10 @@
    right: 30px;
  }
}
.content-box.collapsed {
.mk-tabview-wrap.collapsed {
  max-width: calc(100% - 80px);
}
.side-menu-hidden + .content-box, .side-menu-hidden + .content-box.collapsed {
.mk-side-menu-hidden + .mk-tabview-wrap, .mk-side-menu-hidden + .mk-tabview-wrap.collapsed {
  max-width: 100%;
  >.content-header >.ant-tabs >.ant-tabs-bar {
    display: none;
src/components/video/index.jsx
@@ -28,7 +28,7 @@
    return (
      <div style={{overflow: 'hidden'}}>
        <Player poster="" autoPlay={card.autoPlay === 'true'} aspectRatio={card.aspectRatio || '16:9'}>
        <Player poster="" autoPlay={card.autoPlay === 'true'} aspectRatio={card.aspectRatio || '16:9'} loop={card.loop === 'true'}>
          <source src={value} />
          <BigPlayButton position="center" />
          <ControlBar>
src/locales/en-US/model.js
@@ -247,6 +247,5 @@
  'model.tooltip.action.guide': '在左侧工具栏《按钮》中,选择对应类型的按钮拖至此处添加,如选择按钮类型为表单、新标签页等含有配置页面的按钮,可在左侧工具栏-按钮-可配置按钮处,点击按钮完成相关配置。注:当设置按钮显示位置为表格时,显示列会增加操作列。',
  'model.tooltip.column.guide': '在左侧工具栏《显示列》中,选择对应类型的显示列拖至此处添加;或点击《添加显示列》按钮批量添加,选择批量添加时,需提前选择使用表。注:添加合并列时,需设置可选列。',
  'model.tooltip.tabs.guide': '在左侧工具栏《标签页》中,选择对应类型的标签页拖至此处添加。',
  'model.tooltip.func.innerface': '内部接口: 可自定义数据处理函数,函数名称需以@ableField等字符开始;',
  'model.tooltip.func.outface': '外部接口: 可自定义数据处理函数,提交数据经过内部函数处理后,传入外部接口,未设置时,数据会直接传入外部接口。',
  'model.tooltip.func.innerface': '内部函数名称需以@ableField等字符开始;',
}
src/locales/zh-CN/model.js
@@ -247,6 +247,5 @@
  'model.tooltip.action.guide': '在左侧工具栏《按钮》中,选择对应类型的按钮拖至此处添加,如选择按钮类型为表单、新标签页等含有配置页面的按钮,可在左侧工具栏-按钮-可配置按钮处,点击按钮完成相关配置。注:当设置按钮显示位置为表格时,显示列会增加操作列。',
  'model.tooltip.column.guide': '在左侧工具栏《显示列》中,选择对应类型的显示列拖至此处添加;或点击《添加显示列》按钮批量添加,选择批量添加时,需提前选择使用表。注:添加合并列时,需设置可选列。',
  'model.tooltip.tabs.guide': '在左侧工具栏《标签页》中,选择对应类型的标签页拖至此处添加。',
  'model.tooltip.func.innerface': '内部接口: 可自定义数据处理函数,函数名称需以@ableField等字符开始;',
  'model.tooltip.func.outface': '外部接口: 可自定义数据处理函数,提交数据经过内部函数处理后,传入外部接口,未设置时,数据会直接传入外部接口。',
  'model.tooltip.func.innerface': '内部函数名称需以@ableField等字符开始;',
}
src/menu/bgcontroller/index.jsx
@@ -1,13 +1,15 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Form, Input } from 'antd'
import { Form } from 'antd'
import zhCN from '@/locales/zh-CN/mob.js'
import enUS from '@/locales/en-US/mob.js'
import ColorSketch from '@/mob/colorsketch'
import FileUpload from '@/tabviews/zshare/fileupload'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const ColorSketch = asyncComponent(() => import('@/mob/colorsketch'))
const SourceComponent = asyncComponent(() => import('@/menu/components/share/sourcecomponent'))
class MobController extends Component {
  static propTpyes = {
@@ -19,7 +21,6 @@
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    backgroundColor: '',
    backgroundImage: '',
    bgimages: [],
  }
  UNSAFE_componentWillMount () {
@@ -27,10 +28,7 @@
    let bgImg = config.style.backgroundImage || ''
    if (bgImg && /^linear-gradient/.test(bgImg)) {
      bgImg = bgImg.replace('linear-gradient(', '')
      bgImg = bgImg.replace(')', '')
    } else if (bgImg && /^url/.test(bgImg)) {
    if (bgImg && /^url/.test(bgImg)) {
      bgImg = bgImg.replace('url(', '')
      bgImg = bgImg.replace(')', '')
    }
@@ -59,45 +57,23 @@
    this.props.updateConfig(config)
  }
  /**
   * @description 手动修改路径
   */
  changeImage = (e) => {
    let val = e.target.value
  imgChange = (val) => {
    this.setState({
      backgroundImage: val
    })
    let config = fromJS(this.props.config).toJS()
    val = val.replace(/^\s*|\s*$/ig, '')
    if (/^http|^\/\//.test(val)) {
      val = `url(${val})`
    } else if (/,/ig.test(val) && !/^(radial-gradient|linear-gradient)/ig.test(val)) {
      val = `linear-gradient(${val})`
    if (val) {
      config.style.backgroundImage = `url(${val})`
    } else {
      delete config.style.backgroundImage
    }
    config.style.backgroundImage = val
    this.props.updateConfig(config)
  }
  imgChange = (list) => {
    if (list[0] && list[0].response) {
      this.setState({
        bgimages: [],
        backgroundImage: list[0].response
      })
      let config = fromJS(this.props.config).toJS()
      config.style.backgroundImage = `url(${list[0].response})`
      this.props.updateConfig(config)
    } else {
      this.setState({bgimages: list})
    }
  }
  render () {
    const { backgroundColor, backgroundImage, bgimages } = this.state
    const { backgroundColor, backgroundImage } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
@@ -116,8 +92,7 @@
            <ColorSketch value={backgroundColor} onChange={this.changeBackgroundColor} />
          </Form.Item>
          <Form.Item colon={false} label="图片">
            <FileUpload accept=".jpg,.png,.gif,.svg" value={bgimages} maxFile={2} fileType="text" onChange={this.imgChange}/>
            <Input placeholder="" value={backgroundImage} autoComplete="off" onChange={this.changeImage}/>
            <SourceComponent value={backgroundImage} type="" placement="right" onChange={this.imgChange}/>
          </Form.Item>
        </Form>
      </div>
src/menu/bgcontroller/index.scss
@@ -6,6 +6,12 @@
    padding-top: 10px;
    line-height: 35px;
  }
  .mk-source-wrap {
    height: 32px;
    .mk-source-item-info {
      top: 5px;
    }
  }
}
.margin-popover {
src/menu/components/card/cardcellcomponent/elementform/index.jsx
@@ -1,19 +1,21 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Input, Select, Icon, Radio, Tooltip, InputNumber, notification } from 'antd'
import { Form, Row, Col, Input, Select, Icon, Radio, Tooltip, InputNumber } from 'antd'
import { formRule } from '@/utils/option.js'
import FileUpload from '@/tabviews/zshare/fileupload'
import ColorSketch from '@/mob/colorsketch'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const ColorSketch = asyncComponent(() => import('@/mob/colorsketch'))
const SourceComponent = asyncComponent(() => import('@/menu/components/share/sourcecomponent'))
const cardTypeOptions = {
  sequence: ['eleType', 'width'],
  text: ['eleType', 'datatype', 'format', 'width', 'height', 'prefix', 'postfix', 'link'],
  number: ['eleType', 'datatype', 'format', 'width', 'height', 'prefix', 'postfix'],
  picture: ['eleType', 'datatype', 'width', 'lenWidRadio', 'link'],
  video: ['eleType', 'datatype', 'width', 'aspectRatio', 'autoPlay'],
  video: ['eleType', 'datatype', 'width', 'aspectRatio', 'autoPlay', 'loop'],
  icon: ['eleType', 'icon', 'datatype', 'width'],
  slider: ['eleType', 'datatype', 'width', 'color', 'maxValue'],
  splitline: ['eleType', 'color', 'width', 'borderWidth'],
@@ -38,7 +40,6 @@
    link: ''
  }
  UNSAFE_componentWillMount () {
    const { card, config } = this.props
    let _options = this.getOptions(card.eleType, card.datatype, card.link)
@@ -74,6 +75,8 @@
          } else if (card.eleType === 'number') {
            item.options = item.oriOptions.filter(op => op.value !== 'YYYY-MM-DD')
          }
        } else if (item.key === 'url') {
          item.required = card.eleType !== 'qrcode'
        }
        if (item.key === 'linkurl') {
          item.type = card.link === 'dynamic' ? 'select' : 'text'
@@ -155,6 +158,8 @@
          } else if (value === 'number') {
            item.options = item.oriOptions.filter(op => op.value !== 'YYYY-MM-DD')
          }
        } else if (item.key === 'url') {
          item.required = value !== 'qrcode'
        }
        return item
@@ -356,22 +361,11 @@
          </Col>
        )
      } else if (item.type === 'file') {
        let filelist = []
        if (item.initVal) {
          filelist = [{
            uid: `1`,
            name: item.initVal.slice(item.initVal.lastIndexOf('/') + 1),
            status: 'done',
            url: item.initVal,
            origin: true
          }]
        }
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: filelist,
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
@@ -379,7 +373,7 @@
                  }
                ]
              })(
                <FileUpload maxFile={item.maxfile} fileType={'text'} />
                <SourceComponent type={this.state.eleType} />
              )}
            </Form.Item>
          </Col>
@@ -397,28 +391,14 @@
          values.uuid = this.props.card.uuid
          values.marks = this.props.card.marks || null
          if (values.url) {
            if (values.url.length > 0) {
              if (values.url[0].origin && values.url[0].url) {
                values.url = values.url[0].url
              } else if (!values.url[0].origin && values.url[0].status === 'done' && values.url[0].response) {
                values.url = values.url[0].response
              } else {
                values.url = ''
              }
            } else {
              values.url = ''
            }
          }
          if (values.eleType === 'picture' && values.datatype === 'static' && !values.url) {
            notification.warning({
              top: 92,
              message: '尚未添加图片或图片上传失败,请重新添加!',
              duration: 5
            })
            return
          }
          // if (values.eleType === 'picture' && values.datatype === 'static' && !values.url) {
          //   notification.warning({
          //     top: 92,
          //     message: '尚未添加图片或图片上传失败,请重新添加!',
          //     duration: 5
          //   })
          //   return
          // }
          resolve(values)
        } else {
src/menu/components/card/cardcellcomponent/elementform/index.scss
@@ -21,4 +21,7 @@
      }
    }
  }
  .ant-form-explain, .ant-form-extra {
    font-size: 13px;
  }
}
src/menu/components/card/cardcellcomponent/formconfig.jsx
@@ -109,7 +109,7 @@
      label: '图片/文件',
      initVal: card.url || '',
      maxfile: 1,
      required: false
      required: true
    },
    {
      type: 'radio',
@@ -124,6 +124,17 @@
    },
    {
      type: 'radio',
      key: 'loop',
      label: '循环播放',
      initVal: card.loop || 'false',
      required: false,
      options: [
        { value: 'true', text: '是' },
        { value: 'false', text: '否' }
      ]
    },
    {
      type: 'radio',
      key: 'link',
      label: '链接',
      initVal: card.link || '',
src/menu/components/card/cardcellcomponent/index.jsx
@@ -292,7 +292,6 @@
    let ableField = usefulFields.join(', ')
    let functip = <div>
      <p style={{marginBottom: '5px'}}>{this.state.dict['model.tooltip.func.innerface'].replace('@ableField', ableField)}</p>
      <p>{this.state.dict['model.tooltip.func.outface']}</p>
    </div>
    let menulist = sessionStorage.getItem('fstMenuList')
src/menu/components/code/sandbox/codecontent/index.jsx
New file
@@ -0,0 +1,88 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Empty, message } from 'antd'
import './index.scss'
class BraftContent extends Component {
  static propTpyes = {
    name: PropTypes.string,
    html: PropTypes.any,
    css: PropTypes.any,
    js: PropTypes.any,
  }
  state = {
    csselement: null
  }
  UNSAFE_componentWillMount () {
    const { css } = this.props
    if (css) {
      // let style = css.replace(/^[^}{]*{|}[^}{]*{/ig, (word) => {
      //   return word.replace(/}\n*/ig, `}\n#${mark}`).replace(/,/ig, `,#${mark} `)
      // })
      // style = `\n/* 自定义 */\n#${mark} ${style}\n`
      let ele = document.createElement('style')
      ele.innerHTML = css
      document.getElementsByTagName('head')[0].appendChild(ele)
      // document.getElementsByTagName('head')[0].prepend(ele)
      this.setState({csselement: ele})
    }
  }
  componentDidMount () {
    const { js, name } = this.props
    if (js) {
      try {
        // eslint-disable-next-line no-eval
        eval(js)
      } catch {
        message.warning(name + 'JS 执行失败!')
      }
    }
  }
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (this.props.css !== nextProps.css) {
      const { csselement } = this.state
      if (csselement && csselement.remove) {
        csselement.remove()
      }
      if (nextProps.css) {
        let ele = document.createElement('style')
        ele.innerHTML = nextProps.css
        document.getElementsByTagName('head')[0].appendChild(ele)
        this.setState({csselement: ele})
      }
    }
    if (this.props.html !== nextProps.html || this.props.js !== nextProps.js) {
      if (nextProps.js) {
        try {
          // eslint-disable-next-line no-eval
          eval(nextProps.js)
        } catch {
          message.warning(nextProps.name + 'JS 执行失败!')
        }
      }
    }
  }
  render() {
    const { html } = this.props
    if (!html) return <Empty style={{padding: '10px 0px'}} description={null}/>
    return (
      <div dangerouslySetInnerHTML={{ __html: html }}></div>
    )
  }
}
export default BraftContent
src/menu/components/code/sandbox/codecontent/index.scss
New file
@@ -0,0 +1,26 @@
.braft-content {
  .media-wrap {
    max-width: 100%;
  }
  img {
    max-width: 100%;
  }
  video {
    max-width: 100%;
    width: 100%;
  }
  table {
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0;
    margin: 10px 0px;
    tr:first-child {
      background-color: #f0f0f0;
    }
    td, th {
      padding: 5px 14px;
      font-size: 16px;
      border: 1px solid #ddd;
    }
  }
}
src/menu/components/code/sandbox/editorcode/index.jsx
New file
@@ -0,0 +1,110 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Modal, Tabs, message } from 'antd'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const CodeMirror = asyncComponent(() => import('@/templates/zshare/codemirror'))
const { TabPane } = Tabs
class DataSource extends Component {
  static propTpyes = {
    config: PropTypes.any,
    updateConfig: PropTypes.func
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    visible: false,
    html: '',
    css: '',
    js: ''
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  trigger = () => {
    const { config } = this.props
    this.setState({
      visible: true,
      html: config.html || '',
      css: config.css || '',
      js: config.js || '',
    })
  }
  verifySubmit = () => {
    const { config } = this.props
    const { html, css, js } = this.state
    this.setState({
      visible: false
    })
    this.props.updateConfig({...config, html, css, js})
  }
  onHtmlChange = (val) => {
    this.setState({
      html: val
    })
  }
  onCssChange = (val) => {
    this.setState({
      css: val
    })
  }
  onJsChange = (val) => {
    if (/document\.getElementsByTagName/g.test(val)) {
      message.warning('为防止代码冲突,js中不可使用document.getElementsByTagName方法!')
      return
    }
    this.setState({
      js: val
    })
  }
  render () {
    const { visible, dict, html, css, js } = this.state
    return (
      <div style={{display: 'inline-block'}}>
        <Icon title="代码编辑" style={{color: 'purple'}} type="form" onClick={() => this.trigger()} />
        <Modal
          wrapClassName="popview-modal code-sand-box-code-editor"
          title="内容编辑"
          visible={visible}
          width={950}
          maskClosable={false}
          okText={dict['model.submit']}
          onOk={this.verifySubmit}
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <Tabs>
            <TabPane tab="HTML" key="HTML">
              <CodeMirror mode="text/xml" theme="cobalt" value={html} onChange={this.onHtmlChange} />
            </TabPane>
            <TabPane tab="CSS" key="CSS">
              <CodeMirror mode="text/css" theme="cobalt" value={css} onChange={this.onCssChange} />
            </TabPane>
            <TabPane tab="JS" key="JS">
              <CodeMirror mode="text/javascript" theme="cobalt" value={js} onChange={this.onJsChange} />
            </TabPane>
          </Tabs>
        </Modal>
      </div>
    )
  }
}
export default DataSource
src/menu/components/code/sandbox/editorcode/index.scss
New file
@@ -0,0 +1,20 @@
.code-sand-box-code-editor.popview-modal {
  .ant-modal-body {
    padding-top: 0px;
    .ant-tabs-bar {
      margin: 0;
    }
    .ant-tabs-bar {
      border: 0;
    }
    .code-mirror-wrap .code-mirror-area {
      border-radius: 0;
      .CodeMirror {
        border-radius: 0;
        height: 400px;
      }
    }
  }
}
src/menu/components/code/sandbox/index.jsx
New file
@@ -0,0 +1,188 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { is, fromJS } from 'immutable'
import { Icon, Popover } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import asyncIconComponent from '@/utils/asyncIconComponent'
import MKEmitter from '@/utils/events.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
const WrapComponent = asyncIconComponent(() => import('./wrapsetting'))
const EditorCode = asyncIconComponent(() => import('./editorcode'))
const CodeContent = asyncComponent(() => import('./codecontent'))
class CodeSandBox extends Component {
  static propTpyes = {
    card: PropTypes.object,
    deletecomponent: PropTypes.func,
    updateConfig: PropTypes.func,
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    card: null,
    back: false
  }
  UNSAFE_componentWillMount () {
    const { card } = this.props
    if (card.isNew) {
      let _card = {
        uuid: card.uuid,
        type: card.type,
        floor: card.floor,
        tabId: card.tabId || '',
        parentId: card.parentId || '',
        dataName: card.dataName || '',
        format: 'object',   // 组件属性 - 数据格式
        pageable: false,    // 组件属性 - 是否可分页
        switchable: false,  // 组件属性 - 数据是否可切换
        width: card.width || 24,
        name: card.name,
        subtype: card.subtype,
        setting: { interType: 'system' },
        wrap: { name: card.name, width: card.width || 24, encryption: 'true' },
        style: { marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px' },
        columns: [],
        scripts: [],
        html: '',
        css: '',
        js: '',
      }
      if (card.config) {
        let config = fromJS(card.config).toJS()
        _card.wrap = config.wrap
        _card.wrap.name = card.name
        _card.style = config.style
        _card.html = config.html
        _card.css = config.css
        _card.js = config.js
      }
      this.setState({
        card: _card
      })
      this.props.updateConfig(_card)
    } else {
      this.setState({
        card: fromJS(card).toJS()
      })
    }
  }
  componentDidMount () {
    MKEmitter.addListener('submitStyle', this.getStyle)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState)) || (!this.props.menu && nextProps.menu)
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('submitStyle', this.getStyle)
  }
  /**
   * @description 卡片行外层信息更新(数据源,样式等)
   */
  updateComponent = (component) => {
    this.setState({
      card: component
    })
    component.width = component.wrap.width
    component.name = component.wrap.name
    this.props.updateConfig(component)
  }
  changeStyle = () => {
    const { card } = this.state
    MKEmitter.emit('changeStyle', [card.uuid], ['background', 'border', 'padding', 'margin'], card.style)
  }
  getStyle = (comIds, style) => {
    const { card } = this.state
    if (comIds[0] !== card.uuid || comIds.length !== 1) return
    let _card = {...card, style}
    this.setState({
      card: _card
    })
    this.props.updateConfig(_card)
  }
  /**
   * @description 更新搜索条件配置信息
   */
  updateconfig = (config) => {
    this.setState({
      card: config
    })
    this.props.updateConfig(config)
  }
  clickComponent = (e) => {
    if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
      e.stopPropagation()
      MKEmitter.emit('clickComponent', this.state.card)
    }
  }
  render() {
    const { card } = this.state
    return (
      <div className="menu-editor-sand-box" style={{...card.style}} onClick={this.clickComponent} id={card.uuid}>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <WrapComponent config={card} updateConfig={this.updateComponent} />
            <CopyComponent type="normaltable" card={card}/>
            <Icon className="style" title="调整样式" onClick={this.changeStyle} type="font-colors" />
            <UserComponent config={card}/>
            <Icon className="close" title="删除组件" type="delete" onClick={() => this.props.deletecomponent(card.uuid)} />
            <EditorCode config={card} updateConfig={this.updateComponent}/>
            {card.wrap.datatype !== 'static' ? <SettingComponent config={card} updateConfig={this.updateComponent} /> : null}
            {card.wrap.datatype === 'static' ? <Icon style={{color: '#eeeeee', cursor: 'not-allowed'}} type="setting"/> : null}
          </div>
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        <CodeContent name={card.name} html={card.html} css={card.css} js={card.js}/>
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  return {
    menu: state.customMenu
  }
}
const mapDispatchToProps = () => {
  return {}
}
export default connect(mapStateToProps, mapDispatchToProps)(CodeSandBox)
src/menu/components/code/sandbox/index.scss
New file
@@ -0,0 +1,36 @@
.menu-editor-sand-box {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 30px;
  .anticon-tool {
    position: absolute;
    z-index: 2;
    font-size: 16px;
    right: 1px;
    top: 1px;
    cursor: pointer;
    padding: 5px;
    background: rgba(255, 255, 255, 0.55);
  }
  .empty-content {
    text-align: center;
    font-size: 30px;
    margin: 0;
    line-height: 90px;
    color: #bcbcbc;
  }
}
.menu-editor-sand-box::after {
  display: block;
  content: ' ';
  clear: both;
}
.menu-editor-sand-box:hover {
  z-index: 1;
  box-shadow: 0px 0px 4px #1890ff;
}
src/menu/components/code/sandbox/wrapsetting/index.jsx
New file
@@ -0,0 +1,83 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Modal } from 'antd'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import SettingForm from './settingform'
import './index.scss'
class DataSource extends Component {
  static propTpyes = {
    config: PropTypes.any,
    updateConfig: PropTypes.func
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    visible: false,
    wrap: null
  }
  UNSAFE_componentWillMount () {
    const { config } = this.props
    this.setState({wrap: fromJS(config.wrap).toJS()})
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  editDataSource = () => {
    this.setState({
      visible: true
    })
  }
  verifySubmit = () => {
    const { config } = this.props
    this.verifyRef.handleConfirm().then(res => {
      this.setState({
        wrap: res,
        visible: false
      })
      this.props.updateConfig({...config, wrap: res})
    })
  }
  render () {
    const { config } = this.props
    const { visible, dict, wrap } = this.state
    return (
      <div className="model-menu-setting-wrap">
        <Icon type="edit" onClick={() => this.editDataSource()} />
        <Modal
          wrapClassName="popview-modal"
          title="富文本设置"
          visible={visible}
          width={700}
          maskClosable={false}
          okText={dict['model.submit']}
          onOk={this.verifySubmit}
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <SettingForm
            dict={dict}
            wrap={wrap}
            config={config}
            inputSubmit={this.verifySubmit}
            wrappedComponentRef={(inst) => this.verifyRef = inst}
          />
        </Modal>
      </div>
    )
  }
}
export default DataSource
src/menu/components/code/sandbox/wrapsetting/index.scss
New file
@@ -0,0 +1,7 @@
.model-menu-setting-wrap {
  display: inline-block;
  >.anticon-edit {
    color: #1890ff;
  }
}
src/menu/components/code/sandbox/wrapsetting/settingform/index.jsx
New file
@@ -0,0 +1,152 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, Tooltip, Icon, InputNumber, Select } from 'antd'
import './index.scss'
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,      // 字典项
    config: PropTypes.object,    // 卡片行信息
    wrap: PropTypes.object,      // 数据源配置
    inputSubmit: PropTypes.func  // 回车事件
  }
  state = {
    roleList: [],
  }
  UNSAFE_componentWillMount () {
    let roleList = sessionStorage.getItem('sysRoles')
    if (roleList) {
      try {
        roleList = JSON.parse(roleList)
      } catch {
        roleList = []
      }
    } else {
      roleList = []
    }
    this.setState({roleList})
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  handleSubmit = (e) => {
    e.preventDefault()
    if (this.props.inputSubmit) {
      this.props.inputSubmit()
    }
  }
  render() {
    const { wrap } = this.props
    const { getFieldDecorator } = this.props.form
    const { roleList } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-menu-setting-form">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="用于组件间的区分。">
                  <Icon type="question-circle" />
                  组件名称
                </Tooltip>
              }>
                {getFieldDecorator('name', {
                  initialValue: wrap.name,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '组件名称!'
                    }
                  ]
                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="栅格布局,每行等分为24列。">
                  <Icon type="question-circle" />
                  宽度
                </Tooltip>
              }>
                {getFieldDecorator('width', {
                  initialValue: wrap.width || 24,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '宽度!'
                    }
                  ]
                })(<InputNumber min={1} max={24} precision={0} onPressEnter={this.handleSubmit} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="选择静态值,无需配置数据源。">
                  <Icon type="question-circle" />
                  数据来源
                </Tooltip>
              }>
                {getFieldDecorator('datatype', {
                  initialValue: wrap.datatype || 'dynamic'
                })(
                  <Radio.Group>
                    <Radio value="dynamic">动态</Radio>
                    <Radio value="static">静态</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="黑名单">
                {getFieldDecorator('blacklist', {
                  initialValue: wrap.blacklist || []
                })(
                  <Select
                    showSearch
                    mode="multiple"
                    filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  >
                    {roleList.map(option =>
                      <Select.Option key={option.uuid} value={option.value}>{option.text}</Select.Option>
                    )}
                  </Select>
                )}
              </Form.Item>
            </Col>
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/menu/components/code/sandbox/wrapsetting/settingform/index.scss
New file
@@ -0,0 +1,15 @@
.model-menu-setting-form {
  position: relative;
  .anticon-question-circle {
    color: #c49f47;
    margin-right: 3px;
  }
  .ant-input-number {
    width: 100%;
  }
  .color-sketch-block {
    position: relative;
    top: 7px;
  }
}
src/menu/components/editor/braft-editor/editorcontent/index.jsx
New file
@@ -0,0 +1,82 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Modal } from 'antd'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const Editor = asyncComponent(() => import('@/components/editor'))
class EditorContentComponent extends Component {
  static propTpyes = {
    config: PropTypes.any,
    updateConfig: PropTypes.func
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    visible: false,
    html: null
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  trigger = () => {
    const { config } = this.props
    this.setState({
      visible: true,
      html: config.html || null
    })
  }
  verifySubmit = () => {
    const { config } = this.props
    const { html } = this.state
    this.setState({
      visible: false
    })
    this.props.updateConfig({...config, html})
  }
  onChange = (val) => {
    this.setState({
      html: val
    })
  }
  render () {
    const { config } = this.props
    const { visible, dict, html } = this.state
    if (!config) return null
    return (
      <div className="model-menu-edit-content-wrap">
        {config.wrap.datatype === 'static' ? <Icon title="内容编辑" type="form" onClick={() => this.trigger()} /> : null}
        {config.wrap.datatype !== 'static' ? <Icon title="内容编辑" style={{color: '#eeeeee', cursor: 'not-allowed'}} type="form"/> : null}
        <Modal
          wrapClassName="popview-modal model-menu-edit-content-form"
          title="内容编辑"
          visible={visible}
          width={950}
          maskClosable={false}
          okText={dict['model.submit']}
          onOk={this.verifySubmit}
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <Editor defaultValue={html} onChange={this.onChange} />
        </Modal>
      </div>
    )
  }
}
export default EditorContentComponent
src/menu/components/editor/braft-editor/editorcontent/index.scss
New file
@@ -0,0 +1,14 @@
.model-menu-edit-content-wrap {
  display: inline-block;
  >.anticon-form {
    color: purple;
  }
}
.model-menu-edit-content-form {
  .normal-braft-editor {
    border: 1px solid #d9d9d9;
    border-radius: 4px;
    overflow-x: hidden;
  }
}
src/menu/components/editor/braft-editor/index.jsx
New file
@@ -0,0 +1,196 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { is, fromJS } from 'immutable'
import { Icon, Popover } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import asyncIconComponent from '@/utils/asyncIconComponent'
import MKEmitter from '@/utils/events.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
const WrapComponent = asyncIconComponent(() => import('./wrapsetting'))
const EditorContent = asyncIconComponent(() => import('./editorcontent'))
const BraftContent = asyncComponent(() => import('@/tabviews/custom/components/share/braftContent'))
class BraftEditorComponent extends Component {
  static propTpyes = {
    card: PropTypes.object,
    deletecomponent: PropTypes.func,
    updateConfig: PropTypes.func,
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    card: null,
    back: false
  }
  UNSAFE_componentWillMount () {
    const { card } = this.props
    if (card.isNew) {
      let _card = {
        uuid: card.uuid,
        type: card.type,
        floor: card.floor,
        tabId: card.tabId || '',
        parentId: card.parentId || '',
        dataName: card.dataName || '',
        format: 'object',   // 组件属性 - 数据格式
        pageable: false,    // 组件属性 - 是否可分页
        switchable: false,  // 组件属性 - 数据是否可切换
        width: card.width || 24,
        name: card.name,
        subtype: card.subtype,
        setting: { interType: 'system' },
        wrap: { name: card.name, width: card.width || 24, encryption: 'true' },
        style: { marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px' },
        headerStyle: { fontSize: '16px', borderBottomWidth: '1px', borderBottomColor: '#e8e8e8' },
        columns: [],
        scripts: [],
        html: ''
      }
      if (card.config) {
        let config = fromJS(card.config).toJS()
        _card.wrap = config.wrap
        _card.wrap.name = card.name
        _card.style = config.style
        _card.headerStyle = config.headerStyle
        _card.html = config.html
      }
      this.setState({
        card: _card
      })
      this.props.updateConfig(_card)
    } else {
      this.setState({
        card: fromJS(card).toJS()
      })
    }
  }
  componentDidMount () {
    MKEmitter.addListener('submitStyle', this.getStyle)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState)) || (!this.props.menu && nextProps.menu)
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('submitStyle', this.getStyle)
  }
  /**
   * @description 卡片行外层信息更新(数据源,样式等)
   */
  updateComponent = (component) => {
    this.setState({
      card: component
    })
    component.width = component.wrap.width
    component.name = component.wrap.name
    this.props.updateConfig(component)
  }
  changeStyle = () => {
    const { card } = this.state
    MKEmitter.emit('changeStyle', [card.uuid], ['background', 'border', 'padding', 'margin'], card.style)
  }
  getStyle = (comIds, style) => {
    const { card } = this.state
    if (comIds[0] !== card.uuid) return
    let _card = {}
    if (comIds.length === 1) {
      _card = {...card, style}
    } else {
      return
    }
    this.setState({
      card: _card
    })
    this.props.updateConfig(_card)
  }
  /**
   * @description 更新搜索条件配置信息
   */
  updateconfig = (config) => {
    this.setState({
      card: config
    })
    this.props.updateConfig(config)
  }
  clickComponent = (e) => {
    if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
      e.stopPropagation()
      MKEmitter.emit('clickComponent', this.state.card)
    }
  }
  render() {
    const { card } = this.state
    return (
      <div className="menu-normal-editor-box" style={{...card.style}} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader defaultshow="hidden" hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <WrapComponent config={card} updateConfig={this.updateComponent} />
            <CopyComponent type="normaltable" card={card}/>
            <Icon className="style" title="调整样式" onClick={this.changeStyle} type="font-colors" />
            <UserComponent config={card}/>
            <Icon className="close" title="删除组件" type="delete" onClick={() => this.props.deletecomponent(card.uuid)} />
            <EditorContent config={card} updateConfig={this.updateComponent}/>
            {card.wrap.datatype !== 'static' ? <SettingComponent config={card} updateConfig={this.updateComponent} /> : null}
            {card.wrap.datatype === 'static' ? <Icon style={{color: '#eeeeee', cursor: 'not-allowed'}} type="setting"/> : null}
          </div>
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        <BraftContent
          value={card.wrap.datatype !== 'static' ? '<p class="empty-content">富文本</p>' : card.html}
          encryption="false"
        />
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  return {
    menu: state.customMenu
  }
}
const mapDispatchToProps = () => {
  return {}
}
export default connect(mapStateToProps, mapDispatchToProps)(BraftEditorComponent)
src/menu/components/editor/braft-editor/index.scss
New file
@@ -0,0 +1,36 @@
.menu-normal-editor-box {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 100px;
  .anticon-tool {
    position: absolute;
    z-index: 2;
    font-size: 16px;
    right: 1px;
    top: 1px;
    cursor: pointer;
    padding: 5px;
    background: rgba(255, 255, 255, 0.55);
  }
  .empty-content {
    text-align: center;
    font-size: 30px;
    margin: 0;
    line-height: 90px;
    color: #bcbcbc;
  }
}
.menu-normal-editor-box::after {
  display: block;
  content: ' ';
  clear: both;
}
.menu-normal-editor-box:hover {
  z-index: 1;
  box-shadow: 0px 0px 4px #1890ff;
}
src/menu/components/editor/braft-editor/wrapsetting/index.jsx
New file
@@ -0,0 +1,83 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Modal } from 'antd'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import SettingForm from './settingform'
import './index.scss'
class DataSource extends Component {
  static propTpyes = {
    config: PropTypes.any,
    updateConfig: PropTypes.func
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    visible: false,
    wrap: null
  }
  UNSAFE_componentWillMount () {
    const { config } = this.props
    this.setState({wrap: fromJS(config.wrap).toJS()})
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  editDataSource = () => {
    this.setState({
      visible: true
    })
  }
  verifySubmit = () => {
    const { config } = this.props
    this.verifyRef.handleConfirm().then(res => {
      this.setState({
        wrap: res,
        visible: false
      })
      this.props.updateConfig({...config, wrap: res})
    })
  }
  render () {
    const { config } = this.props
    const { visible, dict, wrap } = this.state
    return (
      <div className="model-menu-setting-wrap">
        <Icon type="edit" onClick={() => this.editDataSource()} />
        <Modal
          wrapClassName="popview-modal"
          title="富文本设置"
          visible={visible}
          width={700}
          maskClosable={false}
          okText={dict['model.submit']}
          onOk={this.verifySubmit}
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <SettingForm
            dict={dict}
            wrap={wrap}
            config={config}
            inputSubmit={this.verifySubmit}
            wrappedComponentRef={(inst) => this.verifyRef = inst}
          />
        </Modal>
      </div>
    )
  }
}
export default DataSource
src/menu/components/editor/braft-editor/wrapsetting/index.scss
New file
@@ -0,0 +1,7 @@
.model-menu-setting-wrap {
  display: inline-block;
  >.anticon-edit {
    color: #1890ff;
  }
}
src/menu/components/editor/braft-editor/wrapsetting/settingform/index.jsx
New file
@@ -0,0 +1,199 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, Tooltip, Icon, InputNumber, Select } from 'antd'
import './index.scss'
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,      // 字典项
    config: PropTypes.object,    // 卡片行信息
    wrap: PropTypes.object,      // 数据源配置
    inputSubmit: PropTypes.func  // 回车事件
  }
  state = {
    roleList: [],
    datatype: this.props.wrap.datatype || 'dynamic'
  }
  UNSAFE_componentWillMount () {
    let roleList = sessionStorage.getItem('sysRoles')
    if (roleList) {
      try {
        roleList = JSON.parse(roleList)
      } catch {
        roleList = []
      }
    } else {
      roleList = []
    }
    this.setState({roleList})
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  handleSubmit = (e) => {
    e.preventDefault()
    if (this.props.inputSubmit) {
      this.props.inputSubmit()
    }
  }
  changeDataType = (e) => {
    this.setState({datatype: e.target.value})
  }
  render() {
    const { wrap, config } = this.props
    const { getFieldDecorator } = this.props.form
    const { roleList, datatype } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-menu-setting-form">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={12}>
              <Form.Item label="标题">
                {getFieldDecorator('title', {
                  initialValue: wrap.title || ''
                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="用于组件间的区分。">
                  <Icon type="question-circle" />
                  组件名称
                </Tooltip>
              }>
                {getFieldDecorator('name', {
                  initialValue: wrap.name,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '组件名称!'
                    }
                  ]
                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="栅格布局,每行等分为24列。">
                  <Icon type="question-circle" />
                  宽度
                </Tooltip>
              }>
                {getFieldDecorator('width', {
                  initialValue: wrap.width || 24,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '宽度!'
                    }
                  ]
                })(<InputNumber min={1} max={24} precision={0} onPressEnter={this.handleSubmit} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="选择静态值,无需配置数据源。">
                  <Icon type="question-circle" />
                  数据来源
                </Tooltip>
              }>
                {getFieldDecorator('datatype', {
                  initialValue: datatype
                })(
                  <Radio.Group onChange={this.changeDataType}>
                    <Radio value="dynamic">动态</Radio>
                    <Radio value="static">静态</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            {datatype === 'dynamic' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="选择动态值时,需设置文本字段才可生效。">
                  <Icon type="question-circle" />
                  文本字段
                </Tooltip>
              }>
                {getFieldDecorator('field', {
                  initialValue: wrap.field || ''
                })(
                  <Select>
                    {config.columns.map(option =>
                      <Select.Option key={option.uuid} value={option.field}>{option.label}</Select.Option>
                    )}
                  </Select>
                )}
              </Form.Item>
            </Col> : null}
            {datatype === 'dynamic' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="从数据源获取的数据是否需要解码。">
                  <Icon type="question-circle" />
                  数据解码
                </Tooltip>
              }>
                {getFieldDecorator('encryption', {
                  initialValue: wrap.encryption || 'true'
                })(
                  <Radio.Group>
                    <Radio value="true">是</Radio>
                    <Radio value="false">否</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col> : null}
            <Col span={12}>
              <Form.Item label="黑名单">
                {getFieldDecorator('blacklist', {
                  initialValue: wrap.blacklist || []
                })(
                  <Select
                    showSearch
                    mode="multiple"
                    filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  >
                    {roleList.map(option =>
                      <Select.Option key={option.uuid} value={option.value}>{option.text}</Select.Option>
                    )}
                  </Select>
                )}
              </Form.Item>
            </Col>
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/menu/components/editor/braft-editor/wrapsetting/settingform/index.scss
New file
@@ -0,0 +1,15 @@
.model-menu-setting-form {
  position: relative;
  .anticon-question-circle {
    color: #c49f47;
    margin-right: 3px;
  }
  .ant-input-number {
    width: 100%;
  }
  .color-sketch-block {
    position: relative;
    top: 7px;
  }
}
src/menu/components/group/groupcomponents/card.jsx
@@ -10,6 +10,7 @@
const PropCard = asyncComponent(() => import('@/menu/components/card/prop-card'))
const TableCard = asyncComponent(() => import('@/menu/components/card/table-card'))
const NormalTable = asyncComponent(() => import('@/menu/components/table/normal-table'))
const BraftEditor = asyncComponent(() => import('@/menu/components/editor/braft-editor'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -57,6 +58,8 @@
      return (<TableCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'table' && card.subtype === 'normaltable') {
      return (<NormalTable card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'editor') {
      return (<BraftEditor card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
src/menu/components/group/groupcomponents/index.jsx
@@ -103,6 +103,7 @@
        line: '折线图',
        pie: '饼图',
        table: '表格',
        editor: '富文本',
        card: '卡片'
      }
      let i = 1
src/menu/components/share/actioncomponent/actionform/index.jsx
@@ -34,6 +34,7 @@
    openType: null,  // 打开方式
    interType: null, // 接口类型:内部、外部
    funcType: null,  // 功能类型
    procMode: null,  // 参数方式
    requireOptions: [{
      value: 'notRequired',
      text: this.props.dict['header.form.notRequired']
@@ -66,6 +67,19 @@
    }, {
      value: 'custom',
      text: this.props.dict['header.form.custom']
    }],
    interTypeOptions: [{
      value: 'system',
      text: this.props.dict['model.interface.system']
    }, {
      value: 'inner',
      text: this.props.dict['model.interface.inner']
    }, {
      value: 'outer',
      text: this.props.dict['model.interface.outer']
    }, {
      value: 'custom',
      text: '自定义'
    }]
  }
@@ -76,16 +90,23 @@
    let _opentype = card.OpenType                // 打开方式
    let _intertype = card.intertype || 'system'  // 接口类型
    let _funcType = card.funcType || 'print'     // 功能按钮默认类型
    let _procMode = card.procMode || 'system'     // 参数请求方式
    let _options = this.getOptions(_opentype, _intertype, _funcType, card.pageTemplate)
    let _options = this.getOptions(_opentype, _intertype, _funcType, card.pageTemplate, _procMode)
    this.setState({
      openType: _opentype,
      interType: _intertype,
      procMode: _procMode,
      funcType: _funcType,
      formlist: this.props.formlist.map(item => {
        if (item.key === 'class') {
          item.options = btnCustomClasses
        } else if (item.key === 'innerFunc' && _procMode === 'inner') {
          item.required = true
        } else if (item.key === 'intertype') {
          let iscustom = ['pop', 'prompt', 'exec'].includes(_opentype)
          item.options = this.state.interTypeOptions.filter(op => (iscustom || op.value !== 'custom'))
        } else if (item.key === 'icon') {
          item.options = btnIcons
        } else if (item.key === 'Ot') {
@@ -116,7 +137,7 @@
    })
  }
  getOptions = (_opentype, _intertype, _funcType, _pageTemplate) => {
  getOptions = (_opentype, _intertype, _funcType, _pageTemplate, _procMode) => {
    let _options = fromJS(actionTypeOptions[_opentype]).toJS() // 选项列表
    
    if (_opentype === 'innerpage') {         // 新页面,可选模板(自定义时,可填入外部链接)
@@ -125,13 +146,13 @@
      }
    } else if (_opentype === 'excelOut') {    // 导入导出
      if (_intertype === 'outer') {
        _options.push('innerFunc', 'sysInterface', 'interface', 'outerFunc')
        _options.push('innerFunc', 'sysInterface', 'interface', 'proInterface', 'outerFunc')
      } else if (_intertype === 'inner') {
        _options.push('innerFunc')
      }
    } else if (_opentype === 'excelIn') {    // 导入导出
      if (_intertype === 'outer') {
        _options.push('innerFunc', 'sysInterface', 'interface', 'outerFunc', 'callbackFunc')
        _options.push('innerFunc', 'sysInterface', 'interface', 'proInterface', 'outerFunc', 'callbackFunc')
      } else if (_intertype === 'inner') {
        _options.push('innerFunc')
      }
@@ -139,14 +160,21 @@
      if (_funcType === 'print') {
        _options.push('execMode', 'intertype', 'Ot', 'execSuccess', 'execError', 'resetPageIndex')
        if (_intertype === 'outer') {
          _options.push('innerFunc', 'sysInterface', 'interface', 'outerFunc', 'callbackFunc')
          _options.push('innerFunc', 'sysInterface', 'interface', 'proInterface', 'outerFunc', 'callbackFunc')
        } else if (_intertype === 'inner') {
          _options.push('innerFunc')
        }
      }
    } else if (_opentype !== 'popview' && _opentype !== 'tab') {
      if (_intertype === 'outer') {
        _options.push('innerFunc', 'sysInterface', 'interface', 'outerFunc', 'callbackFunc')
      if (_intertype === 'custom') {
        _options.push('procMode', 'interface', 'callbackType', 'cbTable', 'proInterface', 'method')
        if (_procMode === 'system') {
          _options.push('sql', 'sqlType')
        } else {
          _options.push('innerFunc')
        }
      } else if (_intertype === 'outer') {
        _options.push('innerFunc', 'sysInterface', 'interface', 'proInterface', 'outerFunc', 'callbackFunc')
      } else if (_intertype === 'inner') {
        _options.push('innerFunc')
      } else {
@@ -176,21 +204,27 @@
   * 2、显示位置切换,重置选择行
   * 3、切换标签类型,重置可选标签
   */
  openTypeChange = (key, value) => {
  optionChange = (key, value) => {
    const { card, type } = this.props
    const { openType, procMode } = this.state
    if (key === 'OpenType') {
      let _options = this.getOptions(value, this.state.interType, this.state.funcType, card.pageTemplate)
      let _options = this.getOptions(value, 'system', this.state.funcType, card.pageTemplate, 'system')
      let _fieldval = {}
      
      let _formlist = this.state.formlist.map(item => {
        item.hidden = !_options.includes(item.key)
        if (item.key === 'intertype') {
          let iscustom = ['pop', 'prompt', 'exec'].includes(value)
          item.options = this.state.interTypeOptions.filter(op => (iscustom || op.value !== 'custom'))
        }
        if (item.hidden) return item
        if (item.key === 'intertype') {
          _fieldval.intertype = this.state.interType
          _fieldval.intertype = 'system'
        } else if (item.key === 'Ot') {
          if (type === 'card') {
            item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl'].includes(op.value))
@@ -217,6 +251,8 @@
      this.setState({
        openType: value,
        intertype: 'system',
        procMode: 'system',
        formlist: _formlist
      }, () => {
        if (value === 'excelIn') {
@@ -230,7 +266,7 @@
        this.props.form.setFieldsValue(_fieldval)
      })
    } else if (key === 'funcType') {
      let _options = this.getOptions(this.state.openType, this.state.interType, value, card.pageTemplate)
      let _options = this.getOptions(this.state.openType, this.state.interType, value, card.pageTemplate, procMode)
      let _fieldval = {}
      this.setState({
@@ -294,7 +330,7 @@
      })
    } else if (key === 'pageTemplate') {
      let _fieldval = {}
      let _options = this.getOptions(this.state.openType, this.state.interType, this.state.funcType, value)
      let _options = this.getOptions(this.state.openType, this.state.interType, this.state.funcType, value, procMode)
      this.setState({
        openType: value,
@@ -317,16 +353,8 @@
      }, () => {
        this.props.form.setFieldsValue(_fieldval)
      })
    }
  }
  onChange = (e, key) => {
    const { type } = this.props
    const { openType } = this.state
    let value = e.target.value
    if (key === 'intertype') {
      let _options = this.getOptions(openType, value, this.state.funcType)
    } else if (key === 'intertype') {
      let _options = this.getOptions(openType, value, this.state.funcType, '', procMode)
      this.setState({
        interType: value,
@@ -346,10 +374,20 @@
          }
          return item
        })
      }, () => {
        if (this.props.form.getFieldValue('sqlType') !== undefined) {
          this.props.form.setFieldsValue({sqlType: ''})
        }
      })
    } else if (key === 'procMode') {
      let _options = this.getOptions(openType, this.state.interType, this.state.funcType, '', value)
      this.setState({
        procMode: value,
        formlist: this.state.formlist.map(item => {
          item.hidden = !_options.includes(item.key)
          if (item.key === 'innerFunc') {
            item.required = true
          }
          return item
        })
      })
    } else if (key === 'sysInterface') {
      if (value === 'true') {
@@ -475,7 +513,7 @@
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children[2].toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={(value) => {this.openTypeChange(item.key, value)}}
                  onChange={(value) => {this.optionChange(item.key, value)}}
                  getPopupContainer={() => document.getElementById('winter')}
                >
                  {item.options.map((option, index) =>
@@ -501,7 +539,7 @@
                  }
                ]
              })(
                <Radio.Group onChange={(e) => {this.onChange(e, item.key)}} disabled={item.readonly}>
                <Radio.Group onChange={(e) => {this.optionChange(item.key, e.target.value)}} disabled={item.readonly}>
                  {
                    item.options.map(option => {
                      return (
@@ -519,8 +557,14 @@
          <Col span={24} key={index}>
            <Form.Item label={item.label} className="textarea">
              {getFieldDecorator(item.key, {
                initialValue: item.initVal
              })(<TextArea rows={4} />)}
                initialValue: item.initVal,
                rules: [
                  {
                    required: item.readonly ? false : !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<TextArea rows={2} readOnly={item.readonly}/>)}
            </Form.Item>
          </Col>
        )
@@ -612,7 +656,7 @@
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-action-form" id="winter">
      <Form {...formItemLayout} className="menu-action-list-form" id="winter">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
src/menu/components/share/actioncomponent/actionform/index.scss
@@ -1,4 +1,4 @@
.ant-advanced-search-form.commontable-action-form {
.menu-action-list-form {
  min-height: 190px;
  .superconfig {
    color: #1890ff;
@@ -10,6 +10,12 @@
    }
    .ant-col-sm-17 {
      width: 86%;
    }
  }
  .ant-radio-group {
    white-space: nowrap;
    .ant-radio-wrapper {
      margin-right: 4px;
    }
  }
  .ant-input-number {
@@ -30,4 +36,12 @@
      top: 4.5px;
    }
  }
  .ant-input:read-only {
    background: #fafafa;
    resize: none;
  }
  .ant-input:read-only:hover, .ant-input:read-only:focus {
    border-color: #d9d9d9;
    box-shadow: none;
  }
}
src/menu/components/share/actioncomponent/formconfig.jsx
@@ -66,7 +66,7 @@
      options: opentypes
    },
    {
      type: 'select',
      type: 'radio',
      key: 'funcType',
      label: Formdict['header.form.funcType'],
      initVal: card.funcType || 'print',
@@ -102,23 +102,6 @@
      label: Formdict['header.form.intertype'],
      initVal: card.intertype || 'system',
      required: true,
      options: [{
        value: 'system',
        text: '系统'
      }, {
        value: 'inner',
        text: Formdict['model.interface.inner']
      }, {
        value: 'outer',
        text: Formdict['model.interface.outer']
      }]
    },
    {
      type: 'select',
      key: 'sqlType',
      label: Formdict['header.form.action.type'],
      initVal: card.sqlType || '',
      required: true,
      options: []
    },
    {
@@ -128,6 +111,28 @@
      initVal: card.label,
      required: true,
      readonly: false
    },
    {
      type: 'radio',
      key: 'procMode',
      label: '参数处理',
      initVal: card.procMode || 'system',
      required: true,
      options: [{
        value: 'system',
        text: '系统函数'
      }, {
        value: 'inner',
        text: '内部函数'
      }]
    },
    {
      type: 'radio',
      key: 'sqlType',
      label: Formdict['header.form.action.type'],
      initVal: card.sqlType || '',
      required: true,
      options: []
    },
    {
      type: 'text',
@@ -194,12 +199,56 @@
      readonly: false
    },
    {
      type: 'text',
      type: 'textarea',
      key: 'interface',
      label: Formdict['header.form.interface'],
      label: '测试地址',
      initVal: card.sysInterface === 'true' ? (window.GLOB.mainSystemApi || '') : (card.interface || ''),
      required: true,
      readonly: card.sysInterface === 'true'
    },
    {
      type: 'textarea',
      key: 'proInterface',
      label: '正式地址',
      initVal: card.proInterface || '',
      tooltip: '正式系统所使用的接口地址。',
      required: false
    },
    {
      type: 'radio',
      key: 'method',
      label: '请求方式',
      initVal: card.method || 'post',
      required: true,
      options: [{
        value: 'get',
        text: 'GET'
      }, {
        value: 'post',
        text: 'POST'
      }]
    },
    {
      type: 'radio',
      key: 'callbackType',
      label: '回调方式',
      initVal: card.callbackType || 'script',
      tooltip: '使用默认方式执行时,需要配合计划任务。',
      required: true,
      options: [{
        value: 'default',
        text: '默认脚本'
      }, {
        value: 'script',
        text: '自定义脚本'
      }]
    },
    {
      type: 'text',
      key: 'cbTable',
      label: '回调表名',
      initVal: card.cbTable || '',
      required: true
    },
    {
      type: 'text',
@@ -308,7 +357,7 @@
      required: true
    },
    {
      type: 'select',
      type: 'radio',
      key: 'show',
      label: "显示为",
      initVal: card.show || 'icon',
src/menu/components/share/actioncomponent/index.jsx
@@ -159,7 +159,6 @@
    let ableField = usefulFields.join(', ')
    let functip = <div>
      <p style={{marginBottom: '5px'}}>{this.state.dict['model.tooltip.func.innerface'].replace('@ableField', ableField)}</p>
      <p>{this.state.dict['model.tooltip.func.outface']}</p>
    </div>
    let menulist = sessionStorage.getItem('fstMenuList')
@@ -466,7 +465,7 @@
        <Modal
          title={dict['model.action'] + '-' + (card && card.copyType === 'action' ? dict['model.copy'] : dict['model.edit'])}
          visible={visible}
          width={800}
          width={850}
          maskClosable={false}
          onCancel={this.editModalCancel}
          footer={[
src/menu/components/share/sourcecomponent/index.jsx
New file
@@ -0,0 +1,81 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Radio, Icon, Modal } from 'antd'
import InputForm from './inputform'
import './index.scss'
class CopyComponent extends Component {
  static propTpyes = {
    type: PropTypes.string,
    placement: PropTypes.any,
    onChange: PropTypes.func
  }
  state = {
    url: this.props.value,
    visible: ''
  }
  UNSAFE_componentWillMount () {
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  deleteUrl = () => {
    this.setState({url: ''})
    this.props.onChange('')
  }
  handleChange = (key) => {
    this.setState({visible: key})
  }
  popSubmit = () => {
    let url = ''
    if (this.inputWrap && this.inputWrap.state.url) {
      url = this.inputWrap.state.url
    }
    this.setState({visible: '', url})
    this.props.onChange(url)
  }
  render () {
    const { url, visible } = this.state
    const { type } = this.props
    let name = url ? url.slice(url.lastIndexOf('/') + 1) : ''
    return (
      <div className="mk-source-wrap">
        {!url ? <Radio.Group>
          <Radio.Button value="input" size="small" onClick={() => this.handleChange('input')}>输入</Radio.Button>
          <Radio.Button value="upload" size="small" onClick={() => this.handleChange('upload')}>上传</Radio.Button>
          <Radio.Button value="system" size="small" onClick={() => this.handleChange('system')}>系统</Radio.Button>
        </Radio.Group> : null}
        {url ? <div className="mk-source-item-info">
          <Icon type="paper-clip" />
          <a target="_blank" rel="noopener noreferrer" href={url}>{name}</a>
          <Icon title="删除文件" type="delete" onClick={this.deleteUrl}/>
        </div> : null}
        <Modal
          visible={!!visible}
          width={visible !== 'system' ? 600 : 1000}
          closable={false}
          maskClosable={false}
          onOk={this.popSubmit}
          onCancel={() => {this.setState({visible: ''})}}
          destroyOnClose
        >
          <InputForm type={type === 'picture' ? 'image' : type} keyword={visible} ref={dom => { this.inputWrap = dom }} />
        </Modal>
      </div>
    )
  }
}
export default CopyComponent
src/menu/components/share/sourcecomponent/index.scss
New file
@@ -0,0 +1,42 @@
.mk-source-wrap {
  .mk-source-item-info {
    position: relative;
    line-height: 1.3;
    top: 3px;
    .anticon-paper-clip {
      position: absolute;
      top: 3px;
      color: rgba(0,0,0,.45);
      font-size: 14px;
    }
    .anticon-delete {
      position: absolute;
      top: 3px;
      right: 0px;
      padding-right: 6px;
      color: rgba(0,0,0,.45);
      cursor: pointer;
      display: none;
    }
    a {
      display: inline-block;
      width: 100%;
      padding-left: 22px;
      padding-right: 14px;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
  }
  .mk-source-item-info:hover {
    background-color: #e6f7ff;
    .anticon-delete {
      display: inline-block;
    }
  }
  .ant-radio-button-wrapper {
    height: 28px;
    line-height: 26px;
  }
}
src/menu/components/share/sourcecomponent/inputform/index.jsx
New file
@@ -0,0 +1,212 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Input, Form, Row, Col, Pagination, Empty, Button, Modal, notification } from 'antd'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { TextArea } = Input
const { Search } = Input
const FileUpload = asyncComponent(() => import('@/tabviews/zshare/fileupload'))
const Video = asyncComponent(() => import('@/menu/picturecontroller/video'))
const Image = asyncComponent(() => import('@/components/Image'))
const EditForm = asyncComponent(() => import('@/menu/picturecontroller/editform'))
class PopSource extends Component {
  static propTpyes = {
    btnlog: PropTypes.array,
    keyword: PropTypes.string,
    handlelog: PropTypes.func
  }
  state = {
    url: '',
    originlist: [],
    list: [],
    pagelist: [],
    fileList: [],
    searchKey: '',
    pageSize: 12,
    pageIndex: 1,
    selectId: '',
    editvisible: false,
    card: null
  }
  UNSAFE_componentWillMount () {
    if (this.props.keyword === 'system') {
      this.init()
    }
  }
  componentDidMount() {
    try {
      let _form = document.getElementById('source-input')
      if (_form && _form.focus) {
        _form.focus()
      }
    } catch {}
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  init = () => {
    const { type } = this.props
    let originlist = []
    if (type === 'video') {
      let videos = sessionStorage.getItem('app_videos')
      try {
        originlist = JSON.parse(videos)
      } catch {
        originlist = []
      }
    } else {
      let pictures = sessionStorage.getItem('app_pictures')
      try {
        originlist = JSON.parse(pictures)
      } catch {
        originlist = []
      }
    }
    let list = originlist
    let pagelist = list.filter((item, index) => index < this.state.pageSize)
    this.setState({originlist, list, url: '', searchKey: '', pageIndex: 1, fileList: [], pagelist})
  }
  changeSearch = () => {
    const { originlist, pageSize, searchKey } = this.state
    let list = originlist.filter(item => item.remark.indexOf(searchKey) > -1)
    let pagelist = list.filter((item, index) => index < pageSize)
    this.setState({list, pagelist, pageIndex: 1})
  }
  changeValue = (e) => {
    this.setState({url: e.target.value})
  }
  changeSize = (page) => {
    const { list, pageSize } = this.state
    let pagelist = list.filter((item, index) => index < pageSize * page && index >= pageSize * (page - 1))
    this.setState({pageIndex: page, pagelist})
  }
  changeFile = (vals) => {
    this.setState({fileList: vals})
    if (vals && vals[0] && vals[0].status === 'done' && vals[0].response) {
      this.setState({url: vals[0].response})
    }
  }
  selectItem = (item) => {
    if (item.linkurl) {
      this.setState({url: item.linkurl, selectId: item.id})
    }
  }
  handleSource = (item) => {
    this.setState({
      editvisible: true,
      card: item
    })
  }
  save = () => {
    const { card } = this.state
    this.editFormRef.handleConfirm().then(res => {
      res = {...card, ...res}
      if (!res.id) {
        res.id = Utils.getuuid()
      }
      Api.getSystemConfig({
        func: 's_url_db_adduptdel',
        id: res.id,
        PageIndex: 0, // 0 代表全部
        PageSize: 0,  // 0 代表全部
        remark: res.remark || '',
        linkurl: res.linkurl,
        typecharone: card.typecharone,
        type: 'add'
      }).then(result => {
        if (result.status) {
          if (card.typecharone === 'image') {
            sessionStorage.setItem('app_pictures', JSON.stringify(result.data || []))
            this.init()
          } else {
            sessionStorage.setItem('app_videos', JSON.stringify(result.data || []))
            this.init()
          }
          this.setState({editvisible: false})
        } else {
          notification.warning({
            top: 92,
            message: result.message,
            duration: 5
          })
        }
      })
    })
  }
  render () {
    const { type, keyword } = this.props
    const { list, url, pagelist, fileList, searchKey, pageIndex, pageSize, selectId, editvisible, card } = this.state
    return (
      <div className="mk-source-pop-wrap">
        {keyword === 'input' ? <Form.Item label="地址" labelCol={{xs: { span: 24 }, sm: { span: 4 }}} wrapperCol={{xs: { span: 24 }, sm: { span: 20 }}}>
          <TextArea id="source-input" value={url} rows={4} onChange={this.changeValue}/>
        </Form.Item> : null}
        {keyword === 'upload' ? <Form.Item label="上传" labelCol={{xs: { span: 24 }, sm: { span: 4 }}} wrapperCol={{xs: { span: 24 }, sm: { span: 20 }}}>
          <FileUpload value={fileList} onChange={this.changeFile} accept={type === 'video' ? '.mp4,.webm,.ogg' : '.jpg,.png,.gif,.pjp,.pjpeg,.jpeg,.jfif,.webp'} maxFile={1} fileType={type === 'video' ? 'text' : 'picture'} />
        </Form.Item> : null}
        {keyword === 'system' ?
          <Search value={searchKey} placeholder="" onChange={(e) => this.setState({searchKey: e.target.value})} onSearch={this.changeSearch} enterButton/> : null}
        {keyword === 'system' ? <Button className="picture-plus mk-green" icon="plus" onClick={() => this.handleSource({typecharone: type})}>
          添加
        </Button> : null}
        {keyword === 'system' && list.length ?
          <Row gutter={16} style={{minHeight: '250px'}}>
            {pagelist.map(item => (
              <Col span={4} key={item.id}>
                <div className={'image-video-box' + (selectId === item.id ? ' active' : '')} onClick={() => this.selectItem(item)}>
                  <div className="image-video-box-body">
                    {type !== 'video' ? <Image url={item.linkurl} /> : null}
                    {type === 'video' ? <Video value={item.linkurl} /> : null}
                  </div>
                </div>
              </Col>
            ))}
          </Row> : null}
        {keyword === 'system' && list.length === 0 ? <Empty description={null}/> : null}
        {keyword === 'system' && list.length > pageSize ? <Pagination size="small" current={pageIndex} pageSize={pageSize} onChange={this.changeSize} total={list.length} /> : null}
        <Modal
          title={'新建'}
          wrapClassName="picture-edit-model"
          visible={editvisible}
          width={600}
          maskClosable={false}
          onOk={this.save}
          onCancel={() => {this.setState({editvisible: false})}}
          destroyOnClose
        >
          <EditForm card={card} wrappedComponentRef={(inst) => this.editFormRef = inst} inputSubmit={this.save}/>
        </Modal>
      </div>
    )
  }
}
export default PopSource
src/menu/components/share/sourcecomponent/inputform/index.scss
New file
@@ -0,0 +1,60 @@
.mk-source-pop-wrap {
  padding: 20px 10px 15px;
  min-height: 150px;
  .image-video-box {
    position: relative;
    padding-top: 75%;
    cursor: pointer;
    margin-bottom: 16px;
    transition: all 0.3s;
    .image-video-box-body {
      position: absolute;
      top: 0px;
      left: 0px;
      right: 0px;
      bottom: 0px;
      .video-react-big-play-button {
        display: none;
      }
      .video-react-control-bar {
        display: none;
      }
    }
    .image-video-box-body::after {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
    }
  }
  .image-video-box.active {
    box-shadow: 0px 0px 8px #1890ff;
  }
  .ant-input-search {
    width: 300px;
    position: relative;
    top: -15px;
  }
  .ant-empty {
    height: 250px;
    padding-top: 50px;
  }
  .ant-pagination {
    text-align: right;
  }
  .picture-plus {
    float: right;
    position: relative;
    top: -15px;
  }
}
.picture-edit-model {
  .ant-modal {
    .ant-modal-body {
      min-height: 200px;
    }
  }
}
src/menu/components/share/usercomponent/index.jsx
@@ -6,6 +6,7 @@
import Api from '@/api'
import Utils from '@/utils/utils.js'
import options from '@/store/options.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import UserForm from './settingform'
@@ -53,6 +54,9 @@
    _config.search = config.search || []
    _config.cols = config.cols || []
    _config.plot = config.plot || {}
    _config.html = config.html || ''
    _config.css = config.css || ''
    _config.js = config.js || ''
    _config.width = _config.wrap.width || _config.plot.width || config.width || 24
@@ -144,8 +148,17 @@
      setTimeout(() => {
        let template = this.getUserComponent()
        html2canvas(document.getElementById(config.uuid)).then(canvas => {
          let img = canvas.toDataURL('image/png') // 获取生成的图片
          Api.fileuploadbase64(img, 'cloud').then(result => {
          let param = {
            Base64Img: canvas.toDataURL('image/png') // 获取生成的图片
          }
          if (options.cloudServiceApi) {
            param.rduri = options.cloudServiceApi
            param.userid = sessionStorage.getItem('CloudUserID') || ''
            param.LoginUID = sessionStorage.getItem('CloudLoginUID') || ''
          }
          Api.fileuploadbase64(param).then(result => {
            if (result.status) {
              Api.getSystemConfig({
                func: 's_custom_components_adduptdel',
@@ -199,7 +212,6 @@
      <div className="user-component-wrap">
        <Icon type="user" title="生成自定义组件" onClick={this.trigger} />
        <Modal
          wrapClassName="popview-modal"
          title="自定义组件"
          visible={visible}
          width={500}
src/menu/components/table/normal-table/columns/editColumn/index.jsx
@@ -9,7 +9,7 @@
const columnTypeOptions = {
  text: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'textFormat', 'blacklist', 'perspective', 'rowspan'],
  number: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'blacklist', 'perspective', 'sum'],
  number: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'blacklist', 'perspective', 'sum', 'rowspan'],
  link: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'joint', 'Width', 'blacklist', 'nameField'],
  textarea: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'blacklist'],
  picture: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'blacklist', 'scale', 'lenWidRadio', 'picSort'],
src/menu/components/tabs/tabcomponents/card.jsx
@@ -13,6 +13,7 @@
const TableCard = asyncComponent(() => import('@/menu/components/card/table-card'))
const NormalTable = asyncComponent(() => import('@/menu/components/table/normal-table'))
const NormalGroup = asyncComponent(() => import('@/menu/components/group/normal-group'))
const BraftEditor = asyncComponent(() => import('@/menu/components/editor/braft-editor'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -66,6 +67,8 @@
      return (<NormalTable card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'group' && card.subtype === 'normalgroup') {
      return (<NormalGroup group={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'editor') {
      return (<BraftEditor card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
src/menu/components/tabs/tabcomponents/index.jsx
@@ -96,6 +96,7 @@
        search: '搜索',
        table: '表格',
        group: '分组',
        editor: '富文本',
        card: '卡片'
      }
      let i = 1
src/menu/datasource/index.jsx
@@ -115,7 +115,7 @@
    return (
      <div className="model-datasource">
        <Icon type="setting" onClick={() => this.editDataSource()} />
        <Icon type="setting" title="数据源" onClick={() => this.editDataSource()} />
        <Modal
          wrapClassName="popview-modal"
          title={'数据源配置'}
src/menu/datasource/verifycard/index.jsx
@@ -229,7 +229,7 @@
        res.data.forEach(item => {
          let _item = {
            name: item.funcname,
            value: Utils.UnformatOptions(item.longparam)
            value: window.decodeURIComponent(window.atob(item.longparam))
          }
          _scripts.push(_item)
src/menu/menushell/card.jsx
@@ -13,6 +13,8 @@
const TableCard = asyncComponent(() => import('@/menu/components/card/table-card'))
const NormalTable = asyncComponent(() => import('@/menu/components/table/normal-table'))
const NormalGroup = asyncComponent(() => import('@/menu/components/group/normal-group'))
const BraftEditor = asyncComponent(() => import('@/menu/components/editor/braft-editor'))
const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -66,6 +68,10 @@
      return (<NormalTable card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'group' && card.subtype === 'normalgroup') {
      return (<NormalGroup group={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'editor') {
      return (<BraftEditor card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'code') {
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
  return (
src/menu/menushell/index.jsx
@@ -89,6 +89,8 @@
        search: '搜索',
        table: '表格',
        group: '分组',
        editor: '富文本',
        code: '自定义',
        card: '卡片'
      }
      let i = 1
src/menu/modulesource/dragsource/index.jsx
src/menu/modulesource/dragsource/index.scss
src/menu/modulesource/index.jsx
src/menu/modulesource/index.scss
src/menu/modulesource/option.jsx
File was renamed from src/menu/modelsource/option.jsx
@@ -9,6 +9,8 @@
import TableCard from '@/assets/mobimg/table-card.png'
import NormalTable from '@/assets/mobimg/normal-table.png'
import Pie from '@/assets/mobimg/pie.png'
import Editor from '@/assets/mobimg/editor.png'
import SandBox from '@/assets/mobimg/sandbox.png'
import Pie1 from '@/assets/mobimg/ring.png'
import Pie2 from '@/assets/mobimg/nightingale.png'
import Mainsearch from '@/assets/mobimg/mainsearch.png'
@@ -27,6 +29,8 @@
  { type: 'menu', url: bar1, component: 'bar', subtype: 'bar1', title: '条形图', width: 24 },
  { type: 'menu', url: Pie, component: 'pie', subtype: 'pie', title: '饼图', width: 12 },
  { type: 'menu', url: Pie1, component: 'pie', subtype: 'ring', title: '环图', width: 12 },
  { type: 'menu', url: Editor, component: 'editor', subtype: 'brafteditor', title: '富文本', width: 24 },
  { type: 'menu', url: SandBox, component: 'code', subtype: 'sandbox', title: '自定义', width: 24 },
  { type: 'menu', url: Pie2, component: 'pie', subtype: 'nightingale', title: '南丁格尔图', width: 12 },
  { type: 'menu', url: group, component: 'group', subtype: 'normalgroup', title: '分组', width: 24, forbid: ['billPrint'] },
]
src/menu/pastecontroller/index.jsx
@@ -241,7 +241,7 @@
    return (
      <div style={{display: 'inline-block'}}>
        {type !== 'menu' ? <Icon type="snippets" style={{color: 'purple'}} onClick={() => {this.setState({visible: true})}} /> : null}
        {type === 'menu' ? <Button type="link" style={{padding: '5px'}} icon="snippets" onClick={() => {this.setState({visible: true})}}>粘贴</Button> : null}
        {type === 'menu' ? <Button className="menu-config-paste" icon="snippets" onClick={() => {this.setState({visible: true})}}>粘贴</Button> : null}
        <Modal
          title="粘贴"
          visible={visible}
src/menu/pastecontroller/index.scss
@@ -0,0 +1,4 @@
.menu-config-paste {
  border-color: #40a9ff;
  color: #40a9ff;
}
src/menu/picturecontroller/editform/index.jsx
New file
@@ -0,0 +1,160 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, notification } from 'antd'
import FileUpload from '@/tabviews/zshare/fileupload'
import './index.scss'
const { TextArea } = Input
class MainSearch extends Component {
  static propTpyes = {
    card: PropTypes.object,
    inputSubmit: PropTypes.func // 回车事件
  }
  state = {
    urls: [],
    linkurl: '',
    plusType: 'upload'
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          if (values.urls && values.urls[0].status === 'error') {
            notification.warning({
              top: 92,
              message: '请重新上传文件!',
              duration: 5
            })
            return
          } else if (values.urls && values.urls[0]) {
            values.linkurl = values.urls[0].response
          }
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  changeType = (val) => {
    const { linkurl, urls } = this.state
    let _urls = this.props.form.getFieldValue('urls') || ''
    let _url = this.props.form.getFieldValue('linkurl') || ''
    if (val === 'input') {
      if (_urls && _urls[0] && _urls[0].status === 'done' && (_urls[0].url || _urls[0].response)) {
        _url = _urls[0].url || _urls[0].response
      } else {
        _url = linkurl || ''
      }
    } else {
      _urls = urls.filter(item => item.status === 'done')
      _url = linkurl
    }
    this.setState({plusType: val, urls: _urls, linkurl: _url})
  }
  render() {
    const { getFieldDecorator } = this.props.form
    const { card } = this.props
    const { urls, linkurl, plusType } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 5 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 18 }
      }
    }
    return (
      <Form {...formItemLayout} className="picture-edit-model-form">
        <Row gutter={24}>
          {!card.id ? <Col span={24}>
            <Form.Item label="添加方式">
              <Radio.Group value={plusType} onChange={(e) => {this.changeType(e.target.value)}} disabled={false}>
                <Radio value="upload">上传</Radio>
                <Radio value="input">地址输入</Radio>
              </Radio.Group>
            </Form.Item>
          </Col> : null}
          {!card.id && card.typecharone === 'image' && plusType === 'upload' ? <Col span={24}>
            <Form.Item label="图片上传">
              {getFieldDecorator('urls', {
                initialValue: urls,
                rules: [
                  {
                    required: true,
                    message: '请上传图片!'
                  }
                ]
              })(
                <FileUpload accept=".jpg,.png,.gif,.pjp,.pjpeg,.jpeg,.jfif,.webp" maxFile={1} fileType={'picture'} />
              )}
            </Form.Item>
          </Col> : null}
          {!card.id && card.typecharone === 'video' && plusType === 'upload' ? <Col span={24}>
            <Form.Item label="视频上传">
              {getFieldDecorator('urls', {
                initialValue: urls,
                rules: [
                  {
                    required: true,
                    message: '请上传视频!'
                  }
                ]
              })(
                <FileUpload accept=".mp4,.webm,.ogg" maxFile={1} fileType={'text'} />
              )}
            </Form.Item>
          </Col> : null}
          {!card.id && plusType === 'input' ? <Col span={24}>
            <Form.Item label="地址">
              {getFieldDecorator('linkurl', {
                initialValue: linkurl,
                rules: [
                  {
                    required: true,
                    message: '请添加地址信息!'
                  },
                  {
                    max: 1024,
                    message: '地址最多1024个字符!'
                  }
                ]
              })(<TextArea autoSize={{ minRows: 3 }} onPressEnter={() => this.props.inputSubmit()}/>)}
            </Form.Item>
          </Col> : null}
          {card.id ? <Col span={24}>
            <Form.Item label="地址">
              <TextArea value={card.linkurl} readOnly={true} autoSize={{ minRows: 3 }} />
            </Form.Item>
          </Col> : null}
          <Col span={24}>
            <Form.Item label="备注">
              {getFieldDecorator('remark', {
                initialValue: card.remark,
                rules: [
                  {
                    max: 50,
                    message: '备注最多50个字符!'
                  }
                ]
              })(<TextArea autoSize={{ minRows: 4 }} onPressEnter={() => this.props.inputSubmit()}/>)}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(MainSearch)
src/menu/picturecontroller/editform/index.scss
New file
@@ -0,0 +1,14 @@
.picture-edit-model-form {
  min-height: 150px;
  .ant-input:read-only {
    background: #fafafa;
    resize: none;
  }
  .ant-input:read-only:hover, .ant-input:read-only:focus {
    border-color: #d9d9d9;
    box-shadow: none;
  }
  .ant-form-explain, .ant-form-extra {
    font-size: 13px;
  }
}
src/menu/picturecontroller/index.jsx
New file
@@ -0,0 +1,320 @@
import React, {Component} from 'react'
import { Modal, Button, Row, Col, Input, Icon, message, Tabs, Empty, Pagination, notification } from 'antd'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { Search } = Input
const { confirm } = Modal
const { TabPane } = Tabs
const EditForm = asyncComponent(() => import('./editform'))
const Video = asyncComponent(() => import('./video'))
const Image = asyncComponent(() => import('@/components/Image'))
class PasteController extends Component {
  state = {
    visible: false,
    editvisible: false,
    pictures: [],
    imageKey: '',
    videoKey: '',
    videos: [],
    card: null,
    pageSize: 12,
    filpictures: [],
    filvideos: [],
    piclist: [],
    vidlist: [],
    picIndex: 1,
    vidIndex: 1,
  }
  trigger = () => {
    let pictures = sessionStorage.getItem('app_pictures')
    let videos = sessionStorage.getItem('app_videos')
    try {
      pictures = JSON.parse(pictures)
      videos = JSON.parse(videos)
    } catch {
      pictures = []
      videos = []
    }
    let filpictures = pictures
    let filvideos = videos
    let piclist = filpictures.filter((item, index) => index < this.state.pageSize)
    let vidlist = filvideos.filter((item, index) => index < this.state.pageSize)
    this.setState({
      visible: true,
      filpictures,
      filvideos,
      pictures,
      picIndex: 1,
      vidIndex: 1,
      imageKey: '',
      videoKey: '',
      piclist,
      vidlist,
      videos
    })
  }
  handleSource = (item) => {
    this.setState({
      editvisible: true,
      card: item || null
    })
  }
  save = () => {
    const { card } = this.state
    this.editFormRef.handleConfirm().then(res => {
      res = {...card, ...res}
      if (!res.id) {
        res.id = Utils.getuuid()
      }
      Api.getSystemConfig({
        func: 's_url_db_adduptdel',
        id: res.id,
        PageIndex: 0, // 0 代表全部
        PageSize: 0,  // 0 代表全部
        remark: res.remark || '',
        linkurl: res.linkurl,
        typecharone: card.typecharone,
        type: card.id ? 'upt' : 'add'
      }).then(result => {
        if (result.status) {
          if (card.typecharone === 'image') {
            sessionStorage.setItem('app_pictures', JSON.stringify(result.data || []))
            this.resetPicture(result.data || [])
          } else {
            sessionStorage.setItem('app_videos', JSON.stringify(result.data || []))
            this.resetVideo(result.data || [])
          }
          this.setState({editvisible: false})
        } else {
          notification.warning({
            top: 92,
            message: result.message,
            duration: 5
          })
        }
      })
    })
  }
  copySource = (item) => {
    if (item.linkurl) {
      let oInput = document.createElement('input')
      oInput.value = item.linkurl
      document.body.appendChild(oInput)
      oInput.select()
      document.execCommand('Copy')
      document.body.removeChild(oInput)
      message.success('复制成功。')
    }
  }
  deleteSource = (item) => {
    const _this = this
    confirm({
      title: '确定删除吗?',
      content: '',
      onOk() {
        return new Promise((resolve) => {
          Api.getSystemConfig({
            func: 's_url_db_adduptdel',
            id: item.id,
            PageIndex: 0, // 0 代表全部
            PageSize: 0,  // 0 代表全部
            remark: '',
            linkurl: '',
            typecharone: item.typecharone,
            type: 'del'
          }).then(res => {
            if (res.status) {
              if (item.typecharone === 'image') {
                sessionStorage.setItem('app_pictures', JSON.stringify(res.data || []))
                _this.resetPicture(res.data || [])
              } else {
                sessionStorage.setItem('app_videos', JSON.stringify(res.data || []))
                _this.resetVideo(res.data || [])
              }
            } else {
              notification.warning({
                top: 92,
                message: res.message,
                duration: 5
              })
            }
            resolve()
          })
        })
      },
      onCancel() {}
    })
  }
  resetPicture = (data) => {
    const { imageKey, pageSize } = this.state
    let filpictures = data.filter(item => !imageKey || item.remark.indexOf(imageKey) > -1)
    let piclist = filpictures.filter((item, index) => index < pageSize)
    this.setState({picIndex: 1, filpictures, piclist, pictures: data})
  }
  resetVideo = (data) => {
    const { videoKey, pageSize } = this.state
    let filvideos = data.filter(item => !videoKey || item.remark.indexOf(videoKey) > -1)
    let vidlist = filvideos.filter((item, index) => index < pageSize)
    this.setState({vidIndex: 1, filvideos, vidlist, videos: data})
  }
  filterPicture = () => {
    const { imageKey, pictures, pageSize } = this.state
    let filpictures = pictures.filter(item => !imageKey || item.remark.indexOf(imageKey) > -1)
    let piclist = filpictures.filter((item, index) => index < pageSize)
    this.setState({picIndex: 1, filpictures, piclist})
  }
  filterVideo = () => {
    const { videoKey, videos, pageSize } = this.state
    let filvideos = videos.filter(item => !videoKey || item.remark.indexOf(videoKey) > -1)
    let vidlist = filvideos.filter((item, index) => index < pageSize)
    this.setState({vidIndex: 1, filvideos, vidlist})
  }
  changePicSize = (page) => {
    const { filpictures, pageSize } = this.state
    let piclist = filpictures.filter((item, index) => index < pageSize * page && index >= pageSize * (page - 1))
    this.setState({picIndex: page, piclist})
  }
  changeVidSize = (page) => {
    const { filvideos, pageSize } = this.state
    let vidlist = filvideos.filter((item, index) => index < pageSize * page && index >= pageSize * (page - 1))
    this.setState({vidIndex: page, vidlist})
  }
  render() {
    const { visible, editvisible, card, filpictures, filvideos, piclist, vidlist, imageKey, videoKey, pageSize, picIndex, vidIndex } = this.state
    return (
      <div style={{display: 'inline-block'}}>
        <Button className="mk-border-purple" icon="picture" onClick={this.trigger}>资源管理</Button>
        <Modal
          title="粘贴"
          wrapClassName="picture-control-model"
          visible={visible}
          width={1200}
          maskClosable={false}
          onCancel={() => {this.setState({visible: false})}}
          footer={[
            <Button key="colse" onClick={() => {this.setState({visible: false})}}>
              关闭
            </Button>
          ]}
          destroyOnClose
        >
          <Tabs>
            <TabPane tab="图片管理" key="picture">
              <Row style={{marginBottom: '15px'}}>
                <Col span={8}>
                  <Search placeholder="" value={imageKey} onChange={(e) => this.setState({imageKey: e.target.value})} onSearch={this.filterPicture} enterButton />
                </Col>
                <Col span={16}>
                  <Button className="picture-plus mk-green" icon="plus" onClick={() => this.handleSource({typecharone: 'image'})}>
                    添加
                  </Button>
                </Col>
              </Row>
              <Row gutter={16} style={{height: '340px'}}>
                {piclist.length && piclist.map(item => (
                  <Col span={4} key={item.id}>
                    <div className="image-video-box">
                      <div className="image-video-box-body">
                        <Image url={item.linkurl} />
                      </div>
                      <div className="image-video-control">
                        <Icon type="copy" onClick={() => this.copySource(item)}/>
                        <Icon type="edit" onClick={() => this.handleSource(item)}/>
                        <Icon type="delete" onClick={() => this.deleteSource(item)}/>
                      </div>
                    </div>
                    <p className="image-video-remark">{item.remark}</p>
                  </Col>
                ))}
                {!piclist.length ? <Empty description={null}/> : null}
              </Row>
              {filpictures.length > pageSize ? <Pagination size="small" current={picIndex} pageSize={pageSize} onChange={this.changePicSize} total={filpictures.length} /> : null}
            </TabPane>
            <TabPane tab="视频管理" key="video">
              <Row style={{marginBottom: '15px'}}>
                <Col span={8}>
                  <Search placeholder="" value={videoKey} onChange={e => this.setState({videoKey: e.target.value})} onSearch={this.filterVideo} enterButton />
                </Col>
                <Col span={16}>
                  <Button className="picture-plus mk-green" icon="plus" onClick={() => this.handleSource({typecharone: 'video'})}>
                    添加
                  </Button>
                </Col>
              </Row>
              <Row gutter={16} style={{height: '340px'}}>
                {vidlist.length && vidlist.map(item => (
                  <Col span={4} key={item.id}>
                    <div className="image-video-box">
                      <div className="image-video-box-body">
                        <Video value={item.linkurl} />
                      </div>
                      <div className="image-video-control">
                        <Icon type="copy" onClick={() => this.copySource(item)}/>
                        <Icon type="edit" onClick={() => this.handleSource(item)}/>
                        <Icon type="delete" onClick={() => this.deleteSource(item)}/>
                      </div>
                    </div>
                    <p className="image-video-remark">{item.remark}</p>
                  </Col>
                ))}
                {!vidlist.length ? <Empty description={null}/> : null}
              </Row>
              {filvideos.length > pageSize ? <Pagination size="small" current={vidIndex} pageSize={pageSize} onChange={this.changeVidSize} total={filvideos.length} /> : null}
            </TabPane>
          </Tabs>
        </Modal>
        <Modal
          title={card ? '编辑' : '新建'}
          wrapClassName="picture-edit-model"
          visible={editvisible}
          width={600}
          maskClosable={false}
          onOk={this.save}
          onCancel={() => {this.setState({editvisible: false})}}
          destroyOnClose
        >
          <EditForm card={card} wrappedComponentRef={(inst) => this.editFormRef = inst} inputSubmit={this.save}/>
        </Modal>
      </div>
    )
  }
}
export default PasteController
src/menu/picturecontroller/index.scss
New file
@@ -0,0 +1,88 @@
.picture-control-model {
  .ant-modal {
    top: 60px;
    .ant-modal-body {
      max-height: calc(100vh - 120px);
      min-height: 510px;
      padding-top: 5px;
    }
  }
  .ant-tabs-tabpane {
    padding-left: 3px;
    padding-right: 3px;
  }
  .picture-plus {
    float: right;
    font-size: 16px;
  }
  .image-video-box {
    position: relative;
    padding-top: 75%;
    box-shadow: 0 0 2px #bcbcbc;
    .image-video-box-body {
      position: absolute;
      top: 0px;
      left: 0px;
      right: 0px;
      bottom: 0px;
      .video-react-big-play-button {
        display: none;
      }
      .video-react-control-bar {
        display: none;
      }
    }
    .image-video-control {
      position: absolute;
      top: 0;
      left: 0px;
      right: 0px;
      bottom: 0px;
      background: rgba(255, 255, 255, 0.8);
      padding-top: 30%;
      text-align: center;
      opacity: 0;
      transition: all 0.3s;
      i {
        font-size: 20px;
        cursor: pointer;
      }
      .anticon-copy {
        color: rgb(38, 194, 129);
        margin-right: 15px;
      }
      .anticon-edit {
        color: #1890ff;
        margin-right: 15px;
      }
      .anticon-delete {
        color: #ff4d4f;
      }
    }
  }
  .image-video-box:hover {
    .image-video-control {
      opacity: 1;
    }
  }
  .image-video-remark {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
    word-break: break-all;
    font-size: 13px;
    margin-bottom: 0;
    margin-top: 2px;
    height: 35px;
  }
  .ant-pagination {
    text-align: right;
  }
}
.picture-edit-model {
  .ant-modal {
    .ant-modal-body {
      min-height: 200px;
    }
  }
}
src/menu/picturecontroller/video/index.jsx
New file
@@ -0,0 +1,29 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Player } from 'video-react'
import './index.scss'
class Video extends Component {
  static propTpyes = {
    value: PropTypes.string,    // 视频地址
  }
  componentDidMount () {
    this.player.seek(1)
  }
  shouldComponentUpdate () {
    return false
  }
  render() {
    return (
      <Player ref={player => { this.player = player }} aspectRatio={'4:3'}>
        <source src={this.props.value} />
      </Player>
    )
  }
}
export default Video
src/menu/picturecontroller/video/index.scss
New file
@@ -0,0 +1,52 @@
.video-react {
  display: block;
  box-sizing: border-box;
  color: #fff;
  background-color: #000;
  position: relative;
  font-size: 10px;
  line-height: 1;
  font-family: serif, Times, "Times New Roman";
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.video-react *,
.video-react *:before,
.video-react *:after {
  box-sizing: inherit;
}
.video-react ul {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  list-style-position: outside;
  margin-left: 0;
  margin-right: 0;
  margin-top: 0;
  margin-bottom: 0;
}
.video-react.video-react-fluid, .video-react.video-react-16-9, .video-react.video-react-4-3 {
  width: 100%;
  max-width: 100%;
  height: 0;
}
.video-react.video-react-16-9 {
  padding-top: 56.25%;
}
.video-react.video-react-4-3 {
  padding-top: 75%;
}
.video-react.video-react-fill {
  width: 100%;
  height: 100%;
}
.video-react .video-react-video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
src/menu/popview/index.jsx
@@ -21,7 +21,7 @@
const { confirm } = Modal
const MenuForm = asyncComponent(() => import('./menuform'))
const SourceWrap = asyncComponent(() => import('@/menu/modelsource'))
const SourceWrap = asyncComponent(() => import('@/menu/modulesource'))
const MenuShell = asyncComponent(() => import('@/menu/menushell'))
const BgController = asyncComponent(() => import('@/menu/bgcontroller'))
const PasteController = asyncComponent(() => import('@/menu/pastecontroller'))
@@ -246,37 +246,40 @@
          traversal(item.components)
        } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.action && item.action.forEach(btn => {
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') 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
              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.action && item.action.forEach(btn => {
            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.action && item.action.forEach(btn => {
            if (btn.origin) return
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
          item.cols && item.cols.forEach(col => {
            if (col.type !== 'action') return
            col.elements.forEach(btn => {
              this.checkBtn(btn)
              buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
            })
@@ -288,6 +291,26 @@
    traversal(config.components)
    return buttons
  }
  checkBtn = (btn) => {
    if (['prompt', 'exec', 'pop'].includes(btn.OpenType) && btn.Ot === 'required' && btn.verify && btn.verify.scripts && btn.verify.scripts.length > 0) {
      let hascheck = false
      btn.verify.scripts.forEach(item => {
        if (item.status === 'false') return
        if (/\$check@|@check\$/ig.test(item.sql)) {
          hascheck = true
        }
      })
      if (hascheck) {
        notification.warning({
          top: 92,
          message: `可选择多行的按钮《${btn.label}》中 $check@ 或 @check$ 将不会生效!`,
          duration: 5
        })
      }
    }
  }
  filterConfig = (components) => {
@@ -321,52 +344,54 @@
      return
    }
    config.components = this.filterConfig(config.components)
    if (config.enabled && this.verifyConfig()) {
      config.enabled = false
    }
    let _config = fromJS(config).toJS()
    delete _config.tableFields
    let _name = (btn.component.name ? btn.component.name + '-' : '') + btn.label
    let param = {
      func: 'sPC_ButtonParam_AddUpt',
      ParentID: btn.config.uuid,
      MenuID: _config.uuid,
      MenuNo: _config.MenuNo || '',
      Template: 'CustomPage',
      MenuName: _name,
      PageParam: JSON.stringify({Template: 'CustomPage'}),
      LongParam: window.btoa(window.encodeURIComponent(JSON.stringify(_config)))
    }
    if (openEdition) { // 版本管理
      param.open_edition = openEdition
    }
    let btnParam = {             // 添加菜单按钮
      func: 'sPC_Button_AddUpt',
      Type: 60,                  // 添加菜单下的按钮type为40,按钮下的按钮type为60
      ParentID: _config.uuid,
      MenuNo: _config.MenuNo,
      Template: 'CustomPage',
      PageParam: '',
      LongParam: '',
      LText: []
    }
    btnParam.LText = this.getMenuMessage()
    btnParam.LText = btnParam.LText.join(' union all ')
    btnParam.LText = Utils.formatOptions(btnParam.LText)
    btnParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    btnParam.secretkey = Utils.encrypt(btnParam.LText, btnParam.timestamp)
    this.setState({
      menuloading: true
    }, () => {
    })
    setTimeout(() => {
      config.components = this.filterConfig(config.components)
      if (config.enabled && this.verifyConfig()) {
        config.enabled = false
      }
      let _config = fromJS(config).toJS()
      delete _config.tableFields
      let _name = (btn.component.name ? btn.component.name + '-' : '') + btn.label
      let param = {
        func: 'sPC_ButtonParam_AddUpt',
        ParentID: btn.config.uuid,
        MenuID: _config.uuid,
        MenuNo: _config.MenuNo || '',
        Template: 'CustomPage',
        MenuName: _name,
        PageParam: JSON.stringify({Template: 'CustomPage'}),
        LongParam: window.btoa(window.encodeURIComponent(JSON.stringify(_config)))
      }
      if (openEdition) { // 版本管理
        param.open_edition = openEdition
      }
      let btnParam = {             // 添加菜单按钮
        func: 'sPC_Button_AddUpt',
        Type: 60,                  // 添加菜单下的按钮type为40,按钮下的按钮type为60
        ParentID: _config.uuid,
        MenuNo: _config.MenuNo,
        Template: 'CustomPage',
        PageParam: '',
        LongParam: '',
        LText: []
      }
      btnParam.LText = this.getMenuMessage()
      btnParam.LText = btnParam.LText.join(' union all ')
      btnParam.LText = Utils.formatOptions(btnParam.LText)
      btnParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
      btnParam.secretkey = Utils.encrypt(btnParam.LText, btnParam.timestamp)
      new Promise(resolve => {
        if (delButtons.length === 0) {
          resolve({
@@ -453,7 +478,7 @@
          })
        }
      })
    })
    }, 300)
  }
  onEnabledChange = () => {
@@ -474,7 +499,7 @@
    config.components.forEach(item => {
      if (error) return
      if (item.subtype === 'propcard' && item.wrap.datatype === 'static') return
      if (['propcard', 'brafteditor', 'sandbox'].includes(item.subtype) && item.wrap.datatype === 'static') return
      if (item.setting) {
        if (item.setting.interType === 'system' && item.setting.execute !== 'false' && !item.setting.dataresource) {
@@ -560,10 +585,10 @@
              {customComponents && customComponents.length ? <Panel header="自定义组件" key="cuscomponent">
                <SourceWrap components={customComponents} MenuType={MenuType} />
              </Panel> : null}
              <Panel header={'背景'} key="background">
              <Panel header={'页面背景'} key="background">
                {config ? <BgController config={config} updateConfig={this.updateConfig} /> : null}
              </Panel>
              <Panel header={'内边距'} key="padding">
              <Panel header={'页面内边距'} key="padding">
                {config ? <PaddingController config={config} updateConfig={this.updateConfig} /> : null}
              </Panel>
            </Collapse>
src/menu/stylecombcontrolbutton/index.jsx
@@ -148,7 +148,7 @@
  render() {
    const { label } = this.state
    return (
      <Button className="style-control-button" type="link" icon="font-colors" title="调整样式" onClick={this.triggerStyleChange}>{label}</Button>
      <Button className="style-control-button" icon="font-colors" title="调整样式" onClick={this.triggerStyleChange}>{label}</Button>
    )
  }
}
src/menu/stylecombcontrolbutton/index.scss
@@ -1,6 +1,7 @@
.style-control-button.ant-btn-link, .style-control-button.ant-btn-link:hover, .style-control-button.ant-btn-link:focus {
.style-control-button, .style-control-button:hover, .style-control-button:focus {
  color: orange;
  border-color: orange;
  position: relative;
  z-index: 13;
  background: #ffffff;
src/menu/stylecontroller/index.jsx
@@ -1,18 +1,19 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Collapse, Form, Input, Col, Icon, InputNumber, Select, Radio, Drawer, Button } from 'antd'
import { Collapse, Form, Col, Icon, InputNumber, Select, Radio, Drawer, Button } from 'antd'
import MKEmitter from '@/utils/events.js'
import zhCN from '@/locales/zh-CN/mob.js'
import enUS from '@/locales/en-US/mob.js'
import ColorSketch from '@/mob/colorsketch'
import asyncComponent from '@/utils/asyncComponent'
import StyleInput from './styleInput'
import FileUpload from '@/tabviews/zshare/fileupload'
import './index.scss'
const { Panel } = Collapse
const { Option } = Select
const ColorSketch = asyncComponent(() => import('@/mob/colorsketch'))
const SourceComponent = asyncComponent(() => import('@/menu/components/share/sourcecomponent'))
class MobController extends Component {
  static propTpyes = {
@@ -24,7 +25,6 @@
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    card: null,
    comIds: [],
    bgimages: [],
    backgroundImage: '',
    options: [],
    borposition: 'outer'
@@ -50,12 +50,8 @@
  initStyle = (comIds, options, style = {}) => {
    let backgroundImage = ''
    if (style.backgroundImage) {
      if (/^url/ig.test(style.backgroundImage)) {
        backgroundImage = style.backgroundImage.replace(/^url\(/ig, '').replace(/\)$/ig, '')
      } else if (/^linear-gradient/ig.test(style.backgroundImage)) {
        backgroundImage = style.backgroundImage.replace(/^linear-gradient\(/ig, '').replace(/\)$/ig, '')
      }
    if (style.backgroundImage && /^url/ig.test(style.backgroundImage)) {
      backgroundImage = style.backgroundImage.replace(/^url\(/ig, '').replace(/\)$/ig, '')
    }
    this.setState({
@@ -69,6 +65,35 @@
  }
  onCloseDrawer = () => {
    let { card } = this.state
    let check = false
    if (card.borderWidth === '0px') {
      delete card.borderWidth
      delete card.borderColor
      check = true
    } else if (card.borderLeftWidth === '0px') {
      delete card.borderLeftWidth
      delete card.borderLeftColor
      check = true
    } else if (card.borderRightWidth === '0px') {
      delete card.borderRightWidth
      delete card.borderRightWidth
      check = true
    } else if (card.borderTopWidth === '0px') {
      delete card.borderTopWidth
      delete card.borderTopWidth
      check = true
    } else if (card.borderBottomWidth === '0px') {
      delete card.borderBottomWidth
      delete card.borderBottomWidth
      check = true
    }
    if (check) {
      MKEmitter.emit('submitStyle', this.state.comIds, card)
    }
    this.setState({
      visible: false,
      comIds: [],
@@ -175,30 +200,15 @@
    this.updateStyle({shadow: val})
  }
  imgChange = (list) => {
    if (list[0] && list[0].response) {
      this.setState({
        bgimages: [],
        backgroundImage: list[0].response
      })
      this.updateStyle({backgroundImage: `url(${list[0].response})`})
  imgChange = (val) => {
    this.setState({
      backgroundImage: val
    })
    if (val) {
      this.updateStyle({backgroundImage: `url(${val})`})
    } else {
      this.setState({bgimages: list})
      this.updateStyle({backgroundImage: ''})
    }
  }
  changeBackgroundImageInput = (e) => {
    let val = e.target.value
    val = val.replace(/^\s*|\s*$/ig, '')
    if (/^http|^\/\//.test(val)) {
      val = `url(${val})`
    } else if (/,/ig.test(val) && !/^(radial-gradient|linear-gradient)/ig.test(val)) {
      val = `linear-gradient(${val})`
    }
    this.setState({backgroundImage: e.target.value})
    this.updateStyle({backgroundImage: val})
  }
  changeBorderStyle = (val) => {
@@ -272,7 +282,7 @@
  }
  render () {
    const { card, options, backgroundImage, bgimages, borposition } = this.state
    const { card, options, backgroundImage, borposition } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
@@ -415,8 +425,7 @@
                    label={<Icon title="背景图片" type="picture" />}
                    labelCol={{xs: { span: 24 }, sm: { span: 4 }}} wrapperCol={ {xs: { span: 24 }, sm: { span: 20 }} }
                  >
                    <FileUpload accept=".jpg,.png,.gif,.svg" value={bgimages} maxFile={2} fileType="text" onChange={this.imgChange}/>
                    <Input placeholder="" value={backgroundImage} autoComplete="off" onChange={this.changeBackgroundImageInput} />
                    <SourceComponent value={backgroundImage} type="" placement="right" onChange={this.imgChange}/>
                  </Form.Item>
                </Col> : null}
              </Panel> : null}
src/menu/stylecontroller/index.scss
@@ -28,6 +28,18 @@
        .ant-input-number {
          width: 100%;
        }
        .mk-source-wrap {
          height: 32px;
          .anticon-paper-clip {
            color:rgba(255, 255, 255, 0.7);
          }
          .anticon-delete {
            color:rgba(255, 255, 255, 0.7);
          }
          .mk-source-item-info:hover {
            background-color: transparent;
          }
        }
        .ant-form-item {
          margin-bottom: 2px;
src/menu/sysinterface/index.jsx
New file
@@ -0,0 +1,215 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Modal, Button, Icon, Popconfirm, message } from 'antd'
import Utils from '@/utils/utils.js'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const SettingForm = asyncComponent(() => import('./settingform'))
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
class InterfaceController extends Component {
  static propTpyes = {
    config: PropTypes.object,       // 页面配置
    updateConfig: PropTypes.func    // 更新
  }
  state = {
    visible: false,
    setvisible: false,
    interfaces: [],
    card: null,
    columns: [
      {
        title: '接口名称',
        dataIndex: 'name',
        width: '50%'
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '20%',
        render: (text, record) => record.status !== 'true' ?
          (
            <div>
              禁用
              <Icon style={{marginLeft: '5px'}} type="stop" theme="twoTone" twoToneColor="#ff4d4f" />
            </div>
          ) :
          (
            <div>
              启用
              <Icon style={{marginLeft: '5px'}} type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
            </div>
          )
      },
      {
        title: '操作',
        align: 'center',
        width: '30%',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div style={{textAlign: 'center'}}>
            <span onClick={() => this.handleEdit(record)} style={{color: '#1890ff', cursor: 'pointer', fontSize: '16px', marginRight: '15px'}}><Icon type="edit" /></span>
            <span onClick={() => {this.copy(record)}} style={{color: '#26C281', cursor: 'pointer', fontSize: '16px', marginRight: '15px'}}><Icon type="copy" /></span>
            <Popconfirm
              overlayClassName="popover-confirm"
              title="确定删除?"
              onConfirm={() => this.deleteScript(record)
            }>
              <span style={{color: '#ff4d4f', cursor: 'pointer', fontSize: '16px'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  copy = (item) => {
    let msg = { key: 'interface', type: 'line', data: item }
    try {
      msg = window.btoa(window.encodeURIComponent(JSON.stringify(msg)))
    } catch {
      console.warn('Stringify Failure')
      msg = ''
    }
    if (msg) {
      let oInput = document.createElement('input')
      oInput.value = msg
      document.body.appendChild(oInput)
      oInput.select()
      document.execCommand('Copy')
      document.body.removeChild(oInput)
      message.success('复制成功。')
    }
  }
  trigger = () => {
    const { config } = this.props
    let interfaces = config.interfaces ? fromJS(config.interfaces).toJS() : []
    this.setState({
      visible: true,
      interfaces
    })
  }
  handleEdit = (record) => {
    this.setState({card: record, setvisible: true})
  }
  deleteScript = (record) => {
    const { config } = this.props
    let interfaces = this.state.interfaces.filter(item => item.uuid !== record.uuid)
    this.setState({ interfaces })
    this.props.updateConfig({...config, interfaces})
  }
  changeScripts = (interfaces) => {
    const { config } = this.props
    this.setState({ interfaces })
    this.props.updateConfig({...config, interfaces})
  }
  settingSave = () => {
    const { config } = this.props
    const { card } = this.state
    let interfaces = fromJS(this.state.interfaces).toJS()
    this.settingRef.handleConfirm().then(res => {
      interfaces = interfaces.map(item => {
        if (item.uuid === card.uuid) {
          res.uuid = item.uuid
          if (res.procMode !== 'inner' && res.preScripts && res.preScripts.filter(item => item.status !== 'false').length === 0) {
            message.warning('未设置前置脚本,不可启用!')
            res.status = 'false'
          } else if (res.callbackType === 'script' && res.cbScripts && res.cbScripts.filter(item => item.status !== 'false').length === 0) {
            message.warning('未设置回调脚本,不可启用!')
            res.status = 'false'
          }
          return res
        }
        return item
      })
      this.setState({
        card: null,
        setvisible: false,
        interfaces
      })
      this.props.updateConfig({...config, interfaces})
    })
  }
  addInterface = () => {
    const { config } = this.props
    let interfaces = fromJS(this.state.interfaces).toJS()
    interfaces.push({
      uuid: Utils.getuuid(),
      name: 'interface ' + (interfaces.length + 1),
      procMode: 'script',
      callbackType: 'script',
      preScripts: [],
      cbScripts: []
    })
    this.setState({
      interfaces
    })
    this.props.updateConfig({...config, interfaces})
  }
  render() {
    const { visible, setvisible, columns, interfaces, card } = this.state
    return (
      <div style={{display: 'inline-block'}}>
        <Button className="mk-border-green" icon="api" onClick={this.trigger}>接口管理</Button>
        <Modal
          title="接口管理"
          wrapClassName="interface-controller-modal"
          visible={visible}
          width={800}
          maskClosable={false}
          onCancel={() => {this.setState({visible: false})}}
          footer={[
            <Button key="colse" onClick={() => {this.setState({visible: false})}}>
              关闭
            </Button>
          ]}
          destroyOnClose
        >
          <Button key="add-interface" className="mk-border-green" onClick={this.addInterface}> 添加 </Button>
          <EditTable key="manage-interface" actions={['move', 'copy']} type="interface" data={interfaces} columns={columns} onChange={this.changeScripts}/>
        </Modal>
        <Modal
          title={card ? card.name : '接口'}
          wrapClassName="interface-edit-modal"
          visible={setvisible}
          width={900}
          maskClosable={false}
          onOk={this.settingSave}
          onCancel={() => { this.setState({ setvisible: false })}}
          destroyOnClose
        >
          <SettingForm config={card} wrappedComponentRef={(inst) => this.settingRef = inst}/>
        </Modal>
      </div>
    )
  }
}
export default InterfaceController
src/menu/sysinterface/index.scss
New file
@@ -0,0 +1,20 @@
.interface-controller-modal {
  >.ant-modal >.ant-modal-content >.ant-modal-body {
    min-height: 400px;
    >.mk-border-green {
      float: right;
      position: relative;
      z-index: 1;
      margin-bottom: 10px;
    }
  }
}
.interface-edit-modal {
  .ant-modal {
    top: 70px;
  }
  .ant-modal-body {
    min-height: 300px;
    padding-top: 5px;
  }
}
src/menu/sysinterface/settingform/baseform/index.jsx
New file
@@ -0,0 +1,243 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, Tooltip, Icon } from 'antd'
import { formRule } from '@/utils/option.js'
import './index.scss'
const { TextArea } = Input
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,       // 字典项
    setting: PropTypes.object,    // 数据源配置
    updateStatus: PropTypes.func, // 状态更新
  }
  state = {
    procMode: 'script',
    funcTooltip: '',
    funcRules: []
  }
  UNSAFE_componentWillMount () {
    const { setting } = this.props
    let usefulFields = sessionStorage.getItem('permFuncField')
    if (usefulFields) {
      try {
        usefulFields = JSON.parse(usefulFields)
      } catch {
        usefulFields = []
      }
    } else {
      usefulFields = []
    }
    let tooltip = null
    let rules = []
    if (usefulFields.length > 0) {
      tooltip = '开头可用字符:' + usefulFields.join(', ')
      let str = '^(' + usefulFields.join('|') + ')'
      let _patten = new RegExp(str + formRule.func.innerPattern + '$', 'g')
      rules.push({
        pattern: _patten,
        message: formRule.func.innerMessage
      })
    }
    this.setState({
      procMode: setting.procMode || 'script',
      funcTooltip: tooltip,
      funcRules: rules
    })
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  onRadioChange = (e, key) => {
    let value = e.target.value
    if (key === 'procMode') {
      this.setState({
        procMode: value
      })
    }
    this.props.updateStatus({[key]: value})
  }
  render() {
    const { setting, dict } = this.props
    const { getFieldDecorator } = this.props.form
    const { funcRules, funcTooltip, procMode } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-table-datasource-setting-form-box">
        <Form {...formItemLayout} className="model-setting-form">
          <Row gutter={24}>
            <Col span={12}>
              <Form.Item label="接口名">
                {getFieldDecorator('name', {
                  initialValue: setting.name || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '接口名!'
                    },
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="状态">
                {getFieldDecorator('status', {
                  initialValue: setting.status || 'true'
                })(
                <Radio.Group>
                  <Radio value="true">启用</Radio>
                  <Radio value="false">禁用</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="参数处理">
                {getFieldDecorator('procMode', {
                  initialValue: procMode,
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.select'] + '参数处理方式!'
                    },
                  ]
                })(
                <Radio.Group style={{whiteSpace: 'nowrap'}} onChange={(e) => {this.onRadioChange(e, 'procMode')}}>
                  <Radio value="script">前置脚本</Radio>
                  <Radio value="inner">前置函数</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            {procMode === 'inner' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={funcTooltip}>
                  <Icon type="question-circle" />
                  前置函数
                </Tooltip>
              }>
                {getFieldDecorator('prevFunc', {
                  initialValue: setting.prevFunc || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '前置函数!'
                    },
                    {
                      max: formRule.func.max,
                      message: formRule.func.maxMessage
                    },
                    ...funcRules
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            <Col className="data-source" span={24}>
              <Form.Item label="测试地址">
                {getFieldDecorator('interface', {
                  initialValue: setting.interface || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '测试地址!'
                    },
                  ]
                })(<TextArea rows={2} />)}
              </Form.Item>
            </Col>
            <Col className="data-source" span={24}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="正式系统所使用的的接口地址。">
                  <Icon type="question-circle" />
                  正式地址
                </Tooltip>
              }>
                {getFieldDecorator('proInterface', {
                  initialValue: setting.proInterface || ''
                })(<TextArea rows={2} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="请求方式">
                {getFieldDecorator('method', {
                  initialValue: setting.method || 'post',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.select'] + '请求方式!'
                    },
                  ]
                })(
                <Radio.Group>
                  <Radio value="get">GET</Radio>
                  <Radio value="post">POST</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="回调方式">
                {getFieldDecorator('callbackType', {
                  initialValue: setting.callbackType || 'script'
                })(
                <Radio.Group onChange={(e) => {this.onRadioChange(e, 'callbackType')}}>
                  <Radio value="default">默认脚本</Radio>
                  <Radio value="script">自定义脚本</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="回调表名">
                {getFieldDecorator('cbTable', {
                  initialValue: setting.cbTable || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '回调表名!'
                    },
                    {
                      max: formRule.input.max,
                      message: formRule.input.message
                    }
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col>
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/menu/sysinterface/settingform/baseform/index.scss
New file
@@ -0,0 +1,22 @@
.model-table-datasource-setting-form-box {
  position: relative;
  .model-setting-form {
    .data-source {
      .ant-form-item-label {
        width: 16.5%;
      }
      .ant-form-item-control-wrapper {
        width: 83.5%;
      }
      .CodeMirror {
        height: 150px;
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      margin-right: 3px;
    }
  }
}
src/menu/sysinterface/settingform/index.jsx
New file
@@ -0,0 +1,179 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, notification, Tabs } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import BaseForm from './baseform'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
const { TabPane } = Tabs
const SimpleScript = asyncComponent(() => import('./simplescript'))
class SettingForm extends Component {
  static propTpyes = {
    config: PropTypes.object,       // 页面配置信息
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    formlist: [],
    btnloading: false,
    activeKey: 'setting',
    setting: null,
    defaultSql: '',
    status: {}
  }
  UNSAFE_componentWillMount() {
    const { config } = this.props
    let _setting = fromJS(config).toJS()
    let _preScripts = _setting.preScripts || []
    let _cbScripts = _setting.cbScripts || []
    this.setState({
      setting: _setting,
      preScripts: _preScripts,
      cbScripts: _cbScripts,
      status: fromJS(_setting).toJS()
    })
  }
  handleConfirm = () => {
    const { activeKey, setting, preScripts, cbScripts } = this.state
    let _loading = false
    if (this.preScriptsForm && this.preScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    } else if (this.cbScriptsForm && this.cbScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    }
    if (_loading) {
      notification.warning({
        top: 92,
        message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
        duration: 5
      })
      return Promise.reject()
    }
    // 表单提交时检查输入值是否正确
    if (activeKey === 'setting') {
      return new Promise((resolve, reject) => {
        this.settingForm.handleConfirm().then(res => {
          resolve({...res, preScripts, cbScripts})
        }, () => {
          reject()
        })
      })
    } else {
      return new Promise((resolve) => {
        resolve({...setting, preScripts, cbScripts})
      })
    }
  }
  // 标签切换
  changeTab = (val) => {
    const { activeKey } = this.state
    let _loading = false
    if (this.preScriptsForm && this.preScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    } else if (this.cbScriptsForm && this.cbScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    }
    if (_loading) {
      notification.warning({
        top: 92,
        message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
        duration: 5
      })
      return
    }
    if (activeKey === 'setting') {
      this.settingForm.handleConfirm().then(res => {
        this.setState({
          setting: res,
          activeKey: val
        })
      })
    } else {
      this.setState({
        activeKey: val
      })
    }
  }
  // 前置脚本更新
  preScriptsUpdate = (preScripts) => {
    this.setState({preScripts})
  }
  // 后置脚本更新
  cbScriptsUpdate = (cbScripts) => {
    this.setState({cbScripts})
  }
  updateStatus = (status) => {
    this.setState({status: {...this.state.status, ...status}})
  }
  render() {
    const { dict, activeKey, setting, preScripts, cbScripts, status } = this.state
    return (
      <div className="model-interface-form-box" id="model-interface-form-body">
        <Tabs activeKey={activeKey} className="verify-card-box" onChange={this.changeTab}>
          <TabPane tab="接口设置" key="setting">
            <BaseForm
              dict={dict}
              setting={setting}
              updateStatus={this.updateStatus}
              wrappedComponentRef={(inst) => this.settingForm = inst}
            />
          </TabPane>
          <TabPane tab={
            <span>
              前置脚本
              {preScripts.length ? <span className="count-tip">{preScripts.length}</span> : null}
            </span>
          } disabled={status.procMode !== 'script'} key="prescripts">
            <SimpleScript
              dict={dict}
              type="front"
              setting={setting}
              scripts={preScripts}
              scriptsUpdate={this.preScriptsUpdate}
              wrappedComponentRef={(inst) => this.preScriptsForm = inst}
            />
          </TabPane>
          <TabPane tab={
            <span>
              回调脚本
              {cbScripts.length ? <span className="count-tip">{cbScripts.length}</span> : null}
            </span>
          } disabled={status.callbackType !== 'script'} key="cbscripts">
            <SimpleScript
              dict={dict}
              type="back"
              setting={setting}
              scripts={cbScripts}
              scriptsUpdate={this.cbScriptsUpdate}
              wrappedComponentRef={(inst) => this.cbScriptsForm = inst}
            />
          </TabPane>
        </Tabs>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/menu/sysinterface/settingform/index.scss
New file
@@ -0,0 +1,65 @@
.model-interface-form-box {
  position: relative;
  >.ant-spin {
    position: absolute;
    top: 150px;
    left: calc(50% - 16px);
  }
  .count-tip {
    position: absolute;
    top: 0px;
    color: #1890ff;
    font-size: 12px;
  }
  .model-table-setting-form {
    .textarea {
      .ant-form-item-label {
        width: 16.3%;
      }
      .ant-form-item-control-wrapper {
        width: 83.33333333%;
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      margin-right: 3px;
    }
    .text-area {
      .CodeMirror {
        height: 150px;
      }
    }
  }
  .operation-btn {
    display: inline-block;
    font-size: 16px;
    padding: 0 5px;
    cursor: pointer;
  }
  td {
    word-break: break-all;
  }
  .setting-custom-back {
    position: absolute;
    top: -20px;
    left: -10px;
    font-size: 16px;
    z-index: 1;
    cursor: pointer;
    padding: 10px;
    color: rgb(24, 144, 255);
  }
  .to-custom-script {
    float: right;
    color: #1890ff;
    margin-right: 12px;
    margin-top: 15px;
    cursor: pointer;
    border: 0;
    box-shadow: unset;
  }
  .ant-tabs-nav-wrap {
    text-align: center;
  }
}
src/menu/sysinterface/settingform/simplescript/index.jsx
New file
@@ -0,0 +1,450 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Icon, Button, notification, Select, Popconfirm, Typography, Modal, Radio } from 'antd'
import moment from 'moment'
import Utils from '@/utils/utils.js'
import Api from '@/api'
import SettingUtils from '../utils'
import CodeMirror from '@/templates/zshare/codemirror'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { Paragraph } = Typography
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
class CustomForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,         // 字典项
    type: PropTypes.string,         // 脚本类型
    setting: PropTypes.object,      // 设置
    scripts: PropTypes.array,       // 自定义脚本列表
    scriptsChange: PropTypes.func,  // 自定义脚本切换时验证
    scriptsUpdate: PropTypes.func   // 表单
  }
  state = {
    editItem: null,
    loading: false,
    systemScripts: [],
    scriptsColumns: [
      {
        title: 'SQL',
        dataIndex: 'sql',
        width: '60%',
        render: (text) => {
          let title = text.match(/^\s*\/\*.+\*\//)
          title = title && title[0] ? title[0] : ''
          text = title ? text.replace(title, '') : text
          return (
            <div>
              {title ? <span style={{color: '#a50'}}>{title}</span> : null}
              <Paragraph copyable ellipsis={{ rows: 4, expandable: true }}>{text}</Paragraph>
            </div>
          )
        }
      },
      {
        title: '执行位置',
        dataIndex: 'position',
        width: '13%',
        render: (text, record) => {
          if (record.position === 'front') {
            return 'sql前'
          } else {
            return 'sql后'
          }
        }
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '12%',
        render: (text, record) => record.status === 'false' ?
          (
            <div>
              {this.props.dict['model.status.forbidden']}
              <Icon style={{marginLeft: '5px'}} type="stop" theme="twoTone" twoToneColor="#ff4d4f" />
            </div>
          ) :
          (
            <div>
              {this.props.dict['model.status.open']}
              <Icon style={{marginLeft: '5px'}} type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
            </div>
          )
      },
      {
        title: '操作',
        align: 'center',
        width: '15%',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div style={{textAlign: 'center'}}>
            <span className="operation-btn" title={this.props.dict['model.edit']} onClick={() => this.handleEdit(record)} style={{color: '#1890ff'}}><Icon type="edit" /></span>
            <span className="operation-btn" title={this.props.dict['header.form.status.change']} onClick={() => this.handleStatus(record)} style={{color: '#8E44AD'}}><Icon type="swap" /></span>
            <Popconfirm
              overlayClassName="popover-confirm"
              title={this.props.dict['model.query.delete']}
              onConfirm={() => this.handleDelete(record)
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
  UNSAFE_componentWillMount() {
    const { scripts } = this.props
    let scriptsColumns = fromJS(this.state.scriptsColumns).toJS()
    this.setState({
      scripts: fromJS(scripts).toJS(),
      scriptsColumns
    })
  }
  componentDidMount () {
    this.getsysScript()
  }
  getsysScript = () => {
    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) {
        let _scripts = res.data.map(item => {
          let _item = {
            name: item.funcname,
            value: window.decodeURIComponent(window.atob(item.longparam))
          }
          return _item
        })
        this.setState({
          systemScripts: _scripts
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  handleCancel = () => {
    this.setState({
      editItem: null
    })
    this.props.form.setFieldsValue({
      sql: ''
    })
  }
  handleConfirm = () => {
    const { scripts, editItem } = this.state
    let _sql = this.props.form.getFieldValue('sql')
    if (!_sql) {
      notification.warning({
        top: 92,
        message: '请填写自定义脚本!',
        duration: 5
      })
      return
    } else if (/^\s+$/.test(_sql)) {
      notification.warning({
        top: 92,
        message: '自定义脚本不可为空!',
        duration: 5
      })
      return
    }
    let values = {
      uuid: editItem && editItem.uuid ? editItem.uuid : Utils.getuuid(),
      sql: _sql,
    }
    if (this.props.form.getFieldValue('position')) {
      values.position = this.props.form.getFieldValue('position')
    }
    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 _scripts = fromJS(scripts).toJS()
    if (editItem && editItem.uuid) {
      _scripts = _scripts.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      _scripts.push(values)
    }
    let param = {
      func: 's_debug_sql',
      exec_type: 'y',
      LText: SettingUtils.getCustomDebugSql(_scripts)
    }
    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(result => {
      if (result.status) {
        this.setState({
          loading: false,
          scripts: _scripts,
          editItem: null
        })
        this.props.scriptsUpdate(_scripts)
        this.props.form.setFieldsValue({
          sql: ''
        })
      } else {
        this.setState({loading: false})
        Modal.error({
          title: result.message
        })
      }
    })
  }
  selectScript = (value, option) => {
    if (!value || !option) return
    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
    })
  }
  handleEdit = (record) => {
    const { type } = this.props
    this.setState({
      editItem: record
    })
    if (type === 'front') {
      this.props.form.setFieldsValue({
        sql: record.sql
      })
    } else {
      this.props.form.setFieldsValue({
        sql: record.sql,
        position: record.position || 'back'
      })
    }
    this.scrolltop()
  }
  scrolltop = () => {
    let node = document.getElementById('model-interface-form-body').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)
    }
  }
  changeScripts = (scripts) => {
    this.setState({scripts})
    this.props.scriptsUpdate(scripts)
  }
  handleStatus = (record) => {
    let scripts = fromJS(this.state.scripts).toJS()
    record.status = record.status === 'false' ? 'true' : 'false'
    scripts = scripts.map(item => {
      if (item.uuid === record.uuid) {
        return record
      } else {
        return item
      }
    })
    this.setState({scripts})
    this.props.scriptsUpdate(scripts)
  }
  handleDelete = (record) => {
    let scripts = fromJS(this.state.scripts).toJS()
    scripts = scripts.filter(item => item.uuid !== record.uuid)
    this.setState({ scripts })
    this.props.scriptsUpdate(scripts)
  }
  render() {
    const { setting, scripts, type } = this.props
    const { getFieldDecorator } = this.props.form
    const { scriptsColumns, systemScripts } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="modal-menu-setting-script">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={8}>
              <Form.Item label={'回调表名'} style={{whiteSpace: 'nowrap', margin: 0}}>
                {setting.cbTable}
              </Form.Item>
            </Col>
            <Col span={16}>
              <Form.Item label={'报错字段'} style={{margin: 0}}>
                ErrorCode, retmsg
              </Form.Item>
            </Col>
            <Col span={24} className="sqlfield">
              <Form.Item label={'可用字段'}>
                bid, loginuid, sessionuid, userid, username, fullname, appkey, time_id
              </Form.Item>
            </Col>
            {type === 'back' ? <Col span={8} style={{whiteSpace: 'nowrap'}}>
              <Form.Item style={{marginBottom: 0}} label="执行位置">
                {getFieldDecorator('position', {
                  initialValue: 'front'
                })(
                  <Radio.Group>
                    <Radio value="front">sql前</Radio>
                    <Radio value="back">sql后</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col> : null}
            <Col span={10} className="quick-add">
              <Form.Item label={'快捷添加'} style={{marginBottom: 0}}>
                <Select
                  allowClear
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={this.selectScript}
                >
                  {type === 'back' ? <Select.Option key="default" value={`declare @${setting.cbTable} table (mk_api_key nvarchar(100),mk_level nvarchar(10),mk_id nvarchar(50),mk_bid nvarchar(50))\n/*@${setting.cbTable}_data table (mk_level nvarchar(10),mk_id nvarchar(50),mk_bid nvarchar(50))*/`}>默认sql</Select.Option> : null}
                  {systemScripts.map((option, i) =>
                    <Select.Option style={{whiteSpace: 'normal'}} 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={{marginTop: 5, marginBottom: 15, marginLeft: 30}}>
                保存
              </Button>
              <Button onClick={this.handleCancel} style={{marginTop: 5, marginBottom: 15, marginLeft: 10}}>
                取消
              </Button>
            </Col>
            <Col span={24} className="sql">
              <Form.Item label={'sql'}>
                {getFieldDecorator('sql', {
                  initialValue: ''
                })(<CodeMirror />)}
              </Form.Item>
            </Col>
          </Row>
        </Form>
        <EditTable data={scripts} actions={['move']} columns={scriptsColumns} onChange={this.changeScripts}/>
      </div>
    )
  }
}
export default Form.create()(CustomForm)
src/menu/sysinterface/settingform/simplescript/index.scss
New file
@@ -0,0 +1,45 @@
.modal-menu-setting-script {
  .sqlfield {
    .ant-form-item {
      margin-bottom: 5px;
    }
    .ant-form-item-control {
      line-height: 24px;
    }
    .ant-form-item-label {
      line-height: 25px;
    }
    .ant-form-item-children {
      line-height: 22px;
    }
    .ant-col-sm-8 {
      width: 10.5%;
    }
    .ant-col-sm-16 {
      width: 89.5%;
    }
  }
  .quick-add {
    .ant-col-sm-8 {
      width: 26%;
    }
    .ant-col-sm-16 {
      width: 74%;
    }
  }
  .sql {
    .ant-col-sm-8 {
      width: 10.5%;
    }
    .ant-col-sm-16 {
      width: 89.5%;
      padding-top: 4px;
    }
    .CodeMirror {
      height: 350px;
    }
  }
  div.ant-typography {
    margin-bottom: 0;
  }
}
src/menu/sysinterface/settingform/utils.jsx
New file
@@ -0,0 +1,45 @@
export default class SettingUtils {
  /**
   * @description 生成前置或后置语句
   * @return {String}  scripts       脚本
   */
  static getCustomDebugSql (scripts) {
    let sql = ''
    let _customScript = ''
    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) select @ErrorCode='',@retmsg =''
        ${_customScript}
      `
    }
    _customScript = _customScript.replace(/@\$|\$@/ig, '')
    _customScript = _customScript.replace(/@userName@|@fullName@/ig, `''`)
    // 外联数据库替换
    if (window.GLOB.externalDatabase !== null) {
      _customScript = _customScript.replace(/@db@/ig, window.GLOB.externalDatabase)
    }
    if (_customScript) {
      sql = `/* sql 验证 */
        ${_customScript}
        aaa:
        if @ErrorCode!=''
          insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
      `
    }
    sql = sql.replace(/\n\s{8}/ig, '\n')
    console.info(sql)
    return sql
  }
}
src/mob/datasource/verifycard/index.jsx
@@ -150,7 +150,7 @@
        res.data.forEach(item => {
          let _item = {
            name: item.funcname,
            value: Utils.UnformatOptions(item.longparam)
            value: window.decodeURIComponent(window.atob(item.longparam))
          }
          _scripts.push(_item)
src/setupProxy.js
@@ -49,4 +49,13 @@
    }
    // cookieDomainRewrite: "http://localhost:3000"
  }))
  app.use(proxy('/trans', {
    target: `${host}/${service}trans`,
    secure: false,
    changeOrigin: true,
    pathRewrite: {
    '^/trans': '/'
    }
  }))
}
src/tabviews/commontable/index.jsx
@@ -8,8 +8,9 @@
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import Utils from '@/utils/utils.js'
import options from '@/store/options.js'
import UtilsDM from '@/utils/utils-datamanage.js'
import UtilsUpdate from '@/utils/utils-update.js'
import { updateCommonTable } from '@/utils/utils-update.js'
import asyncComponent from '@/utils/asyncComponent'
import asyncSpinComponent from '@/utils/asyncSpinComponent'
import MKEmitter from '@/utils/events.js'
@@ -64,6 +65,8 @@
    statFields: [],       // 合计字段
    statFValue: [],       // 合计值
    absFields: [],        // 绝对值字段
    loadCustomApi: true,  // 加载外部资源
    hasReqFields: false
  }
  /**
@@ -86,6 +89,7 @@
        config = JSON.parse(window.decodeURIComponent(window.atob(result.LongParam)))
        config.MenuID = this.props.MenuID
        config.MenuName = MenuName
        config.setting.MenuName = MenuName
      } catch (e) {
        console.warn('Parse Failure')
        config = ''
@@ -147,7 +151,7 @@
      }
      // 版本兼容
      config = UtilsUpdate.updateCommonTable(config)
      config = updateCommonTable(config)
      // 权限过滤
      if (this.props.menuType !== 'HS') {
@@ -210,7 +214,7 @@
      config.setting.execute = config.setting.default !== 'false'     // 默认sql是否执行,转为boolean 统一格式
      config.setting.customScript = ''                                // 自定义脚本
      if (config.setting.interType === 'system') {
      if (config.setting.interType === 'system' || (config.setting.interType === 'custom' && config.setting.requestMode === 'system')) {
        if (config.setting.scripts && config.setting.scripts.length > 0) {
          let _customScript = ''
          config.setting.scripts.forEach(item => {
@@ -338,13 +342,13 @@
      }
      let valid = true // 搜索条件必填验证, 初始搜索条件, 如通过上级透视,写入搜索条件
      let initSearch = config.search.map(item => {
        let _item = fromJS(item).toJS()
        if (_item.required === 'true' && !_item.initval) {
      let hasReqFields = false
      config.search.forEach(item => {
        if (item.required !== 'true') return
        if (!item.initval) {
          valid = false
        }
        return _item
        hasReqFields = true
      })
      this.setState({
@@ -360,11 +364,11 @@
        columns: _columns,
        arr_field: _arrField.join(','),
        BID: param && param.BID ? param.BID : '',
        search: Utils.initMainSearch(initSearch) // 搜索条件初始化(含有时间格式,需要转化)
        search: Utils.initMainSearch(config.search), // 搜索条件初始化(含有时间格式,需要转化)
        hasReqFields
      }, () => {
        if (config.setting.onload !== 'false' && valid) { // 初始化可加载
          this.loadmaindata()
          this.getStatFieldsValue()
          this.loadData()
        }
        this.setShortcut()
      })
@@ -422,12 +426,13 @@
    }
  }
  /**
   * @description 主表数据加载
   */
  async loadmaindata () {
    const { setting, arr_field, BIDs, search, orderBy, BID, pageIndex, pageSize, absFields } = this.state
    let requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
  loadData = () => {
    const { setting, search, BIDs, loadCustomApi, hasReqFields } = this.state
    let requireFields = []
    if (hasReqFields) {
      requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
    }
    this.setState({
      selectedData: [],
@@ -448,7 +453,198 @@
        duration: 3
      })
      return
    } else if (window.GLOB.systemType === 'production' && setting.interType === 'custom' && !setting.proInterface) {
      notification.warning({
        top: 92,
        message: '未设置正式系统地址!',
        duration: 3
      })
      return
    }
    if (setting.interType === 'custom' && loadCustomApi) {
      if (setting.execTime === 'once') {
        this.setState({loadCustomApi: false})
      }
      this.loadOutResource()
      if (setting.execType === 'async') {
        this.loadmaindata()
      }
    } else {
      this.loadmaindata()
    }
  }
  loadOutResource = () => {
    const { setting, search, BID } = this.state
    let param = UtilsDM.getPrevQueryParams(setting, search, BID, this.props.menuType)
    if (setting.execType === 'sync') {
      this.setState({
        loading: true
      })
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (res.mk_ex_invoke === 'false') {
          this.loadmaindata()
        } else {
          this.customOuterRequest(res)
        }
      } else {
        this.setState({
          loading: false
        })
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    }, () => {
      this.setState({
        loading: false
      })
    })
  }
  customOuterRequest = (result) => {
    const { setting } = this.state
    let url = ''
    if (window.GLOB.systemType === 'production') {
      url = setting.proInterface
    } else {
      url = setting.interface
    }
    let mkey = result.mk_api_key || ''
    delete result.mk_ex_invoke
    delete result.status
    delete result.message
    delete result.ErrCode
    delete result.ErrMesg
    delete result.mk_api_key
    let param = {}
    Object.keys(result).forEach(key => {
      key = key.replace(/^mk_/ig, '')
      param[key] = result[key]
    })
    Api.directRequest(url, setting.method, param).then(res => {
      if (typeof(res) !== 'object' || Array.isArray(res)) {
        let error = '未知的返回结果!'
        if (typeof(res) === 'string') {
          error = res.replace(/'/ig, '"')
        }
        let _result = {
          mk_api_key: mkey,
          $ErrCode: 'E',
          $ErrMesg: error
        }
        this.customCallbackRequest(_result)
      } else {
        res.mk_api_key = mkey
        this.customCallbackRequest(res)
      }
    }, (e) => {
      let _result = {
        mk_api_key: mkey,
        $ErrCode: 'E',
        $ErrMesg: e && e.statusText ? e.statusText : ''
      }
      this.customCallbackRequest(_result)
    })
  }
  customCallbackRequest = (result) => {
    const { setting } = this.state
    let errSql = ''
    if (result.$ErrCode === 'E') {
      errSql = `
        set @ErrorCode='E'
        set @retmsg='${result.$ErrMesg}'
      `
      delete result.$ErrCode
      delete result.$ErrMesg
    }
    let lines = UtilsDM.getCallBackSql(setting, result)
    let param = {}
    if (setting.callbackType === 'script') { // 使用自定义脚本
      let sql = lines.map(item => (`
        ${item.insert}
        ${item.selects.join(` union all
        `)}
      `))
      sql = sql.join('')
      param = UtilsDM.getCallBackQueryParams(setting, sql, errSql)
      if (this.state.BID) {
        param.BID = this.state.BID
      }
      if (this.props.menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
        param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
      }
    } else {
      param.func = 's_ex_result_back'
      param.s_ex_result = lines.map((item, index) => ({
        MenuID: this.state.config.MenuID,
        MenuName: this.state.config.MenuName,
        TableName: item.table,
        LongText: window.btoa(window.encodeURIComponent(`${item.insert}  ${item.selects.join(` union all `)}`)),
        Sort: index + 1
      }))
      if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
        let sql = lines.map(item => (`
          ${item.insert}
          ${item.selects.join(` union all
          `)}
        `))
        sql = sql.join('')
        console.info(sql.replace(/\n\s{10}/ig, '\n'))
      }
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        this.loadmaindata()
      } else {
        this.setState({
          loading: false
        })
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    }, () => {
      this.setState({
        loading: false
      })
    })
  }
  /**
   * @description 主表数据加载
   */
  async loadmaindata () {
    const { setting, arr_field, search, orderBy, BID, pageIndex, pageSize, absFields } = this.state
    this.setState({
      loading: true
@@ -462,6 +658,9 @@
    }
    let result = await Api.genericInterface(param)
    this.getStatFieldsValue()
    if (result.status) {
      this.setState({
        data: result.data.map((item, index) => {
@@ -567,12 +766,7 @@
  getStatFieldsValue = () => {
    const { setting, search, BID, orderBy, statFields } = this.state
    if (statFields.length === 0 || setting.interType !== 'system' || !setting.dataresource) return
    let requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
    if (requireFields.length > 0) {
      return
    }
    if (statFields.length === 0 || !(setting.interType === 'system' || (setting.interType === 'custom' && setting.requestMode === 'system')) || !setting.dataresource) return
    let _orderBy = orderBy || setting.order
    let param = UtilsDM.getStatQueryDataParams(setting, statFields, search, _orderBy, BID, this.props.menuType)
@@ -627,8 +821,7 @@
        search: searches,
        setting: {...setting, onload: 'true'}
      }, () => {
        this.loadmaindata()
        this.getStatFieldsValue()
        this.loadData()
      })
    } else {
      MKEmitter.emit('resetTable', this.props.MenuID + 'mainTable') // 列表重置
@@ -636,8 +829,7 @@
        pageIndex: 1,
        search: searches
      }, () => {
        this.loadmaindata()
        this.getStatFieldsValue()
        this.loadData()
      })
    }
  }
@@ -659,7 +851,7 @@
      pageSize: pagination.pageSize,
      orderBy: (sorter.field && sorter.order) ? `${sorter.field} ${sorter.order}` : ''
    }, () => {
      this.loadmaindata()
      this.loadData()
    })
  }
@@ -672,13 +864,11 @@
      this.setState({
        pageIndex: 1
      }, () => {
        this.loadmaindata()
        this.getStatFieldsValue()
        this.loadData()
      })
    } else {
      MKEmitter.emit('resetTable', this.props.MenuID + 'mainTable', 'false') // 列表重置
      this.loadmaindata()
      this.getStatFieldsValue()
      this.loadData()
    }
  }
src/tabviews/custom/components/card/data-card/asyncButtonComponent.jsx
File was deleted
src/tabviews/custom/components/card/prop-card/asyncButtonComponent.jsx
File was deleted
src/tabviews/custom/components/card/table-card/asyncButtonComponent.jsx
File was deleted
src/tabviews/custom/components/code/sand-box/index.jsx
New file
@@ -0,0 +1,222 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Spin, notification } from 'antd'
import Api from '@/api'
import UtilsDM from '@/utils/utils-datamanage.js'
import MKEmitter from '@/utils/events.js'
import './index.scss'
class SandBoxComponent extends Component {
  static propTpyes = {
    BID: PropTypes.any,              // 父级Id
    data: PropTypes.array,           // 统一查询数据
    config: PropTypes.object,        // 组件配置信息
    mainSearch: PropTypes.any,       // 外层搜索条件
    menuType: PropTypes.any,         // 菜单类型
  }
  state = {
    BID: '',                   // 上级ID
    config: null,              // 图表配置信息
    loading: false,            // 数据加载状态
    sync: false,               // 是否统一请求数据
    data: {},                  // 数据
    html: '',
  }
  UNSAFE_componentWillMount () {
    const { data, initdata, BID } = this.props
    let _config = fromJS(this.props.config).toJS()
    let _data = {}
    let _sync = false
    if (_config.setting && _config.wrap.datatype !== 'static') {
      _sync = _config.setting.sync === 'true'
      if (_sync && data) {
        _data = data[_config.dataName] || {}
        if (_data && Array.isArray(_data)) {
          _data = _data[0] || {}
        }
        _sync = false
      } else if (_sync && initdata) {
        _data = initdata || {}
        if (_data && Array.isArray(_data)) {
          _data = _data[0] || {}
        }
        _sync = false
      }
    } else {
      _data = {}
    }
    if (_config.css) {
      let ele = document.createElement('style')
      ele.innerHTML = _config.css
      document.getElementsByTagName('head')[0].appendChild(ele)
    }
    this.setState({
      sync: _sync,
      data: _data,
      BID: BID || '',
      config: _config,
      arr_field: _config.columns.map(col => col.field).join(','),
    }, () => {
      if (_config.wrap.datatype !== 'static' && _config.setting && _config.setting.sync !== 'true' && _config.setting.onload === 'true') {
        this.loadData()
      }
      this.renderView()
    })
  }
  componentDidMount () {
    MKEmitter.addListener('reloadData', this.reloadData)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('reloadData', this.reloadData)
  }
  /**
   * @description 图表数据更新,刷新内容
   */
  UNSAFE_componentWillReceiveProps (nextProps) {
    const { sync, config } = this.state
    if (sync && !is(fromJS(this.props.data), fromJS(nextProps.data))) {
      let _data = {}
      if (nextProps.data && nextProps.data[config.dataName]) {
        _data = nextProps.data[config.dataName]
        if (_data && Array.isArray(_data)) {
          _data = _data[0]
        }
      }
      this.setState({sync: false, data: _data}, () => {
        this.renderView()
      })
    } else if (nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
      if (config.wrap.datatype !== 'static' && config.setting.syncRefresh === 'true') {
        this.setState({}, () => {
          this.loadData()
        })
      }
    }
  }
  reloadData = (menuId) => {
    const { config } = this.state
    if (menuId !== config.uuid) return
    this.loadData()
  }
  async loadData () {
    const { mainSearch, menuType } = this.props
    const { config, arr_field, BID } = this.state
    if (config.wrap.datatype === 'static') {
      this.setState({
        data: {},
        loading: false
      })
      return
    } else if (config.setting.supModule && !BID) { // BID 不存在时,不做查询
      this.setState({
        data: {},
        loading: false
      })
      return
    }
    let searches = []
    if (mainSearch && mainSearch.length > 0) { // 主表搜索条件
      searches = mainSearch
    }
    this.setState({
      loading: true
    })
    let _orderBy = config.setting.order || ''
    let param = UtilsDM.getQueryDataParams(config.setting, arr_field, searches, _orderBy, 1, 1, BID, menuType)
    let result = await Api.genericInterface(param)
    if (result.status) {
      let _data = result.data && result.data[0] ? result.data[0] : {}
      this.setState({
        data: _data,
        loading: false
      }, () => {
        this.renderView()
      })
    } else {
      this.setState({
        loading: false
      })
      notification.error({
        top: 92,
        message: result.message,
        duration: 10
      })
    }
  }
  renderView = () => {
    const { data } = this.state
    const { html, js, wrap, columns } = this.state.config
    let _html = html
    if (_html && wrap.datatype !== 'static') {
      columns.forEach(col => {
        if (col.field) {
          let val = (data[col.field] || data[col.field] === 0) ? data[col.field] : ''
          let reg = new RegExp('@' + col.field + '@', 'ig')
          _html = _html.replace(reg, val)
        }
      })
    }
    this.setState({html: _html}, () => {
      if (js) {
        try {
          // eslint-disable-next-line no-eval
          eval(js)
        } catch {
          console.warn('JS 执行失败!')
        }
      }
    })
  }
  render() {
    const { config, loading, html } = this.state
    return (
      <div className="custom-sand-box" style={{...config.style}}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
            <Spin />
          </div> : null
        }
        <div dangerouslySetInnerHTML={{ __html: html }}></div>
      </div>
    )
  }
}
export default SandBoxComponent
src/tabviews/custom/components/code/sand-box/index.scss
New file
@@ -0,0 +1,34 @@
.custom-sand-box {
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  position: relative;
  .loading-mask {
    position: absolute;
    left: 40px;
    top: 0;
    right: 40px;
    bottom: 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: justify;
    z-index: 1;
    .ant-spin-blur {
      position: absolute;
      width: 100%;
      height: 100%;
      opacity: 0.5;
      background: #ffffff;
    }
  }
}
.custom-sand-box::after {
  content: ' ';
  display: block;
  clear: both;
}
src/tabviews/custom/components/editor/braft-editor/index.jsx
New file
@@ -0,0 +1,191 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Spin, notification } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import Api from '@/api'
import UtilsDM from '@/utils/utils-datamanage.js'
import MKEmitter from '@/utils/events.js'
import './index.scss'
const BraftContent = asyncComponent(() => import('@/tabviews/custom/components/share/braftContent'))
const NormalHeader = asyncComponent(() => import('@/tabviews/custom/components/share/normalheader'))
class BraftEditorContent extends Component {
  static propTpyes = {
    BID: PropTypes.any,              // 父级Id
    data: PropTypes.array,           // 统一查询数据
    config: PropTypes.object,        // 组件配置信息
    mainSearch: PropTypes.any,       // 外层搜索条件
    menuType: PropTypes.any,         // 菜单类型
  }
  state = {
    BID: '',                   // 上级ID
    config: null,              // 图表配置信息
    loading: false,            // 数据加载状态
    sync: false,               // 是否统一请求数据
    data: {}                   // 数据
  }
  UNSAFE_componentWillMount () {
    const { data, initdata, BID } = this.props
    let _config = fromJS(this.props.config).toJS()
    let _data = {}
    let _sync = false
    if (_config.setting && _config.wrap.datatype !== 'static') {
      _sync = _config.setting.sync === 'true'
      if (_sync && data) {
        _data = data[_config.dataName] || {}
        if (_data && Array.isArray(_data)) {
          _data = _data[0] || {}
        }
        _sync = false
      } else if (_sync && initdata) {
        _data = initdata || {}
        if (_data && Array.isArray(_data)) {
          _data = _data[0] || {}
        }
        _sync = false
      }
    } else {
      _data = {}
    }
    this.setState({
      sync: _sync,
      data: _data,
      BID: BID || '',
      config: _config,
      arr_field: _config.columns.map(col => col.field).join(','),
    }, () => {
      if (_config.wrap.datatype !== 'static' && _config.setting && _config.setting.sync !== 'true' && _config.setting.onload === 'true') {
        this.loadData()
      }
    })
  }
  componentDidMount () {
    MKEmitter.addListener('reloadData', this.reloadData)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('reloadData', this.reloadData)
  }
  /**
   * @description 图表数据更新,刷新内容
   */
  UNSAFE_componentWillReceiveProps (nextProps) {
    const { sync, config } = this.state
    if (sync && !is(fromJS(this.props.data), fromJS(nextProps.data))) {
      let _data = {}
      if (nextProps.data && nextProps.data[config.dataName]) {
        _data = nextProps.data[config.dataName]
        if (_data && Array.isArray(_data)) {
          _data = _data[0]
        }
      }
      this.setState({sync: false, data: _data})
    } else if (nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
      if (config.wrap.datatype !== 'static' && config.setting.syncRefresh === 'true') {
        this.setState({}, () => {
          this.loadData()
        })
      }
    }
  }
  reloadData = (menuId) => {
    const { config } = this.state
    if (menuId !== config.uuid) return
    this.loadData()
  }
  async loadData () {
    const { mainSearch, menuType } = this.props
    const { config, arr_field, BID } = this.state
    if (config.wrap.datatype === 'static') {
      this.setState({
        data: {},
        loading: false
      })
      return
    } else if (config.setting.supModule && !BID) { // BID 不存在时,不做查询
      this.setState({
        data: {},
        loading: false
      })
      return
    }
    let searches = []
    if (mainSearch && mainSearch.length > 0) { // 主表搜索条件
      searches = mainSearch
    }
    this.setState({
      loading: true
    })
    let _orderBy = config.setting.order || ''
    let param = UtilsDM.getQueryDataParams(config.setting, arr_field, searches, _orderBy, 1, 1, BID, menuType)
    let result = await Api.genericInterface(param)
    if (result.status) {
      let _data = result.data && result.data[0] ? result.data[0] : {}
      this.setState({
        data: _data,
        loading: false
      })
    } else {
      this.setState({
        loading: false
      })
      notification.error({
        top: 92,
        message: result.message,
        duration: 10
      })
    }
  }
  render() {
    const { config, loading, data } = this.state
    return (
      <div className="custom-braft-editor-box" style={{...config.style}}>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
            <Spin />
          </div> : null
        }
        <NormalHeader config={config}/>
        <BraftContent
          value={config.wrap.datatype !== 'static' ? (data[config.wrap.field] || '') : config.html}
          encryption={config.wrap.datatype !== 'static' ? config.wrap.encryption : 'false'}
        />
      </div>
    )
  }
}
export default BraftEditorContent
src/tabviews/custom/components/editor/braft-editor/index.scss
New file
@@ -0,0 +1,35 @@
.custom-braft-editor-box {
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 20px;
  position: relative;
  .loading-mask {
    position: absolute;
    left: 40px;
    top: 0;
    right: 40px;
    bottom: 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: justify;
    z-index: 1;
    .ant-spin-blur {
      position: absolute;
      width: 100%;
      height: 100%;
      opacity: 0.5;
      background: #ffffff;
    }
  }
}
.custom-braft-editor-box::after {
  content: ' ';
  display: block;
  clear: both;
}
src/tabviews/custom/components/group/normal-group/index.jsx
@@ -18,6 +18,8 @@
const DataCard = asyncComponent(() => import('@/tabviews/custom/components/card/data-card'))
const TableCard = asyncComponent(() => import('@/tabviews/custom/components/card/table-card'))
const PropCard = asyncComponent(() => import('@/tabviews/custom/components/card/prop-card'))
const BraftEditor = asyncComponent(() => import('@/tabviews/custom/components/editor/braft-editor'))
const SandBox = asyncComponent(() => import('@/tabviews/custom/components/code/sand-box'))
class TabTransfer extends Component {
  static propTpyes = {
@@ -45,7 +47,7 @@
      if (item.type === 'tabs') return
      if (!item.setting || item.setting.interType !== 'system') return
      if (!item.format || (item.subtype === 'propcard' && item.wrap.datatype === 'static')) return
      if (!item.format) return
      if (item.dataName && (!item.pageable || (item.pageable && !item.setting.laypage)) && item.setting.onload === 'true' && item.setting.sync === 'true') {
        let param = this.getDefaultParam(item, _mainSearch)
@@ -267,6 +269,18 @@
            <TableCard config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'editor') {
        return (
          <Col span={item.width} key={item.uuid}>
            <BraftEditor config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'code') {
        return (
          <Col span={item.width} key={item.uuid}>
            <SandBox config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else {
        return null
      }
src/tabviews/custom/components/share/braftContent/index.jsx
New file
@@ -0,0 +1,58 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import './index.scss'
class BraftContent extends Component {
  static propTpyes = {
    value: PropTypes.any,       // 内容
    encryption: PropTypes.any,  // 是否解码
  }
  state = {
    html: ''
  }
  UNSAFE_componentWillMount () {
    const { encryption, value } = this.props
    let html = value
    if (encryption === 'true' && html) {
      try {
        html = window.decodeURIComponent(window.atob(html))
      } catch {
        html = value
      }
    }
    this.setState({html})
  }
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (!is(fromJS(this.props), fromJS(nextProps))) {
      const { encryption, value } = nextProps
      let html = value
      if (encryption === 'true' && html) {
        try {
          html = window.decodeURIComponent(window.atob(html))
        } catch {
          html = value
        }
      }
      this.setState({html})
    }
  }
  render() {
    const { html } = this.state
    return (
      <div className="braft-content" dangerouslySetInnerHTML={{ __html: html }}></div>
    )
  }
}
export default BraftContent
src/tabviews/custom/components/share/braftContent/index.scss
New file
@@ -0,0 +1,26 @@
.braft-content {
  .media-wrap {
    max-width: 100%;
  }
  img {
    max-width: 100%;
  }
  video {
    max-width: 100%;
    width: 100%;
  }
  table {
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0;
    margin: 10px 0px;
    tr:first-child {
      background-color: #f0f0f0;
    }
    td, th {
      padding: 5px 14px;
      font-size: 16px;
      border: 1px solid #ddd;
    }
  }
}
src/tabviews/custom/components/share/normalTable/index.jsx
@@ -248,6 +248,10 @@
        content = md5(content)
      }
      if (col.rowspan === 'true') {
        resProps.rowSpan = record['$$' + col.field]
      }
      if (col.linkThdMenu || col.linkurl) {
        content = (
          <div>
@@ -689,13 +693,13 @@
        let preItem = data[index - 1]
        rowspans.forEach((cell, i) => {
          if (i === 0) {
            if ((item[cell] || item[cell] === 0) && preItem[cell] === item[cell]) {
            if (preItem[cell] === item[cell]) {
              item['$' + cell] = preItem['$' + cell] + 1
            } else {
              item['$' + cell] = 1
            }
          } else {
            if ((item[cell] || item[cell] === 0) && preItem[cell] === item[cell]) {
            if (preItem[cell] === item[cell]) {
              item['$' + cell] = preItem['$' + cell] + 1
            } else {
              item['$' + cell] = 1
src/tabviews/custom/components/share/tabtransfer/index.jsx
@@ -21,6 +21,8 @@
const TableCard = asyncComponent(() => import('@/tabviews/custom/components/card/table-card'))
const PropCard = asyncComponent(() => import('@/tabviews/custom/components/card/prop-card'))
const NormalGroup = asyncComponent(() => import('@/tabviews/custom/components/group/normal-group'))
const BraftEditor = asyncComponent(() => import('@/tabviews/custom/components/editor/braft-editor'))
const SandBox = asyncComponent(() => import('@/tabviews/custom/components/code/sand-box'))
class TabTransfer extends Component {
  static propTpyes = {
@@ -58,7 +60,7 @@
      if (item.type === 'tabs' || item.type === 'group') return
      if (!item.setting || item.setting.interType !== 'system') return
      if (!item.format || (item.subtype === 'propcard' && item.wrap.datatype === 'static')) return
      if (!item.format) return
      if (item.dataName && (!item.pageable || (item.pageable && !item.setting.laypage)) && item.setting.onload === 'true' && item.setting.sync === 'true') {
        let param = this.getDefaultParam(item, _mainSearch)
@@ -304,6 +306,18 @@
            <NormalGroup config={item} bids={bids} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'editor') {
        return (
          <Col span={item.width} key={item.uuid}>
            <BraftEditor config={item} data={data} BID={BID} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'code') {
        return (
          <Col span={item.width} key={item.uuid}>
            <SandBox config={item} data={data} BID={BID} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else {
        return null
      }
src/tabviews/custom/index.jsx
@@ -11,6 +11,7 @@
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import Utils from '@/utils/utils.js'
import UtilsDM from '@/utils/utils-datamanage.js'
import asyncComponent from '@/utils/asyncComponent'
import MKEmitter from '@/utils/events.js'
import NotFount from '@/components/404'
@@ -26,6 +27,8 @@
const MainSearch = asyncComponent(() => import('@/tabviews/zshare/topSearch'))
const NormalTable = asyncComponent(() => import('./components/table/normal-table'))
const NormalGroup = asyncComponent(() => import('./components/group/normal-group'))
const BraftEditor = asyncComponent(() => import('./components/editor/braft-editor'))
const SandBox = asyncComponent(() => import('./components/code/sand-box'))
const SettingComponent = asyncComponent(() => import('@/tabviews/zshare/settingcomponent'))
const PagemsgComponent = asyncComponent(() => import('@/tabviews/zshare/pageMessage'))
@@ -178,6 +181,8 @@
        if (!this.props.Tab) {
          this.setShortcut()
        }
        this.loadData()
      })
    } else {
      this.setState({
@@ -225,6 +230,178 @@
        return false
      })
    }
  }
  loadData = () => {
    const { config } = this.state
    if (!config.interfaces || config.interfaces.length === 0) return
    let inters = []
    config.interfaces.forEach(item => {
      if (item.status !== 'true') return
      if (window.GLOB.systemType === 'production' && !item.proInterface) {
        notification.warning({
          top: 92,
          message: `《${item.name}》未设置正式系统地址!`,
          duration: 3
        })
        return
      }
      inters.push(item)
    })
    if (inters.length > 0) {
      this.loadOutResource(inters)
    }
  }
  loadOutResource = (inters) => {
    let setting = inters.shift()
    let param = UtilsDM.getPrevQueryParams(setting, [], this.state.BID, this.props.menuType)
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (res.mk_ex_invoke === 'false') {
          if (inters.length > 0) {
            this.loadOutResource(inters)
          }
        } else {
          this.customOuterRequest(res, setting, inters)
        }
      } else {
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    })
  }
  customOuterRequest = (result, setting, inters) => {
    let url = ''
    if (window.GLOB.systemType === 'production') {
      url = setting.proInterface
    } else {
      url = setting.interface
    }
    let mkey = result.mk_api_key || ''
    delete result.mk_ex_invoke
    delete result.status
    delete result.message
    delete result.ErrCode
    delete result.ErrMesg
    delete result.mk_api_key
    let param = {}
    Object.keys(result).forEach(key => {
      key = key.replace(/^mk_/ig, '')
      param[key] = result[key]
    })
    Api.directRequest(url, setting.method, param).then(res => {
      if (typeof(res) !== 'object' || Array.isArray(res)) {
        let error = '未知的返回结果!'
        if (typeof(res) === 'string') {
          error = res.replace(/'/ig, '"')
        }
        let _result = {
          mk_api_key: mkey,
          $ErrCode: 'E',
          $ErrMesg: error
        }
        this.customCallbackRequest(_result, setting, inters)
      } else {
        res.mk_api_key = mkey
        this.customCallbackRequest(res, setting, inters)
      }
    }, (e) => {
      let _result = {
        mk_api_key: mkey,
        $ErrCode: 'E',
        $ErrMesg: e && e.statusText ? e.statusText : ''
      }
      this.customCallbackRequest(_result, setting, inters)
    })
  }
  customCallbackRequest = (result, setting, inters) => {
    let errSql = ''
    if (result.$ErrCode === 'E') {
      errSql = `
        set @ErrorCode='E'
        set @retmsg='${result.$ErrMesg}'
      `
      delete result.$ErrCode
      delete result.$ErrMesg
    }
    let lines = UtilsDM.getCallBackSql(setting, result)
    let param = {}
    if (setting.callbackType === 'script') { // 使用自定义脚本
      let sql = lines.map(item => (`
        ${item.insert}
        ${item.selects.join(` union all
        `)}
      `))
      sql = sql.join('')
      param = UtilsDM.getCallBackQueryParams(setting, sql, errSql)
      if (this.state.BID) {
        param.BID = this.state.BID
      }
      if (this.props.menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
        param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
      }
    } else {
      param.func = 's_ex_result_back'
      param.s_ex_result = lines.map((item, index) => ({
        MenuID: this.props.MenuID || '',
        MenuName: this.props.MenuName || '',
        TableName: item.table,
        LongText: window.btoa(window.encodeURIComponent(`${item.insert}  ${item.selects.join(` union all `)}`)),
        Sort: index + 1
      }))
      if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
        let sql = lines.map(item => (`
          ${item.insert}
          ${item.selects.join(` union all
          `)}
        `))
        sql = sql.join('')
        console.info(sql.replace(/\n\s{10}/ig, '\n'))
      }
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (inters.length > 0) {
          this.loadOutResource(inters)
        }
      } else {
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    })
  }
  filterComponent = (components, roleId, permAction, permMenus) => {
@@ -482,8 +659,12 @@
        return component
      }
      if (['propcard', 'brafteditor', 'sandbox'].includes(component.subtype) && component.wrap.datatype === 'static') {
        component.format = ''
      }
      if (!component.setting) return component // 不使用系统函数时
      if (!component.format || (component.subtype === 'propcard' && component.wrap.datatype === 'static')) return component // 没有动态数据  数据格式 array 或 object
      if (!component.format) return component  // 没有动态数据  数据格式 array 或 object
      if (component.setting.interType !== 'system') { // 不使用系统函数时
        component.setting.sync = 'false'
        component.setting.laypage = component.setting.laypage === 'true'
@@ -822,6 +1003,18 @@
            <NormalGroup config={item} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'editor') {
        return (
          <Col span={item.width} key={item.uuid}>
            <BraftEditor config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'code') {
        return (
          <Col span={item.width} key={item.uuid}>
            <SandBox config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else {
        return null
      }
src/tabviews/formtab/actionList/index.jsx
@@ -2,7 +2,7 @@
import PropTypes from 'prop-types'
import moment from 'moment'
import { Button, Modal, notification, message } from 'antd'
import Utils from '@/utils/utils.js'
import Utils, { getSysDefaultSql } from '@/utils/utils.js'
import options from '@/store/options.js'
import Api from '@/api'
import './index.scss'
@@ -121,7 +121,7 @@
      if (btn.sql && btn.sqlType === 'insert') { // 系统函数添加时,生成uuid
        param.ID = Utils.getguid()
        param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, data, []) // 数据源
        param.LText = getSysDefaultSql(btn, setting, formdata, param, data, []) // 数据源
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        param.secretkey = Utils.encrypt('', param.timestamp)
        param.LText = Utils.formatOptions(param.LText)
@@ -129,7 +129,7 @@
        _primaryId = param.ID
      } else if (btn.sql && btn.sqlType === 'insertOrUpdate') { // 系统函数添加或修改时
        param.ID = primaryId || Utils.getguid()
        param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, data, []) // 数据源
        param.LText = getSysDefaultSql(btn, setting, formdata, param, data, []) // 数据源
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        param.secretkey = Utils.encrypt('', param.timestamp)
        param.LText = Utils.formatOptions(param.LText)
@@ -137,7 +137,7 @@
        _primaryId = param.ID
      } else if (btn.sql) {
        param.ID = primaryId
        param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, data, []) // 数据源
        param.LText = getSysDefaultSql(btn, setting, formdata, param, data, []) // 数据源
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        param.secretkey = Utils.encrypt('', param.timestamp)
        param.LText = Utils.formatOptions(param.LText)
src/tabviews/formtab/index.jsx
@@ -673,16 +673,15 @@
                        {_tab.label}
                      </span>
                    } key={`${index}`}>
                      {_tab.type === 'SubTable' ?
                        <SubTable
                          Tab={_tab}
                          MenuID={_tab.linkTab}
                          SupMenuID={this.props.MenuID}
                          ContainerId={this.state.ContainerId}
                          BID={this.state.BIDs[_tab.supMenu] || ''}
                          BData={this.state.BIDs[_tab.supMenu + 'data'] || ''}
                          handleTableId={this.handleTableId}
                        /> : null}
                      <SubTable
                        Tab={_tab}
                        MenuID={_tab.linkTab}
                        SupMenuID={this.props.MenuID}
                        ContainerId={this.state.ContainerId}
                        BID={this.state.BIDs[_tab.supMenu] || ''}
                        BData={this.state.BIDs[_tab.supMenu + 'data'] || ''}
                        handleTableId={this.handleTableId}
                      />
                    </TabPane>
                  )
                })}
src/tabviews/scriptmanage/actionList/index.jsx
@@ -33,10 +33,6 @@
    configMap: {}
  }
  refreshdata = (item, type) => {
    this.props.refreshdata(item, type)
  }
  /**
   * @description 触发按钮操作
   */
@@ -124,8 +120,6 @@
      LText: values.LongParam
    }
    param.LText = window.btoa(window.encodeURIComponent(JSON.stringify(param.LText)))
    if (btn.sqlType === 'delete') {
      param.LText = window.GLOB.appkey || ''
      param.Remark = ''
@@ -179,7 +173,9 @@
      })
    }
    this.refreshdata(btn, 'success')
    if (btn.execSuccess !== 'never') {
      this.props.refreshdata()
    }
  }
  /**
@@ -209,8 +205,6 @@
    } else if (res.ErrCode === 'NM') {
      message.error(res.message || res.ErrMesg)
    }
    this.refreshdata(btn, 'error')
  }
src/tabviews/scriptmanage/actionList/index.scss
@@ -15,30 +15,3 @@
    top: calc(50vh - 70px);
  }
}
// 设置模态框样式,规定最大最小高度,重置滚动条
.action-modal {
  .ant-modal {
    max-width: 95vw;
  }
  .ant-modal-body {
    max-height: calc(100vh - 235px);
    min-height: 150px;
    overflow-y: auto;
    padding-bottom: 35px;
  }
  .ant-modal-body::-webkit-scrollbar {
    width: 10px;
    height: 10px;
  }
  .ant-modal-body::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .ant-modal-body::-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);
  }
}
src/tabviews/scriptmanage/config.jsx
@@ -63,7 +63,7 @@
      {"label":"函数","field":"func","type":"text","initval":"","readonly":"false","required":"true","hidden":"false","readin":"true","fieldlength":50,"regular":"funcname","supField":"","blacklist":[],"uuid":"1587006164634l397q15t49u2pfq02f5"},
      {"label":"排序","field":"Sort","type":"number","initval":0,"decimal":0,"min":"","max":"","readonly":"false","hidden":"false","readin":"true","supField":"","blacklist":[],"uuid":"15870101796149403f2pqfpviuo415m2"},
      {"label":"描述","field":"Remark","type":"textarea","initval":"","readonly":"false","required":"false","hidden":"false","readin":"true","fieldlength":512,"maxRows":6,"supField":"","blacklist":[],"uuid":"1587006199263k8hm45cmtomgu6hd881"},
      {"label":"脚本","field":"LongParam","type":"textarea","initval":"","readonly":"false","required":"true","hidden":"false","readin":"true","fieldlength":8000,"maxRows":20,"supField":"","blacklist":[],"uuid":"1587006209935qbkle15h4d9i9lg9tcu"}
      {"label":"脚本","field":"LongParam","type":"textarea","initval":"","readonly":"false","required":"true","encryption":"true","hidden":"false","readin":"true","fieldlength":12000,"maxRows":20,"supField":"","blacklist":[],"uuid":"1587006209935qbkle15h4d9i9lg9tcu"}
    ]
  },
  '1587007258155ut4nbggg4r66t9uhut2': {
@@ -84,7 +84,7 @@
      {"label":"函数","field":"func","type":"text","initval":"","readonly":"false","required":"true","hidden":"false","readin":"true","fieldlength":50,"regular":"funcname","supField":"","blacklist":[],"uuid":"1587006164634l397q15t49u2pfq02f5"},
      {"label":"排序","field":"Sort","type":"number","initval":0,"decimal":0,"min":"","max":"","readonly":"false","hidden":"false","readin":"true","supField":"","blacklist":[],"uuid":"1587010196675i9m6ie3tv9kg2rhgfi0"},
      {"label":"描述","field":"Remark","type":"textarea","initval":"","readonly":"false","required":"false","hidden":"false","readin":"true","fieldlength":512,"maxRows":6,"supField":"","blacklist":[],"uuid":"1587006199263k8hm45cmtomgu6hd881"},
      {"label":"脚本","field":"LongParam","type":"textarea","initval":"","readonly":"false","required":"true","hidden":"false","readin":"true","fieldlength":8000,"maxRows":20,"supField":"","blacklist":[],"uuid":"1587006209935qbkle15h4d9i9lg9tcu"}
      {"label":"脚本","field":"LongParam","type":"textarea","initval":"","readonly":"false","required":"true","encryption":"true","hidden":"false","readin":"true","fieldlength":12000,"maxRows":20,"supField":"","blacklist":[],"uuid":"1587006209935qbkle15h4d9i9lg9tcu"}
    ]
  }
}
src/tabviews/scriptmanage/index.jsx
@@ -134,7 +134,7 @@
    if (result.status) {
      this.setState({
        data: result.data.map((item, index) => {
          item.LongParam = Utils.UnformatOptions(item.LongParam)
          // item.LongParam = this.UnformatOptions(item.LongParam)
          item.key = index
          return item
        }),
@@ -152,6 +152,75 @@
      })
    }
  }
  // UnformatOptions = (value) => {
  //   if (!value) return ''
  //   let salt = 'minKe' // 盐值
  //   let _value = ''
  //   const formatKeys = [
  //     { key: 'select', value: ' msltk ' },
  //     { key: 'from', value: ' mfrmk ' },
  //     { key: 'where', value: ' mwhrk ' },
  //     { key: 'order by', value: ' modbk ' },
  //     { key: 'asc', value: ' modack ' },
  //     { key: 'desc', value: ' moddesk ' },
  //     { key: 'top', value: ' mtpk ' },
  //     { key: 'like', value: ' mlkk ' },
  //     { key: 'not like', value: ' mnlkk ' },
  //     { key: 'between', value: ' mbtnk ' },
  //     { key: 'and', value: ' madk ' },
  //     { key: 'insert', value: ' mistk ' },
  //     { key: 'into', value: ' mitk ' },
  //     { key: 'update', value: ' muptk ' },
  //     { key: 'delete', value: ' mdelk ' },
  //     { key: 'begin', value: ' mbgink ' },
  //     { key: 'end', value: ' medk ' },
  //     { key: 'if', value: ' mefk ' },
  //     { key: 'while', value: ' mwilk ' },
  //     { key: 'create', value: ' mcrtk ' },
  //     { key: 'alter', value: ' matek ' },
  //     { key: 'len', value: ' mlnk ' },
  //     { key: 'left', value: ' mlftk ' },
  //     { key: 'right', value: ' mritk ' },
  //     { key: 'union', value: ' munok ' },
  //     { key: 'varchar', value: ' mvcrk ' },
  //     { key: 'getdate', value: ' mgtdtk ' },
  //     { key: 'TRY', value: ' mtryonek ' },
  //     { key: 'TRAN', value: ' mtrnk ' },
  //     { key: 'goto', value: ' mgtk ' },
  //     { key: 'set', value: ' mstk ' },
  //     { key: 'ROLLBACK', value: ' mrlbkk ' }
  //   ]
  //   try {
  //     try {
  //       _value = JSON.parse(window.decodeURIComponent(window.atob(value)))
  //     } catch {
  //       _value = ''
  //     }
  //     if (!_value) {
  //       _value = window.atob(value)
  //       _value = _value.replace(salt, '')
  //       _value = window.decodeURIComponent(window.atob(_value))
  //       _value = _value.replace(/\smpercent\s/g, '%')
  //       formatKeys.forEach(item => {
  //         let reg = new RegExp(item.value, 'g')
  //         _value = _value.replace(reg, ' ' + item.key + ' ')
  //       })
  //       _value = _value.replace(/\s\n\s/ig, '\n')
  //       _value = _value.replace(/(^\s+|\s+$)/ig, '')
  //     }
  //   } catch {
  //     console.warn('UnFormat Failure')
  //     _value = ''
  //   }
  //   return _value
  // }
  /**
   * @description 获取系统存储过程 sPC_Get_TableData 的参数
@@ -255,21 +324,6 @@
  }
  /**
   * @description 按钮操作完成后(成功或失败),页面刷新,重置页码及选择项
   */
  refreshbyaction = (btn, type) => {
    if (btn.execSuccess === 'grid' && type === 'success') {
      this.reloadtable()
    } else if (btn.execError === 'grid' && type === 'error') {
      this.reloadtable()
    } else if (btn.execSuccess === 'view' && type === 'success') {
      this.reloadview()
    } else if (btn.execError === 'view' && type === 'error') {
      this.reloadview()
    }
  }
  /**
   * @description 表格选择项切换
   */
  changeSelectedData = (selectedData) => {
@@ -332,7 +386,7 @@
          MenuID={this.props.MenuID}
          selectedData={selectedData}
          ContainerId={this.state.ContainerId}
          refreshdata={this.refreshbyaction}
          refreshdata={this.reloadtable}
        />
        <div className="main-table-box">
          {this.state.data && this.state.data.length > 0 ?
src/tabviews/subtable/index.jsx
@@ -8,8 +8,9 @@
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import Utils from '@/utils/utils.js'
import options from '@/store/options.js'
import UtilsDM from '@/utils/utils-datamanage.js'
import UtilsUpdate from '@/utils/utils-update.js'
import { updateSubTable } from '@/utils/utils-update.js'
import { modifyTabview } from '@/store/action'
import asyncComponent from '@/utils/asyncComponent'
import asyncSpinComponent from '@/utils/asyncSpinComponent'
@@ -61,29 +62,27 @@
    statFields: [],       // 合计字段
    statFValue: [],       // 合计值
    absFields: [],        // 绝对值字段
    loadCustomApi: true,  // 加载外部资源
    hasReqFields: false
  }
  /**
   * @description 上级菜单id变化时,刷新数据
   */
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (this.state.config && this.props.Tab.supMenu && !is(fromJS(this.props.BID), fromJS(nextProps.BID))) {
    const { config, setting } = this.state
    if (config && setting && this.props.Tab.supMenu && !is(fromJS(this.props.BID), fromJS(nextProps.BID))) {
      MKEmitter.emit('resetTable', this.props.MenuID + this.props.Tab.uuid) // 列表重置
      this.setState({
        pageIndex: 1
      }, () => {
        if (this.state.setting) {
          this.loadmaindata(nextProps.BID, 'refresh')
          this.getStatFieldsValue(nextProps.BID, 'refresh')
        }
        this.loadData()
      })
    } else if (!this.props.Tab.supMenu && nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
      if (this.state.setting) {
        this.setState({}, () => {
          this.loadmaindata()
          this.getStatFieldsValue()
        })
      }
    } else if (setting && !this.props.Tab.supMenu && nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
      this.setState({}, () => {
        this.loadData()
      })
    }
  }
@@ -103,6 +102,7 @@
      try { // 配置信息解析
        config = JSON.parse(window.decodeURIComponent(window.atob(result.LongParam)))
        config.setting.MenuName = Tab.label
      } catch (e) {
        console.warn('Parse Failure')
        config = ''
@@ -135,7 +135,7 @@
      let absFields = []     // 绝对值字段
      // 版本兼容
      config = UtilsUpdate.updateSubTable(config)
      config = updateSubTable(config)
      // 权限过滤
      if (this.props.menuType !== 'HS') {
@@ -262,8 +262,11 @@
      }
      let valid = true // 搜索条件必填验证
      let hasReqFields = false
      config.search.forEach(field => {
        if (field.required === 'true' && !field.initval) {
        if (field.required !== 'true') return
        hasReqFields = true
        if (!field.initval) {
          valid = false
        }
      })
@@ -274,7 +277,7 @@
      config.setting.execute = config.setting.default !== 'false'     // 默认sql是否执行,转为boolean 统一格式
      config.setting.customScript = ''                                // 自定义脚本
      if (config.setting.interType === 'system') {
      if (config.setting.interType === 'system' || (config.setting.interType === 'custom' && config.setting.requestMode === 'system')) {
        if (config.setting.scripts && config.setting.scripts.length > 0) {
          let _customScript = ''
          config.setting.scripts.forEach(item => {
@@ -317,11 +320,11 @@
        actions: _actions,
        columns: _columns,
        arr_field: _arrField.join(','),
        search: Utils.initMainSearch(config.search) // 搜索条件初始化(含有时间格式,需要转化)
        search: Utils.initMainSearch(config.search), // 搜索条件初始化(含有时间格式,需要转化)
        hasReqFields
      }, () => {
        if (config.setting.onload !== 'false' && (!Tab.supMenu || BID || Tab.isTreeNode) && valid) { // 初始化可加载
          this.loadmaindata()
          this.getStatFieldsValue()
          this.loadData()
        }
      })
    } else {
@@ -338,24 +341,23 @@
      })
    }
  }
  loadData = () => {
    const { mainSearch, BID } = this.props
    const { setting, search, loadCustomApi, hasReqFields } = this.state
  /**
   * @description 子表数据加载
   */
  async loadmaindata (bid, type) {
    const { mainSearch } = this.props
    const { setting, arr_field, search, orderBy, pageIndex, pageSize, absFields } = this.state
    let _BID = this.props.BID
    let searches = fromJS(search).toJS()
    if (mainSearch && mainSearch.length > 0) { // 主表搜索条件
      searches = [...mainSearch, ...searches]
    }
    let requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
    let prex = this.props.Tab && this.props.Tab.label ? this.props.Tab.label + '-' : ''
    let requireFields = []
    if (hasReqFields) {
      requireFields = searches.filter(item => item.required && (!item.value || item.value.length === 0))
    }
    if (requireFields.length > 0) {
      let prex = this.props.Tab && this.props.Tab.label ? this.props.Tab.label + '-' : ''
      let labels = requireFields.map(item => item.label)
      labels = Array.from(new Set(labels))
@@ -365,34 +367,235 @@
        duration: 3
      })
      return
    } else if (this.props.Tab.supMenu && !BID) { // 主表ID不存在时,不查询子表
      this.setState({
        data: [],
        selectedData: [],
        statFValue: [],
        total: 0
      })
      this.handleTableId()
      return
    } else if (window.GLOB.systemType === 'production' && setting.interType === 'custom' && !setting.proInterface) {
      notification.warning({
        top: 92,
        message: '未设置正式系统地址!',
        duration: 3
      })
      return
    }
    if (type === 'refresh') {
      if (!bid) { // 主表ID不存在时,不查询子表
        this.setState({
          data: [],
          total: 0
        })
        return
    this.setState({
      selectedData: []
    })
    this.handleTableId()
    if (setting.interType === 'custom' && loadCustomApi) {
      if (setting.execTime === 'once') {
        this.setState({loadCustomApi: false})
      }
      this.loadOutResource(searches)
      if (setting.execType === 'async') {
        this.loadmaindata()
      }
    } else {
      this.loadmaindata()
    }
  }
  loadOutResource = (searches) => {
    const { BID } = this.props
    const { setting } = this.state
    let param = UtilsDM.getPrevQueryParams(setting, searches, BID, this.props.menuType)
    if (setting.execType === 'sync') {
      this.setState({
        loading: true
      })
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (res.mk_ex_invoke === 'false') {
          this.loadmaindata()
        } else {
          this.customOuterRequest(res)
        }
      } else {
        _BID = bid
        this.setState({
          loading: false
        })
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    }, () => {
      this.setState({
        loading: false
      })
    })
  }
  customOuterRequest = (result) => {
    const { setting } = this.state
    let url = ''
    if (window.GLOB.systemType === 'production') {
      url = setting.proInterface
    } else {
      url = setting.interface
    }
    let mkey = result.mk_api_key || ''
    delete result.mk_ex_invoke
    delete result.status
    delete result.message
    delete result.ErrCode
    delete result.ErrMesg
    delete result.mk_api_key
    let param = {}
    Object.keys(result).forEach(key => {
      key = key.replace(/^mk_/ig, '')
      param[key] = result[key]
    })
    Api.directRequest(url, setting.method, param).then(res => {
      if (typeof(res) !== 'object' || Array.isArray(res)) {
        let error = '未知的返回结果!'
        if (typeof(res) === 'string') {
          error = res.replace(/'/ig, '"')
        }
        let _result = {
          mk_api_key: mkey,
          $ErrCode: 'E',
          $ErrMesg: error
        }
        this.customCallbackRequest(_result)
      } else {
        res.mk_api_key = mkey
        this.customCallbackRequest(res)
      }
    }, (e) => {
      let _result = {
        mk_api_key: mkey,
        $ErrCode: 'E',
        $ErrMesg: e && e.statusText ? e.statusText : ''
      }
      this.customCallbackRequest(_result)
    })
  }
  customCallbackRequest = (result) => {
    const { setting } = this.state
    let errSql = ''
    if (result.$ErrCode === 'E') {
      errSql = `
        set @ErrorCode='E'
        set @retmsg='${result.$ErrMesg}'
      `
      delete result.$ErrCode
      delete result.$ErrMesg
    }
    let lines = UtilsDM.getCallBackSql(setting, result)
    let param = {}
    if (setting.callbackType === 'script') { // 使用自定义脚本
      let sql = lines.map(item => (`
        ${item.insert}
        ${item.selects.join(` union all
        `)}
      `))
      sql = sql.join('')
      param = UtilsDM.getCallBackQueryParams(setting, sql, errSql)
      if (this.props.BID) {
        param.BID = this.props.BID
      }
      if (this.props.menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
        param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
      }
    } else {
      param.func = 's_ex_result_back'
      param.s_ex_result = lines.map((item, index) => ({
        MenuID: this.props.MenuID,
        MenuName: this.props.Tab.label,
        TableName: item.table,
        LongText: window.btoa(window.encodeURIComponent(`${item.insert}  ${item.selects.join(` union all `)}`)),
        Sort: index + 1
      }))
      if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
        let sql = lines.map(item => (`
          ${item.insert}
          ${item.selects.join(` union all
          `)}
        `))
        sql = sql.join('')
        console.info(sql.replace(/\n\s{10}/ig, '\n'))
      }
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        this.loadmaindata()
      } else {
        this.setState({
          loading: false
        })
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    }, () => {
      this.setState({
        loading: false
      })
    })
  }
  /**
   * @description 子表数据加载
   */
  async loadmaindata () {
    const { mainSearch, BID } = this.props
    const { setting, arr_field, search, orderBy, pageIndex, pageSize, absFields } = this.state
    let searches = fromJS(search).toJS()
    if (mainSearch && mainSearch.length > 0) { // 主表搜索条件
      searches = [...mainSearch, ...searches]
    }
    this.setState({
      selectedData: [],
      loading: true
    })
    let _orderBy = orderBy || setting.order
    let param = UtilsDM.getQueryDataParams(setting, arr_field, searches, _orderBy, pageIndex, pageSize, _BID, this.props.menuType)
    this.handleTableId()
    let param = UtilsDM.getQueryDataParams(setting, arr_field, searches, _orderBy, pageIndex, pageSize, BID, this.props.menuType)
    if (param.func === 'sPC_Get_TableData') {
      param.menuname = this.props.Tab.label || ''
    }
    let result = await Api.genericInterface(param)
    this.getStatFieldsValue(searches)
    if (result.status) {
      this.setState({
        data: result.data.map((item, index) => {
@@ -406,7 +609,7 @@
          }
          item.key = index
          item.$$uuid = item[setting.primaryKey] || ''
          item.$$BID = _BID || ''
          item.$$BID = BID || ''
          return item
        }),
@@ -415,6 +618,7 @@
        loading: false
      })
    } else {
      let prex = this.props.Tab && this.props.Tab.label ? this.props.Tab.label + '-' : ''
      this.setState({
        loading: false
      })
@@ -501,36 +705,14 @@
  /**
   * @description 获取合计字段值
   */
  getStatFieldsValue = (bid, type) => {
    const { mainSearch } = this.props
    const { setting, search, orderBy, statFields } = this.state
  getStatFieldsValue = (searches) => {
    const { BID } = this.props
    const { setting, orderBy, statFields } = this.state
    let _BID = this.props.BID
    let searches = fromJS(search).toJS()
    if (mainSearch && mainSearch.length > 0) { // 主表搜索条件
      searches = [...mainSearch, ...searches]
    }
    if (statFields.length === 0 || setting.interType !== 'system' || !setting.dataresource) return
    let requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
    if (requireFields.length > 0) {
      return
    }
    if (type === 'refresh') {
      if (!bid) { // 主表ID不存在时,不查询子表合计值
        this.setState({
          statFValue: []
        })
        return
      } else {
        _BID = bid
      }
    }
    if (statFields.length === 0 || !(setting.interType === 'system' || (setting.interType === 'custom' && setting.requestMode === 'system')) || !setting.dataresource) return
    let _orderBy = orderBy || setting.order
    let param = UtilsDM.getStatQueryDataParams(setting, statFields, searches, _orderBy, _BID, this.props.menuType)
    let param = UtilsDM.getStatQueryDataParams(setting, statFields, searches, _orderBy, BID, this.props.menuType)
    if (param.func === 'sPC_Get_TableData') {
      param.menuname = this.props.Tab.label || ''
@@ -579,8 +761,7 @@
      pageIndex: 1,
      search: searches,
    }, () => {
      this.loadmaindata()
      this.getStatFieldsValue()
      this.loadData()
    })
  }
@@ -601,7 +782,7 @@
      pageSize: pagination.pageSize,
      orderBy: (sorter.field && sorter.order) ? `${sorter.field} ${sorter.order}` : ''
    }, () => {
      this.loadmaindata()
      this.loadData()
    })
  }
@@ -614,13 +795,11 @@
      this.setState({
        pageIndex: 1
      }, () => {
        this.loadmaindata()
        this.getStatFieldsValue()
        this.loadData()
      })
    } else {
      MKEmitter.emit('resetTable', this.props.MenuID + this.props.Tab.uuid, 'false') // 列表重置
      this.loadmaindata()
      this.getStatFieldsValue()
      this.loadData()
    }
  }
src/tabviews/subtable/index.scss
@@ -96,7 +96,7 @@
      position: absolute;
      right: 5px;
      top: -22px;
      z-index: 10;
      z-index: 1;
    }
  }
  .ant-table-fixed-left, .ant-table-fixed-right {
src/tabviews/subtabtable/index.jsx
@@ -8,8 +8,9 @@
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import Utils from '@/utils/utils.js'
import options from '@/store/options.js'
import UtilsDM from '@/utils/utils-datamanage.js'
import UtilsUpdate from '@/utils/utils-update.js'
import { updateSubTable } from '@/utils/utils-update.js'
import asyncComponent from '@/utils/asyncComponent'
import asyncSpinComponent from '@/utils/asyncSpinComponent'
import NotFount from '@/components/404'
@@ -57,6 +58,8 @@
    statFields: [],       // 合计字段
    statFValue: [],       // 合计值
    absFields: [],        // 绝对值字段
    loadCustomApi: true,  // 加载外部资源
    hasReqFields: false
  }
  /**
@@ -75,6 +78,7 @@
      try { // 配置信息解析
        config = JSON.parse(window.decodeURIComponent(window.atob(result.LongParam)))
        config.setting.MenuName = Tab.label
      } catch (e) {
        console.warn('Parse Failure')
        config = ''
@@ -107,7 +111,7 @@
      let absFields = []     // 绝对值字段
      // 版本兼容
      config = UtilsUpdate.updateSubTable(config)
      config = updateSubTable(config)
      // 不支持funcbutton、popview 类型按钮
      let labels = []
@@ -232,8 +236,11 @@
      }
      let valid = true // 搜索条件必填验证
      let hasReqFields = false
      config.search.forEach(field => {
        if (field.required === 'true' && !field.initval) {
        if (field.required !== 'true') return
        hasReqFields = true
        if (!field.initval) {
          valid = false
        }
      })
@@ -244,7 +251,7 @@
      config.setting.execute = config.setting.default !== 'false'     // 默认sql是否执行,转为boolean 统一格式
      config.setting.customScript = ''                                // 自定义脚本
      if (config.setting.interType === 'system') {
      if (config.setting.interType === 'system' || (config.setting.interType === 'custom' && config.setting.requestMode === 'system')) {
        if (config.setting.scripts && config.setting.scripts.length > 0) {
          let _customScript = ''
          config.setting.scripts.forEach(item => {
@@ -287,11 +294,11 @@
        actions: _actions,
        columns: _columns,
        arr_field: _arrField.join(','),
        search: Utils.initMainSearch(config.search) // 搜索条件初始化(含有时间格式,需要转化)
        search: Utils.initMainSearch(config.search), // 搜索条件初始化(含有时间格式,需要转化)
        hasReqFields
      }, () => {
        if (config.setting.onload !== 'false' && valid) { // 初始化可加载
          this.loadmaindata()
          this.getStatFieldsValue()
          this.loadData()
        }
      })
    } else {
@@ -307,6 +314,223 @@
    }
  }
  loadData = () => {
    const { mainSearch } = this.props
    const { setting, search, hasReqFields, loadCustomApi } = this.state
    let searches = fromJS(search).toJS()
    if (mainSearch && mainSearch.length > 0) { // 主表搜索条件
      searches = [...mainSearch, ...searches]
    }
    let requireFields = []
    if (hasReqFields) {
      requireFields = searches.filter(item => item.required && (!item.value || item.value.length === 0))
    }
    if (requireFields.length > 0) {
      let labels = requireFields.map(item => item.label)
      labels = Array.from(new Set(labels))
      notification.warning({
        top: 92,
        message: this.state.dict['form.required.input'] + labels.join('、') + ' !',
        duration: 3
      })
      return
    } else if (window.GLOB.systemType === 'production' && setting.interType === 'custom' && !setting.proInterface) {
      notification.warning({
        top: 92,
        message: '未设置正式系统地址!',
        duration: 3
      })
      return
    }
    this.setState({
      selectedData: []
    })
    if (setting.interType === 'custom' && loadCustomApi) {
      if (setting.execTime === 'once') {
        this.setState({loadCustomApi: false})
      }
      this.loadOutResource(searches)
      if (setting.execType === 'async') {
        this.loadmaindata()
      }
    } else {
      this.loadmaindata()
    }
  }
  loadOutResource = (searches) => {
    const { setting } = this.state
    let param = UtilsDM.getPrevQueryParams(setting, searches, this.props.BID, this.props.menuType)
    if (setting.execType === 'sync') {
      this.setState({
        loading: true
      })
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (res.mk_ex_invoke === 'false') {
          this.loadmaindata()
        } else {
          this.customOuterRequest(res)
        }
      } else {
        this.setState({
          loading: false
        })
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    }, () => {
      this.setState({
        loading: false
      })
    })
  }
  customOuterRequest = (result) => {
    const { setting } = this.state
    let url = ''
    if (window.GLOB.systemType === 'production') {
      url = setting.proInterface
    } else {
      url = setting.interface
    }
    let mkey = result.mk_api_key || ''
    delete result.mk_ex_invoke
    delete result.status
    delete result.message
    delete result.ErrCode
    delete result.ErrMesg
    delete result.mk_api_key
    let param = {}
    Object.keys(result).forEach(key => {
      key = key.replace(/^mk_/ig, '')
      param[key] = result[key]
    })
    Api.directRequest(url, setting.method, param).then(res => {
      if (typeof(res) !== 'object' || Array.isArray(res)) {
        let error = '未知的返回结果!'
        if (typeof(res) === 'string') {
          error = res.replace(/'/ig, '"')
        }
        let _result = {
          mk_api_key: mkey,
          $ErrCode: 'E',
          $ErrMesg: error
        }
        this.customCallbackRequest(_result)
      } else {
        res.mk_api_key = mkey
        this.customCallbackRequest(res)
      }
    }, (e) => {
      let _result = {
        mk_api_key: mkey,
        $ErrCode: 'E',
        $ErrMesg: e && e.statusText ? e.statusText : ''
      }
      this.customCallbackRequest(_result)
    })
  }
  customCallbackRequest = (result) => {
    const { setting } = this.state
    let errSql = ''
    if (result.$ErrCode === 'E') {
      errSql = `
        set @ErrorCode='E'
        set @retmsg='${result.$ErrMesg}'
      `
      delete result.$ErrCode
      delete result.$ErrMesg
    }
    let lines = UtilsDM.getCallBackSql(setting, result)
    let param = {}
    if (setting.callbackType === 'script') { // 使用自定义脚本
      let sql = lines.map(item => (`
        ${item.insert}
        ${item.selects.join(` union all
        `)}
      `))
      sql = sql.join('')
      param = UtilsDM.getCallBackQueryParams(setting, sql, errSql)
      if (this.state.BID) {
        param.BID = this.state.BID
      }
      if (this.props.menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
        param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
      }
    } else {
      let name = this.props.Tab ? (this.props.Tab.label || '') : ''
      param.func = 's_ex_result_back'
      param.s_ex_result = lines.map((item, index) => ({
        MenuID: this.props.MenuID,
        MenuName: name,
        TableName: item.table,
        LongText: window.btoa(window.encodeURIComponent(`${item.insert}  ${item.selects.join(` union all `)}`)),
        Sort: index + 1
      }))
      if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
        let sql = lines.map(item => (`
          ${item.insert}
          ${item.selects.join(` union all
          `)}
        `))
        sql = sql.join('')
        console.info(sql.replace(/\n\s{10}/ig, '\n'))
      }
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        this.loadmaindata()
      } else {
        this.setState({
          loading: false
        })
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    }, () => {
      this.setState({
        loading: false
      })
    })
  }
  /**
   * @description 子表数据加载
   */
@@ -319,22 +543,7 @@
      searches = [...mainSearch, ...searches]
    }
    let requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
    if (requireFields.length > 0) {
      let labels = requireFields.map(item => item.label)
      labels = Array.from(new Set(labels))
      notification.warning({
        top: 92,
        message: this.state.dict['form.required.input'] + labels.join('、') + ' !',
        duration: 3
      })
      return
    }
    this.setState({
      selectedData: [],
      loading: true
    })
@@ -346,6 +555,9 @@
    }
    let result = await Api.genericInterface(param)
    this.getStatFieldsValue(searches)
    if (result.status) {
      this.setState({
        data: result.data.map((item, index) => {
@@ -381,21 +593,11 @@
  /**
   * @description 获取合计字段值
   */
  getStatFieldsValue = () => {
    const { mainSearch, BID } = this.props
    const { setting, search, orderBy, statFields } = this.state
  getStatFieldsValue = (searches) => {
    const { BID } = this.props
    const { setting, orderBy, statFields } = this.state
    let searches = fromJS(search).toJS()
    if (mainSearch && mainSearch.length > 0) { // 主表搜索条件
      searches = [...mainSearch, ...searches]
    }
    if (statFields.length === 0 || setting.interType !== 'system' || !setting.dataresource) return
    let requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
    if (requireFields.length > 0) {
      return
    }
    if (statFields.length === 0 || !(setting.interType === 'system' || (setting.interType === 'custom' && setting.requestMode === 'system')) || !setting.dataresource) return
    let _orderBy = orderBy || setting.order
    let param = UtilsDM.getStatQueryDataParams(setting, statFields, searches, _orderBy, BID, this.props.menuType)
@@ -447,8 +649,7 @@
      pageIndex: 1,
      search: searches
    }, () => {
      this.loadmaindata()
      this.getStatFieldsValue()
      this.loadData()
    })
  }
@@ -469,7 +670,7 @@
      pageSize: pagination.pageSize,
      orderBy: (sorter.field && sorter.order) ? `${sorter.field} ${sorter.order}` : ''
    }, () => {
      this.loadmaindata()
      this.loadData()
    })
  }
@@ -482,13 +683,11 @@
      this.setState({
        pageIndex: 1
      }, () => {
        this.loadmaindata()
        this.getStatFieldsValue()
        this.loadData()
      })
    } else {
      MKEmitter.emit('resetTable', this.props.MenuID + this.props.Tab.uuid, 'false') // 列表重置
      this.loadmaindata()
      this.getStatFieldsValue()
      this.loadData()
    }
    
  }
src/tabviews/verupmanage/actionList/index.jsx
@@ -3,7 +3,7 @@
import moment from 'moment'
import { Button, Modal, notification, message } from 'antd'
import MutilForm from '@/tabviews/zshare/mutilform'
import Utils from '@/utils/utils.js'
import Utils, { getSysDefaultSql } from '@/utils/utils.js'
import options from '@/store/options.js'
import Api from '@/api'
import './index.scss'
@@ -171,7 +171,7 @@
            }
          } else if (btn.sql) {
            param.ID = primaryId
            param.LText = Utils.getSysDefaultSql(btn, setting, '', param, data[0], columns) // 数据源
            param.LText = getSysDefaultSql(btn, setting, '', param, data[0], columns) // 数据源
            
            param.exec_type = 'y' // 后台解码
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -199,7 +199,7 @@
              }
            }
            param.ID = primaryId || Utils.getguid()
            param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, data[0], columns) // 数据源
            param.LText = getSysDefaultSql(btn, setting, formdata, param, data[0], columns) // 数据源
            
            param.exec_type = 'y' // 后台解码
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -207,7 +207,7 @@
            param.LText = Utils.formatOptions(param.LText)
          } else if (btn.sql) {
            param.ID = primaryId
            param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, data[0], columns) // 数据源
            param.LText = getSysDefaultSql(btn, setting, formdata, param, data[0], columns) // 数据源
            
            param.exec_type = 'y' // 后台解码
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -253,7 +253,7 @@
              }
            } else if (btn.sql) {
              param.ID = primaryId
              param.LText = Utils.getSysDefaultSql(btn, setting, '', param, cell, columns) // 数据源
              param.LText = getSysDefaultSql(btn, setting, '', param, cell, columns) // 数据源
              param.exec_type = 'y' // 后台解码
              param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -277,7 +277,7 @@
              }
            } else if (btn.sql && btn.sqlType === 'insert') { // 系统函数添加时,生成uuid
              param.ID = _formPrimaryId || Utils.getguid()
              param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, cell, columns) // 数据源
              param.LText = getSysDefaultSql(btn, setting, formdata, param, cell, columns) // 数据源
              param.exec_type = 'y' // 后台解码
              param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -294,7 +294,7 @@
              }
              param.ID = primaryId
              param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, cell, columns) // 数据源
              param.LText = getSysDefaultSql(btn, setting, formdata, param, cell, columns) // 数据源
              param.exec_type = 'y' // 后台解码
              param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
src/tabviews/verupmanage/actionList/index.scss
@@ -15,30 +15,3 @@
    top: calc(50vh - 70px);
  }
}
// 设置模态框样式,规定最大最小高度,重置滚动条
.action-modal {
  .ant-modal {
    max-width: 95vw;
  }
  .ant-modal-body {
    max-height: calc(100vh - 235px);
    min-height: 150px;
    overflow-y: auto;
    padding-bottom: 35px;
  }
  .ant-modal-body::-webkit-scrollbar {
    width: 10px;
    height: 10px;
  }
  .ant-modal-body::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .ant-modal-body::-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);
  }
}
src/tabviews/verupmanage/index.jsx
@@ -137,6 +137,7 @@
    }
    this.setState({
      selectedData: [],
      pickup: false
    })
@@ -285,7 +286,6 @@
    this.setState({
      loading: true,
      pageIndex: 1,
      selectedData: [],
      search: searches
    }, () => {
      this.loadmaindata()
@@ -306,7 +306,6 @@
    this.setState({
      loading: true,
      selectedData: [],
      pageIndex: pagination.current,
      pageSize: pagination.pageSize,
      orderBy: (sorter.field && sorter.order) ? `${sorter.field} ${sorter.order}` : ''
@@ -322,8 +321,7 @@
    MKEmitter.emit('resetTable', this.props.MenuID + 'mainTable') // 列表重置
    this.setState({
      pageIndex: 1,
      loading: true,
      selectedData: []
      loading: true
    }, () => {
      this.loadmaindata()
    })
@@ -333,6 +331,7 @@
   * @description 页面刷新,重新获取配置
   */
  reloadview = () => {
    MKEmitter.emit('resetTable', this.props.MenuID + 'mainTable') // 列表重置
    this.setState({
      config: {},
      searchlist: [],
src/tabviews/verupmanage/subtabtable/index.scss
@@ -41,7 +41,7 @@
      position: absolute;
      right: 5px;
      top: -22px;
      z-index: 10;
      z-index: 1;
    }
  }
}
src/tabviews/zshare/actionList/excelInbutton/index.jsx
@@ -6,7 +6,7 @@
import { Button, Modal, notification, message } from 'antd'
import ExcelIn from './excelin'
import Utils from '@/utils/utils.js'
import Utils, { getExcelInSql } from '@/utils/utils.js'
import options from '@/store/options.js'
import Api from '@/api'
import zhCN from '@/locales/zh-CN/main.js'
@@ -93,25 +93,28 @@
        duration: 5
      })
      return
    }
    if (btn.verify && btn.verify.sheet && btn.verify.columns && btn.verify.columns.length > 0) {
      let primaryId = '' // 导入时行Id
      if (btn.Ot === 'requiredSgl') {
        primaryId = data[0][setting.primaryKey] || ''
      }
      this.setState({
        primaryId: primaryId
      }, () => {
        this.refs.excelIn.exceltrigger()
      })
    } else {
    } else if (!btn.verify || !btn.verify.sheet || !btn.verify.columns || btn.verify.columns.length === 0) {
      notification.warning({
        top: 92,
        message: 'excel导入验证信息未设置!',
        duration: 5
      })
      return
    }
    let primaryId = '' // 导入时行Id
    if (btn.Ot === 'requiredSgl') {
      primaryId = data[0][setting.primaryKey] || ''
    }
    this.setState({
      primaryId: primaryId
    }, () => {
      this.refs.excelIn.exceltrigger()
    })
    if (window.GLOB.systemType === 'production') {
      MKEmitter.emit('queryTrigger', {menuId: btn.uuid, name: '导入Excel'})
    }
  }
@@ -235,7 +238,7 @@
      })
    }
    let result = Utils.getExcelInSql(btn, data, this.state.dict, (this.props.BID || ''))
    let result = getExcelInSql(btn, data, this.state.dict, (this.props.BID || ''))
    if (result.errors) {
      notification.warning({
@@ -342,13 +345,21 @@
          if (btn.sysInterface === 'true' && options.cloudServiceApi) {
            param.rduri = options.cloudServiceApi
          } else if (btn.sysInterface !== 'true') {
            param.rduri = btn.interface
            if (window.GLOB.systemType === 'production' && btn.proInterface) {
              param.rduri = btn.proInterface
            } else {
              param.rduri = btn.interface
            }
          }
        } else {
          if (btn.sysInterface === 'true' && window.GLOB.mainSystemApi) {
            param.rduri = window.GLOB.mainSystemApi
          } else if (btn.sysInterface !== 'true') {
            param.rduri = btn.interface
            if (window.GLOB.systemType === 'production' && btn.proInterface) {
              param.rduri = btn.proInterface
            } else {
              param.rduri = btn.interface
            }
          }
        }
src/tabviews/zshare/actionList/exceloutbutton/index.jsx
@@ -111,6 +111,9 @@
    }
    MKEmitter.emit('getexceloutparam', btn.$menuId, btn.uuid)
    if (window.GLOB.systemType === 'production') {
      MKEmitter.emit('queryTrigger', {menuId: btn.uuid, name: '导出Excel'})
    }
  }
  /**
@@ -190,13 +193,21 @@
          if (btn.sysInterface === 'true' && options.cloudServiceApi) {
            param.rduri = options.cloudServiceApi
          } else if (btn.sysInterface !== 'true') {
            param.rduri = btn.interface
            if (window.GLOB.systemType === 'production' && btn.proInterface) {
              param.rduri = btn.proInterface
            } else {
              param.rduri = btn.interface
            }
          }
        } else {
          if (btn.sysInterface === 'true' && window.GLOB.mainSystemApi) {
            param.rduri = window.GLOB.mainSystemApi
          } else if (btn.sysInterface !== 'true') {
            param.rduri = btn.interface
            if (window.GLOB.systemType === 'production' && btn.proInterface) {
              param.rduri = btn.proInterface
            } else {
              param.rduri = btn.interface
            }
          }
        }
  
@@ -228,13 +239,21 @@
              if (btn.sysInterface === 'true' && options.cloudServiceApi) {
                res.rduri = options.cloudServiceApi
              } else if (btn.sysInterface !== 'true') {
                res.rduri = btn.interface
                if (window.GLOB.systemType === 'production' && btn.proInterface) {
                  res.rduri = btn.proInterface
                } else {
                  res.rduri = btn.interface
                }
              }
            } else {
              if (btn.sysInterface === 'true' && window.GLOB.mainSystemApi) {
                res.rduri = window.GLOB.mainSystemApi
              } else if (btn.sysInterface !== 'true') {
                res.rduri = btn.interface
                if (window.GLOB.systemType === 'production' && btn.proInterface) {
                  res.rduri = btn.proInterface
                } else {
                  res.rduri = btn.interface
                }
              }
            }
  
@@ -289,13 +308,21 @@
          if (btn.sysInterface === 'true' && options.cloudServiceApi) {
            res.rduri = options.cloudServiceApi
          } else if (btn.sysInterface !== 'true') {
            res.rduri = btn.interface
            if (window.GLOB.systemType === 'production' && btn.proInterface) {
              res.rduri = btn.proInterface
            } else {
              res.rduri = btn.interface
            }
          }
        } else {
          if (btn.sysInterface === 'true' && window.GLOB.mainSystemApi) {
            res.rduri = window.GLOB.mainSystemApi
          } else if (btn.sysInterface !== 'true') {
            res.rduri = btn.interface
            if (window.GLOB.systemType === 'production' && btn.proInterface) {
              res.rduri = btn.proInterface
            } else {
              res.rduri = btn.interface
            }
          }
        }
@@ -353,13 +380,21 @@
        if (btn.sysInterface === 'true' && options.cloudServiceApi) {
          param.rduri = options.cloudServiceApi
        } else if (btn.sysInterface !== 'true') {
          param.rduri = btn.interface
          if (window.GLOB.systemType === 'production' && btn.proInterface) {
            param.rduri = btn.proInterface
          } else {
            param.rduri = btn.interface
          }
        }
      } else {
        if (btn.sysInterface === 'true' && window.GLOB.mainSystemApi) {
          param.rduri = window.GLOB.mainSystemApi
        } else if (btn.sysInterface !== 'true') {
          param.rduri = btn.interface
          if (window.GLOB.systemType === 'production' && btn.proInterface) {
            param.rduri = btn.proInterface
          } else {
            param.rduri = btn.interface
          }
        }
      }
      
src/tabviews/zshare/actionList/index.scss
@@ -24,30 +24,3 @@
    display: none;
  }
}
// 设置模态框样式,规定最大最小高度,重置滚动条
.action-modal {
  .ant-modal {
    max-width: 95vw;
  }
  .ant-modal-body {
    max-height: calc(100vh - 235px);
    min-height: 150px;
    overflow-y: auto;
    padding-bottom: 35px;
  }
  .ant-modal-body::-webkit-scrollbar {
    width: 10px;
    height: 10px;
  }
  .ant-modal-body::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .ant-modal-body::-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);
  }
}
src/tabviews/zshare/actionList/newpagebutton/index.jsx
@@ -97,7 +97,10 @@
      Remark = data[0].Remark || ''
    }
    let _name = '新页面'
    if (btn.pageTemplate === 'billprint') {
      _name = '单据打印'
      if (btn.Ot === 'required' && data && data.length > 0) {
        data.forEach((item, i) => {
          let _id = item[setting.primaryKey] || ''
@@ -118,6 +121,7 @@
    } else if (btn.pageTemplate === 'pay') {
      let _p = `ID=${Id}&userid=${sessionStorage.getItem('UserID')}&LoginUID=${sessionStorage.getItem('LoginUID')}&logo=${window.GLOB.doclogo}&name=${sessionStorage.getItem('Full_Name')}&icp=${window.GLOB.ICP}&copyRight=${window.GLOB.copyRight}`
      let url = '#/pay/' +  window.btoa(window.encodeURIComponent(_p))
      _name = '支付'
      
      confirm({
        title: '请在付款页面完成订单支付。',
@@ -152,6 +156,10 @@
      window.open(url)
    }
    if (window.GLOB.systemType === 'production') {
      MKEmitter.emit('queryTrigger', {menuId: btn.uuid, name: _name})
    }
  }
  render() {
src/tabviews/zshare/actionList/normalbutton/index.jsx
@@ -6,7 +6,7 @@
import { Button, Modal, notification, message } from 'antd'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import Utils, { getSysDefaultSql } from '@/utils/utils.js'
import options from '@/store/options.js'
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
@@ -40,7 +40,8 @@
    confirmLoading: false,
    btnconfig: null,
    loading: false,
    loadingNumber: ''
    loadingNumber: '',
    checkParam: null
  }
  shouldComponentUpdate (nextProps, nextState) {
@@ -146,13 +147,32 @@
        this.actionSettingError()
        return
      }
    } else if (btn.intertype === 'custom') {
      if (btn.callbackType === 'script' && (!btn.verify || !btn.verify.cbScripts || !btn.verify.cbScripts.filter(item => item.status !== 'false').length === 0)) {
        this.actionSettingError()
        return
      } else if (btn.procMode === 'system' && data.length === 0 && btn.verify && btn.verify.voucher && btn.verify.voucher.enabled) {
        notification.warning({
          top: 92,
          message: '使用创建凭证函数,需要选择行!',
          duration: 5
        })
        return
      } else if (window.GLOB.systemType === 'production' && !btn.proInterface) {
        notification.warning({
          top: 92,
          message: '尚未设置正式系统接口地址!',
          duration: 5
        })
        return
      }
    } else if (btn.intertype === 'outer') {
      // 接口地址不存在时报错
      if (!btn.interface && btn.sysInterface !== 'true') {
        this.actionSettingError()
        return
      }
    } else if (!['inner', 'outer', 'system'].includes(btn.intertype)) {
    } else if (!['inner', 'outer', 'system', 'custom'].includes(btn.intertype)) {
      // 接口类型错误
      this.actionSettingError()
      return
@@ -184,83 +204,218 @@
        this.improveAction()
      })
    }
    if (window.GLOB.systemType === 'production') {
      let _change = {
        prompt: '提示框',
        exec: '直接执行',
        pop: '弹窗(表单)'
      }
      MKEmitter.emit('queryTrigger', {menuId: btn.uuid, name: _change[btn.OpenType]})
    }
  }
  /**
   * @description 按钮提交执行
   */
  execSubmit = (data, _resolve, formdata) => {
  getSystemParam = (data, formdata, retmsg) => {
    const { setting, columns, btn } = this.props
    if (btn.intertype === 'inner' || btn.intertype === 'system') {
      // 执行方式为多行拼接,且打开方式为表单时,会转为循环发送请求
      // 打开方式为模态框,使用内部函数添加(有批量添加场景,已去除)
      if (
        btn.Ot === 'notRequired' ||
        btn.Ot === 'requiredSgl' ||
        btn.Ot === 'requiredOnce'
      ) {
        let param = { // 系统存储过程
    let _params = []
    if ( btn.Ot === 'notRequired' || btn.Ot === 'requiredSgl' || btn.Ot === 'requiredOnce' ) {
      let param = { // 系统存储过程
        func: 'sPC_TableData_InUpDe'
      }
      let check_param = null
      if (this.props.BID) {
        param.BID = this.props.BID
      }
      let primaryId = ''
      if (btn.Ot === 'requiredSgl' || btn.Ot === 'requiredOnce') {
        let ids = data.map(d => { return d[setting.primaryKey] || ''})
        ids = ids.filter(Boolean)
        primaryId = ids.join(',')
      }
      if (btn.OpenType === 'prompt' || btn.OpenType === 'exec') { // 是否弹框或直接执行
        param.ID = primaryId
        if (retmsg) {
          const { sql, callbacksql } = getSysDefaultSql(btn, setting, '', param, data[0], columns, this.props.Tab, retmsg) // 数据源
          param.LText = sql
          param.$callbacksql = callbacksql
        } else {
          param.LText = getSysDefaultSql(btn, setting, '', param, data[0], columns, this.props.Tab) // 数据源
        }
        if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
          param.LText = param.LText.replace(/\$@/ig, '/*')
          param.LText = param.LText.replace(/@\$/ig, '*/')
        } else {
          param.LText = param.LText.replace(/@\$|\$@/ig, '')
        }
        param.exec_type = 'y' // 后台解码
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        param.secretkey = Utils.encrypt('', param.timestamp)
        if (/\$check@|@check\$/ig.test(param.LText)) {
          check_param = fromJS(param).toJS()
          check_param.LText = check_param.LText.replace(/\$check@/ig, '/*')
          check_param.LText = check_param.LText.replace(/@check\$/ig, '*/')
          check_param.LText = Utils.formatOptions(check_param.LText)
          param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
        }
        param.LText = Utils.formatOptions(param.LText)
      } else if (btn.OpenType === 'pop') { // 表单
        if (btn.sqlType === 'insert') { // 系统函数添加时,生成uuid
          primaryId = ''
          if (formdata && setting.primaryKey) { // 表单中存在主键字段,主键值以表单中的值为准
            let _form = formdata.filter(_form => _form.key === setting.primaryKey)[0]
            if (_form) {
              primaryId = _form.value
            }
          }
          param.ID = primaryId || Utils.getguid()
          if (retmsg) {
            const { sql, callbacksql } = getSysDefaultSql(btn, setting, formdata, param, data[0], columns, this.props.Tab, retmsg) // 数据源
            param.LText = sql
            param.$callbacksql = callbacksql
          } else {
            param.LText = getSysDefaultSql(btn, setting, formdata, param, data[0], columns, this.props.Tab) // 数据源
          }
          if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
            param.LText = param.LText.replace(/\$@/ig, '/*')
            param.LText = param.LText.replace(/@\$/ig, '*/')
          } else {
            param.LText = param.LText.replace(/@\$|\$@/ig, '')
          }
          param.exec_type = 'y' // 后台解码
          param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
          param.secretkey = Utils.encrypt('', param.timestamp)
          if (/\$check@|@check\$/ig.test(param.LText)) {
            check_param = fromJS(param).toJS()
            check_param.LText = check_param.LText.replace(/\$check@/ig, '/*')
            check_param.LText = check_param.LText.replace(/@check\$/ig, '*/')
            check_param.LText = Utils.formatOptions(check_param.LText)
            param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
          }
          param.LText = Utils.formatOptions(param.LText)
        } else {
          param.ID = primaryId
          if (retmsg) {
            const { sql, callbacksql } = getSysDefaultSql(btn, setting, formdata, param, data[0], columns, this.props.Tab, retmsg) // 数据源
            param.LText = sql
            param.$callbacksql = callbacksql
          } else {
            param.LText = getSysDefaultSql(btn, setting, formdata, param, data[0], columns, this.props.Tab) // 数据源
          }
          if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
            param.LText = param.LText.replace(/\$@/ig, '/*')
            param.LText = param.LText.replace(/@\$/ig, '*/')
          } else {
            param.LText = param.LText.replace(/@\$|\$@/ig, '')
          }
          param.exec_type = 'y' // 后台解码
          param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
          param.secretkey = Utils.encrypt('', param.timestamp)
          if (/\$check@|@check\$/ig.test(param.LText)) {
            check_param = fromJS(param).toJS()
            check_param.LText = check_param.LText.replace(/\$check@/ig, '/*')
            check_param.LText = check_param.LText.replace(/@check\$/ig, '*/')
            check_param.LText = Utils.formatOptions(check_param.LText)
            param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
          }
          param.LText = Utils.formatOptions(param.LText)
        }
      }
      if (this.props.menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
        param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
        if (check_param) {
          check_param.open_key = Utils.encryptOpenKey(check_param.secretkey, check_param.timestamp)
        }
      }
      param.menuname = btn.logLabel
      if (check_param) {
        check_param.menuname = btn.logLabel
        this.setState({checkParam: check_param})
      }
      _params.push(param)
    } else if (btn.Ot === 'required') {
      _params = data.map((cell, index) => {
        let param = {
          func: 'sPC_TableData_InUpDe'
        }
        if (this.props.BID) {
          param.BID = this.props.BID
        }
        let primaryId = ''
        if (btn.Ot === 'requiredSgl' || btn.Ot === 'requiredOnce') {
          let ids = data.map(d => { return d[setting.primaryKey] || ''})
          ids = ids.filter(Boolean)
          primaryId = ids.join(',')
        }
        let primaryId = setting.primaryKey ? cell[setting.primaryKey] || '' : ''
        if (btn.OpenType === 'prompt' || btn.OpenType === 'exec') { // 是否弹框或直接执行
          if (btn.innerFunc) { // 使用自定义函数
            param.func = btn.innerFunc
            if (setting.primaryKey) { // 主键存在时,设置主键参数
              param[setting.primaryKey] = primaryId
            }
          } else if (btn.sql) {
            param.ID = primaryId
            param.LText = Utils.getSysDefaultSql(btn, setting, '', param, data[0], columns, this.props.Tab) // 数据源
          param.ID = primaryId
            if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
              param.LText = param.LText.replace(/\$@/ig, '/*')
              param.LText = param.LText.replace(/@\$/ig, '*/')
            } else {
              param.LText = param.LText.replace(/@\$|\$@/ig, '')
            }
            param.exec_type = 'y' // 后台解码
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
            param.secretkey = Utils.encrypt('', param.timestamp)
            param.LText = Utils.formatOptions(param.LText)
          if (retmsg) {
            const { sql, callbacksql } = getSysDefaultSql(btn, setting, '', param, cell, columns, this.props.Tab, retmsg) // 数据源
            param.LText = sql
            param.$callbacksql = callbacksql
          } else {
            param.LText = getSysDefaultSql(btn, setting, '', param, cell, columns, this.props.Tab) // 数据源
          }
          if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
            param.LText = param.LText.replace(/\$@/ig, '/*')
            param.LText = param.LText.replace(/@\$/ig, '*/')
          } else {
            param.LText = param.LText.replace(/@\$|\$@/ig, '')
          }
          param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
          param.exec_type = 'y' // 后台解码
          param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
          param.secretkey = Utils.encrypt('', param.timestamp)
          param.LText = Utils.formatOptions(param.LText)
        } else if (btn.OpenType === 'pop') { // 表单
          if (btn.innerFunc) {
            param.func = btn.innerFunc
            if (setting.primaryKey) { // 主键存在时,设置主键参数
              param[setting.primaryKey] = primaryId
            }
            formdata.forEach(_data => {
              param[_data.key] = _data.value
            })
          } else if (btn.sql && btn.sqlType === 'insert') { // 系统函数添加时,生成uuid
            primaryId = ''
            if (formdata && setting.primaryKey) { // 表单中存在主键字段,主键值以表单中的值为准
              let _form = formdata.filter(_form => _form.key === setting.primaryKey)[0]
              if (_form) {
                primaryId = _form.value
          if (index !== 0) {
            formdata = formdata.map(_data => {
              if (_data.readin && cell.hasOwnProperty(_data.key)) {
                _data.value = cell[_data.key]
              }
            }
              return _data
            })
          }
            param.ID = primaryId || Utils.getguid()
            param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, data[0], columns, this.props.Tab) // 数据源
          if (btn.sqlType === 'insert') { // 系统函数添加时,生成uuid
            param.ID = Utils.getguid()
            if (retmsg) {
              const { sql, callbacksql } = getSysDefaultSql(btn, setting, formdata, param, cell, columns, this.props.Tab, retmsg) // 数据源
              param.LText = sql
              param.$callbacksql = callbacksql
            } else {
              param.LText = getSysDefaultSql(btn, setting, formdata, param, cell, columns, this.props.Tab) // 数据源
            }
            
            if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
              param.LText = param.LText.replace(/\$@/ig, '/*')
@@ -268,14 +423,22 @@
            } else {
              param.LText = param.LText.replace(/@\$|\$@/ig, '')
            }
            param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
            param.exec_type = 'y' // 后台解码
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
            param.secretkey = Utils.encrypt('', param.timestamp)
            param.LText = Utils.formatOptions(param.LText)
          } else if (btn.sql) {
          } else {
            param.ID = primaryId
            param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, data[0], columns, this.props.Tab) // 数据源
            if (retmsg) {
              const { sql, callbacksql } = getSysDefaultSql(btn, setting, formdata, param, cell, columns, this.props.Tab, retmsg) // 数据源
              param.LText = sql
              param.$callbacksql = callbacksql
            } else {
              param.LText = getSysDefaultSql(btn, setting, formdata, param, cell, columns, this.props.Tab) // 数据源
            }
            
            if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
              param.LText = param.LText.replace(/\$@/ig, '/*')
@@ -283,6 +446,7 @@
            } else {
              param.LText = param.LText.replace(/@\$|\$@/ig, '')
            }
            param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
            param.exec_type = 'y' // 后台解码
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
@@ -291,11 +455,7 @@
          }
        }
        if (this.props.menuType === 'HS' && param.timestamp) { // 函数 sPC_TableData_InUpDe 云端验证
          param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
        } else if (this.props.menuType === 'HS' && param.func === 's_sDataDictb_TBBack' && param.LTextOut) { // 函数 s_sDataDictb_TBBack 云端验证
          param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
          param.secretkey = Utils.encrypt(param.LTextOut, param.timestamp)
        if (this.props.menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
          param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
        }
@@ -303,160 +463,135 @@
          param.menuname = btn.logLabel
        }
        Api.genericInterface(param).then((res) => {
          if (res.status) {
            this.execSuccess(res)
          } else {
            this.execError(res)
          }
          _resolve()
        }, () => {
          this.updateStatus('over')
          _resolve()
        return param
      })
    }
    return _params
  }
  getInnerParam = (data, formdata) => {
    const { setting, btn } = this.props
    let _params = []
    if ( btn.Ot === 'notRequired' || btn.Ot === 'requiredSgl' || btn.Ot === 'requiredOnce' ) {
      let param = {
        func: btn.innerFunc
      }
      if (this.props.BID) {
        param.BID = this.props.BID
      }
      let primaryId = ''
      if (btn.Ot === 'requiredSgl' || btn.Ot === 'requiredOnce') {
        let ids = data.map(d => { return d[setting.primaryKey] || ''})
        ids = ids.filter(Boolean)
        primaryId = ids.join(',')
      }
      param[setting.primaryKey] = primaryId // 设置主键参数
      if (btn.OpenType === 'pop') { // 表单
        formdata.forEach(_data => {
          param[_data.key] = _data.value
        })
      } else if (btn.Ot === 'required') {
        let _formPrimaryId = ''
        if (formdata && setting.primaryKey) { // 表单中存在主键字段,主键值以表单中的值为准
          let _form = formdata.filter(_form => _form.key === setting.primaryKey)[0]
          if (_form) {
            _formPrimaryId = _form.value
          }
      }
      if (this.props.menuType === 'HS' && param.func === 's_sDataDictb_TBBack' && param.LTextOut) { // 函数 s_sDataDictb_TBBack 云端验证
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        param.secretkey = Utils.encrypt(param.LTextOut, param.timestamp)
        param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
      }
      _params.push(param)
    } else if (btn.Ot === 'required') {
      _params = data.map((cell, index) => {
        let param = {
          func: btn.innerFunc
        }
        let _params = data.map((cell, index) => {
          let param = {
            func: 'sPC_TableData_InUpDe'
        if (this.props.BID) {
          param.BID = this.props.BID
        }
        let primaryId = setting.primaryKey ? cell[setting.primaryKey] || '' : ''
        if (btn.OpenType === 'pop') { // 表单
          if (index !== 0) {
            formdata = formdata.map(_data => {
              if (_data.readin && cell.hasOwnProperty(_data.key)) {
                _data.value = cell[_data.key]
              }
              return _data
            })
          }
          if (this.props.BID) {
            param.BID = this.props.BID
          }
          formdata.forEach(_data => {
            param[_data.key] = _data.value
          })
        }
        param[setting.primaryKey] = primaryId
          let primaryId = setting.primaryKey ? cell[setting.primaryKey] || '' : ''
        if (this.props.menuType === 'HS' && param.func === 's_sDataDictb_TBBack' && param.LTextOut) { // 函数 s_sDataDictb_TBBack 云端验证
          param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
          param.secretkey = Utils.encrypt(param.LTextOut, param.timestamp)
          param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
        }
          if (btn.OpenType === 'prompt' || btn.OpenType === 'exec') { // 是否弹框或直接执行
        return param
      })
    }
            if (btn.innerFunc) {
              param.func = btn.innerFunc
              if (setting.primaryKey) {
                param[setting.primaryKey] = primaryId
              }
            } else if (btn.sql) {
              param.ID = primaryId
              param.LText = Utils.getSysDefaultSql(btn, setting, '', param, cell, columns, this.props.Tab) // 数据源
              if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
                param.LText = param.LText.replace(/\$@/ig, '/*')
                param.LText = param.LText.replace(/@\$/ig, '*/')
              } else {
                param.LText = param.LText.replace(/@\$|\$@/ig, '')
              }
    return _params
  }
              param.exec_type = 'y' // 后台解码
              param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
              param.secretkey = Utils.encrypt('', param.timestamp)
              param.LText = Utils.formatOptions(param.LText)
            }
          } else if (btn.OpenType === 'pop') { // 表单
            if (index !== 0) {
              formdata = formdata.map(_data => {
                if (_data.readin && cell.hasOwnProperty(_data.key)) {
                  _data.value = cell[_data.key]
                }
                return _data
              })
            }
  /**
   * @description 按钮提交执行
   */
  execSubmit = (data, _resolve, formdata) => {
    const { setting, btn } = this.props
    if (btn.intertype === 'system' || btn.intertype === 'inner') { // 系统接口
      let params = []
            if (btn.innerFunc) {
              param.func = btn.innerFunc
      if (btn.intertype === 'system') {
        params = this.getSystemParam(data, formdata)
      } else {
        params = this.getInnerParam(data, formdata)
      }
              formdata.forEach(_data => {
                param[_data.key] = _data.value
              })
              if (setting.primaryKey) {
                param[setting.primaryKey] = primaryId
              }
            } else if (btn.sql && btn.sqlType === 'insert') { // 系统函数添加时,生成uuid
              param.ID = _formPrimaryId || Utils.getguid()
              param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, cell, columns, this.props.Tab) // 数据源
              if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
                param.LText = param.LText.replace(/\$@/ig, '/*')
                param.LText = param.LText.replace(/@\$/ig, '*/')
              } else {
                param.LText = param.LText.replace(/@\$|\$@/ig, '')
              }
              param.exec_type = 'y' // 后台解码
              param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
              param.secretkey = Utils.encrypt('', param.timestamp)
              param.LText = Utils.formatOptions(param.LText)
            } else if (btn.sql) {
              param.ID = primaryId
              param.LText = Utils.getSysDefaultSql(btn, setting, formdata, param, cell, columns, this.props.Tab) // 数据源
              if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
                param.LText = param.LText.replace(/\$@/ig, '/*')
                param.LText = param.LText.replace(/@\$/ig, '*/')
              } else {
                param.LText = param.LText.replace(/@\$|\$@/ig, '')
              }
              param.exec_type = 'y' // 后台解码
              param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
              param.secretkey = Utils.encrypt('', param.timestamp)
              param.LText = Utils.formatOptions(param.LText)
            }
          }
          if (this.props.menuType === 'HS' && param.timestamp) { // 函数 sPC_TableData_InUpDe 云端验证
            param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
          } else if (this.props.menuType === 'HS' && param.func === 's_sDataDictb_TBBack' && param.LTextOut) { // 函数 s_sDataDictb_TBBack 云端验证
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
            param.secretkey = Utils.encrypt(param.LTextOut, param.timestamp)
            param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
          }
          if (param.func === 'sPC_TableData_InUpDe') {
            param.menuname = btn.logLabel
          }
          return param
        })
        if (_params.length <= 20) {
          let deffers = _params.map(param => {
            return new Promise(resolve => {
              Api.genericInterface(param).then(res => {
                resolve(res)
              }, () => {
                this.updateStatus('over')
                _resolve()
              })
      if (params.length <= 20) {
        let deffers = params.map(param => {
          return new Promise(resolve => {
            Api.genericInterface(param).then(res => {
              resolve(res)
            }, () => {
              this.updateStatus('over')
              _resolve()
            })
          })
          Promise.all(deffers).then(result => {
            let iserror = false
            let errorMsg = ''
            result.forEach(res => {
              if (res.status) {
                errorMsg = res
              } else {
                iserror = true
                errorMsg = res
              }
            })
            if (!iserror) {
              this.execSuccess(errorMsg)
        })
        Promise.all(deffers).then(result => {
          let iserror = false
          let errorMsg = ''
          result.forEach(res => {
            if (res.status) {
              errorMsg = res
            } else {
              this.execError(errorMsg)
              iserror = true
              errorMsg = res
            }
            _resolve()
          })
        } else { // 超出20个请求时循环执行
          this.innerLoopRequest(_params, btn, _resolve)
        }
          if (!iserror) {
            this.execSuccess(errorMsg)
          } else {
            this.execError(errorMsg)
          }
          _resolve()
        })
      } else { // 超出20个请求时循环执行
        this.innerLoopRequest(params, btn, _resolve)
      }
    } else if (btn.intertype === 'outer') {
      /** *********************调用外部接口************************* */
@@ -518,15 +653,342 @@
      // 循环调用外部接口(包括内部及回调函数)
      this.outerLoopRequest(_params, btn, _resolve, _params.length > 20)
    } else if (btn.intertype === 'custom') { // 系统接口
      let params = []
      if (btn.procMode === 'system') {
        params = this.getSystemParam(data, formdata, true)
        params = params.map(item => {
          item.script_type = 'Y'
          return item
        })
      } else {
        params = this.getInnerParam(data, formdata)
      }
      this.customLoopRequest(params, _resolve)
    }
  }
  /**
   * @description 自定义请求循环执行
   */
  customLoopRequest = (params, _resolve) => {
    let param = params.shift()
    this.setState({
      loadingNumber: params.length || ''
    })
    let record = {
      BID: param.BID || '',
      ID: param.ID || '',
      callbacksql: param.$callbacksql || ''
    }
    delete param.$callbacksql
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (res.mk_ex_invoke === 'false' && params.length === 0) {
          this.execSuccess(res)
          _resolve()
        } else if (res.mk_ex_invoke === 'false' && params.length > 0) {
          this.customLoopRequest(params, _resolve)
        } else {
          this.customOuterRequest(params, res, record, _resolve)
        }
      } else if (res.ErrCode === 'C' && this.state.checkParam) {
        const _this = this
        confirm({
          title: res.message || res.ErrMesg,
          content: '继续执行?',
          onOk() {
            return new Promise(resolve => {
              Api.genericInterface(_this.state.checkParam).then((result) => {
                if (result.status) {
                  if (result.mk_ex_invoke === 'false' && params.length === 0) {
                    _this.execSuccess(result)
                    _resolve()
                  } else if (result.mk_ex_invoke === 'false' && params.length > 0) {
                    _this.customLoopRequest(params, _resolve)
                  } else {
                    _this.customOuterRequest(params, result, record, _resolve)
                  }
                } else {
                  _this.execError(result)
                  _resolve()
                }
                resolve()
              }, () => {
                _this.updateStatus('over')
                resolve()
                _resolve()
              })
            })
          },
          onCancel() {
            _this.execError({...res, ErrCode: 'P'})
            _resolve()
          }
        })
        this.setState({checkParam: null})
      } else {
        this.execError(res)
        _resolve()
      }
    }, () => {
      this.updateStatus('over')
      _resolve()
    })
  }
  /**
   * @description 自定义请求循环执行
   */
  customOuterRequest = (params, result, record, _resolve) => {
    const { btn } = this.props
    let url = ''
    if (window.GLOB.systemType === 'production') {
      url = btn.proInterface
    } else {
      url = btn.interface
    }
    let mkey = result.mk_api_key || ''
    delete result.mk_ex_invoke
    delete result.status
    delete result.message
    delete result.ErrCode
    delete result.ErrMesg
    delete result.mk_api_key
    let param = {}
    Object.keys(result).forEach(key => {
      key = key.replace(/^mk_/ig, '')
      param[key] = result[key]
    })
    Api.directRequest(url, btn.method, param).then(res => {
      if (typeof(res) !== 'object' || Array.isArray(res)) {
        let error = '未知的返回结果!'
        if (typeof(res) === 'string') {
          error = res.replace(/'/ig, '"')
        }
        let result = {
          mk_api_key: mkey,
          $ErrCode: 'E',
          $ErrMesg: error
        }
        this.customCallbackRequest(params, result, record, _resolve)
      } else {
        res.mk_api_key = mkey
        this.customCallbackRequest(params, res, record, _resolve)
      }
    }, (e) => {
      let result = {
        mk_api_key: mkey,
        $ErrCode: 'E',
        $ErrMesg: e && e.statusText ? e.statusText : ''
      }
      this.customCallbackRequest([], result, record, _resolve)
    })
  }
  /**
   * @description 回调请求循环执行
   */
  customCallbackRequest = (params, result, record, _resolve) => {
    const { btn } = this.props
    let lines = []
    let pre = btn.callbackType === 'script' ? '@' : ''
    let errSql = ''
    if (result.$ErrCode === 'E') {
      errSql = `
        set @ErrorCode='E'
        set @retmsg='${result.$ErrMesg}'
      `
      delete result.$ErrCode
      delete result.$ErrMesg
    }
    let getDefaultSql = (obj, tb, bid, level) => {
      let keys = []
      let vals = []
      let subObjs = []
      let id = Utils.getuuid()
      delete obj.$$key
      Object.keys(obj).forEach(key => {
        let val = obj[key]
        if (val === null || val === undefined) return
        if (typeof(val) === 'object') {
          if (Array.isArray(val)) {
            val.forEach(item => {
              if (typeof(item) !== 'object' || Array.isArray(item)) return
              if (Object.keys(item).length > 0) {
                item.$$key = tb + '_' + key
                subObjs.push(item)
              }
            })
          } else if (Object.keys(val).length > 0) {
            val.$$key = tb + '_' + key
            subObjs.push(val)
          }
        } else {
          keys.push(key)
          vals.push(`'${val}'`)
        }
      })
      lines.push({
        table: tb,
        insert: `Insert into ${pre}${tb} (${keys.join(',')},mk_level,mk_id,mk_bid)`,
        select: `Select ${vals.join(',')},'${level}','${id}','${bid}'`
      })
      subObjs.forEach(item => {
        getDefaultSql(item, item.$$key, id, level + 1)
      })
    }
    getDefaultSql(result, btn.cbTable, '', 1)
    let lineMap = new Map()
    lines.forEach(line => {
      if (lineMap.has(line.table)) {
        let _line = lineMap.get(line.table)
        _line.selects.push(line.select)
        lineMap.set(line.table, _line)
      } else {
        lineMap.set(line.table, {
          table: line.table,
          insert: line.insert,
          selects: [line.select]
        })
      }
    })
    let param = {}
    if (btn.callbackType === 'script') { // 使用自定义脚本
      param.func = 'sPC_TableData_InUpDe'
      if (record.BID) {
        param.BID = this.props.BID
      }
      if (record.ID) {
        param.ID = record.ID
      }
      let _prevCustomScript = `${record.callbacksql}
        ${errSql}
      `
      let _backCustomScript = ''
      btn.verify.cbScripts.forEach(script => {
        if (script.status === 'false') return
        if (script.position === 'front') {
          _prevCustomScript += `
        /* 自定义脚本 */
        ${script.sql}
        `
        } else {
          _backCustomScript += `
        /* 自定义脚本 */
        ${script.sql}
        `
        }
      })
      _backCustomScript += `
        aaa: select @ErrorCode as ErrorCode,@retmsg as retmsg`
      let sql = [...lineMap.values()].map(item => (`
        ${item.insert}
        ${item.selects.join(` union all
        `)}
      `))
      sql = sql.join('')
      sql = _prevCustomScript + sql
      sql = sql + _backCustomScript
      if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
        console.info(sql.replace(/\n\s{8}/ig, '\n'))
      }
      param.LText = sql
      if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
        param.LText = param.LText.replace(/\$@/ig, '/*')
        param.LText = param.LText.replace(/@\$/ig, '*/')
      } else {
        param.LText = param.LText.replace(/@\$|\$@/ig, '')
      }
      param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
      param.exec_type = 'y' // 后台解码
      param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
      param.secretkey = Utils.encrypt('', param.timestamp)
      param.LText = Utils.formatOptions(param.LText)
      param.menuname = btn.logLabel
      if (this.props.menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
        param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
      }
    } else {
      param.func = 's_ex_result_back'
      param.s_ex_result = [...lineMap.values()].map((item, index) => ({
        MenuID: btn.uuid,
        MenuName: btn.logLabel,
        TableName: item.table,
        LongText: window.btoa(window.encodeURIComponent(`${item.insert}  ${item.selects.join(` union all `)}`)),
        Sort: index + 1
      }))
      if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
        let sql = [...lineMap.values()].map(item => (`
          ${item.insert}
          ${item.selects.join(` union all
          `)}
        `))
        sql = sql.join('')
        console.info(sql.replace(/\n\s{10}/ig, '\n'))
      }
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (params.length === 0) {
          this.execSuccess(res)
          _resolve()
        } else {
          this.customLoopRequest(params, _resolve)
        }
      } else {
        this.execError(res)
        _resolve()
      }
    }, () => {
      this.updateStatus('over')
      _resolve()
    })
  }
  /**
   * @description 内部请求循环执行
   */
  innerLoopRequest = (params, btn, _resolve) => {
    if (!params && params.length === 0) return
    let param = params.shift()
    this.setState({
@@ -617,7 +1079,11 @@
        if (btn.sysInterface === 'true' && options.cloudServiceApi) {
          res.rduri = options.cloudServiceApi
        } else if (btn.sysInterface !== 'true') {
          res.rduri = btn.interface
          if (window.GLOB.systemType === 'production' && btn.proInterface) {
            res.rduri = btn.proInterface
          } else {
            res.rduri = btn.interface
          }
        }
        // 函数 s_sDataDictb_TBBack 云端验证
@@ -630,7 +1096,11 @@
        if (btn.sysInterface === 'true' && window.GLOB.mainSystemApi) {
          res.rduri = window.GLOB.mainSystemApi
        } else if (btn.sysInterface !== 'true') {
          res.rduri = btn.interface
          if (window.GLOB.systemType === 'production' && btn.proInterface) {
            res.rduri = btn.proInterface
          } else {
            res.rduri = btn.interface
          }
        }
      }
@@ -863,10 +1333,53 @@
    } else if (res.ErrCode === 'NM') {
      message.error(res.message || res.ErrMesg)
    }
    this.setState({
      loading: false
      loading: false,
      loadingNumber: ''
    })
    if (res.ErrCode === 'C') {
      const _this = this
      if (this.state.checkParam) {
        let param = this.state.checkParam
        confirm({
          title: res.message || res.ErrMesg,
          content: '继续执行?',
          onOk() {
            return new Promise(resolve => {
              Api.genericInterface(param).then((result) => {
                if (result.status) {
                  _this.execSuccess(result)
                } else {
                  _this.execError(result)
                }
                resolve()
              }, () => {
                _this.setState({
                  visible: false
                })
                resolve()
              })
            })
          },
          onCancel() {
            _this.setState({
              visible: false
            })
            if (btn.execError !== 'never') {
              MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execError, btn)
            }
          }
        })
        this.setState({checkParam: null})
        return
      } else {
        Modal.error({
          title: res.message || res.ErrMesg,
        })
      }
    }
    if (btn.execError !== 'never') {
      MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execError, btn)
@@ -1104,9 +1617,9 @@
   */
  getModels = () => {
    const { setting, BID, btn } = this.props
    const { btnconfig } = this.state
    const { btnconfig, visible } = this.state
    if (!this.state.visible || !btnconfig || !btnconfig.setting) return null
    if (!btnconfig || !btnconfig.setting) return null
    let title = btnconfig.setting.title
    let width = btnconfig.setting.width + 'vw'
@@ -1131,7 +1644,7 @@
        maskClosable={clickouter}
        getContainer={container}
        wrapClassName='action-modal'
        visible={this.state.visible}
        visible={visible}
        width={width}
        onOk={this.handleOk}
        confirmLoading={this.state.confirmLoading}
@@ -1154,7 +1667,7 @@
  render() {
    const { btn, show, style } = this.props
    const { loadingNumber, loading } = this.state
    const { loadingNumber, loading, visible } = this.state
    if (show === 'actionList') {
      return <div style={{display: 'inline-block'}} onClick={(e) => e.stopPropagation()}>
@@ -1164,8 +1677,8 @@
          loading={loading}
          className={'mk-btn mk-' + btn.class}
          onClick={() => {this.actionTrigger()}}
        >{loadingNumber ? `(${loadingNumber})` : '' + btn.label}</Button>
        {this.getModels()}
        >{(loadingNumber ? `(${loadingNumber})` : '') + btn.label}</Button>
        {visible ? this.getModels() : null}
      </div>
    } else if (show && show.indexOf('plus') > -1) {
      return <div className="mk-btn-wrap">
@@ -1176,7 +1689,7 @@
          style={{fontSize: show.substring(4) + 'px'}}
          onClick={() => {this.actionTrigger()}}
        ></Button>
        {this.getModels()}
        {visible ? this.getModels() : null}
      </div>
    } else { // icon、text、 all 卡片
      return <div style={{display: 'inline-block'}} onClick={(e) => e.stopPropagation()}>
@@ -1188,7 +1701,7 @@
          icon={show === 'text' ? '' : (btn.icon || '')}
          onClick={() => {this.actionTrigger()}}
        >{show === 'icon' && btn.icon ? '' : btn.label}</Button>
        {this.getModels()}
        {visible ? this.getModels() : null}
      </div>
    }
  }
src/tabviews/zshare/actionList/popupbutton/index.jsx
@@ -144,6 +144,10 @@
      primaryId: primaryId,
      visible: true
    })
    if (window.GLOB.systemType === 'production') {
      MKEmitter.emit('queryTrigger', {menuId: btn.uuid, name: '弹窗(标签)'})
    }
  }
  /**
src/tabviews/zshare/actionList/printbutton/index.jsx
@@ -160,6 +160,10 @@
    } else {
      this.triggerPrint(data)
    }
    if (window.GLOB.systemType === 'production') {
      MKEmitter.emit('queryTrigger', {menuId: btn.uuid, name: '标签打印'})
    }
  }
  /**
@@ -461,13 +465,21 @@
        if (btn.sysInterface === 'true' && options.cloudServiceApi) {
          res.rduri = options.cloudServiceApi
        } else if (btn.sysInterface !== 'true') {
          res.rduri = btn.interface
          if (window.GLOB.systemType === 'production' && btn.proInterface) {
            res.rduri = btn.proInterface
          } else {
            res.rduri = btn.interface
          }
        }
      } else {
        if (btn.sysInterface === 'true' && window.GLOB.mainSystemApi) {
          res.rduri = window.GLOB.mainSystemApi
        } else if (btn.sysInterface !== 'true') {
          res.rduri = btn.interface
          if (window.GLOB.systemType === 'production' && btn.proInterface) {
            res.rduri = btn.proInterface
          } else {
            res.rduri = btn.interface
          }
        }
      }
src/tabviews/zshare/actionList/tabbutton/index.jsx
@@ -148,6 +148,9 @@
    })
    MKEmitter.emit('openNewTab')
    if (window.GLOB.systemType === 'production') {
      MKEmitter.emit('queryTrigger', {menuId: btn.uuid, name: '标签页'})
    }
  }
  render() {
src/tabviews/zshare/fileupload/index.jsx
@@ -5,7 +5,6 @@
import { Upload, Button, Icon, Progress, notification } from 'antd'
import SparkMD5 from 'spark-md5'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import './index.scss'
let service = ''
@@ -25,51 +24,54 @@
  state = {
    percent: 0,
    showprogress: false
    showprogress: false,
    filelist: []
  }
  UNSAFE_componentWillMount () {
    const { value } = this.props
    if (!value) return
    this.setState({filelist: fromJS(value).toJS()})
  }
  onChange = ({ fileList }) => {
    const { onChange } = this.props
    fileList = fileList.map(item => {
      if (item.status === 'error' && /^<!DOCTYPE html>/.test(item.response)) {
        item.response = ''
      }
      return item
    })
    if (onChange) {
      onChange([...fileList])
    }
    this.setState({filelist: fileList})
    this.props.onChange(fileList)
  }
  onRemove = file => {
    const { value, onChange } = this.props
    const files = this.state.filelist.filter(v => v.uid !== file.uid)
    const files = value.filter(v => v.url !== file.url)
    if (onChange) {
      onChange(files)
    }
    this.setState({filelist: files})
    this.props.onChange(files)
  }
  onUpdate = (url) => {
    const { value, onChange } = this.props
    let filelist = fromJS(this.state.filelist).toJS()
    let filelist = fromJS(value).toJS()
    filelist[filelist.length -1].status = 'done'
    filelist[filelist.length -1].response = url
    filelist[filelist.length -1].origin = false
    if (onChange) {
      onChange([...filelist])
    if (filelist[filelist.length -1]) {
      filelist[filelist.length -1].status = 'done'
      filelist[filelist.length -1].response = url
      filelist[filelist.length -1].origin = false
    }
    this.setState({filelist})
    this.props.onChange(filelist)
  }
  onDelete = (msg) => {
    const { value, onChange } = this.props
    let filelist = value.filter(v => !v.url && !v.response)
    let filelist = this.state.filelist.filter(v => !v.url && !v.response)
    if (onChange) {
      onChange([...filelist])
    }
    this.setState({
      showprogress: false
    })
    this.setState({filelist, showprogress: false})
    this.props.onChange(filelist)
    notification.warning({
      top: 92,
@@ -122,6 +124,16 @@
    })
  }
  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 { accept } = this.props
@@ -165,7 +177,7 @@
    params.file.fileChunks = chunks                                // 记录所有chunks的长度
    if (!/^[A-Za-z0-9]+$/.test(params.file.fileName)) {            // 文件名称含有英文及数字之外字符时,名称系统生成
      params.file.fileName = moment().format('YYYYMMDDHHmmss') + Utils.getuuid().substr(14)
      params.file.fileName = moment().format('YYYYMMDDHHmmss') + this.getuuid()
    }
    totalFileReader.readAsArrayBuffer(file)
@@ -253,12 +265,12 @@
  }
  render() {
    const { value, maxFile, fileType, accept } = this.props
    const { showprogress, percent } = this.state
    const { maxFile, fileType, accept } = this.props
    const { showprogress, percent, filelist } = this.state
    let uploadable = 'fileupload-form-container '
    if (maxFile && maxFile > 0 && value && value.length >= maxFile) {
    if (maxFile && maxFile > 0 && filelist.length >= maxFile) {
      uploadable += 'limit-fileupload'
    }
@@ -266,7 +278,7 @@
      name: 'file',
      disabled: showprogress,
      listType: fileType,
      fileList: value,
      fileList: filelist,
      action: null,
      accept: accept || '',
      method: 'post',
src/tabviews/zshare/mutilform/customTextArea/index.jsx
New file
@@ -0,0 +1,72 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Input } from 'antd'
import './index.scss'
const { TextArea } = Input
class CustomTextArea extends Component {
  static propTpyes = {
    Item: PropTypes.bool,      // 表单
    onChange: PropTypes.func   // 数据切换
  }
  state = {
    value: '',
    encryption: 'false'
  }
  UNSAFE_componentWillMount () {
    let value = ''
    let encryption = 'false'
    if (this.props['data-__meta']) {
      value = this.props['data-__meta'].initialValue || ''
    }
    if (this.props.Item && this.props.Item.encryption === 'true') {
      encryption = 'true'
      if (value) {
        try {
          value = window.decodeURIComponent(window.atob(value))
        } catch {
          value = this.props['data-__meta'].initialValue || ''
        }
      }
    }
    this.setState({
      value,
      encryption
    })
  }
  onChange = (e) => {
    const { encryption } = this.state
    let val = e.target.value
    this.setState({ value: val })
    let _val = val
    if (encryption === 'true') {
      try {
        _val = window.btoa(window.encodeURIComponent(_val))
      } catch {
        _val = val
      }
    }
    this.props.onChange(_val)
  }
  render() {
    const { Item } = this.props
    const { value } = this.state
    return (
      <TextArea value={value} autoSize={{ minRows: 2, maxRows: Item.maxRows || 6 }} onChange={this.onChange} disabled={Item.readonly === 'true'} />
    )
  }
}
export default CustomTextArea
src/tabviews/zshare/mutilform/customTextArea/index.scss
src/tabviews/zshare/mutilform/index.jsx
@@ -9,16 +9,17 @@
import { formRule } from '@/utils/option.js'
import Utils from '@/utils/utils.js'
import asyncComponent from '@/utils/asyncComponent'
import asyncSpinComponent from '@/utils/asyncSpinComponent'
import './index.scss'
const { MonthPicker } = DatePicker
const { TextArea } = Input
const CheckCard = asyncComponent(() => import('./checkCard'))
const CustomSwitch = asyncComponent(() => import('./customSwitch'))
const CustomTextArea = asyncComponent(() => import('./customTextArea'))
const FileUpload = asyncComponent(() => import('../fileupload'))
const ColorSketch = asyncComponent(() => import('@/mob/colorsketch'))
// const Editor = asyncComponent(() => import('@/components/editor'))
const Editor = asyncSpinComponent(() => import('@/components/editor'))
class MainSearch extends Component {
  static propTpyes = {
@@ -39,7 +40,6 @@
    writein: null,   // 执行时是否填入默认sql
    fieldlen: null,  // 字段长度
    formlist: [],    // 表单项
    encrypts: [],    // 加密字段
    intercepts: [],  // 截取字段
    record: {}       // 记录下拉表单关联字段,用于数据写入
  }
@@ -68,7 +68,6 @@
    let writein = {}
    let fieldlen = {}
    let formlist = []
    let encrypts = []
    let intercepts = []
    let _inputfields = []
@@ -99,9 +98,6 @@
        _inputfields.push(item.field)
      } else if (item.type === 'textarea') {
        _inputfields.push(item.field)
        if (item.encryption === 'true') {                                // 加密字段
          encrypts.push(item.field)
        }
      } else if (item.type === 'link') {
        linkFields[item.linkField] = linkFields[item.linkField] || []
        linkFields[item.linkField].push(item.field)
@@ -121,7 +117,7 @@
      }
      let _fieldlen = item.fieldlength || 50
      if (item.type === 'textarea' || item.type === 'fileupload' || item.type === 'multiselect') {
      if (item.type === 'textarea' || item.type === 'fileupload' || item.type === 'multiselect' || item.type === 'brafteditor') {
        _fieldlen = item.fieldlength || 512
      } else if (item.type === 'number') {
        _fieldlen = item.decimal ? item.decimal : 0
@@ -200,14 +196,7 @@
        }
      }
      // 加密字段,解密处理
      if (item.type === 'textarea' && item.encryption === 'true' && newval !== '') {
        try {
          newval = window.decodeURIComponent(window.atob(newval))
        } catch (e) {
          console.warn(e)
        }
      } else if (item.type === 'switch' && newval !== '') { // 开关只接收固定值
      if (item.type === 'switch' && newval !== '') { // 开关只接收固定值
        if (newval !== item.closeVal && newval !== item.openVal) {
          newval = ''
        } else if (newval === item.openVal) {
@@ -239,6 +228,10 @@
      if (['select', 'link', 'multiselect', 'radio', 'checkbox', 'checkcard'].includes(item.type) && item.resourceType === '1') {
        deForms.push(item)
      } else if (['select', 'link', 'radio'].includes(item.type) && item.resourceType !== '1') { // 选中第一项
        if (item.initval && item.initval.indexOf('$first') > -1) {
          item.initval = item.options[0] ? item.options[0].Value : ''
        }
      }
      return item
@@ -260,14 +253,13 @@
    })
    this.setState({
      readtype: readtype,
      datatype: datatype,
      readin: readin,
      writein: writein,
      fieldlen: fieldlen,
      encrypts: encrypts,
      intercepts: intercepts,
      formlist: formlist
      readin,
      writein,
      readtype,
      datatype,
      fieldlen,
      intercepts,
      formlist
    }, () => {
      if (action.setting && action.setting.focus) {
        this.selectInput(action.setting.focus, 'init')
@@ -461,6 +453,7 @@
        }
        return item
      })
      let values = []
      this.setState({
        formlist: _formlist.map(item => {
@@ -469,8 +462,21 @@
          } else if (['select', 'multiselect', 'radio', 'checkbox', 'checkcard'].includes(item.type)) {
            item.options = item.oriOptions
          }
          if (['select', 'link', 'radio'].includes(item.type) && item.initval && item.initval.indexOf('$first') > -1) { // 选中第一项
            item.initval = item.options[0] ? item.options[0].Value : ''
            values.push({field: item.field, value: item.initval})
          }
          return item
        })
      }, () => {
        if (values.length === 0) return
        let fieldsvalue = {}
        values.forEach(item => {
          if (this.props.form.getFieldValue(item.field) !== undefined) {
            fieldsvalue[item.field] = item.value
          }
        })
        this.props.form.setFieldsValue(fieldsvalue)
      })
    })
  }
@@ -554,6 +560,7 @@
        }
        return item
      })
      let values = []
      this.setState({
        formlist: _formlist.map(item => {
@@ -562,8 +569,21 @@
          } else if (['select', 'multiselect', 'radio', 'checkbox', 'checkcard'].includes(item.type)) {
            item.options = item.oriOptions
          }
          if (['select', 'link', 'radio'].includes(item.type) && item.initval && item.initval.indexOf('$first') > -1) { // 选中第一项
            item.initval = item.options[0] ? item.options[0].Value : ''
            values.push({field: item.field, value: item.initval})
          }
          return item
        })
      }, () => {
        if (values.length === 0) return
        let fieldsvalue = {}
        values.forEach(item => {
          if (this.props.form.getFieldValue(item.field) !== undefined) {
            fieldsvalue[item.field] = item.value
          }
        })
        this.props.form.setFieldsValue(fieldsvalue)
      })
    })
  }
@@ -633,30 +653,18 @@
    }
    if (subfields.length === 0) {
      if (Object.keys(fieldsvalue).length > 0) {
        this.props.form.setFieldsValue(fieldsvalue)
      }
      if (Object.keys(_record).length > 0) {
        this.setState({
          record: {...record, ..._record}
        })
      }
      this.props.form.setFieldsValue(fieldsvalue)
      this.setState({
        record: {...record, ..._record}
      })
    } else {
      let result = this.resetform(formlist, subfields, 0, fieldsvalue)
      if (Object.keys(result.fieldsvalue).length > 0) {
        this.props.form.setFieldsValue(fieldsvalue)
      }
      let _param = {
        formlist: result.formlist
      }
      if (Object.keys(_record).length > 0) {
        _param.record = {...record, ..._record}
      }
      this.setState(_param)
      this.props.form.setFieldsValue(fieldsvalue)
      this.setState({
        formlist: result.formlist,
        record: {...record, ..._record}
      })
    }
    this.setState({}, () => {
@@ -1182,7 +1190,34 @@
                  },
                  ..._rules
                ]
              })(<TextArea autoSize={{ minRows: 2, maxRows: item.maxRows || 6 }} disabled={item.readonly === 'true'} />)}
              })(<CustomTextArea Item={item} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'brafteditor') {
        let _max = item.fieldlength || 512
        fields.push(
          <Col span={24} key={index}>
            <Form.Item label={item.hidelabel !== 'true' && item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip> : (item.hidelabel !== 'true' ? item.label : '')
            }>
              {getFieldDecorator(item.field, {
                initialValue: item.initval || '',
                rules: [
                  {
                    required: item.required === 'true',
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  },
                  {
                    max: _max,
                    message: formRule.input.formMessage.replace('@max', _max)
                  }
                ]
              })(<Editor Item={item}/>)}
            </Form.Item>
          </Col>
        )
@@ -1194,7 +1229,6 @@
  handleConfirm = () => {
    const { record, intercepts, writein } = this.state
    let _encrypts = fromJS(this.state.encrypts).toJS()
    let _format = {
      date: 'YYYY-MM-DD',
      datemonth: 'YYYY-MM',
@@ -1225,7 +1259,6 @@
              let _val = item.initval
              if (record.hasOwnProperty(item.field)) {
                _val = record[item.field]
                _encrypts = _encrypts.filter(_field => _field !== item.field) // 隐藏字段,不参与加密处理
              }
              
              _item = {
@@ -1251,12 +1284,18 @@
            if (!_item) return
            if (item.type === 'date' || item.type === 'datemonth' || item.type === 'datetime') {
              if (_item.value && _item.value.format) {
                _item.value = _item.value.format(_format[item.type])
              } else if (!_item.value) {
            if (_item.value === undefined) {
              _item.value = ''
            } else if (item.type === 'date' || item.type === 'datemonth' || item.type === 'datetime') {
              if (!_item.value) {
                _item.value = ''
              } else if (_item.value.format) {
                _item.value = _item.value.format(_format[item.type])
              }
            } else if (item.type === 'text' && _item.value && typeof(_item.value) === 'string') { // 特殊字段替换
              _item.value = _item.value.replace(/^(\s*)@appkey@(\s*)$/ig, window.GLOB.appkey)
              _item.value = _item.value.replace(/^(\s*)@SessionUid@(\s*)$/ig, (localStorage.getItem('SessionUid') || ''))
              _item.value = _item.value.replace(/^(\s*)@bid@(\s*)$/ig, (this.props.BID || ''))
            }
            search.push(_item)
@@ -1279,19 +1318,20 @@
            }
            let _value = ''
            if (this.state.datatype[key] === 'datetime') {
            let _type = this.state.datatype[key]
            if (_type === 'datetime') {
              _value = values[key] ? moment(values[key]).format('YYYY-MM-DD HH:mm:ss') : ''
            } else if (this.state.datatype[key] === 'datemonth') {
            } else if (_type === 'datemonth') {
              _value = values[key] ? moment(values[key]).format('YYYY-MM') : ''
            } else if (this.state.datatype[key] === 'date') {
            } else if (_type === 'date') {
              _value = values[key] ? moment(values[key]).format('YYYY-MM-DD') : ''
            } else if (this.state.datatype[key] === 'number') {
            } else if (_type === 'number') {
              _value = values[key]
            } else if (this.state.datatype[key] === 'multiselect' || this.state.datatype[key] === 'checkbox') {
            } else if (_type === 'multiselect' || _type === 'checkbox') {
              _value = values[key] ? values[key].join(',') : ''
            } else if (this.state.datatype[key] === 'fileupload') {
            } else if (_type === 'fileupload') {
              let vals = []
              if (values[key] && values[key].length > 0) {
@@ -1305,15 +1345,23 @@
              }
              _value = vals.join(',')
            } else if (this.state.datatype[key] === 'text' || this.state.datatype[key] === 'textarea') {
            } else if (_type === 'text' || _type === 'textarea') {
              _value = values[key].replace(/\t*|\v*/g, '') // 去除制表符
              if (intercepts.includes(key)) {              // 去除首尾空格
                _value = _value.replace(/(^\s*|\s*$)/g, '')
              }
              if (_type === 'text' && _value) { // 特殊字段替换
                _value = _value.replace(/^(\s*)@appkey@(\s*)$/ig, window.GLOB.appkey)
                _value = _value.replace(/^(\s*)@SessionUid@(\s*)$/ig, (localStorage.getItem('SessionUid') || ''))
                _value = _value.replace(/^(\s*)@bid@(\s*)$/ig, (this.props.BID || ''))
              }
            } else {
              _value = values[key]
            }
            if (_value === undefined) {
              _value = ''
            }
            search.push({
@@ -1326,23 +1374,6 @@
              value: _value
            })
          })
          // 含有加密字段时,对表单值进行加密
          if (_encrypts && _encrypts.length > 0) {
            search = search.map(item => {
              let _value = item.value
              if (_encrypts.includes(item.key)) {
                try {
                  _value = window.btoa(window.encodeURIComponent(_value))
                } catch (e) {
                  console.warn(e)
                }
              }
              item.value = _value
              return item
            })
          }
          resolve(search)
        } else {
@@ -1373,7 +1404,6 @@
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form main-form-field" id="main-form-box">
        <Row className={'cols' + cols} gutter={24}>{this.getFields()}</Row>
        {/* <Editor /> */}
      </Form>
    )
  }
src/tabviews/zshare/mutilform/index.scss
@@ -81,6 +81,11 @@
      min-width: 100px;
    }
  }
  .normal-braft-editor {
    border: 1px solid #d9d9d9;
    border-radius: 4px;
    overflow-x: hidden;
  }
  p {
    color: #1890ff;
    border-bottom: 1px solid #d9d9d9;
src/tabviews/zshare/normalTable/index.jsx
@@ -182,7 +182,7 @@
    if (rowspans.length === 0) {
      rowspans = null
    }
    this.setState({
      columns: _columns,
      pageSize: pageSize ? pageSize : 10,
@@ -481,6 +481,21 @@
      }
      if (item.linkThdMenu || item.linkurl) {
        if (item.rowspan === 'true') {
          return {
            children: (
              <div className={className}>
                <div className="baseboard link-menu" onDoubleClick={(e) => this.triggerLink(e, item, record)}></div>
                <div className="content link-menu" onDoubleClick={(e) => this.triggerLink(e, item, record)}>
                  {content}
                </div>
              </div>
            ),
            props: {
              rowSpan: record['$$' + item.field],
            }
          }
        }
        return (
          <div className={className}>
            <div className="baseboard link-menu" onDoubleClick={(e) => this.triggerLink(e, item, record)}></div>
@@ -490,6 +505,21 @@
          </div>
        )
      } else {
        if (item.rowspan === 'true') {
          return {
            children: (
              <div className={className}>
                <div className="baseboard"></div>
                <div className="content">
                  {content}
                </div>
              </div>
            ),
            props: {
              rowSpan: record['$$' + item.field],
            }
          }
        }
        return (
          <div className={className}>
            <div className="baseboard"></div>
@@ -998,13 +1028,13 @@
        let preItem = data[index - 1]
        rowspans.forEach((cell, i) => {
          if (i === 0) {
            if ((item[cell] || item[cell] === 0) && preItem[cell] === item[cell]) {
            if (preItem[cell] === item[cell]) {
              item['$' + cell] = preItem['$' + cell] + 1
            } else {
              item['$' + cell] = 1
            }
          } else {
            if ((item[cell] || item[cell] === 0) && preItem[cell] === item[cell]) {
            if (preItem[cell] === item[cell]) {
              item['$' + cell] = preItem['$' + cell] + 1
            } else {
              item['$' + cell] = 1
src/tabviews/zshare/settingcomponent/index.jsx
@@ -5,7 +5,7 @@
import Api from '@/api'
import MKEmitter from '@/utils/events.js'
import UtilsUpdate from '@/utils/utils-update.js'
import { updateSubTable } from '@/utils/utils-update.js'
import options from '@/store/options.js'
import asyncComponent from '@/utils/asyncSpinComponent'
import './index.scss'
@@ -252,7 +252,7 @@
          if (!subconfig || !subconfig.enabled) return
          subconfig = UtilsUpdate.updateSubTable(subconfig)
          subconfig = updateSubTable(subconfig)
          let _comp = {title: res.tab.label, uuid: res.tab.uuid, action: []}
src/templates/calendarconfig/tabcomponent/tabform/index.jsx
@@ -23,9 +23,9 @@
  UNSAFE_componentWillMount () {
    const { formlist } = this.props
    let type = formlist.filter(cell => cell.key === 'type')[0].initVal
    // let type = formlist.filter(cell => cell.key === 'type')[0].initVal
    let _tabs = this.props.tabs.filter(tab => tab.type === type)
    let _tabs = this.props.tabs.filter(tab => tab.type === 'SubTable')
    this.setState({
      formlist: formlist.map(item => {
@@ -247,6 +247,7 @@
          if (!values.linkTab) { // 没有关联标签(新建时),创建新标签Id
            values.linkTab = Utils.getuuid()
          }
          values.type = 'SubTable' // 类型为子表
          resolve(values)
        } else {
src/templates/comtableconfig/index.jsx
@@ -9,7 +9,7 @@
import Api from '@/api'
import Utils from '@/utils/utils.js'
import UtilsUpdate from '@/utils/utils-update.js'
import { updateCommonTable } from '@/utils/utils-update.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
@@ -90,7 +90,7 @@
    _config.easyCode = _config.easyCode || ''
    // 版本兼容
    _config = UtilsUpdate.updateCommonTable(_config)
    _config = updateCommonTable(_config)
    
    let _oriActions = []
    if (_config.type === 'user') {
@@ -819,7 +819,7 @@
        _view = item.tabTemplate    // 新标签页模板
        _btnTab = item
      } else if (type === 'button' && item.OpenType === 'popview') {
        _view = item.tabType        // 新弹窗标签模板
        _view = item.tabType || 'SubTable' // 新弹窗标签模板 tabType 属性已去除
        uuid = item.linkTab
        isbutton = false
      } else if (type === 'tab') {
@@ -973,9 +973,39 @@
      }
    })
    
    config.action && config.action.forEach((btn) => {
      if (['prompt', 'exec', 'pop'].includes(btn.OpenType) && btn.Ot === 'required' && btn.verify && btn.verify.scripts && btn.verify.scripts.length > 0) {
        let hascheck = false
        btn.verify.scripts.forEach(item => {
          if (item.status === 'false') return
          if (/\$check@|@check\$/ig.test(item.sql)) {
            hascheck = true
          }
        })
        if (hascheck) {
          notification.warning({
            top: 92,
            message: `可选择多行的按钮《${btn.label}》中 $check@ 或 @check$ 将不会生效!`,
            duration: 5
          })
        }
      }
      if (btn.intertype === 'custom' && btn.callbackType === 'script' && (!btn.verify || !btn.verify.cbScripts || !btn.verify.cbScripts.filter(item => item.status !== 'false').length === 0)) {
        notification.warning({
          top: 92,
          message: `按钮《${btn.label}》未设置回调脚本, 将不会生效!`,
          duration: 5
        })
      }
    })
    if (config.setting.interType === 'system' && config.setting.default !== 'false' && !config.setting.dataresource) {
      return '菜单尚未设置数据源,不可启用!'
    if ((config.setting.interType === 'system' || config.setting.requestMode === 'system') && config.setting.default === 'false' && config.setting.scripts && config.setting.scripts.filter(item => item.status !== 'false').length === 0) {
      return '数据源中不执行默认sql,且未添加自定义脚本,不可启用!'
    } else if (config.setting.interType === 'custom' && config.setting.procMode !== 'inner' && config.setting.preScripts && config.setting.preScripts.filter(item => item.status !== 'false').length === 0) {
      return '数据源未设置前置脚本,不可启用!'
    } else if (config.setting.interType === 'custom' && config.setting.callbackType === 'script' && config.setting.cbScripts && config.setting.cbScripts.filter(item => item.status !== 'false').length === 0) {
      return '数据源未设置回调脚本,不可启用!'
    } else if (!config.setting.primaryKey) {
      return '菜单尚未设置主键,不可启用!'
    } else if (config.columns.length === 0) {
src/templates/formtabconfig/actionform/index.jsx
@@ -333,8 +333,14 @@
          <Col span={24} key={index}>
            <Form.Item label={item.label} className="textarea">
              {getFieldDecorator(item.key, {
                initialValue: item.initVal
              })(<TextArea rows={4} />)}
                initialValue: item.initVal,
                rules: [
                  {
                    required: item.readonly ? false : !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<TextArea rows={4} readOnly={item.readonly}/>)}
            </Form.Item>
          </Col>
        )
@@ -387,7 +393,7 @@
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-action-form" id="winter">
      <Form {...formItemLayout} className="formtab-action-list-form" id="winter">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
src/templates/formtabconfig/actionform/index.scss
@@ -1,4 +1,4 @@
.ant-advanced-search-form.commontable-action-form {
.formtab-action-list-form {
  min-height: 190px;
  .superconfig {
    color: #1890ff;
@@ -30,4 +30,12 @@
      top: 4.5px;
    }
  }
  .ant-input:read-only {
    background: #fafafa;
    resize: none;
  }
  .ant-input:read-only:hover, .ant-input:read-only:focus {
    border-color: #d9d9d9;
    box-shadow: none;
  }
}
src/templates/formtabconfig/index.jsx
@@ -507,7 +507,6 @@
    let ableField = usefulFields.join(', ')
    let functip = <div>
      <p style={{marginBottom: '5px'}}>{this.state.dict['model.tooltip.func.innerface'].replace('@ableField', ableField)}</p>
      <p>{this.state.dict['model.tooltip.func.outface']}</p>
    </div>
    this.setState({
src/templates/modalconfig/dragelement/card.jsx
@@ -3,12 +3,15 @@
import { Icon, Select, DatePicker, Input, InputNumber, Button, Popover, Switch, Radio, Checkbox } from 'antd'
import moment from 'moment'
import CheckCard from '../checkCard'
import ColorSketch from '@/mob/colorsketch'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { MonthPicker } = DatePicker
const { TextArea } = Input
const Editor = asyncComponent(() => import('@/components/editor'))
const ColorSketch = asyncComponent(() => import('@/mob/colorsketch'))
const CheckCard = asyncComponent(() => import('../checkCard'))
const Card = ({ id, card, cols, moveCard, findCard, editCard, closeCard, copyCard, showField }) => {
  const originalIndex = findCard(id).index
@@ -63,7 +66,7 @@
  let wrapCol = 'ant-col-sm-16'
  let isEntireLine = false
  if (card.entireLine === 'true' || ['textarea', 'hint', 'checkcard'].includes(card.type)) {
  if (card.entireLine === 'true' || ['textarea', 'hint', 'checkcard', 'brafteditor'].includes(card.type)) {
    isEntireLine = true
  }
@@ -77,6 +80,9 @@
    } else if (cols === '4') {
      labelCol = 'ant-col-sm-2'
      wrapCol = 'ant-col-sm-22'
    }
    if (card.hidelabel === 'true') {
      wrapCol = 'ant-col-sm-24'
    }
  }
@@ -97,6 +103,8 @@
    formItem = (<DatePicker showTime value={card.initval ? moment().subtract(card.initval, 'days') : null} />)
  } else if (card.type === 'textarea') {
    formItem = (<TextArea value={card.initval} autoSize={{ minRows: 2, maxRows: 6 }} />)
  } else if (card.type === 'brafteditor') {
    formItem = (<Editor />)
  } else if (card.type === 'fileupload') {
    formItem = (<Button style={{marginTop: '3px'}}><Icon type="upload" /> 点击上传 </Button>)
  } else if (card.type === 'funcvar') {
@@ -141,11 +149,11 @@
      <div className="page-card" style={{ opacity: opacity}}>
        <div ref={node => drag(drop(node))}>
          {<div className="ant-row ant-form-item">
            <div className={'ant-col ant-form-item-label ant-col-xs-24 ' + labelCol}>
            {card.hidelabel !== 'true' ? <div className={'ant-col ant-form-item-label ant-col-xs-24 ' + labelCol}>
              {card.label ? <label className={card.required === 'true' ? 'required' : ''}>{card.tooltip ? 
                <Icon type="question-circle" /> : null}
                {card.label}</label> : null}
            </div>
            </div> : null}
            <div className={'ant-col ant-form-item-control-wrapper ant-col-xs-24 ' + wrapCol}>
              {formItem}
              {showField ? card.field : ''}
src/templates/modalconfig/dragelement/index.jsx
@@ -128,7 +128,7 @@
      {cards.map(card => {
        let isEntireLine = false
        if (card.entireLine === 'true' || ['textarea', 'hint', 'checkcard'].includes(card.type)) {
        if (card.entireLine === 'true' || ['textarea', 'hint', 'checkcard', 'brafteditor'].includes(card.type)) {
          isEntireLine = true
        }
        
src/templates/modalconfig/index.scss
@@ -252,6 +252,10 @@
                  min-width: 100px;
                }
              }
              .normal-braft-editor {
                border: 1px solid #d9d9d9;
                border-radius: 4px;
              }
            }
            .ant-form-item-control-wrapper::after {
              content: '';
src/templates/modalconfig/source.jsx
@@ -170,6 +170,12 @@
  },
  {
    type: 'form',
    label: '富文本',
    subType: 'brafteditor',
    url: ''
  },
  {
    type: 'form',
    label: CommonDict['header.form.funcvar'],
    subType: 'funcvar',
    url: ''
src/templates/sharecomponent/actioncomponent/actionform/index.jsx
@@ -36,6 +36,7 @@
    interType: null, // 接口类型:内部、外部
    funcType: null,  // 功能类型
    position: null,  // 按钮位置
    procMode: null,  // 外部接口参数处理方式
    pageTemplate: null,
    requireOptions: [{
      value: 'notRequired',
@@ -69,6 +70,19 @@
    }, {
      value: 'custom',
      text: this.props.dict['header.form.custom']
    }],
    interTypeOptions: [{
      value: 'system',
      text: this.props.dict['model.interface.system']
    }, {
      value: 'inner',
      text: this.props.dict['model.interface.inner']
    }, {
      value: 'outer',
      text: this.props.dict['model.interface.outer']
    }, {
      value: 'custom',
      text: '自定义'
    }]
  }
@@ -77,8 +91,9 @@
    const { card } = this.props
    let _opentype = card.OpenType               // 打开方式
    let _tabType = card.tabType || 'SubTable'   // 按钮为弹窗(标签)时,标签的类型
    // let _tabType = card.tabType || 'SubTable'   // 按钮为弹窗(标签)时,标签的类型
    let _intertype = card.intertype || 'system' // 接口类型
    let _procMode = card.procMode || 'system'   // 参数处理方式
    let _funcType = card.funcType || ''         // 功能按钮默认类型
    let _tabTemplate = card.tabTemplate         // 按钮为标签页时,标签类型:三级菜单或表单标签页
    let _pageTemplate = card.pageTemplate       // 新页面类型
@@ -90,20 +105,26 @@
      _opentype = 'tab'
    }
    let _tabs = this.props.tabs.filter(tab => tab.type === _tabType)
    let _options = this.getOptions(_opentype, _intertype, _funcType, _pageTemplate, _tabTemplate)
    let _tabs = this.props.tabs.filter(tab => tab.type === 'SubTable')
    let _options = this.getOptions(_opentype, _intertype, _funcType, _pageTemplate, _tabTemplate, _procMode)
    
    this.setState({
      openType: _opentype,
      pageTemplate: _pageTemplate,
      interType: _intertype,
      procMode: _procMode,
      position: card.position || 'toolbar',
      funcType: _funcType,
      formlist: this.props.formlist.map(item => {
        if (item.key === 'class') {
          item.options = btnClasses
        } else if (item.key === 'innerFunc' && _procMode === 'inner') {
          item.required = true
        } else if (item.key === 'icon') {
          item.options = btnIcons
        } else if (item.key === 'intertype') {
          let iscustom = ['pop', 'prompt', 'exec'].includes(_opentype)
          item.options = this.state.interTypeOptions.filter(op => (iscustom || op.value !== 'custom'))
        } else if (item.key === 'Ot') {
          if (card.position === 'grid' || _pageTemplate === 'pay') { // 行级按钮、支付按钮,只能选单行
            item.options = this.state.requireOptions.filter(op => ['requiredSgl'].includes(op.value))
@@ -153,7 +174,7 @@
    }
  }
  getOptions = (_opentype, _intertype, _funcType, _pageTemplate, _tabTemplate) => {
  getOptions = (_opentype, _intertype, _funcType, _pageTemplate, _tabTemplate, _procMode) => {
    let _options = fromJS(actionTypeOptions[_opentype]).toJS() // 选项列表
    
    if (_opentype === 'innerpage') {         // 新页面,可选模板(自定义时,可填入外部链接)
@@ -168,13 +189,13 @@
      }
    } else if (_opentype === 'excelOut') {    // 导入导出
      if (_intertype === 'outer') {
        _options.push('innerFunc', 'sysInterface', 'interface', 'outerFunc')
        _options.push('innerFunc', 'sysInterface', 'interface', 'proInterface', 'outerFunc')
      } else if (_intertype === 'inner') {
        _options.push('innerFunc')
      }
    } else if (_opentype === 'excelIn') {    // 导入导出
      if (_intertype === 'outer') {
        _options.push('innerFunc', 'sysInterface', 'interface', 'outerFunc', 'callbackFunc')
        _options.push('innerFunc', 'sysInterface', 'interface', 'proInterface', 'outerFunc', 'callbackFunc')
      } else if (_intertype === 'inner') {
        _options.push('innerFunc')
      }
@@ -182,14 +203,21 @@
      if (_funcType === 'print') {
        _options.push('execMode', 'intertype', 'Ot', 'execSuccess', 'execError')
        if (_intertype === 'outer') {
          _options.push('innerFunc', 'sysInterface', 'interface', 'outerFunc', 'callbackFunc')
          _options.push('innerFunc', 'sysInterface', 'interface', 'proInterface', 'outerFunc', 'callbackFunc')
        } else if (_intertype === 'inner') {
          _options.push('innerFunc')
        }
      }
    } else if (_opentype !== 'popview') { // 打开方式不是弹窗页面时
      if (_intertype === 'outer') {
        _options.push('innerFunc', 'sysInterface', 'interface', 'outerFunc', 'callbackFunc')
      if (_intertype === 'custom') {
        _options.push('procMode', 'interface', 'callbackType', 'cbTable', 'proInterface', 'method')
        if (_procMode === 'system') {
          _options.push('sql', 'sqlType')
        } else {
          _options.push('innerFunc')
        }
      } else if (_intertype === 'outer') {
        _options.push('innerFunc', 'sysInterface', 'interface', 'proInterface', 'outerFunc', 'callbackFunc')
      } else if (_intertype === 'inner') {
        _options.push('innerFunc')
      } else {
@@ -205,24 +233,27 @@
  }
  /**
   * @description 下拉切换
   * 1、打开方式切换,重置可见表单和表单值
   * 2、显示位置切换,重置选择行
   * 3、切换标签类型,重置可选标签
   * @description 切换
   */
  openTypeChange = (key, value) => {
  optionChange = (key, value) => {
    const { openType, funcType, procMode } = this.state
    const { card } = this.props
    if (key === 'OpenType') {
      let _options = this.getOptions(value, this.state.interType, this.state.funcType, this.state.pageTemplate, card.tabTemplate)
      let _options = this.getOptions(value, 'system', '', this.state.pageTemplate, card.tabTemplate, 'system')
      let _fieldval = {}
      let _formlist = this.state.formlist.map(item => {
        item.hidden = !_options.includes(item.key)
        if (item.key === 'intertype') {
          let iscustom = ['pop', 'prompt', 'exec'].includes(value)
          item.options = this.state.interTypeOptions.filter(op => (iscustom || op.value !== 'custom'))
        }
        if (item.hidden) return item
        if (item.key === 'intertype') {
          _fieldval.intertype = this.state.interType
          _fieldval.intertype = 'system'
        } else if (item.key === 'Ot') {
          if (this.state.position === 'grid' || this.state.pageTemplate === 'pay') {
            item.options = this.state.requireOptions.filter(op => ['requiredSgl'].includes(op.value))
@@ -253,6 +284,9 @@
      this.setState({
        openType: value,
        funcType: '',
        intertype: 'system',
        procMode: 'system',
        formlist: _formlist
      }, () => {
        if (value === 'excelIn') {
@@ -275,10 +309,10 @@
            if (value === 'grid' || this.state.pageTemplate === 'pay') {
              item.options = this.state.requireOptions.filter(op => ['requiredSgl'].includes(op.value))
              _fieldval.Ot = 'requiredSgl'
            } else if (this.state.openType === 'innerpage' && this.state.pageTemplate === 'billprint') {
            } else if (openType === 'innerpage' && this.state.pageTemplate === 'billprint') {
              item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl', 'required'].includes(op.value))
              _fieldval.Ot = 'requiredSgl'
            } else if (['innerpage', 'blank', 'tab', 'popview'].includes(this.state.openType)) {
            } else if (['innerpage', 'blank', 'tab', 'popview'].includes(openType)) {
              item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl'].includes(op.value))
              _fieldval.Ot = 'requiredSgl'
            } else {
@@ -290,28 +324,28 @@
      }, () => {
        this.props.form.setFieldsValue(_fieldval)
      })
    } else if (key === 'tabType') {
      let _tabs = this.props.tabs.filter(tab => tab.type === value)
      let _fieldval = {}
    // } else if (key === 'tabType') {
    //   let _tabs = this.props.tabs.filter(tab => tab.type === value)
    //   let _fieldval = {}
      this.setState({
        formlist: this.state.formlist.map(item => {
          if (item.key === 'linkTab') {
            item.options = [
              {
                value: '',
                text: '新建'
              },
              ..._tabs
            ]
          }
          return item
        })
      }, () => {
        this.props.form.setFieldsValue(_fieldval)
      })
    //   this.setState({
    //     formlist: this.state.formlist.map(item => {
    //       if (item.key === 'linkTab') {
    //         item.options = [
    //           {
    //             value: '',
    //             text: '新建'
    //           },
    //           ..._tabs
    //         ]
    //       }
    //       return item
    //     })
    //   }, () => {
    //     this.props.form.setFieldsValue(_fieldval)
    //   })
    } else if (key === 'funcType') {
      let _options = this.getOptions('funcbutton', this.state.interType, value, card.pageTemplate, card.tabTemplate)
      let _options = this.getOptions('funcbutton', this.state.interType, value, card.pageTemplate, card.tabTemplate, procMode)
      let _fieldval = {}
      this.setState({
@@ -365,7 +399,7 @@
        this.props.form.setFieldsValue(_fieldval)
      })
    } else if (key === 'pageTemplate') {
      let _options = this.getOptions('innerpage', this.state.interType, this.state.funcType, value, card.tabTemplate)
      let _options = this.getOptions('innerpage', this.state.interType, this.state.funcType, value, card.tabTemplate, procMode)
      let _fieldval = {}
      this.setState({
@@ -405,15 +439,8 @@
          return item
        })
      })
    }
  }
  onChange = (e, key) => {
    const { openType, funcType } = this.state
    let value = e.target.value
    if (key === 'intertype') {
      let _options = this.getOptions(openType, value, funcType, '', '')
    } else if (key === 'intertype') {
      let _options = this.getOptions(openType, value, funcType, '', '', procMode)
      this.setState({
        interType: value,
@@ -431,10 +458,20 @@
          }
          return item
        })
      }, () => {
        if (this.props.form.getFieldValue('sqlType') !== undefined) {
          this.props.form.setFieldsValue({sqlType: ''})
        }
      })
    } else if (key === 'procMode') {
      let _options = this.getOptions(openType, this.state.interType, funcType, '', '', value)
      this.setState({
        procMode: value,
        formlist: this.state.formlist.map(item => {
          item.hidden = !_options.includes(item.key)
          if (item.key === 'innerFunc') {
            item.required = true
          }
          return item
        })
      })
    } else if (key === 'sysInterface') {
      if (value === 'true') {
@@ -560,7 +597,7 @@
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children[2].toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={(value) => {this.openTypeChange(item.key, value)}}
                  onChange={(value) => {this.optionChange(item.key, value)}}
                  getPopupContainer={() => document.getElementById('winter')}
                >
                  {item.options.map((option, index) =>
@@ -586,7 +623,7 @@
                  }
                ]
              })(
                <Radio.Group onChange={(e) => {this.onChange(e, item.key)}} disabled={item.readonly}>
                <Radio.Group onChange={(e) => {this.optionChange(item.key, e.target.value)}} disabled={item.readonly}>
                  {
                    item.options.map(option => {
                      return (
@@ -604,8 +641,14 @@
          <Col span={24} key={index}>
            <Form.Item label={item.label} className="textarea">
              {getFieldDecorator(item.key, {
                initialValue: item.initVal
              })(<TextArea rows={4} />)}
                initialValue: item.initVal,
                rules: [
                  {
                    required: item.readonly ? false : !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<TextArea rows={2} readOnly={item.readonly} />)}
            </Form.Item>
          </Col>
        )
@@ -688,7 +731,7 @@
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-action-form" id="winter">
      <Form {...formItemLayout} className="normal-action-list-form" id="winter">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
src/templates/sharecomponent/actioncomponent/actionform/index.scss
@@ -1,4 +1,4 @@
.ant-advanced-search-form.commontable-action-form {
.normal-action-list-form {
  min-height: 190px;
  .superconfig {
    color: #1890ff;
@@ -10,6 +10,12 @@
    }
    .ant-col-sm-17 {
      width: 86%;
    }
  }
  .ant-radio-group {
    white-space: nowrap;
    .ant-radio-wrapper {
      margin-right: 4px;
    }
  }
  .ant-input-number {
@@ -30,4 +36,12 @@
      top: 4.5px;
    }
  }
  .ant-input:read-only {
    background: #fafafa;
    resize: none;
  }
  .ant-input:read-only:hover, .ant-input:read-only:focus {
    border-color: #d9d9d9;
    box-shadow: none;
  }
}
src/templates/sharecomponent/actioncomponent/index.jsx
@@ -154,7 +154,6 @@
    let ableField = usefulFields.join(', ')
    let functip = <div>
      <p style={{marginBottom: '5px'}}>{this.state.dict['model.tooltip.func.innerface'].replace('@ableField', ableField)}</p>
      <p>{this.state.dict['model.tooltip.func.outface']}</p>
    </div>
    let menulist = []
@@ -886,8 +885,9 @@
        {/* 编辑按钮:复制、编辑 */}
        <Modal
          title={dict['model.action'] + '-' + (card && card.copyType === 'action' ? dict['model.copy'] : dict['model.edit'])}
          wrapClassName="model-table-action-edit-modal"
          visible={visible}
          width={800}
          width={850}
          maskClosable={false}
          onCancel={this.editModalCancel}
          footer={[
src/templates/sharecomponent/actioncomponent/index.scss
@@ -34,6 +34,12 @@
  }
}
.model-table-action-edit-modal {
  .ant-modal {
    top: 60px;
    padding-bottom: 5px;
  }
}
.model-table-action-verify-modal {
  .ant-modal {
    top: 50px;
src/templates/sharecomponent/actioncomponent/verifyexcelin/customscript/index.jsx
@@ -259,9 +259,9 @@
              {btn.sheet}
            </Form.Item>
          </Col> : null}
          <Col span={16}>
            <Form.Item label={'报错字段'} style={{margin: 0}}>
              ErrorCode, retmsg
          <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">
src/templates/sharecomponent/actioncomponent/verifyexcelin/index.jsx
@@ -299,7 +299,7 @@
          systemScripts: res.data.map(item => {
            return {
              name: item.funcname,
              value: Utils.UnformatOptions(item.longparam)
              value: window.decodeURIComponent(window.atob(item.longparam))
            }
          })
        })
src/templates/sharecomponent/columncomponent/columnform/index.jsx
@@ -7,7 +7,7 @@
const columnTypeOptions = {
  text: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'textFormat', 'fieldlength', 'blacklist', 'perspective', 'rowspan'],
  number: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'blacklist', 'perspective', 'sum'],
  number: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'blacklist', 'perspective', 'sum', 'rowspan'],
  link: ['label', 'field', 'type', 'nameField', 'Align', 'Hide', 'IsSort', 'joint', 'Width', 'fieldlength', 'blacklist'],
  textarea: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'fieldlength', 'blacklist'],
  picture: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'fieldlength', 'blacklist', 'scale', 'maxHeight']
src/templates/sharecomponent/settingcalcomponent/verifycard/index.jsx
@@ -9,13 +9,13 @@
import asyncComponent from '@/utils/asyncComponent'
import ColForm from './columnform'
import CustomScript from '@/templates/zshare/customscript'
import SettingForm from './settingform'
import SettingUtils from './utils'
import './index.scss'
const { TabPane } = Tabs
const CustomScript = asyncComponent(() => import('@/templates/zshare/customscript'))
const FieldsComponent = asyncComponent(() => import('@/templates/sharecomponent/fieldscomponent'))
class VerifyCard extends Component {
src/templates/sharecomponent/settingcomponent/index.scss
@@ -15,6 +15,7 @@
    padding-bottom: 5px;
    .ant-modal-body {
      max-height: calc(100vh - 190px);
      min-height: 250px;
      overflow-y: auto;
      padding-top: 10px;
    }
src/templates/sharecomponent/settingcomponent/settingform/datasource/index.jsx
@@ -9,6 +9,8 @@
import CodeMirror from '@/templates/zshare/codemirror'
import './index.scss'
const { TextArea } = Input
class SettingForm extends Component {
  static propTpyes = {
    config: PropTypes.object,     // 页面配置
@@ -17,10 +19,13 @@
    setting: PropTypes.object,    // 数据源配置
    columns: PropTypes.array,     // 列设置
    scripts: PropTypes.array,     // 自定义脚本
    updateStatus: PropTypes.func, // 状态更新
  }
  state = {
    interType: 'system',
    procMode: 'script',
    requestMode: 'system',
    funcTooltip: '',
    funcRules: []
  }
@@ -55,6 +60,8 @@
    this.setState({
      interType: setting.interType || 'system',
      procMode: setting.procMode || 'script',
      requestMode: setting.requestMode || 'system',
      funcTooltip: tooltip,
      funcRules: rules
    })
@@ -156,13 +163,20 @@
      this.setState({
        interType: value
      })
    } else if (key === 'sysInterface') {
      if (value === 'true') {
        this.props.form.setFieldsValue({
          interface: window.GLOB.mainSystemApi || ''
        })
      }
    } else if (key === 'procMode') {
      this.setState({
        procMode: value
      })
    } else if (key === 'requestMode') {
      this.setState({
        requestMode: value
      })
    } else if (key === 'sysInterface' && value === 'true') {
      this.props.form.setFieldsValue({
        interface: window.GLOB.mainSystemApi || ''
      })
    }
    this.props.updateStatus({[key]: value})
  }
  primaryKeyChange = (val) => {
@@ -176,7 +190,7 @@
  render() {
    const { setting, dict, menu, config, columns } = this.props
    const { getFieldDecorator } = this.props.form
    const { interType, funcRules, funcTooltip } = this.state
    const { interType, funcRules, funcTooltip, procMode, requestMode } = this.state
    const formItemLayout = {
      labelCol: {
@@ -230,10 +244,11 @@
                    },
                  ]
                })(
                <Radio.Group onChange={(e) => {this.onRadioChange(e, 'interType')}}>
                <Radio.Group style={{whiteSpace: 'nowrap'}} onChange={(e) => {this.onRadioChange(e, 'interType')}}>
                  <Radio value="system">系统</Radio>
                  <Radio value="inner">内部</Radio>
                  <Radio value="outer">外部</Radio>
                  <Radio value="custom">自定义</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
@@ -254,7 +269,106 @@
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {interType === 'inner' ? <Col span={12}>
            {interType === 'custom' ? <Col span={12}>
              <Form.Item label="参数处理">
                {getFieldDecorator('procMode', {
                  initialValue: procMode,
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.select'] + '参数处理方式!'
                    },
                  ]
                })(
                <Radio.Group style={{whiteSpace: 'nowrap'}} onChange={(e) => {this.onRadioChange(e, 'procMode')}}>
                  <Radio value="script">前置脚本</Radio>
                  <Radio value="inner">前置函数</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {interType === 'custom' && procMode === 'inner' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={funcTooltip}>
                  <Icon type="question-circle" />
                  前置函数
                </Tooltip>
              }>
                {getFieldDecorator('prevFunc', {
                  initialValue: setting.prevFunc || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '前置函数!'
                    },
                    {
                      max: formRule.func.max,
                      message: formRule.func.maxMessage
                    },
                    ...funcRules
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {interType === 'outer' || interType === 'custom' ? <Col className="data-source" span={24}>
              <Form.Item label="测试地址">
                {getFieldDecorator('interface', {
                  initialValue: setting.interface || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '测试地址!'
                    },
                  ]
                })(<TextArea rows={2} />)}
              </Form.Item>
            </Col> : null}
            {interType === 'outer' || interType === 'custom' ? <Col className="data-source" span={24}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="正式系统所使用的的接口地址。">
                  <Icon type="question-circle" />
                  正式地址
                </Tooltip>
              }>
                {getFieldDecorator('proInterface', {
                  initialValue: setting.proInterface || ''
                })(<TextArea rows={2} />)}
              </Form.Item>
            </Col> : null}
            {interType === 'custom' ? <Col span={12}>
              <Form.Item label="请求方式">
                {getFieldDecorator('method', {
                  initialValue: setting.method || 'post',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.select'] + '请求方式!'
                    },
                  ]
                })(
                <Radio.Group>
                  <Radio value="get">GET</Radio>
                  <Radio value="post">POST</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {interType === 'custom' ? <Col span={12}>
              <Form.Item label="数据请求">
                {getFieldDecorator('requestMode', {
                  initialValue: requestMode,
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.select'] + '数据请求方式!'
                    },
                  ]
                })(
                <Radio.Group style={{whiteSpace: 'nowrap'}} onChange={(e) => {this.onRadioChange(e, 'requestMode')}}>
                  <Radio value="system">系统函数</Radio>
                  <Radio value="inner">内部函数</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {interType === 'inner' || (interType === 'custom' && requestMode === 'inner') ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={funcTooltip}>
                  <Icon type="question-circle" />
@@ -278,19 +392,6 @@
              </Form.Item>
            </Col> : null}
            {interType === 'outer' ? <Col span={12}>
              <Form.Item label="接口地址">
                {getFieldDecorator('interface', {
                  initialValue: setting.interface || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '接口地址!'
                    },
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {interType === 'outer' ? <Col span={12}>
              <Form.Item label="外部函数">
                {getFieldDecorator('outerFunc', {
                  initialValue: setting.outerFunc || '',
@@ -306,7 +407,7 @@
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {interType === 'system' ? <Col span={24} className="data-source" style={{paddingLeft: '7px'}}>
            {interType === 'system' || (interType === 'custom' && requestMode === 'system') ? <Col span={24} className="data-source" style={{paddingLeft: '7px'}}>
              <Form.Item help={'数据ID:' + menu.MenuID} labelCol={{xs: { span: 24 }, sm: { span: 4 }}} wrapperCol={ {xs: { span: 24 }, sm: { span: 20 }} } label={
                <Tooltip placement="topLeft" title={'使用系统函数时,需填写数据源。注:数据权限替换符 $@ -> /* 或 \'\'、 @$ -> */ 或 \'\''}>
                  <Icon type="question-circle" />
@@ -318,7 +419,7 @@
                })(<CodeMirror />)}
              </Form.Item>
            </Col> : null}
            {interType === 'system' ? <Col span={12}>
            {interType === 'system' || (interType === 'custom' && requestMode === 'system') ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'查询时,搜索条件以where条件拼接进入sql,统计时,将数据源中以“@+搜索字段+@”的内容,以搜索条件中的值进行替换后,提交查询,注:查询类型仅在使用系统函数时有效。'}>
                  <Icon type="question-circle" />
@@ -334,7 +435,7 @@
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {interType === 'system' ? <Col span={12}>
            {interType === 'system' || (interType === 'custom' && requestMode === 'system') ? <Col span={12}>
              <Form.Item label="默认sql">
                {getFieldDecorator('default', {
                  initialValue: setting.default || 'true'
@@ -378,6 +479,61 @@
                })(<Input placeholder={'ID asc, UID desc'} autoComplete="off" />)}
              </Form.Item>
            </Col>
            {interType === 'custom' ? <Col span={12}>
              <Form.Item label="回调方式">
                {getFieldDecorator('callbackType', {
                  initialValue: setting.callbackType || 'script'
                })(
                <Radio.Group onChange={(e) => {this.onRadioChange(e, 'callbackType')}}>
                  <Radio value="default">默认脚本</Radio>
                  <Radio value="script">自定义脚本</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {interType === 'custom' ? <Col span={12}>
              <Form.Item label="回调表名">
                {getFieldDecorator('cbTable', {
                  initialValue: setting.cbTable || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '回调表名!'
                    },
                    {
                      max: formRule.input.max,
                      message: formRule.input.message
                    }
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {interType === 'custom' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'同步执行:外部接口调用成功后再请求数据;异步执行:外部接口调用与请求数据同时进行。'}>
                  <Icon type="question-circle" />
                  执行方式
                </Tooltip>
              }>
                {getFieldDecorator('execType', {
                  initialValue: setting.execType || 'sync'
                })(
                <Radio.Group>
                  <Radio value="sync">同步</Radio>
                  <Radio value="async">异步</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {interType === 'custom' ? <Col span={12}>
              <Form.Item label="执行次数">
                {getFieldDecorator('execTime', {
                  initialValue: setting.execTime || 'once'
                })(
                <Radio.Group>
                  <Radio value="always">始终</Radio>
                  <Radio value="once">一次</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            <Col span={12}>
              <Form.Item label="是否分页">
                {getFieldDecorator('laypage', {
src/templates/sharecomponent/settingcomponent/settingform/index.jsx
@@ -7,11 +7,13 @@
import Api from '@/api'
import Utils from '@/utils/utils.js'
import SettingUtils from './utils.jsx'
import CustomScript from '@/templates/zshare/customscript'
import asyncComponent from '@/utils/asyncComponent'
import DataSource from './datasource'
import './index.scss'
const { TabPane } = Tabs
const CustomScript = asyncComponent(() => import('@/templates/zshare/customscript'))
const SimpleScript = asyncComponent(() => import('./simplescript'))
class SettingForm extends Component {
  static propTpyes = {
@@ -29,7 +31,8 @@
    arr_field: '',
    regoptions: [],
    setting: null,
    defaultSql: ''
    defaultSql: '',
    status: {}
  }
  UNSAFE_componentWillMount() {
@@ -37,6 +40,8 @@
    let _setting = fromJS(config.setting).toJS()
    let _scripts = _setting.scripts || []
    let _preScripts = _setting.preScripts || []
    let _cbScripts = _setting.cbScripts || []
    _setting.default = _setting.default || 'true'            // 默认sql
    _setting.sysInterface = _setting.sysInterface || 'false' // 是否为系统接口
@@ -76,13 +81,21 @@
    _search = _search.replace(/@\$@/ig, '')
    _search = _search ? 'where ' + _search : ''
    let status = fromJS(_setting).toJS()
    status.requestMode = status.requestMode || 'system'
    status.procMode = status.procMode || 'script'
    status.callbackType = status.callbackType || 'script'
    this.setState({
      setting: _setting,
      search: _search,
      arr_field: arr_field.join(','),
      regoptions: Utils.getRegOptions(search), // 搜索条件,正则替换
      columns: columns,
      scripts: _scripts
      scripts: _scripts,
      preScripts: _preScripts,
      cbScripts: _cbScripts,
      status
    })
  }
@@ -141,13 +154,29 @@
  }
  handleConfirm = (trigger) => {
    const { activeKey, setting, scripts } = this.state
    const { activeKey, setting, scripts, preScripts, cbScripts } = this.state
    let _loading = false
    if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    } else if (this.preScriptsForm && this.preScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    } else if (this.cbScriptsForm && this.cbScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    }
    if (_loading) {
      notification.warning({
        top: 92,
        message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
        duration: 5
      })
      return Promise.reject()
    }
    if (trigger) {
      this.setState({loading: true})
    }
    let _scripts = scripts.filter(script => script.status !== 'false')
    // 表单提交时检查输入值是否正确
    if (activeKey === 'setting') {
@@ -171,14 +200,6 @@
            this.setState({loading: false})
            reject()
            return
          } else if (res.interType === 'system' && res.default === 'false' && _scripts.length === 0) {
            notification.warning({
              top: 92,
              message: '不执行默认sql时,请添加自定义脚本!',
              duration: 5
            })
            reject()
            return
          }
          this.setState({
@@ -186,7 +207,7 @@
          }, () => {
            this.sqlverify(() => {
              this.setState({loading: false})
              resolve({...res, scripts})
              resolve({...res, scripts, preScripts, cbScripts})
            }, () => {
              this.setState({loading: false})
              reject()
@@ -197,15 +218,8 @@
          reject()
        })
      })
    } else {
    } else if (activeKey === 'scripts') {
      return new Promise((resolve, reject) => {
        let _loading = false
        if (this.scriptsForm && this.scriptsForm.state.editItem) {
          _loading = true
        } else if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql')) {
          _loading = true
        }
        if (trigger === 'func' && setting.interType !== 'inner') {
          notification.warning({
            top: 92,
@@ -214,31 +228,20 @@
          })
          this.setState({loading: false})
          reject()
        } else if (_loading) {
          notification.warning({
            top: 92,
            message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
            duration: 5
          })
          this.setState({loading: false})
          reject()
        } else if (setting.interType === 'system' && setting.default === 'false' && _scripts.length === 0) {
          notification.warning({
            top: 92,
            message: '不执行默认sql时,请添加自定义脚本!',
            duration: 5
          })
          this.setState({loading: false})
          reject()
        } else {
          this.sqlverify(() => {
            this.setState({loading: false})
            resolve({...setting, scripts})
            resolve({...setting, scripts, preScripts, cbScripts})
          }, () => {
            this.setState({loading: false})
            reject()
          }, 'submit')
        }
      })
    } else {
      this.setState({loading: false})
      return new Promise((resolve) => {
        resolve({...setting, scripts, preScripts, cbScripts})
      })
    }
  }
@@ -246,7 +249,7 @@
  sqlverify = (_resolve, _reject, type, uscripts) => {
    const { setting, scripts, arr_field, regoptions, search } = this.state
    if (setting.interType !== 'system') { // 不使用系统接口时,不需要sql验证
    if (setting.interType !== 'system' && setting.requestMode !== 'system') { // 不使用系统接口时,不需要sql验证
      _resolve()
      return
    }
@@ -287,15 +290,58 @@
  // 标签切换
  changeTab = (val) => {
    const { activeKey, search, arr_field } = this.state
    const { activeKey, search, arr_field, setting } = this.state
    let _loading = false
    if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    } else if (this.preScriptsForm && this.preScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    } else if (this.cbScriptsForm && this.cbScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    }
    if (_loading) {
      notification.warning({
        top: 92,
        message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
        duration: 5
      })
      return
    }
    if (activeKey !== 'setting') {
      if (setting.interType !== 'system' && setting.requestMode !== 'system' && val === 'scripts') {
        notification.warning({
          top: 92,
          message: '使用系统接口时,才可以设置自定义脚本!',
          duration: 5
        })
        return
      } else if (setting.interType !== 'custom' && (val === 'prescripts' || val === 'cbscripts')) {
        notification.warning({
          top: 92,
          message: '使用自定义接口时,才可以设置前置或回调脚本!',
          duration: 5
        })
        return
      }
    }
    if (activeKey === 'setting') {
      let _defaultSql = ''
      this.settingForm.handleConfirm().then(res => {
        if (res.interType !== 'system') {
        if (res.interType !== 'system' && res.requestMode !== 'system' && val === 'scripts') {
          notification.warning({
            top: 92,
            message: '使用系统接口时,才可以设置自定义脚本!',
            duration: 5
          })
          return
        } else if (res.interType !== 'custom' && (val === 'prescripts' || val === 'cbscripts')) {
          notification.warning({
            top: 92,
            message: '使用自定义接口时,才可以设置前置或回调脚本!',
            duration: 5
          })
          return
@@ -330,22 +376,6 @@
        })
      })
    } else if (activeKey === 'scripts') {
      let _loading = false
      if (this.scriptsForm && this.scriptsForm.state.editItem) {
        _loading = true
      } else if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql')) {
        _loading = true
      }
      if (_loading) {
        notification.warning({
          top: 92,
          message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
          duration: 5
        })
        return
      }
      this.setState({loading: true})
      this.sqlverify(() => { // 验证成功
        this.setState({
@@ -358,6 +388,10 @@
          loading: false
        })
      }, activeKey)
    } else {
      this.setState({
        activeKey: val
      })
    }
  }
@@ -372,10 +406,24 @@
  scriptsUpdate = (scripts) => {
    this.setState({scripts})
  }
  // 前置脚本更新
  preScriptsUpdate = (preScripts) => {
    this.setState({preScripts})
  }
  // 后置脚本更新
  cbScriptsUpdate = (cbScripts) => {
    this.setState({cbScripts})
  }
  updateStatus = (status) => {
    this.setState({status: {...this.state.status, ...status}})
  }
  render() {
    const { config, menu, dict } = this.props
    const { loading, activeKey, setting, defaultSql, columns, scripts } = this.state
    const { loading, activeKey, setting, defaultSql, columns, scripts, preScripts, cbScripts, status, regoptions } = this.state
    return (
      <div className="model-table-setting-form-box" id="model-setting-form-body">
@@ -389,6 +437,7 @@
              columns={columns}
              setting={setting}
              scripts={scripts}
              updateStatus={this.updateStatus}
              wrappedComponentRef={(inst) => this.settingForm = inst}
            />
          </TabPane>
@@ -397,7 +446,7 @@
              自定义脚本
              {scripts.length ? <span className="count-tip">{scripts.length}</span> : null}
            </span>
          } key="scripts">
          } disabled={!(status.interType === 'system' || (status.interType === 'custom' && status.requestMode === 'system'))} key="scripts">
            <CustomScript
              dict={dict}
              setting={setting}
@@ -409,6 +458,36 @@
              wrappedComponentRef={(inst) => this.scriptsForm = inst}
            />
          </TabPane>
          <TabPane tab={
            <span>
              前置脚本
              {preScripts.length ? <span className="count-tip">{preScripts.length}</span> : null}
            </span>
          } disabled={status.interType !== 'custom' || status.procMode !== 'script'} key="prescripts">
            <SimpleScript
              dict={dict}
              setting={setting}
              scripts={preScripts}
              regoptions={regoptions}
              searches={this.props.search}
              scriptsUpdate={this.preScriptsUpdate}
              wrappedComponentRef={(inst) => this.preScriptsForm = inst}
            />
          </TabPane>
          <TabPane tab={
            <span>
              回调脚本
              {cbScripts.length ? <span className="count-tip">{cbScripts.length}</span> : null}
            </span>
          } disabled={status.interType !== 'custom' || status.callbackType !== 'script'} key="cbscripts">
            <SimpleScript
              dict={dict}
              setting={setting}
              scripts={cbScripts}
              scriptsUpdate={this.cbScriptsUpdate}
              wrappedComponentRef={(inst) => this.cbScriptsForm = inst}
            />
          </TabPane>
        </Tabs>
      </div>
    )
src/templates/sharecomponent/settingcomponent/settingform/simplescript/index.jsx
New file
@@ -0,0 +1,488 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Icon, Button, notification, Select, Popconfirm, Typography, Modal, Radio } from 'antd'
import moment from 'moment'
import Utils from '@/utils/utils.js'
import Api from '@/api'
import SettingUtils from '../utils'
import CodeMirror from '@/templates/zshare/codemirror'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { Paragraph } = Typography
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
class CustomForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,         // 字典项
    setting: PropTypes.object,      // 设置
    scripts: PropTypes.array,       // 自定义脚本列表
    searches: PropTypes.array,      // 搜索条件
    regoptions: PropTypes.any,      // 正则替换
    scriptsChange: PropTypes.func,  // 自定义脚本切换时验证
    scriptsUpdate: PropTypes.func   // 表单
  }
  state = {
    editItem: null,
    loading: false,
    usefulFields: '',
    systemScripts: [],
    scriptsColumns: [
      {
        title: 'SQL',
        dataIndex: 'sql',
        width: '60%',
        render: (text) => {
          let title = text.match(/^\s*\/\*.+\*\//)
          title = title && title[0] ? title[0] : ''
          text = title ? text.replace(title, '') : text
          return (
            <div>
              {title ? <span style={{color: '#a50'}}>{title}</span> : null}
              <Paragraph copyable ellipsis={{ rows: 4, expandable: true }}>{text}</Paragraph>
            </div>
          )
        }
      },
      {
        title: '执行位置',
        dataIndex: 'position',
        width: '13%',
        render: (text, record) => {
          if (record.position === 'front') {
            return 'sql前'
          } else {
            return 'sql后'
          }
        }
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '12%',
        render: (text, record) => record.status === 'false' ?
          (
            <div>
              {this.props.dict['model.status.forbidden']}
              <Icon style={{marginLeft: '5px'}} type="stop" theme="twoTone" twoToneColor="#ff4d4f" />
            </div>
          ) :
          (
            <div>
              {this.props.dict['model.status.open']}
              <Icon style={{marginLeft: '5px'}} type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
            </div>
          )
      },
      {
        title: '操作',
        align: 'center',
        width: '15%',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div style={{textAlign: 'center'}}>
            <span className="operation-btn" title={this.props.dict['model.edit']} onClick={() => this.handleEdit(record)} style={{color: '#1890ff'}}><Icon type="edit" /></span>
            <span className="operation-btn" title={this.props.dict['header.form.status.change']} onClick={() => this.handleStatus(record)} style={{color: '#8E44AD'}}><Icon type="swap" /></span>
            <Popconfirm
              overlayClassName="popover-confirm"
              title={this.props.dict['model.query.delete']}
              onConfirm={() => this.handleDelete(record)
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
  UNSAFE_componentWillMount() {
    const { searches, scripts } = this.props
    let _usefulFields = []
    let scriptsColumns = fromJS(this.state.scriptsColumns).toJS()
    if (searches) {
      searches.forEach(item => {
        if (!item.field) return
        if (item.type === 'group') {
          if (item.transfer === 'true') {
            _usefulFields.push(item.field)
          }
          _usefulFields.push(item.datefield)
          _usefulFields.push(item.datefield + '1')
        } else if (['dateweek', 'datemonth', 'daterange'].includes(item.type)) {
          _usefulFields.push(item.field)
          _usefulFields.push(item.field + '1')
        } else if (_usefulFields.includes(item.field)) {
          _usefulFields.push(item.field + '1')
        } else {
          _usefulFields.push(item.field)
        }
      })
      _usefulFields = _usefulFields.join(', ')
      scriptsColumns = scriptsColumns.filter(item => {
        if (item.dataIndex === 'sql') {
          item.width = '70%'
        }
        return item.dataIndex !== 'position'
      })
    } else {
      _usefulFields = null
    }
    this.setState({
      usefulFields: _usefulFields,
      scripts: fromJS(scripts).toJS(),
      scriptsColumns
    })
  }
  componentDidMount () {
    this.getsysScript()
  }
  getsysScript = () => {
    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) {
        let _scripts = res.data.map(item => {
          let _item = {
            name: item.funcname,
            value: window.decodeURIComponent(window.atob(item.longparam))
          }
          return _item
        })
        this.setState({
          systemScripts: _scripts
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  handleCancel = () => {
    this.setState({
      editItem: null
    })
    this.props.form.setFieldsValue({
      sql: ''
    })
  }
  handleConfirm = () => {
    const { scripts, editItem } = this.state
    let _sql = this.props.form.getFieldValue('sql')
    if (!_sql) {
      notification.warning({
        top: 92,
        message: '请填写自定义脚本!',
        duration: 5
      })
      return
    } else if (/^\s+$/.test(_sql)) {
      notification.warning({
        top: 92,
        message: '自定义脚本不可为空!',
        duration: 5
      })
      return
    }
    let values = {
      uuid: editItem && editItem.uuid ? editItem.uuid : Utils.getuuid(),
      sql: _sql,
    }
    if (this.props.form.getFieldValue('position')) {
      values.position = this.props.form.getFieldValue('position')
    }
    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 _scripts = fromJS(scripts).toJS()
    if (editItem && editItem.uuid) {
      _scripts = _scripts.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      _scripts.push(values)
    }
    let param = {
      func: 's_debug_sql',
      exec_type: 'y',
      LText: SettingUtils.getCustomDebugSql(_scripts, this.props.regoptions)
    }
    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(result => {
      if (result.status) {
        this.setState({
          loading: false,
          scripts: _scripts,
          editItem: null
        })
        this.props.scriptsUpdate(_scripts)
        this.props.form.setFieldsValue({
          sql: ''
        })
      } else {
        this.setState({loading: false})
        Modal.error({
          title: result.message
        })
      }
    })
  }
  selectScript = (value, option) => {
    if (!value || !option) return
    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
    })
  }
  handleEdit = (record) => {
    const { usefulFields } = this.state
    this.setState({
      editItem: record
    })
    if (usefulFields) {
      this.props.form.setFieldsValue({
        sql: record.sql
      })
    } else {
      this.props.form.setFieldsValue({
        sql: record.sql,
        position: record.position || 'back'
      })
    }
    this.scrolltop()
  }
  scrolltop = () => {
    let node = document.getElementById('model-setting-form-body').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)
    }
  }
  changeScripts = (scripts) => {
    this.setState({scripts})
    this.props.scriptsUpdate(scripts)
  }
  handleStatus = (record) => {
    let scripts = fromJS(this.state.scripts).toJS()
    record.status = record.status === 'false' ? 'true' : 'false'
    scripts = scripts.map(item => {
      if (item.uuid === record.uuid) {
        return record
      } else {
        return item
      }
    })
    this.setState({scripts})
    this.props.scriptsUpdate(scripts)
  }
  handleDelete = (record) => {
    let scripts = fromJS(this.state.scripts).toJS()
    scripts = scripts.filter(item => item.uuid !== record.uuid)
    this.setState({ scripts })
    this.props.scriptsUpdate(scripts)
  }
  render() {
    const { setting, scripts } = this.props
    const { getFieldDecorator } = this.props.form
    const { usefulFields, scriptsColumns, systemScripts } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="modal-menu-setting-script">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={8}>
              <Form.Item label={'回调表名'} style={{whiteSpace: 'nowrap', margin: 0}}>
                {setting.cbTable}
              </Form.Item>
            </Col>
            <Col span={16}>
              <Form.Item label={'报错字段'} style={{margin: 0}}>
                ErrorCode, retmsg
              </Form.Item>
            </Col>
            {usefulFields ? <Col span={24} className="sqlfield">
              <Form.Item label={'可用字段'}>
                id, bid, loginuid, sessionuid, userid, username, fullname, appkey, time_id{usefulFields ? ', ' + usefulFields : ''}
              </Form.Item>
            </Col> : null}
            {!usefulFields ? <Col span={24} className="sqlfield">
              <Form.Item label={'可用字段'}>
                id, bid, loginuid, sessionuid, userid, username, fullname, appkey, time_id
              </Form.Item>
            </Col> : null}
            {!usefulFields ? <Col span={8} style={{whiteSpace: 'nowrap'}}>
              <Form.Item style={{marginBottom: 0}} label="执行位置">
                {getFieldDecorator('position', {
                  initialValue: 'front'
                })(
                  <Radio.Group>
                    <Radio value="front">sql前</Radio>
                    <Radio value="back">sql后</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col> : null}
            <Col span={10} className="quick-add">
              <Form.Item label={'快捷添加'} style={{marginBottom: 0}}>
                <Select
                  allowClear
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={this.selectScript}
                >
                  {!usefulFields ? <Select.Option key="default" value={`declare @${setting.cbTable} table (mk_api_key nvarchar(100),mk_level nvarchar(10),mk_id nvarchar(50),mk_bid nvarchar(50))\n/*@${setting.cbTable}_data table (mk_level nvarchar(10),mk_id nvarchar(50),mk_bid nvarchar(50))*/`}>默认sql</Select.Option> : null}
                  {systemScripts.map((option, i) =>
                    <Select.Option style={{whiteSpace: 'normal'}} 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={{marginTop: 5, marginBottom: 15, marginLeft: 30}}>
                保存
              </Button>
              <Button onClick={this.handleCancel} style={{marginTop: 5, marginBottom: 15, marginLeft: 10}}>
                取消
              </Button>
            </Col>
            <Col span={24} className="sql">
              <Form.Item label={'sql'}>
                {getFieldDecorator('sql', {
                  initialValue: ''
                })(<CodeMirror />)}
              </Form.Item>
            </Col>
          </Row>
        </Form>
        <EditTable data={scripts} actions={['move']} columns={scriptsColumns} onChange={this.changeScripts}/>
      </div>
    )
  }
}
export default Form.create()(CustomForm)
src/templates/sharecomponent/settingcomponent/settingform/simplescript/index.scss
New file
@@ -0,0 +1,45 @@
.modal-menu-setting-script {
  .sqlfield {
    .ant-form-item {
      margin-bottom: 5px;
    }
    .ant-form-item-control {
      line-height: 24px;
    }
    .ant-form-item-label {
      line-height: 25px;
    }
    .ant-form-item-children {
      line-height: 22px;
    }
    .ant-col-sm-8 {
      width: 10.5%;
    }
    .ant-col-sm-16 {
      width: 89.5%;
    }
  }
  .quick-add {
    .ant-col-sm-8 {
      width: 26%;
    }
    .ant-col-sm-16 {
      width: 74%;
    }
  }
  .sql {
    .ant-col-sm-8 {
      width: 10.5%;
    }
    .ant-col-sm-16 {
      width: 89.5%;
      padding-top: 4px;
    }
    .CodeMirror {
      height: 350px;
    }
  }
  div.ant-typography {
    margin-bottom: 0;
  }
}
src/templates/sharecomponent/settingcomponent/settingform/utils.jsx
@@ -99,4 +99,61 @@
    return sql
  }
  /**
   * @description 生成前置或后置语句
   * @return {String}  scripts       脚本
   * @return {Array}   regoptions    搜索条件正则替换
   */
  static getCustomDebugSql (scripts, regoptions) {
    let sql = ''
    let _customScript = ''
    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) select @ErrorCode='',@retmsg =''
        ${_customScript}
      `
    }
    _customScript = _customScript.replace(/@\$|\$@/ig, '')
    _customScript = _customScript.replace(/@userName@|@fullName@/ig, `''`)
    // 外联数据库替换
    if (window.GLOB.externalDatabase !== null) {
      _customScript = _customScript.replace(/@db@/ig, window.GLOB.externalDatabase)
    }
    // 正则替换
    if (regoptions) {
      let _regoptions = regoptions.map(item => {
        return {
          reg: new RegExp('@' + item.key + '@', 'ig'),
          value: `'0'`
        }
      })
      _regoptions.forEach(item => {
        _customScript = _customScript.replace(item.reg, item.value)
      })
    }
    if (_customScript) {
      sql = `/* sql 验证 */
        ${_customScript}
        aaa:
        if @ErrorCode!=''
          insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
      `
    }
    sql = sql.replace(/\n\s{8}/ig, '\n')
    console.info(sql)
    return sql
  }
}
src/templates/sharecomponent/tabscomponent/tabform/index.jsx
@@ -26,9 +26,9 @@
  UNSAFE_componentWillMount () {
    const { formlist } = this.props
    let type = formlist.filter(cell => cell.key === 'type')[0].initVal
    // let type = formlist.filter(cell => cell.key === 'type')[0].initVal
    let _tabs = this.props.tabs.filter(tab => tab.type === type)
    let _tabs = this.props.tabs.filter(tab => tab.type === 'SubTable')
    this.setState({
      formlist: formlist.map(item => {
@@ -281,6 +281,8 @@
            return
          }
          values.type = 'SubTable' // 类型为子表
          resolve(values)
        } else {
          reject(err)
src/templates/sharecomponent/treesettingcomponent/settingform/index.jsx
@@ -8,10 +8,11 @@
import Utils from '@/utils/utils.js'
import SettingUtils from './utils.jsx'
import DataSource from './datasource'
import CustomScript from '@/templates/zshare/customscript'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { TabPane } = Tabs
const CustomScript = asyncComponent(() => import('@/templates/zshare/customscript'))
class SettingForm extends Component {
  static propTpyes = {
src/templates/subtableconfig/index.jsx
@@ -11,7 +11,7 @@
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import Utils from '@/utils/utils.js'
import UtilsUpdate from '@/utils/utils-update.js'
import { updateSubTable } from '@/utils/utils-update.js'
import asyncComponent from '@/utils/asyncComponent'
import SearchComponent from '@/templates/sharecomponent/searchcomponent'
@@ -106,7 +106,7 @@
    let _activeKey =  editSubTab ? editSubTab.activeKey : editTab.activeKey
    // 版本兼容
    _config = UtilsUpdate.updateSubTable(_config)
    _config = updateSubTable(_config)
    this.setState({
      openEdition: editSubTab ? (editSubTab.open_edition || '') : (editTab.open_edition || ''),
@@ -195,7 +195,7 @@
    if (editSubTab) {
      _subconfig = tabConfig
      if (editTab.hasOwnProperty('OpenType')) {
        _tabview = editTab.tabType
        _tabview = editTab.tabType || 'SubTable'
      } else {
        _tabview = editTab.type
      }
@@ -669,7 +669,7 @@
        if (btn.OpenType === 'pop') {
          _view = 'Modal'             // 表单页面
        } else if (btn.OpenType === 'popview') {
          _view = btn.tabType        // 新弹窗标签模板
          _view = btn.tabType || 'SubTable' // 新弹窗标签模板 tabType 属性已去除
          _subtab = btn
          if (editSubTab) {
@@ -816,8 +816,32 @@
      }
    })
    if (config.setting.interType === 'system' && config.setting.default !== 'false' && !config.setting.dataresource) {
      return '尚未设置数据源,不可启用!'
    config.action && config.action.forEach((btn) => {
      if (['prompt', 'exec', 'pop'].includes(btn.OpenType) && btn.Ot === 'required' && btn.verify && btn.verify.scripts && btn.verify.scripts.length > 0) {
        let hascheck = false
        btn.verify.scripts.forEach(item => {
          if (item.status === 'false') return
          if (/\$check@|@check\$/ig.test(item.sql)) {
            hascheck = true
          }
        })
        if (hascheck) {
          notification.warning({
            top: 92,
            message: `可选择多行的按钮《${btn.label}》中 $check@ 或 @check$ 将不会生效!`,
            duration: 5
          })
        }
      }
    })
    if ((config.setting.interType === 'system' || config.setting.requestMode === 'system') && config.setting.default === 'false' && config.setting.scripts && config.setting.scripts.filter(item => item.status !== 'false').length === 0) {
      return '数据源中不执行默认sql,且未添加自定义脚本,不可启用!'
    } else if (config.setting.interType === 'custom' && config.setting.procMode !== 'inner' && config.setting.preScripts && config.setting.preScripts.filter(item => item.status !== 'false').length === 0) {
      return '数据源未设置前置脚本,不可启用!'
    } else if (config.setting.interType === 'custom' && config.setting.callbackType === 'script' && config.setting.cbScripts && config.setting.cbScripts.filter(item => item.status !== 'false').length === 0) {
      return '数据源未设置回调脚本,不可启用!'
    } else if (!config.setting.primaryKey) {
      return '尚未设置主键,不可启用!'
    }  else if (config.columns.length === 0) {
src/templates/zshare/codemirror/index.jsx
@@ -6,6 +6,8 @@
import {UnControlled as CodeMirror} from 'react-codemirror2'
import 'codemirror/mode/javascript/javascript'
import 'codemirror/mode/sql/sql'
import 'codemirror/mode/xml/xml'
import 'codemirror/mode/css/css'
import 'codemirror/addon/display/fullscreen.js'
import 'codemirror/addon/display/fullscreen.css'
@@ -26,7 +28,7 @@
    editor: null,   // code对象
    defaultVal: '', // 初始值
    value: '',      // 实时内容
    options: null,  // mode : text/javascript、text/x-mysql ; theme : cobalt - 黑底
    options: null,  // mode : text/xml, text/css, text/javascript、text/x-mysql ; theme : cobalt - 黑底
    fullScreen: false,
    style: {fontSize: '18px', lineHeight: '32px'},
    display: true
src/templates/zshare/customscript/index.jsx
@@ -139,7 +139,7 @@
        let _scripts = res.data.map(item => {
          let _item = {
            name: item.funcname,
            value: Utils.UnformatOptions(item.longparam)
            value: window.decodeURIComponent(window.atob(item.longparam))
          }
          return _item
        })
@@ -266,6 +266,7 @@
  }
  selectScript = (value, option) => {
    if (!value || !option) return
    let _sql = this.props.form.getFieldValue('sql')
    if (_sql) {
      _sql = _sql + ` 
@@ -379,6 +380,7 @@
            <Col span={10} className="quick-add">
              <Form.Item label={'快捷添加'} style={{marginBottom: 0}}>
                <Select
                  allowClear
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={this.selectScript}
src/templates/zshare/editTable/index.jsx
@@ -172,7 +172,15 @@
    let columns = fromJS(this.props.columns).toJS()
    if (actions && (actions.includes('edit') || actions.includes('copy') || actions.includes('del'))) {
      columns.push({
      let _operation = null
      columns = columns.filter(item => {
        if (item.dataIndex === 'operation') {
          _operation = item
        }
        return item.dataIndex !== 'operation'
      })
      let operation = {
        title: (<div>
          {eTDict['model.operation']}
          {actions.includes('copy') ? (
@@ -213,7 +221,13 @@
            </div>
          )
        }
      })
      }
      if (_operation) {
        operation.render = _operation.render
        operation.width = _operation.width
      }
      columns.push(operation)
    }
    this.setState({
src/templates/zshare/formconfig.jsx
@@ -687,7 +687,7 @@
      options: opentypes
    },
    {
      type: 'select',
      type: 'radio',
      key: 'funcType',
      label: Formdict['header.form.funcType'],
      initVal: card.funcType || '',
@@ -723,19 +723,24 @@
      label: Formdict['header.form.intertype'],
      initVal: card.intertype || 'system',
      required: true,
      options: []
    },
    {
      type: 'radio',
      key: 'procMode',
      label: '参数处理',
      initVal: card.procMode || 'system',
      required: true,
      options: [{
        value: 'system',
        text: Formdict['model.interface.system']
        text: '系统函数'
      }, {
        value: 'inner',
        text: Formdict['model.interface.inner']
      }, {
        value: 'outer',
        text: Formdict['model.interface.outer']
        text: '内部函数'
      }]
    },
    {
      type: 'select',
      type: 'radio',
      key: 'sqlType',
      label: Formdict['header.form.action.type'],
      initVal: card.sqlType || '',
@@ -744,18 +749,18 @@
    },
    {
      type: 'text',
      key: 'sql',
      label: Formdict['model.form.tablename'],
      initVal: card.sql || config.setting.tableName || '',
      required: true
    },
    {
      type: 'text',
      key: 'label',
      label: '按钮名称',
      initVal: card.label,
      required: true,
      readonly: false
    },
    {
      type: 'text',
      key: 'sql',
      label: Formdict['model.form.tablename'],
      initVal: card.sql || config.setting.tableName || '',
      required: true
    },
    {
      type: 'text',
@@ -768,17 +773,17 @@
      required: card.intertype === 'inner',
      readonly: false
    },
    {
      type: 'select',
      key: 'tabType',
      label: Formdict['model.form.tabType'],
      initVal: card.tabType || 'SubTable',
      required: true,
      options: [{
        value: 'SubTable',
        text: Formdict['model.menu.tab.subtable']
      }]
    },
    // {
    //   type: 'select',
    //   key: 'tabType',
    //   label: Formdict['model.form.tabType'],
    //   initVal: card.tabType || 'SubTable',
    //   required: true,
    //   options: [{
    //     value: 'SubTable',
    //     text: Formdict['model.menu.tab.subtable']
    //   }]
    // },
    {
      type: 'select',
      key: 'linkTab',
@@ -848,12 +853,56 @@
      readonly: false
    },
    {
      type: 'text',
      type: 'textarea',
      key: 'interface',
      label: Formdict['header.form.interface'],
      label: '测试地址',
      initVal: card.sysInterface === 'true' ? (window.GLOB.mainSystemApi || '') : (card.interface || ''),
      required: true,
      readonly: card.sysInterface === 'true'
    },
    {
      type: 'textarea',
      key: 'proInterface',
      label: '正式地址',
      initVal: card.proInterface || '',
      tooltip: '正式系统所使用的接口地址。',
      required: false
    },
    {
      type: 'radio',
      key: 'method',
      label: '请求方式',
      initVal: card.method || 'post',
      required: true,
      options: [{
        value: 'get',
        text: 'GET'
      }, {
        value: 'post',
        text: 'POST'
      }]
    },
    {
      type: 'radio',
      key: 'callbackType',
      label: '回调方式',
      initVal: card.callbackType || 'script',
      tooltip: '使用默认方式执行时,需要配合计划任务。',
      required: true,
      options: [{
        value: 'script',
        text: '自定义脚本'
      }, {
        value: 'default',
        text: '后台脚本'
      }]
    },
    {
      type: 'text',
      key: 'cbTable',
      label: '回调表名',
      initVal: card.cbTable || '',
      required: true
    },
    {
      type: 'text',
@@ -864,7 +913,7 @@
      readonly: false
    },
    {
      type: 'select',
      type: 'radio',
      key: 'position',
      label: Formdict['header.form.position'],
      initVal: card.position || 'toolbar',
@@ -1851,7 +1900,7 @@
    })
  }
  if (card.type === 'textarea' || card.type === 'fileupload' || card.type === 'multiselect' || card.type === 'checkbox') {
  if (['textarea', 'fileupload', 'multiselect', 'checkbox', 'brafteditor'].includes(card.type)) {
    _fieldlength = 512
  }
@@ -1927,6 +1976,9 @@
        value: 'color',
        text: Formdict['model.form.color']
      }, {
        value: 'brafteditor',
        text: '富文本'
      }, {
        value: 'funcvar',
        text: Formdict['header.form.funcvar']
      },
@@ -1936,7 +1988,7 @@
      type: 'text',
      key: 'initval',
      label: Formdict['header.form.initval'],
      tooltip: '下拉多选与多选框,添加多个初始值请使用“,”号分隔。',
      tooltip: '下拉多选与多选框,添加多个初始值请使用“,”号分隔。注:下拉选择、联动菜单或单选框中$first表示选择第一项',
      initVal: card.initval || '',
      required: false
    },
@@ -1953,14 +2005,14 @@
      key: 'openVal',
      label: '开启值',
      initVal: card.openVal || '',
      required: true
      required: false
    },
    {
      type: 'text',
      key: 'closeVal',
      label: '关闭值',
      initVal: card.closeVal || '',
      required: true
      required: false
    },
    {
      type: 'text',
@@ -2225,6 +2277,19 @@
    },
    {
      type: 'radio',
      key: 'hidelabel',
      label: '隐藏名称',
      initVal: card.hidelabel || 'false',
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
      }]
    },
    {
      type: 'radio',
      key: 'readonly',
      label: Formdict['header.form.readonly'],
      initVal: card.readonly || 'false',
@@ -2330,7 +2395,7 @@
      type: 'radio',
      key: 'encryption',
      label: '加密传输',
      initVal: card.encryption || 'false',
      initVal: card.type === 'brafteditor' ? (card.encryption || 'true') : (card.encryption || 'false'),
      options: [{
        value: 'true',
        text: Formdict['model.true']
@@ -2450,17 +2515,17 @@
      initVal: card.label || '',
      required: true
    },
    {
      type: 'select',
      key: 'type',
      label: Formdict['model.form.tabType'],
      initVal: card.type || 'SubTable',
      required: true,
      options: [{
        value: 'SubTable',
        text: Formdict['model.menu.tab.subtable']
      }]
    },
    // {
    //   type: 'select',
    //   key: 'type',
    //   label: Formdict['model.form.tabType'],
    //   initVal: card.type || 'SubTable',
    //   required: true,
    //   options: [{
    //     value: 'SubTable',
    //     text: Formdict['model.menu.tab.subtable']
    //   }]
    // },
    {
      type: 'select',
      key: 'linkTab',
src/templates/zshare/modalform/index.jsx
@@ -30,6 +30,7 @@
  textarea: ['initval', 'readonly', 'required', 'hidden', 'readin', 'fieldlength', 'maxRows', 'encryption', 'interception', 'tooltip'],
  color: ['initval', 'readonly', 'required', 'hidden', 'readin', 'entireLine', 'tooltip'],
  hint: ['label', 'type', 'blacklist', 'message'],
  brafteditor: ['required', 'hidelabel', 'hidden', 'readin', 'fieldlength', 'readonly', 'tooltip', 'encryption'],
  funcvar: [],
  linkMain: ['readonly', 'required', 'hidden', 'fieldlength', 'entireLine', 'tooltip']
}
@@ -231,7 +232,9 @@
            }
          } else if (form.key === 'fieldlength') {
            form.initVal = 50
            if (value === 'textarea' || value === 'fileupload' || value === 'multiselect' || value === 'checkbox') {
            if (value === 'textarea') {
              form.initVal = 8000
            } else if (value === 'fileupload' || value === 'multiselect' || value === 'checkbox') {
              form.initVal = 512
            }
@@ -254,6 +257,10 @@
            form.required = true
            if (value === 'hint') {
              form.required = false
            }
          } else if (form.key === 'encryption') {
            if (value === 'brafteditor') {
              fieldValue.encryption = 'true'
            }
          }
@@ -521,7 +528,7 @@
                  getPopupContainer={() => document.getElementById('modal-fields-form-box')}
                >
                  {item.options.map((option, i) =>
                    <Select.Option key={`${i}`} value={option.value || option.field}>
                    <Select.Option key={`${i}`} value={option.value || option.field || ''}>
                      {item.key === 'icon' && <Icon type={option.text} />} {option.text || option.label}
                    </Select.Option>
                  )}
src/templates/zshare/verifycard/callbackcustomscript/index.jsx
New file
@@ -0,0 +1,284 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Button, notification, Modal, Tooltip, Icon, Radio, Select } from 'antd'
import moment from 'moment'
import Utils from '@/utils/utils.js'
import Api from '@/api'
import CodeMirror from '@/templates/zshare/codemirror'
import './index.scss'
class CustomForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,         // 字典项
    btn: PropTypes.object,          // 按钮信息
    initsql: PropTypes.string,      // 初始化脚本
    usefulfields: PropTypes.string, // 可用字段
    systemScripts: PropTypes.array, // 系统脚本
    customScripts: PropTypes.array, // 自定义脚本
    scriptsChange: PropTypes.func   // 表单
  }
  state = {
    editItem: null,
    loading: false
  }
  edit = (record) => {
    this.setState({
      editItem: record
    })
    this.props.form.setFieldsValue({
      sql: record.sql,
      position: record.position || 'back'
    })
  }
  handleConfirm = () => {
    const { editItem } = this.state
    // 表单提交时检查输入值是否正确
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        values.uuid = editItem ? 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 _prevCustomScript = '' // 默认sql前执行脚本
        let _backCustomScript = '' // 默认sql后执行脚本
        this.props.customScripts.forEach(item => {
          if (item.status === 'false') return
          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}
            `
          }
        })
        let param = {
          func: 's_debug_sql',
          exec_type: 'y',
          LText: this.props.initsql +  _prevCustomScript + _backCustomScript + tail
        }
        // 数据权限
        param.LText = param.LText.replace(/@\$|\$@/ig, '')
        // check
        param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
        // 外联数据库替换
        if (window.GLOB.externalDatabase !== null) {
          param.LText = param.LText.replace(/@db@/ig, window.GLOB.externalDatabase)
        }
        console.info(`/* sql 验证 */\n${param.LText.replace(/\n\s{6,20}/ig, '\n')}`)
        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) => {
    if (!value || !option) return
    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 { usefulfields, systemScripts, btn } = this.props
    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-custom-callback-scripts">
        <Row gutter={24}>
          <Col span={8}>
            <Form.Item label={'表名'} style={{whiteSpace: 'nowrap', margin: 0}}>
              {btn.cbTable}
            </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={'可用字段'}>
              {usefulfields}
            </Form.Item>
          </Col> : null}
          <Col span={8} style={{whiteSpace: 'nowrap'}}>
            <Form.Item style={{marginBottom: 0}} label={
              <Tooltip placement="bottomLeft" title={'自定义脚本与默认sql位置关系。'}>
                <Icon type="question-circle" style={{color: '#c49f47', marginRight: '5px'}} />
                执行位置
              </Tooltip>
            }>
              {getFieldDecorator('position', {
                initialValue: 'front'
              })(
                <Radio.Group>
                  <Radio value="front">sql前</Radio>
                  <Radio value="back">sql后</Radio>
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
          <Col span={10}>
            <Form.Item label={'快捷添加'} style={{marginBottom: 0}}>
              <Select
                allowClear
                showSearch
                filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                onChange={this.selectScript}
                getPopupContainer={() => document.getElementById('verify-custom-callback-scripts')}
              >
                <Select.Option key="default" value={`declare @${btn.cbTable} table (mk_api_key nvarchar(100),mk_level nvarchar(10),mk_id nvarchar(50),mk_bid nvarchar(50))\n/*@${btn.cbTable}_data table (mk_level nvarchar(10),mk_id nvarchar(50),mk_bid nvarchar(50))*/`}>默认sql</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={
              <Tooltip placement="topLeft" title={'数据检查替换符 $check@ -> /* 或 \'\'、 @check$ -> */ 或 \'\''}>
                <Icon type="question-circle" />
                sql
              </Tooltip>
            }>
              {getFieldDecorator('sql', {
                initialValue: '',
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.input'] + 'sql!'
                  }
                ]
              })(<CodeMirror />)}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(CustomForm)
src/templates/zshare/verifycard/callbackcustomscript/index.scss
New file
@@ -0,0 +1,5 @@
#verify-custom-scripts {
  .ant-select-dropdown-menu-item {
    white-space: normal;
  }
}
src/templates/zshare/verifycard/customscript/index.jsx
@@ -122,6 +122,8 @@
        // 数据权限
        param.LText = param.LText.replace(/@\$|\$@/ig, '')
        // check
        param.LText = param.LText.replace(/\$check@|@check\$/ig, '')
        // 外联数据库替换
        if (window.GLOB.externalDatabase !== null) {
@@ -169,6 +171,8 @@
  }
  selectScript = (value, option) => {
    if (!value || !option) return
    let _sql = this.props.form.getFieldValue('sql')
    if (_sql) {
      _sql = _sql + ` 
@@ -209,9 +213,9 @@
              {btn.sql}
            </Form.Item>
          </Col> : null}
          <Col span={16}>
            <Form.Item label={'报错字段'} style={{margin: 0}}>
              ErrorCode, retmsg
          <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">
@@ -241,6 +245,7 @@
            <Form.Item label={'快捷添加'} style={{marginBottom: 0}}>
              <Select
                showSearch
                allowClear
                filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                onChange={this.selectScript}
                getPopupContainer={() => document.getElementById('verify-custom-scripts')}
@@ -261,7 +266,12 @@
            </Button>
          </Col>
          <Col span={24} className="sql">
            <Form.Item label={'sql'}>
            <Form.Item label={
              <Tooltip placement="topLeft" title={'数据检查替换符 $check@ -> /* 或 \'\'、 @check$ -> */ 或 \'\''}>
                <Icon type="question-circle" />
                sql
              </Tooltip>
            }>
              {getFieldDecorator('sql', {
                initialValue: '',
                rules: [
src/templates/zshare/verifycard/index.jsx
@@ -11,6 +11,7 @@
import ContrastForm from './contrastform'
import CustomForm from './customform'
import CustomScript from './customscript'
import CallBackCustomScript from './callbackcustomscript'
import BillcodeForm from './billcodeform'
import VoucherForm from './voucherform'
import asyncComponent from '@/utils/asyncComponent'
@@ -355,6 +356,73 @@
          </div>)
      }
    ],
    cbScriptsColumns: [
      {
        title: 'SQL',
        dataIndex: 'sql',
        width: '60%',
        render: (text) => {
          let title = text.match(/^\s*\/\*.+\*\//)
          title = title && title[0] ? title[0] : ''
          text = title ? text.replace(title, '') : text
          return (
            <div>
              {title ? <span style={{color: '#a50'}}>{title}</span> : null}
              <Paragraph copyable ellipsis={{ rows: 4, expandable: true }}>{text}</Paragraph>
            </div>
          )
        }
      },
      {
        title: '执行位置',
        dataIndex: 'position',
        width: '10%',
        render: (text, record) => {
          if (record.position === 'front') {
            return 'sql前'
          } else {
            return 'sql后'
          }
        }
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '10%',
        render: (text, record) => record.status === 'false' ?
          (
            <div>
              {this.props.dict['model.status.forbidden']}
              <Icon style={{marginLeft: '5px'}} type="stop" theme="twoTone" twoToneColor="#ff4d4f" />
            </div>
          ) :
          (
            <div>
              {this.props.dict['model.status.open']}
              <Icon style={{marginLeft: '5px'}} type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
            </div>
          )
      },
      {
        title: '操作',
        align: 'center',
        width: '20%',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div style={{textAlign: 'center'}}>
            <span className="operation-btn" title={this.props.dict['model.edit']} onClick={() => this.handleEdit(record, 'cbscripts')} style={{color: '#1890ff'}}><Icon type="edit" /></span>
            <span className="operation-btn" title={this.props.dict['header.form.status.change']} onClick={() => this.handleStatus(record, 'cbscripts')} style={{color: '#8E44AD'}}><Icon type="swap" /></span>
            <Popconfirm
              overlayClassName="popover-confirm"
              title={this.props.dict['model.query.delete']}
              onConfirm={() => this.handleDelete(record, 'cbscripts')
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ],
    orderColumns: [
      {
        title: this.props.dict['header.form.funcvar'],
@@ -485,10 +553,15 @@
    _verify.billcodes = _verify.billcodes || []
    _verify.voucher = _verify.voucher || {enabled: false}
    _verify.scripts = _verify.scripts || []
    _verify.cbScripts = _verify.cbScripts || []
    this.setState({
      verify: _verify
    })
    if (config.Template !== 'FormTab' && (card.intertype === 'inner' || card.intertype === 'outer')) { // 内部或外部接口
      return
    }
    new Promise(resolve => {
      let _fields = []
@@ -523,11 +596,7 @@
            }
            
            if (!_LongParam) {
              notification.warning({
                top: 92,
                message: '未获取到表单信息,部分验证将无法设置!',
                duration: 5
              })
              message.warning('未获取到表单信息,部分验证将无法设置!')
            } else {
              if (_LongParam.groups.length > 0) {
                _LongParam.groups.forEach(group => {
@@ -908,7 +977,7 @@
          systemScripts: res.scripts.map(item => {
            return {
              name: item.funcname,
              value: Utils.UnformatOptions(item.longparam)
              value: window.decodeURIComponent(window.atob(item.longparam))
            }
          })
        })
@@ -989,6 +1058,25 @@
    this.setState({ verify })
  }
  cbScriptsChange = (values) => {
    let verify = fromJS(this.state.verify).toJS()
    if (values.uuid) {
      verify.cbScripts = verify.cbScripts.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      values.uuid = Utils.getuuid()
      verify.cbScripts.push(values)
    }
    this.setState({ verify })
  }
  orderChange = (values) => {
    let verify = fromJS(this.state.verify).toJS()
@@ -1050,6 +1138,8 @@
      verify.billcodes = verify.billcodes.filter(item => item.uuid !== record.uuid)
    } else if (type === 'scripts') {
      verify.scripts = verify.scripts.filter(item => item.uuid !== record.uuid)
    } else if (type === 'cbscripts') {
      verify.cbScripts = verify.cbScripts.filter(item => item.uuid !== record.uuid)
    }
    this.setState({ verify })
@@ -1062,6 +1152,8 @@
      this.orderForm.edit(record)
    } else if (type === 'scripts') {
      this.scriptsForm.edit(record)
    } else if (type === 'cbscripts') {
      this.cbscriptsForm.edit(record)
    }
    let node = document.getElementById('verify-card-box-tab').parentNode
@@ -1102,6 +1194,14 @@
      })
    } else if (type === 'scripts') {
      verify.scripts = verify.scripts.map(item => {
        if (item.uuid === record.uuid) {
          return record
        } else {
          return item
        }
      })
    } else if (type === 'cbscripts') {
      verify.cbScripts = verify.cbScripts.map(item => {
        if (item.uuid === record.uuid) {
          return record
        } else {
@@ -1267,7 +1367,7 @@
  render() {
    const { card } = this.props
    const { verify, fields, uniqueFields, uniqueColumns, unionFields, onceUniqueColumns, columnsFields, contrastColumns, customColumns, orderColumns, scriptsColumns, orderModular, orderModularDetail, voucher, voucherDetail, notes } = this.state
    const { verify, fields, uniqueFields, uniqueColumns, unionFields, onceUniqueColumns, columnsFields, contrastColumns, customColumns, orderColumns, scriptsColumns, cbScriptsColumns, orderModular, orderModularDetail, voucher, voucherDetail, notes } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
@@ -1281,8 +1381,8 @@
    return (
      <div id="verify-card-box-tab">
        {card.intertype === 'system' ? <Tabs defaultActiveKey="1" className="verify-card-box">
          <TabPane tab="基础验证" key="1">
        <Tabs defaultActiveKey="1" className="verify-card-box">
          {card.intertype === 'system' || (card.intertype === 'custom' && card.procMode === 'system') ? <TabPane tab="基础验证" key="1">
            <Form {...formItemLayout}>
              <Row gutter={24}>
                {this.props.card.sqlType !== 'custom' ? <Col span={8}>
@@ -1388,8 +1488,8 @@
                </Col> : null}
              </Row>
            </Form>
          </TabPane>
          <TabPane tab={
          </TabPane> : null}
          {card.intertype === 'system' || (card.intertype === 'custom' && card.procMode === 'system') ? <TabPane tab={
            <span>
              比较验证
              {verify.contrasts.length ? <span className="count-tip">{verify.contrasts.length}</span> : null}
@@ -1397,8 +1497,8 @@
          } key="2x">
            <ContrastForm dict={this.props.dict} contrastChange={this.contrastChange}/>
            <EditTable actions={['edit', 'move', 'copy', 'del']} type="contrastverify" data={verify.contrasts} columns={contrastColumns} onChange={(contrasts) => this.setState({verify: {...verify, contrasts}})}/>
          </TabPane>
          <TabPane tab={
          </TabPane> : null}
          {card.intertype === 'system' || (card.intertype === 'custom' && card.procMode === 'system') ? <TabPane tab={
            <span>
              自定义验证
              {verify.customverifys.length ? <span className="count-tip">{verify.customverifys.length}</span> : null}
@@ -1413,8 +1513,8 @@
              wrappedComponentRef={(inst) => this.customForm = inst}
            />
            <EditTable actions={['move']} data={verify.customverifys} columns={customColumns} onChange={(customverifys) => {this.setState({verify: {...verify, customverifys}})}}/>
          </TabPane>
          <TabPane tab={
          </TabPane> : null}
          {card.intertype === 'system' || (card.intertype === 'custom' && card.procMode === 'system') ? <TabPane tab={
            <span>
              单号生成
              {verify.billcodes.length ? <span className="count-tip">{verify.billcodes.length}</span> : null}
@@ -1432,8 +1532,8 @@
              wrappedComponentRef={(inst) => this.orderForm = inst}
            />
            <EditTable actions={['move']} data={verify.billcodes} columns={orderColumns} onChange={(billcodes) => {this.setState({verify: {...verify, billcodes}})}}/>
          </TabPane>
          <TabPane tab={
          </TabPane> : null}
          {card.intertype === 'system' || (card.intertype === 'custom' && card.procMode === 'system') ? <TabPane tab={
            <span>
              {card.Ot !== 'requiredOnce' ? '唯一性验证' : '同类数据验证'}
              {verify.uniques.length ? <span className="count-tip">{verify.uniques.length}</span> : null}
@@ -1446,8 +1546,8 @@
              uniqueChange={this.uniqueChange}
            />
            <EditTable actions={['edit', 'move', 'del']} data={verify.uniques} columns={card.Ot !== 'requiredOnce' ? uniqueColumns : onceUniqueColumns} onChange={this.changeUniques}/>
          </TabPane>
          <TabPane tab={
          </TabPane> : null}
          {card.intertype === 'system' || (card.intertype === 'custom' && card.procMode === 'system') ? <TabPane tab={
            <span>
              创建凭证
              {verify.voucher && verify.voucher.enabled ? <span className="count-tip">1</span> : null}
@@ -1462,26 +1562,44 @@
              voucherChange={this.voucherChange}
              wrappedComponentRef={(inst) => this.voucherForm = inst}
            />
          </TabPane>
          <TabPane tab={
          </TabPane> : null}
          {card.intertype === 'system' || (card.intertype === 'custom' && card.procMode === 'system') ? <TabPane tab={
            <span>
              自定义脚本
              {verify.scripts.length ? <span className="count-tip">{verify.scripts.length}</span> : null}
            </span>
          } key="6">
            <CustomScript
              usefulfields={this.state.usefulfields}
              initsql={this.state.initsql}
              dict={this.props.dict}
              btn={this.props.card}
              dict={this.props.dict}
              initsql={this.state.initsql}
              customScripts={verify.scripts}
              defaultsql={this.state.defaultsql}
              usefulfields={this.state.usefulfields}
              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>
          </TabPane> : null}
          {card.callbackType === 'script' && card.intertype === 'custom' ? <TabPane tab={
            <span>
              回调脚本
              {verify.cbScripts.length ? <span className="count-tip">{verify.cbScripts.length}</span> : null}
            </span>
          } key="6a">
            <CallBackCustomScript
              btn={this.props.card}
              dict={this.props.dict}
              initsql={this.state.initsql}
              customScripts={verify.cbScripts}
              usefulfields={this.state.usefulfields}
              systemScripts={this.state.systemScripts}
              scriptsChange={this.cbScriptsChange}
              wrappedComponentRef={(inst) => this.cbscriptsForm = inst}
            />
            <EditTable actions={['move']} data={verify.cbScripts} columns={cbScriptsColumns} onChange={(cbScripts) => {this.setState({verify: {...verify, cbScripts}})}}/>
          </TabPane> : null}
          <TabPane tab="信息提示" key="7">
            <Form {...formItemLayout}>
              <Row gutter={24}>
@@ -1569,96 +1687,7 @@
              </Row>
            </Form>
          </TabPane>
        </Tabs> : null}
        {card.intertype !== 'system' ? <Tabs defaultActiveKey="7" className="verify-card-box">
          <TabPane tab="信息提示" key="7">
            <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> : null}
        </Tabs>
      </div>
    )
  }
src/utils/utils-datamanage.js
@@ -18,7 +18,7 @@
  static getQueryDataParams (setting, arrFields, search = [], orderBy = '', pageIndex = 1, pageSize = 10, BID, menuType, id) {
    let param = null
    if (setting.interType === 'system' || (setting.interType === 'inner' && !setting.innerFunc)) {
    if (setting.interType === 'system' || (setting.interType === 'custom' && setting.requestMode === 'system')) {
      param = this.getDefaultQueryParam(setting, arrFields, search, orderBy, pageIndex, pageSize, menuType, id)
    } else {
      param = this.getCustomQueryParam(setting, search, orderBy, pageIndex, pageSize, menuType, id)
@@ -52,7 +52,7 @@
      param.PageSize = pageSize
    }
    if (setting.interType === 'inner') {
    if (setting.interType === 'inner' || (setting.interType === 'custom' && setting.requestMode === 'inner')) {
      param.func = setting.innerFunc
    } else {
      if (menuType === 'HS') {
@@ -323,4 +323,248 @@
    return param
  }
  /**
   * @description 数据源名称,用于统一查询
   * @param {Object}   setting      数据源设置
   * @param {String}   arrFields    查询字段
   * @param {Array}    search       搜索条件
   * @param {String}   orderBy      排序方式
   * @param {Number}   pageIndex    页码
   * @param {Number}   pageSize     每页数量
   * @param {String}   BID          上级ID
   * @param {String}   menuType     菜单类型,普通菜单与HS
   * @return {Object}  param
   */
  static getPrevQueryParams (setting, search = [], BID, menuType) {
    let param = null
    if (setting.procMode !== 'inner') {
      param = this.getDefaultPrevQueryParam(setting, search, menuType)
    } else {
      param = Utils.formatCustomMainSearch(search)
      param.func = setting.prevFunc || ''
    }
    if (BID) {
      param.BID = BID
    }
    return param
  }
  /**
   * @description 获取系统存储过程 sPC_Get_TableData 的参数
   */
  static getDefaultPrevQueryParam (setting, search, menuType) {
    let param = {
      func: 'sPC_TableData_InUpDe',
      exec_type: 'y',
      script_type: 'Y'
    }
    let sql = ''
    let userName = sessionStorage.getItem('User_Name') || ''
    let fullName = sessionStorage.getItem('Full_Name') || ''
    if (sessionStorage.getItem('isEditState') === 'true') {
      userName = sessionStorage.getItem('CloudUserName') || ''
      fullName = sessionStorage.getItem('CloudFullName') || ''
    }
    setting.preScripts.forEach(item => {
      if (item.status === 'false') return
      sql += `${item.sql}
      `
    })
    if (sql) {
      sql = `/*前置脚本*/
        declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000),@UserName nvarchar(50),@FullName nvarchar(50)
        Select @ErrorCode='',@retmsg ='',@UserName='${userName}', @FullName='${fullName}'
        ${sql}
        aaa:
          if @ErrorCode!=''
            insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
      `
      let allSearch = Utils.getAllSearchOptions(search)
      let regoptions = allSearch.map(item => {
        return {
          reg: new RegExp('@' + item.key + '@', 'ig'),
          value: `'${item.value}'`
        }
      })
      regoptions.push({
        reg: new RegExp('@userName@', 'ig'),
        value: userName
      }, {
        reg: new RegExp('@fullName@', 'ig'),
        value: fullName
      })
      regoptions.forEach(item => {
        sql = sql.replace(item.reg, item.value)
      })
      // 测试系统打印查询语句
      if ((options.sysType === 'local' && !window.GLOB.systemType) || window.debugger === true) {
        console.info(sql.replace(/\n\s{8}/ig, '\n'))
      }
    }
    if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
      sql = sql.replace(/\$@/ig, '/*')
      sql = sql.replace(/@\$/ig, '*/')
    } else {
      sql = sql.replace(/@\$|\$@/ig, '')
    }
    param.LText = Utils.formatOptions(sql)
    param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    param.secretkey = Utils.encrypt('', param.timestamp)
    if (menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
      param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
    }
    param.menuname = setting.MenuName || ''
    return param
  }
  /**
   * @description 获取系统存储过程 sPC_Get_TableData 的参数
   */
  static getCallBackQueryParams (setting, sql, errSql) {
    let param = {
      func: 'sPC_TableData_InUpDe',
      exec_type: 'y',
    }
    let userName = sessionStorage.getItem('User_Name') || ''
    let fullName = sessionStorage.getItem('Full_Name') || ''
    if (sessionStorage.getItem('isEditState') === 'true') {
      userName = sessionStorage.getItem('CloudUserName') || ''
      fullName = sessionStorage.getItem('CloudFullName') || ''
    }
    let _prevCustomScript = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000),@UserName nvarchar(50),@FullName nvarchar(50)
        Select @ErrorCode='',@retmsg='',@UserName='${userName}', @FullName='${fullName}'
        ${errSql}
    `
    let _backCustomScript = `
    `
    setting.cbScripts.forEach(script => {
      if (script.status === 'false') return
      if (script.position === 'front') {
        _prevCustomScript += `
        /* 自定义脚本 */
        ${script.sql}
        `
      } else {
        _backCustomScript += `
        /* 自定义脚本 */
        ${script.sql}
        `
      }
    })
    _backCustomScript += `
      aaa: select @ErrorCode as ErrorCode,@retmsg as retmsg`
    sql = _prevCustomScript + sql
    sql = sql + _backCustomScript
    if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
      console.info(sql.replace(/\n\s{8}/ig, '\n'))
    }
    param.LText = sql
    if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
      param.LText = param.LText.replace(/\$@/ig, '/*')
      param.LText = param.LText.replace(/@\$/ig, '*/')
    } else {
      param.LText = param.LText.replace(/@\$|\$@/ig, '')
    }
    param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    param.secretkey = Utils.encrypt('', param.timestamp)
    param.LText = Utils.formatOptions(param.LText)
    param.menuname = setting.MenuName || ''
    return param
  }
  /**
   * @description 获取回调sql
   */
  static getCallBackSql (setting, result) {
    let lines = []
    let pre = setting.callbackType === 'script' ? '@' : ''
    let getDefaultSql = (obj, tb, bid, level) => {
      let keys = []
      let vals = []
      let subObjs = []
      let id = Utils.getuuid()
      delete obj.$$key
      Object.keys(obj).forEach(key => {
        let val = obj[key]
        if (val === null || val === undefined) return
        if (typeof(val) === 'object') {
          if (Array.isArray(val)) {
            val.forEach(item => {
              if (typeof(item) !== 'object' || Array.isArray(item)) return
              if (Object.keys(item).length > 0) {
                item.$$key = tb + '_' + key
                subObjs.push(item)
              }
            })
          } else if (Object.keys(val).length > 0) {
            val.$$key = tb + '_' + key
            subObjs.push(val)
          }
        } else {
          keys.push(key)
          vals.push(`'${val}'`)
        }
      })
      lines.push({
        table: tb,
        insert: `Insert into ${pre}${tb} (${keys.join(',')},mk_level,mk_id,mk_bid)`,
        select: `Select ${vals.join(',')},'${level}','${id}','${bid}'`
      })
      subObjs.forEach(item => {
        getDefaultSql(item, item.$$key, id, level + 1)
      })
    }
    getDefaultSql(result, setting.cbTable, '', 1)
    let lineMap = new Map()
    lines.forEach(line => {
      if (lineMap.has(line.table)) {
        let _line = lineMap.get(line.table)
        _line.selects.push(line.select)
        lineMap.set(line.table, _line)
      } else {
        lineMap.set(line.table, {
          table: line.table,
          insert: line.insert,
          selects: [line.select]
        })
      }
    })
    return [...lineMap.values()]
  }
}
src/utils/utils-update.js
@@ -1,169 +1,164 @@
import { fromJS } from 'immutable'
import Utils from './utils.js'
/**
 * @description 升级主表信息
 * @param {Object}   config      页面配置信息
 * @return {Object}  config
 */
export function updateCommonTable (config) {
  if (!config.version || config.version < '1.0') {
    config.version = '1.0'
    // 兼容标签
    if (!config.tabgroups) {
      config.tabgroups = [{ uuid: 'tabs', sublist: [] }]
    } else if (typeof(config.tabgroups[0]) === 'string') {
      let _tabgroups = []
      config.tabgroups.forEach(groupId => {
        let _group = {
          uuid: groupId,
          sublist: config[groupId]
        }
export default class UpdateUtils {
  /**
   * @description 升级主表信息
   * @param {Object}   config      页面配置信息
   * @return {Object}  config
   */
  static updateCommonTable (config) {
    if (!config.version || config.version < '1.0') {
      config.version = '1.0'
      // 兼容标签
      if (!config.tabgroups) {
        config.tabgroups = [{ uuid: 'tabs', sublist: [] }]
      } else if (typeof(config.tabgroups[0]) === 'string') {
        let _tabgroups = []
        config.tabgroups.forEach(groupId => {
          let _group = {
            uuid: groupId,
            sublist: fromJS(config[groupId]).toJS()
          }
          delete config[groupId]
          _tabgroups.push(_group)
        })
        config.tabgroups = _tabgroups
      }
      // 兼容图表
      if (!config.charts) {
        config.expand = true
        config.charts = [{ uuid: Utils.getuuid(), label: '', title: '', chartType: 'table', icon: 'table', Hide: 'false', blacklist: [] }]
      } else {
        config.charts.forEach(card => {
          if (card.chartType === 'card') {
            card.details = card.details.map(_cell => {
              _cell.fontSize = _cell.fontSize || 14
              if (!_cell.width) {
                _cell.width = 100
              } else if (_cell.width === 'helf') {
                _cell.width = 50
              } else if (_cell.width === 'third') {
                _cell.width = 33
              }
              if (!_cell.fontWeight && _cell.bold === 'true') {
                _cell.fontWeight = 'normal'
              }
              _cell.height = _cell.height || 1
              return _cell
            })
          }
        })
      }
        delete config[groupId]
        _tabgroups.push(_group)
      })
      config.tabgroups = _tabgroups
    }
    if (config.version < '1.1') {
      config.version = '1.1'
      if (config.setting.interType === 'inner' && !config.setting.innerFunc) {
        config.setting.interType = 'system'
      }
      // 兼容接口类型
      config.action = config.action.map(item => {
        if (item.intertype === 'inner' && !item.innerFunc) {
          item.intertype = 'system'
    // 兼容图表
    if (!config.charts) {
      config.expand = true
      config.charts = [{ uuid: '$$normaltable', label: '', title: '', chartType: 'table', icon: 'table', Hide: 'false', blacklist: [] }]
    } else {
      config.charts.forEach(card => {
        if (card.chartType === 'card') {
          card.details = card.details.map(_cell => {
            _cell.fontSize = _cell.fontSize || 14
            if (!_cell.width) {
              _cell.width = 100
            } else if (_cell.width === 'helf') {
              _cell.width = 50
            } else if (_cell.width === 'third') {
              _cell.width = 33
            }
            if (!_cell.fontWeight && _cell.bold === 'true') {
              _cell.fontWeight = 'normal'
            }
            _cell.height = _cell.height || 1
            return _cell
          })
        }
        return item
      })
    }
    if (config.version < '1.2') {
      config.version = '1.2'
      // 兼容功能按钮
      config.action = config.action.map(item => {
        if (item.execMode) {
          item.OpenType = 'funcbutton'
        }
        return item
      })
    }
    config.Template = 'CommonTable'
    return config
  }
  /**
   * @description 升级子表信息
   * @param {Object}   config      页面配置信息
   * @return {Object}  config
   */
  static updateSubTable (config) {
    if (!config.version || config.version < '1.0') {
      config.version = '1.0'
      // 兼容图表
      if (!config.charts) {
        config.expand = false
        config.charts = [{
          uuid: Utils.getuuid(),
          label: '',
          title: '',
          chartType: 'table',
          icon: 'table',
          Hide: 'false',
          blacklist: []
        }]
      } else {
        config.charts.forEach(card => {
          if (card.chartType === 'card') {
            card.details = card.details.map(_cell => {
              if (!_cell.fontSize) {
                _cell.fontSize = 14
              }
              if (!_cell.width) {
                _cell.width = 100
              } else if (_cell.width === 'helf') {
                _cell.width = 50
              } else if (_cell.width === 'third') {
                _cell.width = 33
              }
              if (_cell.bold === 'true') {
                _cell.fontWeight = 'normal'
              }
              if (!_cell.height) {
                _cell.height = 1
              }
              return _cell
            })
          }
        })
  if (config.version < '1.1') {
    config.version = '1.1'
    if (config.setting.interType === 'inner' && !config.setting.innerFunc) {
      config.setting.interType = 'system'
    }
    // 兼容接口类型
    config.action = config.action.map(item => {
      if (item.intertype === 'inner' && !item.innerFunc) {
        item.intertype = 'system'
      }
    }
    if (config.version < '1.1') {
      config.version = '1.1'
      if (config.setting.interType === 'inner' && !config.setting.innerFunc) {
        config.setting.interType = 'system'
      }
      // 兼容接口类型
      config.action = config.action.map(item => {
        if (item.intertype === 'inner' && !item.innerFunc) {
          item.intertype = 'system'
        }
        return item
      })
    }
    if (config.version < '1.2') {
      config.version = '1.2'
      // 兼容功能按钮
      config.action = config.action.map(item => {
        if (item.execMode) {
          item.OpenType = 'funcbutton'
        }
        return item
      })
    }
    config.Template = 'SubTable'
    return config
      return item
    })
  }
  if (config.version < '1.2') {
    config.version = '1.2'
    // 兼容功能按钮
    config.action = config.action.map(item => {
      if (item.execMode) {
        item.OpenType = 'funcbutton'
      }
      return item
    })
  }
  config.Template = 'CommonTable'
  return config
}
/**
 * @description 升级子表信息
 * @param {Object}   config      页面配置信息
 * @return {Object}  config
 */
export function updateSubTable (config) {
  if (!config.version || config.version < '1.0') {
    config.version = '1.0'
    // 兼容图表
    if (!config.charts) {
      config.expand = false
      config.charts = [{
        uuid: '$$normalsubtable',
        label: '',
        title: '',
        chartType: 'table',
        icon: 'table',
        Hide: 'false',
        blacklist: []
      }]
    } else {
      config.charts.forEach(card => {
        if (card.chartType === 'card') {
          card.details = card.details.map(_cell => {
            if (!_cell.fontSize) {
              _cell.fontSize = 14
            }
            if (!_cell.width) {
              _cell.width = 100
            } else if (_cell.width === 'helf') {
              _cell.width = 50
            } else if (_cell.width === 'third') {
              _cell.width = 33
            }
            if (_cell.bold === 'true') {
              _cell.fontWeight = 'normal'
            }
            if (!_cell.height) {
              _cell.height = 1
            }
            return _cell
          })
        }
      })
    }
  }
  if (config.version < '1.1') {
    config.version = '1.1'
    if (config.setting.interType === 'inner' && !config.setting.innerFunc) {
      config.setting.interType = 'system'
    }
    // 兼容接口类型
    config.action = config.action.map(item => {
      if (item.intertype === 'inner' && !item.innerFunc) {
        item.intertype = 'system'
      }
      return item
    })
  }
  if (config.version < '1.2') {
    config.version = '1.2'
    // 兼容功能按钮
    config.action = config.action.map(item => {
      if (item.execMode) {
        item.OpenType = 'funcbutton'
      }
      return item
    })
  }
  config.Template = 'SubTable'
  return config
}
src/utils/utils.js
@@ -183,45 +183,6 @@
  }
  /**
   * @description 解密
   * @return {String}   value
   */
  static UnformatOptions (value) {
    if (!value) return ''
    let salt = 'minKe' // 盐值
    let _value = ''
    try {
      try {
        _value = JSON.parse(window.decodeURIComponent(window.atob(value)))
      } catch {
        _value = ''
      }
      if (!_value) {
        _value = window.atob(value)
        _value = _value.replace(salt, '')
        _value = window.decodeURIComponent(window.atob(_value))
        _value = _value.replace(/\smpercent\s/g, '%')
        formatKeys.forEach(item => {
          let reg = new RegExp(item.value, 'g')
          _value = _value.replace(reg, ' ' + item.key + ' ')
        })
        _value = _value.replace(/\s\n\s/ig, '\n')
        _value = _value.replace(/(^\s+|\s+$)/ig, '')
      }
    } catch {
      console.warn('UnFormat Failure')
      _value = ''
    }
    return _value
  }
  /**
   * @description sPC_TableData_InUpDe sql加密
   * @return {String}  value
   */
@@ -762,941 +723,962 @@
      field: arrfield
    }
  }
}
  /**
   * @description 获取excel导入参数
   * @return {String} btn   按钮
   * @return {String} data  excel数据
   */
  static getExcelInSql (item, data, dict, BID) {
    let btn = item.verify
    let keys = ['delete', 'drop', 'insert', 'truncate', 'update']
    let userName = sessionStorage.getItem('User_Name') || ''
    let fullName = sessionStorage.getItem('Full_Name') || ''
/**
 * @description 获取excel导入参数
 * @return {String} btn   按钮
 * @return {String} data  excel数据
 */
export function getExcelInSql (item, data, dict, BID) {
  let btn = item.verify
  let keys = ['delete', 'drop', 'insert', 'truncate', 'update']
  let userName = sessionStorage.getItem('User_Name') || ''
  let fullName = sessionStorage.getItem('Full_Name') || ''
    if (sessionStorage.getItem('isEditState') === 'true') {
      userName = sessionStorage.getItem('CloudUserName') || ''
      fullName = sessionStorage.getItem('CloudFullName') || ''
    }
    let database = item.sheet.match(/(.*)\.(.*)\./ig) || ''
    let sheet = item.sheet.replace(/(.*)\.(.*)\./ig, '')
    let errors = []
    let _topline = btn.range || 0
    let upId = this.getuuid()
    let _initCustomScript = '' // 初始化脚本
    let _prevCustomScript = '' // 默认sql前执行脚本
    let _backCustomScript = '' // 默认sql后执行脚本
    if (btn.scripts) {
      btn.scripts.forEach(script => {
        if (script.status === 'false') return
        if (script.position === 'init') {
          _initCustomScript += `
        /* 自定义脚本 */
        ${script.sql}
        `
        } else if (script.position === 'front') {
          _prevCustomScript += `
        /* 自定义脚本 */
        ${script.sql}
        `
        } else {
          _backCustomScript += `
        /* 自定义脚本 */
        ${script.sql}
        `
        }
      })
    }
    // 控制台打印数据
    let conLtext = []
    let _Ltext = data.map((item, lindex) => {
      let vals = []
      let convals = []
      btn.columns.forEach((col, cindex) => {
        if (col.import === 'false') return
        let val = item[col.Column] !== undefined ? item[col.Column] : ''
        let _position = (_topline + lindex + 1) + dict['main.excel.line'] + ' ' + (cindex + 1) + dict['main.excel.column']  + ' '
        if (/^Nvarchar/ig.test(col.type)) {
          if (typeof(val) === 'number') {
            val = val.toString()
          }
          val = val.replace(/(^\s*$)|\t*|\v*/ig, '')
          if (!val && col.required === 'true') {            // 必填校验
            errors.push(_position + dict['main.excel.content.emptyerror'])
          } else if (col.limit && val.length > col.limit) { // 长度校验
            errors.push(_position + dict['main.excel.content.maxlimit'])
          } else {                                          // 关键字校验
            keys.forEach(key => {
              let _patten = new RegExp('(^' + key + '\\s+)|(\\s+' + key + '\\s+)', 'ig')
              if (_patten.test(val)) {
                errors.push(_position + dict['main.excel.includekey'] + key)
              }
            })
          }
        } else if (/^int/ig.test(col.type)) {
          if (!val && val !== 0) {
            errors.push(_position + dict['main.excel.content.emptyerror'])
          } else {
            let _val = val + ''
            if (!/^(([^0][0-9]+|0)$)|^(([1-9]+)$)/.test(_val)) {               // 检验是否为整数
              errors.push(_position + dict['main.excel.content.interror'])
            } else if ((col.min || col.min === 0) && val < col.min) {          // 最小值检验
              errors.push(_position + dict['main.excel.content.limitmin'])
            } else if ((col.max || col.max === 0) && val > col.max) {          // 最大值检验
              errors.push(_position + dict['main.excel.content.limitmax'])
            }
          }
        } else if (/^Decimal/ig.test(col.type)) {
          if (!val && val !== 0) {
            errors.push(_position + dict['main.excel.content.emptyerror'])
          } else {
            let _val = val + ''
            let _vals = _val.split('.')
            if (!/^(([^0][0-9]+|0)\.([0-9]+)$)|^(([^0][0-9]+|0)$)|^(([1-9]+)\.([0-9]+)$)|^(([1-9]+)$)/.test(_val)) {                           // 检验是否为浮点数
              errors.push(_position + dict['main.excel.content.floaterror'])
            } else if (_vals[0].length > 18) {                          // 检验整数位
              errors.push(_position + dict['main.excel.content.floatIntover'])
            } else if (_vals[1] && _vals[1].length > col.limit) {       // 最小值检验
              errors.push(_position + dict['main.excel.content.floatPointover'])
            } else if ((col.min || col.min === 0) && val < col.min) {   // 最小值检验
              errors.push(_position + dict['main.excel.content.limitmin'])
            } else if ((col.max || col.max === 0) && val > col.max) {   // 最大值检验
              errors.push(_position + dict['main.excel.content.limitmax'])
            }
          }
        } else if (col.type === 'date') {
          if (typeof(val) === 'number') {
            if (val > 2958465 || val <= 0) {                 // 时间过大或小于等于0
              errors.push(_position + dict['main.excel.content.date.over'])
            } else {                                         // 时间格式化
              if (val < 60) {                                // 1900-2-29,excel中存在,实际不存在
                val++
              }
              val = moment('19000101', 'YYYYMMDD').add(Math.floor(val - 2), 'days').format('YYYY-MM-DD')
            }
          } else if (typeof(val) === 'string') {
            val = val.replace(/(^\s*$)|\t*|\v*/ig, '')
            if (!val && col.required === 'true') {           // 时间必填校验
              errors.push(_position + dict['main.excel.content.emptyerror'])
            } else if (val && !/^[1-9][0-9]{3}/.test(val)) { // 时间正则校验
              errors.push(_position + dict['main.excel.content.date.formatError'])
            }
          } else {                                           // 时间格式错误
            errors.push(_position + dict['main.excel.content.date.formatError'])
          }
        }
        vals.push(`'${val}'`)
        if (lindex < 40) {
          convals.push(`'${val}' as ${col.Column}`)
        }
      })
      let _lineIndex = '0000' + (lindex + 1) + '0'
      _lineIndex = _lineIndex.substring(_lineIndex.length - 6)
      vals.push(`'${upId + _lineIndex}'`)
      vals.push(`'${BID}'`)
      if (lindex < 40) {
        convals.push(`'${upId + _lineIndex}' as jskey`)
        convals.push(`'${BID}' as BID`)
        conLtext.push(`Select ${convals.join(',')}`)
      }
      return `Select ${vals.join(',')}`
    })
    let result = []
    for(let i = 0; i < _Ltext.length; i += 20) {
      result.push(_Ltext.slice(i, i + 20))
    }
    let _sql = ''
    let _sqlInsert = ''
    let _sqlBottom = ''
    if (item.intertype === 'system') {
      let _uniquesql = ''
      if (btn.uniques && btn.uniques.length > 0) {
        btn.uniques.forEach(unique => {
          if (unique.status === 'false') return
          let _fields = unique.field.split(',')
          let _fields_ = _fields.map(_field => `a.${_field}=b.${_field}`)
          let _afields = _fields.map(_field => `a.${_field}`)
          _fields_ = _fields_.join(' and ')
          if (unique.verifyType !== 'physical') {
            _fields_ += ' and b.deleted=0'
          }
          _uniquesql += `
        /* 重复性验证 */
        Set @tbid=''
        Select top 1 @tbid=${_fields.join('+\' \'+')} from (select 1 as n,${unique.field} from @${sheet} ) a group by ${unique.field} having sum(n)>1
        If @tbid!=''
        Begin
          select @ErrorCode='${unique.errorCode}',@retmsg=@tbid+' 重复'
          goto aaa
        end
        Set @tbid=''
        Select top 1 @tbid=${_afields.join('+\' \'+')} from  @${sheet} a Inner join ${sheet} b on ${_fields_}
        If @tbid!=''
        Begin
          select @ErrorCode='${unique.errorCode}',@retmsg=@tbid+' 与已有数据重复'
          goto aaa
        end
        `
        })
      }
      let declarefields = []
      let fields = []
      btn.columns.forEach(col => {
        if (col.import === 'false') return
        if (col.type === 'date') {
          declarefields.push(`${col.Column} Nvarchar(50)`)
        } else {
          declarefields.push(`${col.Column} ${col.type}`)
        }
        fields.push(col.Column)
      })
      fields = fields.join(',')
      let _insert = ''
      if (btn.default !== 'false') {
        _insert = `
        /* 默认sql */
        Insert into ${database}${sheet} (${fields},createuserid,createuser,createstaff,bid)
        Select ${fields},@userid@,@username,@fullname,@BID@ From @${sheet}
        `
      }
      _sql = `
        /* 系统生成 */
        declare @${sheet} table (${declarefields.join(',')},jskey nvarchar(50),BID nvarchar(50) )
        Declare @UserName nvarchar(50),@FullName nvarchar(50),@ErrorCode nvarchar(50),@retmsg nvarchar(4000),@tbid Nvarchar(512)
        Select  @ErrorCode='', @retmsg='', @UserName='${userName}', @FullName='${fullName}'
        ${_initCustomScript}
        `
      _sqlInsert = `Insert into @${sheet} (${fields},jskey,BID)`
      _sqlBottom = `
        /* 默认sql */
        delete tmp_excel_in where upid=@upid@
        delete tmp_excel_in where datediff(day,createdate,getdate())>15
        ${_uniquesql}
        ${_prevCustomScript}
        ${_insert}
        ${_backCustomScript}
        Delete @${sheet}
        aaa: select @ErrorCode as ErrorCode,@retmsg as retmsg`
      if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
        let fsql = `
        ${_sql}
        ${_sqlInsert}
        /* excel数据(前40条) */
        ${conLtext.join(' Union all ')}
        ${_sqlBottom}
        `
        fsql = fsql.replace(/\n\s{8}/ig, '\n')
        console.info(fsql)
      }
    } else { // s_sDataDictb_excelIn 云端密钥验证参数
      _sql = `
        /* 系统生成 */
        declare @${sheet} table (jskey nvarchar(50))
        Declare @UserName nvarchar(50),@FullName nvarchar(50),@ErrorCode nvarchar(50),@retmsg nvarchar(4000),@tbid Nvarchar(512)
        Select  @ErrorCode='', @retmsg='', @UserName='${userName}', @FullName='${fullName}'
        `
    }
    return {
      sql: _sql,
      lines: result.map((list, index) => {
        return {
          Ltext: window.btoa(window.encodeURIComponent(list.join(' Union all '))),
          Sort: (index + 1) * 10
        }
      }),
      insert: _sqlInsert,
      bottom: _sqlBottom,
      errors: errors.join('; ')
    }
  if (sessionStorage.getItem('isEditState') === 'true') {
    userName = sessionStorage.getItem('CloudUserName') || ''
    fullName = sessionStorage.getItem('CloudFullName') || ''
  }
  /**
   * @description 使用系统函数时(sPC_TableData_InUpDe ),生成sql语句
   * @return {String} type   执行类型
   * @return {String} table  表名
   */
  static getSysDefaultSql (btn, setting, formdata, param, data, columns, tab) {
    let primaryId = param.ID
    let BID = param.BID
    let verify = btn.verify || {}
    let datavars = {}                 // 声明的变量,表单及显示列
    let _actionType = null
    let appkey = window.GLOB.appkey || ''
    let sessionUid = localStorage.getItem('SessionUid') || ''
  let database = item.sheet.match(/(.*)\.(.*)\./ig) || ''
  let sheet = item.sheet.replace(/(.*)\.(.*)\./ig, '')
    if (verify.default !== 'false') { // 判断是否使用默认sql
      _actionType = btn.sqlType
  let getuuid = () => {
    let uuid = []
    let timestamp = new Date().getTime()
    let _options = '0123456789abcdefghigklmnopqrstuv'
    for (let i = 0; i < 19; i++) {
      uuid.push(_options.substr(Math.floor(Math.random() * 0x20), 1))
    }
    uuid = timestamp + uuid.join('')
    return uuid
  }
    let _initCustomScript = '' // 初始化脚本
    let _prevCustomScript = '' // 默认sql前执行脚本
    let _backCustomScript = '' // 默认sql后执行脚本
  let errors = []
  let _topline = btn.range || 0
  let upId = getuuid()
    verify.scripts && verify.scripts.forEach(item => {
      if (item.status === 'false') return
  let _initCustomScript = '' // 初始化脚本
  let _prevCustomScript = '' // 默认sql前执行脚本
  let _backCustomScript = '' // 默认sql后执行脚本
      if (item.position === 'init') {
  if (btn.scripts) {
    btn.scripts.forEach(script => {
      if (script.status === 'false') return
      if (script.position === 'init') {
        _initCustomScript += `
        /* 自定义脚本 */
        ${item.sql}
        `
      } else if (item.position === 'front') {
      /* 自定义脚本 */
      ${script.sql}
      `
      } else if (script.position === 'front') {
        _prevCustomScript += `
        /* 自定义脚本 */
        ${item.sql}
        `
      /* 自定义脚本 */
      ${script.sql}
      `
      } else {
        _backCustomScript += `
        /* 自定义脚本 */
        ${item.sql}
        `
      /* 自定义脚本 */
      ${script.sql}
      `
      }
    })
  }
  // 控制台打印数据
  let conLtext = []
  let _Ltext = data.map((item, lindex) => {
    let vals = []
    let convals = []
    btn.columns.forEach((col, cindex) => {
      if (col.import === 'false') return
      let val = item[col.Column] !== undefined ? item[col.Column] : ''
      let _position = (_topline + lindex + 1) + dict['main.excel.line'] + ' ' + (cindex + 1) + dict['main.excel.column']  + ' '
      if (/^Nvarchar/ig.test(col.type)) {
        if (typeof(val) === 'number') {
          val = val.toString()
        }
        val = val.replace(/(^\s*$)|\t*|\v*/ig, '')
        if (!val && col.required === 'true') {            // 必填校验
          errors.push(_position + dict['main.excel.content.emptyerror'])
        } else if (col.limit && val.length > col.limit) { // 长度校验
          errors.push(_position + dict['main.excel.content.maxlimit'])
        } else {                                          // 关键字校验
          keys.forEach(key => {
            let _patten = new RegExp('(^' + key + '\\s+)|(\\s+' + key + '\\s+)', 'ig')
            if (_patten.test(val)) {
              errors.push(_position + dict['main.excel.includekey'] + key)
            }
          })
        }
      } else if (/^int/ig.test(col.type)) {
        if (!val && val !== 0) {
          errors.push(_position + dict['main.excel.content.emptyerror'])
        } else {
          let _val = val + ''
          if (!/^(([^0][0-9]+|0)$)|^(([1-9]+)$)/.test(_val)) {               // 检验是否为整数
            errors.push(_position + dict['main.excel.content.interror'])
          } else if ((col.min || col.min === 0) && val < col.min) {          // 最小值检验
            errors.push(_position + dict['main.excel.content.limitmin'])
          } else if ((col.max || col.max === 0) && val > col.max) {          // 最大值检验
            errors.push(_position + dict['main.excel.content.limitmax'])
          }
        }
      } else if (/^Decimal/ig.test(col.type)) {
        if (!val && val !== 0) {
          errors.push(_position + dict['main.excel.content.emptyerror'])
        } else {
          let _val = val + ''
          let _vals = _val.split('.')
          if (!/^(([^0][0-9]+|0)\.([0-9]+)$)|^(([^0][0-9]+|0)$)|^(([1-9]+)\.([0-9]+)$)|^(([1-9]+)$)/.test(_val)) {                           // 检验是否为浮点数
            errors.push(_position + dict['main.excel.content.floaterror'])
          } else if (_vals[0].length > 18) {                          // 检验整数位
            errors.push(_position + dict['main.excel.content.floatIntover'])
          } else if (_vals[1] && _vals[1].length > col.limit) {       // 最小值检验
            errors.push(_position + dict['main.excel.content.floatPointover'])
          } else if ((col.min || col.min === 0) && val < col.min) {   // 最小值检验
            errors.push(_position + dict['main.excel.content.limitmin'])
          } else if ((col.max || col.max === 0) && val > col.max) {   // 最大值检验
            errors.push(_position + dict['main.excel.content.limitmax'])
          }
        }
      } else if (col.type === 'date') {
        if (typeof(val) === 'number') {
          if (val > 2958465 || val <= 0) {                 // 时间过大或小于等于0
            errors.push(_position + dict['main.excel.content.date.over'])
          } else {                                         // 时间格式化
            if (val < 60) {                                // 1900-2-29,excel中存在,实际不存在
              val++
            }
            val = moment('19000101', 'YYYYMMDD').add(Math.floor(val - 2), 'days').format('YYYY-MM-DD')
          }
        } else if (typeof(val) === 'string') {
          val = val.replace(/(^\s*$)|\t*|\v*/ig, '')
          if (!val && col.required === 'true') {           // 时间必填校验
            errors.push(_position + dict['main.excel.content.emptyerror'])
          } else if (val && !/^[1-9][0-9]{3}/.test(val)) { // 时间正则校验
            errors.push(_position + dict['main.excel.content.date.formatError'])
          }
        } else {                                           // 时间格式错误
          errors.push(_position + dict['main.excel.content.date.formatError'])
        }
      }
      vals.push(`'${val}'`)
      if (lindex < 40) {
        convals.push(`'${val}' as ${col.Column}`)
      }
    })
    // 需要声明的变量集
    let _vars = ['tbid', 'errorcode', 'retmsg', 'billcode', 'bvoucher', 'fibvoucherdate', 'fiyear', 'username', 'fullname', 'modulardetailcode']
    let _lineIndex = '0000' + (lindex + 1) + '0'
    _lineIndex = _lineIndex.substring(_lineIndex.length - 6)
    // 主键字段
    let primaryKey = setting.primaryKey || 'id'
    vals.push(`'${upId + _lineIndex}'`)
    vals.push(`'${BID}'`)
    if (lindex < 40) {
      convals.push(`'${upId + _lineIndex}' as jskey`)
      convals.push(`'${BID}' as BID`)
      conLtext.push(`Select ${convals.join(',')}`)
    }
    // sql语句
    let _sql = ''
    return `Select ${vals.join(',')}`
  })
    let _initvars = [] // 已赋值字段集
    let _initFormfields = []
    let _initColfields = []
    let _declarefields = []
  let result = []
  for(let i = 0; i < _Ltext.length; i += 20) {
    result.push(_Ltext.slice(i, i + 20))
  }
    // 获取字段键值对
    formdata && formdata.forEach(form => {
      if (form.type === 'text') { // 特殊字段替换
        form.value = form.value.replace(/^(\s*)@appkey@(\s*)$/ig, appkey)
        form.value = form.value.replace(/^(\s*)@SessionUid@(\s*)$/ig, sessionUid)
        form.value = form.value.replace(/^(\s*)@bid@(\s*)$/ig, BID)
  let _sql = ''
  let _sqlInsert = ''
  let _sqlBottom = ''
  if (item.intertype === 'system') {
    let _uniquesql = ''
    if (btn.uniques && btn.uniques.length > 0) {
      btn.uniques.forEach(unique => {
        if (unique.status === 'false') return
        let _fields = unique.field.split(',')
        let _fields_ = _fields.map(_field => `a.${_field}=b.${_field}`)
        let _afields = _fields.map(_field => `a.${_field}`)
        _fields_ = _fields_.join(' and ')
        if (unique.verifyType !== 'physical') {
          _fields_ += ' and b.deleted=0'
        }
        _uniquesql += `
      /* 重复性验证 */
      Set @tbid=''
      Select top 1 @tbid=${_fields.join('+\' \'+')} from (select 1 as n,${unique.field} from @${sheet} ) a group by ${unique.field} having sum(n)>1
      If @tbid!=''
      Begin
        select @ErrorCode='${unique.errorCode}',@retmsg=@tbid+' 重复'
        goto aaa
      end
      Set @tbid=''
      Select top 1 @tbid=${_afields.join('+\' \'+')} from  @${sheet} a Inner join ${sheet} b on ${_fields_}
      If @tbid!=''
      Begin
        select @ErrorCode='${unique.errorCode}',@retmsg=@tbid+' 与已有数据重复'
        goto aaa
      end
      `
      })
    }
    let declarefields = []
    let fields = []
    btn.columns.forEach(col => {
      if (col.import === 'false') return
      if (col.type === 'date') {
        declarefields.push(`${col.Column} Nvarchar(50)`)
      } else {
        declarefields.push(`${col.Column} ${col.type}`)
      }
      fields.push(col.Column)
    })
    fields = fields.join(',')
    let _insert = ''
    if (btn.default !== 'false') {
      _insert = `
      /* 默认sql */
      Insert into ${database}${sheet} (${fields},createuserid,createuser,createstaff,bid)
      Select ${fields},@userid@,@username,@fullname,@BID@ From @${sheet}
      `
    }
    _sql = `
      /* 系统生成 */
      declare @${sheet} table (${declarefields.join(',')},jskey nvarchar(50),BID nvarchar(50) )
      Declare @UserName nvarchar(50),@FullName nvarchar(50),@ErrorCode nvarchar(50),@retmsg nvarchar(4000),@tbid Nvarchar(512)
      Select  @ErrorCode='', @retmsg='', @UserName='${userName}', @FullName='${fullName}'
      ${_initCustomScript}
      `
    _sqlInsert = `Insert into @${sheet} (${fields},jskey,BID)`
    _sqlBottom = `
      /* 默认sql */
      delete tmp_excel_in where upid=@upid@
      delete tmp_excel_in where datediff(day,createdate,getdate())>15
      ${_uniquesql}
      ${_prevCustomScript}
      ${_insert}
      ${_backCustomScript}
      Delete @${sheet}
      aaa: select @ErrorCode as ErrorCode,@retmsg as retmsg`
    if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
      let fsql = `
      ${_sql}
      ${_sqlInsert}
      /* excel数据(前40条) */
      ${conLtext.join(' Union all ')}
      ${_sqlBottom}
      `
      fsql = fsql.replace(/\n\s{8}/ig, '\n')
      console.info(fsql)
    }
  } else { // s_sDataDictb_excelIn 云端密钥验证参数
    _sql = `
      /* 系统生成 */
      declare @${sheet} table (jskey nvarchar(50))
      Declare @UserName nvarchar(50),@FullName nvarchar(50),@ErrorCode nvarchar(50),@retmsg nvarchar(4000),@tbid Nvarchar(512)
      Select  @ErrorCode='', @retmsg='', @UserName='${userName}', @FullName='${fullName}'
      `
  }
  return {
    sql: _sql,
    lines: result.map((list, index) => {
      return {
        Ltext: window.btoa(window.encodeURIComponent(list.join(' Union all '))),
        Sort: (index + 1) * 10
      }
    }),
    insert: _sqlInsert,
    bottom: _sqlBottom,
    errors: errors.join('; ')
  }
}
/**
 * @description 使用系统函数时(sPC_TableData_InUpDe ),生成sql语句
 * @return {String} type   执行类型
 * @return {String} table  表名
 */
export function getSysDefaultSql (btn, setting, formdata, param, data, columns, tab, retmsg = false) {
  let primaryId = param.ID
  let BID = param.BID
  let verify = btn.verify || {}
  let datavars = {}                 // 声明的变量,表单及显示列
  let _actionType = null
  let _callbacksql = ''
  if (verify.default !== 'false') { // 判断是否使用默认sql
    _actionType = btn.sqlType
  }
  let _initCustomScript = '' // 初始化脚本
  let _prevCustomScript = '' // 默认sql前执行脚本
  let _backCustomScript = '' // 默认sql后执行脚本
  verify.scripts && verify.scripts.forEach(item => {
    if (item.status === 'false') return
    if (item.position === 'init') {
      _initCustomScript += `
      /* 自定义脚本 */
      ${item.sql}
      `
    } else if (item.position === 'front') {
      _prevCustomScript += `
      /* 自定义脚本 */
      ${item.sql}
      `
    } else {
      _backCustomScript += `
      /* 自定义脚本 */
      ${item.sql}
      `
    }
  })
  // 需要声明的变量集
  let _vars = ['tbid', 'errorcode', 'retmsg', 'billcode', 'bvoucher', 'fibvoucherdate', 'fiyear', 'username', 'fullname', 'modulardetailcode']
  // 主键字段
  let primaryKey = setting.primaryKey || 'id'
  // sql语句
  let _sql = ''
  let _initvars = [] // 已赋值字段集
  let _initFormfields = []
  let _initColfields = []
  let _declarefields = []
  // 获取字段键值对
  formdata && formdata.forEach(form => {
    let _key = form.key.toLowerCase()
    datavars[_key] = form.value
    if (!_initvars.includes(_key)) {
      _initvars.push(_key)
      if (form.type === 'number') {
        let val = form.value
        if (typeof(val) !== 'number') {
          val = parseFloat(val)
          if (isNaN(val)) {
            val = 0
          }
        }
        _initFormfields.push(`@${_key}=${val}`)
      } else {
        _initFormfields.push(`@${_key}='${form.value}'`)
      }
    }
    if (!_vars.includes(_key)) {
      _vars.push(_key)
      if (form.fieldlen && form.fieldlen > 2048) {
        form.fieldlen = 'max'
      }
      let _key = form.key.toLowerCase()
      datavars[_key] = form.value
      let _type = `nvarchar(${form.fieldlen})`
      if (form.type.match(/date/ig)) {
        _type = 'datetime'
      } else if (form.type === 'number') {
        _type = `decimal(18,${form.fieldlen})`
      }
      _declarefields.push(`@${_key} ${_type}`)
    }
  })
  if (data) {
    Object.keys(data).forEach(key => {
      data[key.toLowerCase()] = data[key]
    })
  }
  // 添加数据中字段,表单值优先(按钮不选行或多行拼接时跳过)
  if (data && btn.Ot !== 'notRequired' && btn.Ot !== 'requiredOnce') {
    datavars = {...data, ...datavars}
    const setField = (col) => {
      if (!col.field) return
      let _key = col.field.toLowerCase()
      if (!_initvars.includes(_key)) {
        _initvars.push(_key)
        let _val = datavars.hasOwnProperty(_key) ? datavars[_key] : ''
        if (form.type === 'number') {
          let val = form.value
          if (typeof(val) !== 'number') {
            val = parseFloat(val)
            if (isNaN(val)) {
              val = 0
            }
          }
          _initFormfields.push(`@${_key}=${val}`)
        } else {
          _initFormfields.push(`@${_key}='${form.value}'`)
        }
        _initvars.push(_key)
        _initColfields.push(`@${_key}='${_val}'`)
      }
      
      if (!_vars.includes(_key)) {
        _vars.push(_key)
        if (form.fieldlen && form.fieldlen > 2048) {
          form.fieldlen = 'max'
        if (col.datatype) {
          _declarefields.push(`@${_key} ${col.datatype}`)
        } else {
          if (col.fieldlength && col.fieldlength > 2048) {
            col.fieldlength = 'max'
          }
          let _type = `nvarchar(${col.fieldlength || 50})`
          if (col.type === 'number') {
            let _length = col.decimal ? col.decimal : 0
            _type = `decimal(18,${_length})`
          } else if (col.type === 'picture' || col.type === 'textarea') {
            _type = `nvarchar(${col.fieldlength || 512})`
          }
          _declarefields.push(`@${_key} ${_type}`)
        }
        let _type = `nvarchar(${form.fieldlen})`
        if (form.type.match(/date/ig)) {
          _type = 'datetime'
        } else if (form.type === 'number') {
          _type = `decimal(18,${form.fieldlen})`
        }
        _declarefields.push(`@${_key} ${_type}`)
      }
    })
    }
    if (data) {
      Object.keys(data).forEach(key => {
        data[key.toLowerCase()] = data[key]
    if (columns && columns.length > 0) {
      columns.forEach(col => {
        if (col.type === 'colspan' || col.type === 'old_colspan') {
          col.subcols.forEach(cell => {
            setField(cell)
          })
        } else {
          setField(col)
        }
      })
    }
  }
    // 添加数据中字段,表单值优先(按钮不选行或多行拼接时跳过)
    if (data && btn.Ot !== 'notRequired' && btn.Ot !== 'requiredOnce') {
      datavars = {...data, ...datavars}
  // 变量声明
  _declarefields = _declarefields.join(',')
  if (_declarefields) {
    _declarefields = ',' + _declarefields
  }
  _sql = `/* 系统生成 */
      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),@ModularDetailCode nvarchar(50)${_declarefields}
    `
      const setField = (col) => {
        if (!col.field) return
        let _key = col.field.toLowerCase()
  // 表单变量赋值
  if (_initFormfields.length > 0) {
    _sql += `
      /* 表单变量赋值 */
      select ${_initFormfields.join(',')}
      `
  }
  // 显示列变量赋值
  if (_initColfields.length > 0) {
    _sql += `
      /* 显示列变量赋值 */
      select ${_initColfields.join(',')}
      `
  }
        if (!_initvars.includes(_key)) {
          let _val = datavars.hasOwnProperty(_key) ? datavars[_key] : ''
  // 去除禁用的验证
  if (verify.contrasts) {
    verify.contrasts = verify.contrasts.filter(item => item.status !== 'false')
  }
  if (verify.uniques) {
    verify.uniques = verify.uniques.filter(item => item.status !== 'false')
  }
  if (verify.customverifys) {
    verify.customverifys = verify.customverifys.filter(item => item.status !== 'false')
  }
  if (verify.billcodes) {
    verify.billcodes = verify.billcodes.filter(item => item.status !== 'false')
  }
          _initvars.push(_key)
          _initColfields.push(`@${_key}='${_val}'`)
        }
        if (!_vars.includes(_key)) {
          _vars.push(_key)
  let userName = sessionStorage.getItem('User_Name') || ''
  let fullName = sessionStorage.getItem('Full_Name') || ''
          if (col.datatype) {
            _declarefields.push(`@${_key} ${col.datatype}`)
          } else {
            if (col.fieldlength && col.fieldlength > 2048) {
              col.fieldlength = 'max'
            }
            let _type = `nvarchar(${col.fieldlength || 50})`
            if (col.type === 'number') {
              let _length = col.decimal ? col.decimal : 0
              _type = `decimal(18,${_length})`
            } else if (col.type === 'picture' || col.type === 'textarea') {
              _type = `nvarchar(${col.fieldlength || 512})`
            }
            _declarefields.push(`@${_key} ${_type}`)
          }
        }
      }
  if (sessionStorage.getItem('isEditState') === 'true') {
    userName = sessionStorage.getItem('CloudUserName') || ''
    fullName = sessionStorage.getItem('CloudFullName') || ''
  }
      if (columns && columns.length > 0) {
        columns.forEach(col => {
          if (col.type === 'colspan' || col.type === 'old_colspan') {
            col.subcols.forEach(cell => {
              setField(cell)
            })
          } else {
            setField(col)
          }
        })
      }
    }
    // 变量声明
    _declarefields = _declarefields.join(',')
    if (_declarefields) {
      _declarefields = ',' + _declarefields
    }
    _sql = `/* 系统生成 */
        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),@ModularDetailCode nvarchar(50)${_declarefields}
  // 初始化凭证及用户信息字段
  _sql += `
      /* 凭证及用户信息初始化赋值 */
      select @BVoucher='',@FIBVoucherDate='',@FiYear='',@ErrorCode='',@retmsg='',@UserName='${userName}', @FullName='${fullName}', @BillCode='', @ModularDetailCode=''
      `
    // 表单变量赋值
    if (_initFormfields.length > 0) {
      _sql += `
        /* 表单变量赋值 */
        select ${_initFormfields.join(',')}
        `
  if (retmsg) {
    _callbacksql = _sql
  }
  if (_initCustomScript) {
    _sql += _initCustomScript
  }
  // 启用账期验证
  if (verify.accountdate === 'true') {
    let orgcode = `''`
    let date = `''`
    if (verify.accountfield && _initvars.includes(verify.accountfield.toLowerCase())) {
      orgcode = '@' + verify.accountfield
    }
    // 显示列变量赋值
    if (_initColfields.length > 0) {
      _sql += `
        /* 显示列变量赋值 */
        select ${_initColfields.join(',')}
        `
    if (verify.voucherdate && _initvars.includes(verify.voucherdate.toLowerCase())) {
      date = '@' + verify.voucherdate
    }
    // 去除禁用的验证
    if (verify.contrasts) {
      verify.contrasts = verify.contrasts.filter(item => item.status !== 'false')
    }
    if (verify.uniques) {
      verify.uniques = verify.uniques.filter(item => item.status !== 'false')
    }
    if (verify.customverifys) {
      verify.customverifys = verify.customverifys.filter(item => item.status !== 'false')
    }
    if (verify.billcodes) {
      verify.billcodes = verify.billcodes.filter(item => item.status !== 'false')
    }
    let userName = sessionStorage.getItem('User_Name') || ''
    let fullName = sessionStorage.getItem('Full_Name') || ''
    if (sessionStorage.getItem('isEditState') === 'true') {
      userName = sessionStorage.getItem('CloudUserName') || ''
      fullName = sessionStorage.getItem('CloudFullName') || ''
    }
    // 初始化凭证及用户信息字段
    _sql += `
        /* 凭证及用户信息初始化赋值 */
        select @BVoucher='',@FIBVoucherDate='',@FiYear='',@ErrorCode='',@retmsg='',@UserName='${userName}', @FullName='${fullName}', @BillCode='', @ModularDetailCode=''
        `
      /* 账期验证 */
      exec s_FIBVoucherDateCheck @OrgCode=${orgcode},@FIBVoucherDate=${date},@ErrorCode=@ErrorCode OUTPUT,@retmsg=@retmsg OUTPUT
      if @ErrorCode!=''
        GOTO aaa
      `
  }
    if (_initCustomScript) {
      _sql += _initCustomScript
  // 失效验证,添加数据时不用
  if (btn.sqlType !== 'insert' && verify.invalid === 'true' && setting.dataresource) {
    let datasource = setting.dataresource
    if (/\s/.test(datasource) && !/tb$/.test(datasource)) { // 拼接别名
      datasource = '(' + datasource + ') tb'
    }
    // 启用账期验证
    if (verify.accountdate === 'true') {
      let orgcode = `''`
      let date = `''`
      if (verify.accountfield && _initvars.includes(verify.accountfield.toLowerCase())) {
        orgcode = '@' + verify.accountfield
    if (setting.customScript) {
      _sql += `
      /* 数据源自定义脚本,请注意变量定义是否重复 */
      ${setting.customScript}
      `
    }
    if (btn.Ot === 'requiredOnce') {
      _sql += `
      /* 失效验证 */
      select @tbid='', @ErrorCode='',@retmsg=''
      select @tbid='X' from ${datasource} right join (select ID from  dbo.SplitComma(@ID@)) sp
      on tb.id =sp.id where tb.id is null
      If @tbid!=''
      Begin
        select @ErrorCode='E',@retmsg='数据已失效'
        goto aaa
      end
      `
    } else {
      _sql += `
      /* 失效验证 */
      select @tbid='', @ErrorCode='',@retmsg=''
      select @tbid=${primaryKey} from ${datasource} where ${primaryKey}=@ID@
      If @tbid=''
      Begin
        select @ErrorCode='E',@retmsg='数据已失效'
        goto aaa
      end
      `
    }
  }
  // 比较验证
  if (verify.contrasts && verify.contrasts.length > 0) {
    verify.contrasts.forEach(item => {
      _sql += `
      /* 比较验证 */
      If ${item.frontfield} ${item.operator} ${item.backfield}
      Begin
        select @ErrorCode='${item.errorCode}',@retmsg='${item.errmsg}'
          goto aaa
      end
      `
    })
  }
  // 自定义验证
  verify.customverifys && verify.customverifys.forEach(item => {
    _sql += `
      /* 自定义验证 */
      select @tbid='', @ErrorCode='',@retmsg=''
      select top 1 @tbid='X' from (${item.sql}) a
      If @tbid ${item.result === 'true' ? '!=' : '='}''
      Begin
        select @ErrorCode='${item.errorCode}',@retmsg='${item.errmsg}'
        goto aaa
      end
      `
  })
  // 单号生成,使用上级id(BID)或列表数据,声明变量(检验)
  let _billcodesSql  = ''
  if (formdata && verify.billcodes && verify.billcodes.length > 0) {
    let keys = formdata.map(item => item.key.toLowerCase()) // 表单字段
    verify.billcodes.forEach(item => {
      let _key = item.field.toLowerCase()
      let _linkKey = item.linkField ? item.linkField.toLowerCase() : ''
      if (!keys.includes(_key)) return // 表单中不含单号生成字段
      let _ModularDetailCode = ''
      let _lpline = ''
      if (item.TypeCharOne === 'Lp') {
        if (_linkKey === 'bid' && BID) { // 替换bid
          _lpline = `set @ModularDetailCode= 'Lp'+ right('${item.mark || btn.uuid}'+@BID@,48)`
        } else {
          _lpline = `set @ModularDetailCode= 'Lp'+ right('${item.mark || btn.uuid}'+@${_linkKey},48)`
        }
        _ModularDetailCode = '@ModularDetailCode'
      } else if (item.TypeCharOne === 'BN') {
        let _val = ''
        if (_linkKey === 'bid' && BID) { // 替换bid
          _val = BID
        } else if (data && data.hasOwnProperty(_linkKey)) {
          _val = data[_linkKey]
        }
        _ModularDetailCode = `'${item.TypeCharOne + _val}'`
      } else {
        _ModularDetailCode = `'${item.ModularDetailCode}'`
      }
      if (verify.voucherdate && _initvars.includes(verify.voucherdate.toLowerCase())) {
        date = '@' + verify.voucherdate
      let _declare = ''
      if (!_vars.includes(_key)) {
        _declare = `Declare @${_key} nvarchar(50)`
        _vars.push(_key)
      }
      _billcodesSql += `
      /* 单号生成 */
      ${_declare}
      select @BillCode='', @${_key}='', @ModularDetailCode=''
      ${_lpline}
      exec s_get_BillCode
        @ModularDetailCode=${_ModularDetailCode},
        @Type=${item.Type},
        @TypeCharOne='${item.TypeCharOne}',
        @TypeCharTwo ='${item.TypeCharTwo}',
        @BillCode =@BillCode output,
        @ErrorCode =@ErrorCode output,
        @retmsg=@retmsg output
      if @ErrorCode!=''
        goto aaa
      set @${_key}=@BillCode
      `
    })
    if (_actionType !== 'insertOrUpdate') {
      _sql += _billcodesSql
    }
  }
  // 唯一性验证,必须存在表单(表单存在时,主键均为单值),必须填写数据源,多行拼接时不可用
  if (formdata && verify.uniques && verify.uniques.length > 0 && btn.Ot !== 'requiredOnce') {
    verify.uniques.forEach(item => {
      let _fieldValue = []                     // 表单键值对field=value
      let _value = []                          // 表单值,用于错误提示
      let _labels = item.fieldlabel.split(',') // 表单提示文字
      let arr = [] // 验证主键
      item.field.split(',').forEach((_field, index) => {
        let _key = _field.toLowerCase()
        let _val = datavars[_key] !== undefined ? datavars[_key] : ''
        let _fval = `'${_val}'`
        if (_key === 'bid' && !datavars.bid) { // 表单中没有bid则使用系统bid变量
          _fval = '@BID@'
        }
        if (_key === 'bid' && tab && tab.foreignKey) {
          arr.push(tab.foreignKey.toLowerCase())
          _fieldValue.push(`${tab.foreignKey}=${_fval}`)
        } else {
          arr.push(_key)
          _fieldValue.push(`${_key}=${_fval}`)
        }
        _value.push(`${_labels[index] || ''}:${_val || ''}`)
      })
      let _verifyType = ''
      if (item.verifyType === 'logic') {
        _verifyType = ' and deleted=0'
      }
      if (!arr.includes(primaryKey.toLowerCase())) {
        _fieldValue.push(`${primaryKey} !='${primaryId}'`)
      }
      _sql += `
        /* 账期验证 */
        exec s_FIBVoucherDateCheck @OrgCode=${orgcode},@FIBVoucherDate=${date},@ErrorCode=@ErrorCode OUTPUT,@retmsg=@retmsg OUTPUT
        if @ErrorCode!=''
          GOTO aaa
        `
    }
    // 失效验证,添加数据时不用
    if (btn.sqlType !== 'insert' && verify.invalid === 'true' && setting.dataresource) {
      let datasource = setting.dataresource
      if (/\s/.test(datasource) && !/tb$/.test(datasource)) { // 拼接别名
      /* 唯一性验证 */
      select @tbid='', @ErrorCode='',@retmsg=''
      select @tbid='X' from ${btn.sql} where ${_fieldValue.join(' and ')}${_verifyType}
      If @tbid!=''
      Begin
        select @ErrorCode='${item.errorCode}',@retmsg='${_value.join(', ')} 已存在'
        goto aaa
      end
      `
    })
  } else if (verify.uniques && verify.uniques.length > 0 && btn.Ot === 'requiredOnce' && setting.dataresource) {
    let datasource = setting.dataresource
    if (/\s/.test(datasource)) { // 拼接别名
      if (!/tb$/.test(datasource)) {
        datasource = '(' + datasource + ') tb'
      }
      if (setting.customScript) {
        _sql += `
        /* 数据源自定义脚本,请注意变量定义是否重复 */
        ${setting.customScript}
        `
      }
      if (btn.Ot === 'requiredOnce') {
        _sql += `
        /* 失效验证 */
        select @tbid='', @ErrorCode='',@retmsg=''
        select @tbid='X' from ${datasource} right join (select ID from  dbo.SplitComma(@ID@)) sp
        on tb.id =sp.id where tb.id is null
        If @tbid!=''
        Begin
          select @ErrorCode='E',@retmsg='数据已失效'
          goto aaa
        end
        `
      } else {
        _sql += `
        /* 失效验证 */
        select @tbid='', @ErrorCode='',@retmsg=''
        select @tbid=${primaryKey} from ${datasource} where ${primaryKey}=@ID@
        If @tbid=''
        Begin
          select @ErrorCode='E',@retmsg='数据已失效'
          goto aaa
        end
        `
      }
    } else {
      datasource = datasource + ' tb'
    }
    // 比较验证
    if (verify.contrasts && verify.contrasts.length > 0) {
      verify.contrasts.forEach(item => {
        _sql += `
        /* 比较验证 */
        If ${item.frontfield} ${item.operator} ${item.backfield}
        Begin
          select @ErrorCode='${item.errorCode}',@retmsg='${item.errmsg}'
            goto aaa
        end
        `
      })
    }
    // 自定义验证
    verify.customverifys && verify.customverifys.forEach(item => {
    if (setting.customScript) {
      _sql += `
        /* 自定义验证 */
        select @tbid='', @ErrorCode='',@retmsg=''
        select top 1 @tbid='X' from (${item.sql}) a
        If @tbid ${item.result === 'true' ? '!=' : '='}''
        Begin
          select @ErrorCode='${item.errorCode}',@retmsg='${item.errmsg}'
          goto aaa
        end
        `
    })
    // 单号生成,使用上级id(BID)或列表数据,声明变量(检验)
    let _billcodesSql  = ''
    if (formdata && verify.billcodes && verify.billcodes.length > 0) {
      let keys = formdata.map(item => item.key.toLowerCase()) // 表单字段
      verify.billcodes.forEach(item => {
        let _key = item.field.toLowerCase()
        let _linkKey = item.linkField ? item.linkField.toLowerCase() : ''
        if (!keys.includes(_key)) return // 表单中不含单号生成字段
        let _ModularDetailCode = ''
        let _lpline = ''
        if (item.TypeCharOne === 'Lp') {
          if (_linkKey === 'bid' && BID) { // 替换bid
            _lpline = `set @ModularDetailCode= 'Lp'+ right('${item.mark || btn.uuid}'+@BID@,48)`
          } else {
            _lpline = `set @ModularDetailCode= 'Lp'+ right('${item.mark || btn.uuid}'+@${_linkKey},48)`
          }
          _ModularDetailCode = '@ModularDetailCode'
        } else if (item.TypeCharOne === 'BN') {
          let _val = ''
          if (_linkKey === 'bid' && BID) { // 替换bid
            _val = BID
          } else if (data && data.hasOwnProperty(_linkKey)) {
            _val = data[_linkKey]
          }
          _ModularDetailCode = `'${item.TypeCharOne + _val}'`
        } else {
          _ModularDetailCode = `'${item.ModularDetailCode}'`
        }
        let _declare = ''
        if (!_vars.includes(_key)) {
          _declare = `Declare @${_key} nvarchar(50)`
          _vars.push(_key)
        }
        _billcodesSql += `
        /* 单号生成 */
        ${_declare}
        select @BillCode='', @${_key}='', @ModularDetailCode=''
        ${_lpline}
        exec s_get_BillCode
          @ModularDetailCode=${_ModularDetailCode},
          @Type=${item.Type},
          @TypeCharOne='${item.TypeCharOne}',
          @TypeCharTwo ='${item.TypeCharTwo}',
          @BillCode =@BillCode output,
          @ErrorCode =@ErrorCode output,
          @retmsg=@retmsg output
        if @ErrorCode!=''
          goto aaa
        set @${_key}=@BillCode
        `
      })
      if (_actionType !== 'insertOrUpdate') {
        _sql += _billcodesSql
      }
    }
    // 唯一性验证,必须存在表单(表单存在时,主键均为单值),必须填写数据源,多行拼接时不可用
    if (formdata && verify.uniques && verify.uniques.length > 0 && btn.Ot !== 'requiredOnce') {
      verify.uniques.forEach(item => {
        let _fieldValue = []                     // 表单键值对field=value
        let _value = []                          // 表单值,用于错误提示
        let _labels = item.fieldlabel.split(',') // 表单提示文字
        let arr = [] // 验证主键
        item.field.split(',').forEach((_field, index) => {
          let _key = _field.toLowerCase()
          let _val = datavars[_key] !== undefined ? datavars[_key] : ''
          let _fval = `'${_val}'`
          if (_key === 'bid' && !datavars.bid) { // 表单中没有bid则使用系统bid变量
            _fval = '@BID@'
          }
          if (_key === 'bid' && tab && tab.foreignKey) {
            arr.push(tab.foreignKey.toLowerCase())
            _fieldValue.push(`${tab.foreignKey}=${_fval}`)
          } else {
            arr.push(_key)
            _fieldValue.push(`${_key}=${_fval}`)
          }
          _value.push(`${_labels[index] || ''}:${_val || ''}`)
        })
        let _verifyType = ''
        if (item.verifyType === 'logic') {
          _verifyType = ' and deleted=0'
        }
        if (!arr.includes(primaryKey.toLowerCase())) {
          _fieldValue.push(`${primaryKey} !='${primaryId}'`)
        }
        _sql += `
        /* 唯一性验证 */
        select @tbid='', @ErrorCode='',@retmsg=''
        select @tbid='X' from ${btn.sql} where ${_fieldValue.join(' and ')}${_verifyType}
        If @tbid!=''
        Begin
          select @ErrorCode='${item.errorCode}',@retmsg='${_value.join(', ')} 已存在'
          goto aaa
        end
        `
      })
    } else if (verify.uniques && verify.uniques.length > 0 && btn.Ot === 'requiredOnce' && setting.dataresource) {
      let datasource = setting.dataresource
      if (/\s/.test(datasource)) { // 拼接别名
        if (!/tb$/.test(datasource)) {
          datasource = '(' + datasource + ') tb'
        }
      } else {
        datasource = datasource + ' tb'
      }
      if (setting.customScript) {
        _sql += `
        /* 数据源自定义脚本,请注意变量定义是否重复 */
        ${setting.customScript}
        `
      }
      verify.uniques.forEach(item => {
        _sql += `
        /* 同类数据验证 */
        Set @tbid=''
        Select top 1 @tbid='X' from (select distinct ${item.field},1 as n from ${datasource} inner join (select ID from  dbo.SplitComma(@ID@)) sp on tb.${primaryKey}=sp.ID ) a having sum(n)>1
        If @tbid!=''
        Begin
          Set @ErrorCode='E' Set @retmsg='${item.fieldlabel} 值不唯一'
          goto aaa
        end
        `
      })
    }
    let hasvoucher = false
    // 凭证-显示列中选取,必须选行
    if (verify.voucher && verify.voucher.enabled && data) {
      let _voucher = verify.voucher
      hasvoucher = true
      _sql += `
        /* 创建凭证 */
        exec s_BVoucher_Create
          @Bill ='${data[_voucher.linkField.toLowerCase()]}',
          @BVoucherType ='${_voucher.BVoucherType}',
          @VoucherTypeOne ='${_voucher.VoucherTypeOne}',
          @VoucherTypeTwo ='${_voucher.VoucherTypeTwo}',
          @Type =${_voucher.Type},
          @UserID=@UserID@,
          @Username=@Username,
          @FullName=@FullName,
          @BVoucher =@BVoucher OUTPUT ,
          @FIBVoucherDate =@FIBVoucherDate OUTPUT ,
          @FiYear =@FiYear OUTPUT ,
          @ErrorCode =@ErrorCode OUTPUT,
          @retmsg=@retmsg OUTPUT
        if @ErrorCode!=''
          GOTO aaa
        `
    }
    let _insertsql = ''
    if (_actionType === 'insert' || _actionType === 'insertOrUpdate') { // 添加语句
      let keys = []
      let values = []
      formdata.forEach(item => {
        if (item.writein === false) return
        let _key = item.key.toLowerCase()
        keys.push(_key)
        values.push('@' + _key)
      })
      if (!keys.includes(primaryKey.toLowerCase())) {
        keys.push(primaryKey.toLowerCase())
        values.push('\'' + primaryId + '\'')
      }
      if (!keys.includes('createuserid')) {
        keys.push('createuserid')
        values.push('@userid@')
      }
      if (!keys.includes('createuser')) {
        keys.push('createuser')
        values.push('@username')
      }
      if (!keys.includes('createstaff')) {
        keys.push('createstaff')
        values.push('@fullname')
      }
      if (!keys.includes('bid')) {
        if (tab && tab.foreignKey && !keys.includes(tab.foreignKey.toLowerCase())) {
          keys.push(tab.foreignKey.toLowerCase())
        } else {
          keys.push('bid')
        }
        values.push('@BID@')
      } else if (tab && tab.foreignKey && !keys.includes(tab.foreignKey.toLowerCase())) {
        keys.push(tab.foreignKey.toLowerCase())
        values.push('@BID@')
      }
      keys = keys.join(',')
      values = values.join(',')
      _insertsql = `insert into ${btn.sql} (${keys}) select ${values};`
    }
    let _updatesql = ''
    if (_actionType === 'update' || _actionType === 'audit' || _actionType === 'insertOrUpdate') { // 修改语句
      let _form = []
      let _arr = []
      formdata.forEach(item => {
        if (item.writein === false) return
        let _key = item.key.toLowerCase()
        _arr.push(_key)
        _form.push(_key + '=@' + _key)
      })
      if (_actionType === 'audit') {
        if (!_arr.includes('submitdate')) {
          _form.push('submitdate=getdate()')
        }
        if (!_arr.includes('submituserid')) {
          _form.push('submituserid=@userid@')
        }
      } else {
        if (!_arr.includes('modifydate')) {
          _form.push('modifydate=getdate()')
        }
        if (!_arr.includes('modifyuserid')) {
          _form.push('modifyuserid=@userid@')
        }
      }
      if (hasvoucher) {
        if (!_arr.includes('bvoucher')) {
          _form.push('BVoucher=@BVoucher')
        }
        if (!_arr.includes('fibvoucherdate')) {
          _form.push('FIBVoucherDate=@FIBVoucherDate')
        }
        if (!_arr.includes('fiyear')) {
          _form.push('FiYear=@FiYear')
        }
      }
      _form = _form.join(',')
      let _ID = '=@ID@'
      if (btn.Ot === 'requiredOnce') {
        _ID = ' in (select ID from  dbo.SplitComma(@ID@))'
      }
      _updatesql = `update ${btn.sql} set ${_form} where ${primaryKey}${_ID};`
    }
    if (_prevCustomScript) {
      _sql += _prevCustomScript
    }
    // 添加、修改、逻辑删除、物理删除
    if (_actionType === 'insert') {
      _sql += `
        /* 默认sql */
        ${_insertsql}`
    } else if (_actionType === 'update' || _actionType === 'audit') {
      _sql += `
        /* 默认sql */
        ${_updatesql}`
    } else if (_actionType === 'LogicDelete') { // 逻辑删除
      let _ID = '=@ID@'
      if (btn.Ot === 'requiredOnce') {
        _ID = ' in (select ID from  dbo.SplitComma(@ID@))'
      }
      _sql += `
        /* 默认sql */
        update ${btn.sql} set deleted=1,modifydate=getdate(),modifyuserid=@userid@ where ${primaryKey}${_ID};`
    } else if (_actionType === 'delete') {      // 物理删除
      let _msg = ''
      if (data && columns && columns.length > 0) {
        let _index = 0
        columns.forEach(col => {
          if (col.Hide !== 'true' && col.type !== 'colspan' && col.type !== 'old_colspan' && _index < 4) {
            _msg += col.label + '=' + data[col.field] + ','
            _index++
          }
        })
      }
      let _ID = '=@ID@'
      if (btn.Ot === 'requiredOnce') {
        _ID = ' in (select ID from  dbo.SplitComma(@ID@))'
      }
      _sql += `
        /* 默认sql */
        insert into snote (remark,createuserid,CreateUser,CreateStaff) select left('删除表:${btn.sql} 数据: ${_msg}${primaryKey}='+@ID@,200),@userid@,@username,@fullname
        delete ${btn.sql} where ${primaryKey}${_ID};`
    } else if (_actionType === 'insertOrUpdate') {
      _sql += `
        /* 默认sql */
        select @tbid=''
        select @tbid='X' from ${btn.sql} where ${primaryKey}=@ID@
        if @tbid=''
          begin
          ${_billcodesSql}
          ${_insertsql}
          end
        else
          begin
          ${_updatesql}
          end
      /* 数据源自定义脚本,请注意变量定义是否重复 */
      ${setting.customScript}
      `
    }
    if (_backCustomScript) {
      _sql += _backCustomScript
    verify.uniques.forEach(item => {
      _sql += `
      /* 同类数据验证 */
      Set @tbid=''
      Select top 1 @tbid='X' from (select distinct ${item.field},1 as n from ${datasource} inner join (select ID from  dbo.SplitComma(@ID@)) sp on tb.${primaryKey}=sp.ID ) a having sum(n)>1
      If @tbid!=''
      Begin
        Set @ErrorCode='E' Set @retmsg='${item.fieldlabel} 值不唯一'
        goto aaa
      end
      `
    })
  }
  let hasvoucher = false
  // 凭证-显示列中选取,必须选行
  if (verify.voucher && verify.voucher.enabled && data) {
    let _voucher = verify.voucher
    hasvoucher = true
    _sql += `
      /* 创建凭证 */
      exec s_BVoucher_Create
        @Bill ='${data[_voucher.linkField.toLowerCase()]}',
        @BVoucherType ='${_voucher.BVoucherType}',
        @VoucherTypeOne ='${_voucher.VoucherTypeOne}',
        @VoucherTypeTwo ='${_voucher.VoucherTypeTwo}',
        @Type =${_voucher.Type},
        @UserID=@UserID@,
        @Username=@Username,
        @FullName=@FullName,
        @BVoucher =@BVoucher OUTPUT ,
        @FIBVoucherDate =@FIBVoucherDate OUTPUT ,
        @FiYear =@FiYear OUTPUT ,
        @ErrorCode =@ErrorCode OUTPUT,
        @retmsg=@retmsg OUTPUT
      if @ErrorCode!=''
        GOTO aaa
      `
  }
  let _insertsql = ''
  if (_actionType === 'insert' || _actionType === 'insertOrUpdate') { // 添加语句
    let keys = []
    let values = []
    formdata.forEach(item => {
      if (item.writein === false) return
      let _key = item.key.toLowerCase()
      keys.push(_key)
      values.push('@' + _key)
    })
    if (!keys.includes(primaryKey.toLowerCase())) {
      keys.push(primaryKey.toLowerCase())
      values.push('\'' + primaryId + '\'')
    }
    if (!keys.includes('createuserid')) {
      keys.push('createuserid')
      values.push('@userid@')
    }
    if (!keys.includes('createuser')) {
      keys.push('createuser')
      values.push('@username')
    }
    if (!keys.includes('createstaff')) {
      keys.push('createstaff')
      values.push('@fullname')
    }
    if (!keys.includes('bid')) {
      if (tab && tab.foreignKey && !keys.includes(tab.foreignKey.toLowerCase())) {
        keys.push(tab.foreignKey.toLowerCase())
      } else {
        keys.push('bid')
      }
      values.push('@BID@')
    } else if (tab && tab.foreignKey && !keys.includes(tab.foreignKey.toLowerCase())) {
      keys.push(tab.foreignKey.toLowerCase())
      values.push('@BID@')
    }
    keys = keys.join(',')
    values = values.join(',')
    _insertsql = `insert into ${btn.sql} (${keys}) select ${values};`
  }
  let _updatesql = ''
  if (_actionType === 'update' || _actionType === 'audit' || _actionType === 'insertOrUpdate') { // 修改语句
    let _form = []
    let _arr = []
    formdata.forEach(item => {
      if (item.writein === false) return
      let _key = item.key.toLowerCase()
      _arr.push(_key)
      _form.push(_key + '=@' + _key)
    })
    if (_actionType === 'audit') {
      if (!_arr.includes('submitdate')) {
        _form.push('submitdate=getdate()')
      }
      if (!_arr.includes('submituserid')) {
        _form.push('submituserid=@userid@')
      }
    } else {
      if (!_arr.includes('modifydate')) {
        _form.push('modifydate=getdate()')
      }
      if (!_arr.includes('modifyuserid')) {
        _form.push('modifyuserid=@userid@')
      }
    }
    if (hasvoucher) {
      if (!_arr.includes('bvoucher')) {
        _form.push('BVoucher=@BVoucher')
      }
      if (!_arr.includes('fibvoucherdate')) {
        _form.push('FIBVoucherDate=@FIBVoucherDate')
      }
      if (!_arr.includes('fiyear')) {
        _form.push('FiYear=@FiYear')
      }
    }
    _form = _form.join(',')
    let _ID = '=@ID@'
    if (btn.Ot === 'requiredOnce') {
      _ID = ' in (select ID from  dbo.SplitComma(@ID@))'
    }
    _updatesql = `update ${btn.sql} set ${_form} where ${primaryKey}${_ID};`
  }
  if (_prevCustomScript) {
    _sql += _prevCustomScript
  }
  // 添加、修改、逻辑删除、物理删除
  if (_actionType === 'insert') {
    _sql += `
      /* 默认sql */
      ${_insertsql}`
  } else if (_actionType === 'update' || _actionType === 'audit') {
    _sql += `
      /* 默认sql */
      ${_updatesql}`
  } else if (_actionType === 'LogicDelete') { // 逻辑删除
    let _ID = '=@ID@'
    if (btn.Ot === 'requiredOnce') {
      _ID = ' in (select ID from  dbo.SplitComma(@ID@))'
    }
    _sql += `
        aaa: select @ErrorCode as ErrorCode,@retmsg as retmsg`
    if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
      _sql = _sql.replace(/\n\s{8}/ig, '\n')
      console.info(_sql)
      /* 默认sql */
      update ${btn.sql} set deleted=1,modifydate=getdate(),modifyuserid=@userid@ where ${primaryKey}${_ID};`
  } else if (_actionType === 'delete') {      // 物理删除
    let _msg = ''
    if (data && columns && columns.length > 0) {
      let _index = 0
      columns.forEach(col => {
        if (col.Hide !== 'true' && col.type !== 'colspan' && col.type !== 'old_colspan' && _index < 4) {
          _msg += col.label + '=' + data[col.field] + ','
          _index++
        }
      })
    }
    let _ID = '=@ID@'
    if (btn.Ot === 'requiredOnce') {
      _ID = ' in (select ID from  dbo.SplitComma(@ID@))'
    }
    _sql += `
      /* 默认sql */
      insert into snote (remark,createuserid,CreateUser,CreateStaff) select left('删除表:${btn.sql} 数据: ${_msg}${primaryKey}='+@ID@,200),@userid@,@username,@fullname
      delete ${btn.sql} where ${primaryKey}${_ID};`
  } else if (_actionType === 'insertOrUpdate') {
    _sql += `
      /* 默认sql */
      select @tbid=''
      select @tbid='X' from ${btn.sql} where ${primaryKey}=@ID@
      if @tbid=''
        begin
        ${_billcodesSql}
        ${_insertsql}
        end
      else
        begin
        ${_updatesql}
        end
    `
  }
  if (_backCustomScript) {
    _sql += _backCustomScript
  }
  if (retmsg) {
    _sql += `
      aaa: if @ErrorCode!=''
      insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@`
  } else {
    _sql += `
      aaa: select @ErrorCode as ErrorCode,@retmsg as retmsg`
  }
  if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
    // _sql = _sql.replace(/\n\s{8}/ig, '\n')
    console.info(_sql)
  }
  if (retmsg) {
    return {
      sql: _sql,
      callbacksql: _callbacksql
    }
  } else {
    return _sql
  }
}
src/views/billprint/index.jsx
@@ -21,6 +21,8 @@
const PropCard = asyncComponent(() => import('@/tabviews/custom/components/card/prop-card'))
const TableCard = asyncComponent(() => import('@/tabviews/custom/components/card/table-card'))
const NormalTable = asyncComponent(() => import('@/tabviews/custom/components/table/normal-table'))
const BraftEditor = asyncComponent(() => import('@/tabviews/custom/components/editor/braft-editor'))
const SandBox = asyncComponent(() => import('@/tabviews/custom/components/code/sand-box'))
class BillPrint extends Component {
  state = {
@@ -119,26 +121,26 @@
          A4: {
            vertical: 980,
            horizontal: 1200,
            verticaldefault: 1.45789,
            verticalwithout: 1.41428,
            horizontaldefault: 0.683,
            horizontalwithout: 0.705,
            verticaldefault: 1.455,
            verticalwithout: 1.411,
            horizontaldefault: 0.679,
            horizontalwithout: 0.701,
          },
          A3: {
            vertical: 1200,
            horizontal: 1600,
            verticaldefault: 1.44404,
            verticalwithout: 1.41414,
            horizontaldefault: 0.6923,
            horizontalwithout: 0.707,
            verticaldefault: 1.441,
            verticalwithout: 1.410,
            horizontaldefault: 0.688,
            horizontalwithout: 0.703,
          },
          A5: {
            vertical: 700,
            horizontal: 1000,
            verticaldefault: 1.482,
            verticalwithout: 1.417,
            horizontaldefault: 0.6736,
            horizontalwithout: 0.7047,
            verticaldefault: 1.478,
            verticalwithout: 1.413,
            horizontaldefault: 0.669,
            horizontalwithout: 0.700,
          }
        }
@@ -153,9 +155,13 @@
          if (component.action) component.action = []
          if (component.search) component.search = []
          component.data = [] // 初始化数据为空
          if (['propcard', 'brafteditor', 'sandbox'].includes(component.subtype) && component.wrap.datatype === 'static') {
            component.format = ''
          }
    
          if (!component.setting) return component // 不使用系统函数时
          if (!component.format || (component.subtype === 'propcard' && component.wrap.datatype === 'static')) return component // 没有动态数据  数据格式 array 或 object
          if (!component.format) return component  // 没有动态数据  数据格式 array 或 object
          if (component.setting.interType !== 'system') { // 不使用系统函数时
            component.setting.sync = 'false'
            return component
@@ -350,7 +356,7 @@
      let _results = results.filter(Boolean)
      let comps = components.map(item => {
        if (item.subtype === 'propcard' && item.wrap.datatype === 'static') return item
        if (!item.format) return item
        _results.forEach(res => {
          if (res.componentId === item.uuid && res.data) {
@@ -566,6 +572,18 @@
            <NormalTable config={item} initdata={item.data} mainSearch={[]} menuType="" />
          </Col>
        )
      } else if (item.type === 'editor') {
        return (
          <Col span={item.width} key={item.uuid}>
            <BraftEditor config={item} initdata={item.data} mainSearch={[]} menuType="" />
          </Col>
        )
      } else if (item.type === 'code') {
        return (
          <Col span={item.width} key={item.uuid}>
            <SandBox config={item} initdata={item.data} mainSearch={[]} menuType="" />
          </Col>
        )
      } else {
        return null
      }
src/views/billprint/index.scss
@@ -31,6 +31,19 @@
  }
}
.print-page {
  table {
    border-radius: 0!important;
    .ant-table-column-sorter {
      display: none!important;
    }
  }
  .ant-table-thead > tr > th {
    background: transparent!important;
    border-radius: 0!important;
  }
}
.normal-custom-table .main-pickup {
  display: none!important;
}
src/views/login/index.jsx
@@ -43,6 +43,21 @@
    syncing: false
  }
  UNSAFE_componentWillMount() {
    localStorage.removeItem('UserID')
    localStorage.removeItem('LoginUID')
    localStorage.removeItem('User_Name')
    localStorage.removeItem('Full_Name')
    localStorage.removeItem('avatar')
    localStorage.removeItem('dataM')
    localStorage.removeItem('localDataM')
    localStorage.removeItem('debug')
    localStorage.removeItem('role_id')
    localStorage.removeItem('localRole_id')
    sessionStorage.clear()
  }
  changelang (item) {
    // 切换语言
    this.setState({
@@ -288,10 +303,10 @@
    Api.getTouristMsg().then(result => {
      if (result.status) {
        if (result.UserID) {
        if (result.UserID && !sessionStorage.getItem('UserID')) {
          sessionStorage.setItem('UserID', result.UserID)
        }
        if (result.LoginUID) {
        if (result.LoginUID && !sessionStorage.getItem('LoginUID')) {
          sessionStorage.setItem('LoginUID', result.LoginUID)
        }
src/views/main/index.jsx
@@ -2,9 +2,11 @@
import { ConfigProvider } from 'antd'
import enUS from 'antd/es/locale/en_US'
import zhCN from 'antd/es/locale/zh_CN'
import Header from '@/components/header'
import Sidemenu from '@/components/sidemenu'
import Tabview from '@/components/tabview'
import QueryLog from '@/components/querylog'
import './index.scss'
@@ -13,11 +15,12 @@
class Main extends Component {
  render () {
    return (
      <div className="flex-container main-view">
      <div className="mk-main-view">
        <ConfigProvider locale={_locale}>
          <Header key="header"/>
          <Sidemenu key="sidemenu"/>
          <Tabview key="tabview"/>
          <QueryLog />
        </ConfigProvider>
      </div>
    )
src/views/main/index.scss
@@ -1,4 +1,4 @@
.flex-container {
.mk-main-view {
  display: flex;
  flex: auto;
  min-height: 100%;
src/views/menudesign/index.jsx
@@ -8,6 +8,7 @@
import html2canvas from 'html2canvas'
import Api from '@/api'
import options from '@/store/options.js'
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/mob.js'
import enUS from '@/locales/en-US/mob.js'
@@ -26,18 +27,20 @@
const MenuForm = asyncComponent(() => import('./menuform'))
const HomeForm = asyncComponent(() => import('./homeform'))
const PrintMenuForm = asyncComponent(() => import('./printmenuform'))
const Header = asyncComponent(() => import('@/menu/header'))
const SourceWrap = asyncComponent(() => import('@/menu/modelsource'))
const MenuShell = asyncComponent(() => import('@/menu/menushell'))
const PrintMenuForm = asyncComponent(() => import('./printmenuform'))
const SourceWrap = asyncComponent(() => import('@/menu/modulesource'))
const PopviewController = asyncComponent(() => import('@/menu/popview'))
const BgController = asyncComponent(() => import('@/menu/bgcontroller'))
const PasteController = asyncComponent(() => import('@/menu/pastecontroller'))
const PaddingController = asyncComponent(() => import('@/menu/padcontroller'))
const StyleController = asyncComponent(() => import('@/menu/stylecontroller'))
const SysInterface = asyncComponent(() => import('@/menu/sysinterface'))
const PictureController = asyncComponent(() => import('@/menu/picturecontroller'))
const ModalController = asyncComponent(() => import('@/menu/modalconfig/controller'))
const StyleCombController = asyncComponent(() => import('@/menu/stylecombcontroller'))
const StyleCombControlButton = asyncComponent(() => import('@/menu/stylecombcontrolbutton'))
const ModalController = asyncComponent(() => import('@/menu/modalconfig/controller'))
const PopviewController = asyncComponent(() => import('@/menu/popview'))
const TableComponent = asyncComponent(() => import('@/templates/sharecomponent/tablecomponent'))
sessionStorage.setItem('isEditState', 'true')
@@ -100,7 +103,10 @@
    MKEmitter.addListener('changePopview', this.initPopview)
    MKEmitter.addListener('submitComponentStyle', this.updateComponentStyle)
    MKEmitter.addListener('updateCustomComponent', this.updateCustomComponent)
    this.updateCustomComponent()
    setTimeout(() => {
      this.updateCustomComponent()
      this.getAppPictures()
    }, 1000)
  }
  /**
@@ -116,6 +122,32 @@
    MKEmitter.removeListener('changePopview', this.initPopview)
    MKEmitter.removeListener('submitComponentStyle', this.updateComponentStyle)
    MKEmitter.removeListener('updateCustomComponent', this.updateCustomComponent)
  }
  getAppPictures = () => {
    Api.getSystemConfig({
      func: 's_url_db_adduptdel',
      PageIndex: 0,  // 0 代表全部
      PageSize: 0,   // 0 代表全部
      typecharone: 'image',
      type: 'search'
    }).then(res => {
      if (res.status) {
        sessionStorage.setItem('app_pictures', JSON.stringify(res.data || []))
      }
      Api.getSystemConfig({
        func: 's_url_db_adduptdel',
        PageIndex: 0,  // 0 代表全部
        PageSize: 0,   // 0 代表全部
        typecharone: 'video',
        type: 'search'
      }).then(res => {
        if (res.status) {
          sessionStorage.setItem('app_videos', JSON.stringify(res.data || []))
        }
      })
    })
  }
  updateCustomComponent = () => {
@@ -151,6 +183,7 @@
        })
      }
      this.setState({customComponents: coms})
      this.getRoleFields()
    })
  }
@@ -315,7 +348,6 @@
        })
        this.props.modifyCustomMenu(config)
        this.getRoleFields()
      } else {
        notification.warning({
          top: 92,
@@ -341,37 +373,40 @@
          traversal(item.components)
        } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.action && item.action.forEach(btn => {
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') 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
              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.action && item.action.forEach(btn => {
            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.action && item.action.forEach(btn => {
            if (btn.origin) return
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
          item.cols && item.cols.forEach(col => {
            if (col.type !== 'action') return
            col.elements.forEach(btn => {
              this.checkBtn(btn)
              buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
              _sort++
            })
@@ -383,6 +418,26 @@
    traversal(config.components)
    return buttons
  }
  checkBtn = (btn) => {
    if (['prompt', 'exec', 'pop'].includes(btn.OpenType) && btn.Ot === 'required' && btn.verify && btn.verify.scripts && btn.verify.scripts.length > 0) {
      let hascheck = false
      btn.verify.scripts.forEach(item => {
        if (item.status === 'false') return
        if (/\$check@|@check\$/ig.test(item.sql)) {
          hascheck = true
        }
      })
      if (hascheck) {
        notification.warning({
          top: 92,
          message: `可选择多行的按钮《${btn.label}》中 $check@ 或 @check$ 将不会生效!`,
          duration: 5
        })
      }
    }
  }
  filterConfig = (components) => {
@@ -429,72 +484,83 @@
      return
    }
    config.components = this.filterConfig(config.components)
    if (config.enabled && this.verifyConfig()) {
      config.enabled = false
    }
    let _config = fromJS(config).toJS()
    delete _config.tableFields
    let param = {
      func: 'sPC_TrdMenu_AddUpt',
      FstID: _config.fstMenuId || '',
      SndID: _config.parentId,
      ParentID: _config.parentId,
      MenuID: _config.uuid,
      MenuNo: _config.MenuNo || '',
      EasyCode: _config.easyCode || '',
      Template: 'CustomPage',
      MenuName: _config.MenuName || '',
      PageParam: JSON.stringify({Template: 'CustomPage', OpenType: 'newtab'}),
      LongParam: window.btoa(window.encodeURIComponent(JSON.stringify(_config))),
      LText: '',
      LTexttb: ''
    }
    param.LText = Utils.formatOptions(param.LText)
    param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    param.secretkey = Utils.encrypt(param.LText, param.timestamp)
    if (openEdition) { // 版本管理
      param.open_edition = openEdition
    }
    let btnParam = {             // 添加菜单按钮
      func: 'sPC_Button_AddUpt',
      Type: 40,                  // 添加菜单下的按钮type为40,按钮下的按钮type为60
      ParentID: _config.uuid,
      MenuNo: _config.MenuNo,
      Template: 'CustomPage',
      PageParam: '',
      LongParam: '',
      LText: []
    }
    let btnIds = '' // 用于复制按钮的过滤
    if (MenuType !== 'billPrint') {
      btnParam.LText = this.getMenuMessage()
      btnParam.LText = btnParam.LText.join(' union all ')
      btnIds = btnParam.LText
      btnParam.LText = Utils.formatOptions(btnParam.LText)
      btnParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
      btnParam.secretkey = Utils.encrypt(btnParam.LText, btnParam.timestamp)
    } else {
      btnParam.LText = ''
    }
    this.setState({
      menuloading: true
    }, () => {
    })
    setTimeout(() => {
      config.components = this.filterConfig(config.components)
      if (config.enabled && this.verifyConfig()) {
        config.enabled = false
      }
      let _config = fromJS(config).toJS()
      delete _config.tableFields
      let param = {
        func: 'sPC_TrdMenu_AddUpt',
        FstID: _config.fstMenuId || '',
        SndID: _config.parentId,
        ParentID: _config.parentId,
        MenuID: _config.uuid,
        MenuNo: _config.MenuNo || '',
        EasyCode: _config.easyCode || '',
        Template: 'CustomPage',
        MenuName: _config.MenuName || '',
        PageParam: JSON.stringify({Template: 'CustomPage', OpenType: 'newtab'}),
        LongParam: window.btoa(window.encodeURIComponent(JSON.stringify(_config))),
        LText: '',
        LTexttb: ''
      }
      param.LText = Utils.formatOptions(param.LText)
      param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
      param.secretkey = Utils.encrypt(param.LText, param.timestamp)
      if (openEdition) { // 版本管理
        param.open_edition = openEdition
      }
      let btnParam = {             // 添加菜单按钮
        func: 'sPC_Button_AddUpt',
        Type: 40,                  // 添加菜单下的按钮type为40,按钮下的按钮type为60
        ParentID: _config.uuid,
        MenuNo: _config.MenuNo,
        Template: 'CustomPage',
        PageParam: '',
        LongParam: '',
        LText: []
      }
      let btnIds = '' // 用于复制按钮的过滤
      if (MenuType !== 'billPrint') {
        btnParam.LText = this.getMenuMessage()
        btnParam.LText = btnParam.LText.join(' union all ')
        btnIds = btnParam.LText
        btnParam.LText = Utils.formatOptions(btnParam.LText)
        btnParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        btnParam.secretkey = Utils.encrypt(btnParam.LText, btnParam.timestamp)
      } else {
        btnParam.LText = ''
      }
      new Promise(resolve => {
        if (MenuType === 'billPrint') { // 打印生成页面效果图
          html2canvas(document.getElementById('menu-shell-inner')).then(canvas => {
            let img = canvas.toDataURL('image/png') // 获取生成的图片
            Api.fileuploadbase64(img, 'cloud').then(result => {
            let param = {
              Base64Img: canvas.toDataURL('image/png') // 获取生成的图片
            }
            if (options.cloudServiceApi) {
              param.rduri = options.cloudServiceApi
              param.userid = sessionStorage.getItem('CloudUserID') || ''
              param.LoginUID = sessionStorage.getItem('CloudLoginUID') || ''
            }
            Api.fileuploadbase64(param).then(result => {
              if (result.status) {
                Api.getSystemConfig({
                  func: 's_PrintTemplateMSub',
@@ -717,7 +783,7 @@
          })
        }
      })
    })
    }, 300)
  }
  getRoleFields = () => {
@@ -754,7 +820,7 @@
  onEnabledChange = () => {
    const { config } = this.state
    if (!config.enabled && this.verifyConfig(true)) {
    if (!config || (!config.enabled && this.verifyConfig(true))) {
      return
    }
@@ -769,7 +835,7 @@
    config.components.forEach(item => {
      if (error) return
      if (item.subtype === 'propcard' && item.wrap.datatype === 'static') return
      if (['propcard', 'brafteditor', 'sandbox'].includes(item.subtype) && item.wrap.datatype === 'static') return
      if (item.setting) {
        if (item.setting.interType === 'system' && item.setting.execute !== 'false' && !item.setting.dataresource) {
@@ -837,7 +903,7 @@
    return (
      <ConfigProvider locale={_locale}>
        <div className="pc-menu-view" id="view">
        <div className={'pc-menu-view ' + (MenuType || '')} id="mk-menu-design-view">
          <Header />
          {!popBtn && !visible ? <DndProvider backend={HTML5Backend}>
            <div className="menu-body">
@@ -875,10 +941,10 @@
                  {customComponents && customComponents.length ? <Panel header="自定义组件" key="cuscomponent">
                    <SourceWrap components={customComponents} MenuType={MenuType} />
                  </Panel> : null}
                  <Panel header={'背景'} key="background">
                  <Panel header={'页面背景'} key="background">
                    {config ? <BgController config={config} updateConfig={this.updateConfig} /> : null}
                  </Panel>
                  <Panel header={'内边距'} key="padding">
                  <Panel header={'页面内边距'} key="padding">
                    {config ? <PaddingController config={config} updateConfig={this.updateConfig} /> : null}
                  </Panel>
                </Collapse>
@@ -888,9 +954,11 @@
                  <div> {config && config.MenuName} </div>
                } bordered={false} extra={
                  <div>
                    <SysInterface config={config} updateConfig={this.updateConfig}/>
                    <PictureController/>
                    <StyleCombControlButton menu={config} />
                    <PasteController type="menu" Tab={null} insert={this.insert} />
                    {config ? <Switch className="big" checkedChildren={dict['mob.enable']} unCheckedChildren={dict['mob.disable']} checked={config.enabled} onChange={this.onEnabledChange} /> : null}
                    <Switch className="big" checkedChildren={dict['mob.enable']} unCheckedChildren={dict['mob.disable']} checked={config && config.enabled} onChange={this.onEnabledChange} />
                    <Button type="primary" onClick={this.submitConfig} loading={menuloading}>{dict['mob.save']}</Button>
                    <Button type="default" onClick={this.closeView}>{dict['mob.return']}</Button>
                  </div>
src/views/menudesign/index.scss
@@ -160,3 +160,15 @@
    }
  }
}
.pc-menu-view.billPrint {
  table {
    border-radius: 0!important;
    .ant-table-column-sorter {
      display: none!important;
    }
  }
  .ant-table-thead > tr > th {
    background: transparent!important;
    border-radius: 0!important;
  }
}
src/views/menudesign/printmenuform/index.jsx
@@ -36,8 +36,12 @@
    this.props.updateConfig({...this.props.config, pageSize: val})
  }
  onRadioChange = (val) => {
  onLayoutChange = (val) => {
    this.props.updateConfig({...this.props.config, pageLayout: val})
  }
  onPaddingChange = (val) => {
    this.props.updateConfig({...this.props.config, pagePadding: val})
  }
  render() {
@@ -87,7 +91,7 @@
                  }
                ]
              })(
                <Radio.Group onChange={(e) => {this.onRadioChange(e.target.value)}}>
                <Radio.Group onChange={(e) => {this.onLayoutChange(e.target.value)}}>
                  <Radio value="vertical">纵向</Radio>
                  <Radio value="horizontal">横向</Radio>
                </Radio.Group>
@@ -105,7 +109,7 @@
                  }
                ]
              })(
                <Radio.Group onChange={(e) => {this.onRadioChange(e.target.value)}}>
                <Radio.Group onChange={(e) => {this.onPaddingChange(e.target.value)}}>
                  <Radio value="default">默认</Radio>
                  <Radio value="without">无</Radio>
                </Radio.Group>
src/views/mobdesign/index.jsx
@@ -192,13 +192,6 @@
    })
  }
  // save = () => {
  //   html2canvas(document.getElementById('view')).then(canvas => {
  //     let imgUri = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); // 获取生成的图片的url
  //     window.location.href = imgUri; // 下载图片
  //   })
  // }
  editCard = (element) => {
    this.setState({
      editElem: element
src/views/printTemplate/index.jsx
@@ -19,6 +19,7 @@
  qrurl,
  imgurl
} from './option.js'
import options from '@/store/options.js'
import Utils from '@/utils/utils.js'
import printCtrl from './print.js'
import Api from '@/api'
@@ -775,7 +776,17 @@
    new Promise(resolve => {
      printCtrl.sketch(config, null).then(res => {
        Api.fileuploadbase64(res, 'cloud').then(result => { // 图片上传,并获取图片路径
        let param = {
          Base64Img: res
        }
        if (options.cloudServiceApi) {
          param.rduri = options.cloudServiceApi
          param.userid = sessionStorage.getItem('CloudUserID') || ''
          param.LoginUID = sessionStorage.getItem('CloudLoginUID') || ''
        }
        Api.fileuploadbase64(param).then(result => { // 图片上传,并获取图片路径
          if (result.status) {
            resolve(Utils.getcloudurl(result.Images))
          } else {