diff --git a/.bk.production.env b/.bk.production.env index 243a11aa1..ef493e14f 100644 --- a/.bk.production.env +++ b/.bk.production.env @@ -24,12 +24,16 @@ BK_COMPONENT_API_URL = {{ BK_COMPONENT_API_URL }} // cookie domian BKPAAS_BK_DOMAIN = {{ BKPAAS_BK_DOMAIN }} +// share url +BK_SHARED_RES_URL = {{ BK_SHARED_RES_URL }} + // apigateway 地址 BK_API_GATEWAY_ORIGIN = ${BK_API_URL_TMPL} // 静态资源前缀 BK_STATIC_URL = {{ BK_STATIC_URL }} + // itsm 地址 BK_ITSM_URL = diff --git a/.gitignore b/.gitignore index d9a86a7d6..2faeb3b4c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ lib/server/conf/data-source.js lib/server/conf/encrypt-secret-key.js lib/server/conf/no-code.js lib/server/conf/iam.js +lib/server/conf/flow.js !lib/server/project-template/project-init-code/bin lib/server/project-template/project-target* diff --git a/lib/client/index.html b/lib/client/index.html index 1b768506e..5ab44d81c 100644 --- a/lib/client/index.html +++ b/lib/client/index.html @@ -12,11 +12,10 @@ - - + - 运维开发平台 | 腾讯蓝鲸智云 + 运维开发平台 | 蓝鲸智云 + + Icon Cool + + +
+

+ +

+
+
单色图标
+
彩色图标
+
+
+ +

为什么使用

+ +

如何使用

+ +
+
+ +

为什么使用

+ +

如何使用

+ +
+ +
+ + + \ No newline at end of file diff --git a/lib/client/src/bk-icon/fonts/iconcool.eot b/lib/client/src/bk-icon/fonts/iconcool.eot index 5c2909f75..bd8853d3d 100644 Binary files a/lib/client/src/bk-icon/fonts/iconcool.eot and b/lib/client/src/bk-icon/fonts/iconcool.eot differ diff --git a/lib/client/src/bk-icon/fonts/iconcool.svg b/lib/client/src/bk-icon/fonts/iconcool.svg index c2fe87d71..2ad84f4f8 100644 --- a/lib/client/src/bk-icon/fonts/iconcool.svg +++ b/lib/client/src/bk-icon/fonts/iconcool.svg @@ -473,12 +473,21 @@ + + + + + + + + + @@ -533,6 +542,15 @@ + + + + + + + + + @@ -611,6 +629,9 @@ + + + @@ -1010,6 +1031,12 @@ + + + + + + @@ -1208,6 +1235,15 @@ + + + + + + + + + diff --git a/lib/client/src/bk-icon/fonts/iconcool.ttf b/lib/client/src/bk-icon/fonts/iconcool.ttf index 653c2511a..76768a9ab 100644 Binary files a/lib/client/src/bk-icon/fonts/iconcool.ttf and b/lib/client/src/bk-icon/fonts/iconcool.ttf differ diff --git a/lib/client/src/bk-icon/fonts/iconcool.woff b/lib/client/src/bk-icon/fonts/iconcool.woff index 065c971aa..92b7484fe 100644 Binary files a/lib/client/src/bk-icon/fonts/iconcool.woff and b/lib/client/src/bk-icon/fonts/iconcool.woff differ diff --git a/lib/client/src/bk-icon/iconcool.js b/lib/client/src/bk-icon/iconcool.js index a2fc16804..51e2639ee 100644 --- a/lib/client/src/bk-icon/iconcool.js +++ b/lib/client/src/bk-icon/iconcool.js @@ -1,5 +1,5 @@ !(function () { - var svgCode = '' + var svgCode = '' if (document.body) { document.body.insertAdjacentHTML('afterbegin', svgCode) } else { diff --git a/lib/client/src/bk-icon/iconcool.json b/lib/client/src/bk-icon/iconcool.json index 506446beb..df6acc735 100644 --- a/lib/client/src/bk-icon/iconcool.json +++ b/lib/client/src/bk-icon/iconcool.json @@ -1 +1 @@ -{"iconName":"bk-drag","icons":[{"name":"assembly-line","svgCode":"\n\n\n\n\n\t\n\t\t\n\t\n\n\n","codepoint":"\\e240"},{"name":"feature-conversion","svgCode":"\n\n\n\n\n\n\t\n\t\n\t\n\t\n\n\n","codepoint":"\\e28c"},{"name":"home","svgCode":"\n\n\n\n\n\n\t\n\n\n","codepoint":"\\e15d"},{"name":"minus-circle","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e219"},{"name":"plus-circle","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e21a"},{"name":"setting","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e26d"},{"name":"check-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e180"},{"name":"assembly-line-fill","svgCode":"","codepoint":"\\e244"},{"name":"close-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e181"},{"name":"setting-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e1f4"},{"name":"exclamation-fill","svgCode":"\n\n\n\n\n\n","codepoint":"\\e288"},{"name":"play","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e103"},{"name":"delete","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e104"},{"name":"undo","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e105"},{"name":"add-fill","svgCode":"\n\n\n\n\n\n","codepoint":"\\e141"},{"name":"reduce-fill","svgCode":"\n\n\n\n\n\n","codepoint":"\\e142"},{"name":"link","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e143"},{"name":"full-screen","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e109"},{"name":"delet","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e148"},{"name":"upload","svgCode":"\n\n\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n","codepoint":"\\e14a"},{"name":"set","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e1af"},{"name":"image","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e107"},{"name":"download","svgCode":"\n\n\n\n\n\n\n","codepoint":"\\e108"},{"name":"member","svgCode":"\n\n\n\n\n\n\n","codepoint":"\\e162"},{"name":"info-tips","svgCode":"\n\n\n\n\n\t\n\t\n\t\n\n\n","codepoint":"\\e182"},{"name":"edit","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e17b"},{"name":"close-small","svgCode":"\n\n\n\n\n\n","codepoint":"\\e147"},{"name":"arrow-down","svgCode":"\n\n\n\n\n\n","codepoint":"\\e106"},{"name":"batch-edit","svgCode":"","codepoint":"\\e146"},{"name":"jump-link","svgCode":"","codepoint":"\\e140"},{"name":"stretch","svgCode":"","codepoint":"\\e145"},{"name":"drag","svgCode":"","codepoint":"\\e144"},{"name":"circle-shape","svgCode":"","codepoint":"\\e192"},{"name":"angle-left","svgCode":"","codepoint":"\\e101"},{"name":"arrow-back","svgCode":"","codepoint":"\\e102"},{"name":"badge","svgCode":"","codepoint":"\\e10b"},{"name":"46","svgCode":"","codepoint":"\\e10c"},{"name":"animatenumber","svgCode":"","codepoint":"\\e10d"},{"name":"button","svgCode":"","codepoint":"\\e10e"},{"name":"collapse","svgCode":"","codepoint":"\\e110"},{"name":"checkbox","svgCode":"","codepoint":"\\e10f"},{"name":"dialog","svgCode":"","codepoint":"\\e111"},{"name":"exception","svgCode":"","codepoint":"\\e112"},{"name":"dropdownmenu","svgCode":"","codepoint":"\\e113"},{"name":"info","svgCode":"","codepoint":"\\e114"},{"name":"input","svgCode":"","codepoint":"\\e115"},{"name":"form","svgCode":"","codepoint":"\\e116"},{"name":"loading","svgCode":"","codepoint":"\\e117"},{"name":"message","svgCode":"","codepoint":"\\e118"},{"name":"nav","svgCode":"","codepoint":"\\e119"},{"name":"notify","svgCode":"","codepoint":"\\e11a"},{"name":"pagination","svgCode":"","codepoint":"\\e11b"},{"name":"play-2","svgCode":"","codepoint":"\\e11c"},{"name":"popconfrim","svgCode":"","codepoint":"\\e11e"},{"name":"progress","svgCode":"","codepoint":"\\e11d"},{"name":"radio","svgCode":"","codepoint":"\\e11f"},{"name":"rate","svgCode":"","codepoint":"\\e120"},{"name":"save","svgCode":"","codepoint":"\\e122"},{"name":"roundprogress","svgCode":"","codepoint":"\\e121"},{"name":"select","svgCode":"","codepoint":"\\e124"},{"name":"sideslider","svgCode":"","codepoint":"\\e125"},{"name":"slider","svgCode":"","codepoint":"\\e123"},{"name":"step","svgCode":"","codepoint":"\\e126"},{"name":"swiper","svgCode":"","codepoint":"\\e127"},{"name":"switcher","svgCode":"","codepoint":"\\e128"},{"name":"tab","svgCode":"","codepoint":"\\e12a"},{"name":"table","svgCode":"","codepoint":"\\e129"},{"name":"text","svgCode":"","codepoint":"\\e12b"},{"name":"tag","svgCode":"","codepoint":"\\e12c"},{"name":"timeline","svgCode":"","codepoint":"\\e12d"},{"name":"tooltip","svgCode":"","codepoint":"\\e12e"},{"name":"transfer","svgCode":"","codepoint":"\\e12f"},{"name":"tree","svgCode":"","codepoint":"\\e130"},{"name":"ctr+y","svgCode":"","codepoint":"\\e131"},{"name":"ctr+z","svgCode":"","codepoint":"\\e132"},{"name":"keyboard","svgCode":"","codepoint":"\\e133"},{"name":"grid-2","svgCode":"","codepoint":"\\e134"},{"name":"grid-3","svgCode":"","codepoint":"\\e135"},{"name":"grid-4","svgCode":"","codepoint":"\\e136"},{"name":"grid-6","svgCode":"","codepoint":"\\e137"},{"name":"grid-5","svgCode":"","codepoint":"\\e13a"},{"name":"grid-7","svgCode":"","codepoint":"\\e13b"},{"name":"grid-8","svgCode":"","codepoint":"\\e138"},{"name":"grid-9","svgCode":"","codepoint":"\\e139"},{"name":"grid-10","svgCode":"","codepoint":"\\e13c"},{"name":"grid-11","svgCode":"","codepoint":"\\e13d"},{"name":"grid-12","svgCode":"","codepoint":"\\e13e"},{"name":"grid-1","svgCode":"","codepoint":"\\e13f"},{"name":"copy","svgCode":"","codepoint":"\\e149"},{"name":"search","svgCode":"","codepoint":"\\e14b"},{"name":"diff","svgCode":"","codepoint":"\\e14d"},{"name":"user","svgCode":"","codepoint":"\\e14c"},{"name":"time-2","svgCode":"","codepoint":"\\e14e"},{"name":"date","svgCode":"","codepoint":"\\e14f"},{"name":"text-2","svgCode":"","codepoint":"\\e150"},{"name":"border-y","svgCode":"","codepoint":"\\e151"},{"name":"border-x","svgCode":"","codepoint":"\\e152"},{"name":"border-all","svgCode":"","codepoint":"\\e153"},{"name":"border-s","svgCode":"","codepoint":"\\e154"},{"name":"radius-all","svgCode":"","codepoint":"\\e155"},{"name":"border-z","svgCode":"","codepoint":"\\e157"},{"name":"radius-s","svgCode":"","codepoint":"\\e156"},{"name":"radius-sj","svgCode":"","codepoint":"\\e158"},{"name":"radius-ys","svgCode":"","codepoint":"\\e159"},{"name":"radius-yx","svgCode":"","codepoint":"\\e15a"},{"name":"radius-zs","svgCode":"","codepoint":"\\e15b"},{"name":"tishi_gaozhi","svgCode":"","codepoint":"\\e246"},{"name":"tixing","svgCode":"","codepoint":"\\e245"},{"name":"yulan","svgCode":"","codepoint":"\\e264"},{"name":"github-logo","svgCode":"","codepoint":"\\e15c"},{"name":"update-log-fill","svgCode":"","codepoint":"\\e15e"},{"name":"histogram","svgCode":"","codepoint":"\\e15f"},{"name":"line-chart","svgCode":"","codepoint":"\\e160"},{"name":"pie-chart","svgCode":"","codepoint":"\\e161"},{"name":"refresh-line","svgCode":"","codepoint":"\\e200"},{"name":"nav-toggle","svgCode":"","codepoint":"\\e163"},{"name":"function-fill","svgCode":"","codepoint":"\\e165"},{"name":"info-fill","svgCode":"","codepoint":"\\e166"},{"name":"favorite","svgCode":"","codepoint":"\\e167"},{"name":"template-fill","svgCode":"","codepoint":"\\e168"},{"name":"summary-fill","svgCode":"","codepoint":"\\e164"},{"name":"favorite-o","svgCode":"","codepoint":"\\e169"},{"name":"list-fill","svgCode":"","codepoint":"\\e16b"},{"name":"user-group","svgCode":"","codepoint":"\\e16c"},{"name":"project-list","svgCode":"","codepoint":"\\e16d"},{"name":"more-dot","svgCode":"","codepoint":"\\e16e"},{"name":"grag-fill","svgCode":"","codepoint":"\\e16f"},{"name":"custom-comp-default","svgCode":"","codepoint":"\\e170"},{"name":"folder-fill","svgCode":"","codepoint":"\\e171"},{"name":"template-logo","svgCode":"","codepoint":"\\e172"},{"name":"flow-fill","svgCode":"","codepoint":"\\e226"},{"name":"function-fill","svgCode":"","codepoint":"\\e1bd"},{"name":"un-full-screen","svgCode":"","codepoint":"\\e174"},{"name":"code-full-screen","svgCode":"","codepoint":"\\e173"},{"name":"angle-down-fill","svgCode":"","codepoint":"\\e175"},{"name":"angle-up-fill","svgCode":"","codepoint":"\\e176"},{"name":"angle-right-fill","svgCode":"","codepoint":"\\e177"},{"name":"angle-left-fill","svgCode":"","codepoint":"\\e178"},{"name":"close-line","svgCode":"","codepoint":"\\e179"},{"name":"add-line","svgCode":"","codepoint":"\\e17a"},{"name":"save-line","svgCode":"","codepoint":"\\e17c"},{"name":"cc-jump-link","svgCode":"","codepoint":"\\e17f"},{"name":"offshelf","svgCode":"","codepoint":"\\e17e"},{"name":"add-3","svgCode":"","codepoint":"\\e183"},{"name":"picture-fail-line","svgCode":"","codepoint":"\\e184"},{"name":"1_deploy-fill","svgCode":"","codepoint":"\\e185"},{"name":"loading-2","svgCode":"","codepoint":"\\e186"},{"name":"router","svgCode":"","codepoint":"\\e187"},{"name":"sync-pending","svgCode":"","codepoint":"\\e297"},{"name":"paste","svgCode":"","codepoint":"\\e248"},{"name":"un-full-screen-2","svgCode":"","codepoint":"\\e21c"},{"name":"filliscreen-line","svgCode":"","codepoint":"\\e21b"},{"name":"card","svgCode":"","codepoint":"\\e189"},{"name":"popover-2","svgCode":"","codepoint":"\\e18a"},{"name":"colorpick","svgCode":"","codepoint":"\\e18b"},{"name":"bigtree","svgCode":"","codepoint":"\\e18c"},{"name":"cascade-jilianxuankuang","svgCode":"","codepoint":"\\e18d"},{"name":"link1","svgCode":"","codepoint":"\\e18e"},{"name":"vesion-fill","svgCode":"","codepoint":"\\e18f"},{"name":"process","svgCode":"","codepoint":"\\e190"},{"name":"off-shelf","svgCode":"","codepoint":"\\e191"},{"name":"alert-line","svgCode":"","codepoint":"\\e193"},{"name":"reflash-line","svgCode":"","codepoint":"\\e194"},{"name":"token-fill","svgCode":"","codepoint":"\\e195"},{"name":"template-fill-2","svgCode":"","codepoint":"\\e196"},{"name":"breadcrumb","svgCode":"","codepoint":"\\e197"},{"name":"bottom-group","svgCode":"","codepoint":"\\e198"},{"name":"freedrag","svgCode":"","codepoint":"\\e199"},{"name":"copy-line","svgCode":"","codepoint":"\\e1a9"},{"name":"comment-fill","svgCode":"","codepoint":"\\e1be"},{"name":"download-line","svgCode":"","codepoint":"\\e1a8"},{"name":"invisible-eye","svgCode":"","codepoint":"\\e19a"},{"name":"level-down","svgCode":"","codepoint":"\\e19b"},{"name":"audit","svgCode":"","codepoint":"\\e19c"},{"name":"unfold","svgCode":"","codepoint":"\\e19d"},{"name":"fold","svgCode":"","codepoint":"\\e19e"},{"name":"directives","svgCode":"","codepoint":"\\e1a1"},{"name":"variable","svgCode":"","codepoint":"\\e19f"},{"name":"variable-manage","svgCode":"","codepoint":"\\e1a0"},{"name":"yemianhanshu","svgCode":"","codepoint":"\\e1a2"},{"name":"hanshuku","svgCode":"","codepoint":"\\e1a3"},{"name":"page-variable","svgCode":"","codepoint":"\\e1a5"},{"name":"yuanma","svgCode":"","codepoint":"\\e1a6"},{"name":"shanchu","svgCode":"","codepoint":"\\e1a4"},{"name":"fankui","svgCode":"","codepoint":"\\e1a7"},{"name":"auth-set","svgCode":"","codepoint":"\\e1aa"},{"name":"json","svgCode":"","codepoint":"\\e1ab"},{"name":"logo","svgCode":"","codepoint":"\\e1ac"},{"name":"visible-eye","svgCode":"","codepoint":"\\e1ad"},{"name":"process","svgCode":"","codepoint":"\\e1ae"},{"name":"huabu","svgCode":"","codepoint":"\\e1b0"},{"name":"drag-small","svgCode":"","codepoint":"\\e1b1"},{"name":"dianzan-tianchong","svgCode":"","codepoint":"\\e28e"},{"name":"dianzan","svgCode":"","codepoint":"\\e28d"},{"name":"select-value","svgCode":"","codepoint":"\\e1b2"},{"name":"select-variable","svgCode":"","codepoint":"\\e1b3"},{"name":"select-expression","svgCode":"","codepoint":"\\e1b4"},{"name":"drag-small1","svgCode":"","codepoint":"\\e1b5"},{"name":"icon-manage","svgCode":"","codepoint":"\\e1b6"},{"name":"document","svgCode":"","codepoint":"\\e1bb"},{"name":"qiyeweixin","svgCode":"","codepoint":"\\e1bc"},{"name":"erweima","svgCode":"","codepoint":"\\e1b7"},{"name":"lianxi","svgCode":"","codepoint":"\\e1b8"},{"name":"qq-2","svgCode":"","codepoint":"\\e1b9"},{"name":"gaojing","svgCode":"","codepoint":"\\e1ba"},{"name":"bubble-chart","svgCode":"","codepoint":"\\e1bf"},{"name":"scatter-chart","svgCode":"","codepoint":"\\e1c0"},{"name":"radar-chart","svgCode":"","codepoint":"\\e1c1"},{"name":"bk-bubble-chart","svgCode":"","codepoint":"\\e1c2"},{"name":"bk-scatter-chart","svgCode":"","codepoint":"\\e1c3"},{"name":"fix","svgCode":"","codepoint":"\\e1c4"},{"name":"export","svgCode":"","codepoint":"\\e1c5"},{"name":"sql","svgCode":"","codepoint":"\\e1c6"},{"name":"csv","svgCode":"","codepoint":"\\e1c7"},{"name":"shuipingjuzhuduiqi","svgCode":"","codepoint":"\\e1c8"},{"name":"diduiqi","svgCode":"","codepoint":"\\e1ca"},{"name":"chuizhijuzhongduiqi","svgCode":"","codepoint":"\\e1c9"},{"name":"youduiqi","svgCode":"","codepoint":"\\e1cc"},{"name":"dingduiqi","svgCode":"","codepoint":"\\e1cb"},{"name":"zuoduiqi","svgCode":"","codepoint":"\\e1cd"},{"name":"data-table","svgCode":"","codepoint":"\\e1ce"},{"name":"data-source-manage","svgCode":"","codepoint":"\\e1cf"},{"name":"version","svgCode":"","codepoint":"\\e1d0"},{"name":"mobilephone","svgCode":"","codepoint":"\\e1d3"},{"name":"pc","svgCode":"","codepoint":"\\e1d2"},{"name":"templateline","svgCode":"","codepoint":"\\e1d4"},{"name":"file-video","svgCode":"","codepoint":"\\e1f5"},{"name":"file-image","svgCode":"","codepoint":"\\e1f6"},{"name":"file-text","svgCode":"","codepoint":"\\e1f7"},{"name":"file-zip","svgCode":"","codepoint":"\\e1f8"},{"name":"file-pdf","svgCode":"","codepoint":"\\e1f9"},{"name":"file-audio","svgCode":"","codepoint":"\\e1fa"},{"name":"file-doc","svgCode":"","codepoint":"\\e1fb"},{"name":"file-excel","svgCode":"","codepoint":"\\e1fc"},{"name":"file-ppt","svgCode":"","codepoint":"\\e1fd"},{"name":"cellcellgroup","svgCode":"","codepoint":"\\e1d5"},{"name":"circle","svgCode":"","codepoint":"\\e1d6"},{"name":"empty","svgCode":"","codepoint":"\\e1d7"},{"name":"countdown","svgCode":"","codepoint":"\\e1d8"},{"name":"divider","svgCode":"","codepoint":"\\e1d9"},{"name":"noticebar","svgCode":"","codepoint":"\\e1da"},{"name":"loading","svgCode":"","codepoint":"\\e1db"},{"name":"navbar","svgCode":"","codepoint":"\\e1dc"},{"name":"numberkeyborad","svgCode":"","codepoint":"\\e1dd"},{"name":"passwordinput","svgCode":"","codepoint":"\\e1de"},{"name":"picker","svgCode":"","codepoint":"\\e1df"},{"name":"search-2","svgCode":"","codepoint":"\\e1e0"},{"name":"skeleton","svgCode":"","codepoint":"\\e1e1"},{"name":"step","svgCode":"","codepoint":"\\e1e2"},{"name":"treeselect","svgCode":"","codepoint":"\\e1e3"},{"name":"stepper","svgCode":"","codepoint":"\\e1e4"},{"name":"icon-2","svgCode":"","codepoint":"\\e1e5"},{"name":"it-new-desktop","svgCode":"","codepoint":"\\e1e6"},{"name":"set-fill","svgCode":"","codepoint":"\\e1e7"},{"name":"battery","svgCode":"","codepoint":"\\e1e8"},{"name":"wifi","svgCode":"","codepoint":"\\e1ea"},{"name":"cellular-connection","svgCode":"","codepoint":"\\e1e9"},{"name":"column","svgCode":"","codepoint":"\\e1eb"},{"name":"grid","svgCode":"","codepoint":"\\e1ec"},{"name":"info","svgCode":"","codepoint":"\\e1ed"},{"name":"page","svgCode":"","codepoint":"\\e1ee"},{"name":"image-small","svgCode":"","codepoint":"\\e1ef"},{"name":"flow","svgCode":"","codepoint":"\\e1f0"},{"name":"source","svgCode":"","codepoint":"\\e1f1"},{"name":"column","svgCode":"","codepoint":"\\e1f2"},{"name":"grid","svgCode":"","codepoint":"\\e1f3"},{"name":"display-card","svgCode":"","codepoint":"\\e1fe"},{"name":"display-list","svgCode":"","codepoint":"\\e1ff"},{"name":"links","svgCode":"","codepoint":"\\e201"},{"name":"compoment-count","svgCode":"","codepoint":"\\e202"},{"name":"fc-count","svgCode":"","codepoint":"\\e203"},{"name":"project-count","svgCode":"","codepoint":"\\e204"},{"name":"user-count","svgCode":"","codepoint":"\\e205"},{"name":"project","svgCode":"","codepoint":"\\e206"},{"name":"chanpinjieshao","svgCode":"","codepoint":"\\e207"},{"name":"chuizhijunfen","svgCode":"","codepoint":"\\e208"},{"name":"shuipingjunfen","svgCode":"","codepoint":"\\e20b"},{"name":"shuipingjuzhong","svgCode":"","codepoint":"\\e209"},{"name":"1_diduiqi","svgCode":"","codepoint":"\\e20c"},{"name":"1_dingduiqi","svgCode":"","codepoint":"\\e20a"},{"name":"1_juzhongduiqi","svgCode":"","codepoint":"\\e20f"},{"name":"1_youduiqi","svgCode":"","codepoint":"\\e20e"},{"name":"1_zuoduiqi","svgCode":"","codepoint":"\\e20d"},{"name":"shangxiachengkai","svgCode":"","codepoint":"\\e210"},{"name":"shangxialashen","svgCode":"","codepoint":"\\e211"},{"name":"xiangxiazishiying","svgCode":"","codepoint":"\\e212"},{"name":"youshangjiaolashen","svgCode":"","codepoint":"\\e213"},{"name":"youxiajiaolashen","svgCode":"","codepoint":"\\e214"},{"name":"yuanjiao","svgCode":"","codepoint":"\\e215"},{"name":"zuoyouchengkai","svgCode":"","codepoint":"\\e216"},{"name":"zuoyoulashen","svgCode":"","codepoint":"\\e217"},{"name":"zuoyouchengman","svgCode":"","codepoint":"\\e218"},{"name":"delete-fill","svgCode":"","codepoint":"\\e28a"},{"name":"more","svgCode":"","codepoint":"\\e28b"},{"name":"attention-fill","svgCode":"","codepoint":"\\e289"},{"name":"rengongjiedian","svgCode":"","codepoint":"\\e221"},{"name":"shenpijiedian","svgCode":"","codepoint":"\\e220"},{"name":"huiqianjiedian","svgCode":"","codepoint":"\\e21f"},{"name":"apijiedian","svgCode":"","codepoint":"\\e21e"},{"name":"shujuchulijiedian","svgCode":"","codepoint":"\\e21d"},{"name":"end","svgCode":"","codepoint":"\\e222"},{"name":"parallel","svgCode":"","codepoint":"\\e223"},{"name":"converge","svgCode":"","codepoint":"\\e224"},{"name":"start","svgCode":"","codepoint":"\\e225"},{"name":"danxingwenben","svgCode":"","codepoint":"\\e229"},{"name":"danxuankuang","svgCode":"","codepoint":"\\e22b"},{"name":"danxuanrenyuanxuanze","svgCode":"","codepoint":"\\e22a"},{"name":"danxuanxiala","svgCode":"","codepoint":"\\e22d"},{"name":"duoxuankuang","svgCode":"","codepoint":"\\e22e"},{"name":"duoxuanrenyuanxuanze","svgCode":"","codepoint":"\\e22c"},{"name":"duoxingwenben","svgCode":"","codepoint":"\\e232"},{"name":"duoxuanxiala","svgCode":"","codepoint":"\\e230"},{"name":"fengexian","svgCode":"","codepoint":"\\e22f"},{"name":"fuwenben","svgCode":"","codepoint":"\\e233"},{"name":"keshurudanxuanxiala","svgCode":"","codepoint":"\\e231"},{"name":"lianjie","svgCode":"","codepoint":"\\e234"},{"name":"miaoshuwenben","svgCode":"","codepoint":"\\e236"},{"name":"riqi","svgCode":"","codepoint":"\\e237"},{"name":"shangchuan","svgCode":"","codepoint":"\\e235"},{"name":"shijian","svgCode":"","codepoint":"\\e238"},{"name":"tupian","svgCode":"","codepoint":"\\e239"},{"name":"shuzi","svgCode":"","codepoint":"\\e23a"},{"name":"zidingyibiaoge","svgCode":"","codepoint":"\\e23b"},{"name":"sort","svgCode":"","codepoint":"\\e23c"},{"name":"liebiao","svgCode":"","codepoint":"\\e23d"},{"name":"kapian","svgCode":"","codepoint":"\\e23f"},{"name":"refill","svgCode":"","codepoint":"\\e241"},{"name":"xlsx","svgCode":"","codepoint":"\\e247"},{"name":"tree1","svgCode":"","codepoint":"\\e249"},{"name":"2-jiantou-you","svgCode":"","codepoint":"\\e24a"},{"name":"2-jiantou-zuo","svgCode":"","codepoint":"\\e24b"},{"name":"xuanzhongfuji","svgCode":"","codepoint":"\\e24c"},{"name":"qianjin","svgCode":"","codepoint":"\\e24f"},{"name":"undo-2","svgCode":"","codepoint":"\\e250"},{"name":"tubiao","svgCode":"","codepoint":"\\e24e"},{"name":"zuixiaokuandu","svgCode":"","codepoint":"\\e24d"},{"name":"zuidagaodu","svgCode":"","codepoint":"\\e251"},{"name":"juhe","svgCode":"","codepoint":"\\e252"},{"name":"zuidakuandu","svgCode":"","codepoint":"\\e254"},{"name":"ziti","svgCode":"","codepoint":"\\e253"},{"name":"zuixiaogaodu","svgCode":"","codepoint":"\\e255"},{"name":"zizhong","svgCode":"","codepoint":"\\e256"},{"name":"xinggao","svgCode":"","codepoint":"\\e25a"},{"name":"zitiyangshi","svgCode":"","codepoint":"\\e257"},{"name":"xiahuaxian","svgCode":"","codepoint":"\\e259"},{"name":"shanghuaxian","svgCode":"","codepoint":"\\e258"},{"name":"zhonghuaxian","svgCode":"","codepoint":"\\e25b"},{"name":"suojin","svgCode":"","codepoint":"\\e25d"},{"name":"zijianju","svgCode":"","codepoint":"\\e25c"},{"name":"zuoduiqi-2","svgCode":"","codepoint":"\\e260"},{"name":"juzhongduiqi","svgCode":"","codepoint":"\\e25f"},{"name":"youduiqi-2","svgCode":"","codepoint":"\\e261"},{"name":"liangduanduiqi","svgCode":"","codepoint":"\\e25e"},{"name":"dancijianju","svgCode":"","codepoint":"\\e262"},{"name":"zitidaxiao","svgCode":"","codepoint":"\\e263"},{"name":"shujuyuan","svgCode":"","codepoint":"\\e268"},{"name":"zidingyiye","svgCode":"","codepoint":"\\e267"},{"name":"markdown","svgCode":"","codepoint":"\\e265"},{"name":"biaodan","svgCode":"","codepoint":"\\e266"},{"name":"liuchengtidanye","svgCode":"","codepoint":"\\e269"},{"name":"yuanma-2","svgCode":"","codepoint":"\\e26a"},{"name":"shangxiaduicheng","svgCode":"","codepoint":"\\e26c"},{"name":"zuoyouduicheng","svgCode":"","codepoint":"\\e26b"},{"name":"zuobianju","svgCode":"","codepoint":"\\e26f"},{"name":"dingbianju","svgCode":"","codepoint":"\\e272"},{"name":"shubiaozhizhen","svgCode":"","codepoint":"\\e26e"},{"name":"crosshair","svgCode":"","codepoint":"\\e273"},{"name":"text-3","svgCode":"","codepoint":"\\e271"},{"name":"wait","svgCode":"","codepoint":"\\e274"},{"name":"move","svgCode":"","codepoint":"\\e275"},{"name":"help","svgCode":"","codepoint":"\\e270"},{"name":"gaodu","svgCode":"","codepoint":"\\e276"},{"name":"kuandu","svgCode":"","codepoint":"\\e277"},{"name":"xing-2","svgCode":"","codepoint":"\\e27a"},{"name":"lie-2","svgCode":"","codepoint":"\\e279"},{"name":"ziyoubuju","svgCode":"","codepoint":"\\e278"},{"name":"display-3","svgCode":"","codepoint":"\\e27b"},{"name":"morenziti","svgCode":"","codepoint":"\\e27d"},{"name":"xieti","svgCode":"","codepoint":"\\e27c"},{"name":"pointer","svgCode":"","codepoint":"\\e27e"},{"name":"ai","svgCode":"","codepoint":"\\e27f"},{"name":"fasong","svgCode":"","codepoint":"\\e281"},{"name":"qingsao","svgCode":"","codepoint":"\\e282"},{"name":"shangxialashen-2","svgCode":"","codepoint":"\\e283"},{"name":"shangxiajuhe","svgCode":"","codepoint":"\\e284"},{"name":"shanchu-2","svgCode":"","codepoint":"\\e286"},{"name":"tag-3","svgCode":"","codepoint":"\\e28f"},{"name":"liaotianchuang","svgCode":"","codepoint":"\\e290"},{"name":"yinyongchuangjian","svgCode":"","codepoint":"\\e291"},{"name":"yinyong-2","svgCode":"","codepoint":"\\e292"},{"name":"saoba","svgCode":"","codepoint":"\\e294"},{"name":"quanquan","svgCode":"","codepoint":"\\e295"},{"name":"builder","svgCode":"","codepoint":"\\e298"}]} \ No newline at end of file +{"iconName":"bk-drag","icons":[{"name":"assembly-line","svgCode":"\n\n\n\n\n\t\n\t\t\n\t\n\n\n","codepoint":"\\e240"},{"name":"feature-conversion","svgCode":"\n\n\n\n\n\n\t\n\t\n\t\n\t\n\n\n","codepoint":"\\e28c"},{"name":"home","svgCode":"\n\n\n\n\n\n\t\n\n\n","codepoint":"\\e15d"},{"name":"minus-circle","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e219"},{"name":"plus-circle","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e21a"},{"name":"setting","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e26d"},{"name":"check-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e180"},{"name":"assembly-line-fill","svgCode":"","codepoint":"\\e244"},{"name":"close-circle-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e181"},{"name":"setting-fill","svgCode":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","codepoint":"\\e1f4"},{"name":"exclamation-fill","svgCode":"\n\n\n\n\n\n","codepoint":"\\e288"},{"name":"play","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e103"},{"name":"delete","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e104"},{"name":"undo","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e105"},{"name":"add-fill","svgCode":"\n\n\n\n\n\n","codepoint":"\\e141"},{"name":"reduce-fill","svgCode":"\n\n\n\n\n\n","codepoint":"\\e142"},{"name":"link","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e143"},{"name":"full-screen","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e109"},{"name":"delet","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e148"},{"name":"upload","svgCode":"\n\n\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n","codepoint":"\\e14a"},{"name":"set","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e1af"},{"name":"image","svgCode":"\n\n\n\n\n\t\n\t\n\n\n","codepoint":"\\e107"},{"name":"download","svgCode":"\n\n\n\n\n\n\n","codepoint":"\\e108"},{"name":"member","svgCode":"\n\n\n\n\n\n\n","codepoint":"\\e162"},{"name":"info-tips","svgCode":"\n\n\n\n\n\t\n\t\n\t\n\n\n","codepoint":"\\e182"},{"name":"edit","svgCode":"\n\n\n\n\n\n\n\n","codepoint":"\\e17b"},{"name":"close-small","svgCode":"\n\n\n\n\n\n","codepoint":"\\e147"},{"name":"arrow-down","svgCode":"\n\n\n\n\n\n","codepoint":"\\e106"},{"name":"batch-edit","svgCode":"","codepoint":"\\e146"},{"name":"jump-link","svgCode":"","codepoint":"\\e140"},{"name":"stretch","svgCode":"","codepoint":"\\e145"},{"name":"drag","svgCode":"","codepoint":"\\e144"},{"name":"circle-shape","svgCode":"","codepoint":"\\e192"},{"name":"angle-left","svgCode":"","codepoint":"\\e101"},{"name":"arrow-back","svgCode":"","codepoint":"\\e102"},{"name":"badge","svgCode":"","codepoint":"\\e10b"},{"name":"46","svgCode":"","codepoint":"\\e10c"},{"name":"animatenumber","svgCode":"","codepoint":"\\e10d"},{"name":"button","svgCode":"","codepoint":"\\e10e"},{"name":"collapse","svgCode":"","codepoint":"\\e110"},{"name":"checkbox","svgCode":"","codepoint":"\\e10f"},{"name":"dialog","svgCode":"","codepoint":"\\e111"},{"name":"exception","svgCode":"","codepoint":"\\e112"},{"name":"dropdownmenu","svgCode":"","codepoint":"\\e113"},{"name":"info","svgCode":"","codepoint":"\\e114"},{"name":"input","svgCode":"","codepoint":"\\e115"},{"name":"form","svgCode":"","codepoint":"\\e116"},{"name":"loading","svgCode":"","codepoint":"\\e117"},{"name":"message","svgCode":"","codepoint":"\\e118"},{"name":"nav","svgCode":"","codepoint":"\\e119"},{"name":"notify","svgCode":"","codepoint":"\\e11a"},{"name":"pagination","svgCode":"","codepoint":"\\e11b"},{"name":"play-2","svgCode":"","codepoint":"\\e11c"},{"name":"popconfrim","svgCode":"","codepoint":"\\e11e"},{"name":"progress","svgCode":"","codepoint":"\\e11d"},{"name":"radio","svgCode":"","codepoint":"\\e11f"},{"name":"rate","svgCode":"","codepoint":"\\e120"},{"name":"save","svgCode":"","codepoint":"\\e122"},{"name":"roundprogress","svgCode":"","codepoint":"\\e121"},{"name":"select","svgCode":"","codepoint":"\\e124"},{"name":"sideslider","svgCode":"","codepoint":"\\e125"},{"name":"slider","svgCode":"","codepoint":"\\e123"},{"name":"step","svgCode":"","codepoint":"\\e126"},{"name":"swiper","svgCode":"","codepoint":"\\e127"},{"name":"switcher","svgCode":"","codepoint":"\\e128"},{"name":"tab","svgCode":"","codepoint":"\\e12a"},{"name":"table","svgCode":"","codepoint":"\\e129"},{"name":"text","svgCode":"","codepoint":"\\e12b"},{"name":"tag","svgCode":"","codepoint":"\\e12c"},{"name":"timeline","svgCode":"","codepoint":"\\e12d"},{"name":"tooltip","svgCode":"","codepoint":"\\e12e"},{"name":"transfer","svgCode":"","codepoint":"\\e12f"},{"name":"tree","svgCode":"","codepoint":"\\e130"},{"name":"ctr+y","svgCode":"","codepoint":"\\e131"},{"name":"ctr+z","svgCode":"","codepoint":"\\e132"},{"name":"keyboard","svgCode":"","codepoint":"\\e133"},{"name":"grid-2","svgCode":"","codepoint":"\\e134"},{"name":"grid-3","svgCode":"","codepoint":"\\e135"},{"name":"grid-4","svgCode":"","codepoint":"\\e136"},{"name":"grid-6","svgCode":"","codepoint":"\\e137"},{"name":"grid-5","svgCode":"","codepoint":"\\e13a"},{"name":"grid-7","svgCode":"","codepoint":"\\e13b"},{"name":"grid-8","svgCode":"","codepoint":"\\e138"},{"name":"grid-9","svgCode":"","codepoint":"\\e139"},{"name":"grid-10","svgCode":"","codepoint":"\\e13c"},{"name":"grid-11","svgCode":"","codepoint":"\\e13d"},{"name":"grid-12","svgCode":"","codepoint":"\\e13e"},{"name":"grid-1","svgCode":"","codepoint":"\\e13f"},{"name":"copy","svgCode":"","codepoint":"\\e149"},{"name":"search","svgCode":"","codepoint":"\\e14b"},{"name":"diff","svgCode":"","codepoint":"\\e14d"},{"name":"user","svgCode":"","codepoint":"\\e14c"},{"name":"time-2","svgCode":"","codepoint":"\\e14e"},{"name":"date","svgCode":"","codepoint":"\\e14f"},{"name":"text-2","svgCode":"","codepoint":"\\e150"},{"name":"border-y","svgCode":"","codepoint":"\\e151"},{"name":"border-x","svgCode":"","codepoint":"\\e152"},{"name":"border-all","svgCode":"","codepoint":"\\e153"},{"name":"border-s","svgCode":"","codepoint":"\\e154"},{"name":"radius-all","svgCode":"","codepoint":"\\e155"},{"name":"border-z","svgCode":"","codepoint":"\\e157"},{"name":"radius-s","svgCode":"","codepoint":"\\e156"},{"name":"radius-sj","svgCode":"","codepoint":"\\e158"},{"name":"radius-ys","svgCode":"","codepoint":"\\e159"},{"name":"radius-yx","svgCode":"","codepoint":"\\e15a"},{"name":"radius-zs","svgCode":"","codepoint":"\\e15b"},{"name":"tishi_gaozhi","svgCode":"","codepoint":"\\e246"},{"name":"tixing","svgCode":"","codepoint":"\\e245"},{"name":"yulan","svgCode":"","codepoint":"\\e264"},{"name":"github-logo","svgCode":"","codepoint":"\\e15c"},{"name":"update-log-fill","svgCode":"","codepoint":"\\e15e"},{"name":"histogram","svgCode":"","codepoint":"\\e15f"},{"name":"line-chart","svgCode":"","codepoint":"\\e160"},{"name":"pie-chart","svgCode":"","codepoint":"\\e161"},{"name":"refresh-line","svgCode":"","codepoint":"\\e200"},{"name":"nav-toggle","svgCode":"","codepoint":"\\e163"},{"name":"function-fill","svgCode":"","codepoint":"\\e165"},{"name":"info-fill","svgCode":"","codepoint":"\\e166"},{"name":"favorite","svgCode":"","codepoint":"\\e167"},{"name":"template-fill","svgCode":"","codepoint":"\\e168"},{"name":"summary-fill","svgCode":"","codepoint":"\\e164"},{"name":"favorite-o","svgCode":"","codepoint":"\\e169"},{"name":"list-fill","svgCode":"","codepoint":"\\e16b"},{"name":"user-group","svgCode":"","codepoint":"\\e16c"},{"name":"project-list","svgCode":"","codepoint":"\\e16d"},{"name":"more-dot","svgCode":"","codepoint":"\\e16e"},{"name":"grag-fill","svgCode":"","codepoint":"\\e16f"},{"name":"custom-comp-default","svgCode":"","codepoint":"\\e170"},{"name":"folder-fill","svgCode":"","codepoint":"\\e171"},{"name":"template-logo","svgCode":"","codepoint":"\\e172"},{"name":"flow-fill","svgCode":"","codepoint":"\\e226"},{"name":"function-fill","svgCode":"","codepoint":"\\e1bd"},{"name":"un-full-screen","svgCode":"","codepoint":"\\e174"},{"name":"code-full-screen","svgCode":"","codepoint":"\\e173"},{"name":"angle-down-fill","svgCode":"","codepoint":"\\e175"},{"name":"angle-up-fill","svgCode":"","codepoint":"\\e176"},{"name":"angle-right-fill","svgCode":"","codepoint":"\\e177"},{"name":"angle-left-fill","svgCode":"","codepoint":"\\e178"},{"name":"close-line","svgCode":"","codepoint":"\\e179"},{"name":"add-line","svgCode":"","codepoint":"\\e17a"},{"name":"save-line","svgCode":"","codepoint":"\\e17c"},{"name":"cc-jump-link","svgCode":"","codepoint":"\\e17f"},{"name":"offshelf","svgCode":"","codepoint":"\\e17e"},{"name":"add-3","svgCode":"","codepoint":"\\e183"},{"name":"picture-fail-line","svgCode":"","codepoint":"\\e184"},{"name":"1_deploy-fill","svgCode":"","codepoint":"\\e185"},{"name":"loading-2","svgCode":"","codepoint":"\\e186"},{"name":"router","svgCode":"","codepoint":"\\e187"},{"name":"sync-failed","svgCode":"","codepoint":"\\e29c"},{"name":"sync-pending","svgCode":"","codepoint":"\\e297"},{"name":"sync-success","svgCode":"","codepoint":"\\e29d"},{"name":"paste","svgCode":"","codepoint":"\\e248"},{"name":"sync-default","svgCode":"","codepoint":"\\e29e"},{"name":"un-full-screen-2","svgCode":"","codepoint":"\\e21c"},{"name":"filliscreen-line","svgCode":"","codepoint":"\\e21b"},{"name":"card","svgCode":"","codepoint":"\\e189"},{"name":"popover-2","svgCode":"","codepoint":"\\e18a"},{"name":"colorpick","svgCode":"","codepoint":"\\e18b"},{"name":"bigtree","svgCode":"","codepoint":"\\e18c"},{"name":"cascade-jilianxuankuang","svgCode":"","codepoint":"\\e18d"},{"name":"link1","svgCode":"","codepoint":"\\e18e"},{"name":"vesion-fill","svgCode":"","codepoint":"\\e18f"},{"name":"process","svgCode":"","codepoint":"\\e190"},{"name":"off-shelf","svgCode":"","codepoint":"\\e191"},{"name":"alert-line","svgCode":"","codepoint":"\\e193"},{"name":"reflash-line","svgCode":"","codepoint":"\\e194"},{"name":"token-fill","svgCode":"","codepoint":"\\e195"},{"name":"template-fill-2","svgCode":"","codepoint":"\\e196"},{"name":"breadcrumb","svgCode":"","codepoint":"\\e197"},{"name":"bottom-group","svgCode":"","codepoint":"\\e198"},{"name":"freedrag","svgCode":"","codepoint":"\\e199"},{"name":"abnormal","svgCode":"","codepoint":"\\e299"},{"name":"normal","svgCode":"","codepoint":"\\e29a"},{"name":"unknown","svgCode":"","codepoint":"\\e29b"},{"name":"copy-line","svgCode":"","codepoint":"\\e1a9"},{"name":"comment-fill","svgCode":"","codepoint":"\\e1be"},{"name":"download-line","svgCode":"","codepoint":"\\e1a8"},{"name":"invisible-eye","svgCode":"","codepoint":"\\e19a"},{"name":"level-down","svgCode":"","codepoint":"\\e19b"},{"name":"audit","svgCode":"","codepoint":"\\e19c"},{"name":"unfold","svgCode":"","codepoint":"\\e19d"},{"name":"fold","svgCode":"","codepoint":"\\e19e"},{"name":"directives","svgCode":"","codepoint":"\\e1a1"},{"name":"variable","svgCode":"","codepoint":"\\e19f"},{"name":"variable-manage","svgCode":"","codepoint":"\\e1a0"},{"name":"yemianhanshu","svgCode":"","codepoint":"\\e1a2"},{"name":"hanshuku","svgCode":"","codepoint":"\\e1a3"},{"name":"page-variable","svgCode":"","codepoint":"\\e1a5"},{"name":"yuanma","svgCode":"","codepoint":"\\e1a6"},{"name":"shanchu","svgCode":"","codepoint":"\\e1a4"},{"name":"fankui","svgCode":"","codepoint":"\\e1a7"},{"name":"auth-set","svgCode":"","codepoint":"\\e1aa"},{"name":"json","svgCode":"","codepoint":"\\e1ab"},{"name":"logo","svgCode":"","codepoint":"\\e1ac"},{"name":"visible-eye","svgCode":"","codepoint":"\\e1ad"},{"name":"process","svgCode":"","codepoint":"\\e1ae"},{"name":"huabu","svgCode":"","codepoint":"\\e1b0"},{"name":"drag-small","svgCode":"","codepoint":"\\e1b1"},{"name":"dianzan-tianchong","svgCode":"","codepoint":"\\e28e"},{"name":"dianzan","svgCode":"","codepoint":"\\e28d"},{"name":"sync-waiting-01","svgCode":"","codepoint":"\\e2a0"},{"name":"select-value","svgCode":"","codepoint":"\\e1b2"},{"name":"select-variable","svgCode":"","codepoint":"\\e1b3"},{"name":"select-expression","svgCode":"","codepoint":"\\e1b4"},{"name":"drag-small1","svgCode":"","codepoint":"\\e1b5"},{"name":"icon-manage","svgCode":"","codepoint":"\\e1b6"},{"name":"document","svgCode":"","codepoint":"\\e1bb"},{"name":"qiyeweixin","svgCode":"","codepoint":"\\e1bc"},{"name":"erweima","svgCode":"","codepoint":"\\e1b7"},{"name":"lianxi","svgCode":"","codepoint":"\\e1b8"},{"name":"qq-2","svgCode":"","codepoint":"\\e1b9"},{"name":"gaojing","svgCode":"","codepoint":"\\e1ba"},{"name":"bubble-chart","svgCode":"","codepoint":"\\e1bf"},{"name":"scatter-chart","svgCode":"","codepoint":"\\e1c0"},{"name":"radar-chart","svgCode":"","codepoint":"\\e1c1"},{"name":"bk-bubble-chart","svgCode":"","codepoint":"\\e1c2"},{"name":"bk-scatter-chart","svgCode":"","codepoint":"\\e1c3"},{"name":"fix","svgCode":"","codepoint":"\\e1c4"},{"name":"export","svgCode":"","codepoint":"\\e1c5"},{"name":"sql","svgCode":"","codepoint":"\\e1c6"},{"name":"csv","svgCode":"","codepoint":"\\e1c7"},{"name":"shuipingjuzhuduiqi","svgCode":"","codepoint":"\\e1c8"},{"name":"diduiqi","svgCode":"","codepoint":"\\e1ca"},{"name":"chuizhijuzhongduiqi","svgCode":"","codepoint":"\\e1c9"},{"name":"youduiqi","svgCode":"","codepoint":"\\e1cc"},{"name":"dingduiqi","svgCode":"","codepoint":"\\e1cb"},{"name":"zuoduiqi","svgCode":"","codepoint":"\\e1cd"},{"name":"data-table","svgCode":"","codepoint":"\\e1ce"},{"name":"data-source-manage","svgCode":"","codepoint":"\\e1cf"},{"name":"version","svgCode":"","codepoint":"\\e1d0"},{"name":"mobilephone","svgCode":"","codepoint":"\\e1d3"},{"name":"pc","svgCode":"","codepoint":"\\e1d2"},{"name":"templateline","svgCode":"","codepoint":"\\e1d4"},{"name":"file-video","svgCode":"","codepoint":"\\e1f5"},{"name":"file-image","svgCode":"","codepoint":"\\e1f6"},{"name":"file-text","svgCode":"","codepoint":"\\e1f7"},{"name":"file-zip","svgCode":"","codepoint":"\\e1f8"},{"name":"file-pdf","svgCode":"","codepoint":"\\e1f9"},{"name":"file-audio","svgCode":"","codepoint":"\\e1fa"},{"name":"file-doc","svgCode":"","codepoint":"\\e1fb"},{"name":"file-excel","svgCode":"","codepoint":"\\e1fc"},{"name":"file-ppt","svgCode":"","codepoint":"\\e1fd"},{"name":"cellcellgroup","svgCode":"","codepoint":"\\e1d5"},{"name":"circle","svgCode":"","codepoint":"\\e1d6"},{"name":"empty","svgCode":"","codepoint":"\\e1d7"},{"name":"countdown","svgCode":"","codepoint":"\\e1d8"},{"name":"divider","svgCode":"","codepoint":"\\e1d9"},{"name":"noticebar","svgCode":"","codepoint":"\\e1da"},{"name":"loading","svgCode":"","codepoint":"\\e1db"},{"name":"navbar","svgCode":"","codepoint":"\\e1dc"},{"name":"numberkeyborad","svgCode":"","codepoint":"\\e1dd"},{"name":"passwordinput","svgCode":"","codepoint":"\\e1de"},{"name":"picker","svgCode":"","codepoint":"\\e1df"},{"name":"search-2","svgCode":"","codepoint":"\\e1e0"},{"name":"skeleton","svgCode":"","codepoint":"\\e1e1"},{"name":"step","svgCode":"","codepoint":"\\e1e2"},{"name":"treeselect","svgCode":"","codepoint":"\\e1e3"},{"name":"stepper","svgCode":"","codepoint":"\\e1e4"},{"name":"icon-2","svgCode":"","codepoint":"\\e1e5"},{"name":"it-new-desktop","svgCode":"","codepoint":"\\e1e6"},{"name":"set-fill","svgCode":"","codepoint":"\\e1e7"},{"name":"battery","svgCode":"","codepoint":"\\e1e8"},{"name":"wifi","svgCode":"","codepoint":"\\e1ea"},{"name":"cellular-connection","svgCode":"","codepoint":"\\e1e9"},{"name":"column","svgCode":"","codepoint":"\\e1eb"},{"name":"grid","svgCode":"","codepoint":"\\e1ec"},{"name":"info","svgCode":"","codepoint":"\\e1ed"},{"name":"page","svgCode":"","codepoint":"\\e1ee"},{"name":"image-small","svgCode":"","codepoint":"\\e1ef"},{"name":"flow","svgCode":"","codepoint":"\\e1f0"},{"name":"source","svgCode":"","codepoint":"\\e1f1"},{"name":"column","svgCode":"","codepoint":"\\e1f2"},{"name":"grid","svgCode":"","codepoint":"\\e1f3"},{"name":"display-card","svgCode":"","codepoint":"\\e1fe"},{"name":"display-list","svgCode":"","codepoint":"\\e1ff"},{"name":"links","svgCode":"","codepoint":"\\e201"},{"name":"compoment-count","svgCode":"","codepoint":"\\e202"},{"name":"fc-count","svgCode":"","codepoint":"\\e203"},{"name":"project-count","svgCode":"","codepoint":"\\e204"},{"name":"user-count","svgCode":"","codepoint":"\\e205"},{"name":"project","svgCode":"","codepoint":"\\e206"},{"name":"chanpinjieshao","svgCode":"","codepoint":"\\e207"},{"name":"chuizhijunfen","svgCode":"","codepoint":"\\e208"},{"name":"shuipingjunfen","svgCode":"","codepoint":"\\e20b"},{"name":"shuipingjuzhong","svgCode":"","codepoint":"\\e209"},{"name":"1_diduiqi","svgCode":"","codepoint":"\\e20c"},{"name":"1_dingduiqi","svgCode":"","codepoint":"\\e20a"},{"name":"1_juzhongduiqi","svgCode":"","codepoint":"\\e20f"},{"name":"1_youduiqi","svgCode":"","codepoint":"\\e20e"},{"name":"1_zuoduiqi","svgCode":"","codepoint":"\\e20d"},{"name":"shangxiachengkai","svgCode":"","codepoint":"\\e210"},{"name":"shangxialashen","svgCode":"","codepoint":"\\e211"},{"name":"xiangxiazishiying","svgCode":"","codepoint":"\\e212"},{"name":"youshangjiaolashen","svgCode":"","codepoint":"\\e213"},{"name":"youxiajiaolashen","svgCode":"","codepoint":"\\e214"},{"name":"yuanjiao","svgCode":"","codepoint":"\\e215"},{"name":"zuoyouchengkai","svgCode":"","codepoint":"\\e216"},{"name":"zuoyoulashen","svgCode":"","codepoint":"\\e217"},{"name":"zuoyouchengman","svgCode":"","codepoint":"\\e218"},{"name":"delete-fill","svgCode":"","codepoint":"\\e28a"},{"name":"more","svgCode":"","codepoint":"\\e28b"},{"name":"attention-fill","svgCode":"","codepoint":"\\e289"},{"name":"rengongjiedian","svgCode":"","codepoint":"\\e221"},{"name":"shenpijiedian","svgCode":"","codepoint":"\\e220"},{"name":"huiqianjiedian","svgCode":"","codepoint":"\\e21f"},{"name":"apijiedian","svgCode":"","codepoint":"\\e21e"},{"name":"shujuchulijiedian","svgCode":"","codepoint":"\\e21d"},{"name":"end","svgCode":"","codepoint":"\\e222"},{"name":"parallel","svgCode":"","codepoint":"\\e223"},{"name":"converge","svgCode":"","codepoint":"\\e224"},{"name":"start","svgCode":"","codepoint":"\\e225"},{"name":"danxingwenben","svgCode":"","codepoint":"\\e229"},{"name":"danxuankuang","svgCode":"","codepoint":"\\e22b"},{"name":"danxuanrenyuanxuanze","svgCode":"","codepoint":"\\e22a"},{"name":"danxuanxiala","svgCode":"","codepoint":"\\e22d"},{"name":"duoxuankuang","svgCode":"","codepoint":"\\e22e"},{"name":"duoxuanrenyuanxuanze","svgCode":"","codepoint":"\\e22c"},{"name":"duoxingwenben","svgCode":"","codepoint":"\\e232"},{"name":"duoxuanxiala","svgCode":"","codepoint":"\\e230"},{"name":"fengexian","svgCode":"","codepoint":"\\e22f"},{"name":"fuwenben","svgCode":"","codepoint":"\\e233"},{"name":"keshurudanxuanxiala","svgCode":"","codepoint":"\\e231"},{"name":"lianjie","svgCode":"","codepoint":"\\e234"},{"name":"miaoshuwenben","svgCode":"","codepoint":"\\e236"},{"name":"riqi","svgCode":"","codepoint":"\\e237"},{"name":"shangchuan","svgCode":"","codepoint":"\\e235"},{"name":"shijian","svgCode":"","codepoint":"\\e238"},{"name":"tupian","svgCode":"","codepoint":"\\e239"},{"name":"shuzi","svgCode":"","codepoint":"\\e23a"},{"name":"zidingyibiaoge","svgCode":"","codepoint":"\\e23b"},{"name":"sort","svgCode":"","codepoint":"\\e23c"},{"name":"liebiao","svgCode":"","codepoint":"\\e23d"},{"name":"kapian","svgCode":"","codepoint":"\\e23f"},{"name":"refill","svgCode":"","codepoint":"\\e241"},{"name":"warning-2","svgCode":"","codepoint":"\\e29f"},{"name":"delete","svgCode":"","codepoint":"\\e2a1"},{"name":"xlsx","svgCode":"","codepoint":"\\e247"},{"name":"tree1","svgCode":"","codepoint":"\\e249"},{"name":"2-jiantou-you","svgCode":"","codepoint":"\\e24a"},{"name":"2-jiantou-zuo","svgCode":"","codepoint":"\\e24b"},{"name":"xuanzhongfuji","svgCode":"","codepoint":"\\e24c"},{"name":"qianjin","svgCode":"","codepoint":"\\e24f"},{"name":"undo-2","svgCode":"","codepoint":"\\e250"},{"name":"tubiao","svgCode":"","codepoint":"\\e24e"},{"name":"zuixiaokuandu","svgCode":"","codepoint":"\\e24d"},{"name":"zuidagaodu","svgCode":"","codepoint":"\\e251"},{"name":"juhe","svgCode":"","codepoint":"\\e252"},{"name":"zuidakuandu","svgCode":"","codepoint":"\\e254"},{"name":"ziti","svgCode":"","codepoint":"\\e253"},{"name":"zuixiaogaodu","svgCode":"","codepoint":"\\e255"},{"name":"zizhong","svgCode":"","codepoint":"\\e256"},{"name":"xinggao","svgCode":"","codepoint":"\\e25a"},{"name":"zitiyangshi","svgCode":"","codepoint":"\\e257"},{"name":"xiahuaxian","svgCode":"","codepoint":"\\e259"},{"name":"shanghuaxian","svgCode":"","codepoint":"\\e258"},{"name":"zhonghuaxian","svgCode":"","codepoint":"\\e25b"},{"name":"suojin","svgCode":"","codepoint":"\\e25d"},{"name":"zijianju","svgCode":"","codepoint":"\\e25c"},{"name":"zuoduiqi-2","svgCode":"","codepoint":"\\e260"},{"name":"juzhongduiqi","svgCode":"","codepoint":"\\e25f"},{"name":"youduiqi-2","svgCode":"","codepoint":"\\e261"},{"name":"liangduanduiqi","svgCode":"","codepoint":"\\e25e"},{"name":"dancijianju","svgCode":"","codepoint":"\\e262"},{"name":"zitidaxiao","svgCode":"","codepoint":"\\e263"},{"name":"shujuyuan","svgCode":"","codepoint":"\\e268"},{"name":"zidingyiye","svgCode":"","codepoint":"\\e267"},{"name":"markdown","svgCode":"","codepoint":"\\e265"},{"name":"biaodan","svgCode":"","codepoint":"\\e266"},{"name":"liuchengtidanye","svgCode":"","codepoint":"\\e269"},{"name":"yuanma-2","svgCode":"","codepoint":"\\e26a"},{"name":"shangxiaduicheng","svgCode":"","codepoint":"\\e26c"},{"name":"zuoyouduicheng","svgCode":"","codepoint":"\\e26b"},{"name":"zuobianju","svgCode":"","codepoint":"\\e26f"},{"name":"dingbianju","svgCode":"","codepoint":"\\e272"},{"name":"shubiaozhizhen","svgCode":"","codepoint":"\\e26e"},{"name":"crosshair","svgCode":"","codepoint":"\\e273"},{"name":"text-3","svgCode":"","codepoint":"\\e271"},{"name":"wait","svgCode":"","codepoint":"\\e274"},{"name":"move","svgCode":"","codepoint":"\\e275"},{"name":"help","svgCode":"","codepoint":"\\e270"},{"name":"gaodu","svgCode":"","codepoint":"\\e276"},{"name":"kuandu","svgCode":"","codepoint":"\\e277"},{"name":"xing-2","svgCode":"","codepoint":"\\e27a"},{"name":"lie-2","svgCode":"","codepoint":"\\e279"},{"name":"ziyoubuju","svgCode":"","codepoint":"\\e278"},{"name":"display-3","svgCode":"","codepoint":"\\e27b"},{"name":"morenziti","svgCode":"","codepoint":"\\e27d"},{"name":"xieti","svgCode":"","codepoint":"\\e27c"},{"name":"pointer","svgCode":"","codepoint":"\\e27e"},{"name":"ai","svgCode":"","codepoint":"\\e27f"},{"name":"fasong","svgCode":"","codepoint":"\\e281"},{"name":"qingsao","svgCode":"","codepoint":"\\e282"},{"name":"shangxialashen-2","svgCode":"","codepoint":"\\e283"},{"name":"shangxiajuhe","svgCode":"","codepoint":"\\e284"},{"name":"shanchu-2","svgCode":"","codepoint":"\\e286"},{"name":"tag-3","svgCode":"","codepoint":"\\e28f"},{"name":"liaotianchuang","svgCode":"","codepoint":"\\e290"},{"name":"yinyongchuangjian","svgCode":"","codepoint":"\\e291"},{"name":"yinyong-2","svgCode":"","codepoint":"\\e292"},{"name":"saoba","svgCode":"","codepoint":"\\e294"},{"name":"quanquan","svgCode":"","codepoint":"\\e295"},{"name":"builder","svgCode":"","codepoint":"\\e298"},{"name":"tuopu","svgCode":"","codepoint":"\\e2a2"},{"name":"jiedian","svgCode":"","codepoint":"\\e2a3"},{"name":"rongqi","svgCode":"","codepoint":"\\e2a4"}]} \ No newline at end of file diff --git a/lib/client/src/bk-icon/style.css b/lib/client/src/bk-icon/style.css index 36f8f98b9..9b8a2bd24 100644 --- a/lib/client/src/bk-icon/style.css +++ b/lib/client/src/bk-icon/style.css @@ -467,12 +467,21 @@ url("fonts/iconcool.eot?#iefix") format("embedded-opentype"); .bk-drag-router:before { content: "\e187"; } +.bk-drag-sync-failed:before { + content: "\e29c"; +} .bk-drag-sync-pending:before { content: "\e297"; } +.bk-drag-sync-success:before { + content: "\e29d"; +} .bk-drag-paste:before { content: "\e248"; } +.bk-drag-sync-default:before { + content: "\e29e"; +} .bk-drag-un-full-screen-2:before { content: "\e21c"; } @@ -527,6 +536,15 @@ url("fonts/iconcool.eot?#iefix") format("embedded-opentype"); .bk-drag-freedrag:before { content: "\e199"; } +.bk-drag-abnormal:before { + content: "\e299"; +} +.bk-drag-normal:before { + content: "\e29a"; +} +.bk-drag-unknown:before { + content: "\e29b"; +} .bk-drag-copy-line:before { content: "\e1a9"; } @@ -605,6 +623,9 @@ url("fonts/iconcool.eot?#iefix") format("embedded-opentype"); .bk-drag-dianzan:before { content: "\e28d"; } +.bk-drag-sync-waiting-01:before { + content: "\e2a0"; +} .bk-drag-select-value:before { content: "\e1b2"; } @@ -1004,6 +1025,12 @@ url("fonts/iconcool.eot?#iefix") format("embedded-opentype"); .bk-drag-refill:before { content: "\e241"; } +.bk-drag-warning-2:before { + content: "\e29f"; +} +.bk-drag-delete:before { + content: "\e2a1"; +} .bk-drag-xlsx:before { content: "\e247"; } @@ -1202,3 +1229,12 @@ url("fonts/iconcool.eot?#iefix") format("embedded-opentype"); .bk-drag-builder:before { content: "\e298"; } +.bk-drag-tuopu:before { + content: "\e2a2"; +} +.bk-drag-jiedian:before { + content: "\e2a3"; +} +.bk-drag-rongqi:before { + content: "\e2a4"; +} diff --git a/lib/client/src/common/ai.js b/lib/client/src/common/ai.js index ad55d04c9..00c254d36 100644 --- a/lib/client/src/common/ai.js +++ b/lib/client/src/common/ai.js @@ -102,8 +102,11 @@ export class Ai { // push user prompt this.pushPrompt(message, 'user') // api + const controller = new AbortController() + this.controllers.push(controller) return fetch('/api/ai/chat/stream', { method: 'post', + signal: controller.signal, headers: { 'Content-Type': 'application/json' }, diff --git a/lib/client/src/common/chart-color-sets.js b/lib/client/src/common/chart-color-sets.js index 9e3b27aee..11f2ba60c 100644 --- a/lib/client/src/common/chart-color-sets.js +++ b/lib/client/src/common/chart-color-sets.js @@ -34,6 +34,20 @@ const colorSets = [ '#d2f5a6', '#76f2f2' ] + }, + { + name: 'echartsDef', + list: [ + '#5470c6', + '#91cc75', + '#fac858', + '#ee6666', + '#73c0de', + '#3ba272', + '#fc8452', + '#9a60b4', + '#ea7ccc' + ] } ] diff --git a/lib/client/src/common/constant-en.js b/lib/client/src/common/constant-en.js index 9644f3ec9..5ffc30ab4 100644 --- a/lib/client/src/common/constant-en.js +++ b/lib/client/src/common/constant-en.js @@ -1 +1 @@ -export const FOLD_MENU_ROUTE_LIST = ['new', 'editNocode', 'createTicketPageEdit', 'flowConfig', 'flowAdvancedConfig'] +export const FOLD_MENU_ROUTE_LIST = ['new', 'editNocode', 'createTicketPageEdit', 'flowConfig', 'flowAdvancedConfig', 'flowTplCanvas', 'flowTplConfig'] diff --git a/lib/client/src/common/fully-import.js b/lib/client/src/common/fully-import.js index 040730327..9c54fdcce 100644 --- a/lib/client/src/common/fully-import.js +++ b/lib/client/src/common/fully-import.js @@ -24,15 +24,13 @@ import 'animate.css' // import style import 'bk-magic-vue/dist/bk-magic-vue.min.css' -import bkCharts from '@/components/patch/bkCharts' window.SwiperAnimation = SwiperAnimation -window.register = register +window.swiperRegister = register Vue.use(bkText) Vue.use(bkImage) Vue.use(chart) Vue.use(bkMagicVue) -Vue.use(bkCharts) Vue.use(widgetBkVision) Vue.use(widgetElTable) diff --git a/lib/client/src/common/use-resource-lock.js b/lib/client/src/common/use-resource-lock.js index c36299734..6b95ee305 100644 --- a/lib/client/src/common/use-resource-lock.js +++ b/lib/client/src/common/use-resource-lock.js @@ -2,14 +2,16 @@ import { computed, getCurrentInstance, onBeforeUnmount -} from '@vue/composition-api' +} from 'vue' import { useStore } from '@/store' +import { useRoute } from '@/router' import { bkNotify, bkMessage } from 'bk-magic-vue' let lockNotify = '' export default function () { const store = useStore() + const route = useRoute() const currentInstance = getCurrentInstance() @@ -27,7 +29,7 @@ export default function () { store.dispatch('resourceLock/updateLockInfo', params).then(() => { updateTimer = setTimeout(() => { update(params) - }, 20000) + }, 60000) }) } @@ -35,8 +37,12 @@ export default function () { * @desc 主动释放编辑权 */ const release = (params) => { - Object.assign(params, { activeUser: userInfo.value.username }) - store.dispatch('resourceLock/release', params) + Object.assign(params, { activeUser: userInfo.value.username, projectId: route.params.projectId }) + const data = new FormData() + Object.keys(params).forEach((attr) => { + data.append(attr, params[attr]) + }) + navigator.sendBeacon('/api/resourceLock/release', data) } /** * @desc 个状态下的消息提示 @@ -135,11 +141,11 @@ export default function () { if (render) return lockNotify } - const destroy = (() => { + const destroy = () => { lockNotify && lockNotify.close() lockNotify = null clearTimeout(updateTimer) - }) + } // 间隔更新抢占状态有个定时器,组件卸载时需要去掉 onBeforeUnmount(() => { diff --git a/lib/client/src/common/util.js b/lib/client/src/common/util.js index 0d00bd6a3..9269ddaa6 100644 --- a/lib/client/src/common/util.js +++ b/lib/client/src/common/util.js @@ -398,8 +398,9 @@ export function getWindowHeight () { * * @return {string} uuid */ -export function uuid (len = 5, radix = 16) { - const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') +export function uuid (len = 5, radix = 16, lowercaseOnly = false) { + const allSets = lowercaseOnly ? 'abcdefghijklmnopqrstuvwxyz' : '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + const chars = allSets.split('') const uuid = [] radix = radix || chars.length diff --git a/lib/client/src/components/api/choose-api.vue b/lib/client/src/components/api/choose-api.vue index 452bf6e20..e9181da07 100644 --- a/lib/client/src/components/api/choose-api.vue +++ b/lib/client/src/components/api/choose-api.vue @@ -40,7 +40,7 @@ }" @click="chooseApi(data, node)" > - {{data.name}} + {{data.name}} import { defineComponent - } from '@vue/composition-api' + } from 'vue' export default defineComponent({ props: { diff --git a/lib/client/src/components/api/common/scheme.css b/lib/client/src/components/api/common/scheme.css index 5b82dec50..b7deb033c 100644 --- a/lib/client/src/components/api/common/scheme.css +++ b/lib/client/src/components/api/common/scheme.css @@ -33,6 +33,11 @@ &.close { transform: rotate(-90deg); } + &.disabled { + transform: rotate(-90deg); + cursor: not-allowed; + color: #DCDEE5; + } &.icon-down-shape { margin-right: 4px; } diff --git a/lib/client/src/components/api/common/single-scheme.tsx b/lib/client/src/components/api/common/single-scheme.tsx index 1d92d9082..07b446744 100644 --- a/lib/client/src/components/api/common/single-scheme.tsx +++ b/lib/client/src/components/api/common/single-scheme.tsx @@ -1,7 +1,8 @@ import { getDefaultApiUseScheme, getDefaultApiEditScheme, - API_PARAM_TYPES + API_PARAM_TYPES, + API_PARAM_VALUE_TYPES } from 'shared/api' import './scheme.css' import { @@ -9,7 +10,7 @@ import { toRef, ref, getCurrentInstance -} from '@vue/composition-api' +} from 'vue' import useCustomValidate from '@/hooks/use-custom-validate' export default defineComponent({ @@ -74,7 +75,7 @@ export default defineComponent({ } const customValidateRule = { validator (val) { - const value = copyScheme.value.valueType === 'variable' ? copyScheme.value.code : val + const value = copyScheme.value.valueType === API_PARAM_VALUE_TYPES.VARIABLE ? copyScheme.value.code : val return customValidate(value, copyScheme.value.validate, props.variableList, props.functionList, props.apiList) }, message: window.i18n.t('参数值不符合自定义校验'), @@ -82,6 +83,8 @@ export default defineComponent({ } // 切换是否展示子节点 const toggleShowProperty = () => { + if (copyScheme.value.valueType === API_PARAM_VALUE_TYPES.VARIABLE) return + copyScheme.value.showChildren = !copyScheme.value.showChildren triggerChange() } @@ -178,7 +181,8 @@ export default defineComponent({ API_PARAM_TYPES.BOOLEAN.VAL, API_PARAM_TYPES.NUMBER.VAL, API_PARAM_TYPES.STRING.VAL - ].includes(this.copyScheme.type) + ].includes(this.copyScheme.type), + disabled: this.copyScheme.valueType === API_PARAM_VALUE_TYPES.VARIABLE } ] } @@ -216,7 +220,7 @@ export default defineComponent({ id={option.name} name={option.name} key={option.name} - v-bk-tooltips={{ content: option.comment, disabled: !option.comment, maxWidth: 400 }} + v-bk-tooltips={{ content: option.comment, disabled: !option.comment, maxWidth: 400, allowHTML: false }} > )) @@ -237,7 +241,7 @@ export default defineComponent({ value={this.copyScheme.type} clearable={false} disabled={this.finalTypeDisable} - onChange={(type) => this.updateType(type)} + onSelected={(type) => this.updateType(type)} > { Object.keys(API_PARAM_TYPES).map((key) => ( @@ -288,20 +292,19 @@ export default defineComponent({ { - this.showRule ? - [API_PARAM_TYPES.ARRAY.VAL, API_PARAM_TYPES.OBJECT.VAL].includes(this.copyScheme.type) - ? -- - : this.update({ validate })} - /> - : - '' + this.showRule + ? [API_PARAM_TYPES.ARRAY.VAL, API_PARAM_TYPES.OBJECT.VAL].includes(this.copyScheme.type) + ? -- + : this.update({ validate })} + /> + : '' } this.update({ description })} @@ -317,7 +320,7 @@ export default defineComponent({ [ API_PARAM_TYPES.OBJECT.VAL, API_PARAM_TYPES.ARRAY.VAL - ].includes(this.copyScheme.type) && this.disablePlusBrother + ].includes(this.copyScheme.type) && this.disablePlusBrother && this.copyScheme.valueType !== API_PARAM_VALUE_TYPES.VARIABLE ? + : '' + } + { + [ + API_PARAM_TYPES.OBJECT.VAL, + API_PARAM_TYPES.ARRAY.VAL + ].includes(this.copyScheme.type) && !this.disablePlusBrother && this.copyScheme.valueType !== API_PARAM_VALUE_TYPES.VARIABLE ? @@ -362,7 +376,7 @@ export default defineComponent({ [ API_PARAM_TYPES.OBJECT.VAL, API_PARAM_TYPES.ARRAY.VAL - ].includes(this.copyScheme.type) + ].includes(this.copyScheme.type) && this.copyScheme.valueType !== API_PARAM_VALUE_TYPES.VARIABLE ? import { ref - } from '@vue/composition-api' + } from 'vue' import { API_VALIDATE_TYPES } from 'shared/api' @@ -278,6 +278,13 @@ line-height: 16px; padding: 0 8px; } + /deep/ .bk-checkbox { + line-height: 16px; + &:after { + top: 2px; + left: 5px; + } + } } .plus-rule { cursor: pointer; diff --git a/lib/client/src/components/api/create-api-sideslider/basic.vue b/lib/client/src/components/api/create-api-sideslider/basic.vue index 2fb0ce916..6fb7726fd 100644 --- a/lib/client/src/components/api/create-api-sideslider/basic.vue +++ b/lib/client/src/components/api/create-api-sideslider/basic.vue @@ -131,7 +131,7 @@ defineComponent, ref, onBeforeMount - } from '@vue/composition-api' + } from 'vue' import useForm from './use-form' import { useStore } from '@/store' import { useRoute } from '@/router' diff --git a/lib/client/src/components/api/create-api-sideslider/header.vue b/lib/client/src/components/api/create-api-sideslider/header.vue index 224d49cc4..22ead6bb1 100644 --- a/lib/client/src/components/api/create-api-sideslider/header.vue +++ b/lib/client/src/components/api/create-api-sideslider/header.vue @@ -14,7 +14,7 @@ defineComponent, ref, watch - } from '@vue/composition-api' + } from 'vue' import { getDefaultApiEditScheme } from 'shared/api' diff --git a/lib/client/src/components/api/create-api-sideslider/index.vue b/lib/client/src/components/api/create-api-sideslider/index.vue index 0ad8f8798..9757b9283 100644 --- a/lib/client/src/components/api/create-api-sideslider/index.vue +++ b/lib/client/src/components/api/create-api-sideslider/index.vue @@ -27,7 +27,15 @@ :api-list="apiList" @update="handleUpdate" /> -

{{ $t('默认请求参数') }}

+
+

{{ $t('默认请求参数') }}

+ +
+ import { defineComponent - } from '@vue/composition-api' + } from 'vue' import EditResponseScheme from '@/components/api/edit-scheme/response' import useForm from './use-form' diff --git a/lib/client/src/components/api/create-api-sideslider/use-form.ts b/lib/client/src/components/api/create-api-sideslider/use-form.ts index eb3fbf926..2b1582831 100644 --- a/lib/client/src/components/api/create-api-sideslider/use-form.ts +++ b/lib/client/src/components/api/create-api-sideslider/use-form.ts @@ -1,6 +1,6 @@ import { ref -} from '@vue/composition-api' +} from 'vue' export default (emit) => { const editObjectRef = ref(null) diff --git a/lib/client/src/components/api/edit-scheme/get.vue b/lib/client/src/components/api/edit-scheme/get.vue index 558b59eef..64a2fb88e 100644 --- a/lib/client/src/components/api/edit-scheme/get.vue +++ b/lib/client/src/components/api/edit-scheme/get.vue @@ -39,7 +39,7 @@ watch, computed, getCurrentInstance - } from '@vue/composition-api' + } from 'vue' import SchemeTab from '../common/scheme-tab' import SchemeHeader from '../common/scheme-header' import SingleScheme from '../common/single-scheme' diff --git a/lib/client/src/components/api/edit-scheme/header.vue b/lib/client/src/components/api/edit-scheme/header.vue index a36e69930..7066a1c8c 100644 --- a/lib/client/src/components/api/edit-scheme/header.vue +++ b/lib/client/src/components/api/edit-scheme/header.vue @@ -39,7 +39,7 @@ watch, computed, getCurrentInstance - } from '@vue/composition-api' + } from 'vue' import SchemeTab from '../common/scheme-tab' import SchemeHeader from '../common/scheme-header' import SingleScheme from '../common/single-scheme' diff --git a/lib/client/src/components/api/edit-scheme/post.vue b/lib/client/src/components/api/edit-scheme/post.vue index f0ff05b93..82b85cf36 100644 --- a/lib/client/src/components/api/edit-scheme/post.vue +++ b/lib/client/src/components/api/edit-scheme/post.vue @@ -35,7 +35,7 @@ watch, computed, getCurrentInstance - } from '@vue/composition-api' + } from 'vue' import SchemeTab from '../common/scheme-tab' import SchemeHeader from '../common/scheme-header' import SingleScheme from '../common/single-scheme' diff --git a/lib/client/src/components/api/edit-scheme/response.vue b/lib/client/src/components/api/edit-scheme/response.vue index d1c6fdc8f..9466f4b27 100644 --- a/lib/client/src/components/api/edit-scheme/response.vue +++ b/lib/client/src/components/api/edit-scheme/response.vue @@ -34,7 +34,7 @@ ref, getCurrentInstance, watch - } from '@vue/composition-api' + } from 'vue' import SchemeTab from '../common/scheme-tab' import SchemeHeader from '../common/scheme-header' import SingleScheme from '../common/single-scheme' diff --git a/lib/client/src/components/api/use-scheme/get.vue b/lib/client/src/components/api/use-scheme/get.vue index 06dac5b9c..27b06681e 100644 --- a/lib/client/src/components/api/use-scheme/get.vue +++ b/lib/client/src/components/api/use-scheme/get.vue @@ -44,7 +44,7 @@ watch, computed, getCurrentInstance - } from '@vue/composition-api' + } from 'vue' import SchemeTab from '../common/scheme-tab' import SchemeHeader from '../common/scheme-header' import SingleScheme from '../common/single-scheme' diff --git a/lib/client/src/components/api/use-scheme/header.vue b/lib/client/src/components/api/use-scheme/header.vue index e2b29b574..54bf37af6 100644 --- a/lib/client/src/components/api/use-scheme/header.vue +++ b/lib/client/src/components/api/use-scheme/header.vue @@ -44,7 +44,7 @@ watch, computed, getCurrentInstance - } from '@vue/composition-api' + } from 'vue' import SchemeTab from '../common/scheme-tab' import SchemeHeader from '../common/scheme-header' import SingleScheme from '../common/single-scheme' diff --git a/lib/client/src/components/api/use-scheme/post.vue b/lib/client/src/components/api/use-scheme/post.vue index 9d736871b..427658cd5 100644 --- a/lib/client/src/components/api/use-scheme/post.vue +++ b/lib/client/src/components/api/use-scheme/post.vue @@ -40,7 +40,7 @@ watch, computed, getCurrentInstance - } from '@vue/composition-api' + } from 'vue' import SchemeTab from '../common/scheme-tab' import SchemeHeader from '../common/scheme-header' import SingleScheme from '../common/single-scheme' diff --git a/lib/client/src/components/api/use-scheme/response.vue b/lib/client/src/components/api/use-scheme/response.vue index 4a0f4d93f..f031b9c00 100644 --- a/lib/client/src/components/api/use-scheme/response.vue +++ b/lib/client/src/components/api/use-scheme/response.vue @@ -24,7 +24,7 @@ watch, onMounted, getCurrentInstance - } from '@vue/composition-api' + } from 'vue' import SchemeHeader from '../common/scheme-header' import SingleScheme from '../common/single-scheme' diff --git a/lib/client/src/components/app-header.vue b/lib/client/src/components/app-header.vue index 135b2f44c..153e45bb1 100644 --- a/lib/client/src/components/app-header.vue +++ b/lib/client/src/components/app-header.vue @@ -12,8 +12,9 @@ + + diff --git a/lib/client/src/components/help-docs.vue b/lib/client/src/components/help-docs.vue new file mode 100644 index 000000000..44d7dff89 --- /dev/null +++ b/lib/client/src/components/help-docs.vue @@ -0,0 +1,743 @@ + + + diff --git a/lib/client/src/components/lc-sideslider/index.vue b/lib/client/src/components/lc-sideslider/index.vue index dcc50333f..9bd5d06a2 100644 --- a/lib/client/src/components/lc-sideslider/index.vue +++ b/lib/client/src/components/lc-sideslider/index.vue @@ -68,3 +68,9 @@ } } + + diff --git a/lib/client/src/components/methods/choose-function/index.vue b/lib/client/src/components/methods/choose-function/index.vue index 8e9b8e9a5..e0d85e83b 100644 --- a/lib/client/src/components/methods/choose-function/index.vue +++ b/lib/client/src/components/methods/choose-function/index.vue @@ -75,7 +75,8 @@ disabled: !functionData.funcSummary, placements: ['left-start'], width: 200, - boundary: 'window' + boundary: 'window', + allowHTML: false }" v-for="functionData in funcGroup.children" :class="{ @@ -113,7 +114,8 @@ disabled: !functionData.funcSummary, placements: ['left-start'], width: 200, - boundary: 'window' + boundary: 'window', + allowHTML: false }" :key="functionData.funcName" > @@ -165,7 +167,8 @@ v-bk-tooltips="{ content: paramTips, boundary: 'window', - disabled: !paramTips + disabled: !paramTips, + allowHTML: false }" @change="value => handleInputParam(index, { value })" @blur="triggleUpdate" diff --git a/lib/client/src/components/methods/forms/edit-func-form.vue b/lib/client/src/components/methods/forms/edit-func-form.vue index 713328dcf..988c10610 100644 --- a/lib/client/src/components/methods/forms/edit-func-form.vue +++ b/lib/client/src/components/methods/forms/edit-func-form.vue @@ -33,7 +33,6 @@ > @@ -18,6 +47,7 @@ import LC from '@/element-materials/core' import renderSlot from './render-slot.vue' import { encodeRegexp } from '../../component/utils' + import { isEmpty } from '@/common/util' export default { components: { renderSlot @@ -43,6 +73,58 @@ result[key] = this.config[key] return result }, {}) + }, + slotGroupList () { + if (this.keyword.length) { + return this.filerConfig + } else { + const groups = this.componentNode?.material?.groups || [] + const slotGroups = [] + Object.keys(this.filerConfig).reduce((pre, cur) => { + // 该插槽无法用 值 变量 表达式 + if (this.filerConfig[cur].type.includes('remote')) { + const aGroup = { + value: cur, + label: this.filerConfig[cur]?.displayName, + isDisplay: true, + groupSlots: {} + } + aGroup.groupSlots[cur] = this.filerConfig[cur] + pre.push(aGroup) + } else { + const groupVal = this.filerConfig[cur]?.belongGroup || 'defSlot' + const groupItem = pre.find(item => item.value === groupVal) + if (!pre?.length || !groupItem) { + const aGroup = { + value: groupVal, + label: groupVal === 'defSlot' ? this.filerConfig[cur]?.displayName || '默认插槽' : groups.find(item => item.value === groupVal)?.label, + isDisplay: true, + groupSlots: {} + } + aGroup.groupSlots[cur] = this.filerConfig[cur] + pre.push(aGroup) + } + if (groupItem) { + groupItem.isDisplay ??= true + groupItem.groupSlots ??= {} + groupItem.groupSlots[cur] = this.filerConfig[cur] + } + } + return pre + }, slotGroups) + return slotGroups + } + }, + hasGroupSlot () { + return (group) => { + if (isEmpty(group.groupSlots)) { + return false + } + if (!Object.keys(group.groupSlots).length) { + return false + } + return true + } } }, created () { @@ -103,13 +185,46 @@ }) this.isInnerChange = true this.componentNode.setRenderSlots(slotData, slotName) - }, 60) + }, 60), + toggleShowGroupSlot (group) { + group.isDisplay = !group.isDisplay + this.$forceUpdate() + } } } diff --git a/lib/client/src/element-materials/modifier/component/slots/render-config/vue2/bk/index.js b/lib/client/src/element-materials/modifier/component/slots/render-config/vue2/bk/index.js index 0a2fc9e3e..2c0a39c7c 100644 --- a/lib/client/src/element-materials/modifier/component/slots/render-config/vue2/bk/index.js +++ b/lib/client/src/element-materials/modifier/component/slots/render-config/vue2/bk/index.js @@ -101,6 +101,7 @@ const bkRenderMap = { :align="item.align" :filterable="item.${filterableKey}" :type="item.type" + :status="item.status" ` return ` ` + }, + 'bk-collapse-item' ({ val, valueKeys }) { + const displayVal = getVal(val) + const nameKey = valueKeys?.name || 'name' + const labelKey = valueKeys?.label || 'label' + const contentKey = valueKeys?.content || 'content' + return ` + + {{item.${labelKey}}} +
{{item.${contentKey}}}
+
+ ` } } diff --git a/lib/client/src/element-materials/modifier/component/slots/render-config/vue2/element/index.js b/lib/client/src/element-materials/modifier/component/slots/render-config/vue2/element/index.js index 27fa0708a..bb64b46e1 100644 --- a/lib/client/src/element-materials/modifier/component/slots/render-config/vue2/element/index.js +++ b/lib/client/src/element-materials/modifier/component/slots/render-config/vue2/element/index.js @@ -52,6 +52,7 @@ const elementRenderMap = { :sortable="item.${sortableKey}" :width="item.width" :type="item.type" + :status="item.status" ` return ` @@ -102,7 +102,7 @@ // 选择值类型的字段统一用select组件来做筛选 if (this.isSelectComp(fieldCopy.type)) { fieldCopy.type = ['MULTISELECT', 'CHECKBOX'].includes(fieldCopy.type) ? 'MULTISELECT' : 'SELECT' - fieldCopy.placeholder = `请选择${fieldCopy.name}` + fieldCopy.placeholder = this.$t('请选择{0}', [fieldCopy.name]) } filterFields.push(fieldCopy) localVal[fieldCopy.key] = key in this.value ? cloneDeep(this.value[key]) : '' diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/src/components/flow-form-comp/components/float-workbench-block.vue b/lib/server/project-template/vue3/project-init-code/lib/client/src/components/flow-form-comp/components/float-workbench-block.vue index b1b04c569..2d714735f 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/client/src/components/flow-form-comp/components/float-workbench-block.vue +++ b/lib/server/project-template/vue3/project-init-code/lib/client/src/components/flow-form-comp/components/float-workbench-block.vue @@ -1,7 +1,7 @@ diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/i18n.js b/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/i18n.js new file mode 100644 index 000000000..9cc1ae4ec --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/i18n.js @@ -0,0 +1,73 @@ +import { createI18n } from 'vue-i18n' +import dayjs from 'dayjs' +import { defaultRootConfig } from 'bkui-vue' +import { reactive } from 'vue' +import Cookies from 'js-cookie' + +export const BK_UI_ROOT_CONFIG = defaultRootConfig || reactive({ + locale: null +}) + +// 初始化加载fallbackLocale 语言包,但是图表平台首先加载框架,无需放到框架里面去加载 +const i18n = createI18n({ + fallbackLocale: 'zh-cn', // 设置备用语言 + silentFallbackWarn: true, + silentTranslationWarn: true, + globalInjection: true +}) + +window.i18n = i18n.global + +export async function initLang () { + try { + const lang = getCurLang() + const LangBkUI = await import(`bkui-vue/dist/locale/${lang}.esm`) + BK_UI_ROOT_CONFIG.locale = LangBkUI?.default + const langFile = await import(`./lang/${lang}.json`) + i18n.global.setLocaleMessage(lang, langFile) + i18n.global.locale = lang + dayjs.locale(lang) + return Promise.resolve(true) + } catch (e) { + console.error(e) + return Promise.reject(e) + } +} + +export function getCurLang () { + const coookieLang = Cookies.get('blueking_language') + return coookieLang || 'zh-cn' +} + +export function changeLang (lang) { + const domainList = location.hostname.split('.') + + // 本项目开发环境因为需要配置了 host 域名比联调环境多 1 级 + if (process.env.NODE_ENV === 'development') { + domainList.splice(0, 1) + } + + // 删除已有cookie + for (let i = 0; i < domainList.length - 1; i++) { + Cookies.remove('blueking_language', { + domain: domainList.slice(i).join('.') + }) + } + + // 优先使用环境变量中的bk_domain + let domain = window.BKPAAS_BK_DOMAIN + if (!domain) { + domain = domainList.length > 2 ? domainList.slice(1).join('.') : domainList.join('.') + } + console.log(domain, 'bk_domain:', window.BKPAAS_BK_DOMAIN) + Cookies.set('blueking_language', lang, { + expires: 30, + domain + }) + + initLang() + .finally(() => { + window.location.reload() + }) +} +export default i18n diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/lang/en.json b/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/lang/en.json new file mode 100644 index 000000000..ff6848905 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/lang/en.json @@ -0,0 +1,94 @@ +{ + "去申请": "To Apply", + "已申请": "Already Applied", + "取消": "Cancel", + "该操作需要以下权限": "This operation requires the following permissions", + "需申请的权限": "Permissions to apply for", + "关联的资源实例": "Associated resource instance", + "你已拥有权限,请刷新页面": "You already have permission, please refresh the page", + "开始": "Start", + "中奖逻辑有误,请检查概率数组": "There is an error in the winning logic, please check the probability array", + "提交人": "Submitter", + "提交时间": "Submission Time", + "更新人": "Last modified by", + "更新时间": "Last modified at", + "更多": "More", + "请输入{0}": "Enter {0}", + "查询": "Query", + "重置": "Reset", + "操作": "Actions", + "查看": "Check", + "查看【{0}】详情": "Check [{0}] the Details", + "关闭": "Close", + "表格设置": "Form Settings", + "系统字段": "System Field", + "自定义字段": "Custom Fields", + "确定": "Sure", + "确认删除该条数据": "Are you sure to delete this piece of data?", + "删除成功": "Deleted Successfully", + "查看详情": "Check the Details", + "Sorry,您的权限不足": "Sorry, you have insufficient permissions!", + "页面找不到了": "Page not found!", + "服务器维护中,请稍后重试": "Server maintenance, please try again later!", + "功能正在建设中···": "Feature is under construction...", + "流程有更新未部署,提单或流程执行可能会失败": "There is an update to the flow that has not been deployed, and the bill of lading or flow execution may fail", + "提交": "Submit", + "清空": "Clear", + "暂无数据": "No data", + "提单完成": "Bill of lading completed", + "数据已提交并保存,接下来你可以继续提单": "The data has been submitted and saved, then you can proceed to the bill of lading", + "继续提单": "Continue bill of lading", + "字段【{0}】为必填项": "Field [{0}] is required", + "lesscode 提单": "LessCode bill of lading", + "服务未启用": "Service not enabled", + "当前流程未部署,请部署后提单": "The current flow has not been deployed, please deploy the bill of lading", + "富文本": "Rich Text", + "处理人": "Handler", + "处理时间": "Processing Time", + "请选择{0}": "Select {0}", + "工作台": "Workbench", + "流程总览": "Flow Overview", + "节点数据": "Node Data", + "【{0}】节点": "[{0}] Node", + "详情【{0}】": "Detail [{0}]", + "点击下载": "Click to Download", + "下载": "Download", + "暂无可展示字段,请在节点表单中配置": "There are currently no fields to display, please configure in the node form.", + "创建人": "Created by", + "请输入创建人": "Enter the creator", + "创建时间": "Created at", + "请选择创建时间": "Select a creation time", + "单号": "Ticket No", + "请输入单号": "Enter the ticket No", + "状态": "Status", + "请选择状态": "Select a status", + "详情": "Detail", + "请选择": "Select", + "全屏": "Full-screen", + "自定义表单组件": "Custom form Components", + "自定义表格组件": "Custom table Components", + "点击上传": "Click to Upload", + "上传图片超过最大长度": "The uploaded image exceeds the maximum length", + "图片小于最小长度": "The uploaded image exceeds the maximum length", + "添加": "Add", + "模板下载": "Template Download:", + "找不到该类型控件,请删除字段": "Cannot find this type of control, please delete the field", + "请输入正确格式的数据": "Enter data in the correct format", + "编辑": "Edit", + "删除": "Delete", + "编辑数据": "Edit Data", + "请输入数字": "Enter the number", + "请输入字符串": "Enter a string", + "确定删除": "Confirm Delete", + "请先配置uid": "Please configure uid first", + "sdk 加载异常": "sdk loading exception", + "请输入并按回车键进行搜索": "Enter and press Enter to search", + "请设置正确的计算公式": "Please set the correct calculation formula", + "无法确定表达式的安全性": "Could not determine the safety of the expression", + "表单数据添加成功": "Form data added successfully", + "{0}天": "{0} days", + "{0}分钟": "{0} minutes", + "{0}小时": "{0} hours", + "无法连接到后端服务,请稍候再试": "Unable to connect to backend service, please try again later.", + "页面不存在": "Page does not exist" +} \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/lang/zh-cn.json b/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/lang/zh-cn.json new file mode 100644 index 000000000..7801e78f2 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/src/locales/lang/zh-cn.json @@ -0,0 +1,94 @@ +{ + "去申请": "去申请", + "已申请": "已申请", + "取消": "取消", + "该操作需要以下权限": "该操作需要以下权限", + "需申请的权限": "需申请的权限", + "关联的资源实例": "关联的资源实例", + "你已拥有权限,请刷新页面": "你已拥有权限,请刷新页面", + "开始": "开始", + "中奖逻辑有误,请检查概率数组": "中奖逻辑有误,请检查概率数组", + "提交人": "提交人", + "提交时间": "提交时间", + "更新人": "更新人", + "更新时间": "更新时间", + "更多": "更多", + "请输入{0}": "请输入{0}", + "查询": "查询", + "重置": "重置", + "操作": "操作", + "查看": "查看", + "查看【{0}】详情": "查看【{0}】详情", + "关闭": "关闭", + "表格设置": "表格设置", + "系统字段": "系统字段", + "自定义字段": "自定义字段", + "确定": "确定", + "确认删除该条数据": "确认删除该条数据?", + "删除成功": "删除成功", + "查看详情": "查看详情", + "Sorry,您的权限不足": "Sorry,您的权限不足!", + "页面找不到了": "页面找不到了!", + "服务器维护中,请稍后重试": "服务器维护中,请稍后重试!", + "功能正在建设中···": "功能正在建设中···", + "流程有更新未部署,提单或流程执行可能会失败": "流程有更新未部署,提单或流程执行可能会失败", + "提交": "提交", + "清空": "清空", + "暂无数据": "暂无数据", + "提单完成": "提单完成", + "数据已提交并保存,接下来你可以继续提单": "数据已提交并保存,接下来你可以继续提单", + "继续提单": "继续提单", + "字段【{0}】为必填项": "字段【{0}】为必填项", + "lesscode 提单": "lesscode 提单", + "服务未启用": "服务未启用", + "当前流程未部署,请部署后提单": "当前流程未部署,请部署后提单", + "富文本": "富文本", + "处理人": "处理人", + "处理时间": "处理时间", + "请选择{0}": "请选择{0}", + "工作台": "工作台", + "流程总览": "流程总览", + "节点数据": "节点数据", + "【{0}】节点": "【{0}】节点", + "详情【{0}】": "详情【{0}】", + "点击下载": "点击下载", + "下载": "下载", + "暂无可展示字段,请在节点表单中配置": "暂无可展示字段,请在节点表单中配置。", + "创建人": "创建人", + "请输入创建人": "请输入创建人", + "创建时间": "创建时间", + "请选择创建时间": "请选择创建时间", + "单号": "Ticket No", + "请输入单号": "请输入单号", + "状态": "Status", + "请选择状态": "请选择状态", + "详情": "详情", + "请选择": "请选择", + "全屏": "全屏", + "自定义表单组件": "自定义表单组件", + "自定义表格组件": "自定义表格组件", + "点击上传": "点击上传", + "上传图片超过最大长度": "上传图片超过最大长度", + "图片小于最小长度": "图片小于最小长度", + "添加": "添加", + "模板下载": "模板下载:", + "找不到该类型控件,请删除字段": "找不到该类型控件,请删除字段", + "请输入正确格式的数据": "请输入正确格式的数据", + "编辑": "编辑", + "删除": "删除", + "编辑数据": "编辑数据", + "请输入数字": "请输入数字", + "请输入字符串": "请输入字符串", + "确定删除": "确定删除", + "请先配置uid": "请先配置uid", + "sdk 加载异常": "sdk 加载异常", + "请输入并按回车键进行搜索": "请输入并按回车键进行搜索", + "请设置正确的计算公式": "请设置正确的计算公式", + "无法确定表达式的安全性": "无法确定表达式的安全性", + "表单数据添加成功": "Form data added successfully", + "{0}天": "{0}天", + "{0}分钟": "{0}分钟", + "{0}小时": "{0}小时", + "无法连接到后端服务,请稍候再试": "无法连接到后端服务,请稍候再试。", + "页面不存在": "页面不存在" +} \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/src/main.js b/lib/server/project-template/vue3/project-init-code/lib/client/src/main.js index 5713fe29d..61b7b18c2 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/client/src/main.js +++ b/lib/server/project-template/vue3/project-init-code/lib/client/src/main.js @@ -8,6 +8,7 @@ import { createApp } from 'vue' import App from '@/App' import router from '@/router' import store from '@/store' +import i18n, { initLang } from '@/locales/i18n' import { injectCSRFTokenToHeaders } from '@/api' import auth from '@/common/auth' import Img403 from '@/images/403.png' @@ -17,15 +18,17 @@ import bkui, { bkTooltips, Message } from 'bkui-vue' import 'bkui-vue/dist/style.css' import bkuiVueComplex from '@blueking/bkui-vue-complex' import renderHtml from '@/components/html' -import widgetTableColumn from '@/components/patch/widget-table-column.vue' +import widgetTableColumn from '@/components/patch/widget-table-column.js' import widgetBkTable from '@/components/patch/widget-bk-table.vue' import widgetBkVision from '@/components/patch/widget-bk-vision.vue' import widgetFormContainer from '@/components/form-engine/renderer/index.js' +import widgetFlowContainer from '@/components/flow-manage-container/index.js' import widgetDataManageContainer from '@/components/data-manage-container/form-data-manage/index.js' import pureAxios from '@/api/pureAxios.js' ${importVantLib} import AuthButton from '@/components/auth/button.vue' +${importLuckyCanvas} import cursor from './directives/cursor' auth.requestCurrentUser().then(user => { @@ -35,6 +38,7 @@ auth.requestCurrentUser().then(user => { global.mainComponent = createApp(App) .use(router) .use(store) + .use(i18n) .use(bkui) ${useVantLib} // 挂载全局组件 @@ -44,9 +48,11 @@ auth.requestCurrentUser().then(user => { global.mainComponent.component('widget-table-column', widgetTableColumn) global.mainComponent.component('widget-bk-table', widgetBkTable) global.mainComponent.component('widget-form-container', widgetFormContainer) + global.mainComponent.component('widget-flow-manage-container', widgetFlowContainer) global.mainComponent.component('widget-data-manage-container', widgetDataManageContainer) global.mainComponent.component('auth-button', AuthButton) ${useVantWidget} + ${useLuckyCanvas} // 安装全局插件 global.mainComponent.use(cursor) global.mainComponent.use(bkuiVueComplex) @@ -56,19 +62,22 @@ auth.requestCurrentUser().then(user => { global.mainComponent.config.globalProperties.$http = pureAxios global.mainComponent.config.globalProperties.$bkMessage = Message // mount - global.mainComponent.mount('#app') + initLang() + .finally(() => { + global.mainComponent.mount('#app') + }) } else { auth.redirectToLogin() } }, err => { let message if (err.status === 403) { - message = 'Sorry,您的权限不足!' + message = window.i18n.t('Sorry,您的权限不足') if (err.data && err.data.msg) { message = err.data.msg } } else { - message = '无法连接到后端服务,请稍候再试。' + message = window.i18n.t('无法连接到后端服务,请稍候再试') } const divStyle = '' diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/src/store/index.js b/lib/server/project-template/vue3/project-init-code/lib/client/src/store/index.js index cf99bbf74..18db160ff 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/client/src/store/index.js +++ b/lib/server/project-template/vue3/project-init-code/lib/client/src/store/index.js @@ -73,6 +73,13 @@ export const storeConfig = { }) }, + // 创建流程任务 + createAndExecuteTask ({ state }, flowId) { + return http.post(`/flow/tpl/${flowId}/createAndExecuteTask`).then(response => { + return response + }) + }, + signOut (context) { context.commit('updateUser', {}) } diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/error.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/error.svg new file mode 100644 index 000000000..c07caf86f --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/failed.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/failed.svg new file mode 100644 index 000000000..d34d3e1ea --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/failed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/loading.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/loading.svg new file mode 100644 index 000000000..e15d87bb6 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/loading.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/normal.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/normal.svg new file mode 100644 index 000000000..1ffed3537 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/normal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/pending.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/pending.svg new file mode 100644 index 000000000..4ff187d9f --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/pending.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/success.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/success.svg new file mode 100644 index 000000000..f320716e7 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/unknown.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/unknown.svg new file mode 100644 index 000000000..2cf940fdc --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/unknown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/waiting.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/waiting.svg new file mode 100644 index 000000000..1301bb8cf --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/waiting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/warning.svg b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/warning.svg new file mode 100644 index 000000000..cee3d3497 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/client/static/images/icon/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/conf/iam.js b/lib/server/project-template/vue3/project-init-code/lib/server/conf/iam.js index 6008c853a..e7a2c9790 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/server/conf/iam.js +++ b/lib/server/project-template/vue3/project-init-code/lib/server/conf/iam.js @@ -8,9 +8,9 @@ module.exports = IS_EDV // 权限中心接口 HOST IAM_HOST: '${iamHost}', // 注册到 v3 正式环境的 app id - IAM_APP_ID: '${iamAppId}', + IAM_APP_ID: '', // 注册到 v3 正式环境的 app secret - IAM_APP_SECRET: '${iamAppSecret}', + IAM_APP_SECRET: '', // 注册到权限中心的 system id,即当前 app 在权限中心的 system id IAM_SYSTEM_ID: '${iamSystemId}' } @@ -21,9 +21,9 @@ module.exports = IS_EDV // 权限中心接口 HOST IAM_HOST: '${iamHost}', // 注册到 v3 正式环境的 app id - IAM_APP_ID: '${iamAppId}', + IAM_APP_ID: process.env.BKPAAS_APP_ID, // 注册到 v3 正式环境的 app secret - IAM_APP_SECRET: '${iamAppSecret}', + IAM_APP_SECRET: process.env.BKPAAS_APP_SECRET, // 注册到权限中心的 system id,即当前 app 在权限中心的 system id IAM_SYSTEM_ID: '${iamSystemId}' } diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/controller/data-source.js b/lib/server/project-template/vue3/project-init-code/lib/server/controller/data-source.js index aceccf386..d02edc102 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/server/controller/data-source.js +++ b/lib/server/project-template/vue3/project-init-code/lib/server/controller/data-source.js @@ -21,7 +21,7 @@ import { CookieParams, OutputJson } from '../decorator' -import dataService, { +import { Like } from '../service/data-service' import { @@ -80,6 +80,7 @@ export default class DataSourceController { @HeaderParams({ name: 'x-timezone-offset' }) timezoneOffset, @QueryParams() queryData ) { + const dataService = await getDataService() const { page, pageSize, @@ -142,6 +143,7 @@ export default class DataSourceController { // 入库校验 await validate(formId, data) // 入库 + const dataService = await getDataService() const result = await dataService.add(tableName, transferData(formId, data)) return result } @@ -187,6 +189,7 @@ export default class DataSourceController { ) { // 入库校验 await validate(formId, data) + const dataService = await getDataService() // 入库 const result = await dataService.update(tableName, transferData(formId, data)) return result @@ -216,6 +219,7 @@ export default class DataSourceController { @PathParams({ name: 'tableName', require: true }) tableName, @QueryParams({ name: 'id', require: true }) id ) { + const dataService = await getDataService() const result = await dataService.delete(tableName, id) return result } @@ -237,11 +241,16 @@ export default class DataSourceController { @Post('/user/queryBySql/:thirdPartDBName?') async queryBySql ( @PathParams({ name: 'thirdPartDBName' }) thirdPartDBName, - @BodyParams({ name: 'sql' }) sql, - @BodyParams({ name: 'dataSourceType', default: 'preview' }) dataSourceType, + @BodyParams() body, @CookieParams({ name: global.AUTH_NAME }) bkTicket ) { - const clearSql = decodeSql(sql) + // 解析参数 + const { + sql, + dataSourceType = 'preview', + ...params + } = body + const clearSql = decodeSql(sql, params) querySqlCheck(clearSql) const dataService = await getDataService(thirdPartDBName) const querySqlMap = { diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/controller/flow.js b/lib/server/project-template/vue3/project-init-code/lib/server/controller/flow.js new file mode 100644 index 000000000..c4326f5b9 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/server/controller/flow.js @@ -0,0 +1,379 @@ +/** + * Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available. + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +import dayjs from 'dayjs' +import dataService, { Between } from '../service/data-service' +import { + Controller, + Post, + Get, + BodyParams, + QueryParams, + PathParams, + CookieParams, + SessionParams, + OutputJson +} from '../decorator' + +import { + execApiGateWay +} from '@bkui/apigateway-nodejs-sdk' +import token from '../conf/token' +import apiGateWayConfig from '../conf/apigw' +import { flowTplListMap } from '../../shared/flow-tpl/index' +import { transFlowTplToBkFlowPipelineTree } from '../util/flow/index' + +const spaceId = '${spaceId}' + +// bkflow的预发布环境名称为stage +const bkflowGwStage = apiGateWayConfig.stageName === 'stag' ? 'stage' : apiGateWayConfig.stageName + +@Controller('/api/flow') +export default class FlowController { + // 创建并执行流程任务 + @OutputJson() + @Post('/tpl/:tplId/createAndExecuteTask') + async createAndExecuteTask ( + @SessionParams({ name: 'userInfo' }) userInfo, + @CookieParams({ name: global.AUTH_NAME }) bkToken, + @PathParams({ name: 'tplId', require: true }) tplId + ) { + const flowDetail = flowTplListMap[tplId] + const { nodes, edges, notifyConfig } = flowDetail + const pipelineTree = transFlowTplToBkFlowPipelineTree(nodes, edges) + const taskName = `${flowDetail.name}_${dayjs().format('YYYYMMDDHHmmss')}` + + const bkFlowTaskDetail = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/create_task_without_template`, + method: 'post', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage, + data: { + creator: userInfo.username, + pipeline_tree: pipelineTree, + name: taskName, + notify_config: { + notify_type: notifyConfig.notifyType, + notify_receivers: { + more_receiver:notifyConfig.receivers, + receiver_group: [] + } + }, + scope_type: 'app_env', + scope_value: apiGateWayConfig.stageName + } + }) + if (bkFlowTaskDetail.result) { + const taskData = await dataService.add( + 'flow-task', + { + tplId, + bkFlowTaskId: bkFlowTaskDetail.data.id, + name: taskName, + nodes: JSON.stringify(flowDetail.nodes), + edges: JSON.stringify(flowDetail.edges) + } + ) + await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/task/${bkFlowTaskDetail.data.id}/operate_task/start/`, + method: 'post', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage, + data: { + operator: userInfo.username, + } + }) + return { ...taskData, tplName: flowDetail.name } + } else { + throw new global.BusinessError(`${global.i18n.t('任务创建失败')}: ${bkFlowTaskDetail.message}`, -1) + } + } + + // 获取流程任务列表 + @OutputJson() + @Get('/tpl/:tplId/task/list') + async getFlowListByTpl ( + @CookieParams({ name: global.AUTH_NAME }) bkToken, + @PathParams({ name: 'tplId', require: true }) tplId, + @QueryParams({ name: 'page', default: 1 }) page, + @QueryParams({ name: 'pageSize', default: 10 }) pageSize, + @QueryParams({ name: 'id' }) id, + @QueryParams({ name: 'createAtStart' }) createAtStart, + @QueryParams({ name: 'createAtEnd' }) createAtEnd, + @QueryParams({ name: 'createUser' }) createUser, + ) { + const query = { tplId, deleteFlag: 0 } + + if (id) { + query.id = id + } + + if (createUser) { + query.createUser = createUser + } + + if (createAtStart && createAtEnd) { + query.createTime = Between(createAtStart, createAtEnd) + } + + const { list, count } = await dataService.get({ + tableFileName: 'flow-task', + page, + pageSize, + query + }) + + const taskIds = list.map(item => item.bkFlowTaskId) + + // 调用BkFlow接口查询任务的执行状态 + const tasksStatusRes = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/get_tasks_states/`, + method: 'post', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage, + data: { + task_ids: taskIds + } + }) + + list.forEach(task => { + task.status = tasksStatusRes.data[task.bkFlowTaskId]?.state || '' + }) + + return { list, count } + } + + // 流程任务详情 + @OutputJson() + @Get('/task/:taskId/detail') + async getTaskDetail ( + @CookieParams({ name: global.AUTH_NAME }) bkToken, + @PathParams({ name: 'taskId', require: true }) taskId, + ) { + const runningNodeIds = [] + const taskItem = await dataService.findOne('flow-task', { id: taskId }) + + // 任务执行详情数据 + const taskExecDetail = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/task/${taskItem.bkFlowTaskId}/get_task_states/`, + method: 'get', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage + }) + + if (taskExecDetail.data.state === 'RUNNING') { + const extractIdsOfNodeInExecution = (data) => { + const ids = [] + + function traverse (taskNodes) { + Object.keys(taskNodes).forEach(id => { + const node = taskNodes[id] + if (node.state === 'RUNNING') { + ids.push(node.id) + } + + if (node.children) { + traverse(node.children) + } + }) + } + traverse(data.children) + + return ids + } + + const runningStateNodes = extractIdsOfNodeInExecution(taskExecDetail.data) + + if (runningStateNodes.length > 0) { + // 任务模板详情数据,比较节点id + const taskTplDetail = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/task/${taskItem.bkFlowTaskId}/get_task_detail/`, + method: 'get', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage + }) + + runningStateNodes.forEach(id => { + const node = taskTplDetail.data.pipeline_tree.activities[id] + if (node && node.template_node_id && node.component?.code === 'pause_node') { + runningNodeIds.push(node.template_node_id) + } + }) + } + } + + taskItem.status = taskExecDetail.data.state + taskItem.runningNodeIds = runningNodeIds + + return taskItem + } + + // 流程任务执行状态 + @OutputJson() + @Get('/task/:taskId/states') + async getTaskStates ( + @CookieParams({ name: global.AUTH_NAME }) bkToken, + @PathParams({ name: 'taskId', require: true }) taskId, + ) { + const taskItem = await dataService.findOne('flow-task', { id: taskId }) + + // 任务执行详情数据 + const taskExecDetail = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/task/${taskItem.bkFlowTaskId}/get_task_states/`, + method: 'get', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage + }) + + // 任务模板详情数据 + const taskTplDetail = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/task/${taskItem.bkFlowTaskId}/get_task_detail/`, + method: 'get', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage + }) + + const getBkflowNodeStates = (data) => { + const idMap = {} + + function traverse (taskNodes) { + Object.keys(taskNodes).forEach(id => { + const node = taskTplDetail.data.pipeline_tree.activities[id] + + if (node) { + idMap[node.template_node_id] = { + bkFlowNodeId: id, + state: taskNodes[id].state, + } + } + + if (taskNodes[id].children) { + traverse(taskNodes[id].children) + } + }) + } + traverse(data.children) + + return idMap + } + + const bkflowNodeStates = getBkflowNodeStates(taskExecDetail.data) + + const nodes = JSON.parse(taskItem.nodes || '[]') + + nodes.forEach(node => { + if (node.type === 'Start') { + node.status = 'FINISHED' + } else if (node.type === 'End') { + node.status = taskExecDetail.data.state === 'FINISHED' ? 'FINISHED' : '' + } else { + if (node.id in bkflowNodeStates) { + node.status = bkflowNodeStates[node.id].state + } + } + }) + + taskItem.nodes = JSON.stringify(nodes) + taskItem.status = taskExecDetail.data.state + + return taskItem + } + + // 提交人工节点数据 + @OutputJson() + @Post('/task/:taskId/manual_node/:nodeId/submit/') + async submitManualNodeData ( + @SessionParams({ name: 'userInfo' }) userInfo, + @CookieParams({ name: global.AUTH_NAME }) bkToken, + @PathParams({ name: 'taskId', require: true }) taskId, + @PathParams({ name: 'nodeId', require: true }) nodeId, + @BodyParams() bodyData, + ) { + const nodeCallbackData = {} + let bkFlowNodeId = '' + + Object.keys(bodyData).forEach(key => { + nodeCallbackData[key] = bodyData[key] + }) + + const taskItem = await dataService.findOne('flow-task', { id: taskId }) + + const taskTplDetail = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/task/${taskItem.bkFlowTaskId}/get_task_detail/`, + method: 'get', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage + }) + + Object.values(taskTplDetail.data.pipeline_tree.activities).some(node => { + if (node.template_node_id === nodeId) { + bkFlowNodeId = node.id + return true + } + return false + }) + + if (bkFlowNodeId) { + const res = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/space/${spaceId}/task/${taskItem.bkFlowTaskId}/node/${bkFlowNodeId}/operate_node/callback/`, + method: 'post', + authorization: { + ...token, + [global.AUTH_NAME]: bkToken + }, + stageName: bkflowGwStage, + data: { + operator: userInfo.username, + data: nodeCallbackData + } + }) + if (res.result) { + return res.data + } else { + throw new global.BusinessError(res.message, -1) + } + } else { + throw new global.BusinessError(global.i18n.t('提交失败,流程结构数据异常'), -1) + } + } +} diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/controller/form.js b/lib/server/project-template/vue3/project-init-code/lib/server/controller/form.js index bd81ea9f9..af7617286 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/server/controller/form.js +++ b/lib/server/project-template/vue3/project-init-code/lib/server/controller/form.js @@ -52,10 +52,10 @@ export default class NoCodeController { filterTableDataWithConditions ( @PathParams({ name: 'tableName', require: true }) tableName, @BodyParams({ name: 'conditions' }) conditions, - @BodyParams({ name: 'field' }) field, + @BodyParams({ name: 'fields' }) fields, @BodyParams({ name: 'group' }) group ) { - return filterTableDataWithConditions(conditions, tableName, group, field) + return filterTableDataWithConditions(conditions, tableName, group, fields) } // 代理执行 itsm apigateway 接口 diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/controller/open-api.js b/lib/server/project-template/vue3/project-init-code/lib/server/controller/open-api.js index 019e868b2..914e59e5d 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/server/controller/open-api.js +++ b/lib/server/project-template/vue3/project-init-code/lib/server/controller/open-api.js @@ -103,11 +103,11 @@ export default class OpenApiController { async filterTableDataWithConditions ( @PathParams({ name: 'tableName', require: true }) tableName, @BodyParams({ name: 'conditions' }) conditions, - @BodyParams({ name: 'field' }) field, + @BodyParams({ name: 'fields' }) fields, @BodyParams({ name: 'group' }) group ) { // 获取数据 - return filterTableDataWithConditions(conditions, tableName, group, field) + return filterTableDataWithConditions(conditions, tableName, group, fields) } // api节点在itsm回调 diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/model/entities/flow-task.js b/lib/server/project-template/vue3/project-init-code/lib/server/model/entities/flow-task.js new file mode 100644 index 000000000..93ebe3c27 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/server/model/entities/flow-task.js @@ -0,0 +1,30 @@ +import { Entity, Column } from 'typeorm' +import Base from './base' + +@Entity({ name: 'flow_task', comment: '应用创建的任务' }) +export default class extends Base { + // 关联的流程id + @Column({ type: 'int' }) + tplId + + // 和bkflow关联的任务id + @Column({ type: 'int' }) + bkFlowTaskId + + // 流程任务名称 + @Column({ type: 'varchar', length: 100 }) + name + + // 节点配置,创建任务时的快照 + @Column({ type: 'longtext' }) + nodes + + // 连线配置,创建任务时的快照 + @Column({ type: 'longtext' }) + edges + + // 是否删除,默认值为0,表示未删除,1为已删除 + @Column({ type: 'int' }) + deleteFlag + +} \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/model/migrations/1729578720000-flow-task.js b/lib/server/project-template/vue3/project-init-code/lib/server/model/migrations/1729578720000-flow-task.js new file mode 100644 index 000000000..e4bfcb193 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/server/model/migrations/1729578720000-flow-task.js @@ -0,0 +1,10 @@ +const path = require('path') +const { execSql } = require('../../util') + +export class FlowTask1729578720000 { + async up (queryRunner) { + await execSql(queryRunner, path.resolve(__dirname, './sql/1729578720000-flow-task.sql')) + } + + async down () {} +} \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/model/migrations/sql/1729578720000-flow-task.sql b/lib/server/project-template/vue3/project-init-code/lib/server/model/migrations/sql/1729578720000-flow-task.sql new file mode 100644 index 000000000..7af0d34ba --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/server/model/migrations/sql/1729578720000-flow-task.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS `flow_task`; +CREATE TABLE `flow_task` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `tplId` int(11) DEFAULT NULL COMMENT '流程模板id', + `bkFlowTaskId` int(11) DEFAULT NULL COMMENT '和bkflow关联的任务id', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '任务名称', + `nodes` longtext DEFAULT NULL COMMENT '流程节点快照', + `edges` longtext DEFAULT NULL COMMENT '流程连线快照', + `createTime` datetime(0) DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + `updateTime` datetime(0) DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '最新更新时间', + `createUser` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人,默认当前用户', + `updateUser` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人,默认当前用户', + `deleteFlag` int(11) DEFAULT 0 COMMENT '是否删除,1代表已删除', + PRIMARY KEY (`id`), + INDEX `id`(`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; \ No newline at end of file diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/service/data-service.js b/lib/server/project-template/vue3/project-init-code/lib/server/service/data-service.js index e6f6604b0..c17753a20 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/server/service/data-service.js +++ b/lib/server/project-template/vue3/project-init-code/lib/server/service/data-service.js @@ -261,6 +261,7 @@ export function getDataService (name = 'default', customEntityMap) { * @returns 新增结果 */ async add (tableFileName, data) { + data.id = null const repository = getRepositoryByName(tableFileName) const newData = repository.create(transformData(data, repository.metadata.columns)) try { @@ -278,6 +279,9 @@ export function getDataService (name = 'default', customEntityMap) { * @returns 新增结果 */ async bulkAdd (tableFileName, dataList) { + dataList.forEach(item =>{ + item.id = null + }) const repository = getRepositoryByName(tableFileName) const newDataList = repository.create(transformData(dataList, repository.metadata.columns)) try { @@ -419,7 +423,7 @@ export function getDataService (name = 'default', customEntityMap) { * @param {*} sqls sql语句 */ async execMultSql (sqls) { - const manager = getManager() + const manager = getManager(name) const sqlArr = splitSql(sqls) const res = [] for (const sql of sqlArr) { @@ -436,7 +440,7 @@ export function getDataService (name = 'default', customEntityMap) { * @param {*} sqls sql语句 */ async execSql (sql) { - const manager = getManager() + const manager = getManager(name) const res = await manager.query(sql) return res } diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/service/data-source.js b/lib/server/project-template/vue3/project-init-code/lib/server/service/data-source.js index 295356de7..3890b83da 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/server/service/data-source.js +++ b/lib/server/project-template/vue3/project-init-code/lib/server/service/data-source.js @@ -23,6 +23,7 @@ import DayJSUtcPlugin from 'dayjs/plugin/utc' import thirdPartDBConfs from '../conf/third-part-db' import { EntitySchema, createConnection, EventSubscriber } from 'typeorm' import { RequestContext } from '../middleware/request-context' +import { decode } from 'js-base64' dayjs.extend(DayJSUtcPlugin) @@ -85,21 +86,40 @@ export const querySqlCheck = (sql) => { if (!/;$/.test(sql.trim())) { throw new Error('Sql 语句不完整,需要是【;】号结尾') } + const checkList = [ + { check: (val) => !/^select/.test(val), message: '仅支持 SELECT 查询语句,请修改后再试' }, + { check: (val) => /database\(\)/i.test(val), message: '不允许出现 database() 函数' }, + { check: (val) => /user\(\)/i.test(val), message: '不允许出现 user() 函数' }, + { check: (val) => /version\(\)/i.test(val), message: '不允许出现 version() 函数' }, + { check: (val) => /INFORMATION_SCHEMA/i.test(val), message: '不允许查询 INFORMATION_SCHEMA 相关数据' }, + { check: (val) => /PERFORMANCE_SCHEMA/i.test(val), message: '不允许查询 PERFORMANCE_SCHEMA 相关数据' }, + { check: (val) => /MYSQL\./i.test(val), message: '不允许查询 MYSQL 内置表相关数据' }, + { check: (val) => /\@\@/i.test(val), message: '不允许出现@@符号' } + ] const sqlArr = splitSql(sql) sqlArr.forEach((sqlStr) => { - if (sqlStr && !/^select/.test(sqlStr.trim().toLowerCase())) { - throw new Error('仅支持 SELECT 查询语句,请修改后再试') + const lowerCaseSql = sqlStr.trim().toLowerCase() + if (lowerCaseSql) { + checkList.forEach((checkItem) => { + if (checkItem.check(lowerCaseSql)) { + throw new Error(checkItem.message) + } + }) } }) } // sql 解码 -export const decodeSql = (sql) => { - try { - return Buffer.from(sql, 'base64').toString('utf8') - } catch (error) { - return sql +export const decodeSql = (sql, params) => { + let decodedSql = decode(sql) + const paramKeys = Object.keys(params || {}) + if (paramKeys.length > 0) { + paramKeys.forEach((paramKey) => { + const reg = new RegExp(`\\$\\{${paramKey}\\}`, 'g') + decodedSql = decodedSql.replace(reg, params[paramKey]) + }) } + return decodedSql } @EventSubscriber() diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/service/form.js b/lib/server/project-template/vue3/project-init-code/lib/server/service/form.js index 5ae7fec8c..88542601d 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/server/service/form.js +++ b/lib/server/project-template/vue3/project-init-code/lib/server/service/form.js @@ -97,7 +97,7 @@ export const filterTableDataWithKeys = async (tableName, query, page, pageSize, } // 通过条件过滤数据 -export const filterTableDataWithConditions = async (conditions, tableName, group, field) => { +export const filterTableDataWithConditions = async (conditions, tableName, group, fields) => { const query = parseConditions(conditions) let { list } = await dataService.get({ tableFileName: tableName, @@ -114,10 +114,15 @@ export const filterTableDataWithConditions = async (conditions, tableName, group }) list = groupList } - return list.map((item) => ({ - id: item.id, - [field]: item[field] - })) + return list.map((item) => { + const data = { id: item.id } + fields.forEach(field => { + if (field in item) { + data[field] = item[field] + } + }) + return data + }) } // ApiGateWay 上给 ITSM 授权 @@ -227,6 +232,18 @@ export const grantApiGWPermissionForApps = async () => { ) console.log(`为itsm主动授权完成,appCode为${global.ITSM_APP_CODE}`) + // 应用为自己授权,apigateway自己调用自己也需要授权 + await grantPermissions( + apiName, + { + target_app_code: token.bk_app_code, + grant_dimension: 'api' + }, + token, + 'prod' + ) + console.log('为应用自己授权完成') + // 为bkvision主动授权接口权限 // await grantPermissions( // apiName, diff --git a/lib/server/project-template/vue3/project-init-code/lib/server/util/flow/index.js b/lib/server/project-template/vue3/project-init-code/lib/server/util/flow/index.js new file mode 100644 index 000000000..8afc09804 --- /dev/null +++ b/lib/server/project-template/vue3/project-init-code/lib/server/util/flow/index.js @@ -0,0 +1,178 @@ +import token from '../../conf/token' + +const apiName = '${apiName}' + +// lesscode流程节点类型和BkFlow节点类型映射 +export const NODE_TYPE_MAP = { + Start: 'startpoint', + End: 'endpoint', + Manual: 'tasknode', + DataProcessing: 'tasknode', + API: 'tasknode', + Approval: 'tasknode' +} + +// lesscode流程节点类型和在BkFlow中使用对应的插件映射 +export const NODE_PLUGIN_MAP = { + Manual: 'pause_node', + DataProcessing: 'bk_http_request' +} + +// 将流程模板数据转换为BkFlow需要的pipeline_tree结构 +export const transFlowTplToBkFlowPipelineTree = (nodes, edges) => { + const pipelineTree = { + activities: {}, + canvas_mode: "horizontal", + constants: {}, + end_event: {}, + flows: {}, + gateways: {}, + line: [], + location: [], + outputs: [], + start_event: {} + } + + nodes.forEach(node => { + const { type, id, axis, config } = node + const nodeType = NODE_TYPE_MAP[type] + if (nodeType === 'tasknode') { + // activities + pipelineTree.activities[id] = { + id: id, + name: config.name, + incoming: getNodeIncomingOrOutgoing(id, edges, 'incoming', true), + outgoing: getNodeIncomingOrOutgoing(id, edges, 'outgoing'), + component: getNodeComponentConfig(node), + error_ignorable: false, + loop: null, + optional: true, + stage_name: '', + type: 'ServiceActivity', + retryable: true, + skippable: true, + auto_retry: { enable: false, interval: 0, times: 1 }, + timeout_config: { enable: false, seconds: 10, action: 'forced_fail' }, + labels: [] + } + // 人工节点需要将callback_data勾选为全选变量,在流程上下文中使用 + if (NODE_PLUGIN_MAP[node.type] === 'pause_node') { + pipelineTree.constants[`\${callback_data_${node.id}}`] = { + name: "API回调数据", + key: `\${callback_data_${node.id}}`, + desc: "", + custom_type: "", + source_info: { + [node.id]: ['callback_data'] + }, + source_tag: '', + value: '', + show_type: 'hide', + source_type: 'component_outputs', + validation: '', + index: 0, + plugin_code: '' + } + } + } else if (nodeType === 'startpoint') { + // start_event + pipelineTree.start_event = { + id, + incoming: '', + name: '', + outgoing: getNodeIncomingOrOutgoing(id, edges, 'outgoing'), + type: 'EmptyStartEvent', + labels: [] + } + } else if (nodeType === 'endpoint') { + // end_event + pipelineTree.end_event = { + id, + incoming: getNodeIncomingOrOutgoing(id, edges, 'incoming'), + name: '', + outgoing: '', + type: 'EmptyEndEvent', + labels: [] + } + } + + // location + pipelineTree.location.push({ id, type: nodeType, name: config.name, x: axis.x, y: axis.y }) + }) + + edges.forEach(edge => { + const { id, source, target } = edge + // line + pipelineTree.line.push({ id, source: { id: source.cell, arrow: extractPortDirectionName(source.port) }, target: { id: target.cell, arrow: extractPortDirectionName(target.port) } }) + // flow + pipelineTree.flows[id] = { id, source: source.cell, target: target.cell, is_default: false } + }) + + return pipelineTree +} + +// 获取任务节点component属性配置 +export const getNodeComponentConfig = (node) => { + const pluginName = NODE_PLUGIN_MAP[node.type] + const componentConfig = { + code: pluginName, + data: {}, + version: 'legacy', + } + if (pluginName === 'pause_node') { + componentConfig.data = { + description: { hook: false, need_render: true, value: 'lesscode人工节点' } + } + } else if (pluginName === 'bk_http_request') { + const apiURL = `${process.env.BK_API_URL_TMPL.replace('{api_name}', apiName)}/${process.env.BKPAAS_ENVIRONMENT}/dataManage` + componentConfig.data = { + bk_http_request_method: { hook: false, need_render: true, value: 'POST' }, + bk_http_request_url: { hook: false, need_render: true, value: apiURL }, + bk_http_request_header: { + hook: false, + need_render: true, + value: [ + { + name: 'X-Bkapi-Authorization', + value: JSON.stringify({ + app_code: token.bk_app_code, + app_secret: token.bk_app_secret + }) + } + ] + }, + bk_http_request_body: { hook: false, need_render: true, value: JSON.stringify({ ...node.config, creator: '${_system.operator}' }) }, + bk_http_timeout: { hook: false, need_render: true, value: 5 }, + bk_http_success_exp: { hook: false, need_render: true, value: '' } + } + componentConfig.version = 'v1.0' + } + + return componentConfig +} + +// 提取连接桩方向的名称 +export const extractPortDirectionName = (port) => { + const parts = port.split('_'); + const targetPart = parts[parts.length - 1]; + + // 将首字母转换为大写,其他部分保持不变 + return targetPart.charAt(0).toUpperCase() + targetPart.slice(1); +} + +// 获取节点的入度或出度 +export const getNodeIncomingOrOutgoing = (nodeId, edges, direction, isMultiple = false) => { + const results = [] + edges.forEach(edge => { + const cellId = direction === 'incoming' ? edge.target.cell : edge.source.cell + if (cellId === nodeId) { + results.push(edge.id) + } + }) + + if (results.length > 0) { + return isMultiple ? results : results[0] + } + + return isMultiple ? [] : '' +} diff --git a/lib/server/project-template/vue3/project-init-code/lib/shared/form/validate.js b/lib/server/project-template/vue3/project-init-code/lib/shared/form/validate.js index 9ad1664d7..d66d4736c 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/shared/form/validate.js +++ b/lib/server/project-template/vue3/project-init-code/lib/shared/form/validate.js @@ -28,7 +28,7 @@ const ruleNameMap = { 'PHONE_NUM': '内地手机号码', 'ID_CARD': '身份证', 'IP': 'IP地址', - 'QQ': '腾讯QQ号', + 'QQ': 'QQ号', 'AFTER_DATE': '系统日期之后', 'BEFORE_DATE': '系统日期之前', 'ONLY_NOW_DATE': '系统日期', diff --git a/lib/server/project-template/vue3/project-init-code/lib/shared/util.js b/lib/server/project-template/vue3/project-init-code/lib/shared/util.js index b17e90a0e..6f208176c 100644 --- a/lib/server/project-template/vue3/project-init-code/lib/shared/util.js +++ b/lib/server/project-template/vue3/project-init-code/lib/shared/util.js @@ -115,3 +115,19 @@ export const uuid = (len = 8, radix = 16) => { return uuid.join('') } + +// 判断value字符串是否符合 {{ bk_lesscode_xxx }} 格式 +export const isLesscodeVarStr = (value) => { + return typeof value === 'string' && /^\{\{ bk_lesscode_[^\s]+ \}\}$/.test(value) +} + +// 提取{{ bk_lesscode_xxx }}中的变量名称 +export const getLesscodeVarCode = (value) => { + if (isLesscodeVarStr(value)) { + const matched = value.match(/^\{\{ bk_lesscode_([^\s]+) \}\}$/) + if (matched) { + return matched[1] + } + } + return '' +} diff --git a/lib/server/project-template/vue3/project-init-code/package.json b/lib/server/project-template/vue3/project-init-code/package.json index f5f30f131..a68f09cb6 100644 --- a/lib/server/project-template/vue3/project-init-code/package.json +++ b/lib/server/project-template/vue3/project-init-code/package.json @@ -16,7 +16,6 @@ "NODE_ENV": "production", "APP_CODE": "" } - }, "dev": { "command": "node ./scripts/dev.js", @@ -59,10 +58,41 @@ "type": "git" }, "dependencies": { - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-decorators": "^7.19.3", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/register": "^7.18.9", + "@babel/plugin-proposal-decorators": "7.19.3", + "@babel/plugin-proposal-private-methods": "7.18.6", + "@babel/register": "7.18.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.11", + "@babel/plugin-proposal-class-properties": "7.18.6", + "@babel/runtime": "7.23.9", + "@babel/eslint-parser": "7.23.10", + "@babel/core": "7.23.9", + "@babel/helper-replace-supers": "7.24.1", + "@babel/plugin-transform-private-methods": "7.24.1", + "@babel/code-frame": "7.23.5", + "@babel/generator": "7.23.6", + "@babel/helper-compilation-targets": "7.23.6", + "@babel/helper-module-transforms": "7.23.3", + "@babel/helpers": "7.23.9", + "@babel/parser": "7.23.9", + "@babel/template": "7.23.9", + "@babel/traverse": "7.23.9", + "@babel/types": "7.23.9", + "@babel/helper-create-class-features-plugin": "7.23.10", + "@babel/helper-define-polyfill-provider": "0.5.0", + "@babel/preset-env": "7.23.9", + "@babel/compat-data": "7.23.5", + "@babel/plugin-syntax-decorators": "7.23.3", + "@babel/plugin-transform-async-generator-functions": "7.23.9", + "@babel/plugin-transform-async-to-generator": "7.23.3", + "@babel/plugin-transform-block-scoped-functions": "7.23.3", + "@babel/plugin-transform-block-scoping": "7.23.4", + "@babel/plugin-transform-class-static-block": "7.23.4", + "@babel/plugin-transform-classes": "7.23.8", + "@babel/plugin-transform-runtime": "7.23.9", + "@babel/plugin-transform-class-properties": "7.23.3", + "@babel/helper-remap-async-to-generator": "7.22.20", + "@babel/helper-wrap-function": "7.22.20", + "@babel/plugin-transform-function-name": "7.24.1", "@bkui/apigateway-nodejs-sdk": "0.0.1-beta.32", "@blueking/babel-preset-bk": "2.1.0-beta.9", "@blueking/bk-member-selector": "^0.0.11", @@ -76,13 +106,13 @@ "@typescript-eslint/parser": "^5.40.0", "@vue/eslint-config-typescript": "^7.0.0", "acorn": "~7.2.0", - "ansi_up": "^4.0.4", + "ansi_up": "^5.0.0", "app-root-path": "~3.0.0", - "axios": "~0.19.0", + "axios": "~1.6.0", "babel-eslint": "~10.0.3", "babel-plugin-parameter-decorator": "^1.0.16", "better-npm-run": "~0.1.1", - "bkui-vue": "0.0.1-beta.454", + "bkui-vue": "2.0.1-beta.27", "caniuse-lite": "~1.0.30000974", "chalk": "~2.4.2", "change-case": "~4.1.1", @@ -97,7 +127,6 @@ "eslint": "~7.20.0", "eslint-config-standard": "~12.0.0", "eslint-friendly-formatter": "~4.0.1", - "eslint-loader": "~4.0.2", "eslint-plugin-import": "~2.22.1", "eslint-plugin-node": "~10.0.0", "eslint-plugin-promise": "~4.2.1", @@ -105,6 +134,8 @@ "eslint-plugin-vue": "~7.6.0", "fs-extra": "^8.1.0", "jsonp": "~0.2.1", + "js-base64": "^3.7.7", + "js-cookie": "3.0.1", "koa": "~2.8.2", "koa-body": "^4.2.0", "koa-bodyparser": "~4.2.1", @@ -124,7 +155,7 @@ "nodemon": "~1.19.3", "ora": "~4.0.2", "path-to-regexp": "^6.1.0", - "postcss": "~8.4.16", + "postcss": "~8.4.31", "postcss-import": "^15.0.0", "postcss-mixins": "^9.0.4", "postcss-nested": "^6.0.0", @@ -135,24 +166,55 @@ "prettier": "1.19.1", "query-string": "^7.1.1", "reflect-metadata": "^0.1.13", - "request": "^2.88.2", "shx": "~0.3.2", "swig": "~1.4.2", - "swiper": "11.0.5", "mavon-editor": "^2.9.0", "typeorm": "^0.2.25", "typescript": "^4.4.3", - "url-loader": "~2.2.0", "vue": "~3.2.41", "vue-echarts": "^5.0.0-beta.0", "vue-fullscreen": "^2.6.1", + "vue-i18n": "^9.0.0", "vue-json-viewer": "^2.2.22", "vue-router": "~4.1.6", "vuex": "^4.1.0", - "xlsx": "^0.18.5" + "xlsx": "^0.18.5", + "less": "^4.2.0" + }, + "overrides": { + "@babel/runtime": "7.23.9", + "@babel/eslint-parser": "7.23.10", + "@babel/core": "7.23.9", + "@babel/helper-replace-supers": "7.24.1", + "@babel/plugin-transform-private-methods": "7.24.1", + "@babel/code-frame": "7.23.5", + "@babel/generator": "7.23.6", + "@babel/helper-compilation-targets": "7.23.6", + "@babel/helper-module-transforms": "7.23.3", + "@babel/helpers": "7.23.9", + "@babel/parser": "7.23.9", + "@babel/template": "7.23.9", + "@babel/traverse": "7.23.9", + "@babel/types": "7.23.9", + "@babel/helper-create-class-features-plugin": "7.23.10", + "@babel/helper-define-polyfill-provider": "0.5.0", + "@babel/preset-env": "7.23.9", + "@babel/compat-data": "7.23.5", + "@babel/plugin-syntax-decorators": "7.23.3", + "@babel/plugin-transform-async-generator-functions": "7.23.9", + "@babel/plugin-transform-async-to-generator": "7.23.3", + "@babel/plugin-transform-block-scoped-functions": "7.23.3", + "@babel/plugin-transform-block-scoping": "7.23.4", + "@babel/plugin-transform-class-static-block": "7.23.4", + "@babel/plugin-transform-classes": "7.23.8", + "@babel/plugin-transform-runtime": "7.23.9", + "@babel/plugin-transform-class-properties": "7.23.3", + "@babel/helper-remap-async-to-generator": "7.22.20", + "@babel/helper-wrap-function": "7.22.20", + "@babel/plugin-transform-function-name": "7.24.1" }, "engines": { - "node": ">= 14.16.1", + "node": ">= 16.16.0", "npm": "6.14.15" } -} +} \ No newline at end of file diff --git a/lib/server/router/component-category.js b/lib/server/router/component-category.js index 6bada9081..3359027a1 100644 --- a/lib/server/router/component-category.js +++ b/lib/server/router/component-category.js @@ -8,10 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ - -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -26,35 +23,28 @@ const router = new Router({ prefix: '/api/componentCategory' }) -router.use(['/list'], async (ctx, next) => { - const projectId = ctx.request.query.belongProjectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } +// 只校验projectId +router.use(['/list', '/create', '/sort'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { +// 校验projectId跟资源id +router.use(['/update', '/delete'], async (ctx, next) => { + const tableName = 'COMP_CATEGORY' + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName } ) + if (!projectId) { ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } + code: 500, + message: global.i18n.t('projectId非法'), + data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } - await next() }) router.get('/list', list) diff --git a/lib/server/router/component-favourite.js b/lib/server/router/component-favourite.js index 8bf416f38..63184faa0 100644 --- a/lib/server/router/component-favourite.js +++ b/lib/server/router/component-favourite.js @@ -8,9 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -23,35 +21,9 @@ const router = new Router({ prefix: '/api/componentFavourite' }) -router.use(['/list'], async (ctx, next) => { - const projectId = ctx.request.query?.belongProjectId || ctx.request.query?.projectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } - - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } - await next() +// 只校验projectId +router.use(['/list', '/add', '/delete'], async (ctx, next) => { + await handleProjectPerm(ctx, next) }) router.get('/list', list) diff --git a/lib/server/router/component.js b/lib/server/router/component.js index ab4e50193..5439aafd1 100644 --- a/lib/server/router/component.js +++ b/lib/server/router/component.js @@ -9,9 +9,7 @@ * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -37,35 +35,38 @@ const router = new Router({ prefix: '/api/component' }) -router.use(['/category-count', '/upload', '/list', '/export'], async (ctx, next) => { - const projectId = ctx.request.query.belongProjectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } +// 只校验projectId +router.use(['/list', '/export', '/upload'], async (ctx, next) => { + const projectId = ctx.query.belongProjectId + await handleProjectPerm(ctx, next, projectId) +}) + +// 只校验projectId +router.use(['/using', '/create', '/version-detail'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { +// 校验projectId跟资源id +router.use(['/update', '/updateData', '/off', '/online', 'scope', '/delete', '/page-useing-version'], async (ctx, next) => { + const tableName = 'COMP' + + const resourceId = ['POST', 'PUT'].includes(ctx.request.method) + ? (ctx.request.body?.compId || ctx.request.body?.id) + : (ctx.request.query?.id || ctx.request.query?.compId) + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName, resourceId } ) + if (!projectId) { ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } + code: 500, + message: global.i18n.t('projectId非法'), + data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } - await next() }) router.get('/list', list) diff --git a/lib/server/router/db-upgrade-helper.js b/lib/server/router/db-upgrade-helper.js deleted file mode 100644 index ff268ee1d..000000000 --- a/lib/server/router/db-upgrade-helper.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available. - * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://opensource.org/licenses/MIT - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -const Router = require('koa-router') -const { - updateDialogDev, - updateDialogDataProd, - updateVariableManage, - updateDirectives, - updatePageLifeCycle, - updatePagecode, - setDefaultCompCategory, - setProjectAndPageLayout, - updateProjectLayoutRoutePath, - updateDeledPageRel, - delPageRouteDirtyRow, - updateLayoutInstLayoutCode, - updateSlot, - syncFuncData -} = require('../controller/db-upgrade-helper') - -const router = new Router({ - prefix: '/api/db-upgrade-helper' -}) - -router.get('/20200903_pagecode', updatePagecode) -router.get('/20200907_set_compcategory', setDefaultCompCategory) -router.get('/20200924_set_project_and_page_layout', setProjectAndPageLayout) -router.get('/20200924_update_proj_layout_routepath', updateProjectLayoutRoutePath) -router.get('/20200925_pagecode_and_layoutid', updatePagecode) -router.get('/20200925_update_deled_page_rel', updateDeledPageRel) -router.get('/20200927_del_pageroute_dirty_row', delPageRouteDirtyRow) -router.get('/20200928_update_page_life_cycle', updatePageLifeCycle) -router.get('/20201021_update_layout_inst_code', updateLayoutInstLayoutCode) -router.get('/20210226_update_directives', updateDirectives) -router.get('/20210426_update_variable_manage', updateVariableManage) -router.get('/20210421_update_dialog_data_prod', updateDialogDataProd) -router.get('/20210421_update_dialog_data_dev', updateDialogDev) -router.get('/20210722_update_slot', updateSlot) -router.get('/20220316_sync_func_data', syncFuncData) - -module.exports = router diff --git a/lib/server/router/iam.js b/lib/server/router/iam.js index f6456f198..686520076 100644 --- a/lib/server/router/iam.js +++ b/lib/server/router/iam.js @@ -8,10 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ - -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') @@ -25,38 +22,34 @@ const router = new Router({ prefix: '/api/iam' }) -router.use(['/auth-subject', '/app-perm-model', '/app-perm-model-action', '/check-action'], async (ctx, next) => { - const id = ['POST', 'PUT'].includes(ctx.request.method) - ? (ctx.request.body.id || ctx.request.body.projectId) - : (ctx.request.query.id || ctx.request.query.projectId) - - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, id) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } +// 只校验projectId +router.use(['/auth-subject', '/check-action', '/app-perm-model'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) - // 需要去权限中心查询是否具有 manage_app 权限 - const iamRes = await checkInServer(ctx, String(id), [IAM_ACTION.manage_app[0]]) - // manage_app 无权限 - if (!iamRes[IAM_ACTION.manage_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return +// /app-perm-model-action 增删改查用的是同一个path +router.use(['/app-perm-model-action'], async (ctx, next) => { + // /app-perm-model-action 修改跟删除action用的是同一个path + const methodType = ctx.request.method + if (['DELETE', 'PUT'].includes(methodType)) { + const tableName = 'IAM_APP_PERM_ACTION' + const resourceId = (methodType === 'PUT' ? ctx.request.body.actionId : ctx.request.query.actionId) + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName, resourceId } ) + if (!projectId) { + ctx.send({ + code: 500, + message: global.i18n.t('projectId非法'), + data: null + }) + return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) + } + } else { + await handleProjectPerm(ctx, next) } - await next() }) router.post('/service-check-app-perm', serviceCheckAppPerm) @@ -65,12 +58,13 @@ router.post('/resource-app-perm', fetchResourceAppPerm) router.post('/check', check) router.get('/my-project', myProject) router.post('/check-no-resources', checkNoResources) + +router.post('/check-action', checkIamAppPermActionId) router.post('/auth-subject', fetchAuthSubject) router.get('/app-perm-model', fetchIamAppPerm) router.get('/app-perm-model-action', fetchIamAppPermAction) router.put('/app-perm-model-action', updateIamAppPermAction) router.post('/app-perm-model-action', addIamAppPermAction) router.delete('/app-perm-model-action', deleteIamAppPermAction) -router.post('/check-action', checkIamAppPermActionId) module.exports = router diff --git a/lib/server/router/index.js b/lib/server/router/index.js index 48c2702bd..18b9f076e 100644 --- a/lib/server/router/index.js +++ b/lib/server/router/index.js @@ -25,6 +25,7 @@ const renderParams = { STATIC_URL: '', BK_API_URL_TMPL: httpConf.apiGateWayUrlTmpl, BKPAAS_BK_DOMAIN: process.env.BKPAAS_BK_DOMAIN, + BK_SHARED_RES_URL: process.env.BKPAAS_SHARED_RES_URL, BK_COMPONENT_API_URL: process.env.BK_COMPONENT_API_URL } diff --git a/lib/server/router/layout.js b/lib/server/router/layout.js index 62483a03a..e0feb14a1 100644 --- a/lib/server/router/layout.js +++ b/lib/server/router/layout.js @@ -8,9 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -30,41 +28,40 @@ const router = new Router({ prefix: '/api/layout' }) -router.use(['/getList', 'getFullList'], async (ctx, next) => { - const projectId = ctx.request.query?.belongProjectId || ctx.request.query?.projectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } +// 只校验projectId +router.use(['/getList', '/getFullList', '/create'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) + +// 校验projectId跟资源id +router.use(['/update', '/delete', '/default', '/routePath', '/page'], async (ctx, next) => { + let tableName = 'LAYOUT_INST' + let resourceKey = 'id' - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { + if (ctx.url.startsWith('/api/layout/page')) { + tableName = 'PROJECT_PAGE' + resourceKey = 'pageId' + } + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName,resourceKey } ) + if (!projectId) { ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } + code: 500, + message: global.i18n.t('projectId非法'), + data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } - await next() }) router.get('/getList', getList) router.get('/getFullList', getFullList) router.get('/getPlatformList', getPlatformList) -router.get('/page', getPageLayout) +router.get('/page', getPageLayout) //pageid router.post('/create', create) router.post('/checkName', checkName) router.post('/update', update) diff --git a/lib/server/router/logs.js b/lib/server/router/logs.js index 63468c9d5..bafcc0cc7 100644 --- a/lib/server/router/logs.js +++ b/lib/server/router/logs.js @@ -8,6 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ +import { handleProjectPerm } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -19,7 +20,11 @@ const router = new Router({ prefix: '/api/logs' }) -router.get('/list/:projectId', getList) +router.use(['/list'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) + +router.get('/list', getList) router.get('/options', getOptions) module.exports = router diff --git a/lib/server/router/mock-data.js b/lib/server/router/mock-data.js index 0c278800e..252cee8ce 100644 --- a/lib/server/router/mock-data.js +++ b/lib/server/router/mock-data.js @@ -8,6 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ +import { handleProjectPerm } from '../service/common/project-auth' const { getApiData, getMockData, getXTableData } = require('../controller/mock-data') const Router = require('koa-router') @@ -16,6 +17,15 @@ const router = new Router({ prefix: '/api/data' }) +router.use(['/getApiData'], async (ctx, next) => { + const projectId = ctx.request.body?.projectId + if (projectId) { + await handleProjectPerm(ctx, next, projectId) + } else { + await next() + } +}) + router.post('/getApiData', getApiData) router.get('/getMockData', getMockData) router.post('/postMockData', getMockData) diff --git a/lib/server/router/open-api.config.js b/lib/server/router/open-api.config.js index 158858619..1c9afeb4b 100644 --- a/lib/server/router/open-api.config.js +++ b/lib/server/router/open-api.config.js @@ -24,8 +24,8 @@ module.exports = { prefix: '/api/open', routes: { GET_MY_PROJECT_LIST: ['get', '/get_project_list', getMyProjectList], - GET_PROJECT_TABLES: ['get', '/get_project_tables', getProjectTables], - GET_PROJECT_TABLE_COLS: ['get', '/get_project_table_cols', getProjectTableCols], + GET_PROJECT_TABLES: ['get', '/get_project_tables', getProjectTables, { accessControl: ['project'] }], + GET_PROJECT_TABLE_COLS: ['get', '/get_project_table_cols', getProjectTableCols, { accessControl: ['project'] }], PROJECT_RELEASES: ['get', '/project/releases', getProjectReleases], PROJECT_RELEASE_PACKAGE: ['get', '/project/release/package', getProjectReleasePackage, { accessControl: ['project'] }], FIND_PROJECT_BY_APP: ['get', '/find-project-by-app', getProjectByBindApp], diff --git a/lib/server/router/open-api.js b/lib/server/router/open-api.js index d9f0d8870..4336f793e 100644 --- a/lib/server/router/open-api.js +++ b/lib/server/router/open-api.js @@ -16,6 +16,7 @@ import { checkInServer } from '../controller/iam' import { IAM_ACTION } from '../../shared/constant.js' import { getUserFromApiGW } from '../service/business/open-api' import User from '../model/entities/user' +import { checkProjectPerm } from '../service/common/project-auth' const { addUser } = require('../controller/user') const Router = require('koa-router') @@ -85,39 +86,12 @@ router.use(projectAccessControlRoutes, async (ctx, next) => { ctx.throw(400, ERROR_CODES()['MISSING_PARAMS_PROJECT_ID'][1], { code: ERROR_CODES()['MISSING_PARAMS_PROJECT_ID'][0] }) } - if (global.IAM_ENABLE) { - const project = await iamModel.queryProjectByCreatorAndProjectId(user.username, id) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } - - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(id)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } - } else { - const project = await projectModel.findUserProjectById(user.id, id) - if (!project) { - ctx.throw(403) - } + const res = await checkProjectPerm(ctx, id) + if (res.code > 0) { + ctx.send(res) + return } + await next() }) diff --git a/lib/server/router/operation.js b/lib/server/router/operation.js index bb0393dd0..61435f9f2 100644 --- a/lib/server/router/operation.js +++ b/lib/server/router/operation.js @@ -8,6 +8,8 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ +import { checkNoResourcePerm } from '../service/common/project-auth' +import { IAM_ACTION } from '../../shared/constant.js' const Router = require('koa-router') const { @@ -43,6 +45,16 @@ const router = new Router({ prefix: '/api/operation' }) +// 校验是否有view_operation_data权限 +router.use('*', async (ctx, next) => { + const res = await checkNoResourcePerm(ctx, IAM_ACTION.view_operation_data[0]) + if (res.code > 0) { + ctx.send(res) + return + } + await next() +}) + router.post('/stats/user/base', getUserBaseList) router.post('/stats/user/projectCount', getUserProjectCountList) router.post('/stats/user/pageCount', getUserPageCountList) diff --git a/lib/server/router/page-template-category.js b/lib/server/router/page-template-category.js index 62e7eba71..841e912cd 100644 --- a/lib/server/router/page-template-category.js +++ b/lib/server/router/page-template-category.js @@ -8,9 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -26,34 +24,36 @@ const router = new Router({ }) router.use(['/list'], async (ctx, next) => { - const projectId = ctx.request.query?.belongProjectId || ctx.request.query?.projectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return + const projectId = ctx.query.projectId + if (projectId) { + await handleProjectPerm(ctx, next, projectId) + } else { + await next() } +}) + +// 只校验projectId +router.use(['/create', '/sort'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { +// 校验projectId跟资源id +router.use(['/update', '/delete'], async (ctx, next) => { + const tableName = 'PAGE_TEMPLATE_CATEGORY' + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName } ) + if (!projectId) { ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } + code: 500, + message: global.i18n.t('projectId非法'), + data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } - await next() }) router.get('/list', list) diff --git a/lib/server/router/page-template.js b/lib/server/router/page-template.js index f12dbda9f..ee63ba9dc 100644 --- a/lib/server/router/page-template.js +++ b/lib/server/router/page-template.js @@ -8,9 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -31,52 +29,48 @@ const router = new Router({ prefix: '/api/pageTemplate' }) -router.use(['/list', '/listByCategory', 'getProjectFuncAndVar', 'categoryCount'], async (ctx, next) => { - const projectId = ctx.request.query?.belongProjectId || ctx.request.query?.projectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - - if (ctx.url.startsWith('/api/pageTemplate/list') && !projectId) { +// 这两个api比较特殊, 当获取的是公开模板时, 不需要鉴权 +router.use(['/list', 'listByCategory'], async (ctx, next) => { + const query = ctx.request.query + if (query.type === 'OFFCIAL') { await next() - return - } - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return + } else { + await handleProjectPerm(ctx, next) } +}) - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { +router.use(['/create', '/categoryCount', '/import'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) + +router.use(['/update', '/delete'], async (ctx, next) => { + const tableName = 'PAGE_TEMPLATE' + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName } ) + if (!projectId) { ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } + code: 500, + message: global.i18n.t('projectId非法'), + data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } - await next() }) router.get('/list', list) router.get('/listByCategory', listByCategory) -router.get('/getProjectFuncAndVar', getProjectFuncAndVar) router.post('/create', create) router.post('/update', update) -router.post('/apply', apply) router.post('/import', importTemplate) router.get('/detail', detail) router.post('/checkIsExist', checkIsExist) router.delete('/delete', deleteTemplate) router.get('/categoryCount', categoryCount) +router.post('/apply', apply) +router.get('/getProjectFuncAndVar', getProjectFuncAndVar) module.exports = router diff --git a/lib/server/router/page.js b/lib/server/router/page.js index 4bc980815..27c787b07 100644 --- a/lib/server/router/page.js +++ b/lib/server/router/page.js @@ -9,10 +9,7 @@ * specific language governing permissions and limitations under the License. */ -// import PageModel from '../model/page' -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') @@ -33,79 +30,31 @@ const { getDeletePageCodes } = require('../controller/page') -const { - syncPageData, - fixPageData -} = require('../controller/db-migration-helper') - const router = new Router({ prefix: '/api/page' }) -router.use(['/getList'], async (ctx, next) => { - const projectId = ctx.request.query.projectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } +router.use(['/getList', '/create', '/getDeletePageCodes'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { +const hasPageIdRoutes = ['/update', '/copy', '/delete', '/detail', '/updatePageActive', '/occupyPage'] +router.use(hasPageIdRoutes, async (ctx, next) => { + const tableName = 'PROJECT_PAGE' + const resourceKey = 'pageId' + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName, resourceKey }) + if (!projectId) { ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } + code: 500, + message: global.i18n.t('projectId非法'), + data: null }) return - } - await next() -}) - -// 访问控制 -router.use(['/update', '/copy', '/delete', '/detail', 'getDeletePageCodes'], async (ctx, next) => { - let pageId = ['POST', 'PUT'].includes(ctx.request.method) - ? (ctx.request.body.id || ctx.request.body.pageId) - : (ctx.request.query.id || ctx.request.query.pageId) - - pageId = pageId || (ctx.request.body.pageData || {}).id - if (pageId) { - const project = await iamModel.queryProjectByCreatorAndPageId(ctx.session.userInfo.username, pageId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('页面不存在'), - data: null - }) - return - } - - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(project.id)) - // deploy_app 无权限 - if (!iamRes[IAM_ACTION.deploy_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: iamRes, - permissionType: 'page' - }) - return - } await next() } else { - ctx.throw(400) + await handleProjectPerm(ctx, next, projectId) } }) @@ -122,8 +71,6 @@ router.post('/updatePageActive', updatePageActive) router.post('/occupyPage', occupyPage) router.post('/releasePage', releasePage) router.get('/previewimg', getPreviewImg) -router.get('/syncPageData', syncPageData) -router.get('/fixPageData', fixPageData) router.get('/getDeletePageCodes', getDeletePageCodes) module.exports = router diff --git a/lib/server/router/project-code.js b/lib/server/router/project-code.js index ac21c835e..7eee91938 100644 --- a/lib/server/router/project-code.js +++ b/lib/server/router/project-code.js @@ -8,10 +8,8 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ - -import iamModel from '../model/iam' -import { checkInServer, officalProject } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm } from '../service/common/project-auth' +import { officalProject } from '../controller/iam' const { downloadCode, previewCode } = require('../controller/project-code') const Router = require('koa-router') @@ -20,45 +18,19 @@ const router = new Router({ prefix: '/api/projectCode' }) -router.use(['/downloadCode', '/previewCode'], async (ctx, next) => { - const id = ['POST', 'PUT', 'DELETE'].includes(ctx.request.method) - ? (ctx.request.body.id || ctx.request.body.projectId) - : (ctx.request.query.id || ctx.request.query.projectId) - - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, id) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } +// 预览、下载官方应用不用鉴权 +router.use('*', async (ctx, next) => { + const projectId = String(ctx.request.query?.projectId) const officalProjectList = await officalProject(ctx) const officalProjectIdList = officalProjectList.map(item => String(item.id)) - + // 应用模板 预览、下载源码 不需要鉴权了 - if (officalProjectIdList.indexOf(id) < 0) { - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(id)) - // develop_app 均无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } + if (officalProjectIdList.indexOf(projectId) < 0) { + await handleProjectPerm(ctx, next, projectId) + } else { + await next() } - - await next() }) router.get('/downloadCode', downloadCode) diff --git a/lib/server/router/project-version.js b/lib/server/router/project-version.js index 8328ed319..330afed2d 100644 --- a/lib/server/router/project-version.js +++ b/lib/server/router/project-version.js @@ -9,9 +9,7 @@ * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') @@ -28,83 +26,33 @@ const router = new Router({ prefix: '/api/projectVersion' }) -// /list 接口在部署页面也会用到,因此这个接口 develop_app, deploy_app 两种权限有一个即可访问 +// 这个api有develop或deploy权限均可 router.use(['/list'], async (ctx, next) => { - const id = ['POST', 'PUT', 'DELETE'].includes(ctx.request.method) - ? (ctx.request.body.projectId || ctx.request.body.id) - : (ctx.request.query.projectId || ctx.request.query.id) - // const project = await projectModel.findUserProjectById(ctx.session.userInfo.id, id) - // if (!project) { - // ctx.throw(403) - // } - // await next() - - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, id) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } - - // 需要去权限中心查询是否具有 deploy_app 权限 - const iamRes = await checkInServer(ctx, String(id)) - // develop_app, deploy_app 均无权限 - if (!iamRes[IAM_ACTION.develop_app[0]] && !iamRes[IAM_ACTION.deploy_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } - await next() + await handleProjectPerm(ctx, next, 0, ['develop_app', 'deploy_app']) }) -router.use(['/create', '/update', '/recover', '/archive'], async (ctx, next) => { - const id = ['POST', 'PUT', 'DELETE'].includes(ctx.request.method) - ? (ctx.request.body.projectId || ctx.request.body.id) - : (ctx.request.query.projectId || ctx.request.query.id) - // const project = await projectModel.findUserProjectById(ctx.session.userInfo.id, id) - // if (!project) { - // ctx.throw(403) - // } - // await next() +// 只校验projectid +router.use(['/create', '/optionList'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, id) - if (!project) { +// 校验projectId跟资源id +router.use(['/update', '/recover', '/archive'], async (ctx, next) => { + const tableName = 'PROJECT_VERSION' + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName } ) + if (!projectId) { ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), + code: 500, + message: global.i18n.t('projectId非法'), data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } - - // 需要去权限中心查询是否具有 deploy_app 权限 - const iamRes = await checkInServer(ctx, String(id)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } - await next() }) router.post('/create', create) diff --git a/lib/server/router/project.js b/lib/server/router/project.js index 232fe92be..6369710c4 100644 --- a/lib/server/router/project.js +++ b/lib/server/router/project.js @@ -8,11 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ - -import iamModel from '../model/iam' -import projectModel from '../model/project' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm } from '../service/common/project-auth' const Router = require('koa-router') @@ -35,80 +31,11 @@ const router = new Router({ prefix: '/api/project' }) -// /detail 接口在部署页面也会用到,因此这个接口 develop_app, deploy_app 两种权限有一个即可访问 -router.use(['/detail'], async (ctx, next) => { - const id = ['POST', 'PUT', 'DELETE'].includes(ctx.request.method) - ? (ctx.request.body.id || ctx.request.body.projectId) - : (ctx.request.query.id || ctx.request.query.projectId) - - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, id) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } - - // 需要去权限中心查询是否具有 develop_app, deploy_app, manage_perms_in_app, manage_app 权限 - const iamRes = await checkInServer(ctx, String(id)) - // develop_app, deploy_app 均无权限 - if (!iamRes[IAM_ACTION.develop_app[0]] && !iamRes[IAM_ACTION.deploy_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } - await next() -}) - -router.use(['/update', '/delete', '/favorite', '/delete', '/export'], async (ctx, next) => { - const id = ['POST', 'PUT', 'DELETE'].includes(ctx.request.method) - ? (ctx.request.body.id || ctx.request.body.projectId) - : (ctx.request.query.id || ctx.request.query.projectId) - - if (global.IAM_ENABLE) { - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, id, false) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } - - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(id)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } - } else { - const project = await projectModel.findUserProjectById(ctx.session.userInfo?.id, id) - if (!project) { - ctx.throw(403) - } - } - await next() +router.use(['/detail', '/update', '/delete', '/favorite', '/delete', '/export', '/previewimg'], async (ctx, next) => { + const projectId = ['POST', 'PUT', 'DELETE'].includes(ctx.request.method) + ? (ctx.request.body?.id || ctx.request.body?.projectId) + : (ctx.request.query?.id || ctx.request.query?.projectId) + await handleProjectPerm(ctx, next, projectId) }) router.post('/create', createProject) diff --git a/lib/server/router/release.js b/lib/server/router/release.js index 1bf5590f9..bb5a6f625 100644 --- a/lib/server/router/release.js +++ b/lib/server/router/release.js @@ -8,10 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ - -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm } from '../service/common/project-auth' const { checkConfig, @@ -39,53 +36,22 @@ const router = new Router({ prefix: '/api/release' }) -router.use(['/releaseProject', '/offlineProject', '/updateReleaseVersion', '/getProjectVersionOptionList', '/getSucVersionList', '/detailInfo', '/detailFromV3', '/getList'], async (ctx, next) => { - let id = ['POST', 'PUT', 'DELETE'].includes(ctx.request.method) - ? (ctx.request.body.id || ctx.request.body.projectId) - : (ctx.request.query.id || ctx.request.query.projectId) - if (ctx.request.url === '/api/release/releaseProject') { - id = ctx.request.body?.releaseForm?.projectId - } - - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, id) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } - - // 需要去权限中心查询是否具有 deploy_app 权限 - const iamRes = await checkInServer(ctx, String(id)) - // deploy_app 无权限 - if (!iamRes[IAM_ACTION.deploy_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } - await next() +router.use('*', async (ctx, next) => { + // const excludeRouters = ['/checkConfig', '/applicationList', '/moduleList', '/getRunningLog'] + await handleProjectPerm(ctx, next, ctx.request.headers['x-project-id'], ['deploy_app']) }) router.get('/checkConfig', checkConfig) router.get('/applicationList', applicationList) router.get('/moduleList', moduleList) +router.get('/getRunningLog', getRunningLog) + router.post('/releaseProject', releaseProject) router.get('/getList', getList) router.get('/getAppDetail', getAppDetail) router.post('/detailInfo', detailInfo) router.get('/getSucVersionList', getSucVersionList) router.get('/getProjectVersionOptionList', getProjectVersionOptionList) -router.get('/getRunningLog', getRunningLog) router.get('/detailFromV3', detailFromV3) router.put('/updateReleaseVersion', updateReleaseVersion) router.post('/checkAppInfoExist', checkAppInfoExist) diff --git a/lib/server/router/route.js b/lib/server/router/route.js index 3315e7eec..379709fe7 100644 --- a/lib/server/router/route.js +++ b/lib/server/router/route.js @@ -8,9 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -29,47 +27,70 @@ const router = new Router({ prefix: '/api/route' }) -router.use(['/query/:projectId', '/project/:projectId/page', '/project/:id', '/project/:id/tree'], async (ctx, next) => { - const id = ctx.params.projectId || ctx.params.id +// 只校验projectId +const checkList = ['/query/:projectId', '/project/:projectId/page', '/project/:id', '/project/:id/tree', '/bind-page-route'] +router.use(checkList, async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) + +// 校验projectId跟资源id +router.use(['/save-page-route', '/remove'], async (ctx, next) => { + let tableName = 'PAGE_ROUTE' + let resourceKey = 'id' + let resourceId = ctx.request.body?.pageRoute?.id - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, id) - if (!project) { + if (ctx.url.startsWith('/api/route/remove')) { + resourceKey = 'routeId' + resourceId = ctx.query.id + } + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName, resourceKey, resourceId } ) + if (!projectId) { ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), + code: 500, + message: global.i18n.t('projectId非法'), data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } +}) - // 需要去权限中心查询是否具有 deploy_app 权限 - const iamRes = await checkInServer(ctx, String(id)) - // deploy_app 无权限 - if (!iamRes[IAM_ACTION.deploy_app[0]]) { +// 校验projectId跟资源id +router.use(['/get-page-route/:pageId', '/update-page-route/:pageId'], async (ctx, next) => { + const tableName = 'PROJECT_PAGE' + const resourceKey = 'pageId' + const resourceId = ctx.request.body?.pageId || ctx.params?.pageId + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName, resourceId, resourceKey } ) + if (!projectId) { ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } + code: 500, + message: global.i18n.t('projectId非法'), + data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } - await next() }) router.get('/query/:projectId', queryProjectPageRoute) router.get('/project/:projectId/page', queryProjectPageRoute) router.get('/project/:id', getProjectRoute) router.get('/project/:id/tree', getProjectRouteTree) -router.get('/page/:id', getPageRoute) -router.put('/page/:id?', savePageRoute) router.post('/project/:id', createProjectRoute) -router.post('/page-route/:id', updatePageRoute) -router.post('/bind', bindPageRoute) + +router.get('/get-page-route/:pageId', getPageRoute) +router.put('/save-page-route/:pageId?', savePageRoute) +router.post('/update-page-route/:pageId', updatePageRoute) +router.post('/bind-page-route', bindPageRoute) + router.delete('/remove', removeRoute) module.exports = router diff --git a/lib/server/router/user.js b/lib/server/router/user.js index f36cd144b..a291f9676 100644 --- a/lib/server/router/user.js +++ b/lib/server/router/user.js @@ -8,9 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm } from '../service/common/project-auth' const Router = require('koa-router') const { @@ -27,35 +25,12 @@ const router = new Router({ prefix: '/api/user' }) -router.use(['/getMember'], async (ctx, next) => { - const projectId = ctx.request.query?.belongProjectId || ctx.request.query?.projectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return +router.use('*', async (ctx, next) => { + if (ctx.url === '/api/user/userinfo') { + await next() + } else { + await handleProjectPerm(ctx, next) } - - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { - ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } - }) - return - } - await next() }) router.get('/userinfo', getUserInfo) diff --git a/lib/server/router/variable.js b/lib/server/router/variable.js index 61922f21e..8a8acbb6e 100644 --- a/lib/server/router/variable.js +++ b/lib/server/router/variable.js @@ -8,9 +8,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -import iamModel from '../model/iam' -import { checkInServer } from '../controller/iam' -import { IAM_ACTION } from '../../shared/constant.js' +import { handleProjectPerm, findRealProjectId } from '../service/common/project-auth' const Router = require('koa-router') const { getAllVariable, getFunctionVariable, addVariable, editVariable, deleteVariable, uploadImage, updatePageBuildInVariable } = require('../controller/variable') @@ -19,35 +17,28 @@ const router = new Router({ prefix: '/api/variable' }) -router.use(['/getAllVariable'], async (ctx, next) => { - const projectId = ctx.request.query?.belongProjectId || ctx.request.query?.projectId - const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) - if (!project) { - ctx.send({ - code: 404, - message: global.i18n.t('项目不存在'), - data: null - }) - return - } +// 只校验projectId +router.use(['/getAllVariable', '/addVariable', '/uploadImage', '/updatePageBuildInVariable', '/getFunctionVariable'], async (ctx, next) => { + await handleProjectPerm(ctx, next) +}) - // 需要去权限中心查询是否具有 develop_app 权限 - const iamRes = await checkInServer(ctx, String(projectId)) - // develop_app 无权限 - if (!iamRes[IAM_ACTION.develop_app[0]]) { +// 校验projectId跟资源id +router.use(['/editVariable', '/deleteVariable'], async (ctx, next) => { + const tableName = 'VARIABLE' + + // 校验header获参数中获传过来的projceId,与根据传过来的资源id反差出来的projectId是否一致 + const projectId = await findRealProjectId(ctx, { tableName } ) + if (!projectId) { ctx.send({ - code: 403, - message: global.i18n.t('没有权限,请去权限中心申请权限'), - data: { - pass: false, - applyUrl: iamRes.applyUrl, - requiredPermissions: iamRes.requiredPermissions, - permissionType: 'page' - } + code: 500, + message: global.i18n.t('projectId非法'), + data: null }) return + await next() + } else { + await handleProjectPerm(ctx, next, projectId) } - await next() }) router.get('/getAllVariable', getAllVariable) diff --git a/lib/server/router/vue-code.js b/lib/server/router/vue-code.js index 56f690da2..5f3769e67 100644 --- a/lib/server/router/vue-code.js +++ b/lib/server/router/vue-code.js @@ -8,15 +8,15 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ +import { handleProjectPerm } from '../service/common/project-auth' -const { saveAsFile, formatCode, getPageCode } = require('../controller/vue-code') +const { formatCode, getPageCode } = require('../controller/vue-code') const Router = require('koa-router') const router = new Router({ prefix: '/api/vueCode' }) -router.post('/saveAsFile', saveAsFile) router.post('/formatCode', formatCode) router.post('/getPageCode', getPageCode) diff --git a/lib/server/service/business/ai.js b/lib/server/service/business/ai.js index 5db1fdcaf..f32056136 100644 --- a/lib/server/service/business/ai.js +++ b/lib/server/service/business/ai.js @@ -1,56 +1,6 @@ export const layoutPrompt = () => { - const firstTableId = 'jdhr1' const tableId = 'hg74b' return [ - { - role: 'user', - content: 'help me solve this task:\n帮我新增一个表格' - }, - { - role: 'assistant', - content: [ - '好的,我们需要创建一个表格组件,我看看我们有哪些适合的组件', - '```', - 'cmd: ', - '```' - ].join('\n') - }, - { - role: 'user', - content: [ - '# cmd', - 'You can use these component type:', - '- widget-bk-table (表格)', - '- render-column (列)' - ].join('\n') - }, - { - role: 'assistant', - content: [ - '看起来我们有很多适合的组件可以用来创建表格,比如widget-bk-table、render-column等。由于您没有特殊指明需要用哪个组件,我将默认采用第一种组件,现在将使用默认配置新增并选中表格组件(widget-bk-table)', - '```', - `cmd: `, - `cmd: `, - '```' - ].join('\n') - }, - { - role: 'user', - content: [ - '# cmd', - 'Inserted and selected', - 'Have you finished the task? If so, call `done()`. Otherwise please continue."' - ].join('\n') - }, - { - role: 'assistant', - content: [ - `任务已完成,已为您新增了表格组件(table-${firstTableId})`, - '```', - 'cmd: ', - '```' - ].join('\n') - }, { role: 'user', content: 'help me solve this task:\n给按钮组件(button-xyz12)的点击事件绑定函数 handleClick' @@ -58,7 +8,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '好的,我现在给按钮点击事件绑定 handleClick 函数', + '按钮点击事件绑定 handleClick 函数', '```', 'cmd: ', '```' @@ -74,7 +24,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '看起来函数库并不存在handleClick函数。你可以尝试说“查询函数库”后再执行该任务', + '函数库并不存在handleClick函数。你可以尝试说“查询函数库”后再执行该任务', '```', 'cmd: ', '```' @@ -87,7 +37,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '好的,首先我们需要新增或者更新 handleButtonXyz12Click 函数,函数内部执行"打印“我被点击了”"。然后将 handleButtonXyz12Click 函数绑定到按钮组件(button-xyz12)的点击事件上', + '先新增或者更新 handleButtonXyz12Click 函数。然后将 handleButtonXyz12Click 函数绑定到按钮组件(button-xyz12)的点击事件上', '```', 'cmd: ', 'cmd: ', @@ -118,7 +68,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '好的,首先我们需要创建一个表格组件,用来展示page表的所有数据,我看看我们有哪些适合的组件', + '先查找所有可用的组件', '```', 'cmd: ', '```' @@ -135,7 +85,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '看起来我们有表格组件(widget-bk-table)来展示page表的数据,现在新增并选中表格组件(widget-bk-table),并设置表格组件(widget-bk-table)展示page表的数据', + '可以使用表格组件(widget-bk-table),现在新增并选中表格组件,并让它展示page表的数据', '```', `cmd: `, `cmd: `, @@ -167,7 +117,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '好的,对于修改表格组件(table-3f357)的表头名称,我们需要首先获取其配置信息以了解如何设置', + '先获取组件配置信息以了解如何设置', '```', 'cmd: ', '```' @@ -184,7 +134,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '好的,我们需要修改table-3f357的default slot,将第一列的"label"值由"第一列"修改为"234"', + '需要修改table-3f357的default slot,将第一列的"label"值由"第一列"修改为"234"', '```', 'cmd: ', '```' @@ -214,7 +164,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '好的,我们需要修改按钮组件(button-75ke8)的 title 属性为234,首先获取button-75ke8的配置信息以了解如何设置', + '先获取组件配置信息以了解如何设置', '```', 'cmd: ', '```' @@ -231,7 +181,7 @@ export const layoutPrompt = () => { { role: 'assistant', content: [ - '好的,我们可以看到 title 是 renderProps 中的一个属性,我们可以通过修改这个属性来改变按钮的标题。现在设置 button-75ke8 的 title 属性为 234。', + '现在设置 button-75ke8 的 title 属性为 234。', '```', 'cmd: ', '```' diff --git a/lib/server/service/business/data-source.js b/lib/server/service/business/data-source.js index 816acc434..6d6e18021 100644 --- a/lib/server/service/business/data-source.js +++ b/lib/server/service/business/data-source.js @@ -2,7 +2,8 @@ import { validateData } from '../../../shared/no-code/validate' import { - isEmpty + isEmpty, + decodeBase64 } from '../../../shared/util' import { LCDataService, @@ -74,13 +75,21 @@ export const transferTimeByTimezoneOffset = (columns, data, timezoneOffset) => { if (column.type === 'datetime') { transferList.forEach((transferItem) => { // null 时间不进行转换 - if (transferItem[name] && timezoneOffset) { + if (transferItem[name] && timezoneOffset !== undefined) { transferItem[name] = dayjs(transferItem[name]) .utcOffset(-timezoneOffset) .format('YYYY-MM-DD HH:mm:ss') } }) } + if (column.type === 'date') { + transferList.forEach((transferItem) => { + if (transferItem[name]) { + transferItem[name] = dayjs(transferItem[name]) + .format('YYYY-MM-DD') + } + }) + } }) return Array.isArray(data) ? transferList : transferList[0] } @@ -104,19 +113,38 @@ export const querySqlCheck = (sql) => { if (!/;$/.test(sql.trim())) { throw new Error(global.i18n.t('Sql 语句不完整,需要是【;】号结尾')) } + const checkList = [ + { check: (val) => !/^select/.test(val), message: global.i18n.t('仅支持 SELECT 查询语句,请修改后再试') }, + { check: (val) => /database\(\)/i.test(val), message: global.i18n.t('不允许出现 database() 函数') }, + { check: (val) => /user\(\)/i.test(val), message: global.i18n.t('不允许出现 user() 函数') }, + { check: (val) => /version\(\)/i.test(val), message: global.i18n.t('不允许出现 version() 函数') }, + { check: (val) => /INFORMATION_SCHEMA/i.test(val), message: global.i18n.t('不允许查询 INFORMATION_SCHEMA 相关数据') }, + { check: (val) => /PERFORMANCE_SCHEMA/i.test(val), message: global.i18n.t('不允许查询 PERFORMANCE_SCHEMA 相关数据') }, + { check: (val) => /MYSQL\./i.test(val), message: global.i18n.t('不允许查询 MYSQL 内置表相关数据') }, + { check: (val) => /\@\@/i.test(val), message: global.i18n.t('不允许出现@@符号') } + ] const sqlArr = splitSql(sql) sqlArr.forEach((sqlStr) => { - if (sqlStr && !/^select/.test(sqlStr.trim().toLowerCase())) { - throw new Error(global.i18n.t('仅支持 SELECT 查询语句,请修改后再试')) + const lowerCaseSql = sqlStr.trim().toLowerCase() + if (lowerCaseSql) { + checkList.forEach((checkItem) => { + if (checkItem.check(lowerCaseSql)) { + throw new Error(checkItem.message) + } + }) } }) } // sql 解码 -export const decodeSql = (sql) => { - try { - return Buffer.from(sql, 'base64').toString('utf8') - } catch (error) { - return sql +export const decodeSql = (sql, params) => { + let decodedSql = decodeBase64(sql) + const paramKeys = Object.keys(params || {}) + if (paramKeys.length > 0) { + paramKeys.forEach((paramKey) => { + const reg = new RegExp(`\\$\\{${paramKey}\\}`, 'g') + decodedSql = decodedSql.replace(reg, params[paramKey]) + }) } + return decodedSql } diff --git a/lib/server/service/business/flow.js b/lib/server/service/business/flow.js new file mode 100644 index 000000000..5990019eb --- /dev/null +++ b/lib/server/service/business/flow.js @@ -0,0 +1,65 @@ +/** + * Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available. + * Copyright (C) 2017-2024 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { execApiGateWay } from '@bkui/apigateway-nodejs-sdk' +import { LCDataService, TABLE_FILE_NAME } from '../common/data-service' +import httpConf from '../../conf/http' +import v3Conf from '../../conf/v3' + +// lesscode平台创建bkflow空间 +export const createBkFlowSpace = async () => { + const spaceData = await LCDataService.findOne(TABLE_FILE_NAME.PROJECT_BKFLOW_SPACE, { projectId: 0 }) + + if (!spaceData) { + try { + + let maintainers = ['admin'] + if (process.env.LESSCODE_DEFAULT_IAM_MANAGER) { + maintainers = process.env.LESSCODE_DEFAULT_IAM_MANAGER.split(';') + } + + const response = await execApiGateWay({ + apiName: 'bkflow-eng-svc', + path: `/create_space/`, + method: 'post', + authorization: { + bk_app_code: v3Conf.APP_ID, + bk_app_secret: v3Conf.APP_SECRET, + }, + apiUrlTemp: httpConf.apiGateWayUrlTmpl, + stageName: httpConf.stageName === 'stag' ? 'stage' : httpConf.stageName, + data: { + name: 'LessCode', + app_code: v3Conf.APP_ID, + desc: '运维开发平台', + platform_url: 'https://lesscode.domain.com', + config: { + superusers: maintainers + } + } + }) + + console.log('create bkflow space response ============>', response) + + if (response.result) { + await LCDataService.add(TABLE_FILE_NAME.PROJECT_BKFLOW_SPACE, { + projectId: 0, + bkFlowSpaceId: response.data.space.id + }) + } + } catch (err) { + console.error('create bkflow space error:\n', err) + throw new Error(err.message, -1) + } + } + + return spaceData +} diff --git a/lib/server/service/business/nocode-form.js b/lib/server/service/business/nocode-form.js index 5506fa15e..a81ed310b 100644 --- a/lib/server/service/business/nocode-form.js +++ b/lib/server/service/business/nocode-form.js @@ -34,12 +34,12 @@ export const createNocodeForm = async (formData = {}) => { // 生成相应数据表或update数据表 let dbRes = {} - const hasTable = LCDataService.findOne(TABLE_FILE_NAME.DATA_TABLE, { projectId: formData.projectId, tableName, deleteFlag: 0 }) + const hasTable = await LCDataService.findOne(TABLE_FILE_NAME.DATA_TABLE, { projectId: formData.projectId, tableName, deleteFlag: 0 }) if (hasTable && hasTable.id) { await this.updateNocodeForm({ id: hasTable.id, tableName, projectId: formData.projectId, columns: dbJson }) dbRes.id = hasTable.id } else { - dbRes = await createNCTable({ tableName, projectId: formData.projectId, columns: dbJson }) + dbRes = await createNCTable({ tableName, projectId: formData.projectId, thirdPartDBId: 0, columns: dbJson }) } if (dbRes && dbRes.id) { diff --git a/lib/server/service/business/notice-center.js b/lib/server/service/business/notice-center.js index 281b6dace..76b2e4234 100644 --- a/lib/server/service/business/notice-center.js +++ b/lib/server/service/business/notice-center.js @@ -27,17 +27,25 @@ export const getCurrentNoticeList = async () => { export const registerNoticeCenter = async () => { if (global.NOTICE_CENTER_ENABLE) { - const response = await execApiGateWay({ - authorization: { - ...authorization - }, - apiUrlTemp: httpConf.apiGateWayUrlTmpl, - stageName: httpConf.stageName === 'prod' ? 'prod' : 'stage', - apiName: 'bk-notice', - path: '/apigw/v1/register/', - method: 'post', - data: {} - }) - return response?.data + try { + const response = await execApiGateWay({ + authorization: { + ...authorization + }, + apiUrlTemp: httpConf.apiGateWayUrlTmpl, + stageName: httpConf.stageName === 'prod' ? 'prod' : 'stage', + apiName: 'bk-notice', + path: '/apigw/v1/register/', + method: 'post', + data: {} + }) + return response?.data + } catch (err) { + console.error('register notice center error:\n', err) + return { + data: [], + errorMsg: `register notice center error: ${err}` + } + } } -} \ No newline at end of file +} diff --git a/lib/server/service/business/open-api.js b/lib/server/service/business/open-api.js index d5f127a26..7a6576c84 100644 --- a/lib/server/service/business/open-api.js +++ b/lib/server/service/business/open-api.js @@ -14,7 +14,11 @@ import { transformVersionToNum } from '../../../shared/util' -const apiName = 'bk-lesscode' +const fs = require('fs') +const FormData = require('form-data') +const path = require('path') + +const apiName = global.LESSCODE_API_GATEWAY_NAME const token = { bk_app_code: process.env.BKPAAS_APP_ID, bk_app_secret: process.env.BKPAAS_APP_SECRET @@ -97,6 +101,21 @@ export const generateApiGateway = async () => { console.log('--------------> 线上资源版本号: ', version, '系统配置资源版本号: ', openApiJson.info.version) if (transformVersionToNum(version) < transformVersionToNum(openApiJson.info.version)) { try { + const formData = new FormData(); + formData.append('file', fs.createReadStream(path.resolve(__dirname, './system-conf/apigw-docs.zip'))); + // 更新资源文档 + await execApiGateWay({ + apiName: 'bk-apigateway', + apiUrlTemp: process.env.BK_API_URL_TMPL, + path: `/api/v1/apis/${apiName}/resource-docs/import/by-archive/`, + method: 'post', + stageName: 'prod', + authorization: token, + data: formData, + config: { + 'Content-Type': 'multiple/form-data' + } + }) // 创建资源版本 await createResourceVersion( apiName, @@ -153,6 +172,18 @@ export const grantApiPermissionForApps = async () => { token, 'prod' ) + + // 网关主动为bkvision授权 + // await grantPermissions( + // apiName, + // { + // target_app_code: 'bkvision', + // grant_dimension: 'resource', + // resource_names: ['get_my_project_list', 'getTableData'] + // }, + // token, + // 'prod' + // ) } export const getUserFromApiGW = async (jwt) => { diff --git a/lib/server/service/business/preview-db-service.js b/lib/server/service/business/preview-db-service.js index f41e155d8..ec077cb4c 100644 --- a/lib/server/service/business/preview-db-service.js +++ b/lib/server/service/business/preview-db-service.js @@ -152,7 +152,7 @@ export const getPreviewDataService = async (projectId, thirdPartDBName) => { const [{ list: tables }, config] = await Promise.all([ LCDataService.get({ tableFileName: TABLE_FILE_NAME.DATA_TABLE, - query: { projectId, deleteFlag: 0 } + query: { projectId, deleteFlag: 0, thirdPartDBId: thirdPartDBId || 0 } }), getPreviewDbConfig(projectId, null, thirdPartDBId) ]) @@ -288,7 +288,7 @@ export const getThirdPartDBTables = async (projectId, page, pageSize, thirdPartD const designTable = designTables.find(designTable => designTable.tableName === onlineTableDetail.tableName) if (designTable) { let isColumnChanged - const checkKeys = ORM_KEYS.filter(key => key !== 'columnId') + const checkKeys = ORM_KEYS.filter(key => !['columnId', 'default'].includes(key)) JSON.parse(tableData.columns).forEach((tableDataColumn) => { const tableColumn = JSON.parse(designTable.columns).find((tableColumn) => { return checkKeys.every(key => tableColumn[key] === tableDataColumn[key]) @@ -306,9 +306,10 @@ export const getThirdPartDBTables = async (projectId, page, pageSize, thirdPartD } else { const idColumn = onlineTableDetail.columns.find(column => column.name === 'id') const generatedColumn = onlineTableDetail.columns.find(column => column.generated) + const primaryColumn = onlineTableDetail.columns.find(column => column.primary) if (idColumn?.generated) { addDesignTables.push(tableData) - } else if (!generatedColumn) { + } else if (!generatedColumn && !idColumn && !primaryColumn) { addDesignTables.push({ ...tableData, columns: JSON.stringify([ diff --git a/lib/server/service/business/project.js b/lib/server/service/business/project.js index 327c2b839..3c6dc073e 100644 --- a/lib/server/service/business/project.js +++ b/lib/server/service/business/project.js @@ -285,6 +285,7 @@ export const importProject = async (projectData, userProjectRoleData, importData await queryRunner.startTransaction() // 创建新应用 + projectData.id = null const newProject = getRepository(Project).create(projectData) const { id: projectId } = await queryRunner.manager.save(newProject) diff --git a/lib/server/service/business/saas_builder.js b/lib/server/service/business/saas_builder.js index 4c8f53fce..c4fc769d1 100644 --- a/lib/server/service/business/saas_builder.js +++ b/lib/server/service/business/saas_builder.js @@ -105,10 +105,7 @@ export const queryBuilder = async (id, bkToken) => { return response?.data } -export const queryApiDoc = async (appName, bkToken) => { - const apiHost = httpConf.apiGateWayUrlTmpl?.replace('{api_name}', 'bkaidev') - const stageName = apiHost + httpConf.stageName === 'prod' ? '/prod' : '/stage' - const url = `${apiHost}${stageName}/worker_service/preview/${appName}/api_doc/schema/` +export const queryApiDoc = async (url, bkToken) => { const response = await axios({ url, method: 'GET', diff --git a/lib/server/service/common/data-service.js b/lib/server/service/common/data-service.js index bb06b16fd..30f5942a9 100644 --- a/lib/server/service/common/data-service.js +++ b/lib/server/service/common/data-service.js @@ -323,6 +323,7 @@ export function getDataService (name = 'default', customEntityMap) { * @returns 新增结果 */ async add (tableFileName, data) { + data.id = null const repository = getRepositoryByName(tableFileName) const newData = repository.create(transformData(data, repository.metadata.columns)) try { @@ -340,6 +341,9 @@ export function getDataService (name = 'default', customEntityMap) { * @returns 新增结果 */ async bulkAdd (tableFileName, dataList) { + dataList.forEach(item =>{ + item.id = null + }) const repository = getRepositoryByName(tableFileName) const newDataList = repository.create(transformData(dataList, repository.metadata.columns)) try { @@ -547,6 +551,7 @@ export const TABLE_FILE_NAME = { PROJECT_FUNC_MARKET: 'project-func-market', PROJECT_PAGE: 'project-page', PROJECT_VERSION: 'project-version', + PROJECT_BKFLOW_SPACE: 'project-bkflow-space', ROLE_PERM: 'role-perm', ROLE: 'role', ROUTE: 'route', @@ -559,6 +564,8 @@ export const TABLE_FILE_NAME = { VERSION: 'version', FORM: 'form', FLOW: 'flow', + FLOW_TPL: 'flow-tpl', + FLOW_TASK: 'flow-task', API: 'api', API_CATEGORY: 'api-category', FILE: 'file', diff --git a/lib/server/service/common/online-db-service.js b/lib/server/service/common/online-db-service.js index 740fb5248..2c65672b9 100644 --- a/lib/server/service/common/online-db-service.js +++ b/lib/server/service/common/online-db-service.js @@ -99,6 +99,14 @@ export default class OnlineService { generated: EXTRA === 'auto_increment' } + if (DATA_TYPE === 'enum') { + const execResult = /\((.+)\)/.exec(COLUMN_TYPE) + if (execResult) { + const enumValues = execResult[1].split(',') + column.enum = enumValues.map(val => val.slice(1, -1)) + } + } + const table = { tableName: TABLE_NAME, engine: ENGINE, @@ -115,9 +123,11 @@ export default class OnlineService { } // 分页获取表数据 - async getTableData (tableName, page, pageSize) { + async getTableData (tableName, page, pageSize, filterKey, filterValue, sortKey, sortValue) { + const where = `${filterKey && filterValue ? `WHERE \`${filterKey}\` LIKE '%${filterValue}%'` : ''}` + const orderBy = `ORDER BY ${sortKey || 'id'} ${sortValue || 'DESC'}` const [list, foundRows = []] = await this.dbEngine.execMultSql(` - SELECT SQL_CALC_FOUND_ROWS * FROM \`${tableName}\` ORDER BY id DESC LIMIT ${(page - 1) * pageSize},${pageSize}; + SELECT SQL_CALC_FOUND_ROWS * FROM \`${tableName}\` ${where} ${orderBy} LIMIT ${(page - 1) * pageSize},${pageSize}; SELECT FOUND_ROWS(); `) const rows = foundRows[0] || {} @@ -126,9 +136,11 @@ export default class OnlineService { } // 获取表所有的数据 - async getTableAllData (tableName) { + async getTableAllData (tableName, filterKey, filterValue, sortKey, sortValue) { + const where = `${filterKey && filterValue ? `WHERE \`${filterKey}\` LIKE '%${filterValue}%'` : ''}` + const orderBy = `ORDER BY ${sortKey || 'id'} ${sortValue || 'DESC'}` const [list, foundRows = []] = await this.dbEngine.execMultSql(` - SELECT SQL_CALC_FOUND_ROWS * FROM \`${tableName}\` ORDER BY id DESC; + SELECT SQL_CALC_FOUND_ROWS * FROM \`${tableName}\` ${where} ${orderBy}; SELECT FOUND_ROWS(); `) const rows = foundRows[0] || {} diff --git a/lib/server/service/common/project-auth.js b/lib/server/service/common/project-auth.js new file mode 100644 index 000000000..210432688 --- /dev/null +++ b/lib/server/service/common/project-auth.js @@ -0,0 +1,144 @@ +import { LCDataService, TABLE_FILE_NAME } from './data-service' + +import iamModel from '../../model/iam' +import { checkInServer, fetchActionsPerm } from '../../controller/iam' +import { IAM_ACTION } from '../../../shared/constant.js' + +// 查找对应资源id对应的projectId +export const queryProjectIdByResource = async (tableName, where) => { + const data = await LCDataService.findOne( + TABLE_FILE_NAME[tableName], + where + ) || {} + return data?.projectId || data?.belongProjectId || data?.project_id || '' +} + +/** + * @desc 校验header中获传过来的projceId,与根据传过来的资源id反查出来的projectId是否一致 + * @param { Ctx }ctx + * @param { Object: { + * projectId: 要对比的projectId 默认从header取 + * resourceKey: 资源在数据表中的关联字段, 默认为id, + * resourceId: 传递过来资源id, 默认为ctx.request.body[resourceKey] || ctx.request.query[resourceKey] + * findType: projectId字段跟resourceKey字段的关系, 默认 IN_SAME_TABLE + * } } payload + * @returns { String } + */ +export const findRealProjectId = async (ctx, payload = {}) => { + const tableName = payload.tableName + const paramProjectId = payload?.projectId || ctx.request.headers['x-project-id'] + const resourceKey = payload?.resourceKey || 'id' + const resourceId = payload?.resourceId || ctx.request.body[resourceKey] || ctx.request.query[resourceKey] + // const type = payload?.findType || 'IN_SAME_TABLE' + if (!paramProjectId) { + return 0 + } + const realProjectId = await queryProjectIdByResource(tableName, { [resourceKey]: resourceId }) + if (realProjectId.toString() !== paramProjectId.toString()) { + return 0 + } + return realProjectId +} + +// 鉴权projectId对应的权限 +export const handleProjectPerm = async (ctx, next, projectId, needAuthActions = ['develop_app']) => { + const res = await checkProjectPerm(ctx, projectId, needAuthActions) + if (res.code === 0) { + await next() + } else { + ctx.send(res) + } +} + +// 检查projectId是否存在及有对应的权限 +export const checkProjectPerm = async (ctx, projectId, needAuthActions = ['develop_app']) => { + try { + projectId = projectId || ctx.request.headers['x-project-id'] + const project = await iamModel.queryProjectByCreatorAndProjectId(ctx.session.userInfo.username, projectId) + if (!project) { + return { + code: 404, + message: global.i18n.t('应用不存在'), + data: null + } + } + + // 需要去权限中心查询是否具有相应权限 + const iamRes = await checkInServer(ctx, String(projectId)) + let pass = false + for (let i = 0; i < needAuthActions.length; i++) { + const needAuthAction = needAuthActions[i] + if (iamRes[IAM_ACTION[needAuthAction][0]]) { + pass = true + break + } + } + if (!pass) { + return { + code: 403, + message: global.i18n.t('您没有应用[ID:{{n}}]的权限,请去权限中心申请权限', { n: projectId }), + data: { + pass: false, + applyUrl: iamRes.applyUrl, + requiredPermissions: iamRes.requiredPermissions, + permissionType: 'page' + } + } + } else { + return { + code: 0, + data: true + } + } + } catch (error) { + return { + code: 500, + data: null, + message: error.message || error + } + } +} + +// 校验几个不对应资源的超级管理员权限 +export const checkNoResourcePerm = async (ctx, checkAction) => { + try { + if (!global.IAM_ENABLE) { + // 未开启权限中心则校验老的platformadmin + const isPlatformAdmin = await LCDataService.findOne(TABLE_FILE_NAME.PLATFORM_ADMIN, { + username: ctx.session.userInfo.username + }) + if (isPlatformAdmin?.id) { + return { + code: 0, + data: true + } + } else { + return { + code: 403, + message: global.i18n.t('您不是平台管理员,请联系管理员授权后再试') + } + } + } else { + const authedRet = await fetchActionsPerm(ctx, [ + checkAction + ]) + + if (!authedRet[checkAction]) { + return { + code: 403, + message: global.i18n.t('您不是平台管理员,请联系管理员授权后再试') + } + } else { + return { + code: 0, + data: true + } + } + } + } catch (err) { + return { + code: 500, + message: err.message || err + } + } +} \ No newline at end of file diff --git a/lib/server/service/common/user-info.js b/lib/server/service/common/user-info.js index 8666d1a3c..9df688b0d 100644 --- a/lib/server/service/common/user-info.js +++ b/lib/server/service/common/user-info.js @@ -4,23 +4,28 @@ const httpConf = require('../../conf/http') const https = require('https') export const getUserInfoByToken = async (bkToken) => { - const hostUrl = httpConf.hostUrl.replace(/\/$/, '') - const params = querystring.stringify({ - bk_app_code: httpConf.appCode, - bk_app_secret: httpConf.appSecret, - bk_token: bkToken - }) - const response = await axios({ - withCredentials: true, - url: `${hostUrl}/api/c/compapi/v2/bk_login/get_user/?${params}`, - method: 'GET', - headers: { - 'Content-Type': 'application/json' - }, - responseType: 'json', - httpsAgent: new https.Agent({ rejectUnauthorized: false }), - // 不设置 proxy false 的话,会导致 rejectUnauthorized: false 的设置(忽略 ssl 证书)生效 - proxy: false - }) - return response + try { + const hostUrl = httpConf.hostUrl.replace(/\/$/, '') + const params = querystring.stringify({ + bk_app_code: httpConf.appCode, + bk_app_secret: httpConf.appSecret, + bk_token: bkToken + }) + const response = await axios({ + withCredentials: true, + url: `${hostUrl}/api/c/compapi/v2/bk_login/get_user/?${params}`, + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + responseType: 'json', + httpsAgent: new https.Agent({ rejectUnauthorized: false }), + // 不设置 proxy false 的话,会导致 rejectUnauthorized: false 的设置(忽略 ssl 证书)生效 + proxy: false + }) + return response + } catch (error) { + console.log(error, 'login err') + } + } diff --git a/lib/server/system-conf/apigw-docs.zip b/lib/server/system-conf/apigw-docs.zip new file mode 100644 index 000000000..868fdefc5 Binary files /dev/null and b/lib/server/system-conf/apigw-docs.zip differ diff --git a/lib/server/system-conf/open-api.json b/lib/server/system-conf/open-api.json index fc76032a5..d7875cbdd 100644 --- a/lib/server/system-conf/open-api.json +++ b/lib/server/system-conf/open-api.json @@ -2,7 +2,7 @@ "swagger": "2.0", "basePath": "/", "info": { - "version": "1.1.9", + "version": "1.1.10", "title": "bk-lesscode", "description": "bk-lesscode 对外接口" }, @@ -21,7 +21,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { @@ -83,7 +83,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { @@ -145,7 +145,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { @@ -176,7 +176,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { @@ -207,7 +207,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { @@ -238,7 +238,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { @@ -300,7 +300,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { @@ -331,7 +331,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { @@ -362,7 +362,7 @@ } }, "x-bk-apigateway-resource": { - "isPublic": true, + "isPublic": false, "allowApplyPermission": true, "matchSubpath": false, "backend": { diff --git a/lib/server/system-conf/operate-log.js b/lib/server/system-conf/operate-log.js index f42c73e94..007d97245 100644 --- a/lib/server/system-conf/operate-log.js +++ b/lib/server/system-conf/operate-log.js @@ -171,7 +171,7 @@ module.exports = { }, // 函数 - 'POST-/api/function/addFunction': { + 'POST-/api/function/createFunction': { code: 'add_func', codeText: global.i18n.t('创建函数'), manualSuccess: true, diff --git a/lib/server/utils/file-service/index.js b/lib/server/utils/file-service/index.js index 2fb4315ce..df159424f 100644 --- a/lib/server/utils/file-service/index.js +++ b/lib/server/utils/file-service/index.js @@ -37,9 +37,15 @@ const getUrl = (path, isPublic) => { return `${prefix}${newPath}` } -const checkBkRepoSetting = () => { +const checkBkRepoSetting = (throwErr = true) => { if (!config.BKREPO_USERNAME || !config.BKREPO_PROJECT || !config.BKREPO_ENDPOINT_URL) { - throw new Error(global.i18n.t('bkRepo: 请先搭建bkRepo服务并确认配置信息已设置正确')) + if (throwErr) { + throw new Error(global.i18n.t('bkRepo: 请先搭建bkRepo服务并确认配置信息已设置正确')) + } else { + return false + } + } else { + return true } } diff --git a/lib/server/utils/flow/index.js b/lib/server/utils/flow/index.js new file mode 100644 index 000000000..058e12ff5 --- /dev/null +++ b/lib/server/utils/flow/index.js @@ -0,0 +1,181 @@ +import httpConf from '../../conf/http' +import v3Conf from '../../conf/v3' + +// lesscode流程节点类型和BkFlow节点类型映射 +export const NODE_TYPE_MAP = { + Start: 'startpoint', + End: 'endpoint', + Manual: 'tasknode', + DataProcessing: 'tasknode', + API: 'tasknode', + Approval: 'tasknode' +} + +// lesscode流程节点类型和在BkFlow中使用对应的插件映射 +export const NODE_PLUGIN_MAP = { + Manual: 'pause_node', + DataProcessing: 'bk_http_request' +} + +// 将流程模板数据转换为BkFlow需要的pipeline_tree结构 +export const transFlowTplToBkFlowPipelineTree = (projectId, nodes, edges) => { + const pipelineTree = { + activities: {}, + canvas_mode: "horizontal", + constants: {}, + end_event: {}, + flows: {}, + gateways: {}, + line: [], + location: [], + outputs: [], + start_event: {} + } + + nodes.forEach(node => { + const { type, id, axis, config } = node + const nodeType = NODE_TYPE_MAP[type] + if (nodeType === 'tasknode') { + // activities + pipelineTree.activities[id] = { + id: id, + name: config.name, + incoming: getNodeIncomingOrOutgoing(id, edges, 'incoming', true), + outgoing: getNodeIncomingOrOutgoing(id, edges, 'outgoing'), + component: getNodeComponentConfig(projectId, node), + error_ignorable: false, + loop: null, + optional: true, + stage_name: '', + type: 'ServiceActivity', + retryable: true, + skippable: true, + auto_retry: { enable: false, interval: 0, times: 1 }, + timeout_config: { enable: false, seconds: 10, action: 'forced_fail' }, + labels: [] + } + // 人工节点需要将callback_data勾选为全选变量,在流程上下文中使用 + if (NODE_PLUGIN_MAP[node.type] === 'pause_node') { + pipelineTree.constants[`\${callback_data_${node.id}}`] = { + name: "API回调数据", + key: `\${callback_data_${node.id}}`, + desc: "", + custom_type: "", + source_info: { + [node.id]: ['callback_data'] + }, + source_tag: '', + value: '', + show_type: 'hide', + source_type: 'component_outputs', + validation: '', + index: 0, + plugin_code: '' + } + } + } else if (nodeType === 'startpoint') { + // start_event + pipelineTree.start_event = { + id, + incoming: '', + name: '', + outgoing: getNodeIncomingOrOutgoing(id, edges, 'outgoing'), + type: 'EmptyStartEvent', + labels: [] + } + } else if (nodeType === 'endpoint') { + // end_event + pipelineTree.end_event = { + id, + incoming: getNodeIncomingOrOutgoing(id, edges, 'incoming'), + name: '', + outgoing: '', + type: 'EmptyEndEvent', + labels: [] + } + } + + // location + pipelineTree.location.push({ id, type: nodeType, name: config.name, x: axis.x, y: axis.y }) + }) + + edges.forEach(edge => { + const { id, source, target } = edge + // line + pipelineTree.line.push({ id, source: { id: source.cell, arrow: extractPortDirectionName(source.port) }, target: { id: target.cell, arrow: extractPortDirectionName(target.port) } }) + // flow + pipelineTree.flows[id] = { id, source: source.cell, target: target.cell, is_default: false } + }) + + return pipelineTree +} + +// 获取任务节点component属性配置 +export const getNodeComponentConfig = (projectId, node) => { + const pluginName = NODE_PLUGIN_MAP[node.type] + const componentConfig = { + code: pluginName, + data: {}, + version: 'legacy', + } + if (pluginName === 'pause_node') { + componentConfig.data = { + description: { hook: false, need_render: true, value: 'lesscode人工节点' } + } + } else if (pluginName === 'bk_http_request') { + const apiURL = `${httpConf.apiGateWayUrlTmpl.replace('{api_name}', global.LESSCODE_API_GATEWAY_NAME)}/${process.env.BKPAAS_ENVIRONMENT}/dataManage` + componentConfig.data = { + bk_http_request_method: { hook: false, need_render: true, value: 'POST' }, + bk_http_request_url: { hook: false, need_render: true, value: apiURL }, + bk_http_request_header: { + hook: false, + need_render: true, + value: [ + { + name: 'x-project-id', + value: projectId + }, + { + name: 'X-Bkapi-Authorization', + value: JSON.stringify({ + app_code: v3Conf.APP_ID, + app_secret: v3Conf.APP_SECRET + }) + } + ] + }, + bk_http_request_body: { hook: false, need_render: true, value: JSON.stringify({ ...node.config, creator: '${_system.operator}' })}, + bk_http_timeout: { hook: false, need_render: true, value: 5 }, + bk_http_success_exp: { hook: false, need_render: true, value: '' } + } + componentConfig.version = 'v1.0' + } + + return componentConfig +} + +// 提取连接桩方向的名称 +export const extractPortDirectionName = (port) => { + const parts = port.split('_'); + const targetPart = parts[parts.length - 1]; + + // 将首字母转换为大写,其他部分保持不变 + return targetPart.charAt(0).toUpperCase() + targetPart.slice(1); +} + +// 获取节点的入度或出度 +export const getNodeIncomingOrOutgoing = (nodeId, edges, direction, isMultiple = false) => { + const results = [] + edges.forEach(edge => { + const cellId = direction === 'incoming' ? edge.target.cell : edge.source.cell + if (cellId === nodeId) { + results.push(edge.id) + } + }) + + if (results.length > 0) { + return isMultiple ? results : results[0] + } + + return isMultiple ? [] : '' +} \ No newline at end of file diff --git a/lib/server/utils/npm/index.js b/lib/server/utils/npm/index.js index 21a3cffd0..6c79e2968 100644 --- a/lib/server/utils/npm/index.js +++ b/lib/server/utils/npm/index.js @@ -68,9 +68,15 @@ const generatorPackageJson = (params) => { }` } -export const checkNpmSetting = () => { +export const checkNpmSetting = (throwErr = true) => { if (!config || !config.registry || !config.username) { - throw new Error(global.i18n.t('请先确保{{n}}相关配置信息已正确填写', { n: 'lib/server/conf/npm.js' })) + if (throwErr) { + throw new Error(global.i18n.t('请先确保{{n}}相关配置信息已正确填写', { n: 'lib/server/conf/npm.js' })) + } else { + return false + } + } else { + return true } } diff --git a/lib/shared/api/helper.js b/lib/shared/api/helper.js index eaad98925..335778ed0 100644 --- a/lib/shared/api/helper.js +++ b/lib/shared/api/helper.js @@ -4,9 +4,11 @@ import { API_PARAM_VALUE_TYPES } from './constant' import { - VARIABLE_VALUE_TYPE + getVariableValue } from '../variable' -import { uuid } from '../util' +import { + uuid +} from '../util' /** * 获取默认 API 所有字段 @@ -99,7 +101,7 @@ export const parseHeaderScheme2HeaderString = (schemes, getVal) => { // 编辑态 scheme 转换为值 export const parseScheme2Value = (scheme, getVal) => { let val = getVal ? getVal(scheme) : scheme.value - if (API_PARAM_TYPES.OBJECT.VAL === scheme.type) { + if (API_PARAM_TYPES.OBJECT.VAL === scheme.type && scheme.valueType !== API_PARAM_VALUE_TYPES.VARIABLE) { val = {} scheme.children.forEach((item) => { if (item.name) { @@ -107,7 +109,7 @@ export const parseScheme2Value = (scheme, getVal) => { } }) } - if (API_PARAM_TYPES.ARRAY.VAL === scheme.type) { + if (API_PARAM_TYPES.ARRAY.VAL === scheme.type && scheme.valueType !== API_PARAM_VALUE_TYPES.VARIABLE) { val = [] scheme.children.forEach((item) => { val.push(parseScheme2Value(item, getVal)) @@ -135,7 +137,7 @@ export const parseValue2Scheme = (val, name = 'root', description = '') => { if (API_PARAM_TYPES.ARRAY.TYPE === type) { scheme.value = '' val.forEach((item) => { - scheme.children.push(parseValue2Scheme(item, `${name}-item`)) + scheme.children.push(parseValue2Scheme(item, `${name}_item`)) }) } @@ -221,7 +223,7 @@ export const LCGetParamsVal = (variableList) => { result = param.value } else { const variable = variableList.find(variable => variable.variableCode === param.code) || {} - result = VARIABLE_VALUE_TYPE.SAME === variable.defaultValueType ? variable.defaultValue?.all : variable.defaultValue?.stag + result = getVariableValue(variable) } return result } diff --git a/lib/shared/data-source/constant.js b/lib/shared/data-source/constant.js index 45cff68d4..1d98e7afd 100644 --- a/lib/shared/data-source/constant.js +++ b/lib/shared/data-source/constant.js @@ -77,7 +77,8 @@ export const ORM_KEYS = [ 'length', 'columnId', 'generated', - 'scale' + 'scale', + 'enum' ] /** diff --git a/lib/shared/data-source/helper.js b/lib/shared/data-source/helper.js index bde128af4..9c6af0506 100644 --- a/lib/shared/data-source/helper.js +++ b/lib/shared/data-source/helper.js @@ -521,7 +521,7 @@ export const checkTable = (tables) => { throw new Error(`${sharedI18n().t('表字段不能为空')}:【${tableName}】${sharedI18n().t('表缺少字段信息')}`) } // 字段信息检查 - const checkKeys = ORM_KEYS.filter(key => key !== 'columnId') + const checkKeys = ORM_KEYS.filter(key => !['enum', 'columnId'].includes(key)) checkKeys.forEach((key) => { if (!columns.find(column => Reflect.has(column, key))) { throw new Error(`${sharedI18n().t('表字段信息缺失')}:【${tableName}】${sharedI18n().t('表缺少')}【${key}】${sharedI18n().t('字段')}`) diff --git a/lib/shared/data-source/index.js b/lib/shared/data-source/index.js index f1e067b6c..088f3c656 100644 --- a/lib/shared/data-source/index.js +++ b/lib/shared/data-source/index.js @@ -18,4 +18,4 @@ export { DataJsonParser } from './data-parse/data-parser/json-parser' export { StructXlsxParser } from './data-parse/struct-parser/xlsx-parser' export { StructSqlParser } from './data-parse/struct-parser/sql-parser' export { StructJsonParser } from './data-parse/struct-parser/json-parser' -export { generateSqlByCondition } from './sql-builder' +export { generateSqlByCondition, getSqlParam } from './sql-builder' diff --git a/lib/shared/data-source/sql-builder.js b/lib/shared/data-source/sql-builder.js index a5258bc0d..e3b90d811 100644 --- a/lib/shared/data-source/sql-builder.js +++ b/lib/shared/data-source/sql-builder.js @@ -34,6 +34,36 @@ export const generateSqlByCondition = (condition = {}, tableList, useParam = tru return sql } +/** + * 通过查询 json 生成 sql 参数 + * @param {*} condition 查询 json + * @returns params + */ +export const getSqlParam = (condition = {}) => { + const params = {} + // where + condition?.where?.forEach((item) => { + if (item.param) { + params[item.param] = `\$\{${item.param}\}` + } + }) + // having + condition?.groupBy?.having?.forEach((item) => { + if (item.param) { + params[item.param] = `\$\{${item.param}\}` + } + }) + // indexParam + if (condition?.limit?.indexParam) { + params[condition.limit.indexParam] = `\$\{${condition.limit.indexParam}\}` + } + // lengthParam + if (condition?.limit?.lengthParam) { + params[condition.limit.lengthParam] = `\$\{${condition.limit.lengthParam}\}` + } + return params +} + /** * sql 关键字包含一层 * @param {*} key 关键字 diff --git a/lib/shared/flow/constant.js b/lib/shared/flow/constant.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/lib/shared/flow/constant.js @@ -0,0 +1 @@ + diff --git a/lib/shared/flow/index.js b/lib/shared/flow/index.js new file mode 100644 index 000000000..63e460774 --- /dev/null +++ b/lib/shared/flow/index.js @@ -0,0 +1 @@ +export * from './constant' diff --git a/lib/shared/form-field-protocol-transform/index.js b/lib/shared/form-field-protocol-transform/index.js index bf086c5b5..98a152158 100644 --- a/lib/shared/form-field-protocol-transform/index.js +++ b/lib/shared/form-field-protocol-transform/index.js @@ -121,6 +121,7 @@ export const transformNocodeFields = (fields) => { config: { tableName, fieldKey, + fieldLabel: '', logic: conditions.connector, conditions: conditions.expressions }, diff --git a/lib/shared/function/constant.js b/lib/shared/function/constant.js index 20ef9c0e8..5f694538f 100644 --- a/lib/shared/function/constant.js +++ b/lib/shared/function/constant.js @@ -78,7 +78,8 @@ export const USE_API_TYPE = { */ export const EVENT_TYPE = { METHOD: 'method', - ACTION: 'action' + ACTION: 'action', + FLOW: 'flow' } /** diff --git a/lib/shared/no-code/constant.js b/lib/shared/no-code/constant.js index 27fa00856..c6b3a2ff1 100644 --- a/lib/shared/no-code/constant.js +++ b/lib/shared/no-code/constant.js @@ -184,7 +184,7 @@ export const REGX_CHIOCE_LIST = () => ([ { id: 'PHONE_NUM', name: sharedI18n().t('内地手机号码'), type: ['STRING', 'INPUT'] }, { id: 'ID_CARD', name: sharedI18n().t('身份证'), type: ['STRING', 'INPUT'] }, { id: 'IP', name: sharedI18n().t('IP地址'), type: ['STRING', 'INPUT'] }, - { id: 'QQ', name: sharedI18n().t('腾讯QQ号'), type: ['STRING', 'INPUT'] }, + { id: 'QQ', name: sharedI18n().t('QQ号'), type: ['STRING', 'INPUT'] }, { id: 'AFTER_DATE', name: sharedI18n().t('系统日期之后'), type: 'DATE' }, { id: 'BEFORE_DATE', name: sharedI18n().t('系统日期之前'), type: 'DATE' }, { id: 'ONLY_NOW_DATE', name: sharedI18n().t('系统日期'), type: 'DATE' }, diff --git a/lib/shared/no-code/validate.js b/lib/shared/no-code/validate.js index 358c5373b..d6ee8e7ef 100644 --- a/lib/shared/no-code/validate.js +++ b/lib/shared/no-code/validate.js @@ -28,7 +28,7 @@ const ruleNameMap = () => ({ 'PHONE_NUM': sharedI18n().t('内地手机号码'), 'ID_CARD': sharedI18n().t('身份证'), 'IP': sharedI18n().t('IP地址'), - 'QQ': sharedI18n().t('腾讯QQ号'), + 'QQ': sharedI18n().t('QQ号'), 'AFTER_DATE': sharedI18n().t('系统日期之后'), 'BEFORE_DATE': sharedI18n().t('系统日期之前'), 'ONLY_NOW_DATE': sharedI18n().t('系统日期'), diff --git a/lib/shared/page-code/common/modelMethods.js b/lib/shared/page-code/common/modelMethods.js index 102ad8a40..22bd75750 100644 --- a/lib/shared/page-code/common/modelMethods.js +++ b/lib/shared/page-code/common/modelMethods.js @@ -146,6 +146,29 @@ export function eventActionTemplate (pageCode, componentId, eventName, actions, }) } +export function addFlowActionTemplate (pageCode, eventHandlerName, config) { + if (config.actionName === 'createAndExecuteTask') { + addUsedFunc(pageCode, eventHandlerName) + pageCode.flowActionList.push({ + name: eventHandlerName, + code: ` + const res = await this.$store.dispatch('createAndExecuteTask', ${config.id}) + if (res.code === 0) { + this.$bkMessage({ + theme: 'success', + message: '流程【' + res.data.tplName + '】执行任务创建成功' + }) + } else { + this.$bkMessage({ + theme: 'error', + message: res.message + }) + } + ` + }) + } +} + /** * @desc 获取解析后的函数参数 * @param { CodeModel } code @@ -173,8 +196,9 @@ export function getFuncParamStr (code, method, params, errMessagePerfix, withThi let lastIndex = 0 return displayParams ?.reduce((acc, cur, index) => { - if (cur.format === 'value') { - acc.push(code.handleUsedVariable(cur.format, cur.value, errMessagePerfix) || '\'\'') + // 兼容老数据中没有format、则视为format=value + if (cur.format === 'value' || !cur.format) { + acc.push(code.handleUsedVariable('value', cur.value, errMessagePerfix) || '\'\'') } else if (withThis && cur.format === 'variable') { acc.push(`this.${code.handleUsedVariable(cur.format, cur.code, errMessagePerfix)}`) } else if (cur.format === 'event') { diff --git a/lib/shared/page-code/index.js b/lib/shared/page-code/index.js index c1ebeaaa8..34d39a590 100644 --- a/lib/shared/page-code/index.js +++ b/lib/shared/page-code/index.js @@ -12,7 +12,8 @@ import { handleUsedVariable, addUsedFunc, addUsedVariable, - eventActionTemplate + eventActionTemplate, + addFlowActionTemplate } from './common/modelMethods' class NewPageCode { @@ -20,6 +21,7 @@ class NewPageCode { dataObj = {} // 暂存使用到的data变量map remoteDataStr = '' // 远程函数使用列表str eventActionList = [] // 事件描述 + flowActionList = [] // 操作函数列表 isUseElement = false // 是都使用element组件库 isUseSwiper = false // 是否使用H5-container容器,使用Swiper组件 isUseBkCharts = false // 是否使用bkcharts标志位 @@ -174,6 +176,15 @@ class NewPageCode { eventActionTemplate(this, componentId, eventName, actions, errMessagePerfix) } + /** + * @desc 添加操作函数事件到flowActionList中 + * @param { String } eventHandlerName 模板中生成事件名 + * @param { Object } config 事件配置 + */ + addFlowActionTemplate (eventHandlerName, config) { + addFlowActionTemplate(this, eventHandlerName, config) + } + /** * @desc 获取解析后的函数参数 * @param { Object } params 参数列表 diff --git a/lib/shared/page-code/script/vue2/computed.js b/lib/shared/page-code/script/vue2/computed.js index 8bc49ed5b..da444dc23 100644 --- a/lib/shared/page-code/script/vue2/computed.js +++ b/lib/shared/page-code/script/vue2/computed.js @@ -1,3 +1,27 @@ +// 获取语种切换的computed的字符串 +function getLangComputedStr (pageType) { + let gtLangStr = 'getCurLang' + if (pageType === 'preview') { + gtLangStr = 'this.$getCurLang' + } + let computedCon = '' + // 获取当前语种 + computedCon += `curLang() { + return ${gtLangStr}() + },\n` + // 根据不同语种获取iconClass + computedCon += `langIconCls() { + return (langVal) => { + let iconDifCls = 'icon-chinese' + if(['en', 'english'].includes(langVal)) { + iconDifCls = 'icon-english' + } + return 'bk-icon ' + iconDifCls + ' language-icon' + } + },\n` + return computedCon +} + /** * @desc 返回computed内容 * @param { CodeModel } code @@ -6,10 +30,14 @@ export default function (code) { let computed = '' // isUseSwiper时,添加bkH5Container的compute属性,获取swiper实例 - if ((['vueCode', 'projectCode'].includes(code.pageType) && code.hasLayout) || code.projectVariables.length || code.pageComputedVariables.length || code.isUseSwiper) { + if ((['vueCode', 'projectCode'].includes(code.pageType) && code.hasLayout) || code.projectVariables.length || code.pageComputedVariables.length || code.isUseSwiper || code.isGenerateNav) { let computedCon = '' - if (['vueCode', 'projectCode'].includes(code.pageType) && code.hasLayout) { + if (['vueCode', 'projectCode'].includes(code.pageType) && code.hasLayout && code.platform === 'PC') { computedCon += '...mapGetters([\'user\']),\n' + computedCon += getLangComputedStr() + } + if (['preview'].includes(code.pageType) && code.isGenerateNav && code.platform === 'PC') { + computedCon += getLangComputedStr(code.pageType) } // 生成项目源码或预览时,项目级变量统一放到store中 code.projectVariables.forEach((variable) => { diff --git a/lib/shared/page-code/script/vue2/import.js b/lib/shared/page-code/script/vue2/import.js index 0c231f4dc..217acfbe0 100644 --- a/lib/shared/page-code/script/vue2/import.js +++ b/lib/shared/page-code/script/vue2/import.js @@ -74,6 +74,7 @@ export default function (code) { } importStr += 'import { mapGetters } from \'vuex\'\n' importStr += 'import auth from \'@/common/auth\'\n' + importStr += 'import { getCurLang, changeLang } from \'@/locales/i18n.js\'\n' } // import页面使用到的自定义组件 diff --git a/lib/shared/page-code/script/vue2/lifeCycle.js b/lib/shared/page-code/script/vue2/lifeCycle.js index e86070427..21740a902 100644 --- a/lib/shared/page-code/script/vue2/lifeCycle.js +++ b/lib/shared/page-code/script/vue2/lifeCycle.js @@ -38,7 +38,7 @@ export default function (code) { // 如果使用h5容器, 必须调用register方法 注册swiper-container if (code.isUseSwiper && code.platform === 'MOBILE') { - const createdMethod = 'register()' + const createdMethod = '(window.swiperRegister = window.swiperRegister || register)()' const mountedMethod = `const swiperAnimation = new SwiperAnimation() const swiperParams = { on: { diff --git a/lib/shared/page-code/script/vue2/methods.js b/lib/shared/page-code/script/vue2/methods.js index 21643a46f..a14807fa9 100644 --- a/lib/shared/page-code/script/vue2/methods.js +++ b/lib/shared/page-code/script/vue2/methods.js @@ -9,12 +9,14 @@ export default function (code) { let methods = '' // 当页面中含有导航布局或使用了远程函数或使用了函数管理中的函数时,methods里面会有内容 - if (code.hasLayout || code.remoteDataStr || (code.usingFuncCodes.length && code.pageType !== 'projectCode') || code.eventActionList?.length > 0) { + if (code.hasLayout || code.remoteDataStr || (code.usingFuncCodes.length && code.pageType !== 'projectCode') || code.eventActionList?.length > 0 || code.flowActionList?.length > 0) { let methodsCon = '' - // web端导航布局相关的方法 if (code.hasLayout && code.platform !== 'MOBILE') { + // web端导航布局相关的方法 methodsCon += getWebNavMethods(code.pageType, code.layoutType, code.uniqueKey, code.deletePageCodes) + // 切换语种 + methodsCon += getLangMethodStr(code.pageType) } // 远程数据源方法 @@ -45,6 +47,21 @@ export default function (code) { }) } + // 操作函数事件 + if (code.flowActionList?.length > 0) { + code.flowActionList.forEach((action) => { + methodsCon += ` + async ${action.name} () { + try { + ${action.code} + } catch (error) { + console.error(error) + } + }, + ` + }) + } + // 预览和查看源码,函数写在页面里面 if (code.pageType !== 'projectCode') methodsCon += code.methodStrList.map((func) => (func.funcStr)).join(',') @@ -60,6 +77,11 @@ export default function (code) { function getWebNavMethods (pageType, layoutType, uniqueKey, deletePageCodes) { /* eslint-disable indent */ let navStr = ` + toRouterName (routeName) { + if (routeName) { + this.$router.push({ name: routeName }) + } + }, goToPage (item) { let routeUrl = {} const getRouterUrl = (openNewHref) => { @@ -174,3 +196,13 @@ function getWebNavMethods (pageType, layoutType, uniqueKey, deletePageCodes) { /* eslint-enable indent */ return navStr } +// 切换语种 +function getLangMethodStr (pageType) { + let changelgStr = 'changeLang' + if (pageType === 'preview') { + changelgStr = 'this.$changeLang' + } + return `handleLanguageChange(lang) { + ${changelgStr}(lang.id) + },` +} diff --git a/lib/shared/page-code/style/nav-style/pc-nav.js b/lib/shared/page-code/style/nav-style/pc-nav.js index cb97fca76..3ec49227d 100644 --- a/lib/shared/page-code/style/nav-style/pc-nav.js +++ b/lib/shared/page-code/style/nav-style/pc-nav.js @@ -14,9 +14,17 @@ export default function (layoutType, layoutContent) { .bk-navigation .bk-navigation-wrapper { height:calc(100vh - 252px)!important; } + .title-icon, + .title-desc + { + cursor: pointer; + } .bk-navigation-wrapper .navigation-container .container-content { padding: 0px !important; } + .bk-lesscode-navigation-wrapper .navigation-container .container-content { + padding: 0px !important; + } .navigation-header { -webkit-box-flex:1; -ms-flex:1; @@ -50,6 +58,31 @@ export default function (layoutType, layoutContent) { max-height: 52px; overflow: hidden; } + + .header-right-area .info-language { + cursor: pointer; + margin-right: 16px; + } + .header-right-area .info-language i { + font-size: 18px; + color: #979aa5; + } + .popover-link { + display: block; + padding: 8px 12px; + font-size: 12px; + color: #63656E; + cursor: pointer; + } + .popover-link:hover, .popover-link.active { + color:#3A84FF; + background: #F0F1F5; + } + .popover-link .language-icon { + margin-right: 4px; + font-size: 16px; + } + .navigation-header .header-nav-item, .navigation-header .is-complex-item { list-style:none; diff --git a/lib/shared/page-code/template/navigation/web-navigation.js b/lib/shared/page-code/template/navigation/web-navigation.js index 032aa0d7b..e94f82c06 100644 --- a/lib/shared/page-code/template/navigation/web-navigation.js +++ b/lib/shared/page-code/template/navigation/web-navigation.js @@ -1,6 +1,6 @@ import generateLayout from '../page/layout' import generateHelpMenu from '../page/layout/help-menu-layout' - +import generateInternationMenu from '../page/layout/i18-layout' /** * @desc pc端顶部导航布局 * @param { CodeModel } code @@ -20,10 +20,10 @@ export function getTopBottomLayout (code, pageContent, componentProps) { return `