From 260e17980c5a89b7d854d453bf386113af385c9e Mon Sep 17 00:00:00 2001 From: JackLian Date: Mon, 28 Nov 2022 12:18:56 +0800 Subject: [PATCH] feat: add docs site --- docs/.gitignore | 20 + docs/README.md | 46 + docs/babel.config.js | 3 + docs/{ => community}/code-specification.md | 4 + docs/community/img/i-see.png | Bin 0 -> 144153 bytes docs/community/img/you-think.png | Bin 0 -> 106090 bytes docs/community/issue.md | 112 +++ docs/config/navbar.js | 82 ++ docs/config/sidebars.js | 55 ++ docs/config/sidebarsCommunity.js | 13 + docs/docs/api/common.md | 57 ++ docs/docs/api/config.md | 107 +++ docs/docs/api/datasource.md | 29 + docs/docs/api/event.md | 81 ++ docs/docs/api/hotkey.md | 70 ++ docs/docs/api/index.md | 16 + docs/docs/api/init.md | 138 +++ docs/docs/api/logger.md | 49 + docs/docs/api/material.md | 320 +++++++ docs/docs/api/plugins.md | 281 ++++++ docs/docs/api/project.md | 881 ++++++++++++++++++ docs/docs/api/setters.md | 346 +++++++ docs/docs/api/simulatorHost.md | 31 + docs/docs/api/skeleton.md | 278 ++++++ docs/docs/article/index.md | 38 + docs/docs/demoUsage/advanced/_category_.json | 6 + docs/docs/demoUsage/advanced/hotkey.md | 24 + docs/docs/demoUsage/appendix/_category_.json | 6 + docs/docs/demoUsage/appendix/loop.md | 17 + docs/docs/demoUsage/intro.md | 80 ++ docs/docs/demoUsage/makeStuff/_category_.json | 6 + docs/docs/demoUsage/makeStuff/dialog.md | 33 + docs/docs/demoUsage/makeStuff/table.md | 128 +++ docs/docs/demoUsage/panels/_category_.json | 6 + docs/docs/demoUsage/panels/canvas.md | 74 ++ docs/docs/demoUsage/panels/code.md | 165 ++++ docs/docs/demoUsage/panels/component.md | 32 + docs/docs/demoUsage/panels/datasource.md | 150 +++ docs/docs/demoUsage/panels/settings.md | 217 +++++ docs/docs/faq/faq001.md | 6 + docs/docs/faq/faq002.md | 36 + docs/docs/faq/faq003.md | 32 + docs/docs/faq/faq004.md | 59 ++ docs/docs/faq/faq005.md | 23 + docs/docs/faq/faq006.md | 6 + docs/docs/faq/faq007.md | 9 + docs/docs/faq/faq008.md | 7 + docs/docs/faq/faq009.md | 74 ++ docs/docs/faq/faq010.md | 18 + docs/docs/faq/faq011.md | 29 + docs/docs/faq/faq012.md | 90 ++ docs/docs/faq/faq013.md | 22 + docs/docs/faq/faq014.md | 20 + docs/docs/faq/faq015.md | 7 + docs/docs/faq/faq016.md | 19 + docs/docs/faq/faq017.md | 11 + docs/docs/faq/faq018.md | 10 + docs/docs/faq/faq019.md | 10 + docs/docs/faq/faq020.md | 44 + docs/docs/faq/index.md | 7 + docs/docs/guide/appendix/_category_.json | 6 + docs/docs/guide/appendix/glossary.md | 5 + docs/docs/guide/appendix/metaSpec.md | 7 + docs/docs/guide/appendix/npms.md | 49 + docs/docs/guide/appendix/repos.md | 87 ++ .../appendix/setterDetails/_category_.json | 4 + .../guide/appendix/setterDetails/array.md | 68 ++ .../guide/appendix/setterDetails/behavior.md | 5 + .../docs/guide/appendix/setterDetails/bool.md | 15 + .../guide/appendix/setterDetails/color.md | 14 + .../guide/appendix/setterDetails/event.md | 73 ++ .../docs/guide/appendix/setterDetails/icon.md | 81 ++ .../guide/appendix/setterDetails/mixed.md | 13 + .../guide/appendix/setterDetails/number.md | 19 + .../appendix/setterDetails/radioGroup.md | 24 + .../guide/appendix/setterDetails/select.md | 24 + .../docs/guide/appendix/setterDetails/slot.md | 93 ++ .../guide/appendix/setterDetails/string.md | 14 + .../guide/appendix/setterDetails/style.md | 49 + .../guide/appendix/setterDetails/textArea.md | 14 + .../guide/appendix/setterDetails/variable.md | 12 + docs/docs/guide/appendix/setters.md | 41 + docs/docs/guide/create/_category_.json | 6 + docs/docs/guide/create/useEditor.md | 184 ++++ docs/docs/guide/create/useRenderer.md | 100 ++ docs/docs/guide/design/_category_.json | 6 + docs/docs/guide/design/datasourceEngine.md | 153 +++ docs/docs/guide/design/editor.md | 283 ++++++ docs/docs/guide/design/generator.md | 100 ++ docs/docs/guide/design/materialParser.md | 70 ++ docs/docs/guide/design/renderer.md | 210 +++++ docs/docs/guide/design/setter.md | 86 ++ docs/docs/guide/design/specs.md | 60 ++ docs/docs/guide/design/summary.md | 57 ++ docs/docs/guide/expand/_category_.json | 6 + docs/docs/guide/expand/editor/_category_.json | 4 + docs/docs/guide/expand/editor/cli.md | 181 ++++ docs/docs/guide/expand/editor/material.md | 271 ++++++ docs/docs/guide/expand/editor/metaSpec.md | 535 +++++++++++ docs/docs/guide/expand/editor/partsIntro.md | 100 ++ .../guide/expand/editor/pluginContextMenu.md | 69 ++ docs/docs/guide/expand/editor/pluginWidget.md | 170 ++++ docs/docs/guide/expand/editor/setter.md | 202 ++++ docs/docs/guide/expand/editor/summary.md | 90 ++ .../docs/guide/expand/runtime/_category_.json | 4 + .../guide/expand/runtime/codeGeneration.md | 132 +++ docs/docs/guide/expand/runtime/renderer.md | 349 +++++++ docs/docs/guide/quickStart/_category_.json | 6 + docs/docs/guide/quickStart/demo.md | 47 + docs/docs/guide/quickStart/intro.md | 48 + docs/docs/guide/quickStart/start.md | 296 ++++++ docs/docs/participate/config.md | 80 ++ docs/docs/participate/flow.md | 96 ++ docs/docs/participate/index.md | 26 + docs/docs/participate/prepare.md | 61 ++ docs/docusaurus.config.js | 132 +++ docs/package.json | 62 ++ docs/scripts/getDocsFromDir.js | 60 ++ docs/src/css/custom.css | 103 ++ docs/src/pages/index.module.css | 29 + docs/src/pages/index.tsx | 40 + docs/src/pages/markdown-page.md | 15 + docs/static/img/docusaurus.png | Bin 0 -> 5142 bytes docs/static/img/logo.svg | 1 + .../static/img/undraw_docusaurus_mountain.svg | 171 ++++ docs/static/img/undraw_docusaurus_react.svg | 170 ++++ docs/static/img/undraw_docusaurus_tree.svg | 40 + docs/tsconfig.json | 7 + 128 files changed, 10175 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/README.md create mode 100644 docs/babel.config.js rename docs/{ => community}/code-specification.md (97%) create mode 100644 docs/community/img/i-see.png create mode 100644 docs/community/img/you-think.png create mode 100644 docs/community/issue.md create mode 100644 docs/config/navbar.js create mode 100644 docs/config/sidebars.js create mode 100644 docs/config/sidebarsCommunity.js create mode 100644 docs/docs/api/common.md create mode 100644 docs/docs/api/config.md create mode 100644 docs/docs/api/datasource.md create mode 100644 docs/docs/api/event.md create mode 100644 docs/docs/api/hotkey.md create mode 100644 docs/docs/api/index.md create mode 100644 docs/docs/api/init.md create mode 100644 docs/docs/api/logger.md create mode 100644 docs/docs/api/material.md create mode 100644 docs/docs/api/plugins.md create mode 100644 docs/docs/api/project.md create mode 100644 docs/docs/api/setters.md create mode 100644 docs/docs/api/simulatorHost.md create mode 100644 docs/docs/api/skeleton.md create mode 100644 docs/docs/article/index.md create mode 100644 docs/docs/demoUsage/advanced/_category_.json create mode 100644 docs/docs/demoUsage/advanced/hotkey.md create mode 100644 docs/docs/demoUsage/appendix/_category_.json create mode 100644 docs/docs/demoUsage/appendix/loop.md create mode 100644 docs/docs/demoUsage/intro.md create mode 100644 docs/docs/demoUsage/makeStuff/_category_.json create mode 100644 docs/docs/demoUsage/makeStuff/dialog.md create mode 100644 docs/docs/demoUsage/makeStuff/table.md create mode 100644 docs/docs/demoUsage/panels/_category_.json create mode 100644 docs/docs/demoUsage/panels/canvas.md create mode 100644 docs/docs/demoUsage/panels/code.md create mode 100644 docs/docs/demoUsage/panels/component.md create mode 100644 docs/docs/demoUsage/panels/datasource.md create mode 100644 docs/docs/demoUsage/panels/settings.md create mode 100644 docs/docs/faq/faq001.md create mode 100644 docs/docs/faq/faq002.md create mode 100644 docs/docs/faq/faq003.md create mode 100644 docs/docs/faq/faq004.md create mode 100644 docs/docs/faq/faq005.md create mode 100644 docs/docs/faq/faq006.md create mode 100644 docs/docs/faq/faq007.md create mode 100644 docs/docs/faq/faq008.md create mode 100644 docs/docs/faq/faq009.md create mode 100644 docs/docs/faq/faq010.md create mode 100644 docs/docs/faq/faq011.md create mode 100644 docs/docs/faq/faq012.md create mode 100644 docs/docs/faq/faq013.md create mode 100644 docs/docs/faq/faq014.md create mode 100644 docs/docs/faq/faq015.md create mode 100644 docs/docs/faq/faq016.md create mode 100644 docs/docs/faq/faq017.md create mode 100644 docs/docs/faq/faq018.md create mode 100644 docs/docs/faq/faq019.md create mode 100644 docs/docs/faq/faq020.md create mode 100644 docs/docs/faq/index.md create mode 100644 docs/docs/guide/appendix/_category_.json create mode 100644 docs/docs/guide/appendix/glossary.md create mode 100644 docs/docs/guide/appendix/metaSpec.md create mode 100644 docs/docs/guide/appendix/npms.md create mode 100644 docs/docs/guide/appendix/repos.md create mode 100644 docs/docs/guide/appendix/setterDetails/_category_.json create mode 100644 docs/docs/guide/appendix/setterDetails/array.md create mode 100644 docs/docs/guide/appendix/setterDetails/behavior.md create mode 100644 docs/docs/guide/appendix/setterDetails/bool.md create mode 100644 docs/docs/guide/appendix/setterDetails/color.md create mode 100644 docs/docs/guide/appendix/setterDetails/event.md create mode 100644 docs/docs/guide/appendix/setterDetails/icon.md create mode 100644 docs/docs/guide/appendix/setterDetails/mixed.md create mode 100644 docs/docs/guide/appendix/setterDetails/number.md create mode 100644 docs/docs/guide/appendix/setterDetails/radioGroup.md create mode 100644 docs/docs/guide/appendix/setterDetails/select.md create mode 100644 docs/docs/guide/appendix/setterDetails/slot.md create mode 100644 docs/docs/guide/appendix/setterDetails/string.md create mode 100644 docs/docs/guide/appendix/setterDetails/style.md create mode 100644 docs/docs/guide/appendix/setterDetails/textArea.md create mode 100644 docs/docs/guide/appendix/setterDetails/variable.md create mode 100644 docs/docs/guide/appendix/setters.md create mode 100644 docs/docs/guide/create/_category_.json create mode 100644 docs/docs/guide/create/useEditor.md create mode 100644 docs/docs/guide/create/useRenderer.md create mode 100644 docs/docs/guide/design/_category_.json create mode 100644 docs/docs/guide/design/datasourceEngine.md create mode 100644 docs/docs/guide/design/editor.md create mode 100644 docs/docs/guide/design/generator.md create mode 100644 docs/docs/guide/design/materialParser.md create mode 100644 docs/docs/guide/design/renderer.md create mode 100644 docs/docs/guide/design/setter.md create mode 100644 docs/docs/guide/design/specs.md create mode 100644 docs/docs/guide/design/summary.md create mode 100644 docs/docs/guide/expand/_category_.json create mode 100644 docs/docs/guide/expand/editor/_category_.json create mode 100644 docs/docs/guide/expand/editor/cli.md create mode 100644 docs/docs/guide/expand/editor/material.md create mode 100644 docs/docs/guide/expand/editor/metaSpec.md create mode 100644 docs/docs/guide/expand/editor/partsIntro.md create mode 100644 docs/docs/guide/expand/editor/pluginContextMenu.md create mode 100644 docs/docs/guide/expand/editor/pluginWidget.md create mode 100644 docs/docs/guide/expand/editor/setter.md create mode 100644 docs/docs/guide/expand/editor/summary.md create mode 100644 docs/docs/guide/expand/runtime/_category_.json create mode 100644 docs/docs/guide/expand/runtime/codeGeneration.md create mode 100644 docs/docs/guide/expand/runtime/renderer.md create mode 100644 docs/docs/guide/quickStart/_category_.json create mode 100644 docs/docs/guide/quickStart/demo.md create mode 100644 docs/docs/guide/quickStart/intro.md create mode 100644 docs/docs/guide/quickStart/start.md create mode 100644 docs/docs/participate/config.md create mode 100644 docs/docs/participate/flow.md create mode 100644 docs/docs/participate/index.md create mode 100644 docs/docs/participate/prepare.md create mode 100644 docs/docusaurus.config.js create mode 100644 docs/package.json create mode 100644 docs/scripts/getDocsFromDir.js create mode 100644 docs/src/css/custom.css create mode 100644 docs/src/pages/index.module.css create mode 100644 docs/src/pages/index.tsx create mode 100644 docs/src/pages/markdown-page.md create mode 100644 docs/static/img/docusaurus.png create mode 100644 docs/static/img/logo.svg create mode 100644 docs/static/img/undraw_docusaurus_mountain.svg create mode 100644 docs/static/img/undraw_docusaurus_react.svg create mode 100644 docs/static/img/undraw_docusaurus_tree.svg create mode 100644 docs/tsconfig.json diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..b2d6de306 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..12c8f1fbe --- /dev/null +++ b/docs/README.md @@ -0,0 +1,46 @@ +# Low-Code Engine 文档中心(site) + +This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. + +### 安装 + +``` +$ yarn +``` + +### 本地开发 + +``` +$ yarn start +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. + +### 构建 + +``` +$ yarn build +``` + +### 部署 +```bash +1. npm run build +2. npm publish # 记得改下版本号,比如 1.0.1 + +# 发布完后执行 tnpm sync +3. tnpm sync + +4. 更新 diamond 版本 1.0.1 +5. lowcode-engine.cn 站点生效 +``` + + +## 功能 +- [x] 支持本地离线搜搜 +- [x] 版本化文档管理 +- [x] 离线静态部署 +- [x] 主题(fork 宜搭开发者中心) + +## 使用文档 +https://docusaurus.io/zh-CN/docs/docs-introduction + diff --git a/docs/babel.config.js b/docs/babel.config.js new file mode 100644 index 000000000..e00595dae --- /dev/null +++ b/docs/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/docs/code-specification.md b/docs/community/code-specification.md similarity index 97% rename from docs/code-specification.md rename to docs/community/code-specification.md index 0a7c9f555..f4378ee2c 100644 --- a/docs/code-specification.md +++ b/docs/community/code-specification.md @@ -1,3 +1,7 @@ +--- +title: 编码规约 +--- + 编码规约 --- diff --git a/docs/community/img/i-see.png b/docs/community/img/i-see.png new file mode 100644 index 0000000000000000000000000000000000000000..43143ea7702721dd9e1d68794ba5dae9143cd8d4 GIT binary patch literal 144153 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%0DmM3j17m+Fk*aMwfUZEg>VnlJ}5 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 c12Z1@KOn2G8T`uqc)D zlV^|5+7UnGC6E#D5C8xGvXrE#5&!@R4FEty!a;uSU|bge1^|$(eu{|vkP;Cg{^4k6 z_S4!F0FaDGQHRw~9>dPnPK=MAhD3`;_rxetry*$qKq^7wlfaWF<6s0vcGJ_gVT+2^ z6;zT&)sjlM-0T|_}+nd;7qDXOdo1D*e=_qg@goxgH9?F&$_K1>4gSN^0(Iw~>% zt_PoHA1@8|m*?3ik8lM6D2M*=I&Hie!B}6uAm=09`=vc_{Q9136^l^g7yF?;vxR8F z3y8l0nO~ZA@rGwXfS0bwXT1U82;D>T-Vu;fAsXrkP{D1o0gcJ-VUXt3R|ybVgYN8u z>VO}Gj_tGb0Jd`xzDEBvXb(1ikyHBps6l{5tUsqe)$TQk+8{saH@gN%bwO*yGYZgv zyT9A8Teha(&J@n}fn#H69ZM~QA|l!tdHdSYhxyO<*4@3|us#|C;#ksT+gMKnc>n#N_)jG=<^NJ1C^^oTs&T>hKt16pVvm(aKxg zi~=Tgw8R|7hAbdbz+KjS5c@$vc65%odn72{vM zNI+B+W$vC7PkmRv+d0J&@k<*`P3p%`9M2zwj0=YC}aWX2y5ns%l z$aa#iHt7V}41z#yFS~W7{$s@W;aZ(|9h=|wBylz&X+|L-H_K*`CM9uk&<4yQseyii z9(_=SkUs!-`k_s~OUXtFa2RmX6QFX1vSAAZ8DANQ(kTe9Q;~9oV-AVd+v~0j zy4ve#hCjM}f4s;I1ZG;Ki1QyNlyg}~AyI^a$ifXpdDcLv;I~VQN2I_BtcPJAn43p4 z1$S`^GG59x;wFr9S@emtchH{2G|JO}O7rMr4|d!4-CJ;wgWo7hh69W(9D#$|VOR*P z@D$;XKWanOWe7L0=e?$v|9XElkntobUNbp@R_;eK@b2*lMd%~7{Pc-Tt8hi`%ur)q z5A)u1@1E-7o@datqt8Yrji~BwmeHypnCf>l$XwxhD!?L@Cr998yjyGmkOIms+S>q; z`ku0)REATvQ|XQQo<@&>R*%t6J(Wy}jF@J>HzlI)SDjU4N|HfL=GM6)2Db)N zuKk1o12q8J>2Y+IKwo0G;6OyNRC)CO1MkH1N%!}oO)>k#Hbh$xVlJUDb34QddxUYG!+K8O!5D4bwHPADZd zn?NrbJ(`d)3RwiC5u;6*kVL+wbA;mx$d>pU&ofF^gy9k8EzF!8d0a4D_MIF3PF&Lh zGc#OXuyLBkf}{ha8>*JSaLCw1$QPg|YL|a@NY?}lmCij39%qsm$2T^v*T74Skg8wN zU{;G&HFnX!c+IOdJk$7ck4ClaoE^c_k3|qbwZ-d=z>lvVslLr~>G_K3i|iNdwxbWv z7Kqjtuqm?%8b)t`Q-&7{cI;>G7wZ4nmc|Wt5g|$%WFc}J=*66pE|)E&Bdw#bCSpz5 zf|dvJk|a-r7z@}{h@^0kdMYBF)tY6Tb(mFEf-P5<)16S6P-T#1kdv!o*RR&GDgDdb zTxmJA@I0zzLf7z(D|MVof$AHT9R)|iw{e|egxta_(OjxWXBZBxx#N2L zds2Ke*MG^(H_z-Hnw_K`o-f)goGs4HI4#L6W|gQHyQ$RX1Ta_O5{NSza~TuuQ0~kz z?HE=W1{%tQE+BVup9a0#zY+Tc_}0G@zTrRTA*I9og5iRpL&ifQ#MutL4>d+A#j|DN zwORhI%JhzYY(2G+RpxGgJLBOGh{idmytBfk^=4f)N}zSlUB#t-@~ z1{Q|j^jh>C3~~$-+LKLH&0SVvE!QpRTGjR3R(E}Whq;G56I_Ds5b=HRW95FyRZsY6 zXWHbSVXj)Pp16wg*mV+g-npuuDte)M{qjk9GliY+n-kitTmNMPGz^}Cy9z%>Yb7X; zr5}MD(HfDEM=G@_eUe9(ua(e>mBnv&++9h~&*95<%4y}^aGG(5aL%}x-MwC1SWh|7 z+VlJuvyZ*cK2kk%7SkEj3HAwku|e$^v4|5RWFXY%?dC1cb;-@qOVvBlJL$6ZIPo0u z_;$yCAaV(RpRwaKcu-hdYS(kYzaj8O@aFjH`rM6U$|}udc~VC8zW=WCG4Xx|G46ls zF9&fJU>2|jJ_e5nVGF(H+Xzi*x>i{)GA^DN92oq(?At`O*1xBBb%Mi2CsZPs#jC)` z#MHulW*XkPG;#ni}7I$|7l919GnGQ0<64($i z#Mt7+#KsbITi(9mRN^?uiqMDe4c0{ZfXp#CD9$f&w$s~~RgH6pItQns1+oMc(v$00 zw|tE3x334iq*|n|i&9M{cCW_1j}a*eD@iN=9f>92u{s@68BkHQG{&Q0;Ih%ynrfSN zs2p*Le!$UZd*26U!Ou}<=UH$vyKT=%WhS*H-Irp_D$lx=`0WonR<`ruW=Ff={^Mo0 zL%zTg2-hFJz6Vg*)XVrjEPFZojO(qP zrrqm${(ANXCBkKpYg;){i@Qnqv- zQtxHN`S}(3ZkE^cZ*xth5v97BwgNt5w+A7HA-&^Z^Gma)akeZjyZu7VD$Oi&vPM?3 z*75rEMtk{@waa#%eZ~E~@fVw(o7dSVKL$z7>85G-t+fyZUxbhH7Rf?MEoZBQje?k{ ztD%CSYMHgvD&6bmbbaq%gY89KyEjVfvxownuR2#w9}VNC)qnWX>@OgDdj zE&f{^QmmjBqo=ArOi#!7Vs%%vaPa5fAC8(|RSC5}Yr|_SbgbH4hyG5b^#8_J?Njwy z#I4qDI3MtQf3Cz2WS_TTTi0l~nxmX4SYSW?$kPqQPjrCGkVwz^(y>~;T8YMijw z9&7owI<&g#Hh96Qx}&PxIA?Kit<|Kax7u#?T0_?<;>xkewP>?Gu&mthf^cfwp_qhx^u5u9=k6QbZMof)-`-8V}d~?wzu+d~K5rg>c6jey&3 z*Ts{_m#%MYI=5XKeq%eceH*;KJj=%`T7D}hjyqU+baFO@e`9r#yF9nGMw?lexR$=H zhquqTvL5Z9Y46xIZ|raQI#4}KW_3cly_`>UE41mq3Z4i(i4=w{^sc`3l>TdcJ1mTs zNF$`;?Rf}(*j-RwnVb5Gm^YJ~BEaFJchi42lrUL0N%w7}`_!GnV_`}4Hte40L~pf+ z|I&OqFRf$5)vYU`=g6<~Hm9%i%&+}pVHRWQ=F(^Vr3ZDW8>X9w-_wuxg=TN=rl&kl zDvz#b+2`jo-rb$ApPpZDFx?&s0W{O{^tdcw-4@nA6#?*deU*AA*{u&kDEVOt0{D)h zw*Ccn5W}B)nI`t?)C_RnZ}t_~Zdl9NPXz-pKVef=E}D9@U$I-zp9T8W+WV2i3?O&U zeIh-(xX|9%_%NkIx<>&_C?~u92mx*77%GQD{?q(!$oTlle(u8YwH`JSZmlowtryU! zpFoR8g0=QpeQ=s;NSVpW0cbzhZ~%y~p8(+J3gYv@`+PnNm3+wmzXJ)K5B2}o5Rv~K z{?hqX0{{pDq(p^O+#yc?{j9T5SN(Q55@f#dfSR10l+PxO4+kmLl_n$077r9IASQ*7 zu6z}I8jE|LnEj@vX_}G>rwUJg3lh%bPar2RAdLx!e~1)P#H_!_op?EWQyA9O^ty0T zXq;NNy5Ktq^BBf+8fQKrFdk<%Yh2_GXa^v|B*Pl z#Z~@_(EmS|gk&IOhAK^&l4+WXQ%0d2*$l#mYr%&^x{znGn0obUSU)kXQN73Zd z+d_>&lmY*v1R&we;57MvR40$_zC1vvHt>}9@sxK?_UiYx zr^)soQv)#yP^rMt+sE0)yWGr&FO#aZV0!@>-@I+ByM5WV&3M+Q)Y#+@jC$KwJGVE~ zAV3EnoDk{?#j;6T&KH}8`_<(v;G!txBv+C9llv<#|2A5jKhQjTh05Q2C}=S8JKFE{ zu$k??b<;)5|D;Beg>0shWSZvjXS!7QxK#+?i$tbLnTmvk!e1=toSTfoX1H3^`@{yk zMgh9`1^`$?ULPLgR8^m%T0Q_UUQIa6wy0>9(C6-9t;!eK%P0e4k^pWScaOq>$q(U) zQ3xsQYZp^2u`L4W6d9p*pQvi!!rR}vLOC*n+d*zb;{vY(0&p-N=GmJk-<1cIM_RTV z3eHeVw8TW%{}WFU(iO1d@*&d0M7^u-v!H%-+xvdf>p5}zNB{l$?O_A;@%2F9qk7}H zTL0rt|Ksb%)z^(1zKx*Atlp=rp`hBw$lAv=zo)d?*Wb06JmYXB!Cc`Y+A;FF6}eIRcMC z0+0XclPml-gdi`#TqZ!=0L02`M2bTWxGm`ai`X<2)U57+Z;%9jmdroM_c-G`|9GAH z*xjza^|*gI@4OHpdY+aLc%BjXxDhzmhK4E?XGiwCrt-V?8$bdmZuH zEbovsa01z0v#EUYvL8uka<40N*E0O?TY$-@5#c>YtZq)P{uKm56$re|3G5x_py;}; zn8rqAfAu*vCVGkLeT>=}iCg8P_8ex-C6V`jY4z)5BF5Q#`WXJ1=pinED8RHr(8R+) zNT3o}?fV~J5V^s^RAVQrz1nT@H=iTsI1i*!zpaoj++Q7Q?I*bo)_h;iTHuI29!EYQ z^?OQ`VXV(++YChPd9Li$f}8PM=#=kug@Hygj1K!3hK-7%-5=xkf4W0H?XqI{TOVo- zRmSh*!SAD7!RHwD{WEKUo8*5vEtH7Mb^ZHu@ol^B^X$QIj>Bh8|9ww?-F+v9W5fH# zYyF1sKx}n&Rl##MDcL9xDa;ds23d}sqcs63Q85ZONJZvE<-hmTP=K>|zHR?LLnVOq zxr)-i^*t&idOIB9J9mhe4}L$tct6MQy}~EDE0f^jY0wqCe_rZ+Z&|f{skwNrp{r># zva&+#UL1t-o;@`i4!Zw9b+vJhsr%0?%h2mOrtmgURoayHHyAz`Ue9pA1(1*debo(o zeN%61Q*XNp@4FkX6B{}{cMC+1VAwSdoYWvPI|6tpDJK72h*0iN1*jYf@{n%4vivjy zP8E2KzuH3w(UKy(%}yT#Zv%99wEw3Seh+Sb54h&pBrtF={ZNABg8MUyiCMd!YW`G% z9USb|wexjJ->+LeQN?X4Ita9_A*CaQ^MeohCnkj2r7)rfmf+~-0<=s#1+bYsy{I1* z!{R_}U|!}#_5Tne2d@@1LT!fkWwWGjxLuQdA=LR2l=FpV;sEX*!WG^;*JGbb;7O|H zqo(ux+9hxm+M)k0s8#+9>(PxcdnD~r+CG;w0Od+_;MeGutvs8*fOWbeoLmN%F)8A5Nb> z&+ELsUGIHVg1_JsFWEJ2IH-G&1ylXhWo{zyJ=N|BJC;xZsgZ0Z>^RPXguM*0Tt&Fu zv3w{fMOpL2J_)=po&Wj`jhvkPARHQs2=|e4rPT~2${LRN>HI%e@!lx#!g?TJ8#l#$ zLhrE6n)ei~Md)`J<~tg@759D^r$ge7m7?&_of$;ji2!Utv?V^p_a3=yD&?CG@}vMPGg>g0}fMatX>?YB5gYuK-D)f~I6JXgj9HaN8vzKnydmAIKPS^dnaxN+DVRRiy1O%sh z5#c{X0HXoL{Ni?>bXmDe!>2`TlAxElgThXj5nTfUUM4+IIBcReRYpPqxM0Y^c2KIcy7d3z{nDrmI6o3OrMlmQN8 zb_}gSF5Rjg51^Zf@E6}v_t2|Pl$ma)J!d{qwg zosF07jn2D8Z5z&4Guqu9q7Qfc*kA}nQEPxgtl4ItV3;;<`n=WvK>^TAE~5PA0Q54W zfvOFu>jk$qba}c~qwc@Gmouf&vf=aaPwfh6oMj_akcmljL}D@!)CqJ2?m~hY`>qo7 z1ypzY!7hP&yw z3FE2n_Z#w(zyd-|mMKkOPuPN56C*icPwz+@^CT zc?8W#QP+2U0*(cwm^G}Pi%>eJouv3%%areq4fqgDZx+7#af6t+I zT>Bzz0rUZ1MU*Lv4~Od|XE&~elu z*kST*-A8hE*YAm1(s2Y_+iOy=U`U+Ybc?^PLU)ERP?45bn+A)2EiV)yGZvCkV0c6* zJO45v#tW`Up>C#m7=Eog?%mI{s_DMhI$;0LO!aRvmoDemfqMyJaOOf?OsI^2%h7ep zMD1TZq7C?)ogo24PYFa*_R;aTL#>)k4!g3ieN;w^i$Zk+%f6 zP4Pz^8(i#os|WP_c-go>CR)E=t~VBV-x07C0qLb4>%laSyAka2hMA{fj#ckbpoNg_ zZ{L)F@@gdAH?MR>U&vz$zp&04v14nA`pXhK+|UzhJdzDagMUN{O!hcC)n0~jbO}{m zi!~`Z2+Pv;#^GqkNqv7LK5>W;jDnTH^tI~vPm&HGEdW?1!7;|$rrK5LcX9t1m#&A~ zfG$C-qS3O-Xg?_LTQ#RkvU^E&;vARVqZJ4dS8K?a3!4Te==LkMhUzj z2u%JF$q^X5e+wjHTyGJav;ETKvhH!vJ~rJ{iy=z3gw-Pt9;^{Y66E!13hHZl+3hPc z>gK-Q#4Gz~Z8fbCDx_i^sI+y+aO zO%t(k-dx~3i}xAn6Ky&L4$f3&A)5b!({5Lne_KxQI{FXz#Ts{FekseXynS$Ds7e|n zs?ZSwA(9ZH)pD37C4VODT%snkDuPAnHohYtIY>L501@^#*#U9x>Ry_MlH}x)8w^U6 z%9Vf(W6z@zooDQFg$|9Iwd$m1rB(B!oAxA`Jp#FVIH({5o8cxTBb{Kso>?^*;nisb z{L71OYz%eMx1`^7u+;L?RM*+Up!SbDQbA14;?w;gB_i;)+`1nYs*XHyk!}F2 zDXHi)AU7GT#B|nWL1VO$EN*=MZzhxK-$-B>D@BD2vW7qK(MUW|$H@R5{C&j5aqjA$ z1*g%4^K(@PWuP{8EM10CK~%(rj3&*hEnYh! z+E3~xSV28Box@l|wi&yP#!I(r0ZQo|?Rz#Jd^Qeha4?umE(w{pZarMX6!p+Uwi22d zBOuTL!!x~UYB^7j1bxNMdSW5!f6n`AEwztUDGt*Is7pmik<~I=xIjtd-mkLjQTne& zwfHL&a5C~W&M>fFB|Cp=wW19mkvF?DfyAoJInp}=C{W{{>Rx8l{{2(`nM_9&kgE_q zYfSq}kNRhUyYi|p0E_$MoD%$Z?J6qrs+adWY)+KXV0LXJ_Ktyf)e6J%*T3MhZC(i) znt+`&=9w~r^;&)J#{r`K&aVhpJ6&8T{XxyWFHn--8lMxN_%1x+z?c68Zr>Lz$!WT^ zAHPGeN0cEWC||wxYA{ZDZ>6ce7b!9$nTl(HXR(&H`({<^a=(WE!pA4JzdlW)8nIrN zC&f3LBL}nMIqJ|Jpu{S`+E<}Oac+O+B}@HX47HhJd>h9L#Q4zbyc@XSI%)#Wt3fOh z2$pv(UV9+s3TcpnO8gGEQTzQ5*Yjt^VzhmZ=d?k1k{;^ry7Z^V(LS@t3ne)?hQOYJ zvKVW9LpPAnlSJCN+#!Z)&>|-oUn4j9ou0$)%AoyP|7H;b3W>#k?KYj;OxZ!tG5-f+pRqNyJ zs!8JDe;W&1kCt#mY2Nb<5wAQLQ|Y_wd%2l6srkU(N`Mfapu zISeX7O{C?{S2K&5q~iYx$xH#62rhS*%={|)gS)7HB81`lc*qnC3$v8bL=`WOsUeuA zj%$B&f$cIjUtQ?*H$#=X-<%-nWhPE=DlQ8R-?$voqu%=McbZDoLDA|aA}9lfY?^OB zXTSs371=CEF6LuI&tnMi5L!jjA$hea%B&gq$dNK|l$_^X- zK9p^(+#DMrKFIxSMj<3jUVm#mwZrc8xXKvv^M8zlHN&Liu&ci4B6Yl5P}io*2t(13 zqa`|hG-XbjE}&uU(@|QI&z`PYDnVhe6;{MlJf_Z9w@Q8WGC6Y~M$gxutcTN~x^;mdv>w&5Sm!OK8p-<3raU1xh3;adg!)|I^k#5d0m7wF1kvDy&HI_gU()l;ZFqdQU z=}02)IeM;*1|#PHvk#{n4g03zY~4tI5s}9!HWoz7rFDw(ekMfbX82pu_u3fGfxG5f zpXy3u2HFXS_*f5T#c>BjtVND~f4GE?KP8wVhQ$23=PBfsMEf?3aQKM^WEJqJnM|N9 zq1>22N|hyu!kR5GJ@IEG0g%`kVK^Kj{as_~lcoB0cJ)sA>z}_(bHo;`9{K87WlhI# z>(>1SYF3DG=4OM2qg;v}S*&7cf2+S!6$_U^iGu2G|6=8i(J08{#%NFR`gkdq#yZeZ z`)N%Kzelwzp@|WO;p4O}<4;~nS6@|Ykpq261{nJm9K5@WqhD;QZ;k*|G9A0rV-X-$ zHO0B@08L_M7j0aDTuYAA8Hc>eJTg#jZ;FoR)x8*Lg;aGGwNF^!<+9XB5;in?7h# z=QOtJb6q|pi5sPGlQLHNV{#^I;YV(0DMW~Y7D3Vh|RuXV3VKTSSMlOu2J z$L?eKz1Aq*;Kjtnnjm{Olbk6@ks*s31S1(j6pf=L;hG3Ykz**45Tg&Ou6zVjeK$` zA!FpLdR|Spg#4!4G+dQT{5H2CC?K;{M|jg_vwPeW@@}#ZHuDb%zsCQK<$Omsd*xBk zM&=2X#by;wI3n)Qz$#w-H);MudCyhQWR)me*ngfWB~ZF#+nnTJ$Q=9Q*Vb5=!V;Z7 z7<@<(yDoc)KXg9{8KjhD`^~m9mepqcb)SRB{jEIjx!j!pCWA6G5?ifgasF3EtD?{z zgxQz}wlD2Xs7?p)(Smj#qdbL~3F!-r+Qh8|?)hkfz@Bzv#5kmYk9eH;H4aYvh(V%p z4Lm$DXN|to&-s3VN{m6Hpzo0%y=DQZi^zFHPqjSJYMtj9Arv|F!*1QD6X8uEtaE$k zGf>k81z6b-u_@&HHWa%&2imsnL*caFJ4u Di(~?sxEk`-Fv6qy6#!2Us{jt2`eQMS54{Yk-eHXbnv5aJlFhCWkEhDmo-g`VBcV}}Nxu6!DtSG>^x zkuy$49Cdr_+U7Bs4exLW*N_csCAM8#eU$@OuR%!*Gx&W#d!p=5JI_;qgh3-Djd|q} z)3@5yMAM$VSiWG|{!j`5&+5THK`!V-Fm_6h`gXQUYpTT4Afr_@Yd+E>7NDS8y3i!^ zMw`1}rtwv-&YeV0g8O)J)(9{I1+qlsR(dBI0mCG1peVH830(jY!&bFp=~(ru4&sd* zIl`Wl2$7D)s-w%yl6mjV>Sm;u(eR|hWM6~3+)8cWW$znF9}+V~P1m(_CBsKM2-a}m zv|kRZk^1GZiFC7Mhq+Y}C0CpHh`3)MO9tSCaRaA`!{61^Azw~B6f6>; z6q&4iH()k=Snx{h;%eu!nK4-K`>5ZRM0&N4Dke-W@d0Y)B}qCi1>)eZ%hB3w89!+q z5!4jSD2uqF;mKK~c7LS($){vR9_9sJ*jZFBDv^V=s1x1><&--_kH-B&e$eS(RLlK+ z5GlQMMO8brbSRv+;dks)+dJaLK;QtLidMj{;(z@;Zjr2H@U+|@FOJu&gxRHqw-B72 z276dKc|uD$u7?UikD;U#^;Q|ec{N@*9g-Mw!mfd367I#vJ3oSrDo?UL3%jR?T0D#i zPWqlPO77Ur+)Z+jkSq&N%8~*9gQ;%hVf4^&93m;T9g4TVu>RU;#umZ=%P-XJ@CF{^FYO{T3nKP!^VO%I!evi_~XfJKfz2n06I zn>pF~#oyw73Az%02q}2%_y1WIS@!8jfA?dC^jNS%93rn2Dq>_VfWW0$*`3-DbBP-q zNoej%hYX)hyVZ?%SH{&WtZ#l#f40UJw@2B985;Wo{3$+U^q-YvgvAyRIE}viJVoy+ zKA7HoKB@Iv>RjK0HSwOct1Uq&=1#FTr(t(oHK53Kc!6&zxLESr(4KGy@UK5pW2$fU?4 zER0!TE;=L^)JR=7Q`q|^eDC~WaKX^fm~GOspMg-Fch9?R%AlLOTIZ( znTi*M6L%S-NE7swn=6fFkMh0iL(O9XC<@V_u;rcw9`9F4ENDVcDu)#60v(I50R~mq zI7s}4wv`o7ylpYH1gp1mjB3f-x1Vjl>;5l2(@U14I8?p00+aFy8F0@LDND!Bgr~M^ zOO{&z{c44?V?$z&s~3~NRL@x?O4?FX_5|MhFBmk+f;#tu^vgogP*YNq-Jy%839^=_ z354*HMI+~5Be?OdgGy3=n&#Kr9t<7U(0shXG!CAQ5oLF>B zkY3QLyZenRrd}jp%(5w0L6>NMn~j?D$xfKFIakK% zVZdkHiVm;Be;89=cj3MAH6_lXgHxY8t>AbRW78KjLpI}ID$>g?4OA!5c^inby;4E# zG%2*?nCQ$!m{!3GE@#^`Y0gj(``N=h8&C3j>pOJ%Prti(C+IgGhdDf{e!H>j^|pA~ zkCsvwCMGCloMNIsEQSk1 zLMnpe?&%ar#jG%D74RLQ5jIw0f%MAi6{L^=nYnDO)3Bcq1|TSFkpY5?wD8A=*6uT# z3-=M8M}*;1&g83sRf-dI&r)$(78!hL{XayTK$iUZ0U*RHrN>Q zw+3tjlfMf!qZf-$%y4MV6)QqnqiJA-hSAO!1r-@f00s z^~4Pr%jmKm(~OCD{p=n0{Y!i_d^xm8n8;V>Q2U*&l6)B!p=m@&EFKuTCkapccb3|d z_3Nzc5*;%<|1EYxPkk%=<{Our?rfP7_9&G*e1D)gI+C|+DT%Bzpm;&)4kSLK6x=&` z<-cD*{v5srpBZ?R_?#gI*|Si&XDQFnR@6nW;9)_XrN)#nE%KYeZiS%OQAy9c#l=N$ ziw{DrK#Uhn**sd+Y3FaDH4OrPmpDA@c*N3RI2uM^{QxzoIU?y;AY3jT6-M$tYCTwE z=3_bT4M!@Fq5Ox|rq|EG6#u*^2i#59KHcmmYbCKn&L0#D@uh0yNE8HYRJ)II^`Eoy zz@<9Fhkf`E+Sy1_Aa{9WhyUdm_3lY&{($luB&UQNUS#I@cXn{oxKISV=9nUNWxkI| zB>lH4c$~PiK!$$4hc#pKxYN%Gi}Up0R)_gDAFFs6)bB{B=GJ!;x{w!JXig*u!&s0QDd4~i#;rJQ z|Do00zr#UFgqn>wp2g_CeL{r(dHJG{l{bV8eOS+>@D?oTXVGyj}G%o9b8 zYeTq(`1(bXVXV`r!lAJm6`HCt=hwVdGZNr4a99#&Foafx-(h@@c1bnOjN-pWGlG_1 z*QeQ9W=`J?KAmy??gUs=Ze4SXGA6b1R$ejP@mLA6~Xo~LQMNsKu zVyz;)?5&mW&3A?TR2oY+7Rw#h9JOT=YU#O*kmfb1GWRGv8Qm`=E+d@Z_8B4`1lX(%5l;Ie zg~_mHq*B6x1F*8o5Oc)o&S|3^M%|ZoQ2sIH~(nqZ0+tCeHw*x3;ov2i* zbg_K>pO|&+i@ty@m8=D$wQF$Q(b>!A?U_W!bCt#DaE%GSq}JN-66>>=Qag|P!zJH( z5Sfu6Ew36?m?wisH;4x4xm0mxTEr?1VRZ_?N)){PHxi^nOC=eu zBa|RWB}u>0!K~WupZ9P_P7t3GlyJK3ON&CcMFXdk6Lc6Dc-S&fW(@E9e+7^fvbjVg z+gBAibMn6hA~NPfC(&M)@^8B+7@vt3QQB_{nN?~14IF`8K|%<-lqfYJ3g_ zt=Ojt5xVU&IzI%=<~O(SK+;>EMW+A@A1cCHMCzr3g3wNJ0f-ULyXgPKe8$0~^sHYp z$S6sKbk$6L?GJ$(*Api_{J#@4nj{fUY~b5nRx4jcO8_f=;> zrF_VFRGnt|jfu>@N#e~}Axckj)1NvN+FsTtRA0|3~C6r zu2@iekxM(J0T@?Iyvg6&W(UG40=pfm(p_7uqQR+1%n+Cpj5M$0|5CTV z$ta3O2R~d+Qi(x_E*t-AL|TMCzBs_45us`1jxe~G3XDd*g9*j3xxfn)=kGl ziq32j(^msf#_q8z)(nVyH*jt3}Jcw1EFAMpdEs zxwu3doDH{jCFwOa=pQCfzQ_1Gu*6v|d8;$80g#ph z2b6|?5BySgg&9YOh%$}Tpi%v2uY{=Gyc^Fcxd~YK-S7ViqzM(v90)#h9MDf;Q8OsK zFxD){(DkB2btUzWNu`n#l^`7C%>VWPSGM&d;I0SJB+ZOnYR$(Mavb~p-zg~_Nnb+_qk#%0 zIg-9uW2`-CUU)b;1XBwlX+R@?78HkI38;$}uoM)?{oZmPnZ3-NM ztF7tyho<5@MJNUf+APHCgHpKBnG9}XoE5< zBc+R3Tp3feQAMZlNAj!`N*@#NpcrZf?JZ%hqa-iIV8);5q`h5a%u1=7Ob=?YpcGoE z^uav$LH7E9HzqD#2G9z7=%@+-{*Hx!VorbxnzkweZayJvWXK-fPMnz(`Cp2j!vR@w z8kQFl9sTaeWBHRV%WX?Vd-k#7!$dqI?M6;m{nscY7}-udWI7yQ(ZGWuf)w)0HhB>G zGSI8xFyH0YI`eB8x-xYO6l^%1aH@=FR=o*7fq-vzWpj~Z>TepJhSpjRWTZKFhS?CH zge8paPgG;wS~z4Z5aLV%m0D9Frn%%GCui6lsedD+gm)1F$dP`#swErmDB@p`O<0Hk zWnhe&V-bkIBj)V>zU6Uua-*JvRWMXl&2l9cC$?k{r-B;7Sp;FW5U8g{TGP(P_l>`i z+X+W$);!Vx2|Xx+Mh!Mu`feFYg`o1h=)B|hU%_TImPMXRJ+ABC2o=GHmV$<<(a`Xt zo1A;HM!8l!!F~nlpTVRrf$9hbzLBKZ$ zMR}w%{5Iaf`lDMan3)}c@e!P+cPL1FxR=Rgn|=c9K2V$eVScL01Tv|xvGfnQcukQ2 z*72&`@$5_TfikkVUE>iOx_n(3_FAfpJXff6lT5T`)pUtfp|s(^t4jShOB;x~#HQHx z{a}piFEYMoOTIC&gA9n!NUg)YaVB?R?rcLN54h})E)vMKF7QmU3snxaMldjvk5Udf zA$dU0Sp#`K*+)Sm3cgYI+qCuuZ1%A@ZH{mRYXueio(ocGEbPf;z-W5#B5pb{gqg5r z^!C8mW_HhzOuobzi3xs1^7r2)GnM=+V}xeRaXtra>1qe-2*2h6cEeYM7pf^sH%(S9 z2(_8Sqv(y5&Y8csYi0bc)R1ir&!oWGqd~@C;y1V#U0KA8<rASe)s2arWrZG8%BS*YH7p&-r0LGfbhJ{L?1n_S6 z_cd)59?hr?%<>N=$8vT8*=&isaJN(VOO>_*3dT9qAOy`sh&#dwTH?&UD?6Ooos>)! zggi~Dq{q0Togn4X!T<(j{LeK~QBbP|!bm{Ivi-c2Gd9JM9ixF!gt#VboQu0sCoJX^ zi_mr)Q*Mp8?Bh5ptyRn+89+{W+--Wd%>~m)B}*Dpc3C!NsO*x2Vs$3!`s*?{P+)Qe zp&-gRHrD&)e{QTuwhbtezcTx6wp^E zAIo$gucMEk18$cNROfNhAVf5X^iL!-F4-2uU))AGU!cboDomda0fc%OXGCodA3pJC zv*pJ(m_JiQh(6n%tNQd~=6BQ7W?0DBcG1x!G&>X2kONSzgzD7Db<~K|YE_VQ!%`PW z!-@UvfnuSiKsrNonT!f^rCW!CKPo$4KzCp76>5 z!Trwf5vax-+wce;ruuHVT^l!){_AWy61Vk1atT3?E-CfFTN1?-Uc5}qR#HheRZQu# z&=d(;SXRif!eWTZS-SV3w3ErGt{cmxBg!NWdbKSbYgm*+SI^uvCJHDHjWk#jTamLw z|8MM|B$5FV>;k~t=da0OyNV1LP((tc=sP(>arLj_LV?zOK)53t=M>1F?q zrgM&t>v`_8{5vtHXGYE+E|U**jB^F$-Djj&U?hJE1nBBa`nPEqmlkHAwD@UNOdV(mN=p{&?g%5MyVrNjPuaadAjl^EnfGx|oW zs0q?Zrao-;(JjJ%%{B%u@yi2-XDY|OB&E-Adf`aq?CMB?ngL7SEx!ZkK{$CQ5flT^ z?0(aQag*^^zlE9iZ?lDwDj$NS?WUB+EMV!MIl@G=VV(jqH@INvW!+F)2&lkRrU`Q) zvIhaWtnJSjLD(N(3Igzn5(!dgA8!}KZv|(k7ey0)vKG_ti4tSU+UTk?izSj=F1$CeC}=EOo=6|3S0niS(A&PIfj#<>KIh}+&E zhnA0}#f@6y=k=bTg_-zIiyE(to$|XD1(KfFs3Eh!g)n2%dO>(k`4%B)n{q89kGStc zxlpFwww4L(Gx*9?($%;%r=gM&!9P*D(PO;ZBHvb5>QZ(SBs%13QV|L987 zv|ikBCO3ktBycKC1k}?cTA!e{8vs5QLL2dgX3QLCa)Os8KU)`rnGtaP#`Vlq8`@z$ z`pPccDIwngh6uyZEe$|(0XEK-{ff=@YioNeGki%1>IWw;6|fEF71H-***O1q4W{De*s zhuh$9-rlSDz0KS^w~}nzm_T1^ek4htlQb2Ew9!W#zPK4WMRrc4^l6u<4nha@=EToZ z6h1;1@Q4!0>mBi&I&LQZJ@LB9w#AJIxhf3~+6kUyzfMABeEPUiO+9)wnaSPfT4`th z%?To@lF^6_$vgQ7`!7nS3Yvd#^@^E_+sm{E+bCtz)r~m?`7lvr)0)O^`N>dp$>R(6 ztRJ|#7eOi845LViMP>S@TfXT8VbRd7fQ8W!RpqV7EhKIuz9^S#_n#E>C=~p|C`D{J z!?q{jF1zc*jxYOxB@UN9$3^5*jIlZ~&2q`9x zS=F97`XZr*LVHpv=$!Rcd%qO$$jAxQM#>=fF)7O95<^l9deyY|Ft>XEd$dhVXin-D zvCb7@Imdt#)(V-eiUQ)s=mm9p#;xWwz>~!_4ry% z)>>-kCEB{>MuKx;Od%NG(jR{f1SQ(jkXVX;pPhtK^iU9|?-TDx5_ZmcW*`er=TswS zoixOmsXzFTz&B93=b&*fec}Jo{Uu#5j zu=ei}ki2`6R1=_GqEFKD79Hre7D;(7n~!8f?&S&UxzNxJ5w6BH$TAffkF#z3papUX z;xyx9kT5uRTgGodxjm{pO2&)2Rt2X-AQXuvCw%uQ3ac06Yp6RqFh9SCbErYckqN&G zuvD&zbRjHIqrLkgRzf4yR1ge#lbPPIVBDbnl3 z8h#I~E{&e#m@w-P+cu&V^-5%%KgSbZSW7>J$wG-((sY3}%Sc5Z;W10wT4K$YszuDo zLeg+{BEuAE8oSc~Gks)hVQD+9JBdd!Ohso{2+^ErPqNKAF|tx8__4i3RGlKyS4S&A zl{=Du@O`+?Jq3|&s9c=+2JuH%zz#)A@0`?Zx`PW3-izkJC!mG`jwLO>*;+^>JZ)Im z2mQwk2UGHTGbM#_c;U6ZZJb{_M8fL@n5Z0Iiaju|q;A0x=}E4=42L`{7|srg#+t>+ z%_-Wz;>QypzUueC>G=GTI03ndx00O}y4cY4vQJxBKRb9gjmY9NmF11GMeS*+xiE2h zH02K4b>3D4zAM1-Ep3-esQQXpZe@H-+8xx!xt<;XV`-toMrI34LS?o2fIXuJZz5@xuDjdt{d?iWSni$$y8|#3UY-v@^wuCC5_4BS60_S04!A{4>KYP}KAs?wnrA;J~M7Yl5$y!da@O ztyD^>^}+Ba&D+>-yWgo{O)Ug-I#^{Nko*8Edk>n`PDlNi+vsnw#7AvC;aJ<^} z=46C4=R(&mwDL=ley ze@Uz^`JDP#f!P4cQ!9pScU;CJsJi?*412VtPT@0QPDD%WkZ)n}e!Kw;K43_x=C>K4C>Y0Cbh9bTfc)!Q5` zq$y5LE3n|jt6U@nyU@LuQf!gR)X`WY)6pLG>82Fd;IJYrS~;?LRni~zOOgva$xR(D zO|3W1qFFNis5krgoe4hEkLs@msbIM~2Wbo6rn{oOg`n#in>L&Bv~#Z@dwp4fU2~!g zG}OF}CQ6K8v6ZK$A;p8&0HJtjw2kqe2~2bmYuw2b9vTI{E+^usk>rqMLYDLZOfSGj zwIzK6>gF8!jo+h4(kGcXO3*gOvsU`s*^gLaI7OAoqU7Kl*?sZ^sA+0+94U1U7n%@w zB0m1WbeD>0zlylZ*cYDY;GEa8g5*6dvG{{N`s9^s#E-R&sn=`)Sv>ecC6uYD z!h4+ks4T->TWp~82ZbYQa8q~+T{@WRmfUeV7=Jn7&043mpNQ_7)S39JB{Vam0|C)^ z{Eb#5g2ukAeis03za`#^F>a7Wfk+@vK~IN^j%*jLorL)a7UrS~KCo?7r38nYiG=A_ zG0br~Rt$@8mW!N7c29g)_HqpGhR2)|FTV|^=$J$CTC3S4BDf@`*9{?DhL1)kc~7l^ zj;cT~RFnch7zz^zuwtY#zac_ZIdg-*2s8mzg%E)te@n_?z;K5A(R zYCVb~Hm~B#w)_OmUW2e8^N*TAqCKHkvOMiKwYq^?9}oiuBvssUO%!MgE=)ORm~C8v z#k~k@Q219~0B@Wf!0>nr-xtc>ftFa11a`Eb0P|}-$!qDZ1aZiL!(?zdog&7?&{J~m z^+ghoGAi#tN0GNwR?RPs&kvM<mkvLra)ujsvt;$e4WE=%z=b_h}Y~4FB+$k z<)8j^C7pJkR8(eh9h1cEx}+~I#aRc9v2~8qvv}2%tyI%>kdgCMz0=m8SOp)bsUdL` zA7G*G)4>s;bC4HK1VhE4AB{}x7)mhi^gjCHLvOt*$rax!PlC)IECQ{tF$Bd6bjZ{b z4$`3vw}OV&9C=b+v%Y*UkgswP#Um3Xvev9repvhX`2vuYvr+o*F4%wbRpAPuUPG5c#_!=3Rk zP=^p~rGBym;C7gS#+Tbv#+2kfemq1V*O!#vX)jgZj?;dE!Z}@M-}sJHtR2ul^@NpF zCyuF2Zk_HZ+Uq`u9k^A_Bg!`-fn={2km<428o|PJw>T9?Psm5tVA=*L{e-g=u|F)- zAeYJ8`P$Oj>RWK8!@HHC!T)oIRJ1T)n|`(|dEc0Ns=pL3`*DO1!s za+Fqnk6{o7eZ@ecyoN(G(SwTQ^JH!$oAs?Xs8RdH8RJpo6!LM3Eu7^RDf`)H?x^Ek?FwFdDpfBTCB;W$h!}< z>!k^l@|Jm)V`v$uh%~YXq(1n??5r3aJAjf?*|_4vC3}}J@GqihoZQwHCsbbAhZAP0 zT(YIA2UbSE0W_R<=qb*bSl1x?oLABg1-RJI_k!%Ft+MppT? zVmN0*0V8AbrAtwG-p*0XiaU8Ac=vnOQh03>3rC)$eSzFyBe0kdZTEcV zPg~+G8ca?j^JP1Hrh}hw<0isnR;o48Uq{DnWpiqGm@r-kl!nn7aW8)2UkiR0+-7~V zu4xa&e=#LJl(m~_2gRmSX%0MP6Bt!)8iCuD#_|g3KDpdxq1lQvy1Sf54&APM7F^gr*18toDKzxuXtJ@J z9q{;2XmgKNjkyoG`%r|q*COhUeSaIegR`MED9>jptdk1j7@T4|80Q!P&N1KlZFFW4 zzS#zjzJ3o3vAUzw8i!q%NG%fZiG;8X7lImRoyjU06KYPnj04deyuF`yqS)adjIF*r z{(PLUZyAu-!h{EJr}y7t*MsnW1edC3DIn&n2uH-3QPF+?ASG%5ECQ{aleUM_OB=Zz zq{hu1J#~+Rs-ri}q}acyqV4!_gHwvUv#@^1_Rv$?Oad_i{wQ+Cqkg5sW`?gL9p7g; z7Z_L0d0Ls`m?72bkP!6f(&yz-Fb$b0Goq(!A@IU8r z87fi`7&Yur{HD8*bZcB;yN%z_e($`AdS}iuHj@mLX!k@hE!a8c+W^4rb`ABX=;m}^ zxYpiOim`hykDXyBJJB+X4jifTiucPiK?NqzBGC9*aK`ij6;9ug@-yfpC z+=%>Fvl^MMz7)i##1lAhwL8o&EL3kQky^qpMrfAtzCgk1mgLqE7Zegho0<#8B#z$r zY^VR0lxUmP2yA76XX7B`3sYb(!B&bWY|%bT4d2#^;A@&?YD+pU z%`>wi!4Z#+R7C(!FHn&NfWBy8GtVXOEQk&rrAkVQFZ4L7GRc;yz?Cpv$cQ4L7aC$z z5eg4|WHfPjyI2U0?-l~nX4dOtceFQ~?kct6k-&YSi4Q=uopXaAIrysRX`#4gW0iN& z3+UR1ld+*Gljuds`6bUd);bQvjD&_&^EipWEwERSBbg1XJqAS$S4>AoeaGAB_>nvrDbPaHpRX~h%G@UYl*Q_7ox zSR-?oqAGhW=!ubxDW~1TcK#I+WC7!Amq-~uw=srt?}*jlrR4uINXkcB6;sJ8mCmSo zojIXQrHtv&k&h=<(ZWX zt$g>9s&5KX`#-$w7`T=JDej+UZ$QytPj^>eE}E2!9YDnXW(a%`XF?Wa z7n|5gBGnsm0m*dohe4}+YBH+_Zn=u8-U*g_CF`ckxvrN8U3 zKB#HFym*|I+z#%cCiJc1Ux9O<=%D{HAfy;Rfsfjr?f{bs*4CfYZ{C^dSmJN~!LPDc zlVYk~yc)kKRMQbc#Dlc-M93qCRYLT2)(d9gNA4I5@zqemoDW6SzG56qLzr`>I9C_; zrY%wlKA9R(##RJI3;Cd0+zi*70srfwT|g6dpe6CJ6x9jGhE zsiG0(eXUxr6yOA5f$s=FO-4nj|93LEe1iX$jYLc~8FH$f3nJ{9=+w8*G{B`SL@a1u zHHv4&bqlaG2aDQmKpBbH3f%1$=x_Gi$b5$-TQ(F-;IW&j)u17jo?%=HoeR7ZcDF(f zvt$d`RbFW{HfI#?f|9ql(U>T+qwR=NcSf0!;8BV(V!-^`*WS7Fck>9ouwnPA`Q7DM@lN89|;WslXip5 zx1w7?qFb5?3eQQSRgpu@-2D}Vz|=+`t3Hdd0=P(gWnQ-So{`9~dVJ~QhrKw;FMNoz_;$+uvjVB0wY-<axZK;9;8P&#gUKJy4$(mG*05vH1U?rJR-uQTF=Arvy}J=W`j9cWamB!H=Th7 zjL?ch0@3?r*jK92&0LO=bj<@9fpIXS)R3)G*26S*Hg|`0?GJ5PAkH5HU2>u}b#B^1 zYD`hSo-ia~xl8#7-xFCmYUpEV2CWSFZXL69mBka~g#xJY%5~IyKAb50IR>-KYyOYC z{6n-RkVD$>M70BM{{hbSmST~MAnL!XgGRJ2(OY-UzeBZS7lTOM&oT#Z;Pl9ML^qM} zlB$D4;Rd+&s0#^6V24YqHIYEb#}V1RXuNAZr|tEEPJ7|+h!myeY6B~Yjr59infm;w zV9bP~LmEaYKKwu6Aa}tDcKr;2Rmac+o5Q!~KL|;QG#;2_bOrWBxcrq0s=XkEk(W<@ z*ssp0Vq}nOg!zJekV58oOHgM?i$M3$=7u>s1CA*f4hy-ufiVNK@6Q|xcFdq=dAh=kTB8`3& zgsOTiS3P}3T^s74YMS7#LOw~#f8Hcc)M6a z7<%gNW>b2$mu&5p>N_Jv;3$0n-zk%o(XSC*+CQ?h>p~Ys+Bu+shXkZ-RGnCZ=?~^c zIHY(HAgmWU%{Mjp5)nf)62+2|meXCR$Mk^G%?5qvqW*A% zPwMBGw7`(|22A(FyMS{7VM8_Z8DmKh2*-u6OK>RVqJNtZhwPmq>2XMx!1M*U0%!m^ zo5Nw35oDyUBZHO$#@Gs?0E!>UR)XUA!k2kYLdnsFwKgFUnjCf9b+%5@d#`MNeE01# zJVwEJ(XoOWYSo|V69AS;@BlO{C9?9!5Oy)VgG&Gz^T7f_fEeCefanr2?kXt02&xB1 zSPEfJWbXvBwNm^>mlH8j4S>X6WOClEd%q#fw{_!=`Mb{{X)0_#Zq;S*BOefl2}pw< z3%yvXvYr{6@lIP=q()V%gtEbW)C~uBL8HW6S6#;p$1rI{-V6(gy0%qJNw#R?V<2wZ zancAuxkTrD6qqnhw-MAYSQ*Zn3FI`;{AMNq?X`>|eB$IaUK(_Atx^`Qy%7LYUs8St zG%B5=k(o6+)n5>{wz%2@a~}di07qemvWb2U=SpwIsrx07&Cc@fQOsc5twwk=>$gYA zLzHMpvadLnpqlxd^rs<`uTpTA!I;*PEK)!#%A?qI(~&(!2>9q}y1Idz)Gq16r?KuQ z4!x_bbPirpU(iQbS`oh0l&w0hE8yZsMh%aWtmR4H0Y%F7oi_*;t?O>)Uy{B^!-43s zP*H~I9~(={!M!6O0(xxzwi42~7B9{?f&*%x(q4kjEb_fE?bujZUuj$GEpnl^LfVpe z;Hag@h1Ad(d-oa`o~~lxd8D{*L7oSyady;Z#=pOOrdQUn!HfY*(@C6;x1R#VH2JGu zBn$zIjCR!1)c! zpX5Umx#*oBoAB=@;UW@CGU-{{&19k|8UPRz)o)+NFXZc6ANxySE~;_TK?oC;Z~$5y zf6NfNYW-MCXed9QCuLN_Wv}8+<*!N6tK)=}0`CWYVeQ<0XR}yS7$7EL@e>^<#@}(f zZ89-ug6J3yZzqXz?Q2bp)pAcBLs6|P`EHIq$m;+-Y|818CO{%j<%GGp%tt8kz)C{| z8PAzOeG$KxwpF-J*kSapmR9m(T^rR9UMPKW>1AEDE=y_v-|oTH{)VF&)zzD_pbfeY zLRhtLgu7*y-ludY!`>EPrB35h_sIj|CIg`U7(nE~AqjMHcn zKm?B8r-8(9Ep9_fN@y`t3nIhoK1KO&{<};q&y_~a-|ozfUMlm+^9<>*XkZ#gfI_mN zu$C!FvwMvK%e(F3|3Z|Jjx!Qb3G7q%;9u1@ci6n==MwDQpZJ`*`t!XT2ThQ{nR4J> zmxf7sqpN^l^(L5#Vi%I)*hre=4Y=x2;>sH?E}(Nb`h*^ZRIkBw7ixG|UCPWQ4IS!Y zS>g7Ry;LOb(MXE4a82=0QPu(2%t*6^L37(rDHvQxyajaT%<{yy;h8-FwpPVD3^#

ms>j za@e55`ICj!^5P)z)R6d0M*E+QT00?Ai(uJNgWPTN<;OhfrsqjWT4DIBD{JNo4KsBG zWNG(_G^Mip-u?G;RD3*zL`w@5P#?EX9~84j6pwE3iWX3HZ2sCU^7luM z&6O1={_W3Hq$Z-x- zwdGSfDD~`hAxcOc@rxB-3bjO599jr|3oX^YYgyfy-5xa}!p+2$$K%x0w#_$9*(E`t zq?}|{=aPn&k>(=u-v1P5-7xbwJGJx8LL=($N^ZHLo9NCI9};UoN^bI`13|rdJy;NH z{A3UP1;ME4`U;fl!%f5x?Y4h=`1RApDvHQK+`a!mfK4$Jx<9FqhEMAI)g7NkyWW7z zk`N+C4TB-Z5~hQI&yTgj*DzU-PA}~;=iXX>l?0{?j&S2s`-uk?7c4fqYX7JvQbT5@ z0{JMt2Ak{tm4?<2fI4d%N-)xbpeu3MukIlf^5z!5q04v(at@=BCIw%=}KVmYfm;}I$R-nq2rkSS0D}s zrUSdYIhs{3S&pf*H8zG-;+(1hU}}=g`B%odq;xNN2*S@*;!TzUHMu1M!g*}?GqlHB zLU3d~l3;zqCf(%6%2Ao?eQJcxAbPEOSG9n@%UVu(}nl2SilLz^a~$=4h#s1rLF6e zn)F2UFn>)lYtiC8$Mj6Lt74NiBQUAJY+;l=NxPrzYH!WWL`@qxhck-tawI^EiDs+` zRxF6E7<@ejR%PFMUt`#bR0cdd*jp^UKur`@+z(fN6)$XZ0NLRa-SpR_wSZYDoXO@MhBT{0Qv@!+|d zh+{k8rm)fnTYf*0>Sujo#;48r-7dH#=X2yWL}c!wG|xf3s<)f%U5nrEu)iyIJF|dn zm`Fy!%1gv&CmhaSkf3l$=T9a14P`yfb_@KeHRq_GgZ~#Fx;|_a*T-Iio)%n?QZOF+ z{=fmiu z2fbHH%!AQ=bu%|CEG}O7btf4y4q_Icnvq_M+4qip*iny30-Ht;hX5%#Di;Paw4o$txF&4r-BMO+BQItA9QL`7gG?$nk+&tX_=r5nApD#ii*u z4N5dScs7!%@BZv<`*rj7AE(2m*(-*KcGz0`Wz(Fy`g2l`4br93WLq4C$OYbs55g{* zK*>n?AW+6J4+ja@8P`Z;Zz4yNdj9hY#>e(#)tS+r}RhXix! z(2e@YZzJ>Q0%PDap_IiLUV~x!-n6yJN#SR2d5sKGa;M$D2shZY#cq1E11_9scGwW+ zlmYgDilcra{$NlS9YuFzfN+%s&Yp&GgU^R(G^m6Xk7ZIm9_+6-%suQvNh?~0|J-kbr~LM5Y|>P5XwZGSyOnt z!>evXkD!Db0VRf)`e!UnnE>+M`bD~;ZjD4v5(7kzszOOqpHeSZ5ZpyhSD z^p}I#8oPQ|BDseJ#yM_K-}6~YaN1s)VZlU-wS1_sYzsbKdbG4?uc9ZjgkCx8k{#U^ z>jgUI-kn-sU5oar2W{dFmQMk00sLB@ty7JUK_0)yA~b>R!is|_nO0xDWxinbKnM0! z2mmX6W~Bcbwbk!|AS0i2O-`e$|22#8*Y3~fx^H;g<_hwFo8O;G-a>Mjy=qhKY;E_o zzd3DbEO5##_DeDf6%B)rzV7bycD=aj_!sX&)7`CeC_y76h<158hEpd+!3w8fV>fs; zx8R|^K(|nQYpn1D$p^*ah;sKHt%S#pHmLVt}p5E{|WG5tu(K zlmN1*;kA&XW-f;MtOo97aY|}V&VaVAJX%0oB)#0Blpi9|b<>=(Gd?|GK&RZ`#cuDm zhMNZ`EGs#S^kj7k9k@R+fTzlTcY7G`rBl0hz`-gj+rZmI^c<;Y*4Wi#;bCz$M^RFV za*htTY5TCWD$-6RA)>LJW@1R07copfPVR6ii-``CusM`XfF^lSO`-%1Nv5(lk?3*O zTY5S~c+)$8qz3O&=P2yVLns)h6U8$;ASxpPeQF_sk!F77QAs^g%2*j#LVGLJ&AHb1r% zwPpfei#bZ@-Hko^64v$#-egSq%qHdRT|sEPPUw^S3Bd;CS{z*=99|ssh)BwTapS;Z zmJCW~<%Z8YRg{Dw$xy`NLvTrs{8;MA;LgbV-d=j)E2L)_1UmuYj_IQx_ zx;ir~c);mbC+pzaYU2+i1m1kXbX(uD!fClz($V*?z9(UWBA>7f4H4||oLM%{wcS76 z-GdN;hmo+m>Ebr09Qoyc=I95*j_p!JaQ5agPsu5^x-ec=u_3u?Je$tb6n?;iG6@>^ zt$)T$3$|RFCD7|}?2BJ796>yZ;LZSs{jKuXpME1PBDx~kEm*ekuza8F zr*G^W!-Y$PPOBPo-d_%#ZS7K|;3{{ModU!8#bzTKVUp8W65yy+m|jkxT}Mpk#q;id zO5!>NHE=tG`Mlo!hGA>p!+sBVs*m$!=@Jt2dkI>jYFbF?x$xfji_n>-RcM1vm<7uM zx{LUe>g+(CO&oy?K-fvlHNHGauurcJ@wKP@=n%3-<@JFHnTBI~j>Jgbdt|}OCGneu zoY!RICqyTg)J?`GNFBceB(cDEkD3BYBloV#oa~cZ^buk(O+-|crkLJ>j`i8|k?ktQ znS@2Pd{U1pO^=pd&$XAp-d80*Wq5bcL>0|aLqVl&T}bI? z`)_!-n$*_Vgk`XNw{5NgY(rP=A3av)a=vAs&X1_@J~N_-^lXJZwLP=Eq+ENIlgl8b zT4fow#gD=LB`#ojp^i65B~&vBlHu}%Fe1L9NU^9uu*0_KK10SX?f%LVuu*>P5=LgH zkTp?#rMj99%&!per7<7{a)-iAMxiKN>!YlZ%KZ85A^dg~*J&nvGtP|%Np?uS9oqSw zO6Ab%Y(SsO*AqW|zQ?%jn)x?unQ*M60#>tT@HZ{sHCu$k)6@}oK; zEIgQI94X#r^gP+N8miZ#vJJl9LgJqQ;7C4z;t-afi=xE$fIGJy|A6S#TCc-I_)2qHHi6`vK?6*@mouE*Hj z!_Eq%SZxyIl0+|0vmvnhjVIf}RaK#6t_HDab9`vy&ai+`;=n`WAPmR-NOA3S&8lJONaM7;2Wz2Z ztqOWCW*4~J;XSKfl0bHjE^aHy;W&zWoVq|LOGNM?Wc~hEG9jXrkPUV47!zp{Yfez3 zT*C|@Gy8-VnI#$_@q4A0++kvm@1CYmDn!U#76fLxbyC6p zmHWx@FtPcfgk2})0pZziM~7n$NMX(NIM#3%aHP3EtB&x%Lr8(9`z`slHL$U>~j%nER?VatFWSd+l}F8e=Cx+fzG8vI2z( zl^%A-ReZ17E@C$Wj?jsdghn{sP?ReCP)bWVB&Hg%`NvDzql!kWM{bJ^tu2uXX$o}h z@k{VU3#nK;aQmK8W+JZEGX=h|@;E}>&M-%&`|rt(X}8o>OY4t`gqg>~+&{7F1S6iS z@-9d}PV9&=x7>dHtmYJ3rKqZRTAlu=1HG12EL6%!9?-s<1?TAFhWbWP6z6n~*X6Yr zKXBL3vt`uF7)f#I*ht)4KG+IBRB`$QGZyqaIGF49PB*{}Pp;RcvT~oZI#|vr9l*GN zsd$aM{q2$c{)lg+UZo%n>S=oXz3J+7vvu7qvlFNLVt9QOvulr*9Si9!auzBR#uOK3 zFMYnJQY1cc_(YxK^t-(ahk;IUiz5$GJV1sK-XT^{xSJ8~cqo0dN#RnwA|`OZansYS z{PIXf)Z5Xvv~h{j8cZ^bku>pT zv0P*l@TrGt5F#sM-b0a;5AM~08~ozi_r~A#c$n}n-j_P? zP)UDS93m4}K@O#~@#^qZDjRo|X5y<7xd86-@1%`# z4SA_+Z7XaKx==AX*N~bESV+2}TCYOJN6geI?L|FF=aR?=4Z&hQuZ`tN3AGoVD>@DU z1pa|JHiuSCuy#&ZROB-Lyr-5D0Q(1R<@rzdCL?C<#ru-avBH;~Ib&?3+h6_|cixh5 z-fO$TA9|xZb<}2gk-~0`=Z7RTT%Yrglsv7jK%?JtvS6N1RSy>4PFg-^QbYN1(9x^V z$WAI;S3tsuhb2BeYkf(7h`V7t%;u$BN0Sypg~z-w=h zDy%v+Znw8#?YC8;c}}TXF8y3v6GaKHh zCxhHd%^nL8eq~Thad!1WIIr?mvWo3n;TAqdYSo3}WRSbbNwfgp>X7jS4d$na1#nVy zLThss{AJ7Hx?uBr=dl}9&yAOw7MA^KLRgCP&zGt~8gIAS{lK{%s`fOJw@qs3E5~ zTZVrZHXJqz;#pU?`5q_KQh5yt@e+A>*q=>b70BhR{qP{ah_RAcfqLWxpvognx4C=OF#sW9G~~6F7#zfdw9!7gqycX=ZCLvY38*|D{!V| zlqL$b9sFi(U(eaoIB>xqXqGM^D5#0Z(7i~&8#N{kah#=cYrVYdZldOIipQ`pnb9&rCu&GYBM#G4 z?6oPkiwq7LgQmUT=j0TlC(H1f=aV25ZXqzslcSMaMtjrIM(bZ~CFry2H9IOXC!-XK zLk9lMs;73H8ty|5h=#!yO^3D8&nSIP2Hw7&eH{XLOr;6N~3NZ<-^eYWI^Z z>NS{Ups224bHzc;34tn12CYGbGYeRHgHd_Org3-~1A0jX?etMXlfIJ3v zilA~ttTOWx%x$E)5HA{7ZD`#cNnP_6ZY!%`k6Z&cZzS0yDnItPT_zigi7N}U^sv03 zX{=?7tzX;@9r9>RbY17Z_r3qtf9|pAv^pMHpSoluNR432pUX`o+QsBAFRYj24mWu7 zjobJ!4v9-~e%}WN59k;^r$bX73jkr{->r}WnJ2AidjZH8s#4H-zl8#Nf~2<+cJY6I zNdU4^rDD#TrsI~ZN`h{aMkab8mKig{VRiS-2*O*MSC$u=uaILJMaWgpmF#oKHZ2tp zs2rt^O4;#DDba>{abnO=$dS?VGUAhJJZmBR>-803x_=L=wfsTv#eBPzYnjyS`A6i$ zj4O#{Qja2%?wAT|=7~SQ#v7Th=Q;GRDQy6vbSzzaq?9(EcXgOQ#&R{E-aH3F8svf_ zq#5r*%t_bS>CHb6(#G7)<2_mY$-<26gnR_$k&w#w>lpAl`M;CjB${5aD+cJjo+UI7 zzy8xTy^h-2HGi83(?dH;@x#xBScjHkU`tj=XmoN;z$n})!pT=}LV-JtnUiOtbEf*q zjh7H2Sq<#Uzu;CO;9h{@TXo#uU*<>>wo2%_(2Xw#RjwWR$)m2|29=RZ#kcq6&PJe0 zQpLrtG~nXXg*6`X#d9Qy&^QBC9#s@a_;hf7a43P1rzPaCG-0r`g#`lxm;t&Vd?$1_ znmfEL;2HWPdZ(tX_Rq7|mEXzxx$upyf{LrVlEz&vJ-gHO=;7XI(xH3mxX;@HPVU#K zXaTHhQrh>CM*GyHY+s6-#^<&ijE%k}vaiGT#`TNGI%qZ@IR5GZvSrvy*O~8bJ(w}Q zvvR(^7`B9M3EcUr`_yPeHwybqAefW&YQ1T}+!w+}Hp)m{KFwH=78yh?2$SgzRx-<6 z7H$y#H0x|p(T1@W=F{`7S<^<#l0zfKGylWQHq~RP+ubs|bn;bLRt#&6{++vgf}8pF z>2+&dycPMU6=cxIdYYoY~XF8Z%K7+^J3&Hq}!|7*c3I(mxx@B3B6d@#hiH@!pvOFYN_wc!2N zq76#ksPtbCke-RAd!Fts{_8}e49Z_ws=_Mf%`-GX!@qX^ZLeZ>bt3cqPUX{hS+k=R z51?1#>!*`VcY(+^Rl3qr=U4Bhxu8L&QKGpXNgt;#%kOFSB+Y01renfsJLGux&z+~$ zYNKAlOT-MnL)H+{aEIwjtmhlqjgG4PH@oLJVC;jdPD$^(;l&@f1fhKE-jnYk<4cBV zigYLD0U|4!i5xw8mv1={9jBSCY)n-)J3jF^%=3egP93!!abFqh!+0o25e8DeE%f+W zfQZ)s{T5?$gcB@TubAN?Vc?#FJc|xwhF4 zr=8pdU{(od$b}iW(B@Rhfd5MyG>QWq++qfjDc`QIghAU+W;MY(Y$NvQfgyKbINtI* zTIK%T{wB2IE1F6|QT0iL{A}tl3Nce?=g0Gagt@q(3qFQk zWR7!UOJUNmA%k~!jRvUK_ZyM-!bJ+JBS)!g|Bq7r`g*;q;WLGnvW3KD)y|PJgUrQ~ zUL_juw^cEfvw#I~oVImvo=mnV%om`S;^;rSSMk+9ecCrM$eZJrDRQ1CTMON~)WjXHt%jbY~Zf`R-M1@ut_;;*MeAa03lKr+{n; zX4SWDqYM5qaeV2aZp8BU+-{5OFJS0k&s7h?#wjsk0g{C%A&&#BFClH5 z&P$h{Pjm>~{WtDkLFBhUvmY_Grlx#C_3Eb??Iq5(2Bq*o=+im?vYl04kHS@%()r3Q z`&0L7=Z{SC!me3r^B2e%&M!IB zu)#Wx5>>Wm|B1j1ye`uPO{K`~74_c9&l7Xj7S0iPRnN2-DWJO7Q3x@7^2kjy!V0eb zLL%aEQ3KUsOf>($;n)ba<3bg8T@$NaN^@v=+##Vlzdkg{)JoDF8k%4*LWNud7Ky+vD%yg5mJtiTpUg!yj)w znn}GI&b@iX=*Qfx+gN>_+RKi^ilJ#lnVN+b5oKN5*!WpijY*S;8fePMmV4it55M!M zc760AWj{MRP8G~wj!jfJyO?-Qt*#R-A}ve$}!v8 zho5u9;pvR}nL~+1k!qpPMIWmj@-F?M#1T12X)WF$b4p8s)N|O~N9K)!Q(GL~&Mm4K z$LE&tNK5y0rbjFT&+_z^FUGU^qp~DSpvMw8DAAesN@q8F<2W0zhk5xO!q@tDyvTyN z26ZxWl(NDz)A{qX^XJra*Hk~no5StYE~Q^d3?&AS>dpr}kNWHnPw0vC45RlZ;Qz z&JPlvc(_h)#s$3)*xr{QeF3mn~EZ-{{!+9{0Kw_v3GK?Oh95kn&PHEAIX|JWUYB>omp>hycp9A@ZM#?>D2|5^^2s&{jgW zEX;<0)(gb66q059@K1KqKuVzWZ<7D$?0o(=&nd33C@6&lZ}Tx&F(8QQ`uG9r1u+=l z<`|zuxc2n;$`$NPva0YO)Fying zBt7>Yaraw~k?{7sh67f`%Lqqj#sb!gLhu|h+Z-RPcln6(L@RddfO9Pr2+Vo;k3+ma zul}yu`!7@)(f(S!_U}RP0nED`%ww990N>_SUgv9HS8DG(fl2WIGvC@ajuwwVbxPDp z>2|xmOJ2WSUM_kZ&%w`%w(}5c+lb0jkxC+Y5yj$yLzTny$7ZkgnrEbY9txHeA6b&7 zu-l&t`pYsNaCPx8D%$|g(<^Oof!_z@5sdvgX<*kfn!FJ4vRFwxr4{pi5*aDes^?LW z`dE>J@`%)9b#)fAscm)>nv!nMpd|o})D|ZDsh5UA^0?Y)@HC zLIul>(B?mCpW^*LPte3kdhp!PT<)7oiTYYy>Nk4*E93SKEWc&X*K_AIj9Jr*56UUz z&I-1##-kQ}gyb86!>iD+&~$-7-o&XR6EQv;=b=D__epfSfy~ zRYyVH<#_#g_88eKxQFf2*b@weutJeCy3^#vmaEU!stpWZ%-pjMYqFAR3nWF)sRE}$ z?cjwEOob1AY@Ggp^&+*oG2Zh5S~Vd}$LS-%gezm$q_2Ov@zhwwF3=G^DHKMQkO})| z1@eK6+5cIrTaXi_ZB!K&?9m6wEclbpF;LpL&|IT;yuw!oMf`sN4ngt0+qlc6jEjKM z^uSTuTlYDN$KU(C-+TGXU;dLn`I9U`pnu9!p5iSt@D<^nd+zzX&-*-I3*7413JB;w z{KG%|(wDxJ4SeD!e&T0*#%Db0QI7&HCYciuW3ERrBYXjkA(Yh+|EGWYC$~m&k9q&{ zFaOdF6$3L1+fZx^7!1KQ&?_-iD;U<(;|bR1Iv>#EQmw2cMeEFHKp31FicW5?2i}DS zhk!T$*l=&#-w6{6N)bmtD`5viW4O*{hq~y4GyWP9L&l`7Aj3q#In)zOzj<}muk!?L z;!Lxm3Yt-76?znkO2gVP8olxDKmF4`6|(L4#Buw2YUlItLW9`uKA+#+-PIXe`ey)U zf$vN-uj~Zncn+_)MzE2T9)9r`e^I`XLJZ;gpZ(dN?Z62tnNU za8NdH1q3qKK!^`N`lCPkYrpnuGK4P>7cXAC>86`}f*N}uJPy?dS)E|dIsKjt zj6;>HKjnJMP_*@Mezix1L8!fLYY9$|!bG=2(L=c@$MtIS)=+EKp z{>1BccKStUIRHI45C|4t*$IB~Cx7y%e9EVQn;YZlEf6(w+R}en1P6D@&H*6PBT_Jc zPy4h_Gm+fG9&8Eu|Lb4>dO$lOpe)m{G!o9bl6)T5#x-aF*0Y_WPa4J~p8}<{ZEya| zzx>NS7{J9{jT@Vf|M-uGLMfjC*d^E>7>nLmwO63O<(6CE3w=4>sRH7DM_@n464(YZ zub2uNV~L?*pu?rMLI%d>*9Qu=&wAFg@br<{FI;_@kYf-dBYdmQo@?KK^*PN4qfk)o zQ}p!cSF!_*-~pF+*_jh4lVTt75nosg$N_H` z;OYPe1|&2v5i?2#YQ^vx%)+Y?du<%00qUaU^fol`gFoBO z=gcsb64c^?PMb$J3AXcJ@fBa;a=;D_n&2=H)|lHIz}>uhOu-nuE%%7{9A3o#qA&U) z0~v!5^MV(=;M>3b+a(EY1d6di&kQh3ew|6~u|;XDS&SyQ}$| zH-Gat`J;UQIiK@6E(Y7`*~~c#xbfrH4h_B>z|JelKFIVu=}AwrrF;JKpZ_yI^E3Fk z=Xuz}9tM5J5O5{}t3qZyW@i-|C6Yo)D4W7qCkz&d#-YJ}C0o7P^$c|h16x(?PEvZ6N1@BjYq2bQa3!zBhsI^4WMgHkRD8djm0(R5fT zU=4lp09wj-!V^S%lhE(u1Oq?}Zljo1k|%Ru8Mu6`bFGi50lMXYL!3YA(NC)m2i^aO z#WI2O0gvXYV3qR!;1B*lGO#n?nCt{PMo9(+0v#)!-m&kue(SgF|9 zkuipuT6^*V0g?nmMVqOxC{y6TBycd?qaof76K(zTc;bz7mk(%=LR0awksFVGy@G_o zGGJ3C;It6xFc=@kZut)>TG`NkBYvZ5>mYB|4o!Z192|8=+j0=FLs`6B>< z7|?JLn&A#CA>Jkcf;RqOf-j`lOv6Kyzkk+eeU^_;Y{N%p-yRR0d#n z#q4Ix2E0;q5{6-5A{wwg*9JByOv^+`d$1lmX>})6pT~SacjIZJNBC*ed#d-yX`$s2 zsGuEF5HcJH93*YQPIaMbP*p#0noKJ_Zj{yqlqbEg3x za(K0`zMwHmEC80aDd44m(yvWmbjKZch!HM-Bx&F(XyUAHB0a2ylS5hTa7NT%7>X!> z#vPHw1ggq`Mg~l$J9X3P-c3jNdWm`8l@qIWKj%8zF}I>=f$kcEC|Gw z==~ba_hj^bXrQne9LO*xu{4HbF8mtE&S^e4sHyyo($l*~IF-Fw4|*Q17WZux?cd!Y zh~}({8+!#j&?;g&L$RBHy`P{-43dF?!hManu?BD9&9;~zJCI|9B&kKwuv4&KFqeI+ z>m!?1yN0$f2ZdjI?o$H*a;N7vg0Fn#E9u;y|9s&WexaZDzz%BS0yF{SbB;Ybca=QY zfSMVgxp3ivGn|3Y!9h@X!V{ig69+*?Vh50^5JN*jP#Q2669pw15J>s17BQEHoFIiE z=<7#=AoIuwbxuQjE!Us{dZtn!(rwD5XNWr)*pP83pAD9IUHr8%N5doxvgS3uvGog=H-J^?0j>J#2U~Y@&gR7~#=d$O#8% zoF9EYf;yzRvF!tw1~$~$jm&@d=4V9){=zT(g0KQzYz<5jL)fAold>dJK;72ETQq+4 zY!)9LSgK^;2x2$t66uG2=!c|}E*9E2Fcd>tj=FZsY@+IVwz6)4)~;Bf$9f=0Mf3~~ z%7h5jiPwGsVs~!`2TCV;$-u#j8q3qpc@7Qy+1a13Vu*>L&{=_Vg7{0n^h>eDL_WaT zKESUGb7}lx&HoYLX=-fY%Xng?6YhxJF1mN}y93bg?(TNrvxo4?Hwoq>jtpLw#uR6P z)4w(j)j$T&t39*W6bhtU$O_+V!y`~$_Oh4pFMj7|osQrLX&!<(7vQjx_#NB{O;+^iM?V@jPU?{1xXGf7Vo36bpl@FIqI3sfS7)2(`vlx=1es`N7?c12 zKmbWZK~#az?~z@R%H~gCA)&lNLb1j5fH+XjxU`bXKX9@@G|R;vs*P*w+}eC_9Cgqs zT}LTKd!%S_Sq()vF#-`mjfM~VocM}Yyh80#$b}pef8MjDh3*bn8WjS2B2YM2s@Mp+GCoCwKzNCMpTiA|C6>| zA;B=I-WsspQVU@Q#bv727Wbj%SWnx$j6E<9xX9>q?lK24#36_nh$6Bby-;pA0+cp< zx7~IdCcNAEl54Yp%)^mUs=!>P?<*7z9_1Alf;oI)Eod+{+}Hq9G+YcZsfp}G%q2aL zlgkST8Ft#!Hmdr5(&xF$2Nb$g<>*%k;L?o4TQ&h;76WV-M3Ov2MH9an_YI0OyLW#4 z;~$TnW!N2X?9c!F&-c3^<^o_1Lu>QaIaIwEN- z*IWNuA4dZ`3&h|9R>_##@+MpOk}>s`kKtZE7QS&t4+~?tKWKO*dVJW4MBX988PWDG z@8-*~QVZ^`kN7(#9@Q8^*;r=aTFDQZea&VV-m(yvMY4qpD4Y?P&P0arYlS5s^T-C2 z9yRowcsY;xV4o7{nsVEbc_m~6l2?O7zneaPnC=FjOUwW6m+kCC0rt_)fp{+iD%RUV z7V-V;>~lZ&b43$ZJtzeuf5#+9A!An37TphmPphH@f>2=zvDeVLu z`|JSxa6sS&lb;yj;9}QsX@g_K%Z%;`9U9=JU*;}(V`=CX*pJ;X!lrSt*=1?Gz<{~rDy&qAM;I-m)dhi9tTZk!+7`_1IT#54Il%V zN*)JjNrKr(1ERKVqOS#*iwQ=>O|o%oBwV;E6fq3JnJHq0zoN%SN2cfi!8{x?{N4=i z48TFtUI7i$GVhHyfODM>mccaGpe{gU85-Q%Pd~r!`@YW+&+icYIpQt1+`_9PhMxld zolRk0AY>dYM%%5CJ46AU75aNrqulvG8cl@>B33g;MQ2kp-vq#%!5PjRZ7tPoj)@Rb zA-rP9Wq#-Dzt*0&Ov9r#)!EZY%yi2G`J0~rIwgGl*MB`Y=!j<)2vBlt@Oez1PGACk zINIqO>rOz{4;{C?G64>1SG^jLlK5Q)0uizSH)4n;3d@lU;3z-{6GriT8wDb{QkYQ$ zuzv(Gf>vZ_fR(_cXD!#P0VvD6hTDewApk63$2W6EkhS**F{jpM4|FcF^FHPQETNxa zc#8?s60!lr7yyK)1S9dg(RG20Qks_=n2p~rX?V=Xd9DIwJQb`kwE5^rN=UY0@A9qR z`mML#dh3M?7uX>h;*EKGRLHikr~Z?w&s{#C&86;Au9`ux2I?+4K+&U<0ULrkXutZa zzgj+bnkzbp7rdI?*de-4U`FhN|GS02A44I@K~h6HkyM;d?F!jMn|-0God_F3X;k5$ z_|85sapi4e%X<6@!fpb8{TQ+mhDFqf^(j)I zKN+$hqcj&aBCkU8w5nel6JcF0moP+u2?4y_>5wtFDR#ZfjUghYaAq^Q6fz2Y5HTuA zv^5}$xvhe$16e`laN6+gnqZ77So@r4M)WQ#V&0-K!5@c0K&y-YS|48nlnGx4HJ^MO z_(1=-$30FI=80bMW%j@s835(CSKyc~aAXcXj^0)bH1zLLVF^It^;{B~Klp<`=;nvj zf!i&RyF>#Cr4SHIgstJk;JK1h`Nl1Hu-6briVNOi z1wW?4CTt$XBgA3~J3}Tk2p?u)g~E!4s3iJy&Jb~!ATjJXTy)q}YWr9%aEKu7HB3>& zG0VPHoVQ2X%T-1d+)@Cg2dUKcF3sD6f8E!89S5d2ng;=BI6u6M;+)Zg(pW&`w?OnL zi*t2}Su`=1O9ndwCkDiRr)WdxAFgpNzj)68p0$9Moxunib)l(sx{VfRT|(j=&4<(b>I`S{YseHbqc$*%>aaWn&)WR z{wNGOepQT~&TRp`Lg|&TfSX5Y0GfVO;&T-A{mjIV%fI=XzZu!Zix>SPNcMj&3Hh&U zM!NxVz!dC(Z7>N~^WTl7>^(x+X~2GkNCacCEE9nq=DN=kG6O@dN(sj&e!w?;!#5nC z`V3JnRYr;PsFAzYM^PrC z5D#<4q*llyPnddd8H;j;kh(Tet<^v}%&*kic`V&h`w#rU54`xrFNOmD{IW0mGTSs- zrD4$-l%l*EQn2V8Mz_vkcJW-)aLNabwuqpU&DaK_Gz94K1cqqDbSU^ZA9qWH6(zSr)xy?>jZjh&j;yo)a%I|MV~zzKlGcneF|a83iY`MgJg0L*6L-om;7WEaWpy=o%K-*3(!tvGnHA-fXx zWFi)W8_Q#wimibjsYnka6W4HNh)jgE%$@K`21HaM^=S5%(~Re&-iP2uH+$ehjYBFN zkB>QWIlu)6umFUBgJ}c84U%}l&+WzNStmWyEtSFoD83j0%hl8KpZ|PYd(lLUW)ffp zlPQ>i%mFW)aR5qlg_kg)-&+jL3p?@{NC3ZJwiRVaqLQ7ajfFN2MXU0PQHB_(v4nNs zcYz&8huJfco-{yQUn#uyI`Kks~lu;;3D%KO^KH<7*WJz4t z){hO*J_i;Z_$nG47>Yn3xJwn@>JeESGes7M2@n=vK+Y^=2>cQIv5JqMG!&SwxS4%i4kqHi@P#k*frrBVW?X2%kcBAHi6a^f3;nifeAV&aHc-lUd^BTs z%GzeJXM{Ms48-4DOhN-U9P|^QaG=L4mf**Qwtl-j{WvAt4@)LaQM1Ms&*AU4+BtREmjjlu9} ze$7es>p^TA7dkvKK6V@Kc3dQ~8CD^k_@HPbVj`gfhzeGs2PoMC>;f7?dkZ`<_g0`_ zWl;%H!|&^26b z6J%zQT`Z98(dVd9!jClo74|^&h!v9(UkpTPHz5q+z$Q%RLO@s%hHNF2W;9B}snd*J zi3rY~%-8wA-H{%K$i&$ni_u^@II{**vmTO|8V7I9KC4K=t*>(fxP+$l(u^X|Q;t74 zkPmp{g$oy))$I4_D$t#}C|n?*SRe6R6N~#Yv{7^}>wAm2Jv)W7hY$p)uQfj=^UAOi#qyA1S22iCss z`ZHr2bHfc3lqGD4ivxCM>;W{`3K8Bq_ZtF4&@1eX0XRCa9Aan_Loq2bvj8hhy@jlP zGOP6?U^&c&Y|b%qgD;N$rWF%Of?;rQpc{oa{s!jt8AFyZhT-isl&#GNN9lsriVLYv ztxzjqAPOgkM7v7{ByjuI$6T=D!TzlHx<@wUXjbZim^n(hn;0rM=g0< z`^OD56v_dm#R>sF|MFYAJi9gsaw^UbL^|iae3III3jUEF`4I;)DutI#I&{ECnIN_! zqi|#nf?i?bUlp`Z^r;Y;V~1qmqoSP6S`0Te1K2aB%RBel(18)W@Prp371A0a#Mn-f zlWhKBCGBtfwr^9TBt~+2WkrY$8621G>0BFWfZFMq#w32=@*^r`2AqrD0HLN~WR#pH zUG=yR5ci!CpZLTl=7wMr-vMoJ;Zvd=06siX!_AUdVJs$Cv4?VuPNqc+9_=j|m>jDZ zC)Gx;kPsPRj77;lJ&F=0HH5MGMVu>Z!aJbYCOXzA-jbO`8IF~KYMm|oMCz^mo=iLR zbx=n%NTF#H3}~k4w7d8JM0I26WWX})@xfz1UYO9PeKAFY12Z6@#1B^-7-4Le*Lf4h zVgx_O*#SaivG?cL_7uehMn*7JqF^Whsgp`WZeS1!Beo^+TV@})+%4EL;KLNmg)>U> zlNqaXm=92Ry8gxik60^H||BD+y3+pytILPY~ zfL{&C+$f>xbB_BQr*($|Y8MWG0uh@>*L^MoglvdF-#*bJtcd{;L4kOJ1-1|;n1 zJIp~1F1(ClHDCcRxJU+mdf>*7=KcTg7=t(-IvfqRzWjq2)}bt6uq(Sd31Bnfr41z< z0byzbb4)=%ZZJ!eg2cQ&DRF0%tkGP}g2r$J0tL9qrl0w-L$AzBJOtVXsx8FSgS&yX zmp#{D0b837!YMT^bp#=*)K8}c5pOlXK>H%E;+x@DebraF$b*)C(E$w`!V|iXc7|}2 zLvOR-=PSa5og;tt`xAB}Sa4}Z$;jjqC<++{+Ge_%Ftzw%qhV67pss!O(rqwwt*Cui_aSP($Ntsv0x zH#XDC!?lA_t(aPr(U~N6uizsRKgUGe2eEC05Mw8ZgB2MS4W!JqEYp*1kA+X;tb=>C z@>@9lP#V=~yUdXZZomC@JrGnV7Q|@4gt>mo=Z{VXe)uQW`=aBf2s(6*z+1#dp|??B z6=--m8*pxbkyn_Yfh=O42rmPnY>2kEidXKJ>^wl+Cc|GRNK_0Un=u42v7JPXWmmjg zr+p;r6=Ym&>LG(iL$VE=(NqFmUE^~1wfuK$?~CA+cBawe2BS-V;{!-+?tJ(&I2;|oCm9Iq{>Bh|w9VOQRrug?enQ?3ozEM{x1o=} zhDo`-8cZP5?=cfHkmmKLe~sD(0u)9lNoy7sWeB_siG~bMed<%q<&T@7>>f#iVGQWu zi;|@o(qi$ShBo1w@toImLQ9lDt90(tRdQ8)l@Y$hZm&2R6le^B$hf@2HyS-o`L1Z_ z99igS;Nv*xA3(&JCqf*KCNf+>AX1lR5HLSH5!g(K=}ZVER?>hUl>5%)QFvjTE`o4o zfwB>x?I|JdtxjIgLRbagXBB>K9oMB9y3~@r-9Mk?;QQ z@3vzV9oRQpLBKNbF62p8^!>Rb+j)Kmt8Pn1P>iP!va_QfKt8UTIhl<uMUdW5Yo2yS44bQIubi< zn5Gycnt&65!-U&Le0FwrOf5PjUdG3V*^nXR!4PsDix;ILX6L|bkKp@~0K|;kWf33~ z(J-Td2hrW)9*}V=O0z}^1frc3&*60(TUG`KhlF4#BcnjhDPaK1r+zf_#Li#-0MYeJMi2AOK)sM?ZHpexB%n z$(e<9X`_rH%4ov_XKkV1$L11YjW1SLMtb9NK zd%gg*SHMa@Fr$9|rn1jnL^Y{P1p=^n28p4G$giQWsCFlN;x4>?Zw;GOknlds*5(5$ zST5cpl~G>-f@VRYQx7s}oEw8mTySs$q<*f5jP{X1*LNqWUfYjQWo&+{N0MiDw)5N- zNLaB&763M&;wFx>NYIKk#=8Ay1ndKUW6>lePzh!_1(qmDt1U5`UC~0pr9TAK^a&cTsF6an1!ZS*yLI`%&XHQg z1=^!zK#2Qr7dtfU+UQ>=0u9I-j*lT&fxEWZ|U@-u86-ajmF_eE3`?2c@Zt!+4)tO*zMNX#{)FX0s(p$ z+u!*CvLCKU=6;0wtYZf3-*RB7?G+KqOzb*rZXhA`g?(V-vuPr3W{S9-%Md z^Jvz^NSGWe0=;d4EroUYxd8(<3UdWF!VdmAvix&X~1|Z4m3QCqISR)pXT;sSC z=mdZae>Jo;psQg|M;62e>g+y2C~-z{a%2jaNE;+%69_WpjVbO$DE&;t0h4_&Wm_9; zb(+QMaLyE_(c|{j7HHc}hnCN`C2YqvT5>RVu?CJbRurHs{}+Tl6#D~IhXcNs#>7@S zr)N7j>c!I|o&Yk|Ec8!%DOtNTpoc#jOY<6b^a>`9CQkQq0Bb>0Q1Hmo;Lhg(=i*VA z05KT?jwCpn!Vm-9Yq5%?Dh)75(Sa~16{CoUr^I83enqF6NVWBEYq7wCOXJRTLQxL- z&hZFtzWHW9E@BCE;w}BaPRX@xAnv^LPQJz%{Hy>O2vDY8Q?MUQg4m;iz*`Ody~Q)s z^y^V4m=ZTy~d!&==nwN{ECQf{0lf8!mb{^jk=r@vOmk(Aa?5XE0`?mp@eADX;%nitkAEB4tBNt z-#2Pk&umm-HzI|C46#u>-zo}!WMem^A`(%ZR746(YmEoN)_<^b3wl^TRqz}dcDo`U zh_<#@J`DIhNjj&rfS^^EZ1&y$)Ie$H<(08K&AwX$EB4?~p`RR;V!fh6taNI}Fa_2& zm+*~JVE||tmF)!@;fauj`!;}~Z9a?A>9Uaw;6slI0;9~1m{r&Y@^;G+M_}#ZNN{nE zVF`p}SD27TF{81YnfHTZRF;Ob@YP1ceA;a7&JRzb^s(R3Slnf+(HxPd81V?BZ!ks~>T}lu@ zd@Dl91{97jp0F06V}&@*Ou_-z&ke*J_C|h&R6f9sNQy?vQBOIjt1I+u?R^o=(nzRS zKh4sbW015fLOkkS^2^yCS93c3ue>PpVzzP8fF#%gen?c~|$8Clf z2ut|k=9L+l9W!DUWB@!SLKsA40#?F^gPZ^wFh=H(Vf!!EAZA0@;%Tg3DF`lpd}7Ap zj*$z@;t^k`HVS zs4>S(IrJl&(utZ!(>#Tza|HU;R7ZzuD%FF7qODV{jb2+L-PSNr1AiqnF^4`H@Co-o zw<*yh6tFZ~lPrg|xLxo;%c0f*w|ij6M=1&=*T#Y4Tb+flUj5DoZY;>)hY1X6XJ@CF zz-$;svyXU&x3pzXk{?=cZ9bqc3r;&4)Ip~TBxucJE~H23=UO_ul>;Qem znb*8_vJNG{ZY^s3MHX?v&n1N(w^g>2JRfs!6O1J0)z)ct{aL)_D1_YefXxRCZ3Xev zn~u{}5ijkjTaE@@S+yK#u{;f4{7Szh3QCP~xn)9%+7VC)&w-k(phrJtTB@G(1{wpk zt5YNGDaa^u^ny|w2gKeIM!Yr!Yw=g>q7q`_5sVmSAqF&k32=SkW0EXx11oHSk9(I7 z5*4vE5`d5v!v8H_r#-Ljq(=$QeKe~e0}~~Nfh`BPX4GRyJT)pKInL8QqYX=F6_*!x z>Ea#=mM>QF3##_>O`6Or!wy(_C=GDX(Kf^@e5eAB*!Gy4pwYjX?b07G(P}-&#wu{r z8L>uld8?mk8Llmhvjp(H@P#k5FOlXw@@t;X9$1C_gsTGg4e}Y`88c~ZDHxUb0AH{C zDXWIBn`)O!=B!Gn|Um#`0KW@o7{#MQ^xbQcRF(c%=ui zsX=(xU3cAa#~lI(wuX~|w`Z>=72+8Uv)0dC6vK^GV9Z$I&9+R+dTcrvldTZ`=vjjj zaq^&7yv=Y;moU9O{k26Ak{T->DT*4ia<8n;l090Qg{x6EBpURo6w0{=ZM1M>MP8#I zRa>pCXE|vM8=@vdJ^0t4TPQf{IrGW%K2$h!z^KzkA~7(2E1EL^n-b`Aa)LQbAh04v zB}|wdA7)@|x0h_g5I=d)?<)o4ab`80t4rwwnb_yLBMn%ZU4>G7AdwfO)H zK^y#|5o)4quc(`{+Ebd7Pza+vvW@(wvyslnrb%u&3|$YSC$o@BWBO@Q9E4rOp@5i= z(J-CRTOiVmv9y84K(FvIkxmHmx+MoRm1vMvJR&ySqgl-BV=_rF=ktJe4seMC6g}Ps z3QCrMOwMp#5z|EqlIX_@Lk+e;PU*yt*~EQ4_`up}()Fd%|TMazz5c8dj%1?EocJKD+DL2J$`2vXfgnBi#Y@nvir3g z9Buo^?9tQ=Ab&@uy+n}BFiw_?n=J)5x3{ol+d)5+umqnLd_rRzLW*dBhU3Q*ACv|z z86MT%+I#@r);tZ6)3T&0#B0-$(@6 zumfR2b|JrCF8i5;tY9I?*dMD>$cC*UEiKtbCoSRh*Gqw|rNTpmT8AVzel*Pg>7)~Y z6&Zp|KYZm3I-w;B@rvGzr6QW45RdrKFiefA$180GlhKbyZ9LH=gB}{DTOf+yexHUd zYq6tuM?^L@hLwoGrMaurE;$M7IM~QTUr1sVGo-i$p|!}>wV z7~*d(!hp%mGaC?6*am6&IP3M6@$h}$5~PH-2n>d8z+DYu4d)4l%AD9hL5xYeI<^sE z2~G^|6`dm;6ijUj)6vGE;vLnwwfTT|EzzYHN>|e+WrZRo%FKGeAV$*>8nw5a)KnVhdvequBx!c)!M*}d4o4H$}y;xfTu!dKY&#&NKC^=>!w>G_R(g#v(&d*{2+x36mOo)FYRe zS~9+H;R1+B<=24%w{xDjZx)o~v>)W9c5w2&G*4i!%$s>=aDJ4WU-2jbkPUMSRES45 zHtAb54!$$t>1NpnNgDzyLV&mh?vs4GA1#@lY0RF0W_S=a>ZGnQ_yLP5yh5B&QJSKy zN8_XvWonS&3Bi~{SsUmKKtN9h2=aP6hBh?dOFW$j(90QUtWG}~JT?5jz*fK(!3~nw zvBsef%ZjJ zg>&iwfVUM;ZLH|tANGVU{4i1QH{*z*4PZk|2Rw1X)bPU@obnCW0LlLm_>w^$uUsiQ z3CaZa4mc<=N35;WR>D}FK28cfHl74vZC0fA%6`PH0&cQGmXRvb0P#+FNt1i{UH*_j z|I}#tB)7woHEsW8N-2=1S(hP0kJ7?98~2knX;_bl3@i=?Q0nriHiY9rnc8#Xqcjr) z-X?|PAPI3sMh28SJ3DS5(Kyu09CDJKV6mP&{23EuUUe&&7^Ko5 zwpkDZ*OtO|K@zeM3-lJg!HP_O1J6lKw|oa86e}N47i}nm5v`6YVBKRz6@1!=kMfHs zhr=|MY^SpZZa9;n>9jHT2paYVL$k!7J@il89A=jM+4*=pHSiyHJh=XBbK zf$bj6hMa*A!U|_K4zW$GX}tb1FR!A>ESM|~OhI(^6EstU(|rO->~m2{7z$#?K!ps> zl9HBfATh{BB-jI((cq>n?%QS}cDbzBDoSG(qHQ3r@MCuONKTYukTn`he&tYWIE9_m zl2nOJ{m+!`sI-um+&(b<7dJOu$JAj2fc5W|<|>uIjhMl!@u zTR~~K5{+4aXsm`FHDQQEJkj65rMCn&wS-}x!3NkBVr=m#mym&LjTq3FNIcR5R_K{TtPbIw+F6((RPU{cG-|62*NGSb{`U25jf8u@8zX}#074-ftHOj?7z)S*bRQ4x zCH>Tlz=DP0mTWrXQmC3~rSB3MQf<2wEKhL?Ri2sL7Tp&Pq*(M5Vyg zPCp)b3)IveQ}4dz=!9DMLY4yQ#9jWl^&SlV^plXo!Ob6^Ce_`xc1{?$4x1R zJtq&-m5R_e%Q>kbNb_p$)0Io6lF}$aBXBhoC;(1_q7;oR>T1`dI0#@LYuf6A#J`!I`vQ5ZJAf+>+JKptgXZN2!Psrq&-T{VZ^w zgoi%fYxIY{K3koGcbX+U?Q=q^RCJ0Roy*)a@F>S^G|mUz-AfWM2*OVKh5&3Ur{a(XPsLm~7(Plf>9k=Z++gBzgT1m^ZfssVJAXRs+n`V-ptBX+gmrw{>5C@r ze86cTgl8^CSg9Lm-UKKUr0ooegqR55BEDL<$H9z5a(_rd#3-Fvp{dcwa6_;n zAu%+p#X@j!8MwQRV4?^FE)(qUI6M1@aD89=U2%HRjQDEgdiQz4I{LH8pT)iK-1esF}!`dfw{PqgIxkJ*xM>zI=$((1X71KReXU|H&+<*@!Sa;4+CO z@<{j`n}@SfOZGDox1AOu5Rf)xXZEdcq?uVJl?t8wh-RpQJ_~;uXaZu0YjB4`1Xc*N zDb}m%IvWTwkF2OTUOESax9qB*(WvG^&#p$onY~7#nRpTCRJ8eW(qhtZ3o$7RcOyzX zwxMBxi~&udNx#Q77VR6P4R;TcgkWNggh5F>4WsU}Y;8VBr?il|(|)!>i9of(qA9sl zUe&MDBYkP}OxnbOLQ&$NACMk3D0dITjh|z?oWj_n^d1?4f|MyVz;@xn1rcAU0=zv! zKO``Pk3#IvxewU3JS7Y?+QeXp-AgbmVyj7OIG$Nmwkxbyo$t&h5;d|~#Du?3%Q)o( zG)Y1}K$FhVzLC(39(tw$ww3TBAWK zju;+1nYxiVNkF1FivSYZ?8zv=H%q!U_9&%e=oP#T$C(T`;6o%wKd}rS(zAe@s z*{P`W69rl$h2&S1AaTl+$PjpS#~pX*6l&?jDM1?w`Y9F`$f#Ej=Jaq7&}GlEJswWC z)2}g)8Va5$m5y{!4p6VG#YxV*J_Wh)Yttu_xj`b8cn53J8q4|p<-ATi3Hg8l3}j7> z+sH7lwya@+mDXj+l%oxI1)v^vvUZ&)wWF*JWvccT=y`;<5u=Qls&!@woRQD!M~^dH zyzRJ#Mn5t&=8Q+3F!8Bw_Q>K{kGfH!JWQLEczoGhqP)A`M?LCM?hj$D-$x@6iC?1> z1k^0VdC{KI57S*8$AK73ix`*-K3L6kS7>Z)KA;pzrwbTsgG5b7;syQEvREl7h$Gwy zSvY~5F=ZlZ@Yu4s+!r|b9{uXUFrG#!l#99HGs~gsHiV5L+l=0;Kx;ZRCIrKT&9x;o zOT}A5%*!~0GZ6t9Q!vmdS;RewPI&7_gM+qL9_0*cwBMBT7>fAf6(POF^H3qrP$y-A zQdG4T(VyP3cnhc7^rtbqah3< zhC1<4>%k;$h_gLCP}4@EG>b-K_dV*YXBtCb(uQk5xxGKmP-YMQ>X-reALrT60onog zr7wM{pNb$R$vyYnLjYGDEZkK`r({zw5*$piC)wst5D+)xmFum|2V(~{0!f=*#0dF7 z@g>=HiUdGM<#TvYdI&KT7Sk4+_u7Ptzm$s@H#C)O1*J~)QF_M>rCCg2pjm|PdW3$R z3h~-;B&UhIN-&hBZWf=I6vHKh_J$%OGGobrk5_iYF3Jfn+=zn;ZkTw537X7B=h-cm zX5n;2pOn*zBnHd0C@$fx4U~OTcWc7^Lm&E3%cmA!ZjGWnZbomSKW?-a=y+lh+w_CK zx7yVj(-Xne5qMhNJYj;I}BDo+!V`tzWFX8EYyxk0X+(?VBTpd)7eR!I|FaLX41Cd7v8u zF(OJdvbq3YHTIW%^51jEN;#)O`= z#RQq6M?gnlir6A(I%8cIC`?d3U^*bzW)SSaK($BFL_V-j|cr$RqVuv=JDt##U#hsEr<8d9}R=q`b8d|I&cJGLg)j44mZ9W0GU@ zZ88f$ep>(%LG8XQ1Q5@XT^-0AhMNLI%jEW$S9D%(ZtZ`qak)(Gd~+|%Z!V5#TVrVU zs5ga1{FjEk4Mdc{(yvoH<-;W$Ju0y1QNNfVdWg~Xi0ZKu1cobE#d)-E#(KQfQ1DSS zyuyJcGeX(jBS4v}WWXeNuAh^L^;W09-i9@QSzLcA*=t6(TQJ3B%cZfruWkQ{#t zk@N1p`)+Z^HJ3PO7Mu+?uh?NM+d5RvGt|jvED$IuljIudf%Axu)@$MV;f0bO#882i z-VnU_#V__t-~7`?TZ4uX(+?){D^2QD>okiJ5FQoG)%9z5r4T?pZyQ^GZLcu5CxD~P z9i&*Azd<1kvwZr~pZ@U8KiLf(Z~eMV#DTSZ;Kq;t`}IgkLR|2R1ER(pPn^$ffdW7& zGi_ws+8X*pN?oBPjVOa}`oYHrAftH^f`EbmyISI=8u9c;+*o$@NeIRDZ)! z&ZGws!6ddBs>f#csKIEx^?86v+;-b-{vlic$91=Y9T`o*bbVWPK5#=|e_;2(PT00I zfJ1u%4NS_7My=d96=>HPLfSC1Tck}jvcfSk^u|)C24ZdD z#?8Age(rF6<#%|up@V^ef`zx8;Dq3!HqQ7LQ#6#Nq6O{<3NhZAG!7fudoO?WEAfd# zSVjh50fM318W2y$68e2ms^36f!ES6W+&&v_;~;iu$UWr5iX>Q#eP}P`JZ)_&8j;d~ zN5WI4ylA7O?c63}dMiFE&_SaCN@RSMavTI=usGk9*+va%vitLbUMvj5BLDB5J0E7^dt;2VnhH+Fu4(CT?8N%A#FsW z_f?sh8P3LRCLr#p*VI;o8)3*`mdqF>wgk}F;z9$~nEox32GkTnOB6x_UcpXhNquOd z%%apq@K{l1deezNCyKv*qnfd{;A?m2aA{A8Ms$Xzv@KA0WCqJw#0vI9B!b~dDqyjq zrsjzrNv?*kPP+t$SLYAcL}u~7SCI_-p^{gv5%FUl^B6QNhXC?~3nF|7U)0hdt%bK7 zhE$K?hZe-k{;kRf%Z0(lqq;_~KqZ2RjC5y@Oj-0BgtP|lh5!$8-U=LaomENKV#-sW z`cwmXtnD_W(I}8M1ZFixjG0{{D;beSs`}M6z{5DaC6yY_w)|wkoR9*&_(g2On3`qS zMEIxoWGrSBfA(j%B?xdf#UAmokoGb}HN^;dMjO;+A(ja>ib`qLl8?10TH;iSM^0-G zm1k0>R0>DY|9Q`Qo|NEVZuiaiTqxQ139r=N*}_m=^4?A}~1tE#G$-@+y1V*vq9j zWumokYI=qRaBZ{bnI@S>Hh)k*uz=mcvZUPiHsCPquY#oT=RD^*fa7BACxqrn%rb!x zpEYhwW-qo7PthPAcgCNPY=bGv1ilj69QgektPNy`7cXAq!c42+U}>2&PZ!6;0e1k5 z#6A@(uh@)%%OzZGdu#InMbJ^|s6knZyh@ey%V@Y3B|-~G!J{8BZ{(}`Z55yNq$e45 z`|Y>;pH4WYX+Po-kC+d+kmKk0FfZnUiLp(Mk1}m<169wmfuSCC(WH<;9`p*kX@7x+ z#rOu!T(tel*sUTvaBg+!;gcg-hddYp6q@ZLc`J)z zy`3|VY5Qb|z;PeM2*`#HZrK&VG#G>5o5}~6w<9D^wt>HaN3Hjm{ac<70yvE67o1ed z^J!L4pma(snxmmjG?c3rK=r^Md8U}fE2f}F!y^vJwDmBH_>%|lwySO}{VkQF07G2%`N&5;Qd)HtV|S)FgL65f;LhhI3~jZO1U_kx0GO~G#ndEG^dLw=IC_OY%7!2S z0cm9dn|&t42syUT{*1>4vn?Lu^k}$A$?u{P;;s&W=w<*-2?K#;;;)!jG4DAqAQl;= zfBBWF^Bo3olv>i88b-^4zQ-C+@K_tM0K$(8Y|M;|iY4fm9t=Sy zcH4m&s}q8v3Idc2xZj3I21Ws0VGQ`d*F+4!uR@p*U&evaibCcRyB_k8hwSd|xig{9=6paw);~V10TSlI3B9uWQ@9_@kO4r<)d04#=j)rD9eN)d-4Z_e z$xk-QM$%6~40-(H9}gtZ;>ka~f-WZD!DLV|smCPIShkzjEYYYl#QdDaFmZ2LHcA%b z-lKn&NYHm;#frc?J39^mh?$G%FHf)%yA0e|9wS4Btrg**6In53Pn5G+DAS6ph1>8M zjai4b9(togSWum?F2tjRqd;GzMh`VH8Y`ONft;3WNvJ+73D%M>*QH+im-uZ6Xw`>1 zdiMD@#Yz&erq6_-UdDj4K##^isF_#WEYbk0lMfh)@PWA~@3;vdQJ%m885+aM8Cc%h ze>nN!&-uwl0Iv!b?0cO4EpE^ZENGA3N~=r5IJoeQ&i+>wO{!ZutbUyo z4pVkT?Km@@Hk3eT27NjBF5eYcQl7&!vgZXnLd-&&4S?_23M=L4!) zu9ns)RIKE9^d11*}OXID7v)bK)o0%X1nCyZ{ zn8gS43O;%imLXD9P-1Q_1(`?v+7V-`=oDg{Ozjj*>KmibU{V9YZ)0oMz!3QWPY`SD zWLYZP#YB{0D5VzSnL_OqJ(vjRGf{|5Z~H{mqo1zyTZ^`$tsexG9KAp62&5C;~Ut$ppBm5s2do-Oh~A2ap>lnVa&>#jX(B z)8XJ^ff(jBMORoZtZ*YLZ}ms%~+pu;5D0+;o z(b~^dEgaJ)N1!yJ7PwcXkbSE2*znOIhIwUKVfxMMV%Am+ri$>Q0GcJR2cGK$;2mBm&i7?6d)bR#^djFO{W{QZ zLL3_rpahR8HZ$+dSOXtr{C3_h=V}zQC`_|F5+WdguSO=a#Q?eg4VJshzJ)S4rfG{I zR52h{NPe48rh`)ON&q#%h*z#ML=XNMkoGArR7YcKa#kmBZb@U_j&3z5LAbYG3)FxJ zA44N{%jMo`)2d)W{whLXqJs>H;SHF7aT={US)M0jiuRN+Nz_>7}U^bU;?^N z#IR#UZYKQ+A;W_mE7Hl*gdoGQGDL%^Xv+}Vtcw{~i%zy-QpU-P&B%VxE95ch#0@+# zKWdrB>A$YQ$C=0#`Xg* z#`oISzSf@3zaHwRDZ(_56>;PYKHt2V%S_!|9?htb1e_j)2@O377QJ}Q?1TxM3m+c+ zl7V?KCf7ldK_LJKRwCBhd&(9BW|2(Lx`YzLaVE3EXkt#JCK{>ETw~-1N~NW#tp#?J z!+!wkEnm_JYf7M>7E1{f;w@s@gEIY7FV6JlEgiz0qe{J4+3ZUL%tP#^YDK4TZ!CsJ z;ih&zvpqD6jhpDe;q?>4ERZ5Y-vI0Z84rhNEo63UWZNi8a-t!2#?aEHMogr6JP!@|7;2PbTMaL|%8`$`_NBVCD>IfpVFU=-I?pwcP6Szw zxtdN5ME#6P8_?i(-Nyp`UdYDLKpeOy8dhN<&W@6Sk(phw6PPOaSQHy(3Hs5SeRgsT zKrtM*ipOL?dCg^O^Fh&eqqIG((KY9Eqe8!Mr8qsX@d_m}ae+UBfpXzQ?_Pm{V}k)U z+`GVm(#Hdi?G*o#m%PL^j*kj{IEc*c4eUh(wPcQ!A;y~8aPzX%W*5rEo!~T=l1-3= zo#81u_<(G4VP`>9JPAX1aR+C_S)?DE`cozX3mi{*%2WKswP?i}^pFQxQy54FoHIo2 zYqpphJdi?~(L$y{vHVcTrUdJ4nchZh?P;C{sEf`~rhl45uOC4@YMj-WaN4i{S>CP( zl<82d*n#4p;DE+JW+Rcv7QX$0=GI$pm9q(EES^|N63jr4w}dp-EDV6OnF=yrr`#IB zngya40+@$|KuV8Mra-2Y15Ayl+>e#FNn7oaoa(JC8xZy?`nNz!)=+5MeG&79xKkaV z>1})*+32K(ngR{XVK|E*f|>vmj13>(Us9MVxGdI}Qn%@R(dr+1h-Nb_VYrg~gELoC-bC9sPQ%q1F?| zsT*d!TBB$bNk2MGA?w(r2nTi$_>&)*e``-9ayoh96Q9^0E8;9j5!>w6!hn`b zShkmoI?ZjXAl|@~sFtr`v8`xe9Wt*FgrK-UhkA_SsA_7|X)OBONoPZJYV_8t-m06I z^3CO~o`%4Vhf4+sz(nDqJoFJ@jz%MH39Pe(WFp0*esCMboD4@#Y(>AfBp*Ir)u|_G zJ;tZn<8~Jsu<-QIl52Al{-wwHfJg8hNYT<2v@sg1p+;Q2Z`4tI5C>Bnj0zVLCL~i~ z3E<|cc9vXTrZ*&+(V*96Bs2Ro66Ok1N3f56pX68jxc&KnMgTx-A}>7hOkO80gypCV zFbW7!B7;OYjmB#r2z9%&n{U3^cV;)sc6NT0BEGv`V37j1xkLxS0xMH6R`ezn%Iz9D z1Nv8sS&1DDdC;qg#FSl9+3YxA+MF7Qq5EaU59CD$!(|jkhO=DaO8N;;c!IlToLh@m z8dhOB8j0PjWxJkJUh|oygi9;(v$2=GpPBM`h@gQuCTm*!9+;sR8Jf|Qw=htnhawt}fCM)8c2;m=a2+fGu*H7#CRl6B4I2D8 z;3WWW@i&${_B9Me1gVXTHM?hY8Rc7rlpr6-5U!8h9Z5NeO<^r0kpTQ+!MwgYcGARz zz~o765|TF4nVwnEnP^a2xa_MbPzg$}v_nL@RR^m_9K52p=;LGU8VWMC$2_xsofDe1 zmELL~$egre*bw;ui21yJG+=_J9``?46vSsZsT!z8kH3N*<@9SPmQx|8hIyyfNIxy1 zx>4t$rV2||EA$33XDldiF^6m#fPz}{%}Or1l&PoMJ`&v{&O(5Zw=HN&vI zBOa-27YHe;#DP6T4EN={M+q?f8U`*OYwJ8}y$6X8?YTGJ9s2rAcc!wX<3VMquYEe5 zM?*O0KGeO1nWEv9&AuIdNku1^^|&DB{A^Jg0Ke#LTdFM|;Mrw@QH}-TyRo+RZv6H8 z8X<_=(cmEZ`-!1AA`{r}m<~^Ex6)3XxZ!CEDM2dgJGKTIVBy&+tQ0~1Ks>Kn&a@*t z6Zn#RDQ#`~v^pN;twm|;uT!nvO6ftgwM-W>RHPHK-lEq|X&RUy)6X-*R6FH(Wk?Ft zHsdJSM2g`G2m~VlDbSJTy)qCAkTrxP$cTYS+!8W|IyY?g49VLK<9q-9xgG?KHL$TY zyj8SoY&vrD_(PyHZVzP4WIU?G<`<4kI7QihWECC)kZO= zvO61daN*8GvS0M*3u2O&8*pHa%PD43jpbZRnq_-QzwEz;_SWVDI?BO{?9}S9+AE#Y zuQTG*st$w5dYfi(;Mj_>dU&`tfVs4`ng!^Gw{T#%qF-Sg0==sw+yn^Q|5v=?6>fq2 zZ;$+|HO$whCId3WdBVpAlnlkAh9IJdI368`O===pff(6V!stBIu!x=f>ScR6Pjkca z_lZXd_0YT`=~5J(&_CUxr*L(upe~&>qaZeLw2CHesPU}SsrIODBCNF4Ix$z*j+IAb zGPj)|MBEwrnB-SO(g0$wOl=AgA%^G^sexW8+TKQBh#qbHr$;+s4%S1{tJoeS+rFNZ z*1Dk2iR6QwogJGke?;Q9;PzWIdFaA(VH_xSbX$Wym*t>7y&ay~BS!u8gemAzvIOG2 z=}m898-8viZ|O9bw;Anl(`dSxbUD|OY}!{#KLZbKY;!)aV15hu0MiD*c(wRjKeEw& zI)XuPW;DxAaYqRxSZX&5ZuTSS<$Pn!qDCeLwc|s^4z$tO)cXct7s)t?B$OaVi3x^I zmm0>h7`2iBf=h9vN8AKEl*q{Mm2HT&(!K*3wjSfq8kg<(obq~>pPWmisLos2)KN^o z#iF5kYEeVSV|Qyopi>Y1X9hH5RF5&Q-qYY^&Ev1=$GoXSHp+S;(C^9)H^2S$&kXqR z;}IW}$j~I79!NE5ZxMK_r&;RMQ!z@zD?r!AP0@ogp4v*CwYRvwte8unSZO_$&x3Y% zcWK`F+nmFe$MKZk7#!ZGa?Y~URI zMG2JLoEaor^fQY`{ZkVovV--|Ea}aAkn;FFTb>UBDaFwg80gm{BPf#6)Dp-PmnlS6 zPaL#^2P@k>-1HZ_i#Y6w1wguc{Nhe?F%Elt!$qsUG5CFTY`pHOC>DZUY-7OP~fnjRj(+YgYmSytA*#`4Wtc^ zl;%}EG^6OzuMr=50Gu`*YDXNs$7WP32-MMNq+yTxB>+sodiULTOZFbkl7E^AQXUP# zwrzp7w-GdpHcIVfL8j>MRgY>kW;aB8L~Y=~o+ZPx-cAoz(W=!78=ziC1V6-}-fnAA zrl^BUu3xPTh&7DDa5=;yvYByTqqiGQmnwYNk|5yt&NA7C3xh;rkjalm(MB0_Y-_}2 z9yJb@q4erF^anW{+H>af0bRgDndPOO|4-h%?OJ+Vg&uzm5JKu@!v#V>IBgP+u^r4Y zKp-Sw0uc%#T=O2hK@t=YBm@#5KuExyj$@ku;&6cg0ZE&a@(f5@TYLZJ{C5wR=Uj93 zT<5lTU6e86umW=k z=iA`u0b&idy9FAC^8uQO^L7BasR7WC#mY5HlAw_xaTYkhs&iV^hu7DJ50xASrKLpm zZ=iXFg{Rt5a!TL|3(7GkV1{v9#qGfR^g9IS9lq|*gR>3hFHNYBiMmLD0i{K79Ka~c zj1mGysH!7qAjJ>CZ~)+=H>=l@6%gBzF8r;PUh>2BpE9lt%13og){X)wWhCbDk0AvB zvEA>}d>{Y#W5z{?R2{1UHI&@t0`4gF49-`sU68TTgY%)O9>|0;2b!&+!A*azHcJ5b zSQ|IuoB{5qda}$CXSQRYP!46vwESGs-vGopVQlA_Q;iw|v*U9x`k4x^uPNnrnfs7r zP>5CDPJ;N4+?HDsXBlE&3zkS(4v9cC<5>`4u!_%-J)V}}Bi0@c+~NNDpa0o+B@QYQ zQ)XBhrq5t*3pndma$7oGq1-|nL-Cd|g;rK66ME4q7~(O+j&aQ@5w%KC!7`(^W~3$4 zl2TkDt$K16Q|0i|hmo zO+_KisSRUEp#*4SpegNmH6Bk&=A7En))b)rZ2gDH4%zCZ_=M5tOi2E>sz-#K|1>L(uiRz$gd7QK2kqmE!;CzSP&FFNs zQpVi^y;6nenqefFs$M{ z=ZaOPLh2jF`TvKW_k?5oS!gocrV>q&a;Ex?-75kYF)o@2@|D1Oq3kt|&=J9xyJDWc_-*8+M%VagVO5bLRg2B2Y54~WAK+jTgUE3$<@D+8T>?4WRsgFS0<7Z3vG%F-kP^PolMHSN{^1J$d_Xiiy&8=P4tj}UOXA?h zria&dBdIuygVTc)=NHx*hX~` z@O+@w=VDXx0KGQpC882#3Xrw3B>Ko^EdZur`4l{u4c{&2q!6b_d8;UcJt823mnHZk zTgx9xvwF>x9_7k>V6y|ijsO;&t?l?N$2PPiKPdN{X3KTDix)Sx^K*!(p|Km0<-^21 zQtlxFY>Cznw*+A;VKhnQFH8KY;Y#S@Ng0$WKRh8PmnDV7B3irQe*Sxy zRII${!R3a0Snb7@8X90ls=Yb}BG#5O^gYhh^Omt0?aVg`vfLm&0 zAcBiAKP2>$_-j88Y(n;HtJz?U>Z_OcF=HFD_J5e|>IWR)V`5ASScB9=H=dOY7zDPc zka)f5UP-rD0no@NFCQ!1Fd7<|QYuy>B~8d0C08I#!jKDqk*o@nTO=l~B-p|jVnQfc zuCd4f9vbvkrQQl0l9;UP>&J@xKoCtvnSErK(cj)^dsxE88hF^os#V}=DD7~yp2xDz z5@fh>nw@Vm_h+BId9x748+>mC(OVABvHZ-X9FAbC%jP!;K*2qi-*Cu$9Pl5laFZ=k z%X#2S68H$v)45e4*M)lFHU4iZ6+wp;-=3*c}f zIGj(w0Q3OM+h3MwC}C{9+=$o^<)~*5+Lf(>v;@3e`rr=>aEspCA$Vn1xcz&P)DUa? z$d0q5p9~S8PYNUgiV=-7yhi^Kotb)+8vI~ZV8w$`Hroc53{)b~{m=2^S1vl}9YT=k z8AyMyer0=G@U@=@Y}UrS{I&K?p(!WEp*ujShIq^G)h<8oGc2_5LmGc|L9FgS zgJ{YOBCHiQQ<^DEG#Gdb!DG(xfaTUo4h*d#%QnDJ-k1v0RPf4q@GJ6$;qmj<8JjKz z^6F?9U=|?Divk!~92jdjZLG-Eg9n(>^N)Rtk0B1)%E43^4*|*z8bVbNG#IuD6EHpq z+IZ<~VZi8cNq`{nReGEU6d*1uF3I+qmQMs9$v-!4LcVQ`(rh0idq*1QNxoH!nU9`h z4ZWI=Ah#1F5LTJhw-6FNZUTf%Gpsj-%&=(c!R^_04K=XR05BfNq=A=pemrnRfCPvI zK58*C7mI%6_v_ui{_DRwLi+4S)Z8J+vK);5P{Z@k)DE7iFg%lU3~9(A4g;4J2BtQl z8hB^gc6`w@GHH(!gwZkeyPVRBRT1-X?G6XZc0I+hvnM&Rf_ctZ-XJ!F0AK(JAu5ih| z)%%#lF)E!4A~HkGlTZXM;4GEfZ1)*B`x<^b}d1HgpdEoy^?nwB)@B2P5 z%@M<6KHN?PGXK=69VBgQd}WHN)KGJL80~Wd>aojI?vks39S*lLyrJ`eZ5b?AR?CC+ zTvN$FnVa(C5OZ!0g0D5?Sqr&4CgO?`fg!vW2l#2C4wf`!4?dQ|B)YX=7?mh)>l6aR z3$)ZOiCv@YC_2~`wnyBCGofSnDrg8+>Q^6g*DruhA z=TZ@ILiD1X2W(Gu&^_J%x+B|R6mCbRE4&R@m2@N^-sINy&vXdI90psg1daj@8!vk) zFIB9<2eDyoyL9txAq`O4T#}8ph;1=X30?loQcq4|E>hq8&EM?byhDr$VwUPx&kUUM zhA0P~*?oW@l<*j$ZwU;!8f{I5r1Spr+rnJ?d9WImgVw&;t-D_B)+MmfugEZYQwTY< z!lc*a5Mp_-E*ThsP-y@QMN49dkmQhaoAujcK*q>OxoAjGNCf!o>od&H0Me60%fUFy zLV4e%h7ORVlpJGqHpX^^&k%GaQ`xv$%67WZA@>I8L#{%t(s(apr9LO?4Rd3L=<{c5 z^D|^^7{;VsN;EGa5FG@Hf~%H&4nQE{`uwa%peVbMY08#g8mMILaK12r2?H%(frcz1 z+ANhO10&PdE-d#77p8vFsR10rMm_pzc3S(F^RTiPwXe;z& zbu|IwNmWDK^j;SI&=38P?^Ahz<~F0{tL-aHT#g##&T#Mp(2A-!XRHcSJ%Pt9h9pj& z%)(m)ZsI)1@*N`~!;D)28fU=+cKV#SGKofL0o*!L;?Pj|CXqOK*)1vlL}Lg)Z0W4c@P>iWB%jQX8f>9h z{b-1w#H?s$?^ZwMZ$oYKMiL2mGc|@alj5 z=YRh7U;njl4J{;{W0j!~J0KL|5fSJ7& zAj2Q23`a@(jrI@EZ)QSrK1@;OY6mlhtp2k<`?G)gr+@N`ZhwR!^g`y>b;Z}I+{+o^ z@K#N4xhucT2roM7rpOxVz46Bl0Xzl7G&M-66;p0pE3+_=(oAQ`FHfPk(ormZ`9rD#tI4_Vsi8NYd;S% zn_;|=dAYLM3aM?K^Ppb9BSSN-VSV)R0Rwn^9|<$dR{(ukB_UkTH2_TYB{d2m4)C(a z^2Eg_p<&M37x)a62eO3R&C}!d$27Zz2zYE!uIUalYMfW-1?aO;KL94(VnWtK0&>x4 z4w&0Jm5N|$3+j)4!6ft`+xk$ ze>8;mvRE_;rIJ}*1t9hdp8vI+e?Ue7GK(|%Ri(?o7><{U-Kf1vL8e_s(ABP;2x6OJ zS+OrgIY!Vf1BhmJq33P%ue|cgr$7Da&wlo^&H!W`(wwZ~``>#wp1h()BB5wxAz9K3P%4-z`3yC)ZHA;II_6={+f#|v z83G0nt!$MgNoNJN;D8+wY#B9nrmkr?;&P*&Zjc&4V*=#H`&<7+e8>qNx)VHH^Wim&(zy+W_Vh@*{-kEe1X z>^ctzj#z9B{cp3}L*35OC$^b@!^K>OEe<$xMtR6K|?TZ zvbS5pW$1&&8(@{}gcN3L%MXqgd(6>jrH)~OObNM#mPk_>tURerR=x1T3m#+r)8rT; zu*8(I;>rVX7?6dVDcOuOh8h6n1sdtCVhGMk8&-_PAHWv&GHR2^|MNeHk!3#Q1;m}b z1oJZp@;m|nRW$)pYAHrr7j1YBq$IUdl4ne{5QZBO@VtuuU;p)Aeopl7zk6?BiaE%# zSC6bVfinv(G3a0kn4^eu!Au-w(7>1iSdyL?;?Ft&ZB401-VuMtcYFu+thFS=5nMQw zc_u#fIEN$=Tkg_^fOCQ=R-36y0<6F=ld<9lvh;(Qp;;L;sk;WOGug|;&JFNSlaegXCWjbXDTcFrUU24d1oXEHPz?Leo29Zz}sR1{9<)jCqf%;SMp>Ep=|fFa$=8yNO#tFMmde9HdO?(2A~u?B4`vA?eDH>&*s5Oe+M_f?8~_FcLuwI++0Ja}8@4b43NvR2XeBH3 zY=JK&P?i$#ts1rsFVN$Fzt&m`gNY+IhfCUVk=fDw0@diqc6<@QEyr^ zJq_1pRYSxpKKi)DCpew=m*6uTP-;;E;IkKuOi$`I<+{MX>!`07OD25$EQvvM9}#bn$o1^dptq zx|fnFcz9D5=GlCT1GX+Er8VZ7DZ$SBVEhd=vNTI@Q{XgmH3ZHBmmp2qdg6&E+;?DN zHQpGz26H4p=P0=XOaU1Xk}KLp2%2RGI9VxgI2_`0eLdYUr-`0)!}JjvLoOY)y+IZ- z7e+rPe({T6G^_NBh3cT(Gf>sw$$nAvO`-QTLV$x1;E5hh;`kX1lU;`S#O4`GsRXH+ zZ8>^v76Jqm86-hyz)loP2*t-j%4;pr5A&-6wi<0ZQm%^U8GH!ZnO!k--~nx8^S0he zwsKGJ7#6Jjzr%#bY!Jj^2j)pn15SbueBc8vUQUAdyyrc(iFd7D3R#PJo{u>{YvgVm zv3*1kj$p(Amb7q3bi<9h3xOpK@r+>QwsJUxk4D7v#^@Hke%PK0kUlg!1Pyokw1os{ z4OX~MOCwtudATeu9yqMqt6Zp3c%DyLm!Qo43&Fca%WWi z|Lx!Y%})Y;PE;wp5wgmgdWWdq#sBNR?(0OvSDu~*9I?X3STPk2d^94okc<^j8~~cx z$_fIZOdmQJ<~O|tkFwrW1n5QCEKIXSpJ@1K^zlRkxRREvD`a?Ht#%a1Mo1pcrC^nv zH^vWbMtINMdQLN?1&CTY7`mDH(b#uHU;5IQzU|w-?dN{(=YHmAeg-S{+Pr$J5OcC{ z=LE9w1e2az1WoA!5NM}bx<7R)WqfcTCH|4Y1N2HnG(#D|WXSA61d|x4G)n-rM1UhW zqsJCmo^LG|@fa7G(oAVPg3e%gGgDV~9^|8)nB|D3atEta7FAw6lzVR#e5l}q{D3EADdcVB+_W&cZ9w|-a1XP$ZH>%ac%@7%c)4JN`NSgJoJX@ymX3ac^aVGVLC zYV#ZkuUX4E1nJG9u^f=c2F5lG(QEM0g8`<*G5xKLAf9p9thv&MbP@@Qc>owvf8C9g z5d_IPrT+6_HqXl+DvWh3g)Izr<*EOMk?|a|-+;h)8 z`|PtK)n-9yL)mU(E$G$~!PQpjOX)RsM9&b-84YFTL}NI_D?G>ntP0NXm<+4d(g9E! zm}&^4S-bYJ+QBp&iKha?9I-}9=?$nMNY;@KIaSKlA`hMiG(N8+OLBpQEYx_x^7OS0`>3;lb)4XE;xPhv?UCElwBw|>oi(_x_Q$Sq8W(Bp|e7_s~V zV@`pjjOOW69}WKaSTal^mg~xggvP{Hfkc40RWRD{yfCn`!W>Wefg5G0RPiDahQf%^ zXo=gZaFz~-G(3bqWCb-wbkgI%;bNyK2;ATQ{oi|%6Iq{>cmnXl`*(l$cYo72eUpnX0e87&SITYdW;+<5=QMpq4Hf@97Xh^pUc#>ncY5wZ-tJxi+4)MGJ{K50!ys{b0 z_zu7?MPfz^z|~z3EMFa=~=dltz-QhXXeb;RMS|oH4{l zZs{u=0C=v?-KAYSA3m~KRmxHcy{MSgQW%B^89$){;Hkwt;!E}`tKe**A#t0nvpwFx zoN%hF^f`6v5Ym!Urzmg;&qXyfsbol1x5z*I!$0(GiN`yqwM*htPd&wxPHTbdMNnZ< z_bUe$Di8_6fu$J69I!npHvjH-mjD=)hJ}pgFu8~(tum{@;}5B|#O$~c5`pC?A)W4s z-mEAUUFnC8Q zl$z~>kI*3Y#Gr(saenw6$a(Oc-}#;HXMBjKS(_S}+nepf=P-r@6(D?yi$2}*%rX8s zM1%iKfyd+#0jZ(UkI*gdK^HRm)H4ClQ#<^K7+_cdc92c&^fAN}KEd)(g>ih!Ne#iO z6dvRKXxLGDGT7vVCr7N>85% zaDYiwy#_zavDIMUX&XKrFwx)xqKW6ldbm$_Fw_NKDoG@+ag*vxocBETl##^T)>2Hj9;BqvTFLK*DIu$tNubw;X^ovQZ4+ zEf#tTps}`v8v$3>(wR9*DzDN(+7q|hGICc}Tv7!haAO^bfcyZd2`h|wR^bn@ zgnLs1kZCA096=Za0LW+>4zj6+(I4t@n5mUMd_C#W7>1c)OGYcRQ#6L{0PETvSv!14 z6pnmiN*eHOFGXCWIM9&aaLzNFX3N`dBkLA{gGz`xMOmef*g1{|+I{H?8t#WqU+TT$R)0Wt)em5ydP zT=q*)_^$9u0N5;{0)Zgdn>1&+yQJ@kgw^+jzx>O;?5apfLb1X#J-6y_4h9Auqq&7B zYs2K2g=i`zR!uv`V~CIBe_k@)(CFY0!6a0nux_<|9r zJ|4&a>aYIFdGJSn^hY>+!#8}xkNwz>`A*a}cVLJnB>hEGtp{7>nZOUodKS62xdiwE zyD5Efm%4>7{1w`T;p?Tbn!B{sdt0tH2rA9f?dn9=5MbF$x*e6mWdfpnL2zwov4i!Z*Y+Ims#O(n2Z9_bX3`}gmQ z*Y|(__xrHKO9M*;7Y%XfwXNh=4~En@L+9xAL`a78KZAZ}%BnDr6tqK@T*xCA?#MlR z$Zzh0ifk7nex@hWYSsdhdsWg!miM$2BT+zwUX>@>mh^VWFUD?TAO7%%-Sl1m1@T9I zhp zzpfp)*?m4YfZ3|Hd8wT;G~_xCBpQ<6B1prrXcRkJbM!6GAy&a)D=wT|fCSX6zQn4%vg z2N?YW;3=jVmSFmZJCtV+$wIIrCFZ9tynu*P`5Z!~iT9$ulHge4db&!k+1jWvz#)c5 z;#WRkjX!gaohAAX^56Tt-}~L){oPkydF9P-e)GHD^)62aPd@o%8x4V!aI`xoNVKmd zWLwfH;@3W_lrY=Y|0e)*reR)T4_f5O=}DqZPIIX6)4M_g>FPm=azYi56D~w9m@-KY zS;A&WkEq@_!%H2`7~Bp`oeQ76bZD>g6({gvUNs93)=12^qx>;C;9%`6w3cs0L5T_ zGd3Q-WTkBR_{T8JN;B{}wZcPi86fky%P|S|f`yC5Czs)T_FC~mzymcy8w#wZ3%#@a zPyh5!Kl;&+qWSrs|M_>k;~gFg1kTj#T;X=u+rpB^Z9upkY2NYLydV0|hh&0pJ^Wb- zpad)K95|vaB$2T&fP4!e6x{G^D~_K%#4U0Ae9lY1&*69s9a4i({^w!a%`t$3w<8=2 z;3X9j85+AXWW(bHT_5Ga^S}?n_Bp|ot!$GJ>|uj{mcYe9R>=VMT>}00fB!ccACg+) zIg}S*{2nEfe53Z3x4gv;ak)TK-dM{WnjyB5QsWnxLZ}Zo+jz?n3?-nD>{q4mLwPpa zE1yj+cV#bEa3ff9Az91UX4T0;$gK?K;b;%Fz6<ys|qOL%IuBh(`~=5Itk@rh4} zsXr%v-}~O@n(vC*6lGK^^x{irw}JM%=ZVjM{_}oz_6-uvPfkueRouCAhyEplg>p$I zWX?nTHvi4W0Ss#>Lq0^j0sp(Pf7{r`UM}S&yMtq46h?1oUC6-g!S-fRvo2@ex?c{S z2kg8K=NEt3TwgeSDZ5e5}skGeWNkNFN5`&qdxc+m_0%S1w#J_0()`iGDn=W6r*k zC?k$?Qyw11u5H|I7qXXLdg)W2`jr1=6&!6yGDQLGl3H%L#YomX8#v44n*!gVK z>mad1Zz0{B+#VoAkis4X{E+Bd6yfV=5lR{&uFwC;MGpx!`$)C3QBT7|#IfCqjomV2v{1t1|6%$0sKg zU)xHd<7aP*Y$#ia!)+yvw>ip?ftKi7@;KQ0;1}@XEja)h8*FS*r!?4`pZ2()^GmiZ@T(L&--G8z!C&SUr=0Oyfkp+`!>wc?nL+J z$4(SH@uwcPM7RMMlA^C+0ai908_OG9Qh^)mBHbi)&K`rzpl$DbygDT2Na7Cv9} z3A7yOPC%$FjJU@F(G*81;R7Up9^@M{uK^wZk^!YRgw6vnGm8l3WLdJF_e?W8Q3FrI z1gs}b*QtRI0ZBPDdwD!Ra1K5MjRz>{;1qgW+bjn%(Drfh z;s--JPO!LI6bvC_a(R6&$zhYG!-CRGf}tFfSjYz77~H*k*KdysiacYIQu5ZfzSZ4< zJu<_w;nCPahA~f>JK4T-MDWZr&)6Gs4o?JpS6UGCNc%^z0Y=Yb0ITRf{V_|fm8N<} z1g+R^$X(7zZCt_U!F;vm{gH}UtK}}sxmt9vBGJ!-ANj~f{`imoSb#hh_-isH-rmO9 z$*popQ1s%W_Y1Q($bN@ZCVWlg(LvA=P#8Cj2$b5mcjS^a&1`s>3Lb=|E_fB+7v3J_Z9U%w z`6Xkk_5byAw*(N%vWsF?*|1(7`UXkd-SE8Nb+?r;*t+vE1Y4%ryl!&%lmKxs%tk%3 z91@0NGX+>F8^UN-8HQO?BK+2QuW=yh(YGWRpY)lj8w1jFYs$tmOvqO@ODgfUS-0wr zUOr?G%;?Wi`Rc}WK10x}hi&??2fz|a`zK9YF?^gLIb?(Kp@{el*$0T7hr4kMtiwOV|J#ID6st$F(A=~Hv_`_k~#{dLkN>JfQHy9tDdKUC; zi8!hwp8UK)Hk(R%%q;r$!40PQ(sn!#E_jKK!V0 z>Z;2l=}$yb$kk|T2%v4wfr)JNH#_P@e0fdcK$fWW=qB8iC#m(yE3bI0SN+}9NJk^< zHhOQLAgzT|ssP>;J^%dko+n)3>~7BlJ~s1a7^O|^slpy_^CBK&+x{@f2-TW$x7f(o zwr2HO9)NCG4{0IfmcfIAp{e+QTY^$Eq|6|8^9l};MTLZ+J4Ft6o^Q!Ow2r%s)U96tuM96Wpg>+n=2H-H=+ zcz`%3sGs}nK>i7n4q35cc65ti2iY)_`Pt@2787@TKm2II3^xF)WFbqVKT52TWGtl8bUGV|X;$Fo6tHpZ@DA7&KMYW_Nn5 zU@V`0hv1rq|YBuF=g?n@t%MS#^l)|cCcvJx$7P5I1?Pw3 zO>_U002M$NkltFh z@>KnznW-}Nz@?%lhTq?=vs7tuaCq7rU;3dhz$ zoG~eN)b4e5GQqf*0ql<*n?7_7nrT0(O)kTuBtMnp(_kk#*#T?dAG3}TS|KCU$9%0}{h4 zMCQmz?arM$Y!sLMaJabejtwV)&z2G3;NjFSkUmOr3>sp!);Jh0E9%KQ!v!dF z)&OS^?@#SJIzIY+;+w&=Eg9}Gu<>k8hk-K9A&!cC1*QzjsdvZg#$?Pn*$x5N)vkDI zy}fybUs6d1(ab-R&A~uDQTCVT{((X-37u!6 zd~$N4m!i_91hAx2ZOnnOoCrB4b?to>LxaSbGB^aIp$|>c2cS_>xG~Xag*jO?p&oz~ zvppXG%z`=A6u5hgg>k5l;g$RLNQN1;wPusy>Bb6p&hg@1Ew$sE2i>ZN9>6(AO1T-b z&A+2btwI7WmM|RcPY1mAsj`cfStf~!Ddo%?F+UY3B_5OI5X;%@XJkJ~d0v(%JR*=7 zCSv@R3(8=D)L_g8BW8JUE}#qmJ<))cqlYIYB)Dc)HK~t@o^^D#7IO;$kopy{Jj^Bg z_zeHyMkN<6LmS54vw0+k4G_{S4S9^2YO&*%6|;5)(jO*4qc({`ULf|1!px^q|w|XG(-p@w_h}98#3M)#krn$J&*)@kJsY-bO4uqoD zxSDR5Ly`qHg$8U&KA;JYCmIBX)Gi-d8_x*Ti%u^VMUO^xU@QPPN)myCS;KLN*~>Iq zje7XttcN6yXj9zF8`6)S-k192fYlxj+=o11F#$c|jHRI=WR3!d0bH`tB-^Ad`Ki zD#yH0z`-n@Z9qC`36eYjQyQLhu2B*Vm_*I2C`%IOPxs{Odil{U!>(}d`83T!_goJXme>hujd~%0#0*XKF3#~m55NRB zum%v_j6@lb#9%!=!<1MO#W2myg5v|`VzbdhqDO`RewYv2D+g8)DVzxTVTx89sN!?7 zqR%-)A7IH~&ZQemr5eL62b5>Fs5inQC?dF_F{`Hl(EtJBAF=2|6e_s{c)#R&Xp@_@ zmd-+{C?iXAO4_vWITnz^i2!4ptUON>^wZ2npN1-k&5XZYjT@MwaO>oNNeZCKix1an z6*VGQ1V&~G8Mf#0+acok?C_*KaVi%<4g>Dx6C6}SCQySF+H4utu?2xu2@06uA?e5C z^C$B)T2VN0`D6q!2aE=R4AdkECmZUO{$Ki~U;2lC_y-Dm&_V1Pj=(wRn*tF*CSgZn-d_v6d6S0QyjpM12~f*N|y4O3#h>^HpTcI%4GmJ~RZV zHfEI<40&F)QFc7by2Jo5Ki+9qNGb00=U6&(LTYeV+lqB=T4=mN^yJMUJaC25ubkC` z-w&8kLbFOOaAD+qIiU)$))c`k(O_8X7jstw&CZlePHBPnprHELCpiTxZbA^C#|j^_ zI0&1b_Nqf>O(Aq`+v^ zCQH!dr~nKN5nHllf7uQZL@rIow3m|6vo1HYMy)%0<@tbPyI zM2;P(bh6gYTmO)-D`1IE+ANlIR#hDJmE=mUV!guYT^YiRgTQqf1OP~ZS? zLFwZ+#Yc5=LI4yW??RmiE6fA0XiW)GHruQq^N* zZ8BRhCZ!~YZ>E;|ER;HWl-@OqjevPI#9F;;f}!myNFs_S*GLP*fH><3 z%6*avpy4^QoFzdctIunCI_NbtdJ7R~2?F3lIx;XCH)0s&#*hihnFb@bU~Z;SqedLj zV2M(qQ`@tY4a1+*LlA%lB@7$SrKII?$brl>#DO0iJU!=O4#RbJDgWDP8rarF@2cf) zDB>;zHki^dI~-30noUb{tMJLyOh`}8=V9U2z>Xp!+887PGzzdwP_VKJjGG~oadU2w zXh<9mrjR9zR!QL*cEO{;76;AR@&O##b@W6tfgrC+*vP9~M^F2O0$#QldN0S-N)-+1 z&#tY454!8nDwFH;1ECsi4JoYWc0-NY6w_$J&ukGRvtXN-{yro0T1FM~5a5Nd=23vd zkx7piUwo16U>MJlGQ>sD%~y_L&J|?I5YkH$vBMcLL41M+fkrx^HEdz1WT74_N}8e% zjC`;h5-ZXjMRJV85qN{-CoyWEp;AU&hlkJR$ym;(54EENvy{o6Z@f|GN#A9tka8GJ zk=o&TDsqSBW`>&@DtHv)cb1SUJdiLR&4i>Wv!=jIl4R1tdqc78USM;=i@nXD0Wjed z^!&v&V5@KdLjuUcGqwqBiP~hiHfs$nT#=S*D+U2>h~p!Hz#4p%6(vFOvpLfp!bvVzXkV})m7v)$&bk_?cxu?E@MK%XjtwHgEQ#2J60(^ia3NgG;W zjwd}4C{KLiys?B1+{G5iI9r0ul)Tcg0&5p@-Ttc8houe7Hla2drO{2#30|&xC!je* zl_!QMPch`T_^kY?YPgSIX^w~KfS6-7exQ|Mxnu}Q5Q)eDFjP`r(LloR7#b-dOah0D zlfC=>-tYZh_i=9y{g@=Io)uI#HUgY^zB_OdFxFotITx{&!@?ng;(|$SwW37gzzo;d zdYno_^leznXD#N1m7bV$xZH3E`B#&D`shfYtttFx$xQ+|oZ7j1lY-kMv?Kf&<3Wml zJU{8#%P#$ndga3o^0tPXomLE}r5UnNTx~yjBTHbINDc4tJRA+{vu;G9K-6tor|k^*2zv#!tJT3(hA0F>z|?Jz_+n&41_(H^ee{68q# z065pIO4iU^6ToR&%xMmw3EjXPWouE`mqon{HI@GSjDV7}_IUNOin%td?n1qOD+AYr{*Plamv2S%JUhdRIcq_|)yqZ+^2QfyZ#O zJkExQF~{}-u1l+(o=W&*Sc^F+%;%g@fE($?V;-s1CYM$;YOb{zX!#E3s~r-AA$=Im zCu2T9D=QGtDoyyA&taP7KzN=nZE(wz2pkOFa)^b1vJs6fFBUx(bk9ImrUCGw20+6+ z7nz0E$~Qn>9Wt0CT9W?hjLbQtxt(}7HFow6-XUNna*CR(M zef*ma&Mj#yw|P735K~*P-J;I>qBl==MO05+S~AI&V}K@9Fs4M;d4L#3llMXb&qyCs zIT*YzvdYUubpc*D+pCylv+W@l%}Oj;6+`isWE%IuL#mP`Wvj4~C3*|Vq6~(TK(9`z z9bPh+MI1(2w;S82=nAKpU@CXs*m+?~#Ywj$1B1o0jk1jx9QPwefdo_flg0UM73l(a;}rzZk> zs(>yySP8V>7?Jr~#Xe6YLfqX65NJ3HrRDTt0&bZGHbo_JA+Z&XkSOWk2uUTjt3Cu_ zLM1xDi@w`8{%QeYdeWdhr5s=84mLvkeW03m%+*F={q%)Wa@NEZM)j4PHz}tqrU7p;zb0^O5 zG`F@PdTa5?`Su#;zz~NFv1SS;STA{~H?yWR7+x}qA>V3{7E-{Q7H5D{RyKM}AI zi((F?mc}qhtq8RZ(6AO#sPrDz6c?AZmtK0wO9sY6dLiz|1NUDQ!Lx;hS{c*j!uBq} zXgU>8It=V6Cj?3{R$QziT1FOfcnyez9Y#T0QwXkb&+RwmpR94FEm5<^H2D_te9 zg~aN*eM;3G5!8TTi_G@#(R)V^qsS_7G>6YX$XUC>JKPQp@xcu%4J=H2s7Ps?ta(W# zJ2lL?ku$jqF_<&mwtn#!f6@P0S0Q#vIkaT6zbp|ZZlYHwjNC1j(One2}yiK|;|KQ~W6}j`W9= zjW`1^M8Hms%`^3w5mQX(cri$9^R7bMbF{v&Y8Iu||gai9iiiawGJ8G802!(kOY z%va>aV4Mlk@T`%TTXv=YNCVs|6N2qFeE4U*29wVW>jic}#mOE-2ghyT&o=FyKt z_M%BHhGtVtZ)l;}h^L1!%bx&hfa&S=0B5QRa!;o5t>u0(mTdmYRVGM-xXDHf?Ro@w zf^VzQTuTRrP>lXQ*0SGS`#d#f<0Qjv>3NLYO}-XCmVh;-L>>r?qg#OEB0oxTF%xXI zxeeK$DuO9n-NsPmpMU;&1RkH&3&B$+c*oU4jX=;X)sPhFrBo4Zd1VC*Bfyw*E}9&T zp`5nQl^FU!QVESAo&XC8LZFBcXXu|%qJS`nhX-CXxT#{YnVCgy&7pW6WYGbhERBtZtVVCD2SoHZdn1G)iOwXUl!1nR;l$AW!wD+iaNtjDS&9#@@)ach zd^p}PJhGJ!7Orb4W;qN%!`YA^7`=T#e=xPxELS4l^Cw;YcoSJF(G6wHAvsyEty02V zvZtGkY1hz!9|*E91k00(rZCAjLV1iz0GeCED00CKWdcApni3$I1F#ZzWEd(42m~4v z4A~VE63Ie?Ku<~miXlE3-YT3aU`j9g7+O-pP>RYlq4xml)g{f5c_8Sqz@?<oF2SC!AI1{lP@7+R%AW|J#h0)&+ll(vom z38OmD!I^aD&K-&5RM58;F^iM{{4vSibNT92;#kcThtSrL;q)ZMkfSIm?ID@qvG@yL z_=2NLrg^1CCG>^c68=GEm9@k6nt()*#V3TebA?jG!Bo=F*dx4$h9?b~Y)OVA0P79) zm{1jr7$wtSSY(JH00tR{m<7ORnkATX5quotnP^C55niFxLkgub+Fn*eZC-nXlY6Xp z%K9-@CLrc;>A|r?PzEcFTtk{8xT=OB&iWYc09&yS0&0psxq`ijd4@h}pt|G+?7F~A zjR*KdEVyg9U6r<1%jY%c0aJ0WXP9UeSN1@{2#M53lVNOQr)ynl=Vs>;>J}-woB~Fm zc3Z3TD}u#X32*?2Xt$4;6w%Jo_7V(n`aGyEP{1nn!g3i&H?tasQc3_E1|Cqn-L?~a zf#4en`K0nv*jlPGiu)hSC1QBn^eXZ-)U{4C*B6`G#yo_gvjX`|F~7s^!C z6M-QGHvZZ+$n!SKXUPgfV#zbPlpw>QWD7i`Lgu`5nKdP`dr$_mD-Nw%*A9~_1XlhO zqc#m~4IUG5FYW|RGH`M>QjaIm(4i!OZ9&wcpJsJ}EA7Bc`S{O8l0fyER zT2gV83?Wh7L~j^pnQy2$O+8?}{PN2HIbgXLEdIvZSL|GB*GpY%aU&F~QWqZZ#7Ix=N}8k_X%!%u;MzN)5vno~qJBWM`H{7=3O8V*(Zz zOKeD0m=u^6j-F|V7@6hb6#;}r;MT!Y{qSvq$L4b^BBLKIS!x-iY%TeNQ z)?rgPEQCaC+ce@S`zguJjfk@R)Yas zvSNlwLfhy0mhyDdyL^b6muY@EbHMvwO1$%(@08?bV~AeEVFV4ZBWv)N1TuW&ik*7u z+0JpLHc{BwK(zZoPD@Whi!)SRui(7c1K^hRDoXB2171WP8Rz zpoJLH&b5#-;RRi|UEsWGcZqcByM;LZZ4ghWqTC+01UCwhOXxU3A3w;(7F_RjBx4WMuIRj(ndH7q&N;16#V&uI8)X5m{ED^W&_zdiyqrr;^#MT$2EuFPWX zta69L$~ocx;p)mL?y5(X!I6e7&s$C|4qzN4WIinYQYjt6U%sb#4J-VohXgl8nev=U zbfY9~ne)L9e$aWqOPp^l8h1X#Xb^CSVZGOu zQeNQ7_&$hqt@z2G{7KITT|$Y&heafkMFfU`Yi-oSpJ**X62emmbZigBbpRQ=Fg?xtRaFSwvfxHalp(&x%TWhGdP>&$FhU%#UfLr_?>#@nZ0?&>=^;18kwt6^lhrkxG@a0b` zrB$lZ2a|CQ%HZSUJ4>)_Z5Vx+>U%d5e%Khiy^hFpPr#r1+~F86FBhXU2D+7c!1Xh#z2-MIYh6-p z1c_Ag!?2=?grPS4A0OZ~=Rqpg3k-nS!p3GA^8#3S;EMqOhP8$whi0a1p1dZ9A~P$l z$_wQ=1A)^TmHTLFV@CDSf95);Q z8V>r7SXZt2@c@!f0ES_tm9J7f$IECd>rD6Hp33zZd=mgt3J>_rJ-Me?Mm+#&W{K~KV3g>vmp> zR@y0DWcmRRwPZztAz4ZRS#5he6|w@Jz20wp_`@G|9eK}t-t)fqy^lj2`Bcr@V=r&8 z;uJl$3ZJd&sjE&!;=>gL0W>rmrKb;Jfpeltjjj)MF{)Y^U}(AwHNy z02mCIXY}%+S)#3to50C)DMXOQdqj(}_a_tpP{vCF&n`{^7bBs!(EfE?;i<)wMfh_& zD>TT$9f2WWR4Oqxi_h7H!>q$O=K9=7J!D(-1``s71E;r}KJkf9eEj1dSB^X{zW@F2 z_i99XI3&Ei;Bd8+i#RH77HWj*6E_OnI`EqFVAY1ALZbE7g(C!;J+grrdLCh+kZWPY z3>g}HEFWSEqk%^$QSu8P8Crr^Z#kYQF-MaEdc~bXF_BEXtd3~XOI1?+{gfxMoCgjJ z3(@-}=j7xh>3Vw=^QHi+jn@Iwlk)k8I744LNfZxEekJpIl+{_sQk6QN=yQTTOu+FP zVoNCcq+2dJ6tF)9k)Jgfcs>DWFdS})(r*YbRPQ)EB}D`hnVz?cuRh%@0SL`7!3Xn1 z16rHU>A(*n=p=BKJJAvNNXc7*x4rFc9uDxa{}H=CO1UzDEu>J!6mA;kXzD4Jl&17B zre=5suule)rL7Vgrx$Nfpl4P((8|+{Hs<=2M^)tv85I5TnAH#ca+@nd-Y;rHa|;dOOk~@Ja}v* zNbDfhK|b+uxx+xv{lp^fJSoJU;N9D7TSvm3hksUvgS}7^W<`OZs)nHipg<<1No47n z4&g80z5OMz4IfM#>6&4p6KCi!Q1o5+`~c>tb4B>oU;R}I%O4MsQiX>(9K9&;9&`33 zK0cVuTdh=VpM$9`@XBbBplir{fvnmJu$JX!VPu0I##&RnW;0vMUoFQLfdoZU;fOOb zQSWW_e4leKgSc-|a5E95eC-bPP$#S@IJAtuZ*1T;%w|Fa;5_-6kXe&H7s4qBNdmuKMR3QQaf;qg2}nM1P_ zkZ_FE_(w_Q1l~>)`_p>^?`I7~mK5nbKgW zK`w4!5u}EmL>NCFdb?`#fK^zf!P_Cf^oUn%sifh66``(LSd~R#AhnI)ZEH9^ZOO7c z8hyTMtrb!3tW9B4>0n8#C|S{zX_n9gjnd|ghj#*;`QtzS8t`G<9XaPd7dml z+#1%8CM)v7Fg;1hOU&1>N(~OYbLWota!Rv{ly56KXwj6b*0S1i`%A>xiw}ikLQiD8 zCr_Af2nHaNY3MCXP}~gVn*+dI%^NCzY9s4hNBF8=6P@}p(0d_{!ipfvZCI@I#oE&| z9n6}_CzL^87Ju48t}dmO25E^NY<7!#khkkFhA`HmI#H}HC)K0zI?O%O9pc(CH!*X#o6m_|-)XE!3 zX^|!1%*a`0wMh=1nqZ?ej4jUaDhQ9jaFC%&!FOVBIZ6!~wx;+CrHhzLbr zy`(t5`_<9yvXfxynsl!MQcnV{$x0%gSLkaZvWw=iHPqmXG^D)o55)U@$p%pPY)G?= z%2!GWZOe~THLG1r$#rc^>?;=*l~=x!=rbXl>88;qHT2w)<~)XgGaG`1PXmZZLo}8k zfTuG)LJ!6h0agUz5S{|Q{q?tj_wV0VkCX|wLO;De`Q(#+B+#CC;)&vg6`nW%;`7L0 z9qFjp;yD0#Z4%Nj{{zU?FlU#{hXEKekQJZ#%x6CKv5zTv-upfC%rjs4m0u})<*+h= zMq4@G3|xR%r=>hVyx<+Qey4S3cOHE7qaRh3JkZDoyG$GvGDhWpfW2dRfzA-2)Ra8L zydH`Z=;y!QZnKcjstkKy?~OVhP7DLdz;}#_QDG<$ViG2N@qIQZ{ll-0KwRkqGMGA z(dZ%brzIHLw}t?{7mV`*0)1mnmGyD7^di8 z=U|HuCh34b>5%7bmZy(?)#qn-+7))T5t!CyeR&MJxLf4lbd(@!I&38XZ`Rx4F#KE=_joZ&rF z6gGtqdWz}cnFg;P4`=MzUX7-{h2Vy`Sqm8+Ja~q)kq#O%R!iyfoSvq@@LmG@zs$Pv zsFU!|KKm?h=Skd-}G74a7i@4?P!Fj+J{8H}>w_RD{t)FY1 zQ+;xR_PGzhJoF_EfJY$39-N(qTD~iv^e{p`UNcN6_vHzB=276@y?bufN~&2uH+eR9 z9(dR66};MERar$kxfUXe6_{speMtB?Uq`>mrfuCJB|(Zlxjv`!<;?TXKko*ieE8}q zJP%ZSWj}gD+QuY4*>W*ealn)9TK;jv6-^HJq@p_eycNbV+^$1U0gX5qc$yo+;{c#! zvw8%O*@zyD43hp8QF#G_g4reVBup*Q`*$X2^O1$bOQ2~h6Q_p!eh1Xo~c*i?jv>k8h%)>b$ zl~%>*untG)l>^0vES?;RwgyAoTUn&?-o1PG10VQ+H#z;CX8K#GnY96VfFT<79QG|Y zi;3i6yN3YrCBl@w2_w+Tr4XwU${$J#6%A*DrxVx8N=HnbsXb@VkZ*a9>-3s}tHGnk zgbock&{`4!!Kp{XF>}Y6;SuixLuqz}qparyfJUw!8P9;pl?gOqg2eXBZFehE@bqV? zPo!LJnVQkSxBbFY)#7!)HNv4x7)EFW1|f!pC`Q!+i_d7MyysmnKPaVv`C#+gK%xsLvRIk5M5(pz#H zMwXajdv_wsnE;v7u0*RzDFDY(i8!%S!rV}tvMQRaHB^z)xm>3#Pfj6X* zDfwdv&Mokk7;<}JV`b54c?Bfz{CyrcYW4L6Vf^rIQI7-Pst?|GR=bv0Un78}h_ zr#TQ@nYUq_$UNNigJ5Jy7mbZAl z;Fy(gl8k6m;$$suU;%UOm8_J(iURFPFpMEefWBEvuCS#8D{MJt${W(z=%LETXVS&a zQ8nKE?st1QAeS0iT1j^*NiukV<*Qz~hR0Dag6mpB=?pFajR0V|h*=SFkL}u3%}Da)OMVM1~NW?mY|7DPo>%xxB0i~*sgixb&h)a z@wDW1HxusMxg!l6kltG`jl$f;dz8^TQ(Y$Vt@?>)=8y=^9CoF^XwvYu`b}Y_dhX^U z4Q$&>dbPLOiWUNJL$c@q)>9)L3;`(fehO?BT)L$ISz=5C zeuOHK-Y5DF%1zl};Ngbf&`26OSkkVuCGuPTNf!i{REionj}*-DBwZEoC5+3R%Njr1 z%q}l_8glPwLOOa(EM$v5cQMdGqB6>F3x$AnhJxssgG4*TK`u-}=9fdUQ!G3j*e$Zf z+W1&bmeYb0U=%F6kyus5Cxv0m>gmIl^ZKN?)X&;fN&DTq8kc?#1U=MIJuL38jb;=1A7D`Z8DI2bxmQh7222(<0&GV84!qXbS6 zZ9vhfwXGOMf0a&Sm#xYPg2-yO3i0I!d?XJt7d2$~ldI=3y_l+f$Q=CMDWQaSDtLx? z;)y5Zk6EXP+_M%L4f(%?c$SccG!clBq!{VhrJ2M^K$3XfJYhHLD!;;;N=pe3_jSEh#pZXqLc-6kvcM&Zdxo9hEMRlF#+TGZ!fw z2auYI6mh;Bb)%Gd9xX|kz{?4_g#arKEn0d61i_R~L?F;dU=@`}F^iHu@bszQ*nGLh z5Dt;eq$EOSc&zpds=QG<{J!KZvqH=|#gVbps8NPuApY%b z{ORCW@}Z=AjoM9Z`gryN@H1j2P%KUAt;L6u7>b(e&orgM21f79W4?Of?T{%=iS%c> z&pr2?ClC7^H$D+HBHWt*M7Shcl~yq)OZf0%9YGpkTr`+dZIAfWsgOGa)ILOPicmd! zsb`Z5nZ`Pyl&;DmZZ@`}#!~y=!7rc~S|#f$yA2OXxmJAEwL!qVtq?nad6=v1Vg^z` zXy93)etG?+*a@!2Iu;=i3zX7BV(2$625TJ*eoS&UP{|crs;#pipMzf^A8NXYD|>VU5_aX2Aj^#12ZLssIV3q8kY$L}exv z37b-*nOV+sXD)JT8k(wnMT9@D-|ODLV}EzcFFnE|5`+@#ks8hrwToqtJJoP$(j*aw8Ln_}1wnI2bN>=V!yW@+V?nD~DRU?KgN75r zsxA(njEd_6Mhjo=QSeCB7|1w*fGfxt3s>|HjZSEWLga!&Lpj9L3g9r4mMfs(5i+>o zF+jwHc$cKn30e5~fr%vua#I$NL|QOeIFW1L(l~$n?YH$tc8ayaCpcH9K^0;36gS${ zs|v-bF7_q7C03~*eck~Nw4oNf(WFPgqjrx<)Au4Wmaxsfvu--AKqs`MtYD~8jvkAS z$KU+T-{cXQY(|{zh6YYvq*Q?vbk zn`cgInNF|XXn1GHI!ElCSf|OQ8gvvCpHo1b2t(qyrAGada+m;P+Xe{Z&L3!2JTnd2 znp$*dO{2JBpzSmVphs|H`>nU$;>D?g4=`MH0Nuc*A~$ayZG>BY{Lb(Ejx92sL~Bx! zfuYh20HkObP{3q0Tf4>+m0}nscvm$v1u?I#E)Hb;M1jHY@`4M!9TibF)Y9UaETf=7 z)2|>aLd-luBg<>Ay=K>&Eii{ABSEyCwE`T&Tc^SmB7(AE3=bYVlaD!3VxLSfyEb|P zGt!+Un4|`Y{?mbJ2|P|w4~Z;>7>G$B!KHN!BhD~{OamxD(*)6Q9zi6KLR{xZT%XrN zm`i5edXzOV7vv9OZOx)6HtP?RH3WiK4P{kx35xT(a``qr0Z!21gl(;!vrb9H_9yWh38HePfpxgZHii<3A3KgqwfN0$DS#t1LbrUq~`frKoG zCj9@}@9P3`5{w9$6{H0UwVmO$wnaK!brUV7;zH5#iJ=&eR$1~v}Es;p9d3ovn;MhWxu9JS{Wve0Ern?_i`Aw2YP zE+{J|3)_^2OA&GE#T);pd((qU4H{H8Gx&e)*M7}7=L1k?wkXg{79BjGsdf~?*pQP> z{IMk>I8G8eSENfCJj|4inz25`Ti~>$2yMuf@Tj+IaY{3UEe)B=w2@rRL=P}imIgq? z+Kcw$2|OCbitAA{2{{mEfj+rjMgV0N*`wJ8^J*_47DGX$bT++(&WV7IfC7j|^{Pt( zhlZHOjkop)C+#jR7=~h8!N-Gy7=2@tD-u+i6@VBIY?tChVC4*CYiM&;wu^C7mY52n z0NFYlx0s}skOjvW9L%FbnR4;Qy9h)!rN%Q3VGaY}@bL+zk6({C#)S(Zd(zp^TjWIQI`HaBu-L|Jk~BG=mwEQ!i>_Ra1Bx4!k!ROjV>tXhUVuD!2f72=D1W zL_sJ)oaRg8lHL48LJ@bl~$HbCmmAKMANqwmkZxS-sF`_V3g81RRmc@@Xn0Fxbp4-F~A-O=9O=tTDv6LKB>F zwSlslIW0}^sL9ncR9CE@I9R|YQ0>=C3(1m5@9&N1042sFA80$*A~phH*M1*_`q79W#{C&sf}M1)8LUshshfC30+1ZpQTCPDC{ z?L3l2+o_IOk-(l7=B#9Xh)Re40KLavkroaluf1Lix9BX*@(tG zN(|v00WjtaL|V{%Vvl5`Juv|@oUI_;l%oVjz$l<4WHWw)AF_>1MOuU!QIJ3(15=wK ztMBPu!Pg3b078aft*N(!XJsJ>W-1^VbXrY&X)Li01}Lae7s5}9pnz1^P+VD$LYh*e zv*7xY?q@LK6Dg`NJ=Lu2zHM;hLMc5N0{+FQ;Tg9GiuEEqt5k8!;4otfOO&RksV?F} zRvcrHb#+_bnQTOYKR$eL=Y>Y74%OO1iYaDt`*2j9L+=!)ydKp0-zS;24o?q zxS-)&AI!qYsk;W%sZfw*GtJ7Ce0g8r5bZJEvQ8()<_rAr(CK|CkY)8A!N0PELJs6g&MV2R2l?D)S`(c zG#q5~WhDeC)*^vuJ`^aQ{ndgh!UiCk`UX0f(Ow27YN}JVu~YmA#woZy$*Dk-7V~Y& z>X~Z4cI}!&FGGOGLRoP@1yxEGtf~k^EfTr`2xnmFj2Ubog}B@Zjy{%RuT&HUK%_&h zcxpjKf-KxhoPeM3-(!Ceh7zY~iAoCs2EoZJOsdf^@9D`{ZXCU6*-sU6(n3$nf6p#`;s$D`Hw!1p< zk0rpKTii4Y0)mu~BGAC~50nWgKmbuohJmSs{bf7LCcI=}_?R}qYR1_q3j&`W^bGv~ zf=3A*QHb_XV0Suik0BA!#Pm;!=-i{G5tC7G#s;ai3)8GQh>UYt^gw$`L6Bf6h-P?5 z)3YKpMocj=366p+$cRhx1i$o4zhp_ll<@SvoN zE`jIpk0!391=Xn~dRBXQ1Hr98Kv`mfz|>s{2(Fdk;2#5+Ckw8WO(TjKf;1vv0wUxx z<}Cy{4+=4P6flMvdN@J+aFT(&4t!FHQ#@!&dJyoBC6A@VHv0h?2DyRL56v5fAUeD& zG4u?g#p%|-WOFqTl~Mu?q<<*+>Lg0udCX!G*|zf-E6L@+hrMjbff&y&j1ZBct0uMg%EUBrTQ( z(Iwzx21FI62)cB7mo7vP-ZAS$7)Fqb`S|Peskh;JX&p4N*inOv;qd<=c>Ozhdv=@y@ zL$KP&S)z?cidBVNA+`e@YLo`oaH_+Ax=Q;k0Ai1E$G_TfA|MM6O}*f#g?~KDRZc8R z6JW-ARDvJEk1izErL(X@L5--v#{(d;x&Z_e7xNO#q&H$uh%$%iKpt}(Erp-X3}7Hy zAqe91fn+5OMKCMRRFd(#nRdA<39h1%=Rz09lbV<{^pt6EAObQnx1rA35Nxy~TQLcjNR}$eF4{~E?0ueXXsz0Pi`bjA0Jo+a= z_%=anX1Zx^r=W46EFSQQ4Qad~t}JSRsT~{3Ph+xR0@8+{ zUm<=fbmpIUqV`+DL>fan5Mq-}@EGo8!66t39R&?ST9n?Q1*es(g3&BJDwtrnqCoZZ zrMJO}^^~AH@1xPRxfl;@!f1}hld~P>n21m%YfHHrNpY=6*Ai&Tf`9?kREqgfR#%L& zt{}+OB&MUJq5uFu07*naR9F@CD!KZEi6kJ22m+`$W2Y1dg6iU{Vi7bv*Cmb}fdDx@ z1}3Sc(~Y7BeOe%RGy`~kp;w{0VJ(EpBZWwWPZra-g&9Q!E-jZtWY^J!*iA~)5d5)C z08N(B2#pdbkS4{%R7#Mmjo`yfleumb2B=Guv9NNruhlvt3eq-1!#!{nxN414X8;(c zQV=3wCQS^^HpLsEPvM9wq8sQKM9>*9W)lEqKwDMz864<=d*Sg5U-$y{WFd}KOj3ec z5CK$NKn)OY7_NX`xL8N;)t2sKtqfPYcI@v(*CJce$Q*IC< z1+CIBGzo&c5%AZ4{ns>Rsb~I6O9X^TMG&No7pg!ZodHl zF;f>Fu!?PTq|p>hM{q=ltV^iWJk{Zfvfj}0ZkjnFc_FJ8^E6-}C}{XdqiF<52tLy z_nWU}K^ArC9&!~?X?mT}D}fp-!%0Bn)fjgTq-oMRIKdSj)B-`h-~RTuZS`tLrp=ue zXn4kz5i1upEe=y6QpnAt&Sp=55qag6SAOCregYfDGXa%?fPy$hy7(X$KWMnlJkE)u z2?Po!yV&J|KmF4`HR!w*^p&rCg{uGV@BS{{+UT>lMn-KR?&60IvRxILKtoWM>N3)k zm8zp9X`vT6OLY*^C_&I~mBMfuLatr`VF>XkBumR;KnJtpF|8z>`GhQt>0Qdkm>`Z@ zouTUoL=}z4KN{2fGefL>r^zOLkfTb59HlXJy3^-A8mtUAaPHq zs0uvkQ1hDGS?OG2k7g+j4izLJS2~;r^JAr;2$%`VN+^;?l=BFZHl|#y3r+Jl>aoVB zYdX~(c?DU#F|N$(1&uA&Se4LPy$RDDHIMcd3;HPjp@rM(GEFoUNtn9ECBOvHIZ#kG zGaZSP!mV0#G6)eNaTlQ{f`95U!1IXDs_6E{P3^Ry2*EouQ62qAWVF~TU_hB^fvUkN zu(UQgeyInf*9(ZIVDfGepi!b&qy$Q%Ng9?2ntId9xuJlRfoen{h-bwiz+}o%PK(F_ zXQB8TKn)~U(X}-r)8mYClrWDkRTJ@5)$~5M^j=+K_>n$V! zKFz>^oU-CBdxu>71T9fDKGQn1Aq_}yOLOMZyD6`|_L`QNpkL0U*H!@5u-1^eSjjCV0ET-tUc z1}7#cVhGUyW{9k}a|Yk|#y70AOk7;iwCw?n0?}8O(5gC~jmf$Qp=P_0f0zho)R9E^ zre%-m5Yf2DuCn9?e;9{&(0V&j9Ce84E+bzS2%Un6JqDt9M!Ri?EE?nr*f555R^wAz z2)ByY_@32Ppf0GaHa4e7P%xaRS|U{dN}!=?nDm$cB1jX{G{-iP%^a#`h0{P3MMcUQ9zlyB=ujX-09h7801ilF&_J{Z z%34IM;ph{DL8r`&N$ww}TEwYET0wNuQWDB4m?l=8fX}p)ECGGQQBXDdX-&?aEGmK^ zSEY(TfXw2DJrF?^N*VSBGaF$+!;4@>Ey_kBu_8TL` z22%`KkPy-zB$Q1^?dk>WXcE`}!81Q+LW5_LYE3r?7;6UrNbwMugk}aF|AS&Z4jT%4Q!bDw}B0l1{ezsLEF$kG(@MPtbG*_c9td)RMsQpuEvx? zxi_VXSYAcor*)J{2nA)~q#|{}2?LlxIkpUD9^)#YDTQsbT06z0FrnkCI@xp*rPQk+ zWOP&kL7D}@5?MHsYuBzpzf_M~kZ&fJRIfRo+0ofvZ*G7jWBSWtsJELEv z^MLD_=u_iYzxq|{OMc?ZU;Z-Z3K>`FU|kg#FmY@{aTXQ#L>{S0kOr3^QW72L@$MmI z6H{o&Dq`GHB5hU$tqPqc{Vy$~F3rGdFvO`sE{y{UT23i&1Yrv4+#Y2?2+b7il>&A& zz`y~8*dtX-D z?4R%lffHX5s!#+(JBqcV$Ltc&p-}+=np{H&JWA6v9wA650zoZCASb$6S~HGZcm&N41Ti2Nk9mY#zCY(T4trS ziXlg9osI_3U?52-MfIIj$AlA7AcDmm9LUn}g%C8(JLMpxY9=YgVTK265EBLcAofBP zAjy^JY=>ZqCynOtvDbKhiTkbYjvN9SEn<=qGpl~FQOzY#0%eNTC1?(qU3OXg2W0aj z$`@n>K}=mK&bA_+ri4J-3 z!cgeUr&lNr4e>~wx}+^B6yyudlvxcBlo5e1pp%eC#f2gKB>y7*O~1t%o+$y_)X0i? zHN+MfN9b~}%?*d2y1jOR>HPnVH{LL_&U(uhJZ(g85gZY!3pI&SM{77^&1C^Srz0mT zdWrTrKc|$1P7uW5HgPD3hxow>ezFoHLNtviSn+)08{cqL?u^9B4>GS{qQE8K0rLV? zgdb@LhH2Z$7eTan~umL%kB=tr&f8buV(*n08fBL6?nkLy6oJ5&A zry__WC>HWK6z2)1kC}`5hJ0YOH*I74co+~KTE|b8xI>0SdOFk)1fdAXMpx(>-Z^> z$HdT7p*U_sN?B-$C2kM0Qv2;$7XcF9h8%`r$`K+XKIxyxIy(EP38HWavc!s~mn$nv zTFeRi_baaIU#9Y-Q%)99z@$knDuNhC<4ATa&OV)~r`J0tO4g^odFR}8d1S<=n2 z3Jehi(ZRNg2qKntuI^4FE$Ix-A>hkGN=#QM>o%N$$%M}AvkPDN!WVQQbPNZzs7rdt zKeB?l#AoT@F~g5{T~aG{d-aXgCT~oaY(&vFL)P-tWK(TjO!@RcC+sW)M1%}E1=$WwW0qt{_?{hg88GlO z*o+(nr8{pauIbZui1)0s5{k%D1ljQHksAm;c+!lQ{Q}fo zYga)c4ed2gc)Ji80oh!D_e_lO?U3hVU-PZ~Vq@ znEBcWvNB-Eaa9L@@p~69sg(D$^tVC+m0YEgd2IcDX4X=^@$)EfQDaeIkfr;1j z5*!%zAS*&x%){pq0jWZF^3j^~eBU#+yr47-Af=R*u%+gx*+Rs!z}5B279zY zElGn%s2+ETg3#fQIq~ts?{?N7GmFdW9;XI0w~*njUOm{5$fz)WbXMabxyD-_#YS}= z4Kt(57L9>nL@8)c$%PXHajHnH3JEcUJBpdy+p_{6#v!6Q>=}F5U<(){kGcK2PkqGf#rDF?nSC>}Nk~z$=TaM>c{gZfE1dLWX+ABzeg~UiO{+ick_Pnq!04Uw@qv z7)maj`;;pSK9MD;1<=yLI*E0oRnvm#IcgLqP%JenB|&3wnqH6Z0w9YkrW3OCqYdd} zXp&Lr#QAbOq{X>3Ea{VMxBEZ%)IG#-h>thvP61{fm8A-E2uozE3Vj>@_SQHFlO+0y z#fL9AsNhrmRvvhCvC)Bn(AJ=c8xWpAI<8d6dG*{!*--t z#3>wHDQ&@dAWm=40MVpRXaWk-DgN-vELH}+XqZ+Dar9e_XrD)HmBS_Qg(Q*%qQgjJ zNkn{n0){u@cyz5uP>MJa%D8|)Gw?}DCtM~4dS%Z_3mO?!t*kzSh%_L^q6#91{;+8t zUJnmR;bZ&(>I8V0Iv%q4!&JR2`v4lyUV5;CX*i>ZG0jZb6v@CgX5^-4OT=&}0$;y} zZx{etAF#*}jD(=+_p&k=AScL5IBQfuJ3;x#-&4iq1+&w~5;hD%l`{Geq**KJA!w-{U)Z+QV9ts!DADpGP*cuW+=)t2vA%YgvMhv10RKNVozr4M@{lzbS zk>K%p6q0E#vq(iAwL4Y3{r1~jg6o^68^ek`S^; zsa@zatpr0NXC7$yjLOxd2szs(|NhK9ZG6>cv77Lv+SyZjYupx^}5C~`s)T%fB031JIFVs+o zfMyBSyM)p)tfH)?V2bI4-l)YRy{UrxQ3R$12Y9?GtqRzH{0K$v0zSgTnf^c>L!G8d zqCTp+Zzuk!C@<+ej0Q3)LP57SW;g?ug5h8YSWN3;-9V59!AO(N_yNguOmUFj^@Cuv zNEj7&Wbc0a15)VpQXT{8b^#~YW|i=4to9Kuk+iZYWt1XC7Pstujlf7IGH1ZIfx{tV zcqU-V=J6uLk@|K{qWoblc{KsQ&PcItjt&0)@Bf~RUi&wzMFBD;XdpPmjBcrp4xwpx zc9TgBP4rWRSSiI37t+3foC%le(jGSy7a|!7cHS|yVIyAwf zfy8z&uhPc9U>NjKA#LoXM~Rb&n8Wlv3XVyo!s;-_L#P>(OcLf<1Vb8GhKV>5xD`Wd zjF*fkumv`h1v|n9Jb%D=#WErVA>kg`y9j6}EFU}0s71_{z4qE`U;p~o9gzBH9W7}W zUQh#RJAo}zmiS4KMT3IS2J=TzT6QR4*9_uDAzNmYk&%!9;jncmlG%{noG}B_ zh|=l1fq*PzTp3y@5GsQQGBg1^S*ZxMyM(*wMDYYTC&`-2r#cCC+}uz?E})@^F?EHj zlw#c^3~7Q8kSwmH+d?sggimU$W(>=Mb{BpwR2H72hloi7P3v_TmkINGQx*r-eB0aG zFTVI9wM0eCa&arc!bI5CA<260%H&95d()dX9o10yLe6 zhmhbH;wBYQ9XfFjIt83rthin z66xGDpe@kWpgnj5G;++I^kY4qQIKmh!N6uF9KIPsF{Uu#=-C-sfD+1@78y2zT)p4G zK*wlN9Q~98%%3O9DE8saHB(E51Qmb(+s3g$Uspn!v-E^>cD`f~5!xlefby zf{@`@GP;FDUau6GC`4HLL_yzB1(#rqWOEWE($NeGWQRaAs)nCYP<3C_@G<*E7nSWL zU+1NPjJFIYb$Haj^?26PW|18Rqf4jPv)K+E9j1h!-@}(kmH_}$+y^a$;G7rhjcx`W zt{9_iRxn0vJ!fX6j{yi`!TOYpJ;OeBeXMg_Zf8NbVx$$!hBA*c}#^O_vc6XHOd(m#|+e0@; z<~(SXSI{dI6wt&&G@};Pc@zh(55$X5GUKRwpbt$<`HJ7p|0!|q{V~*vr^Z!c^iNe0 z2>oysB+@s`0J6P9YD_}FcJ=9%70mnTP)hJS2i`4kosw>%!30aOWNo% zt}+b7(BwWg8L^R>8XISe!;of%Fc3>*7-KK|Nbm%%nVKzP&&?vjhVAG9njn1v$8$LB zj8QuQ1%lw2m@FMtkR8O-{#)PrmKU(WePGAiXZ!)jNkt~bh_Kpx1AmVW?I8}+r`fbS znuu^JaM)1r)B;mMb%_BLQIQ%Y^a^Iz9D*+qgM+}2Kkz6mek1{@9uSmH9Q}x~C-g_o zkKh>Q5_EvTm4ZXYV4|R7c=ZgLT{STY2OynyMY zKe&L6@;F#J>7S?nW*~(Fo!+D2VL5E7XjyHE5FZc(7Qs+4LW~L!_{M`rX)(`X%t!#L zCW#b0zc`i%eXfY*3J(}F(Zd6HXHo(rB3K-72DrU;?V2~y5D9Ylluqz?l05x4m`^GW zHyTU8gP67j8X#sIHc9h=9Uo|NSMu|p|GcTLT*!14G=dA77(rfv6aD?)|9#tNRu$0v za*>?AycU=$h4%QC;5} zBD#uY^r&9c)QE_xtqkba>kjCv3mm4lfR+|{B#KJAB<^i-R%5A=W8)5Uszg$X0>rk2 z;*>x%%O(gW3XpTIA0PGm-QWFP+bKWuGe7flKlgLK^uVTr za3OXM!ELNjuUcq0l~R#J+yg;Sw#b0Pqhv`DWr9egOFWpox0k{OVB*_zaV3C@gTn<{ z^;!YojqI37i_-uEXu1%Hz(W998(}*g105QZMS(CIRz(U%6@c**LzV!AvpBq+=O+Sr z#H+R^BEz!MN3H?gIy95ei46~9n?EqHb!%gTWyz2Ll#c0V0^$!q*x5|Gkjh99KXO)2 z5yUWl0C8v-+dbzP@Ncn#AMniVpc&FIv270Mn?>ZZJIopm=XV%}DM$#X>beMzoxLza z(wHY=%F<*WP3nDz+0Qqc*T3}AOTYMwzv$Oz3C;o7Gtp$4iLK}9m5Pdd*x62ztrBj- zIjX~FyXS0Z+$|dlUrZ;lgQ?vkJBSB?=|oM)WTIPATm`wijXXYNoJtc%1D&yIx!c-N zL-PkJwRUm5gl&9kdR#%)H)L^p`i%bL<0X*grau!w&xvSCL6Am4je4Dg$J=>GOFg8R z=UsfcJ~(d;KYjEK2|=H3*(S<;atgb-1wKwfo$1lSXufs2_~ zt`9C|yPP{$>7cjAI=bGhmm3(y3>zU~s zc9U%uF+yoFQj?vr1}B#m z^r#UjM0ncFnpLNMaHKhhNu{a%z;w&$KASN_;9G8xU=-|e0EZgJb|~a1QK;U#MF|4 zw~NuI{O^3{J0QG*^JLR}_^i2*nXd#-ZyDij0~=_JxSxl6<&{^Up`c~W31~?|A3%g1 zOoO7SHN{*4)v@16;3HSaDS~PNNt{Gg+_tmG6|M+^!>-2YP;Vi% zEFxGHg>15hbWRZu8v02*b@92k$5W?~XPn`5e(TXC9(6=R4soUJflFg3&VJ1y1+UJZNw z_17h|ri(^D`IA4%Upgx`-zT_tum1ec|D1IBkuQG2?hqmx6cA_P-IcK^{e{jwUe%i?E(;1>h1>W_334vA3MNC#6g1Mw|&I&+a zCd7bZBVWj7tBJPtgaHOKGgfKzg{~E)hL0V7j|s(31$C(uO&qF@d1B5$*fmE51i>WY zc*Z9~V4K)}5$Tvbq-04eg-&=Vc28{XJ#zIp(eRe)!hF{Tg_BM$SxnNce(Z)hNq+0C zx3n6&PwGGZ<3IMHQ(3l;_ybz9O~gGoK>mea_ywya%2AfaXhW<6@Ei`4EhD_NtS-KR zx+s&9v7s(HM+Zb&mb#*Iz^IhkqfhwZ`SGX=8^ans@^+ZPDn;oVxDq%;oYS7(D~mRq zpg3tzRuK%-12kd|sXZPM9z!ilfAAQT{Pb0!$7lQ)5(a?Y&lZ?t8ba)>0B$U0@Mlyj zLQcqrlexBp*4X!?m54>YN(V1qxfhCtt? zUfSk$!W*>cHVi*UI&su!3=oaPUtv zca#FKGw^gJmj2-BYOlw~t>Iw|=<%wQcJ+d>Ft+UyupQurQ^tcq0>RJ{OPgpz^O!9f zM2tP7C?iK2!3>y(88#{sqK-}t18R@0=a%G}w=(pYXN;Y3exCCRdbdDU%9x5JxU zMTQzaCw->Xh80B+wewYJWm>{K25?0pBIpvnRN)!|=5viguRsJsibL*+bK?AokX`zN zC!+R?Ua77x;4%Zi2w@Z$f=r@a&Wn9Fir9Sv19CT@Fxg+@iv49xV47a_!7uShgjea1 z4hB$;m5J>GfrCSjFePH7;NUsZg@S^vnP%e5dIH9yNDR?Do~ks(pKKiEhfk7v)DQyY zYb?Bxn*Jfg;@cB9$7iNlZ3u3Z(u`Nt{`PPG7LLV%4|&=iVe#Qx-tqc$(v{XVY`v&I zXlTmJrjSY*P4(&k=^hALL%jInix^{JQKQ&&fcw(qc+k{G_dD}GJQ|HhL(QPqMl_URVgNGntDsVCOR`D>;@ z`ScI|!1e#-#sB)||EuVK|L@=ZZ~yiGxqkgejz%;#{fF=T!^&j!`nAvfz$ZVg)|E*u zaKi>Mr#M0Tuzi$bJT1FCVrp6Z^zC%ec=J*LYE27a2>!HKZtkj(CdEl3#Xp>| zUGgpcL1|$bx&{oY8c`>qrzK=@2F9~_C1(I)?rFdqlWY+CB4NBS$EKfhJtl+*EAYte zX(%VgG7g1db-2PCd%-4hPTUwv#2G-g8(_DDpCFV{aKfJA(l|fS!PD^<+d|hciSXSa z5*`8BchV)7jAUovWPRyNUjk(HATZ@{QkDXx1voSbpZ3i9?K0T{g3rnWs+VHpc(@Y~ z_(+*$djS|_t?yAt)rrFu%!JH#lU^JhGF8Zq<0Z;i;^BxjFL%JCHHRGv4yKek?zQa? z`p0aiGsA1m3N38B_x^i&vh5%zD7&|I9B*)M*FX0;cdOs|r@B`3$>(2SG4KT_Y>=n zLsl$?fT@B2)0~$}Ac85gTt$R10~nkK5V zqfynet`qb@n$1PN{(a~iRE^Ja27(io@@f%P0aJaxt#MwQHGy>8l1h__?LW2L=Jh5q z>qW0t30zD$7Y`+rf6dHSHI0SEJPhG*f}P5*S^rA>O)226Vd)RRPE>^j(swMnNu2kTh9SN$o*(1g{kMyq#0lzxkWLdHe0R9VVdz8IFL6B^QV)xB*iH zlLP+44|DqJ1DAk30Rn>E8F(yfJ?O%kp={}4+dgSU*xxqwZMz<_%FZ@(+uG}h1O+JZ zOX{kl5(ksY?Lac-;FQL3;MD0x#E5SW3(f+7vy0QjCr4~1E8yTFAR*t!AYOTE@ezod8 zuK$GA(jS~?;wk@I9p-*QA4{hSWkl7$8aRU*U~RkL+O=zDf8J~Ti@*2_+XXDCdaViA z5z|Ib()d4)37T90f-rjbw%sq#<)N792A5SHiHsSU@ z4c>Cf8lSR>Mawo(vRh$Jg()|qu8Fmib|spX#@x)JN|4I$2rEb*pY36$R;mPd@v+b|ZlV|av(c@3C zJ~=vEcxnw+2D!|8aZO_^c0#7`VlF`qb#Q>~!jfMrj`%j&W$(p@O&QwRe+N}dITvx! zpA>)fsLEsdcl)#S2Y1uXvze!__do+}+|>eKVtaer(d({)I37{PBHOO#E)o4a64VN_ktglwTOp7X`w?7%%-J7wz|Tc7`T z|NheE*1!E<|2K;syG#4Kvmg5OXLNGjP6tMfGid)ecSA*GT}xA<=A4chJN{GP3gr)4 zT3nN5o)O-F;>~2q0zyESEz91^(%Ws zj2D++`^Z5eBgIwN+6H70mBlA62z&p$JmC_U?;LbHDL+;En>N50FWgtyet+G(blSn!vKdCvfu(D@e_bia_6)5VKPG=m@TF z%{Nb1>U6ctYa|_m&vtg5T;YM$#?|W9rmdK=x7E$cma@fe_HYIkMCD*isb}+}{R8L8 z8XbF|{^X}N|Lw+)T>sqq*5+(?uQu}!>x9gU%X=Suu$b*X_rmk|Si5xPa51k{f?YN8 z*12%`hGX+u6Zv}XA+6EdWbSl#QFn(3fiwSzM6ycPie@ysH8Q|fCZnrcE1Sqq)=83!VumXsdlWjFga9IoDuh7NYT!{Rn=VU3gG9r$ z4Jc@3?Hjm?^o`hD36H3W5eX;IB>L<%Tvb-gme`DVf9AE3`TmS4-`v_7`RC%MkR25J zO?GW&bC$GrqDlLP5=>E^)Vt=dj=W{7-p59yz zcZue$-Pt?u-B>sppIFmeQdN18wiiU=@|dca9b$KXhwnfh%e7@EY^+aLSJvBFfcL^= zd62d&sj1JYu=EF~6!Nq_>h$-;`tfM|XemIppqbO!9PlW3WIf%~R0WriPdk`#v#N}y zEEZN!I~x%r0%%swTQIdI<4l0Eb`rp24(^N9J0fEbJREk}*&RT!T4I04^ z;mvp6oB6rQ_07@ZU~l)mjnVXIzH7cOvka*EX9p(o)+0{9SDO{|&dZJ69nN5Pe{bvR zmCev0Vd-rDLX6PQv?zZoyxXJZT|NOn1yR+%)hH3kB zeT%^^w=wY>-A$0%nj1UJs8v;S&-@n(nfsH;dgs0OcP7(KQj8`W^TV+MimKut|HrMp znb$#%cK7%BXgqJMt?tk2gYA2>x8z zOw~b-^#KtA@glBUE_evf92>TcmLTAYfotd$5enu#*849TjV7ZaVs`yVXk1E*uHXm| zv~S$7tUx3LaF3l%*BHAu>e^~u;B326HKT6#Pn#R#9;!G&iY*qlUR0~itHr#wrz|Y$ z0ApkhKjJVB7vqD)bZylY)z$U2v4x1*T*I}YQ`TJfUW`}k;CehA9hjS!o6Fw*p>tyH zXgr-Cc=uWN-=Ecyaxo7b>yu*rXz~2zOXDLoj5pwxB;~!t@IKhJxnv6onbplpTWjmJ z*Cfx)Qk?D|(8h{dR@XMH2=))^I-kXdFkPviEm^&^{`_TIwT-68ln-4yQB9*K;{(MC zOMehOEaPJh7`jr%NXE0Y?xk;SZ*Mb^fEnAil~aHjm5Aw=1rxpi!Zb(#qQ=5z!WjA~ z`!F$@>N-UMP>taD6g)!kC}75{9~%Mk3Mm_&#Yq4{2+k?u&*o#)7@bgKG$Yo&6hdhd zLJ-6@ti?_JWp5Q}s=f33eK0j)Gvv2vS3g|Tg;aJ+$73`#CvvK+Ce}=oGW3=$v73@* z&2R4bY@w9_tG(`LnKoQ!I`KKNfAAeD_|*As{aT+|)2nqRlNu`nE8IH9Zj7zgtf}Irv|gKy73$pDcP#eDtLyVxT#WwlpWn9znIG(b`cp5gURg6Yfx;qR zzI1tI^RgoX#MTxTE>(Ko&T(umAq=PZBusJZeThO4WbMyKJF~_5%9^`fYg;wSZtMX` z7`Eq(-v8jnI|p;SwHxc3h_7ul59K&vI$EzI5^AV|#rtHmwMNqNEk;H=QYm%%Cu%E& zlge6+OMlSRu#5&8&{JxpwE&z1Gs=K6n#^T#V+gX%0*yHkB*2ZL8c2hnNbD>M1MtHp z!zwC0G2|TFV{*+~RgE{Rgy7)Eu}9nh&SeETay=|(Pg{8kyd%qjM`KZubV9PkvI-t(AiIVyPqm{bSXyT$*mI)tw*z)iLYJX#MKy&0Bg~?ee!G zXsW>bXk`>#7ruIBt@hcQmCbg3&C%P=iwepLt~yOv+jN0P5k^{vEYryG9L~oF4kUOV zD;`1}I??vo3>7UH^|ID{j2GD&r&UV1m36x})0K(X*@Cleeq?c2hX^$cs!VZhrEtqR zL|t};UF_7+Lf%HyEmLa9PZEdwk~g7 zd9Lcna;2y>0=s*oci-Ve>K?EUOI~jN3r?xpnxa0V)ch#Ld1^Xr5>@ml2WKa`Ogz0B zFfL-&4dY0m*8u8UHhe&sQOklHAj64ym4H(n?Bv+MyQwLz!?L;JBs$qgr6M(k<}e|R ze(aPq-UOV-8zmP%StVpm&^NxqZ5|&LlTR<5Xf+z9LxM=>QI-%$oB>T{)de2q!<0_J zAj9to={B+UHzsPBXkKJdnSIfJr;S`*!)yB`~WjL9yqP$sypW*+RhZ+f(05unE^)jWdN@aFrwllkIceB`*P z{6QVh)-1cOkfJ%Roub;|U%Rxn%Dk_dn={nKRbK7_LZy)YDBKNdgMA> zof*);3omSNIS2cT{k@%f1;f@Bm)9;hMH9!aqt*&MgG*Z&Ksfp>B`y6yX<-?<1`ORA z?rpj!ZMvny3OMCeMvf|Q$tgHiaT}QVQu-53;*I0I!pb_yqjJ{%!E>zPeNC&ZVCu9Bd`Jf>weG`KX(o+TY^-%)$b zR3EF_vY?EiT5M{xy7a-KV-DSHKHF87aM&;F5N2JgwOnjDq=G5o_wcb6b%c`ykrQSLF&gvJ#79Vw@oKumpH-+nzw~@D%W1GJ2ukP%t$A_>kZ4e2Q zsCYTxAOGpS`QhqK3yaB`>qBMA4=3+^u(N+-zBZoQk-Yre)y+#QjQhM^C79SX8m*TX z@kRpU%Ov~smxGErqZZ7@+Q;Tlg0pAar!E-PwcAUZ8{dD|F-E=qP0w!Mx(0;(lB`f<~6pST`;-kCt4ppUK}U_O%~vzQgQe^MGa6$b!tp5 zNYb52vIr)#VF_G-Lnxxj@_35!v;5dK*%cd$pUyysUC(9%(ybAW8zSi;g^qkYG1W>Z zogyv)!b*FuSdWp(4(mqf)TPmyRMhu~%B7oGo2I*aUuVt-D_303Umvfo)F!^BPwOWx zXHdAh2Aj!}-@19z+h?@}GheJv>JE^$Cy3L9+OZinaJ;^lEygQ$?=1+{$Mf<0@W90C@aSM=wm;o)2M-X$0 zjl6nxwCX2CPc%{T6@S9)z*4|!WX;7SH<&ipue|fl_XvLV^47s@|MKNkoCV#Xp!q+d1~h1My6XU-_AdhH_$ZAc!|Iz=`d%ELx5jP7PV>ap{m zEM-AZlMtXu3(5u}?wH_7|I3Td5)GUNqDg6iiulp=hh(L>sWDT8uAHuarR1K{XF+12 zn}T@)R#GUFTJXbZ-TRy_ZtdK9zutXe-qh>2_io&He{;Oj-Z(p2-@3HeeczR!GNOxm zzt_EH`}d9zj%H4J!E07rcUKGJNS14zZ0-CX-}P46{*8A~-uUcikKXZe1BA)VgZ<|} z^MkhS*SyADhIN%c@E)}n`Mh3n_`%KT74NC|O@-;~h!tL0-(0!5w{P#t`E`50Odm29 z4p-Jz4raS!_f^MRc6D~Yw^zTLQ_G6!+y7^Ww;lih5iUtYK~%pNotb^pDIL-og}d5y zq-^Q4zP1XD9zSu#SLK_RE8GE4;RkK=AWMOA@;(Qsw=Q{ZjHOu`kjUzmj0ltVwt#` z2Kv~(8`7QfhW7MM`4g>*xS>#m#_oO=IW9yi|Si!3zp?AE`Sh-FSNUFVPQ2Qe}((=t<-wI!mE2efYfd; zIrmtJVC9fXZ#^viL082xaW@T|k8PAPueI86QHYJNk5bgkzd{`Y%<8n+W#?L~ zoQR=yWAdExtU__uXbRyJsG>Jq<0ft90Kz4oTBPxyoJ@eet` zyS#OIqb)tk7SGEcs2u^{_uE{tIkT!>8$?D)ZFh~2R*vRo~K1fZ-c3hM|KbDMB4RQb|1I4t$d^VvMaL9VHI!IAh=R&zL@Pz-g}?Rm^o!| zx=jc>R$Qbn_dR#nyW!<68?%bFEvnlFW!Kn==9Yh-Z4K0*x>Gm{ewGQ)cke{z++k<& zc@`W)vO2fYk*M!+ojb=DyMnXyW`5A80%zeofMn@>+3Zj8`Rt(CzSc%;_s$0v1=_2A z4uSP&4yUyk(A(;c=c;oD%Yzo`YNyt%tTL|6#@DV>O_J;V`Rw0#55+{FK2&E*-RlnZ zLjh$-O`utSI7}zUb1?Rkv2~2>J3*sy{i0k#?fA4RD^nX| z?rR+E?)d^mU399eN&HOxNG3baM~-Z+@om$I-*;Fi)84_%`xJJGK6$kqn|f7Nn?+L6 z4j$-m5I*uSfG?BJs0Pj&+q%&mHFCfBcO>!{fM@ZkYFRzECmsV!Pem1XQHZSc3UScx zf_!&j%g&w(Ss(byGczsg@W`9InyS8S$HoFO|7F+e2g8~nM#6so@%!hd;&x!_i$-Nv zi-66QGTnuBbl_z$zrSWpAh(AC(F8Gebz6m#fa5S0QzxpVv z`+M`*Vg=^H9iEf!bmPYCgBvrIf9eyLalw7nM%Vc8%9hjRW|w&ubQYtzgJoY4pG>wk zCs!|VAzXs*R@95xt@1I~;h-nwB2>-npfpSyDWt8gYU7CNr6UiQB$ zMyr>+q3xq$GjC42$$IEDxZb)1TieydD*E)}l+k0W}V&YJmKv4;pOak!U8D{WKVYA4!I1td0x4)-R90Ch& z46~y?U{^DJ+aO|B!hFrGtSe&=yYBMC=M0$3g%67Qme9l+q7m#-`xOVQW3|4A&8f6+ zu0))=2a&36*3?0C+h56NPC$G{)QM**)?(i38tKD!Xel>(Ht1!FCUWOF9@AyIRcoyazi$5^1*L&|26e*)~63X_pfp_l|o>|QJ z0OC_%(hs2jyJ*kN<=U8HzkHwkxvO?zj%J4@!nSwXH|QLz){9^EiFe_I-qNzzO13Ym4|C1 z%mG3jhd74t5xuFe)J-;hgkp8wt=heP-;`}{S{yF6HrCc%R>cUmd6>4OQOzcI(?02q zM@xnMjrE({2-mbz&UfWfsX%wnv8Lq zqo`MkS?n}gJK8X6KMPQ{yP0oh5=I_t2Jf-Fhi2y>rE=Fd z$1Ee(t~*z4A4)(zOdZq<7HsrdeKtur@bx2{66;`euy^R(0Gstw3dG^nT5%As&>XqT zG^y3VrAg znBUm3#j;@madUTc^-_H}j`bgpKKcCfS1#MUs@2Hr2l@+#1z>#kxji+@Cz&8|-v#FmsTXBZqJOXPzhOzJM&%+}>X|Ol* z)eNhO6~4>b>4!e!HxJ4ykjFi&J;%ddx3FJylKil)E?b-U; zm96#(rn-<@uJt2f=?^}lj^)Er14|8D6b-Zw&DOhMZKs==obA?!8GMzr-V0#r81CkW zj$I4eY(ni{9sJfJuT~Dc2%}e&)3Hu-zFyhhY9~4bgUFCWj{M zE1PRhjLTeGC?HrDOY7|c{>ay~5B-FhwF-|QZZmyXUCUqW_^qCu1vkJ=xba$xiCO`S zXZt&w^`+H~daqvAVzIk_b1@qqtj&J#6IUqAr?A28M{R9R-+gyRE!HpIU2p-bHrr~o z)aE@O^$1IUKIDHW64z0%sq(aL_uB-j`Frg&`M}+WvbC3}xr*9~_qkFywr*<5zA%M%hjnhT;3a}~ zYVGF}rdyZl%BOjBnf%&<@}8F~pLGt)GV@hF$<Aew}8^$61>*0C&RR zc%|G;n~#@8_ce}ont^48+L3r27&OydPVIzU$-~3lBcH-Jm{Y`>uMwev&}96=AT!{WxUkD zQUgm3+=B-0*B_K=y1!886C0PdOrNa)>Q&^G>8j(=%}cB7By;ZPjQn1=OZ#hn6l|k* zerl(PDdajf?FH362FA}WiIjDM`pcC}eX(wn5nr**a^we{8Qx2dBW$3cxw<-;c`0qp zPbt?1kWX#5A2vXNEne|OA;leR)XmVg>r%(j)*p7Fyl!lLG2U4BYdokh`epI4BYf)l zE6$QF2%IVuqz;zbIYHYm@^W&0pJLO;Eh=yraHQ+F`sm1yIc&sxnal^H-E|*qo5P>f#(p*hlXgZ--ITh+-UP&$Er3RK7 zSZd%NHSnnXL7TVNzD}9!R=2JB20XG~R3_d(yX^b{3ub~}X4Vx>zgs``+n8$`>%MN} zIJJsGXmeuAhr(eQ89XHr_p^}&s_z#~+V@cF!_+EdxyvNBn(Ji++dA$3QahL^3M=Ef z=kH};?ny=JfZtfK%jCrN=wRklowI2ow9g}1!?4}$sJZ6w&E)#4jxzK%sH_I2^{pYS z`K5&Kzgxc)V!89&m8))$dRxq4ajAeyVbK-ARWV+~l!}{i%I({CX@w9M7af1f(K#Zh zcd$V!w|34}>)60QKB+A->Z)#e%Q|sA(ZT4DKR~s1p_-dQsW!0OX!1FTazSX+I{`JXZqt7knYgWbA*?JhV5_+% z=PpZGBG_>%BMc11FBUxi+@xxlZWNs zr3RK7SZd&jXrSz4n?a{`lkWOJ5A)r2{3TdfyE^rgWo6tCUCW78cfe33S?O8{G`CQ{ zm{7Yg=HiZj>r0T#O2e(A2LFmSD~fV`j=StXzXYLf z=O7^tR6Pxit2%NjfLnf{tbQ}V?6Zxp)wadM;RtR7$x+ykVFFw>+fQ9ZyM=b4wt2+T z0-5S-sm?Zh_*5~MTZ?wFn%{D(Eg?%2zsJ*-AbgI0>VUO6UG=I#d8`(yP&p6BF4k3O zeHfPeMIY*7nObUKseuck0fuxK*yDsG?4C#@U533O4io@os% z{lPQc{7d1N8dz#zse!J6r9bGZSSFSlSZZLYfoEC+OMmc8H~&)jr3RK7SZbhaVCfIK zDwc_*29_FFYT%jHz|tQ))6KsWeyM?_29_G=8d&;+u8L)1sez>imKu1bHL&yt&vf%I jgix(5Cyv#V}!7w)#e00000NkvXXu0mjf0R&JT literal 0 HcmV?d00001 diff --git a/docs/community/issue.md b/docs/community/issue.md new file mode 100644 index 000000000..f0afc90d2 --- /dev/null +++ b/docs/community/issue.md @@ -0,0 +1,112 @@ +--- +title: 关于引擎的 issue 说明 +sidebar_position: 2 +--- +> 提交地址:[https://github.com/alibaba/lowcode-engine/issues](https://github.com/alibaba/lowcode-engine/issues) + +### 提交前必读 +由于引擎项目复杂,很多问题在复现和沟通上无法花费太多时间,需要大家尽力将复现步骤说明白。 + + +![image.png](./img/you-think.png) + +**你以为的 issue** + + +![image.png](./img/i-see.png) + +**我们看到的 issue** + +为了更好的进行协作,对引擎 issue 的处理定了一些处理的优先级。请大家认真阅读 Orz. + +- 【支持快】通过线上 Demo 地址 + 控制台输入 API 可复现。 +- 【支持快】通过线上 Demo + 导入 schema 可复现 +- 【支持稍慢】通过线上 Demo + 完整操作步骤可复现 +- 【支持稍慢】通过线上 Demo + 变更代码可复现,并清楚的说明变更代码的位置和内容 +- 【支持慢】有完整的项目地址,下载下来可直接安装依赖并启动复现的 +- 【支持慢】需求类型的由于人力有限,欢迎大家 PR,如能讲清楚背景上下文和场景,项目维护团队更容易给出方案建议或方向指引。 +- 【不保证提供支持】其他 + - 只有标题没有复现步骤 + - 复现步骤不清晰 + - 和引擎无关的 + +### 不同优先级的示例 +#### 【支持快】通过线上 Demo 地址 + 控制台输入 API 可复现。 +**示例** +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656387671833-cd44507b-af59-45ec-b0da-f4f0ef61e92e.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=295&id=ub61f0ab8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1862&originWidth=3322&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5033674&status=done&style=none&taskId=u3646a3b6-4b22-48e7-94e3-564a09cfa24&title=&width=527) +复现步骤: + +- 打开线上 demo +- 在控制台输入 +```json +// 当前 doc +const doc = window.AliLowCodeEngine.project.currentDocument + +// 新建 doc 并成功切换 +window.AliLowCodeEngine.project.openDocument({ + componentName: 'Page' +}); + +// 无法切换回来 +window.AliLowCodeEngine.project.openDocument('docl4xkca5b') +``` + +预期效果: + +- 使用 openDocument 可以正常的切换回原来的 doc + +#### 【支持快】通过线上 demo + 导入 schema 可复现 +步骤: + +- 使用线上 demo +- 导入下面的 schema +- schema 代码/schema zip 压缩包 +- 页面效果如下 + +期望: + +- 页面中的xxx部分和预期不符合,期望的效果是 xxx + +#### 【支持稍慢】通过线上 demo + 完整操作步骤可复现 +**示例** +1.使用 antd 组件 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656387998779-9f621c7f-82cb-48ad-94fc-84c2cd46065c.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=906&id=u0ad0726a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1812&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=838860&status=done&style=none&taskId=u0a0a9e20-f79e-4c8c-8c82-b304f7b7583&title=&width=1792) + +2.拖拽这个组件 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656388046560-e07680ee-809a-4ad1-bc47-47c2c00fdd40.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=901&id=u23c8416a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1802&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=890196&status=done&style=none&taskId=u7ac32b55-f32c-4215-ac1d-f81f5e986ac&title=&width=1792) + +3.配置该属性值为 100 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656388075312-7c06f15a-464a-49f0-beb5-19320ea0e454.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=900&id=ua91e7f85&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1800&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=882142&status=done&style=none&taskId=u61082c8a-1092-4b5b-a2ea-00486cadb71&title=&width=1792) + +期望效果: + +- 组件同配置一致 + +#### 【支持稍慢】通过线上 demo + 变更代码可复现,并清楚的说明变更代码的位置和内容 +**示例** +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656387894830-6850815f-e2ee-46bf-a2bf-fdda4d166691.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u87419dd1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=754&originWidth=1892&originalType=binary&ratio=1&rotation=0&showTitle=false&size=226627&status=done&style=none&taskId=u88b2bbb8-869c-482c-9510-9d513f6e191&title=&width=946) + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656387911054-771dd7fc-db90-46ae-b1db-f5f9f7537ed4.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=389&id=u0a370108&margin=%5Bobject%20Object%5D&name=image.png&originHeight=778&originWidth=1917&originalType=binary&ratio=1&rotation=0&showTitle=false&size=229881&status=done&style=none&taskId=ucbc7af71-f0e1-4319-9097-8ad6b936c5e&title=&width=958.5) + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656387922644-de3f1d64-0206-407d-82ad-2d1155374e37.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=127&id=u9c5921eb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=253&originWidth=1836&originalType=binary&ratio=1&rotation=0&showTitle=false&size=58615&status=done&style=none&taskId=u5c8af90a-0d20-40c8-a1f2-e387f037d85&title=&width=918) + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656387931330-a5453ba1-264b-4325-b3a8-7cb6e22633ee.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=457&id=u687acf85&margin=%5Bobject%20Object%5D&name=image.png&originHeight=914&originWidth=1912&originalType=binary&ratio=1&rotation=0&showTitle=false&size=129980&status=done&style=none&taskId=u3a706b70-0da6-484d-857d-1d086f7a4e5&title=&width=956) + +#### 【支持慢】有完整的项目地址,下载下来可直接安装依赖并启动复现的 +由于完整的项目中有很多冗余的信息,这部分排查起来十分耗时且困难。不推荐使用改方式。 + +#### 【不保证提供支持】其他 +##### 只有标题没有复现步骤 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656388351815-e086b980-0828-4c49-ba72-142446313d2d.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=510&id=u79a38c3b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1020&originWidth=2520&originalType=binary&ratio=1&rotation=0&showTitle=false&size=529258&status=done&style=none&taskId=u3540b08e-9dff-4c72-8ee5-123912439b0&title=&width=1260) + +##### 复现步骤不清晰 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656388451393-2168e5ca-20de-4781-9e51-20e282dbc0ca.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=833&id=ubaf001f6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1666&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1228630&status=done&style=none&taskId=ub26ed4ff-e0cf-4644-9a65-00ddee4b9e5&title=&width=1792) + +##### 和引擎无关的 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1656388376995-0ab5d7c0-8ff9-49cf-8854-70e9bb3ff87a.png#clientId=uaa040ac3-dccc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=715&id=uffc59321&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1430&originWidth=2548&originalType=binary&ratio=1&rotation=0&showTitle=false&size=747119&status=done&style=none&taskId=u861d5fa6-f673-4091-8635-ff45adf680e&title=&width=1274) + + + + +### 扩展阅读 +强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。(此段参考 [antd](https://github.com/ant-design/ant-design)) diff --git a/docs/config/navbar.js b/docs/config/navbar.js new file mode 100644 index 000000000..efbe88ec0 --- /dev/null +++ b/docs/config/navbar.js @@ -0,0 +1,82 @@ +/** + * 此配置的修改,如未生效,可以重新启动下即可 + */ +module.exports = { + title: '', + logo: { + alt: 'LowCode-Engine', + src: 'https://img.alicdn.com/imgextra/i2/O1CN01uv6vu822RBCSYLro2_!!6000000007116-55-tps-139-26.svg', + srcDark: 'https://tianshu.alicdn.com/052a190e-c961-4afe-aa4c-49ee9722952d.svg', + }, + items: [ + { + type: 'doc', + docId: 'guide/quickStart/intro', + position: 'left', + label: '文档', + }, + { + type: 'doc', + docId: 'api/index', + position: 'left', + label: 'API', + }, + { + type: 'doc', + docId: 'faq/index', + position: 'left', + label: 'FAQ', + }, + { + type: 'doc', + docId: 'participate/index', + position: 'left', + label: '参与贡献', + }, + { + type: 'doc', + docId: 'article/index', + position: 'left', + label: '文章', + }, + { + type: 'doc', + docId: 'demoUsage/intro', + position: 'left', + label: 'Demo 使用文档', + }, + { + position: 'left', + href: 'https://developer.aliyun.com/ebook/7507', + label: '技术白皮书', + }, + { + position: 'left', + href: 'https://github.com/alibaba/lowcode-engine/releases', + label: '更新日志', + }, + { + to: '/community/issue', + position: 'left', + label: '社区', + activeBaseRegex: '/community/', + }, + // 版本切换,如需,这里开启即可 + // { + // type: 'docsVersionDropdown', + // position: 'right', + // dropdownActiveClassDisabled: true, + // }, + // { + { + href: 'https://github.com/alibaba/lowcode-engine', + position: 'right', + className: 'header-github-link', + 'aria-label': 'GitHub repository', + }, + { + type: 'search', + position: 'right', + }, + ], +}; diff --git a/docs/config/sidebars.js b/docs/config/sidebars.js new file mode 100644 index 000000000..fc14767a3 --- /dev/null +++ b/docs/config/sidebars.js @@ -0,0 +1,55 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const getDocsFromDir = require('../scripts/getDocsFromDir'); + +module.exports = { + // 手动配置的导航 + // guide: [ + // 'guide/quickStart/intro', + // 'guide/quickStart/start', + // { + // type: 'category', + // label: 'FAQ', + // collapsed: false, + // items: getDocsFromDir('guide/quickStart/faq'), + // }, + // ], + /** + * 根据当前目录自动生成导航配置 + */ + guide: [ + { + type: 'autogenerated', + dirName: 'guide', // '.' 即当前的文档文件夹 + }, + ], + api: [ + { + type: 'autogenerated', + dirName: 'api', + }, + ], + faq: [ + { + type: 'autogenerated', + dirName: 'faq', + }, + ], + participate: [ + { + type: 'autogenerated', + dirName: 'participate', + }, + ], + demoUsage: [ + { + type: 'autogenerated', + dirName: 'demoUsage', + }, + ], + // api: getDocsFromDir('api'), +}; diff --git a/docs/config/sidebarsCommunity.js b/docs/config/sidebarsCommunity.js new file mode 100644 index 000000000..a96aa0521 --- /dev/null +++ b/docs/config/sidebarsCommunity.js @@ -0,0 +1,13 @@ +module.exports = { + community: [ + { + type: 'autogenerated', + dirName: '.', + }, + { + type: 'link', + label: '生态资源', + href: 'https://github.com/lowcode-workspace/awesome-lowcode-engine', + }, + ], +}; diff --git a/docs/docs/api/common.md b/docs/docs/api/common.md new file mode 100644 index 000000000..886ae925a --- /dev/null +++ b/docs/docs/api/common.md @@ -0,0 +1,57 @@ +--- +title: common - 通用 API +sidebar_position: 11 +--- +# 模块简介 +通用模块里包含除了 9 大核心模块 API 之外的所有 API,比如通用 utils、面板扩展相关 等。 +> 高能预警:之所以叫 skeletonCabin / designerCabin 跟兼容上一个版本的引擎有关系。若有必要,后面将用更有意义的命名空间来组织这些 API。 + +# 变量(variables) +### utils +通用 utils,详见下方方法签名 + +### designerCabin +设计器扩展相关,详见下方方法签名 + +### skeletonCabin +面板扩展相关,详见下方方法签名 + +# 方法签名(functions) +## utils +### isNodeSchema +是否为合法的 schema 结构 + +### isFormEvent +是否为表单事件类型 + +### getNodeSchemaById +从 schema 结构中查找指定 id 节点 + +### executeTransaction +批处理事务,用于优化特定场景的性能 +*引擎版本 >= 1.0.16 +```typescript +import { common } from '@alilc/lowcode-engine'; +import { TransitionType } from '@alilc/lowcode-types'; + +common.utils.startTransaction(() => { + node1.setProps(); + node2.setProps(); + node3.setProps(); + // ... +}, TransitionType.repaint); +``` + +## designerCabin +### isSettingField +是否是 SettingField 实例 + +### TransformStage +转换类型枚举对象,包含 init / upgrade / render 等类型,参考 [TransformStage](https://github.com/alibaba/lowcode-engine/blob/4f4ac5115d18357a7399632860808f6cffc33fad/packages/types/src/transform-stage.ts#L1) +## +## skeletonCabin +### Workbench +编辑器框架 View + +# 事件(events) +无 diff --git a/docs/docs/api/config.md b/docs/docs/api/config.md new file mode 100644 index 000000000..40d18eb3a --- /dev/null +++ b/docs/docs/api/config.md @@ -0,0 +1,107 @@ +--- +title: config - 配置 API +sidebar_position: 8 +--- +## 模块简介 +配置模块,负责配置的读、写等操作。 +## +## 变量(variables) +无 +## +## 方法签名(functions) +### get +获取指定 key 的值 + +**类型定义** +```typescript +function get(key: string, defaultValue?: any): any +``` +**调用示例** +```typescript +import { config } from '@alilc/lowcode-engine'; + +config.get('keyA', true); +config.get('keyB', { a: 1 }); +``` +### set +设置指定 key 的值 + +**类型定义** +```typescript +function set(key: string, value: any) +``` +**调用示例** +```typescript +import { config } from '@alilc/lowcode-engine'; + +config.set('keyC', 1); +``` + +### has +判断指定 key 是否有值 + +**类型定义** +```typescript +function has(key: string): boolean +``` +**调用示例** +```typescript +import { config } from '@alilc/lowcode-engine'; + +config.has('keyD'); +``` +### +### setConfig +批量设值,set 的对象版本 + +**类型定义** +```typescript +function setConfig(config: { [key: string]: any }) +``` +**调用示例** +```typescript +import { config } from '@alilc/lowcode-engine'; + +config.setConfig({ keyA: false, keyB: 2 }); +``` + +### onceGot +获取指定 key 的值,若此时还未赋值,则等待,若已有值,则直接返回值 +注:此函数返回 Promise 实例 + +**类型定义** +```typescript +function onceGot(key: string): Promise +``` +**调用示例** +```typescript +import { config } from '@alilc/lowcode-engine'; + +config.onceGot('keyA').then(value => { + console.log(`The value of keyA is ${value}`); +}); + +// or +const value = await config.onceGot('keyA'); +``` + +### onGot +获取指定 key 的值,函数回调模式,若多次被赋值,回调会被多次调用 + +**类型定义** +```typescript +function onGot(key: string, fn: (data: any) => void): () => void +``` +**调用示例** +```typescript +import { config } from '@alilc/lowcode-engine'; + +config.onGot('keyA', (value) => { + console.log(`The value of keyA is ${value}`); +}); + +const.set('keyA', 1); // 'The value of keyA is 1' +const.set('keyA', 2); // 'The value of keyA is 2' +``` +## 事件(events) +无 diff --git a/docs/docs/api/datasource.md b/docs/docs/api/datasource.md new file mode 100644 index 000000000..cadf9cb58 --- /dev/null +++ b/docs/docs/api/datasource.md @@ -0,0 +1,29 @@ +--- +title: DataSource - 数据源 API +sidebar_position: 12 +--- +### 请求数据源 +```javascript +// 请求userList (userList在数据源面板中定义) + +this.dataSourceMap['userList'].load({ + data: {} +}).then(res => {}) + .catch(error => {}); +``` + +### 获取数据源的值 +```javascript +const { userList } = this.state; +``` + +### 手动修改数据源值 +```javascript +// 获取数据源面板中定义的值 +const { user } = this.state; + +// 修改state值 +this.setState({ + user: {} +}); +``` diff --git a/docs/docs/api/event.md b/docs/docs/api/event.md new file mode 100644 index 000000000..26232b7c1 --- /dev/null +++ b/docs/docs/api/event.md @@ -0,0 +1,81 @@ +--- +title: event - 事件 API +sidebar_position: 7 +--- +## 模块简介 +负责事件处理 API,支持自定义监听事件、触发事件。 + +## 方法签名(functions) +### on +监听事件 + +**类型定义** +```typescript +function on(event: string, listener: (...args: unknown[]) => void): void; +``` + +### off +取消监听事件 + +**类型定义** +```typescript +function off(event: string, listener: (...args: unknown[]) => void): void; +``` + +### emit +触发事件 + +**类型定义** + +```typescript +function emit(event: string, ...args: unknown[]): void; +``` + +## 使用示例 +### 事件触发和监听 +```typescript +const eventName = 'eventName'; + +// 事件监听 +event.on(`common:${eventName}`); + +// 触发事件 +event.emit(eventName); +``` + +### setter 和 setter/plugin 之间的联动 +在 A setter 中进行事件注册: +```typescript +import { event } from '@alilc/lowcode-engine'; + +const SETTER_NAME = 'SetterA'; + +class SetterA extends React.Component { + componentDidMount() { + // 这里由于面板上会有多个 setter,使用 field.id 来标记 setter 名 + this.emitEventName = `${SETTER_NAME}-${this.props.field.id}`; + event.on(`common:${this.emitEventName}.bindEvent`, this.bindEvent) + } + + bindEvent = (eventName) => { + // do someting + } + + componentWillUnmount() { + // setter 是以实例为单位的,每个 setter 注销的时候需要把事件也注销掉,避免事件池过多 + event.off(`common:${this.emitEventName}.bindEvent`, this.bindEvent) + } +} +``` +在 B setter 中触发事件,来完成通信: +```typescript +import { event } from '@alilc/lowcode-engine'; + +class SetterB extends React.Component { + bindFunction = () => { + const { field, value } = this.props; + // 这里展示的和插件进行通信, 事件规则是插件名 + 方法 + event.emit('eventBindDialog.openDialog', field.name, this.emitEventName); + } +} +``` diff --git a/docs/docs/api/hotkey.md b/docs/docs/api/hotkey.md new file mode 100644 index 000000000..60e8a3f3f --- /dev/null +++ b/docs/docs/api/hotkey.md @@ -0,0 +1,70 @@ +--- +title: hotkey - 快捷键 API +sidebar_position: 5 +--- +## 模块简介 +绑定快捷键 API,可以自定义项目快捷键使用。 + +## 方法签名(functions) +### bind +绑定快捷键 + +**类型定义** +```typescript +function bind( + combos: string[] | string, + callback: (e: KeyboardEvent, combo?: string) => any | false, + action?: string +): () => void; +``` + +**示例** +```typescript +hotkey.bind('command+s', (e) => { + e.preventDefault(); + // command+s 快捷键按下时需要执行的逻辑 +}); +``` + +## 使用示例 +### 基础示例 +```typescript +hotkey.bind('command+s', (e) => { + e.preventDefault(); + // command+s 快捷键按下时需要执行的逻辑 +}); +``` + +### 同时绑定多个快捷键 +```typescript +hotkey.bind(['command+s', 'command+c'], (e) => { + e.preventDefault(); + // command+s 或者 command+c 快捷键按下时需要执行的逻辑 +}); +``` + +### 保存快捷键配置 +```typescript +import { + hotkey, +} from '@alilc/lowcode-engine'; + +function saveSchema(schema) { + // 保存 schema 相关操作 +} + +const saveSampleHotKey = (ctx: ILowCodePluginContext) => { + return { + name: 'saveSample', + async init() { + hotkey.bind('command+s', (e) => { + e.preventDefault(); + saveSchema(); + }); + }, + }; +} + +saveSampleHotKey.pluginName = 'saveSampleHotKey'; +plugins.register(saveSampleHotKey); +``` diff --git a/docs/docs/api/index.md b/docs/docs/api/index.md new file mode 100644 index 000000000..1afccce18 --- /dev/null +++ b/docs/docs/api/index.md @@ -0,0 +1,16 @@ +--- +title: API 总览 +sidebar_position: 0 +--- + +引擎直接提供 9 大类 API,以及若干间接的 API,具体如下图: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1645445575048-cc511d60-3b84-411d-a70e-21b7a596d09c.png#clientId=uaab5e9c4-fa7b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=695&id=u8e1d0318&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1390&originWidth=1278&originalType=binary&ratio=1&rotation=0&showTitle=false&size=410614&status=done&style=none&taskId=u9fdcdcfb-4e8b-4e22-8865-94181f458d0&title=&width=639) + +### API 设计约定 +一些 API 设计约定: + +1. 所有 API 命名空间都按照 variables / functions / events 来组织 +2. 事件(events)的命名格式为:on[Will|Did]VerbNoun?,参考 [https://code.visualstudio.com/api/references/vscode-api#events](https://code.visualstudio.com/api/references/vscode-api#events) +3. 基于 Disposable 模式,对于事件的绑定、快捷键的绑定函数,返回值则是解绑函数 +4. 对于属性的导出,统一用 .xxx 的 getter 模式,(尽量)不使用 .getXxx() diff --git a/docs/docs/api/init.md b/docs/docs/api/init.md new file mode 100644 index 000000000..50c5cf701 --- /dev/null +++ b/docs/docs/api/init.md @@ -0,0 +1,138 @@ +--- +title: init - 初始化 API +sidebar_position: 10 +--- +## 模块简介 +提供 init 等方法 +## 方法签名 +#### 1. init +初始化引擎 + +**方法定义** +```typescript +function init(container?: Element, options?: EngineOptions): void +``` + +**初始化引擎的参数** +```typescript +interface EngineOptions { + /** + * 指定初始化的 device + */ + device?: 'default' | 'mobile'; + /** + * 指定初始化的 deviceClassName,挂载到画布的顶层节点上 + */ + deviceClassName?: string; + /** + * 是否开启 condition 的能力,默认在设计器中不管 condition 是啥都正常展示 + */ + enableCondition?: boolean; + /** + * 开启拖拽组件时,即将被放入的容器是否有视觉反馈,默认值:false + */ + enableReactiveContainer?: boolean; + /** + * 关闭画布自动渲染,在资产包多重异步加载的场景有效,默认值:false + */ + disableAutoRender?: boolean; + /** + * 打开画布的锁定操作,默认值:false + */ + enableCanvasLock?: boolean; + /** + * 容器锁定后,容器本身是否可以设置属性,仅当画布锁定特性开启时生效, 默认值为:false + */ + enableLockedNodeSetting?: boolean; + /** + * 开启画布上的鼠标事件的冒泡,默认值:false + */ + enableMouseEventPropagationInCanvas?: boolean; + /** + * 关闭拖拽组件时的虚线响应,性能考虑,默认值:false + */ + disableDetecting?: boolean; + /** + * 定制画布中点击被忽略的 selectors,默认值:undefined + */ + customizeIgnoreSelectors?: (defaultIgnoreSelectors: string[]) => string[]; + /** + * 禁止默认的设置面板,默认值:false + */ + disableDefaultSettingPanel?: boolean; + /** + * 禁止默认的设置器,默认值:false + */ + disableDefaultSetters?: boolean; + /** + * 当选中节点切换时,是否停留在相同的设置 tab 上,默认值:false + */ + stayOnTheSameSettingTab?: boolean; + /** + * 自定义 loading 组件 + */ + loadingComponent?: ComponentType; + + /** + * @default true + * JSExpression 是否只支持使用 this 来访问上下文变量,假如需要兼容原来的 'state.xxx',则设置为 false + */ + thisRequiredInJSE?: boolean; + + /** + * @default false + * >= 1.0.14 + * 当开启组件未找到严格模式时,渲染模块不会默认给一个容器组件 + */ + enableStrictNotFoundMode?: boolean; + + /** + * 配置指定节点为根组件 + * >= 1.0.15 + */ + focusNodeSelector?: (rootNode: Node) => Node; + + /** + * 工具类扩展 + */ + appHelper?: { + utils?: {}; + } + + [key: string]: any; +} +``` +## 使用示例 +```typescript +import { init } from '@alilc/lowcode-engine'; + +init(document.getElementById('engine'), { + enableCondition: false, +}); +``` +### +### 默认打开移动端画布 +```typescript +import { init } from '@alilc/lowcode-engine'; + +init({ + device: 'mobile', +}); +``` + +### 使用 utils 第三方工具扩展 +```json +import { init } from '@alilc/lowcode-engine'; + +init({ + device: 'mobile', + appHelper: { + utils: { + xxx: () => {console.log('123')}, + } + } +}); +``` + +在引擎中即可这样使用。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1657272220368-9ee4430e-9e42-4746-9de8-a233840b0950.png#clientId=u951c1fcc-9dab-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=292&id=uacb8d50d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1796&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1641368&status=error&style=none&taskId=u559fb5cd-4a48-4732-b169-c9868a6d7b7&title=&width=582) diff --git a/docs/docs/api/logger.md b/docs/docs/api/logger.md new file mode 100644 index 000000000..3fbb7b7fc --- /dev/null +++ b/docs/docs/api/logger.md @@ -0,0 +1,49 @@ +--- +title: logger - 日志 API +sidebar_position: 9 +--- +## 模块简介 +引擎日志模块,可以按照 **日志级别 **和** 业务类型 **两个维度来定制日志,基于 [zen-logger](https://web.npm.alibaba-inc.com/package/zen-logger) 封装。 +> 注:日志级别可以通过 url query 动态调整,详见下方使用示例。 + +## 变量(variables) +无 +## 方法签名(functions) +### log / warn / error / info / debug +日志记录方法 + +**类型定义** +```typescript +function log(args: any[]): void +function warn(args: any[]): void +function error(args: any[]): void +function info(args: any[]): void +function debug(args: any[]): void +``` +**调用示例** +```typescript +import { logger } from '@alilc/lowcode-engine'; + +logger.log('Awesome Low-Code Engine'); +``` +## 事件(events) +无 + +## 使用示例 +```typescript +import { logger } from '@alilc/lowcode-engine'; + +// 内部实现:logger = getLogger({ level: 'warn', bizName: 'designer:pluginManager' }) + +// 若在url query中增加 `__logConf__` 可改变打印日志级别和限定业务类型日志 +// 默认:__logConf__=warn:* +logger.log('log'); // 不输出 +logger.warn('warn'); // 输出 +logger.error('error'); // 输出 + +// 比如:__logConf__=log:designer:pluginManager +logger.log('log'); // 输出 +logger.warn('warn'); // 输出 +logger.error('error'); // 输出 + +``` diff --git a/docs/docs/api/material.md b/docs/docs/api/material.md new file mode 100644 index 000000000..852287f64 --- /dev/null +++ b/docs/docs/api/material.md @@ -0,0 +1,320 @@ +--- +title: material - 物料 API +sidebar_position: 2 +--- +# 模块简介 +负责物料相关的 API,包括资产包、设计器辅助层、物料元数据和物料元数据管道函数。 + +# 变量(variables) +## componentsMap +获取组件 map 结构 + +# 方法签名(functions) +## 资产包 +### setAssets +设置「[资产包](https://www.yuque.com/lce/doc/vgcyf1)」结构 + +**类型定义** +```typescript +function setAssets(assets: AssetsJson): void; +``` + +**示例** +直接在项目中引用 npm 包 +```javascript +import { material } from '@alilc/lowcode-engine'; +import assets from '@alilc/mc-assets-/assets.json'; + +material.setAssets(assets); +``` + +通过物料中心接口动态引入资产包 +```typescript +import { ILowCodePluginContext, material, plugins } from '@alilc/lowcode-engine' + +// 动态加载 assets +plugins.register((ctx: ILowCodePluginContext) => { + return { + name: 'ext-assets', + async init() { + try { + // 将下述链接替换为您的物料即可。无论是通过 utils 从物料中心引入,还是通过其他途径如直接引入物料描述 + const res = await window.fetch('https://fusion.alicdn.com/assets/default@0.1.95/assets.json') + const assets = await res.text() + material.setAssets(assets) + } catch (err) { + console.error(err) + } + }, + } +}).catch(err => console.error(err)) +``` + +### getAssets +获取「资产包」结构 +**类型定义** +```typescript +function getAssets(): AssetsJson; +``` + + +**示例** +```typescript +import { material } from '@alilc/lowcode-engine'; + +material.getAssets(); +``` + +### loadIncrementalAssets +加载增量的「资产包」结构,该增量包会与原有的合并 + +**类型定义** +```typescript +function loadIncrementalAssets(incrementalAssets: AssetsJson): void; +``` +说明:**该增量包会与原有的合并** + +**示例** +```typescript +import { material } from '@alilc/lowcode-engine'; +import assets1 from '@alilc/mc-assets-/assets.json'; +import assets2 from '@alilc/mc-assets-/assets.json'; + +material.setAssets(assets1); +material.loadIncrementalAssets(assets2); +``` +## 设计器辅助层 +### addBuiltinComponentAction +在设计器辅助层增加一个扩展 action +**类型定义** +```typescript +function addBuiltinComponentAction(action: ComponentAction): void; + +export interface ComponentAction { + /** + * behaviorName + */ + name: string; + /** + * 菜单名称 + */ + content: string | ReactNode | ActionContentObject; + /** + * 子集 + */ + items?: ComponentAction[]; + /** + * 显示与否 + * always: 无法禁用 + */ + condition?: boolean | ((currentNode: any) => boolean) | 'always'; + /** + * 显示在工具条上 + */ + important?: boolean; +} + +export interface ActionContentObject { + /** + * 图标 + */ + icon?: IconType; + /** + * 描述 + */ + title?: TipContent; + /** + * 执行动作 + */ + action?: (currentNode: any) => void; +} + +export type IconType = string | ReactElement | ComponentType | IconConfig; +``` + + +**示例** +新增设计扩展位,并绑定事件 +```typescript +import { material } from '@alilc/lowcode-engine'; + +material.addBuiltinComponentAction({ + name: 'myIconName', + content: { + icon: () => 'x', + title: 'hover title', + action(node) { + console.log('myIconName 扩展位被点击'); + } + }, + important: true, + condition: true, +}); +``` +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1645446545381-aa6f4543-5f6e-4a03-91c1-e88817823153.png#clientId=u51926daa-3723-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=198&id=u34e8d0d9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=198&originWidth=230&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16907&status=done&style=none&taskId=u27bac39f-a38f-43bf-a0e5-157118e3aa6&title=&width=230) + +### removeBuiltinComponentAction +移除设计器辅助层的指定 action +**类型定义** +```typescript +function removeBuiltinComponentAction(name: string): void; +``` + +**示例** +```typescript +import { material } from '@alilc/lowcode-engine'; + +material.removeBuiltinComponentAction('myIconName'); +``` + + +### modifyBuiltinComponentAction +修改已有的设计器辅助层的指定 action +**类型定义** +```typescript +function modifyBuiltinComponentAction( + actionName: string, + handle: (action: ComponentAction) => void +): void; +``` +**内置设计器辅助 name** + +- remove:删除 +- hide:隐藏 +- copy:复制 +- lock:锁定,不可编辑 +- unlock:解锁,可编辑 + + + +**示例** +给原始的 remove 扩展时间添加执行前后的日志 +```typescript +import { material } from '@alilc/lowcode-engine'; + +material.modifyBuiltinComponentAction('remove', (action) => { + const originAction = action.content.action; + action.content.action = (node) => { + console.log('before reomve!'); + originAction(node); + console.log('after remove!'); + } +}); +``` +### +## 物料元数据 +### getComponentMeta +获取指定名称的物料元数据 +**类型定义** +```typescript +function getComponentMeta(componentName: string): ComponentMeta; +``` + +**示例** +```typescript +import { material } from '@alilc/lowcode-engine'; + +material.getComponentMeta('Input'); +``` + +### getComponentMetasMap +获取所有已注册的物料元数据 +**类型定义** +```typescript +function getComponentMetasMap(): new Map; +``` + +**示例** +```typescript +import { material } from '@alilc/lowcode-engine'; + +material.getComponentMetasMap(); +``` + + +## 物料元数据管道函数 +### registerMetadataTransducer +注册物料元数据管道函数,在物料信息初始化时执行。 +**类型定义** +```typescript +function registerMetadataTransducer( + transducer: MetadataTransducer, // 管道函数 + level?: number, // 优先级 + id?: string | undefined, // id +): void; +``` + +**示例** +给每一个组件的配置添加高级配置面板,其中有一个是否渲染配置项 +```typescript +import { material } from '@alilc/lowcode-engine' + +function addonCombine(metadata: TransformedComponentMetadata) { + const { componentName, configure = {} } = metadata; + const advanceGroup = []; + const combined: FieldConfig[] = []; + + advanceGroup.push({ + name: getConvertedExtraKey('condition'), + title: { type: 'i18n', 'zh-CN': '是否渲染', 'en-US': 'Condition' }, + defaultValue: true, + setter: [ + { + componentName: 'BoolSetter', + }, + { + componentName: 'VariableSetter', + }, + ], + extraProps: { + display: 'block', + }, + }); + + combined.push({ + name: '#advanced', + title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advanced' }, + items: advanceGroup, + }); + + return { + ...metadata, + configure: { + ...configure, + combined, + }, + }; +} + +material.registerMetadataTransducer(addonCombine, 1, 'parse-func'); +``` + +### getRegisteredMetadataTransducers +获取所有物料元数据管道函数 +**类型定义** +```typescript +function getRegisteredMetadataTransducers(): MetadataTransducer[]; +``` + +**示例** +```typescript +import { material } from '@alilc/lowcode-engine' + +material.getRegisteredMetadataTransducers('parse-func'); +``` +## +# 事件(Event) +### onChangeAssets +监听 assets 变化的事件 +**类型定义** +```typescript +function onChangeAssets(fn: () => void): void; +``` +**示例** +```typescript +import { material } from '@alilc/lowcode-engine'; + +material.onChangeAssets(() => { + console.log('asset changed'); +}); +``` diff --git a/docs/docs/api/plugins.md b/docs/docs/api/plugins.md new file mode 100644 index 000000000..8762dbacd --- /dev/null +++ b/docs/docs/api/plugins.md @@ -0,0 +1,281 @@ +--- +title: plugins - 插件 API +sidebar_position: 4 +--- +## 模块简介 +插件管理器,提供编排模块中管理插件的能力。 +## 变量(variables) +无 +## 方法签名(functions) +### register +注册插件 + +#### 类型定义 +```typescript +async function register( + pluginConfigCreator: (ctx: ILowCodePluginContext) => ILowCodePluginConfig, + options?: ILowCodeRegisterOptions, +): Promise +``` +pluginConfigCreator 是一个 ILowCodePluginConfig 生成函数,ILowCodePluginConfig 中包含了该插件的 init / destroy 等钩子函数,以及 exports 函数用于返回插件对外暴露的值。 + +另外,pluginConfigCreator 还必须挂载 pluginName 字段,全局确保唯一,否则 register 时会报错,可以选择性挂载 meta 字段,用于描述插件的元数据信息,比如兼容的引擎版本、支持的参数配置、依赖插件声明等。 +> 注:pluginConfigCreator 挂载 pluginName / meta 可以通过低代码工具链的插件脚手架生成,写如 package.json 后将会自动注入到代码中,具体见 [插件元数据工程化示例](#RO9YY) + + +#### 简单示例 +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +const builtinPluginRegistry = (ctx: ILowCodePluginContext) => { + return { + async init() { + const { skeleton } = ctx; + + // 注册组件面板 + const componentsPane = skeleton.add({ + area: 'leftArea', + type: 'PanelDock', + name: 'componentsPane', + content: ComponentsPane, + contentProps: {}, + props: { + align: 'top', + icon: 'zujianku', + description: '组件库', + }, + }); + componentsPane?.disable?.(); + project.onSimulatorRendererReady(() => { + componentsPane?.enable?.(); + }) + }, + }; +} +builtinPluginRegistry.pluginName = 'builtinPluginRegistry'; +await plugins.register(builtinPluginRegistry); +``` +#### 使用 exports 示例 +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +const pluginA = (ctx: ILowCodePluginContext) => { + return { + async init() {}, + exports() { return { x: 1, } }, + }; +} +pluginA.pluginName = 'pluginA'; + +const pluginB = (ctx: ILowCodePluginContext) => { + return { + async init() { + // 获取 pluginA 的导出值 + console.log(ctx.plugins.pluginA.x); // => 1 + }, + }; +} +pluginA.pluginName = 'pluginA'; +pluginB.pluginName = 'pluginB'; +pluginB.meta = { + dependencies: ['pluginA'], +} +await plugins.register(pluginA); +await plugins.register(pluginB); +``` +> 注:ctx 是在插件 creator 中获取引擎 API 的上下文,具体定义参见 [ILowCodePluginContext](#qEhTb) + +#### +#### 设置兼容引擎版本示例 +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +const builtinPluginRegistry = (ctx: ILowCodePluginContext) => { + return { + async init() { + ... + }, + }; +} +builtinPluginRegistry.pluginName = 'builtinPluginRegistry'; +builtinPluginRegistry.meta = { + engines: { + lowcodeEngine: '^1.0.0', // 插件需要配合 ^1.0.0 的引擎才可运行 + }, +} +await plugins.register(builtinPluginRegistry); +``` +#### 设置插件参数版本示例 +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +const builtinPluginRegistry = (ctx: ILowCodePluginContext, options: any) => { + return { + async init() { + // 1.0.4 之后的传值方式,通过 register(xxx, options) + // 取值通过 options + + // 1.0.4 之前的传值方式,通过 init(..., preference) + // 取值通过 ctx.preference.getValue() + }, + }; +} +builtinPluginRegistry.pluginName = 'builtinPluginRegistry'; +builtinPluginRegistry.meta = { + preferenceDeclaration: { + title: 'pluginA 的参数定义', + properties: [ + { + key: 'key1', + type: 'string', + description: 'this is description for key1', + }, + { + key: 'key2', + type: 'boolean', + description: 'this is description for key2', + }, + { + key: 'key3', + type: 'number', + description: 'this is description for key3', + }, + { + key: 'key4', + type: 'string', + description: 'this is description for key4', + }, + ], + }, +} + +// 从 1.0.4 开始,支持直接在 pluginCreator 的第二个参数 options 获取入参 +await plugins.register(builtinPluginRegistry, { key1: 'abc', key5: 'willNotPassToPlugin' }); + +// 1.0.4 之前,通过 preference 来传递 / 获取值 +const preference = new Map(); +preference.set('builtinPluginRegistry', { + key1: 'abc', + key5: 'willNotPassToPlugin', // 因为 key5 不在插件声明可接受的参数里 +}); + +init(document.getElementById('lce'), engineOptions, preference); + +``` + +### get +获取插件实例 + +**类型定义** +```typescript +function get(pluginName: string): ILowCodePlugin | undefined +``` +**调用示例** +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +plugins.get(builtinPluginRegistry); +``` +### +### getAll +获取所有插件实例 + +**类型定义** +```typescript +function getAll(): ILowCodePlugin[] +``` +**调用示例** +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +plugins.getAll(); +``` +### +### has +判断是否已经加载了指定插件 +**类型定义** +```typescript +function has(pluginName: string): boolean +``` +**调用示例** +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +plugins.has('builtinPluginRegistry'); +``` +### delete +删除指定插件 +**类型定义** +```typescript +async function delete(pluginName: string): Promise +``` +**调用示例** +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +plugins.delete('builtinPluginRegistry'); +``` +## +## 事件(events) +无 +## 相关模块 +### ILowCodePluginContext +**类型定义** +```typescript +export interface ILowCodePluginContext { + skeleton: Skeleton; // 参考面板 API + hotkey: Hotkey; // 参考快捷键 API + logger: Logger; // 参考日志 API + plugins: ILowCodePluginManager; // 参考插件 API + setters: Setters; // 参考设置器 API + config: EngineConfig; // 参考配置 API + material: Material; // 参考物料 API + event: Event; // 参考事件 API + project: Project; // 参考模型 API + preference: IPluginPreferenceMananger; +} +``` +### ILowCodePluginConfig +**类型定义** +```typescript +export interface ILowCodePluginConfig { + init?(): void; + destroy?(): void; + exports?(): any; +} +``` +## 插件元数据工程转化示例 +your-plugin/package.json +```json +{ + "name": "@alilc/lowcode-plugin-debug", + "lcMeta": { + "pluginName": "debug", + "meta": { + "engines": { + "lowcodeEgnine": "^1.0.0" + }, + "preferenceDeclaration": { ... } + } + } +} +``` +转换后的结构: +```json +const debug = (ctx: ILowCodePluginContext, options: any) => { + return {}; +} + +debug.pluginName = 'debug'; +debug.meta = { + engines: { + lowcodeEgnine: '^1.51.0', + }, + preferenceDeclaration: { ... } +}; +``` +### + +## 使用示例 + +更多示例参考:[https://github.com/alibaba/lowcode-demo/blob/058450edb584d92be6cb665b1f3a9646ba464ffa/src/universal/plugin.tsx#L36](https://github.com/alibaba/lowcode-demo/blob/058450edb584d92be6cb665b1f3a9646ba464ffa/src/universal/plugin.tsx#L36) diff --git a/docs/docs/api/project.md b/docs/docs/api/project.md new file mode 100644 index 000000000..269184b53 --- /dev/null +++ b/docs/docs/api/project.md @@ -0,0 +1,881 @@ +--- +title: project - 模型 API +sidebar_position: 3 +--- +# 模块简介 +引擎编排模块中包含多种模型,包括[项目模型(project)](#DADnF)、[文档模型(document-model)](#lp7xO)、[节点模型(node)](#m0cJS)、[节点孩子模型(node-children)](#W8seq)、[属性集模型(props)](#IJeRY)以及[属性模型(prop)](#w1diM)。 +他们的依赖关系如下图: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1645510146964-62f26151-e624-48f6-a422-dacdcb60dbea.png#averageHue=%23fefefe&clientId=ue969b413-090d-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=676&id=ucd07aeff&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1352&originWidth=1650&originalType=binary&ratio=1&rotation=0&showTitle=false&size=282048&status=error&style=none&taskId=u8ec0cad1-ed80-46f5-8b6b-b7278b4bb7d&title=&width=825) +在文档模型内部,又有一些引申模型,比如[历史操作(history)](#xvIKj)、[画布节点选中(selection)](#GtFkP)、[画布节点悬停(detecting)](#Tjt05)等。 + +整个模型系统,以项目模型为最顶层的模型,其他模型实例均需要通过 project 来获得,比如 project.currentDocument 来获取当前的文档模型,project.currentDocument.nodesMap 来获取当前文档模型里所有的节点列表。 + +# 项目模型(Project) +## 变量(variables) +### currentDocument + +获取当前的 document 实例 +### documents + +获取当前 project 下所有 documents +### simulatorHost + +获取模拟器的 host +## +## 方法签名(functions) +### openDocument + +openDocument(doc?: string | RootSchema | undefined) + +打开一个 document + +### createDocument + +createDocument(data?: RootSchema): DocumentModel | null + +创建一个 document +### +### removeDocument + +removeDocument(doc: DocumentModel) + +删除一个 document + +### getDocumentByFileName + +getDocumentByFileName(fileName: string): DocumentModel | null + +根据 fileName 获取 document + +### getDocumentById + +getDocumentById(id: string): DocumentModel | null + +根据 id 获取 document + +### exportSchema + +exportSchema() + +导出 project + +### importSchema + +importSchema(schema?: ProjectSchema) + +导入 project + +### getCurrentDocument + +getCurrentDocument(): DocumentModel | null + +获取当前的 document + +### addPropsTransducer + +addPropsTransducer(transducer: PropsTransducer, stage: TransformStage) + +增加一个属性的管道处理函数 + +**示例 1:在保存的时候删除每一个组件的 props.hidden** +```typescript +import { ILowCodePluginContext, project } from '@alilc/lowcode-engine'; +import { CompositeObject, TransformStage } from '@alilc/lowcode-types'; + +export const deleteHiddenTransducer = (ctx: ILowCodePluginContext) => { + return { + name: 'deleteHiddenTransducer', + async init() { + project.addPropsTransducer((props: CompositeObject): CompositeObject => { + delete props.hidden; + return props; + }, TransformStage.Save); + }, + }; +} + +deleteHiddenTransducer.pluginName = 'deleteHiddenTransducer'; +``` + +### onRemoveDocument +绑定删除文档事件 +```typescript +function onRemoveDocument(fn: (data: { docId: string}) => void) {} +``` +*引擎版本>=1.0.16 +### setI18n +设置多语言语料 +```typescript +function setI18n(value: object) {} +``` +*引擎版本>=1.0.17 +## 事件(events) +### onChangeDocument + +onChangeDocument(fn: (doc: DocumentModel) => void) + +当前 project 内的 document 变更事件 + +### onSimulatorHostReady + +onSimulatorHostReady(fn: (host: SimulatorHost) => void) + +当前 project 的模拟器 ready 事件 + +### onSimulatorRendererReady + +onSimulatorRendererReady(fn: () => void) + +当前 project 的渲染器 ready 事件 +# +# 文档模型(DocumentModel) +## 变量(variables) +### selection + +画布节点选中区模型实例,具体方法参见 [画布节点选中区模型](#GtFkP) + +### detecting + +画布节点 hover 区模型实例,具体方法参见 [画布节点 hover 区模型](#Tjt05) + +### history + +操作历史模型实例,具体方法参见 [操作历史模型](#xvIKj) +### canvas + +获取当前画布中的一些信息,比如拖拽时的 dropLocation + +### project + +获取当前文档模型所属的 project + +### root + +获取文档的根节点 + +### nodesMap + +获取文档下所有节点 + +### modalNodesManager + +参见 [模态节点管理](#nNRF9) + +## 方法签名(functions) +### getNodeById + +getNodeById(nodeId: string) + +根据 nodeId 返回 [Node](#m0cJS) 实例 + +### importSchema + +importSchema(schema: RootSchema) + +导入 schema +### exportSchema + +exportSchema(stage: TransformStage = TransformStage.Render) + +导出 schema + +### insertNode + +insertNode( + parent: Node, + thing: Node, + at?: number | null | undefined, + copy?: boolean | undefined, + ) + +插入节点 +### createNode + +createNode(data: any) + +创建一个节点 +### removeNode + +removeNode(idOrNode: string | Node) + +移除指定节点/节点id + +### checkNesting +检查拖拽放置的目标节点是否可以放置该拖拽对象 +*引擎版本 > 1.0.16 +```typescript +function checkNesting(dropTarget: Node, dragObject: DragNodeObject | DragNodeDataObject): boolean {} +``` +## +## 事件(events) +### onAddNode + +onAddNode(fn: (node: Node) => void) + +当前 document 新增节点事件 + +```typescript +import { project } from '@alilc/lowcode-engine'; + +project.currentDocument.onAddNode((node) => { + console.log('node', node); +}) +``` + +### onRemoveNode + +onRemoveNode(fn: (node: Node) => void) + +当前 document 删除节点事件 + +### onChangeDetecting + +onChangeDetecting(fn: (node: Node) => void) + +当前 document 的 hover 变更事件 + +### onChangeSelection + +onChangeSelection(fn: (ids: string[]) => void) + +当前 document 的选中变更事件 + +### onChangeNodeVisible + +onChangeNodeVisible(fn: (node: Node, visible: boolean) => void) + +当前 document 的节点显隐状态变更事件 + +### onChangeNodeChildren + +onChangeNodeChildren(fn: (info?: IOnChangeOptions) => void) + +当前 document 的节点 children 变更事件 + +### onChangeNodeProp +当前 document 节点属性修改事件 +```typescript +onChangeNodeProp(fn: (info: PropChangeOptions) => void) +``` + +### onImportSchema +当前 document 导入新的 schema 事件 +版本 >= 1.0.15 +```typescript +onImportSchema(fn: (schema: any) => void) +``` + +# 画布节点选中模型(Selection) +## 变量(variables) +### selected + +返回选中的节点 id + +## 方法签名(functions) +### select + +select(id: string) + +选中指定节点(覆盖方式) + +### selectAll + +selectAll(ids: string[]) + +批量选中指定节点们 + +### remove + +remove(id: string) + +**取消选中**选中的指定节点,不会删除组件 + +### clear + +clear() + +**取消选中**所有选中节点,不会删除组件 + +### has + +has(id: string) + +判断是否选中了指定节点 + +### add + +add(id: string) + +选中指定节点(增量方式) + +### getNodes + +getNodes() + +获取选中的节点实例 + +### getTopNodes +获取选区的顶层节点 +例如选中的节点为: + +- DivA + - ChildrenA +- DivB + +getNodes 返回的是 [DivA、ChildrenA、DivB],getTopNodes 返回的是 [DivA、DivB],其中 ChildrenA 由于是二层节点,getTopNodes 不会返回 + +*引擎版本 >= 1.0.16 + + +# 画布节点悬停模型(Detecting) +## 方法签名(functions) +### capture + +capture(id: string) + +hover 指定节点 + +### release + +release(id: string) + +hover 离开指定节点 + +### leave + +leave() + +清空 hover 态 + +### current +当前 hover 的节点 +*引擎版本 >= 1.0.16 + +# 操作历史记录模型(History) +## 方法签名(functions) +### go + +go(cursor: number) + +历史记录跳转到指定位置 + +### back + +back() + +历史记录后退 + +### forward + +forward() + +历史记录前进 +### savePoint + +savePoint() + +保存当前状态 +### isSavePoint + +isSavePoint() + +当前是否是「保存点」,即是否有状态变更但未保存 + +### getState + +getState() + +获取 state,判断当前是否为「可回退」、「可前进」的状态 + +## 事件(events) +### onChangeState + +onChangeState(func: () => any) + +监听 state 变更事件 + +### onChangeCursor + +onChangeCursor(func: () => any) + +监听历史记录游标位置变更事件 + +# 节点模型(Node) +## 变量(variables) +### id + +节点 id + +### title + +节点标题 + +### isContainer + +是否为「容器型」节点 + +### isRoot + +是否为根节点 + +### isEmpty + +是否为空节点(无 children 或者 children 为空) + +### isPage + +是否为 Page 节点 + +### isComponent + +是否为 Component 节点 + +### isModal + +是否为「模态框」节点 + +### isSlot + +是否为插槽节点 + +### isParental + +是否为父类/分支节点 + +### isLeaf +是否为叶子节点 + +### isLocked +获取当前节点的锁定状态 +*引擎版本>=1.0.16 + +### isRGLContainer +设置为磁贴布局节点,使用方式可参考:[磁贴布局在钉钉宜搭报表设计引擎中的实现](https://mp.weixin.qq.com/s/PSTut5ahAB8nlJ9kBpBaxw) +*引擎版本>=1.0.16 + +### index + +下标 + +### icon + +图标 + +### zLevel + +节点所在树的层级深度,根节点深度为 0 + +### componentName + +节点 componentName + +### componentMeta + +节点的物料元数据,参见 物料元数据 + +### document + +获取节点所属的[文档模型](#lp7xO)对象 + +### prevSibling + +获取当前节点的前一个兄弟节点 + +### nextSibling + +获取当前节点的后一个兄弟节点 + +### parent + +获取当前节点的父亲节点 + +### children + +获取当前节点的孩子节点模型 + +### slots + +节点上挂载的插槽节点们 + +### slotFor + +当前节点为插槽节点时,返回节点对应的属性实例 + +### props + +返回节点的属性集 + +### propsData + +返回节点的属性集值 + +## 方法签名(functions) +### getDOMNode + +getDOMNode() + +获取节点实例对应的 dom 节点 + +### getRect + +getRect() + +返回节点的尺寸、位置信息 + +### hasSlots + +hasSlots() + +是否有挂载插槽节点 + +### hasCondition + +hasCondition() + +是否设定了渲染条件 + +### hasLoop + +hasLoop() + +是否设定了循环数据 + +### getProp + +getProp(path: string): Prop | null + +获取指定 path 的属性模型实例 + +### getPropValue + +getPropValue(path: string) + +获取指定 path 的属性模型实例值 + +### getExtraProp + +getExtraProp(path: string): Prop | null + +获取指定 path 的属性模型实例,注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级 + +### getExtraPropValue + +getExtraPropValue(path: string) + +获取指定 path 的属性模型实例,注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级 + +### setPropValue + +setPropValue(path: string, value: CompositeValue) + +设置指定 path 的属性模型实例值 + +### setExtraPropValue + +setExtraPropValue(path: string, value: CompositeValue) + +设置指定 path 的属性模型实例值 + +### importSchema + +importSchema(data: NodeSchema) + +导入节点数据 + +### exportSchema + +exportSchema(stage: TransformStage = TransformStage.Render, options?: any) + +导出节点数据 + +### insertBefore + +insertBefore(node: Node, ref?: Node | undefined, useMutator?: boolean) + +在指定位置之前插入一个节点 + +### insertAfter + +insertAfter(node: Node, ref?: Node | undefined, useMutator?: boolean) + +在指定位置之后插入一个节点 + +### replaceChild + +replaceChild(node: Node, data: any) + +替换指定节点 + +### replaceWith + +replaceWith(schema: NodeSchema) + +将当前节点替换成指定节点描述 + +### select + +select() + +选中当前节点实例 + +### hover + +hover(flag = true) + +设置悬停态 + +### lock +设置节点锁定状态 +```typescript +function lock(flag?: boolean){} +``` +*引擎版本>=1.0.16 + +### remove + +remove() + +删除当前节点实例 + +# 节点孩子模型(NodeChildren) +## 变量(variables) +### owner + +返回当前 children 实例所属的节点实例 + +### size + +children 内的节点实例数 + +### isEmpty + +是否为空 + +## 方法签名(functions) +### delete + +delete(node: Node) + +删除指定节点 + +### insert + +insert(node: Node, at?: number | null) + +插入一个节点 + +### indexOf + +indexOf(node: Node) + +返回指定节点的下标 + +### splice + +splice(start: number, deleteCount: number, node?: Node) + +类似数组 splice 操作 + +### get + +get(index: number) + +返回指定下标的节点 + +### has + +has(node: Node) + +是否包含指定节点 + +### forEach + +forEach(fn: (node: Node, index: number) => void) + +类似数组的 forEach + +### map + +map(fn: (node: Node, index: number) => T[]) + +类似数组的 map + +### every + +every(fn: (node: Node, index: number) => boolean) + +类似数组的 every + +### some + +some(fn: (node: Node, index: number) => boolean) + +类似数组的 some + +### filter + +filter(fn: (node: Node, index: number) => boolean) + +类似数组的 filter + +### find + +find(fn: (node: Node, index: number) => boolean) + +类似数组的 find + +### reduce + +reduce(fn: (acc: any, cur: Node) => any, initialValue: any) + +类似数组的 reduce + +### importSchema + +importSchema(data?: NodeData | NodeData[]) + +导入 schema + +### exportSchema + +exportSchema(stage: TransformStage = TransformStage.Render) + +导出 schema + +### mergeChildren + +mergeChildren( + remover: (node: Node, idx: number) => boolean, + adder: (children: Node[]) => any, + sorter: (firstNode: Node, secondNode: Node) => number, + ) + +执行新增、删除、排序等操作 + +# 属性集模型(Props) +## 变量(variables) +### id + +id +### path + +返回当前 props 的路径 +### node + +返回当前属性集所属的节点实例 + +## 方法签名(functions) +### getProp + +getProp(path: string): Prop | null + +获取指定 path 的属性模型实例 + +### getPropValue + +getPropValue(path: string) + +获取指定 path 的属性模型实例值 + +### getExtraProp + +getExtraProp(path: string): Prop | null + +获取指定 path 的属性模型实例,注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级 +### getExtraPropValue + +getExtraPropValue(path: string) + +获取指定 path 的属性模型实例值,注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级 +### + +### setPropValue + +setPropValue(path: string, value: CompositeValue) + +设置指定 path 的属性模型实例值 + +### setExtraPropValue + +setExtraPropValue(path: string, value: CompositeValue) + +设置指定 path 的属性模型实例值 + +# 属性模型(Prop) +## 变量(variables) +### id + +id + +### key + +key 值 + +### path + +返回当前 prop 的路径 + +### node + +返回所属的节点实例 + +## 方法签名(functions) +### setValue + +setValue(val: CompositeValue) + +设置值 + +### getValue + +getValue() + +获取值 + +### remove +移除值 +*引擎版本>=1.0.16 + +### exportSchema + +exportSchema(stage: TransformStage = TransformStage.Render) + +导出值 + +# 模态节点管理模型(ModalNodesManager) +## 方法签名(functions) +### getModalNodes + +getModalNodes() + +获取模态节点(们) + +### getVisibleModalNode + +getVisibleModalNode() + +获取当前可见的模态节点 + +### hideModalNodes + +hideModalNodes() + +隐藏模态节点(们) + +### setVisible + +setVisible(node: Node) + +设置指定节点为可见态 + +### setInvisible + +setInvisible(node: Node) + +设置指定节点为不可见态 + +### setNodes + +setNodes() + +设置模态节点,触发内部事件 diff --git a/docs/docs/api/setters.md b/docs/docs/api/setters.md new file mode 100644 index 000000000..ac45831be --- /dev/null +++ b/docs/docs/api/setters.md @@ -0,0 +1,346 @@ +--- +title: setters - 设置器 API +sidebar_position: 6 +--- +## 模块简介 +负责注册设置器、管理设置器的 API。注册自定义设置器之后可以在物料中进行使用。 + +## 方法签名(functions) +### getSetter +获取指定 setter + +**类型定义** +```typescript +function getSetter(type: string): RegisteredSetter; +``` + +### getSettersMap +获取已注册的所有 settersMap + +**类型定义** +```typescript +function getSettersMap(): Map +``` + +### registerSetter +注册一个 setter + +**类型定义** +```typescript +function registerSetter( + typeOrMaps: string | { [key: string]: CustomView | RegisteredSetter }, + setter?: CustomView | RegisteredSetter | undefined, +): void; +``` + +## 使用示例 +### 注册官方内置 Setter 到设计器中 +```typescript +import { setters, skeleton } from '@alilc/lowcode-engine'; +import { setterMap, pluginMap } from '@alilc/lowcode-engine-ext'; + +const setterRegistry = (ctx: ILowCodePluginContext) => { + return { + name: 'ext-setters-registry', + async init() { + // 注册 setterMap + setters.registerSetter(setterMap); + // 注册插件 + // 注册事件绑定面板 + skeleton.add({ + area: 'centerArea', + type: 'Widget', + content: pluginMap.EventBindDialog, + name: 'eventBindDialog', + props: {}, + }); + + // 注册变量绑定面板 + skeleton.add({ + area: 'centerArea', + type: 'Widget', + content: pluginMap.VariableBindDialog, + name: 'variableBindDialog', + props: {}, + }); + }, + }; +} + +setterRegistry.pluginName = 'setterRegistry'; +await plugins.register(setterRegistry); +``` + +### 开发自定义 Setter +AltStringSetter 代码如下: +```typescript +import * as React from "react"; +import { Input } from "@alifd/next"; + +import "./index.scss"; +interface AltStringSetterProps { + // 当前值 + value: string; + // 默认值 + initialValue: string; + // setter唯一输出 + onChange: (val: string) => void; + // AltStringSetter 特殊配置 + placeholder: string; +} +export default class AltStringSetter extends React.PureComponent { + componentDidMount() { + const { onChange, value, defaultValue } = this.props; + if (value == undefined && defaultValue) { + onChange(defaultValue); + } + } + + // 声明 Setter 的 title + static displayName = 'AltStringSetter'; + + render() { + const { onChange, value, placeholder } = this.props; + return ( + onChange(val)} + > + ); + } +} +``` +开发完毕之后,注册 AltStringSetter 到设计器中: +```typescript +import AltStringSetter from './AltStringSetter'; +const registerSetter = window.AliLowCodeEngine.setters.registerSetter; +registerSetter('AltStringSetter', AltStringSetter); +``` +注册之后,我们就可以在物料中使用了,其中核心配置如下: +```typescript +{ + "props": { + "isExtends": true, + "override": [ + { + "name": "type", + "setter": "AltStringSetter" + } + ] + } +} +``` +完整配置如下: +```typescript +{ + "componentName": "Message", + "title": "Message", + "props": [ + { + "name": "title", + "propType": "string", + "description": "标题", + "defaultValue": "标题" + }, + { + "name": "type", + "propType": { + "type": "oneOf", + "value": [ + "success", + "warning", + "error", + "notice", + "help", + "loading" + ] + }, + "description": "反馈类型", + "defaultValue": "success" + } + ], + "configure": { + "props": { + "isExtends": true, + "override": [ + { + "name": "type", + "setter": "AltStringSetter" + } + ] + } + } +} +``` +## 模块简介 +负责注册设置器、管理设置器的 API。注册自定义设置器之后可以在物料中进行使用。 + +## 方法签名(functions) +### getSetter +获取指定 setter + +**类型定义** +```typescript +function getSetter(type: string): RegisteredSetter; +``` + +### getSettersMap +获取已注册的所有 settersMap + +**类型定义** +```typescript +function getSettersMap(): Map +``` + +### registerSetter +注册一个 setter + +**类型定义** +```typescript +function registerSetter( + typeOrMaps: string | { [key: string]: CustomView | RegisteredSetter }, + setter?: CustomView | RegisteredSetter | undefined, +): void; +``` + +## 使用示例 +### 注册官方内置 Setter 到设计器中 +```typescript +import { setters, skeleton } from '@alilc/lowcode-engine'; +import { setterMap, pluginMap } from '@alilc/lowcode-engine-ext'; + +const setterRegistry = (ctx: ILowCodePluginContext) => { + return { + name: 'ext-setters-registry', + async init() { + // 注册 setterMap + setters.registerSetter(setterMap); + // 注册插件 + // 注册事件绑定面板 + skeleton.add({ + area: 'centerArea', + type: 'Widget', + content: pluginMap.EventBindDialog, + name: 'eventBindDialog', + props: {}, + }); + + // 注册变量绑定面板 + skeleton.add({ + area: 'centerArea', + type: 'Widget', + content: pluginMap.VariableBindDialog, + name: 'variableBindDialog', + props: {}, + }); + }, + }; +} + +setterRegistry.pluginName = 'setterRegistry'; +await plugins.register(setterRegistry); +``` + +### 开发自定义 Setter +AltStringSetter 代码如下: +```typescript +import * as React from "react"; +import { Input } from "@alifd/next"; + +import "./index.scss"; +interface AltStringSetterProps { + // 当前值 + value: string; + // 默认值 + initialValue: string; + // setter唯一输出 + onChange: (val: string) => void; + // AltStringSetter 特殊配置 + placeholder: string; +} +export default class AltStringSetter extends React.PureComponent { + componentDidMount() { + const { onChange, value, defaultValue } = this.props; + if (value == undefined && defaultValue) { + onChange(defaultValue); + } + } + + // 声明 Setter 的 title + static displayName = 'AltStringSetter'; + + render() { + const { onChange, value, placeholder } = this.props; + return ( + onChange(val)} + > + ); + } +} +``` +开发完毕之后,注册 AltStringSetter 到设计器中: +```typescript +import AltStringSetter from './AltStringSetter'; +const registerSetter = window.AliLowCodeEngine.setters.registerSetter; +registerSetter('AltStringSetter', AltStringSetter); +``` +注册之后,我们就可以在物料中使用了,其中核心配置如下: +```typescript +{ + "props": { + "isExtends": true, + "override": [ + { + "name": "type", + "setter": "AltStringSetter" + } + ] + } +} +``` +完整配置如下: +```typescript +{ + "componentName": "Message", + "title": "Message", + "props": [ + { + "name": "title", + "propType": "string", + "description": "标题", + "defaultValue": "标题" + }, + { + "name": "type", + "propType": { + "type": "oneOf", + "value": [ + "success", + "warning", + "error", + "notice", + "help", + "loading" + ] + }, + "description": "反馈类型", + "defaultValue": "success" + } + ], + "configure": { + "props": { + "isExtends": true, + "override": [ + { + "name": "type", + "setter": "AltStringSetter" + } + ] + } + } +} +``` diff --git a/docs/docs/api/simulatorHost.md b/docs/docs/api/simulatorHost.md new file mode 100644 index 000000000..fc0009d3a --- /dev/null +++ b/docs/docs/api/simulatorHost.md @@ -0,0 +1,31 @@ +--- +title: simulatorHost - 模拟器 API +sidebar_position: 3 +--- +# 模块简介 +负责模拟器相关的 API,包括画布尺寸、语言等。 + +# 方法(functions) +## set +设置若干用于画布渲染的变量,比如画布大小、locale 等。 + +以设置画布大小为例: +目前支持 3 种定制方式: +```typescript + +// 直接使用内置设备类型 +project.simulatorHost.set('device', 'mobile' / 'iphonex' / 'iphone6' / 'default'); +// 定制 canvas 的样式类 +project.simulatorHost.set('deviceClassName', 'my-canvas-class'); +// 最灵活的方式,直接设置 canvas / viewport 的样式(canvas 是外框,viewport 是内框,可以在 canvas 设置手机 / 平板背景图) +project.simulatorHost.set('deviceStyle', { canvas: { width: '300px', backgroundColor: 'red' }, viewport: { width: '280px' } }); +``` + +## get +获取模拟器中设置的变量,比如画布大小、locale 等。 +```typescript +project.simulatorHost.get('device'); +``` + +## rerender +刷新渲染画布 diff --git a/docs/docs/api/skeleton.md b/docs/docs/api/skeleton.md new file mode 100644 index 000000000..80e3220f6 --- /dev/null +++ b/docs/docs/api/skeleton.md @@ -0,0 +1,278 @@ +--- +title: skeleton - 面板 API +sidebar_position: 1 +--- +## 模块简介 +面板 API 提供了面板扩展和管理的能力,如下图蓝色内容都是扩展出来的。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1645442085447-d1822e7f-9e5a-4e06-a770-04b1023d5daf.png#clientId=u9aca70b6-1a98-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=498&id=u2dd3deb2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=996&originWidth=1780&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1549904&status=done&style=none&taskId=u28659b69-981c-416e-bed6-b2f06b8e6fc&title=&width=890)
页面上可以扩展的区域共 5 个,具体如下:
![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1645431386085-2710d33d-0652-450a-a993-c804368da1ce.png#clientId=u1724eb73-4c0c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=487&id=ud138f866&margin=%5Bobject%20Object%5D&name=image.png&originHeight=974&originWidth=1892&originalType=binary&ratio=1&rotation=0&showTitle=false&size=228235&status=done&style=none&taskId=u265d50a5-3700-406e-84b2-0158ebadaae&title=&width=946) +### 基本概念 +#### 扩展区域位置(area) +##### topArea +展示在设计器的顶部区域,常见的相关区域的插件主要是:
1)注册设计器 Logo;
2)设计器操作回退和撤销按钮;
3)全局操作按钮,例如:保存、预览等; +##### leftArea +左侧区域的展示形式大多数是 Icon 和对应的面板,通过点击 Icon 可以展示对应的面板并隐藏其他的面板。
该区域相关插件的主要有:
1)大纲树展示,展示该设计器设计页面的大纲。
2)组件库,展示注册到设计器中的组件,点击之后,可以从组件库面板中拖拽到设计器的画布中。
3)数据源面板
4)JS 等代码面板。
可以发现,这个区域的面板大多数操作时是不需要同时并存的,且交互比较复杂的,需要一个更整块的区域来进行操作。 +##### centerArea +画布区域,由于画布大多数是展示作用,所以一般扩展的种类比较少。常见的扩展有:
1)画布大小修改
2)物料选中扩展区域修改 +##### rightArea +右侧区域,常用于组件的配置。常见的扩展有:统一处理组件的配置项,例如统一删除某一个配置项,统一添加某一个配置项的。 +##### toolbar +跟 topArea 类似,按需放置面板插件~ +#### 展示类型(type) +展示类型用于区分插件在设计器内可操作的几种不同界面类型。主要的几种类型为PanelDock、Widget、Dock,另有Panel类型目前不推荐使用。 +##### PanelDock +PanelDock 是以面板的形式展示在设计器的左侧区域的。其中主要有两个部分组成,一个是图标,一个是面板。当点击图标时可以控制面板的显示和隐藏。
下图是组件库插件的展示效果。
![Feb-08-2022 19-44-15.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644320663827-ee9c54a1-f684-40e2-8a6b-875103d04b31.gif#clientId=u221f0bd4-c19e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=555&id=u5292d9cc&margin=%5Bobject%20Object%5D&name=Feb-08-2022%2019-44-15.gif&originHeight=790&originWidth=1536&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1381641&status=done&style=stroke&taskId=ub28a13a4-3d80-4a02-bcaa-cc9d6127243&title=&width=1080)
其中右上角可以进行固定,可以对弹出的宽度做设定
接入可以参考代码 +```javascript +import { skeleton } from "@alilc/lowcode-engine"; + +skeleton.add({ + area: "leftArea", // 插件区域 + type: "PanelDock", // 插件类型,弹出面板 + name: "sourceEditor", + content: SourceEditor, // 插件组件实例 + props: { + align: "left", + icon: "wenjian", + description: "JS面板", + }, + panelProps: { + floatable: true, // 是否可浮动 + height: 300, + hideTitleBar: false, + maxHeight: 800, + maxWidth: 1200, + title: "JS面板", + width: 600, + }, +}); +``` +##### Widget +Widget 形式是直接渲染在当前编辑器的对应位置上。如 demo 中在设计器顶部的所有组件都是这种展现形式。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644320068765-47efc836-30c2-452f-8104-b98b1ea3533d.png#clientId=u221f0bd4-c19e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=51&id=P60UE&margin=%5Bobject%20Object%5D&name=image.png&originHeight=94&originWidth=1988&originalType=binary&ratio=1&rotation=0&showTitle=false&size=58410&status=done&style=stroke&taskId=u4eadd643-2e63-4be7-8736-b27b9c82b81&title=&width=1080)
接入可以参考代码: +```javascript +import {skeleton} from "@alilc/lowcode-engine"; +// 注册 logo 面板 +skeleton.add({ + area: "topArea", + type: "Widget", + name: "logo", + content: Logo, // Widget 组件实例 + contentProps: { // Widget 插件props + logo: + "https://img.alicdn.com/tfs/TB1_SocGkT2gK0jSZFkXXcIQFXa-66-66.png", + href: "/", + }, + props: { + align: "left", + width: 100, + }, +}); +``` +##### Dock +一个图标的表现形式,可以用于语言切换、跳转到外部链接、打开一个 widget 等场景 +```javascript +import { skeleton } from "@alilc/lowcode-engine"; + +skeleton.add({ + area: "leftArea", + type: "Dock", + name: "opener", + content: Opener, // Widget 组件实例 + contentProps: { // Widget 插件props + xxx: "1", + }, + props: { + align: "bottom", + }, + onClick: function() { + // 打开外部链接 + window.open('https://lowcode-engine.cn'); + // 显示 widget + skeleton.showWidget('xxx'); + } +}); +``` +####
+## 变量(variables) +无 +## 方法签名(functions) +### 1. add +```tsx +add(config: IWidgetBaseConfig & { + area?: string; +}, extraConfig?: object): IWidget | Panel; +``` + +往指定扩展区加入一块面板 + +IWidgetBaseConfig 定义如下: + +| 属性名 | 含义 | 备注 | +| --- | --- | --- | +| name | 面板名称 | | +| area | 扩展区位置,可选值:'topArea' | 'leftArea' | 'rightArea' | 'toolbar' | 'bottomArea' | 'mainArea' | | +| type | 面板类型,可选值:'Widget' | 'PanelDock' | 'Panel' | 详见前文中对**展示类型**的描述 | +| content | 面板的实现类/节点,类型是 ReactClass | ReactElement | | +| props | 面板属性 | align: 'top' | 'bottom' | 'left' | 'center' | 'right'; // 指定面板 icon 位置区域
icon: string | ReactElement;  // icon 为字符串时,请确定当前 fusion 主题包中包含该 icon
description: string;
condition: Function; // 指定当前面板的显影状态 | +| contentProps | 面板的实现类/节点的参数 | | +| panelProps | 假如 type: 'Panel' | 'PanelDock' 时有效,传给 Panel 类的参数 | keepVisibleWhileDragging: boolean; // 当有元素在当前 panel 拖拽时,是否保持 panel 为展开状态,默认值:false
area: 'leftFloatArea' | 'leftFixedArea' // 指定 panel 位于浮动面板还是钉住面板 | +| index | 面板的位置,不传默认按插件注册顺序 | | + + + + +### 2. remove + +remove(config: IWidgetBaseConfig) + +移除一个面板实例 + +### 3. showPanel + +showPanel(name: string) + +展示指定 Panel 实例 + +### 4. hidePanel + +hidePanel(name: string) + +### 5. showWidget + +showWidget(name: string) + +展示指定 Widget 实例 + +### 6. hideWidget + +hideWidget(name: string) + +隐藏指定 widget 实例。 + +### 7. enableWidget + +enableWidget(name: string) + +将 widget 启用。
注:该函数将会触发全局事件 'skeleton.widget.enable' + +### 8. disableWidget + +disableWidget(name: string) + +将 widget 禁用掉,禁用后,所有鼠标事件都会被禁止掉。
适用场景:在该面板还在进行初始化构造时,可以先禁止掉,防止用户点击报错,待初始化完成,重新启用。 + +## 事件(events) +### 1. onShowPanel + +onShowPanel(listener: (...args: unknown[]) => void) + +监听 Panel 实例显示事件 + +### 2. onHidePanel +
onHidePanel(listener: (...args: unknown[]) => void) + +监听 Panel 实例隐藏事件 + +### 3. onShowWidget + +onShowWidget(listener: (...args: unknown[]) => void) + +监听 Widget 实例显示事件 + +### 4. onHideWidget +
onHideWidget(listener: (...args: unknown[]) => void) + +监听 Widget 实例隐藏事件 +## 使用示例 +```typescript +import { skeleton } from '@alilc/lowcode-engine'; + +skeleton.add({ + name: 'logo', + area: 'topArea', + type: 'Widget', + contentProps: {}, + content: LogoContent, +}); + +skeleton.add({ + name: 'sourceEditor', + type: 'PanelDock', + area: 'leftArea', + props: { + align: 'top', + icon: 'wenjian', + description: 'JS面板', + }, + panelProps: { + floatable: true, + height: 300, + help: undefined, + hideTitleBar: false, + maxHeight: 800, + maxWidth: 1200, + title: 'JS面板', + width: 600, + }, + content: SourceEditor, +}); + +// 显隐 panel +skeleton.showPanel('sourceEditor'); +skeleton.hidePanel('sourceEditor'); + + +// 创建一个浮动的 widget +skeleton.add({ + name: 'floatingWidget', + type: 'Widget', + area: 'mainArea', + props: {}, + content: React.createElement('div', {}, 'haha'), + contentProps: { + style: { + position: 'fixed', + top: '200px', + bottom: 0, + width: 'calc(100% - 46px)', + 'background-color': 'lightblue' + } + } +}); + +// 显隐 widget +skeleton.showWidget('floatingWidget'); +skeleton.hideWidget('floatingWidget'); + +// 控制 widget 的可点击态 +skeleton.enableWidget('sourceEditor'); +skeleton.disableWidget('sourceEditor'); +``` +### bottomArea 示例 +```typescript +import { skeleton } from '@alilc/lowcode-engine'; + +skeleton.add({ + name: 'bottomAreaPanelName', + area: 'bottomArea', + type: 'Panel', + content: () => 'demoText', +}); + + +skeleton.showPanel('bottomAreaPanelName'); +``` +### widget 示例 +```typescript +// 注册 logo 面板 +skeleton.add({ + area: 'topArea', + type: 'Widget', + name: 'logo', + content: Logo, + contentProps: { + logo: 'https://img.alicdn.com/imgextra/i4/O1CN013w2bmQ25WAIha4Hx9_!!6000000007533-55-tps-137-26.svg', + href: 'https://lowcode-engine.cn', + }, + props: { + align: 'left', + }, +}); +``` diff --git a/docs/docs/article/index.md b/docs/docs/article/index.md new file mode 100644 index 000000000..d0a65524c --- /dev/null +++ b/docs/docs/article/index.md @@ -0,0 +1,38 @@ +--- +title: 低代码引擎相关文章资料 +--- + +## 官方文章 + +- [低代码引擎半岁啦,来跟大家唠唠嗑...](https://segmentfault.com/a/1190000042884409) +- [低代码技术在研发团队的应用模式探讨](https://mp.weixin.qq.com/s/Ynk_wjJbmNw7fEG6UtGZbQ) +- [关于 LowCode&ProCode 混合研发的思考](https://mp.weixin.qq.com/s/TY3VXjkSmsQoT47xma3wig) +- [低代码渲染那些事](https://mp.weixin.qq.com/s/yqYey76qLGYPfDtpGkVFfA) +- [阿里低代码引擎和生态建设实战及思考](https://mp.weixin.qq.com/s/MI6MrUKKydtnSdO4xq6jwA) +- [磁贴布局在钉钉宜搭报表设计引擎中的实现](https://mp.weixin.qq.com/s/PSTut5ahAB8nlJ9kBpBaxw) +- [2B 领域下的低代码探索之路](https://mp.weixin.qq.com/s/HAxrMHLT43dPH488RiEIdw) +- [阿里低代码引擎 LowCodeEngine 正式开源!](https://mp.weixin.qq.com/s/T66LghtWLz2Oh048XqaniA) + +## Portal设计项目实战 +#### 直播回放 +[https://www.bilibili.com/video/BV1AS4y1K7DP/](https://www.bilibili.com/video/BV1AS4y1K7DP/) + +#### 示例项目 +- 前端: [https://github.com/mark-ck/lowcode-portal](https://github.com/mark-ck/lowcode-portal) +- 后端: [https://github.com/mark-ck/document-solution-site](https://github.com/mark-ck/document-solution-site) +- 组件库:[https://github.com/mark-ck/portal-components](https://github.com/mark-ck/portal-components) + +**注意** +1. 前端项目要把代码里请求接口的域名改成本地或者自己的域名; +2. 后端项目要把 config.default.js 里的 yuque 和 oss 配置补全; + +#### 视频链接 +- [阿里低代码引擎项目实战(1)-引擎 demo 部署到 faas 服务](https://www.bilibili.com/video/BV1B44y1P7GM/) +- [【有翻车】阿里低代码引擎项目实战(2)-保存页面到远端存储](https://www.bilibili.com/video/BV1AS4y1K7DP/) +- [阿里巴巴低代码引擎项目实战(3)-自定义组件接入](https://www.bilibili.com/video/BV1dZ4y1m76S/) +- [阿里低代码引擎项目实战(4)-自定义插件-页面管理](https://www.bilibili.com/video/BV17a411i73f/) +- [阿里低代码引擎项目实战(4)-用户登录](https://www.bilibili.com/video/BV1Wu411e7EQ/) +- [【有翻车】阿里低代码引擎项目实战(5)-表单回显](https://www.bilibili.com/video/BV1UY4y1v7D7/) +- [阿里低代码引擎项目实战(6)-自定义插件-页面管理-后端](https://www.bilibili.com/video/BV1uZ4y1U7Ly/) +- [阿里低代码引擎项目实战(6)-自定义插件-页面管理-前端](https://www.bilibili.com/video/BV1Yq4y1a74P/) +- [阿里低代码引擎项目实战(7)-自定义插件-页面管理(完结)](https://www.bilibili.com/video/BV13Y4y1e7EV/) \ No newline at end of file diff --git a/docs/docs/demoUsage/advanced/_category_.json b/docs/docs/demoUsage/advanced/_category_.json new file mode 100644 index 000000000..b83dbf807 --- /dev/null +++ b/docs/docs/demoUsage/advanced/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "进阶功能", + "position": 3, + "collapsed": false, + "collapsible": false +} diff --git a/docs/docs/demoUsage/advanced/hotkey.md b/docs/docs/demoUsage/advanced/hotkey.md new file mode 100644 index 000000000..a35183f29 --- /dev/null +++ b/docs/docs/demoUsage/advanced/hotkey.md @@ -0,0 +1,24 @@ +--- +title: 8. 编辑器快捷键 +sidebar_position: 0 +--- +- 任意时机: + - `⌘` `S` 保存 + - `⌘` `P` 预览 + - `⌘` `D` 查看 Diff + - `⌘` `Z` 撤销 + - `⇧` `⌘` `Z` 重做 +- 选择任意组件后: + - `Backspace` 删除组件 + - `⌘` `C` 复制组件 + - `⌘` `V` 粘贴组件 + - `⌘` `X` 剪切组件 + - `⌥` `↑` 向外层移动组件 + - `⌥` `↓` 向内层移动组件 + - `⌥` `←` 同级向上移动组件 + - `⌥` `→` 同级向下移动组件 + - `↑` 向上选择组件 + - `↓` 向下选择组件 + - `←` 向左选择组件 + - `→` 向右选择组件 + - `Escape` 取消选择组件 diff --git a/docs/docs/demoUsage/appendix/_category_.json b/docs/docs/demoUsage/appendix/_category_.json new file mode 100644 index 000000000..2021a2aad --- /dev/null +++ b/docs/docs/demoUsage/appendix/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "常见问题", + "position": 4, + "collapsed": false, + "collapsible": false +} diff --git a/docs/docs/demoUsage/appendix/loop.md b/docs/docs/demoUsage/appendix/loop.md new file mode 100644 index 000000000..0afb5c931 --- /dev/null +++ b/docs/docs/demoUsage/appendix/loop.md @@ -0,0 +1,17 @@ +--- +title: 如何使用循环值 +sidebar_position: 0 +--- +1.设置循环数据 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1655975447215-026bd3ae-ae2a-4f90-805e-df0d5c4bb7d2.png#clientId=ubd100ffc-952a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=950&id=u6413eee5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1900&originWidth=3840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=339030&status=done&style=none&taskId=ued46d732-83a2-441f-a80f-23061587689&title=&width=1920) + +2.给需要的变量绑定 this.item +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1655975499246-f9d14ef4-6736-46a5-8b24-8eedd4477617.png#clientId=ubd100ffc-952a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=946&id=u0b50f02a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1892&originWidth=3840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=451804&status=done&style=none&taskId=uf4916102-2e3d-4277-ac81-604c6761615&title=&width=1920) + +绑定之后的效果如下: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1655975540038-ccf3aabc-3f7c-4e33-a701-a9b005b1cf25.png#clientId=uc887596b-8aed-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=942&id=u32901b3a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1884&originWidth=3840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=333998&status=done&style=none&taskId=u2853d459-4432-4d0a-ba12-494e79e892a&title=&width=1920) + +其中 this.item 的 item 是可以配置的。配置不同的 key 可以方便在多层循环中使用不同层级的循环 item 值。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1655975569197-33d90389-7394-4e65-bc6a-582b7ceb9fee.png#clientId=uc887596b-8aed-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=948&id=u6e6741d2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1896&originWidth=3840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=311961&status=done&style=none&taskId=u14bbcfbb-e7cf-4307-a58d-3cb58afe8f7&title=&width=1920) + +this.index 是当前循环的索引值。 diff --git a/docs/docs/demoUsage/intro.md b/docs/docs/demoUsage/intro.md new file mode 100644 index 000000000..5154f0ea7 --- /dev/null +++ b/docs/docs/demoUsage/intro.md @@ -0,0 +1,80 @@ +--- +title: 1. 试用低代码引擎 Demo +sidebar_position: 0 +--- +低代码编辑器中的区块主要包含这些功能点: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644562161350-50ae7ccd-2e6f-4f50-af56-30e5cc5624dc.png#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=892&id=udd8e7731&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1784&originWidth=3384&originalType=binary&ratio=1&rotation=0&showTitle=false&size=509888&status=done&style=none&taskId=u1621cea1-8e9d-48d0-9273-bf852ef8e82&title=&width=1692) + +## 分区块功能介绍 +### 左侧:面板与操作区 +#### 物料面板 +可以查找组件,并在此拖动组件到编辑器画布中 +![Dec-17-2021 19-12-46.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562213143-49b9aff8-b538-43f4-a66d-53fac98ce7ae.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u3a98c25c&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-12-46.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u265abeb3-a0b1-4cdf-9291-c5fa865d06c&title=&width=734) + +#### 大纲面板 +可以调整页面内的组件树结构: +![Dec-17-2021 19-14-34.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562213701-39f3e2c3-f52c-4be4-bb56-90842daa58ab.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u1d18d088&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-14-34.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u2d6ebf59-3cd5-4e80-8599-a4d594a2cbf&title=&width=734) +可以在这里打开或者关闭模态浮层的展现: +![Dec-17-2021 19-19-18.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562213674-44d91956-ac82-4909-98b5-e0bd4fcbe12d.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u7d3beb31&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-19-18.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u6adfe95e-0c27-4c03-8e3c-ca62cb37387&title=&width=734) + + +#### 源码面板 +可以编辑页面级别的 JavaScript 代码和 CSS 配置 +![Feb-11-2022 14-51-59.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562356337-9e7f7490-396c-4520-b780-4a43a29050ef.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=u02b5cb05&margin=%5Bobject%20Object%5D&name=Feb-11-2022%2014-51-59.gif&originHeight=614&originWidth=1532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2080513&status=done&style=none&taskId=u2f95447f-b7a6-453d-8a8c-7d1649581d9&title=) + +#### Schema 编辑 +【开发者专属】可以编辑页面的底层 Schema 数据。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644562411102-a8596fce-fd77-4f20-bd3c-b52e2a0beb52.png#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=824&id=u3488f050&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1648&originWidth=3070&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1082743&status=done&style=none&taskId=u529bf58c-2203-484f-bf9f-19c2a3fe870&title=&width=1535) +搭配顶部操作区的“保存到本地”和“重置页面”功能,可以实验各种 schema 对低代码页面的改变。 + +它们操作的数据关系是: + +- 页面中的 Schema 数据:保存在低代码引擎中的 Schema,点击 Schema 面板中的“保存 Schema” 时将修改引擎中的值,此外低代码引擎中的所有操作都可能修改到 Schema +- localStorage 数据:由“保存到本地”保存到 localStorage 中,页面初始化时将读取,预览页面时也会读取 +- 默认 Schema:保存在 Demo 项目中的默认 Schema(`public/schema.json`),初始化页面时如果不存在 localStorage 数据即会读取,点击“重置页面”时,也会读取 + +#### 中英文切换 +可以切换编辑器的语言;注:需要组件配置配合。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644562219182-e4233163-b731-4f09-a442-9d5c0e71e7e8.png#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=756&id=ua3adfd78&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1512&originWidth=3018&originalType=binary&ratio=1&rotation=0&showTitle=false&size=384093&status=done&style=none&taskId=uf546934b-ae91-4e3e-9e21-2447de70ed1&title=&width=1509) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644562219666-1baf7da2-6d70-45fa-8805-b6cc9ac99f3f.png#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=755&id=u34aad08e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1510&originWidth=3016&originalType=binary&ratio=1&rotation=0&showTitle=false&size=380190&status=done&style=none&taskId=ud264115a-ae01-4b65-9ccc-4e6efa37b62&title=&width=1508) +## 中部:可视化页面编辑画布区域 + +点击组件在右侧面板中能够显示出对应组件的属性配置选项 +![Dec-17-2021 19-28-28.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562216925-c4bd5f10-2469-452c-8c2d-fe92ba6d03a7.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=uff491710&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-28-28.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u2f775208-8b07-4968-9dd4-420c6e4d3c1&title=&width=734) + +拖拽修改组件的排列顺序 +![Dec-17-2021 19-29-40.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562219867-61a41b16-4513-4827-80bf-f7e4832bcf3a.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=ueda50ec8&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-29-40.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ue0ec6bea-81f1-4d04-bf82-acde7c9983a&title=&width=734) + +将组件拖拽到容器类型的组件中,注意拖拽时会在右侧提示当前的组件树。 +![Dec-17-2021 19-31-30.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562220001-4afae72e-f9fd-4564-a904-c87f61ba79b5.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=ucc719a0e&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-31-30.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u2c46a827-8702-471c-a8c1-eb4f069d108&title=&width=734) + +## 右侧:组件级别配置 + +### 选中的组件 +从页面开始一直到当前选中的组件位置,点击对应的名称可以切换到对应的组件上。 +![Dec-17-2021 19-35-25.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562220818-c6532319-51df-4698-a3a4-80f3ab70b209.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u648c740b&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-35-25.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u03dd1651-8139-47f1-9cd1-a5089b64bf9&title=&width=734) + +### 选中组件的配置 +当前组件的大类目选项,根据组件类型不同,包含如下子类目: + +#### 属性 +组件的基础属性值设置 +![Dec-17-2021 19-37-26.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562222884-191c8433-2386-47f4-bab4-d3d1fe534f12.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u43676a31&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-37-26.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u170b8d2a-c1f9-4acf-a0e2-9825c588dcd&title=&width=734) + +#### 样式 +组件的样式配置,如文字: +![Dec-17-2021 19-38-55.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562224062-86fcf97b-d229-487f-951d-d2070337c058.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u4a9930ae&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-38-55.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u4a81ecb9-5b51-4758-9dd0-eaeb2e1a318&title=&width=734) + +#### 事件 +绑定组件对外暴露的事件。 +![Dec-17-2021 19-41-17.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562224632-a3ee9b18-97e8-4d31-b4fe-b58720dc6bf5.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u534bb1ea&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-41-17.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u50691375-2514-4a6c-8bec-0be44adf141&title=&width=734) + +#### 高级 +循环、条件渲染与 key 设置。 +![Dec-17-2021 19-46-26.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562226094-899cf104-3c60-439f-8b68-83af595ef275.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u9190ed31&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-46-26.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=uf02555c1-cd82-486d-8561-ca97e0ec1cd&title=&width=734) + +## 顶部:操作区 + +### 撤回和重做 +![Dec-17-2021 19-52-23.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562226083-d7f69bff-42e6-4173-8ac8-6e5a0c0262d6.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u81f5d842&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-52-23.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ubeb556cd-2349-44d8-b1be-ba6e32bea4e&title=&width=734) diff --git a/docs/docs/demoUsage/makeStuff/_category_.json b/docs/docs/demoUsage/makeStuff/_category_.json new file mode 100644 index 000000000..606d8d658 --- /dev/null +++ b/docs/docs/demoUsage/makeStuff/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "如何制作", + "position": 1, + "collapsed": false, + "collapsible": false +} diff --git a/docs/docs/demoUsage/makeStuff/dialog.md b/docs/docs/demoUsage/makeStuff/dialog.md new file mode 100644 index 000000000..772bd5647 --- /dev/null +++ b/docs/docs/demoUsage/makeStuff/dialog.md @@ -0,0 +1,33 @@ +--- +title: 3. 如何通过按钮展示/隐藏弹窗 +sidebar_position: 1 +--- +### 1.拖拽一个按钮 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652355202273-1a84b1e5-e33c-4686-b92b-633936423141.png#clientId=udd01ff56-e3fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=906&id=u81f6abfa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1812&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=749009&status=done&style=none&taskId=u6f4bf7e1-db67-4fca-8107-04021936c00&title=&width=1792) +### 2.拖拽一个弹窗 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652355233863-6d65ee77-b2fa-4d51-a04c-f0582c99eb72.png#clientId=udd01ff56-e3fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=811&id=u848a34e1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1622&originWidth=3578&originalType=binary&ratio=1&rotation=0&showTitle=false&size=774132&status=done&style=none&taskId=ue713e331-7ce0-4bd8-b41d-3ae1e07c69b&title=&width=1789) + +### 3.查看弹窗 refId +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652355269097-3e5282ed-2fdd-4a3b-b9b8-d78fac69c42e.png#clientId=udd01ff56-e3fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=794&id=ufd9346c1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1588&originWidth=3574&originalType=binary&ratio=1&rotation=0&showTitle=false&size=843332&status=done&style=none&taskId=ubc630826-e577-4dee-a2c3-5478bdf266a&title=&width=1787) + +- 点击弹窗 +- 点击右侧面板中的高级 +- 找到 refId + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652355320821-dd2c85f7-a75e-495d-896a-67e4761561ac.png#clientId=udd01ff56-e3fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=898&id=u4bf6b721&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1796&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=993930&status=done&style=none&taskId=u8c648fa2-c660-4979-8991-1cf138d2372&title=&width=1792) + +这里我们的 refId 是 "pro-dialog-entryl32xgrus" +### 4.隐藏弹窗 +点击工具栏的隐藏小图标,将弹窗在画布中隐藏 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652355400766-f7bdca37-7ba9-497d-a7e2-ad1d92233a26.png#clientId=udd01ff56-e3fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=784&id=ucbbe5086&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1568&originWidth=3578&originalType=binary&ratio=1&rotation=0&showTitle=false&size=774518&status=done&style=none&taskId=u2c8e73cd-10c5-47d3-b96e-30e6840d1af&title=&width=1789) + +### 5.按钮绑定事件 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652355486231-172c5797-c376-4f6f-94f7-8c3c593caa02.png#clientId=udd01ff56-e3fe-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=907&id=ufcf7d50e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1814&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=845218&status=done&style=none&taskId=u7c2c54ce-9c18-4b29-a066-f3024a95443&title=&width=1792) + +**通过下面的代码即可打开弹窗** + +```typescript +this.$('pro-dialog-entryl32xgrus').open(); +``` +#### diff --git a/docs/docs/demoUsage/makeStuff/table.md b/docs/docs/demoUsage/makeStuff/table.md new file mode 100644 index 000000000..06999010d --- /dev/null +++ b/docs/docs/demoUsage/makeStuff/table.md @@ -0,0 +1,128 @@ +--- +title: 2. 如何制作表格 +sidebar_position: 0 +--- +# 步骤详解 +## 拖入组件 +一个常见的表格页面会包含查询框、表格和分页按钮。这些都在 Fusion UI 中进行了相应的封装,我们可以在左侧组件面板处找到他们。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645011856718-ed2aa0b1-0c5c-4ec0-a72b-377bc500faf3.png#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=824&id=ue90ea461&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1648&originWidth=3032&originalType=binary&ratio=1&rotation=0&showTitle=false&size=963971&status=done&style=stroke&taskId=u3b1dfd98-44b7-4a13-be2a-e0124084288&title=&width=1516) + +将他们拖到画布之中: +![Feb-16-2022 16-58-59.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645002115004-4f01eb8d-cf68-4a7c-b0db-bc5aaf2604a3.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=uf69dc239&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2016-58-59.gif&originHeight=792&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7510570&status=done&style=stroke&taskId=ua6ea2651-6c6c-4762-98cc-cc3ab5734cd&title=&width=767) +## 配置组件 + +选中刚拖入的“查询筛选”组件,您可以配置此组件: +![Feb-14-2022 17-59-47.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644832912542-4b2e66ae-ba15-4e38-ab79-9f83e413a493.gif#clientId=uec0ffd6f-d4e1-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=u83c491b2&margin=%5Bobject%20Object%5D&name=Feb-14-2022%2017-59-47.gif&originHeight=792&originWidth=1532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2147213&status=done&style=stroke&taskId=uffd7092e-a247-4f48-b831-aaffe3646f7&title=) + +对于形如 Array 的配置项目,我们可以增删项目、修改常用项、修改顺序。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645012184644-444d82fa-a226-4784-b0df-92a5a52748bc.png#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=738&id=uc4ea8ded&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1476&originWidth=3060&originalType=binary&ratio=1&rotation=0&showTitle=false&size=375890&status=done&style=none&taskId=u7a1f43d8-eac4-405e-a3c9-38d3047f452&title=&width=1530) + +掌握组件配置功能,我们就可以完成一个常用的查询框的配置: +![Feb-21-2022 18-05-52.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645437973453-1fd1dc10-99ad-4c18-af49-2741bd81c4ae.gif#clientId=u022fc577-71a7-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=395&id=u964ae52f&margin=%5Bobject%20Object%5D&name=Feb-21-2022%2018-05-52.gif&originHeight=790&originWidth=1532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7210902&status=done&style=stroke&taskId=u9e39c54a-7467-4a96-b716-681cf598f09&title=&width=766) + +## 绑定数据 + +低代码场景下,我们需要绑定动态的数据。通过左侧的源码编辑面板,我们可以创建动态数据和它的相关处理函数: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645012532562-596d4a96-908e-4094-836c-974bda61d8a2.png#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=739&id=ufa7b81f8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1478&originWidth=2976&originalType=binary&ratio=1&rotation=0&showTitle=false&size=816197&status=done&style=none&taskId=u5ab9656e-26b6-427a-a52e-5e11dbc4a7a&title=&width=1488) +如图,我们配入如下自定义值进 state 里: +```json + "companies": [ + { company: '测试公司1', id: 1, createTime: +new Date() }, + { company: '测试公司2', id: 2, createTime: +new Date() }, + { company: '测试公司3', id: 3, createTime: +new Date() }, + ] +``` +定义动态数据以后,我们需要绑定它到组件的属性中,我们找到相关属性的配置: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645012714358-f3f39d5f-1790-4196-9f16-b45f51fa8f28.png#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=896&id=u1126fd65&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1792&originWidth=3546&originalType=binary&ratio=1&rotation=0&showTitle=false&size=413958&status=done&style=none&taskId=u976689ac-18a1-4f15-9fc2-60681670fc7&title=&width=1773) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645012791356-4fed1bea-bec2-4be9-85ea-b366d0acb4ab.png#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=820&id=ub81b6dc8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1640&originWidth=3428&originalType=binary&ratio=1&rotation=0&showTitle=false&size=354847&status=done&style=none&taskId=uc645e654-b293-4c18-86da-a6637083e55&title=&width=1714) +如图,输入表达式: +```json +this.state.companies +``` +再结合上一节的“配置组件”操作,我们已经可以把表格的主体配置出来了: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645013130950-4219cf27-760c-4749-8d4e-013dd53dbc83.png#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=820&id=u73c837e3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1640&originWidth=3058&originalType=binary&ratio=1&rotation=0&showTitle=false&size=408420&status=done&style=stroke&taskId=u23f7f045-8077-4e9d-9335-fea3ba54273&title=&width=1529) + +## 动态请求 + +我们进入代码区块,使用生命周期方法来完成动态数据的请求。假设提供数据的接口是:[http://rap2api.taobao.org/app/mock/250089/testCompanies](http://rap2api.taobao.org/app/mock/250089/testCompanies),那么,我们可以在源码面板进行如下配置: +```typescript +class LowcodeComponent extends Component { + state = { + "text": "outer", + "isShowDialog": false, + "loading": false, + "companies": [ + { company: '测试公司1', id: 1, createTime: +new Date() }, + { company: '测试公司2', id: 2, createTime: +new Date() }, + { company: '测试公司3', id: 3, createTime: +new Date() }, + ] + } + componentDidMount() { + this.setState({ loading: true }) + window.fetch('http://rap2api.taobao.org/app/mock/250089/testCompanies') + .then((res) => res.json()) + .then((companies) => { + this.setState({ + companies, + }) + }) + .catch(err => console.error(err)) + .then(() => { + this.setState({ loading: false }) + }) + } +} +``` +在 `componentDidMount` 生命周期,将请求接口并设置 loading 和数据字段。 +点击保存或叉关闭源码面板后,我们可以看到代码已经生效了: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645013883960-ca217c38-5c40-4ecc-9e05-277098fef16a.png#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=817&id=u1a3f852b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1634&originWidth=3058&originalType=binary&ratio=1&rotation=0&showTitle=false&size=427572&status=done&style=stroke&taskId=ubd2291b7-36c3-48c1-b489-9c61f0f6230&title=&width=1529) + +## 配置插槽 + +我们可以用绑定数据的方法把 loading 绑在加载指示上: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645014111323-c45f9b9a-77dd-4724-b6ee-78572863a871.png#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=952&id=u3bdd353b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1904&originWidth=3170&originalType=binary&ratio=1&rotation=0&showTitle=false&size=503197&status=done&style=none&taskId=u1faed9f0-3c68-4385-8d08-e59e2a1600a&title=&width=1585) +![Feb-16-2022 20-24-35.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645014292272-68e07740-47dc-4c94-8437-beded0b07c63.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=u4506fc72&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2020-24-35.gif&originHeight=792&originWidth=1532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6960677&status=done&style=stroke&taskId=u9fe02184-e6dc-4886-b371-c48ca1e2832&title=&width=766) +将 Loading 的“是否显示”字段绑定 `this.state.loading` 后,我们可以看到,这里暴露了一个插槽。插槽是可以任意扩展的预设部分,我们可以把其他的部分拖进插槽: +![Feb-16-2022 20-27-03.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645014436894-9b975ae6-76cc-412b-829a-fae3605277dc.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=u407467ac&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2020-27-03.gif&originHeight=792&originWidth=1528&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3443266&status=done&style=stroke&taskId=u0a091444-8b12-49a0-a57a-bfa758d351a&title=&width=764) +点击右上角的预览,我们能够看到完整的动态请求效果了: +![Feb-16-2022 20-28-36.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645014527841-b621f38f-2c03-40f1-aa41-19293f96b08f.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=u6ee6beea&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2020-28-36.gif&originHeight=792&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1968612&status=done&style=stroke&taskId=u2bdcee3f-91c5-4cb3-8405-f44f995cc78&title=&width=767) + +## 列挂钩浮层 + +为了能够让表格里的操作挂钩浮层,我们先拖入一个浮层: +![Feb-16-2022 20-32-09.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645014772471-0fce9b50-0f70-492e-bb53-5f875c00f5b4.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=u4d33cd05&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2020-32-09.gif&originHeight=792&originWidth=1532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7475148&status=done&style=stroke&taskId=u9dc26cba-41eb-4fe8-b96f-fe391968861&title=&width=766) +使用大纲树能够临时显示和隐藏此浮层: +![Feb-16-2022 20-32-39.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645014828329-b2de4db6-9032-4280-b886-db17070eea21.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=ue27e6676&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2020-32-39.gif&originHeight=792&originWidth=1530&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7335022&status=done&style=stroke&taskId=u73554a5d-5ebe-48d1-a861-426ba8501b1&title=&width=765) +我们给表格增加一个数据列: +![Feb-16-2022 20-39-41.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645015242447-3e019714-4b86-4c10-9bf7-01e19201bf0c.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=uc2c35de3&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2020-39-41.gif&originHeight=792&originWidth=1532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3415710&status=done&style=stroke&taskId=u5aedc5dd-f361-4e45-88b0-be09af09a6a&title=&width=766) + +然后配置它的行为为“弹窗”: +![Feb-16-2022 20-40-05.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645015223838-7f180e28-43e0-442b-a47e-ea5ff69d4900.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=u80f44f38&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2020-40-05.gif&originHeight=792&originWidth=1532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7261162&status=done&style=stroke&taskId=u3828503a-ecac-452a-8d20-02e4a46ad02&title=&width=766) + +实现的效果如下: +![Feb-16-2022 20-42-51.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645015379808-7d7852b1-5902-42d0-b951-c9c5d8f4c893.gif#clientId=uf61aba9b-3a69-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=ua2e7ceda&margin=%5Bobject%20Object%5D&name=Feb-16-2022%2020-42-51.gif&originHeight=792&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=588625&status=done&style=stroke&taskId=uf0466dde-ca4c-41d9-bf42-1ff443d02c5&title=&width=767) + +## 事件回调 + +上述功能点中,我们是把操作行为绑定在数据列上的,这一节我们绑定到操作列中。在操作列按钮处,点击下方的“添加一项”: +![Feb-23-2022 11-58-02.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645588703676-2a36cab4-52f4-4f31-9018-d56b41a55283.gif#clientId=u74bf469f-47f0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=395&id=u18d8ea0b&margin=%5Bobject%20Object%5D&name=Feb-23-2022%2011-58-02.gif&originHeight=790&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8440133&status=done&style=stroke&taskId=u73e25800-c0fa-486b-9b68-4df7db9b9f1&title=&width=767) + +点击左侧的详情按钮,配置它的事件回调: +![Feb-23-2022 12-00-18.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645588832183-7ed0f06b-731d-4bd8-b934-723de43a8b42.gif#clientId=u74bf469f-47f0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=395&id=u59505da7&margin=%5Bobject%20Object%5D&name=Feb-23-2022%2012-00-18.gif&originHeight=790&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9047220&status=done&style=stroke&taskId=ub8ab1b4e-4195-426f-b792-fc8bf91d142&title=&width=767) + +代码侧,我们配置这个回调函数: +```javascript +onClick_new(e, { rowKey, rowIndex, rowRecord }){ + window.Next.Message.show(JSON.stringify({ rowKey, rowIndex, rowRecord })) +} +``` +保存。预览时我们可以看到效果了: +![Feb-23-2022 12-05-25.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645589138764-d6514256-2a1f-4127-9591-747b4808848e.gif#clientId=u74bf469f-47f0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=395&id=u9f09d078&margin=%5Bobject%20Object%5D&name=Feb-23-2022%2012-05-25.gif&originHeight=790&originWidth=1532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2238638&status=done&style=stroke&taskId=u460c90f3-d692-45f9-9028-cf45d4cea98&title=&width=766) +# 研究本例的 schema + +我们把本例的 schema 保存在云端,您可以自行下载研究:[https://mo.m.taobao.com/marquex/lowcode-showcase-table](https://mo.m.taobao.com/marquex/lowcode-showcase-table) + +您可以通过左下角的 Schema 面板直接导入本例子的 Schema。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645589288482-0ce8ea2f-c4e1-4956-be9c-143c9b71654b.png#clientId=u74bf469f-47f0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=810&id=u713729c6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1620&originWidth=3054&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1074154&status=done&style=stroke&taskId=u783f33a5-241d-43ec-8b46-8385b733810&title=&width=1527) diff --git a/docs/docs/demoUsage/panels/_category_.json b/docs/docs/demoUsage/panels/_category_.json new file mode 100644 index 000000000..71315059f --- /dev/null +++ b/docs/docs/demoUsage/panels/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "面板详解", + "position": 2, + "collapsed": false, + "collapsible": false +} diff --git a/docs/docs/demoUsage/panels/canvas.md b/docs/docs/demoUsage/panels/canvas.md new file mode 100644 index 000000000..0e4678076 --- /dev/null +++ b/docs/docs/demoUsage/panels/canvas.md @@ -0,0 +1,74 @@ +--- +title: 5. 画布详解 +sidebar_position: 1 +--- +## 组件操作 +### 画布操作 +点击组件在右侧面板中能够显示出对应组件的属性配置选项 +![Dec-17-2021 19-28-28.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562216925-c4bd5f10-2469-452c-8c2d-fe92ba6d03a7.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=uff491710&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-28-28.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u2f775208-8b07-4968-9dd4-420c6e4d3c1&title=&width=734) + +拖拽修改组件的排列顺序 +![Dec-17-2021 19-29-40.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562219867-61a41b16-4513-4827-80bf-f7e4832bcf3a.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=ueda50ec8&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-29-40.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ue0ec6bea-81f1-4d04-bf82-acde7c9983a&title=&width=734) + +拖拽时会在右侧提示当前的组件树。 +![Dec-17-2021 19-31-30.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562220001-4afae72e-f9fd-4564-a904-c87f61ba79b5.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=ucc719a0e&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-31-30.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u2c46a827-8702-471c-a8c1-eb4f069d108&title=&width=734) + +### 组件控制 +点击组件右上角的复制按钮,或者按下 `ctrl + c` 再按下 `ctrl + v`,可以将其复制; +点击组件右上角的删除按钮,或者直接使用 `Delete` 键,可以将其删除。 +![Dec-17-2021 19-33-20.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644562220898-a54f0cfa-26bf-461f-a4aa-9708fc367d7c.gif#clientId=u99b5ef7a-7ebb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u2bae31a2&margin=%5Bobject%20Object%5D&name=Dec-17-2021%2019-33-20.gif&originHeight=754&originWidth=1468&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u2c4c7b00-b316-431a-9c54-949ae4ed766&title=&width=734) + +### 选择组件切换 + +可以用键盘上的按键切换组件选择: + +- `↑` 向上选择组件 +- `↓` 向下选择组件 +- `←` 向左选择组件 +- `→` 向右选择组件 + +可以 hover 到组件操作辅助区的第一项来选中组件的父级节点: +![Feb-22-2022 14-42-30.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645512169966-17f26afa-00fc-47a5-86be-08505ab39a4f.gif#clientId=u5c3042e1-7626-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=395&id=uee0cbe0a&margin=%5Bobject%20Object%5D&name=Feb-22-2022%2014-42-30.gif&originHeight=790&originWidth=1536&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2913977&status=done&style=stroke&taskId=ud9314fe0-0943-48e5-9f0c-b9b9b4a6b47&title=&width=768) + +### 可扩展项简述 + +快捷键、操作辅助区均可扩展。 + +## Slot 区块 + +React 中,可以定义一个 prop 选项为 `JSXElement` 或 `(...args) => JSXElement` 的形式,这个形式在低代码画布中,被定义为 Slot,允许往其内部拖入组件,进行符合直觉的操作。 +![Feb-22-2022 14-46-02.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645512376500-46baf1b5-2335-4fb5-a430-c2f2245c8439.gif#clientId=u5c3042e1-7626-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=395&id=u8c429d95&margin=%5Bobject%20Object%5D&name=Feb-22-2022%2014-46-02.gif&originHeight=790&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2389349&status=done&style=stroke&taskId=u7462c2e4-64bf-432a-aa2e-2fef526b4d4&title=&width=767) + +### 锁定 Slot + +您可以对 Slot 进行锁定操作,锁定后内部内容无法选中; +![Feb-22-2022 14-50-03.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1645512638545-ae46bcd2-883b-4229-9f78-d59087d03d28.gif#clientId=u5c3042e1-7626-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=395&id=u87ff9fe3&margin=%5Bobject%20Object%5D&name=Feb-22-2022%2014-50-03.gif&originHeight=790&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9318074&status=done&style=none&taskId=ua4e1f652-2e72-4dcf-ad78-19b42e179c3&title=&width=767) + +在组件树可以解除操作。 + +## 组件编辑态 + +低代码引擎允许组件在编辑状态下表现得和渲染时不一样。Demo 中的布局组件就是用对应 API 完成布局的高级操作的。 + +它背后的实现有两种方法: + +- 侵入型:组件编辑态下,会往组件内传入 `__designMode: 'design'`,可以在组件中进行相应处理; + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645512859914-b51c23b9-50d9-4962-a6f7-96dbdcef6cef.png#clientId=u5c3042e1-7626-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=821&id=uf96a3071&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1642&originWidth=3066&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1024714&status=done&style=none&taskId=u7838e6c7-2349-4224-94ed-4e0e972b2a2&title=&width=1533) + +- 双入口型:通过配置物料的 editUrls,加载专属于编辑态组件的物料。pro-layout 使用的是这种方式 +```json + { + "package": "@alifd/pro-layout", + "version": "1.0.1-beta.6", + "library": "AlifdProLayout", + "urls": [ + "https://alifd.alicdn.com/npm/@alifd/pro-layout@1.0.1-beta.6/dist/AlifdProLayout.js", + "https://alifd.alicdn.com/npm/@alifd/pro-layout@1.0.1-beta.6/dist/AlifdProLayout.css" + ], + "editUrls": [ + "https://alifd.alicdn.com/npm/@alifd/pro-layout@1.0.1-beta.6/build/lowcode/view.js", + "https://alifd.alicdn.com/npm/@alifd/pro-layout@1.0.1-beta.6/build/lowcode/view.css" + ] + } +``` diff --git a/docs/docs/demoUsage/panels/code.md b/docs/docs/demoUsage/panels/code.md new file mode 100644 index 000000000..717c2353a --- /dev/null +++ b/docs/docs/demoUsage/panels/code.md @@ -0,0 +1,165 @@ +--- +title: 7. 源码面板详解 +sidebar_position: 3 +--- +在源码面板中,您可以完成低代码中的代码部分编写。 + +## 面板功能拆解 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644897390779-cefb2c31-82fc-44f4-b824-adc32569ac6f.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=870&id=u23446c19&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1740&originWidth=2502&originalType=binary&ratio=1&rotation=0&showTitle=false&size=865371&status=done&style=none&taskId=u44e2b188-c268-4a30-a628-76a046be9d4&title=&width=1251) + +### 代码编辑面板 + +代码编辑面板允许您书写 JavaScript 代码,并支持 JSX 语法。 +由于依赖了 Babel,所以书写的 JSX 和 Chrome 80+ 以后的新语法也会被自动编译: + +| 编译前 | 编译后 | +| --- | --- | +| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644897774925-b54126e0-ff6b-445e-bc68-569731aef8c3.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=95&id=nhuiT&margin=%5Bobject%20Object%5D&name=image.png&originHeight=190&originWidth=670&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25045&status=done&style=none&taskId=u323192f6-7cfa-4d73-a184-2699f648c6f&title=&width=335) | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644897840129-83fe9a81-d8b2-4873-8764-904f531ec959.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=55&id=u3ba8300e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=110&originWidth=2094&originalType=binary&ratio=1&rotation=0&showTitle=false&size=44006&status=done&style=none&taskId=uef1552e3-ccdb-45dd-95d5-187a6c6b7df&title=&width=1047) | +| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644897884917-641b1547-7b90-4f78-86c1-0cc51996623d.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=60&id=u5fa00781&margin=%5Bobject%20Object%5D&name=image.png&originHeight=120&originWidth=434&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17421&status=done&style=none&taskId=uecee8fbf-a786-4f89-ac9c-f2f8d059fe0&title=&width=217) | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644897915892-a1784bc2-693b-4cf6-a082-3c8e0368a987.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=60&id=ubcca6a46&margin=%5Bobject%20Object%5D&name=image.png&originHeight=120&originWidth=2536&originalType=binary&ratio=1&rotation=0&showTitle=false&size=50743&status=done&style=none&taskId=ue0f418e0-4192-4bfd-8912-9b64faedb66&title=&width=1268) | + + +> 注:因为编译结果会被 `@babel/runtime` 干扰,目前面板不支持 `async await`或 `{ ...arr }` 形态的语法编译。如果您需要此类编译,您可以考虑在读取 schema 中的 `originCode` 之后自己手动通过 babel 编译。 + + +#### 全局变量引用 + +在代码中,您可以通过 window 来引用全局变量。资产包中的 packages 都是通过 UMD 方式引入的对应内容,如果您引入了 Fusion Next(Demo 中默认引入),那么可以通过此方法直接唤起 Fusion Next 的内容,如弹窗提示: +```typescript +window.Next.Message.success('成功') +``` +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644898647058-9a5d6800-31fd-4c62-a577-850b90fc5d21.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=57&id=ue6231d61&margin=%5Bobject%20Object%5D&name=image.png&originHeight=114&originWidth=238&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11360&status=done&style=none&taskId=u869f3709-a599-4ead-a80f-fa3b49c9836&title=&width=119) + +#### 局部变量引用 + +您可以在成员函数中访问到如下变量: + +- `this.state` +- `this.setState` +- `this.context.appHelper.utils` +- `this.context.appHelper.constants` +- `this.context.appHelper.requestHandlerMap` +- `this.context.components` + +#### 读、写与异常处理 + +- 读取:每次打开面板时,都会尝试读取 schema 中的 originCode 字段,如果没有,则从 schema 上的字段还原代码; +- 写入:在关闭代码编辑面板(主动点击叉或者点击非代码编辑区块的被动关闭都算)时,将自动写入到 schema 中;您也可以在编辑过程中点击“保存”按钮手动保存; +| 源码面板中 | schema 中 | +| --- | --- | +| 本地数据初始值设置: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899552013-3de394fd-f530-4b4f-8258-8b9c64f11c11.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=73&id=u291f7733&margin=%5Bobject%20Object%5D&name=image.png&originHeight=146&originWidth=370&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17505&status=done&style=none&taskId=u55496884-bc04-4867-9295-c71f44b77ef&title=&width=185) | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899587782-0ceea074-07bb-4260-a580-7f49a82740ed.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=413&id=u01ae12cb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=826&originWidth=2098&originalType=binary&ratio=1&rotation=0&showTitle=false&size=776122&status=done&style=none&taskId=ube04795b-6244-4aac-9ebc-f4624e605db&title=&width=1049) | +| 生命周期方法: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899759963-d198edc4-a8c7-4a3f-90ee-b42244398958.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=130&id=uafcbf72e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=260&originWidth=478&originalType=binary&ratio=1&rotation=0&showTitle=false&size=37208&status=done&style=none&taskId=u19b58f72-7058-4a22-9a8e-334a9a541bd&title=&width=239) | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899791416-a7969846-8d7d-4c51-9c55-6b1c65faf07b.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=418&id=uc6edd06d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=836&originWidth=2010&originalType=binary&ratio=1&rotation=0&showTitle=false&size=806116&status=done&style=none&taskId=uacb7cf67-ee4b-45ba-962a-24f43b525bc&title=&width=1005) | +| 自定义函数: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899808831-538e59a7-6d40-4e1a-bd72-bd2332bb9d7c.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=321&id=u2ea3e043&margin=%5Bobject%20Object%5D&name=image.png&originHeight=642&originWidth=660&originalType=binary&ratio=1&rotation=0&showTitle=false&size=72124&status=done&style=none&taskId=uc6ec76e1-89a0-4dad-a0ab-053730e2b4d&title=&width=330) | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899830711-e262e41e-8332-4810-9293-bd4ef540c919.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=411&id=ueb7c1ad8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=822&originWidth=1862&originalType=binary&ratio=1&rotation=0&showTitle=false&size=815729&status=done&style=none&taskId=u3aae2a2e-4de4-468a-bd5a-5bec53b908a&title=&width=931) | +| 编译前全量代码: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899850193-0b1990ea-e494-4c5f-94ef-9a1fdbde0a98.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=561&id=u92136fdf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1122&originWidth=762&originalType=binary&ratio=1&rotation=0&showTitle=false&size=165346&status=done&style=none&taskId=u727c08ae-f56f-4632-acc0-837fa220681&title=&width=381) | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899882162-648366a3-5b0b-4cf3-b103-bf3812f6e807.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=398&id=ub882b04a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=796&originWidth=1906&originalType=binary&ratio=1&rotation=0&showTitle=false&size=716114&status=done&style=none&taskId=u94d53b7d-5ea9-471a-b82c-3dec1a532b5&title=&width=953) | + + +- 异常处理:如果代码解析失败,它将无法被正常保存到 schema 中,此时编辑器会弹层提示: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899252223-57317fcb-0958-4f38-a37b-00eaa5561512.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=819&id=u2d66f54c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1638&originWidth=3068&originalType=binary&ratio=1&rotation=0&showTitle=false&size=473979&status=done&style=none&taskId=u9e4a4c69-dd56-4265-93d7-9b2e4e8971a&title=&width=1534) + +### 样式编辑面板 + +您可以在这里书写 CSS 内容。它对应 schema 中的 css 字段: + +| 源码面板中 | Schema 中 | +| --- | --- | +| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899312003-76f4c95e-221f-4b5f-92ae-c51e664385e0.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=194&id=u30912dec&margin=%5Bobject%20Object%5D&name=image.png&originHeight=388&originWidth=634&originalType=binary&ratio=1&rotation=0&showTitle=false&size=42979&status=done&style=none&taskId=ue2a18106-55f3-4cff-8f95-904317d0419&title=&width=317) | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899355488-aa352d2d-a001-434f-9368-021befea52ed.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=291&id=u60b8f9d4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=582&originWidth=1646&originalType=binary&ratio=1&rotation=0&showTitle=false&size=454443&status=done&style=none&taskId=u236b94fb-6c20-4c6c-9fe3-7cd75eef0c4&title=&width=823) | + + +## 对接代码 + +### 生命周期对接 +如果您书写了视图相关的声明周期方法,那么对应的方法会在视图的特定周期被调用。支持的生命周期函数在《阿里巴巴中后台前端搭建协议规范》中被定义,包含: +```typescript +{ + componentDidMount(): void; + constructor(props: Record, context: any); + render(): void; + componentDidUpdate(prevProps: Record, prevState: Record, snapshot: Record): void; + componentWillUnmount(): void; + componentDidCatch(error: Error, info: any): void; +} +``` + +### 设置器面板对接 + +书写完了函数 / state 后,您可以在右侧的设置器面板中配置对代码的部分。 + +通常书写代码是为了对接低代码配置中的“变量绑定”、“事件回调”、“条件判断”和“循环”部分的。 + +#### 变量绑定 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644899977727-f4f44171-52e8-4062-b558-436536b84640.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=732&id=ua42e46e3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1464&originWidth=2738&originalType=binary&ratio=1&rotation=0&showTitle=false&size=957243&status=done&style=stroke&taskId=u56f7f36d-535d-48e9-8a0c-e0cb1f9af1d&title=&width=1369) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644900005551-14c356a0-2e51-4b0b-82b5-8a135d1c6c3e.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=583&id=ufcb9db2b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1166&originWidth=1528&originalType=binary&ratio=1&rotation=0&showTitle=false&size=153133&status=done&style=stroke&taskId=u208e369b-f019-4019-8c2e-4c28b6eba91&title=&width=764) +```json +{ + "componentName": "NextBlockCell", + "id": "node_ockzmje8tf5", + "props": { + "bodyPadding": { + "type": "JSExpression", + "value": "this.state.text", + "mock": "" + } + } +} +``` + + +#### 事件回调 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644900095962-2ec54fb5-e1f8-4d4a-a75e-24e1c685a833.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=726&id=ufed11f2e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1452&originWidth=2734&originalType=binary&ratio=1&rotation=0&showTitle=false&size=749908&status=done&style=stroke&taskId=uc379b8ec-c344-48f8-9b43-8d9be961356&title=&width=1367) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644900119673-f9538274-c896-4951-86f2-54d60ac95316.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=581&id=uffdcbbce&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1162&originWidth=1670&originalType=binary&ratio=1&rotation=0&showTitle=false&size=118712&status=done&style=stroke&taskId=u261a9b7f-9f5a-406a-aa55-8a3f33bdd05&title=&width=835) +```json +{ + "componentName": "Filter", + "id": "node_ockzmj0cl11w", + "props": { + "__events": { + "eventDataList": [ + { + "type": "componentEvent", + "name": "onSearch", + "relatedEventName": "closeDialog" + } + ] + }, + "onSearch": { + "type": "JSFunction", + "value": "function(){this.onSearch.apply(this,Array.prototype.slice.call(arguments).concat([])) }" + } + } +} +``` + +#### 条件判断 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644900186943-de6b33de-adca-4c1b-8f47-f68cf6ce5f77.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=726&id=u23b46226&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1452&originWidth=2738&originalType=binary&ratio=1&rotation=0&showTitle=false&size=789132&status=done&style=stroke&taskId=u6322e6a8-bea3-47d8-a374-b9ec6558bb9&title=&width=1369) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644900005551-14c356a0-2e51-4b0b-82b5-8a135d1c6c3e.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=583&id=G2uKJ&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1166&originWidth=1528&originalType=binary&ratio=1&rotation=0&showTitle=false&size=153133&status=done&style=stroke&taskId=u208e369b-f019-4019-8c2e-4c28b6eba91&title=&width=764) +```json +{ + "componentName": "Filter", + "id": "node_ockzmj0cl11w", + "condition": { + "type": "JSExpression", + "value": "this.state.text", + "mock": true + } +} +``` + +#### 循环 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644900265929-c21c9927-1f34-49b6-9dc6-bcb4357190be.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=730&id=u8f457b1e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1460&originWidth=2746&originalType=binary&ratio=1&rotation=0&showTitle=false&size=781151&status=done&style=stroke&taskId=u92be7d31-2070-4a08-bc1c-6b1a599c682&title=&width=1373) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644900005551-14c356a0-2e51-4b0b-82b5-8a135d1c6c3e.png#clientId=ud3fa1588-e66f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=583&id=ot5cO&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1166&originWidth=1528&originalType=binary&ratio=1&rotation=0&showTitle=false&size=153133&status=done&style=stroke&taskId=u208e369b-f019-4019-8c2e-4c28b6eba91&title=&width=764) +```json +{ + "componentName": "Filter", + "id": "node_ockzmj0cl11w", + "loop": { + "type": "JSExpression", + "value": "this.state.text", + "mock": true + } +} +``` diff --git a/docs/docs/demoUsage/panels/component.md b/docs/docs/demoUsage/panels/component.md new file mode 100644 index 000000000..814792741 --- /dev/null +++ b/docs/docs/demoUsage/panels/component.md @@ -0,0 +1,32 @@ +--- +title: 4. 组件面板详解 +sidebar_position: 0 +--- +## 概述 +组件面板顾名思义就是承载组件的面板,组件面板会获取并解析传入给低代码引擎的资产包数据(数据结构[点此查看](https://lowcode-engine.cn/assets)),得到需要被展示的组件列表,并根据分类、排序规则对组件进行排列,同时也提供了搜索功能。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647964501932-33676243-c42b-4e7c-8663-77c5898d3343.png#clientId=uf38e3cbf-9388-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=438&id=ubb9e4616&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1672&originWidth=3056&originalType=binary&ratio=1&rotation=0&showTitle=true&size=451947&status=done&style=stroke&taskId=u0fc240e1-a792-4bd1-b84d-5bbc8e8fc8b&title=%E7%BB%84%E4%BB%B6%E9%9D%A2%E6%9D%BF&width=800 "组件面板") +## 组件信息 +组件面板承载的组件信息有: + +- 组件标题; +- 组件截图; +- 组件低代码 schema 片段; +- 组件分组; +- 组件分类; +- 是否隐藏组件; +- 关键词:关键词用于搜索,会聚合 name、title、description、keywords 等字段作为搜索匹配的目标; + +其中标题和截图是我们能够看到的,schema 片段则是拖拽到设计器时会自动插入页面 schema 中,面板会根据分组、分类来对组件进行排列; +这些组件信息均通过资产包数据获取,字段对应关系如下图所示: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647965256061-d15ad119-471f-43c7-8856-2c91bb3670ad.png#clientId=uf38e3cbf-9388-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1012&id=u1b3132db&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1678&originWidth=1326&originalType=binary&ratio=1&rotation=0&showTitle=false&size=996705&status=done&style=stroke&taskId=u6730f591-c7e1-42ba-8cb7-fcc95e76e8c&title=&width=800) +## 组件分组、分类排序 +组件面板会把相同分组的组件放在同一个 tab 下,相同分类的组件放在同一个 collapse 中,同时也支持对 tab 和 collapse 进行排序; +由于是整体性的排序,组件自身的信息无法决定此排序,因此在资产包数据根节点新增了 sort 字段用于指定分组和分类的排序,具体定义在[《低代码引擎资产包协议规范》](https://lowcode-engine.cn/assets)2.4 sort 章节; + +| **根属性名称** | **类型** | **说明** | **变量支持** | **默认值** | +| --- | --- | --- | --- | --- | +| sort.groupList | String[] | 组件分组,用于组件面板 tab 展示 | - | ['精选组件', '原子组件'] | +| sort.categoryList | String[] | 组件面板中同一个 tab 下的不同区间用 category 区分,category 的排序依照 categoryList 顺序排列 | - | ['通用', '数据展示', '表格类', '表单类'] | + +## 搜索 +组件面板会提取组件的 name、title、description、keywords 等字段作为搜索匹配的目标,因此除了能够通过组件名称、描述进行搜索外,还可以指定一些关键词-keywords,keywords 是数组也可以是字符串类型。 diff --git a/docs/docs/demoUsage/panels/datasource.md b/docs/docs/demoUsage/panels/datasource.md new file mode 100644 index 000000000..3116f1939 --- /dev/null +++ b/docs/docs/demoUsage/panels/datasource.md @@ -0,0 +1,150 @@ +--- +title: 8. 数据源面板详解 +sidebar_position: 4 +--- +## 🪚 概述 +数据源面板主要负责管理低代码中远程数据源内容,通过可视化编辑的方式操作低代码协议中的数据源Schema,配合 [数据源引擎](https://www.yuque.com/lce/doc/datasource-engine) 即可实现低代码中数据源的生产和消费; +![image.png](https://cdn.nlark.com/yuque/0/2022/png/84508/1648397674378-aec10892-5ee4-414d-807e-39f55f3a5be5.png#clientId=u38847497-05f3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=821&id=u07e82f8a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1642&originWidth=2878&originalType=binary&ratio=1&rotation=0&showTitle=false&size=246032&status=done&style=none&taskId=uc18acbc5-1404-4266-a499-e952d1084c4&title=&width=1439) +数据源面板 +## ❓如何使用 +> 面板内包含了数据源创建、删除、编辑、排序、导入导出、复制以及搜索等能力,内置支持了 `fecth` & `JSONP`两种常用远程请求类型; + +### 三步创建一个数据源 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/84508/1648398269436-bd241801-e617-4640-830f-03b44aca80a1.png#clientId=u38847497-05f3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=819&id=u1ee9fa0d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1638&originWidth=2878&originalType=binary&ratio=1&rotation=0&showTitle=false&size=279302&status=done&style=none&taskId=ue1248934-df36-423c-86f3-160a4e865da&title=&width=1439) +三步创建数据源 + +### 参数详解 +> TODO + +## ☠️ 更多介绍 +### 数据源顺序 +> 数据源为何支持排序功能,主要原因是数据源的加载存在先后顺序;接下来我们从协议层以及实现层看数据源之间的顺序关系; + +TODO +### 如何定制数据源 +#### 定制数据源类型(设计态) +#### 定制数据源请求实现(运行态) +> 当出现以下两种情况的时,我们需要定制数据源请求实现, +> - 当你默认提供的 `handler`无法满足你的需求 +> - 定制了数据源类型,比如 `GraphQL`,需要实现一个对应的 `handler` + +接下来我们来看一个例子,如何实现一个 `handler` +```javascript +import { RuntimeOptionsConfig } from '@alilc/lowcode-datasource-types'; + +import request from 'universal-request'; +import { RequestOptions, AsObject } from 'universal-request/lib/types'; + +export function createFetchHandler(config?: Record) { + return async function(options: RuntimeOptionsConfig) { + const requestConfig: RequestOptions = { + ...options, + url: options.uri, + method: options.method as RequestOptions['method'], + data: options.params as AsObject, + headers: options.headers as AsObject, + ...config, + }; + const response = await request(requestConfig); + return response; + }; +} +``` +低代码fetch-handler默认实现 + +以上代码是低代码内置的fetch-handler默认实现,内部使用了 `universal-request`,假如你们内部使用的 `axios`,你完全重新实现一个; +```javascript +import axios from 'axios'; +export function createAxiosFetchHandler(config?: Record) { + return async function(options: RuntimeOptionsConfig) { + const requestConfig: RequestOptions = { + ...options, + url: options.uri, + method: options.method as RequestOptions['method'], + data: options.params, + headers: options.headers, + ...config, + }; + const response = await axios(requestConfig); + return response; + }; +} +``` + +##### 注册到render +完成一个Handler后你可以通过以下方式接入到render或者出码中使用 + +###### 渲染Render +```tsx +import React, { memo } from 'react'; +import ReactRenderer from '@alilc/lowcode-react-renderer'; + +const SamplePreview = memo(() => { + return ( + + ); +}); +``` +###### 出码 +> 目前自定义只能通过重新定义类型来完成,接下来我们会给出码添加requestHandlersMap映射能力;如有需求请联系 荣彬(github-id:xingmolu) + + +### 设计态启用数据源引擎 +> 默认情况下设计态没有开启数据源引擎,我们可以在设计器init的时候来传递`requstHandlersMap`来开启;具体代码如下: + +```javascript +import { init, plugins } from '@alilc/lowcode-engine'; +import { RequestHandlersMap } from '@alilc/lowcode-datasource-types'; + +const preference = new Map(); + +(async function main() { + await plugins.register(scenarioSwitcher); + await registerPlugins(); + + init(document.getElementById('lce-container')!, { + // designMode: 'live', + // locale: 'zh-CN', + enableCondition: true, + enableCanvasLock: true, + // 默认绑定变量 + supportVariableGlobally: true, + // simulatorUrl 在当 engine-core.js 同一个父路径下时是不需要配置的!!! + // 这里因为用的是 alifd cdn,在不同 npm 包,engine-core.js 和 react-simulator-renderer.js 是不同路径 + simulatorUrl: [ + 'https://alifd.alicdn.com/npm/@alilc/lowcode-react-simulator-renderer@latest/dist/css/react-simulator-renderer.css', + 'https://alifd.alicdn.com/npm/@alilc/lowcode-react-simulator-renderer@latest/dist/js/react-simulator-renderer.js' + ], + requestHandlersMap: { + fetch: createAxiosFetchHandler() + } + }, preference); +})(); + +``` +## 🥡 附录 +### 数据源协议 +| **参数** | **说明** | **类型** | **支持变量** | **默认值** | **备注** | +| --- | --- | --- | --- | --- | --- | +| id | 数据请求 ID 标识 | String | - | - | | +| isInit | 是否为初始数据 | Boolean | ✅ | true | 值为 true 时,将在组件初始化渲染时自动发送当前数据请求 | +| isSync | 是否需要串行执行 | Boolean | ✅ | false | 值为 true 时,当前请求将被串行执行 | +| type | 数据请求类型 | String | - | fetch | 支持四种类型:fetch/mtop/jsonp/custom | +| shouldFetch | 本次请求是否可以正常请求 | (options: ComponentDataSourceItemOptions) => boolean | - | () => true | function 参数参考 [ComponentDataSourceItemOptions 对象描述](https://lowcode-engine.cn/lowcode#2315-componentdatasourceitemoptions-%E5%AF%B9%E8%B1%A1%E6%8F%8F%E8%BF%B0) | +| willFetch | 单个数据结果请求参数处理函数 | Function | - | options => options | 只接受一个参数(options),返回值作为请求的 options,当处理异常时,使用原 options。也可以返回一个 Promise,resolve 的值作为请求的 options,reject 时,使用原 options | +| requestHandler | 自定义扩展的外部请求处理器 | Function | - | - | 仅 type=‘custom’ 时生效 | +| dataHandler | request 成功后的回调函数 | Function | - | response => response.data | 参数: 请求成功后 promise 的 value 值 | +| errorHandler | request 失败后的回调函数 | Function | - | - | 参数: 请求出错 promise 的 error 内容 | +| options {} | 请求参数 | **ComponentDataSourceItemOptions** | - | - | 每种请求类型对应不同参数, 详见 [ComponentDataSourceItemOptions 对象描述](https://lowcode-engine.cn/lowcode#2315-componentdatasourceitemoptions-%E5%AF%B9%E8%B1%A1%E6%8F%8F%E8%BF%B0) | + +### 运行时实现层:数据源引擎设计 +[https://www.yuque.com/lce/doc/datasource-engine](https://www.yuque.com/lce/doc/datasource-engine) diff --git a/docs/docs/demoUsage/panels/settings.md b/docs/docs/demoUsage/panels/settings.md new file mode 100644 index 000000000..03c28eabf --- /dev/null +++ b/docs/docs/demoUsage/panels/settings.md @@ -0,0 +1,217 @@ +--- +title: 6. 设置面板详解 +sidebar_position: 2 +--- +# 设置器介绍 +## 展示区域 +设置器,又称为 Setter,主要展示在编辑器的右边区域,如下图: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1647695118402-ac146307-f6e2-4755-8be3-67278c505283.png#clientId=u547a37e3-c43d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=865&id=u3cac31de&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1730&originWidth=3836&originalType=binary&ratio=1&rotation=0&showTitle=false&size=947162&status=done&style=none&taskId=u35373859-102e-4809-adfd-680b2dd4cda&title=&width=1918) +其中包含 属性、样式、事件、高级 + +- 属性:展示该物料常规的属性 +- 样式:展示该物料样式的属性 +- 事件:如果该物料有声明事件,则会出现事件面板,用于绑定事件。 +- 高级:两个逻辑相关的属性,**条件渲染**和**循环** + + + +## 设置器 +上述区域中是有多项设置器的,对于一个组件来说,每一项配置都对应一个设置器,比如我们的配置是一个文本,我们需要的是文本设置器,我们需要配置的是数字,我们需要的就是数字设置器。 +下图中的标题和按钮类型配置就分别是文本设置器和下拉框设置器。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1647695118227-bf6caf7c-4974-4b35-8d6b-0c4969fc316d.png#clientId=u547a37e3-c43d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=407&id=u51d889e6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1460&originWidth=2120&originalType=binary&ratio=1&rotation=0&showTitle=false&size=489840&status=done&style=none&taskId=u35d4519f-b82d-43c4-9eb4-bd44e6d67b1&title=&width=591) +我们提供了常用的设置器作为内置设置器,也提供了定制能力帮助大家开发特定需求的设置器。 +# 内置设置器 +| **预置 Setter** | **用途** | +| --- | --- | +| StringSetter | 短文本型数据设置器,不可换行 | +| NumberSetter | 数值型数据设置器, | +| BoolSetter | 布尔型数据设置器, | +| SelectSetter | 枚举型数据设置器,采用下拉的形式展现 | +| VariableSetter | 变量型数据设置器, | +| RadioGroupSetter | 枚举型数据设置器,采用tab选择的形式展现 | +| TextAreaSetter | 长文本型数据设置器,可换行 | +| DateSetter | 日期型数据设置器 | +| TimePicker | 时间型数据设置器 | +| DateYearSetter | 日期型-年数据设置器 | +| DateMonthSetter | 日期型-月数据设置器 | +| DateRangeSetter | 日期型数据设置器,可选择时间区间 | +| EventsSetter | 事件绑定设置器 | +| ColorSetter | 颜色设置器 | +| JsonSetter | json型数据设置器 | +| StyleSetter | 样式设置器 | +| ClassNameSetter | 样式名设置器 | +| FunctionSetter | 函数型数据设置器 | +| MixedSetter | 混合型数据设置器 | +| SlotSetter | 节点型数据设置器 | +| ArraySetter | 列表数组行数据设置器 | +| ObjectSetter | 对象数据设置器,一般内嵌在ArraySetter中 | + + +# 设置器定制 +## 编写 AltStringSetter +我们编写一个简单的 Setter,这里我们编写的 Setter 是 AltStringSetter。代码如下: +```javascript +import * as React from "react"; +import { Input } from "@alifd/next"; + +import "./index.scss"; +interface AltStringSetterProps { + // 当前值 + value: string; + // 默认值 + defaultValue: string; + // setter唯一输出 + onChange: (val: string) => void; + // AltStringSetter 特殊配置 + placeholder: string; +} +export default class AltStringSetter extends React.PureComponent { + componentDidMount() { + const { onChange, value, defaultValue } = this.props; + if (value == undefined && defaultValue) { + onChange(defaultValue); + } + } + + // 声明Setter的title + static displayName = 'AltStringSetter'; + + render() { + const { onChange, value, placeholder } = this.props; + return ( + onChange(val)} + > + ); + } +} +``` + +### setter 和 setter/plugin 之间的联动 +我们采用 emit 来进行相互之前的通信,首先我们在 A setter 中进行事件注册: +```javascript +import { event } from '@ali/lowcode-engine'; + +componentDidMount() { + // 这里由于面板上会有多个setter,这里我用field.id来标记setter名 + this.emitEventName = `${SETTER_NAME}-${this.props.field.id}`; + event.on(`${this.emitEventName}.bindEvent`, this.bindEvent) +} + +bindEvent = (eventName) => { + // do someting +} + +componentWillUnmount() { + // setter是以实例为单位的,每个setter注销的时候需要把事件也注销掉,避免事件池过多 + event.off(`${this.emitEventName}.bindEvent`, this.bindEvent) +} +``` +在 B setter 中触发事件,来完成通信: +```javascript +import { event } from '@ali/lowcode-engine'; + +bindFunction = () => { + const { field, value } = this.props; + // 这里展示的和插件进行通信,事件规则是插件名+方法 + event.emit('eventBindDialog.openDialog', field.name, this.emitEventName); +} +``` +### 修改同级 props 的其他属性值 +setter 本身只影响其中一个 props 的值,如果需要影响其他组件的 props 的值,需要使用 field 的 props: +```javascript +bindFunction = () => { + const { field, value } = this.props; + const propsField = field.parent; + // 获取同级其他属性showJump的值 + const otherValue = propsField.getPropValue('showJump'); + // set同级其他属性showJump的值 + propsField.setPropValue('showJump', false); +} +``` +## 注册 AltStringSetter +我们需要在低代码引擎中注册 Setter,这样就可以通过 AltStringSetter 的名字在物料中使用了。 +```javascript +import AltStringSetter from './AltStringSetter'; +import { setters } from '@alilc/lowcode-engine'; +setters.registerSetter({ + AltStringSetter: { + component: AltStringSetter, + } +}); +``` +## 物料中使用 +我们需要将目标组件的属性值类型值配置到物料资源配置文件中,例如 `packages/demo/public/assets.json`  +其中核心配置如下: +```json +{ + "props": { + "isExtends": true, + "override": [ + { + "name": "type", + "setter": "AltStringSetter" + } + ] + } +} +``` +在物料中的完整配置如下: +```json +{ + "componentName": "Message", + "title": "Message", + "docUrl": "", + "screenshot": "", + "npm": { + "package": "@alifd/next", + "version": "1.19.18", + "exportName": "Message", + "main": "src/index.js", + "destructuring": true, + "subName": "" + }, + "props": [ + { + "name": "title", + "propType": "string", + "description": "标题", + "defaultValue": "标题" + }, + { + "name": "type", + "propType": { + "type": "oneOf", + "value": [ + "success", + "warning", + "error", + "notice", + "help", + "loading" + ] + }, + "description": "反馈类型", + "defaultValue": "success" + } + + ], + + "configure": { + "props": { + "isExtends": true, + "override": [ + { + "name": "type", + "setter": "AltStringSetter" + } + ] + } + } +} +``` +### +# 小结 +本章介绍了设置器是什么,我们有哪些内置的设置器。以及当不满足设置器诉求时,我们如何定制一个设置器。 diff --git a/docs/docs/faq/faq001.md b/docs/docs/faq/faq001.md new file mode 100644 index 000000000..56305cf2d --- /dev/null +++ b/docs/docs/faq/faq001.md @@ -0,0 +1,6 @@ +--- +title: build-scripts 的使用文档 +sidebar_position: 1 +tags: [FAQ] +--- +build-scripts 是一个开源项目,详见 [https://github.com/ice-lab/build-scripts](https://github.com/ice-lab/build-scripts) diff --git a/docs/docs/faq/faq002.md b/docs/docs/faq/faq002.md new file mode 100644 index 000000000..45e1c0c50 --- /dev/null +++ b/docs/docs/faq/faq002.md @@ -0,0 +1,36 @@ +--- +title: 渲染唯一标识(key) +sidebar_position: 2 +tags: [FAQ] +--- +渲染唯一标识(key)和 React 中组件的 key 属性的原理是一致的,都是为了在渲染场景或者组件切换的场景中唯一标识一个组件。 + +你可以在组件右侧配置面板的「高级」中看到此配置项,该配置项一般配合「是否渲染」和「循环」功能使用。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1659076591507-f37841b4-a3c2-4c87-b875-5f0458b2a3d2.png#clientId=u87aade85-8350-4&crop=0&crop=0&crop=1&crop=1&height=348&id=dbojA&margin=%5Bobject%20Object%5D&name=image.png&originHeight=696&originWidth=560&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102096&status=done&style=none&taskId=ub4a95aaa-8630-47ad-b0e3-881b5d54ac9&title=&width=280) + +## 以下场景必需设置「渲染唯一标识」 +#### 场景一:同类组件切换 +以下场景中,当「爱好」选择「游戏」时显示「最喜欢的游戏」,选择「运动」时显示「最喜欢的运动」 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1659076591520-b2e1121b-3f4e-4c7c-88c8-82a761b85fe0.png#clientId=u87aade85-8350-4&crop=0&crop=0&crop=1&crop=1&height=294&id=Xr3NB&margin=%5Bobject%20Object%5D&name=image.png&originHeight=588&originWidth=1560&originalType=binary&ratio=1&rotation=0&showTitle=false&size=78723&status=done&style=none&taskId=u59b975eb-93a1-4c8a-bb3b-17ef8b7005c&title=&width=780) + +配置方式如下: + +1. 增加变量数据源:hobby +2. 「最喜欢的游戏」表单标识设置为 game,「是否渲染」绑定变量「state.hobby === '游戏'」 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1659076591653-27fac7ab-bba0-4965-a706-d0c1c867f539.png#clientId=u87aade85-8350-4&crop=0&crop=0&crop=1&crop=1&height=369&id=f8kif&margin=%5Bobject%20Object%5D&name=image.png&originHeight=738&originWidth=2164&originalType=binary&ratio=1&rotation=0&showTitle=false&size=306076&status=done&style=none&taskId=ub6da537a-3bc7-450d-b640-dc0db8defac&title=&width=1082) + +3. 「最喜欢的运动」表单标识设置为 sport,「是否渲染」绑定变量「state.hobby === '运动'」 +4. 「爱好」设置 onChange 动作 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1659076591442-2903e2b2-4688-4a5e-98de-7df9933710b5.png#clientId=u87aade85-8350-4&crop=0&crop=0&crop=1&crop=1&height=97&id=hR3Pp&margin=%5Bobject%20Object%5D&name=image.png&originHeight=194&originWidth=892&originalType=binary&ratio=1&rotation=0&showTitle=false&size=53803&status=done&style=none&taskId=ue8919283-2e5f-46a8-9d6b-942c03f8482&title=&width=446) + +5. 「提交」按钮绑定 onClick 动作 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1659076591508-ddd3c767-bf22-4dae-a006-5e6c4e0ea956.png#clientId=u87aade85-8350-4&crop=0&crop=0&crop=1&crop=1&height=67&id=VWtPz&margin=%5Bobject%20Object%5D&name=image.png&originHeight=134&originWidth=750&originalType=binary&ratio=1&rotation=0&showTitle=false&size=46519&status=done&style=none&taskId=ud6a5d2b0-481b-4bbb-8fe3-8622e2ebfb3&title=&width=375) + +按以上配置(不配置渲染唯一标识),确实可以实现切换爱好时下方的文本框切换,但在提交数据时会发现,即使选择了「运动」,提交的时候 sport 字段是「最喜欢的游戏」的值。 + +原因:在进行文本框组件切换时,由于没有设置 key,底层会「复用」切换之前的组件 + +以上场景只需要给两个组件配置「渲染唯一标识」即可。 diff --git a/docs/docs/faq/faq003.md b/docs/docs/faq/faq003.md new file mode 100644 index 000000000..96e01c1fd --- /dev/null +++ b/docs/docs/faq/faq003.md @@ -0,0 +1,32 @@ +--- +title: 点击事件如何添加参数 +sidebar_position: 3 +tags: [FAQ] +--- +背景: + +- [Antd Table 下 button 点击事件怎么拿到行数据?](https://github.com/alibaba/lowcode-engine/issues/341) +## 方式1 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1657593243427-fb5641b2-4987-475e-88ab-c68d2085edbd.png#clientId=u31b40d04-56f2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=563&id=u5167bf33&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1126&originWidth=3342&originalType=binary&ratio=1&rotation=0&showTitle=false&size=225551&status=done&style=none&taskId=ud1b89a63-4b6a-4986-a6df-2a463fcf08a&title=&width=1671) + +参考 fusion protable , 将操作列直接耦合 button 组件,因为 col.render 函数能拿到 行数据record,那么 pro-table 组件封装的时候,就可以在渲染操作列按钮的时候,将 col.render 参数透传给 button 组件 + +## 方式2 +slot + 扩展参数 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1657593299698-9628db14-7b48-4c06-9e6f-bda637c209a8.png#clientId=u31b40d04-56f2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=574&id=u20b07439&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1148&originWidth=3284&originalType=binary&ratio=1&rotation=0&showTitle=false&size=232140&status=done&style=none&taskId=ubc80905d-0607-4e73-9386-5dde706e572&title=&width=1642) + +将扩展参数写成: +```json +{ + record: this.record, + index: this.index +} +``` + +那事件处理函数的第二个参数即可得到: +```json +onClick_new_new(...args){ + console.log(args) +} +``` diff --git a/docs/docs/faq/faq004.md b/docs/docs/faq/faq004.md new file mode 100644 index 000000000..67431c0e5 --- /dev/null +++ b/docs/docs/faq/faq004.md @@ -0,0 +1,59 @@ +--- +title: 最小渲染单元配置 +sidebar_position: 4 +tags: [FAQ] +--- +## 背景 +在低代码引擎画布中,每一个节点的更新是**增量更新**机制。也就是通过 API 监听到组件的参数配置变化的时候,只更新该节点。 + +一些场景下,父组件需要在子组件的属性变化,获取新的子组件的属性值,也就是从父组件开始渲染。 +例如:父组件需要强制修改 children props 值。示例代码如下: +``` +React.Children.forEach(children, (child: React.ReactElement) => { + // 子元素的参数只有 behavior,且 behavior 为 'READONLY'; + const newChild = React.cloneElement(child, { + behavior: 'READONLY' + }); +}) +``` + +**对于这种场景,需要配置其为“最小渲染单元”****。**即: +> **最小渲染单元下的组件渲染和更新都从单元的根节点开始渲染和更新。如果嵌套了多层最小渲染单元,渲染会从最外层的最小渲染单元开始渲染。** + + +### 组件能力配置 component +| **字段** | **用途** | **类型** | +| --- | --- | --- | +| isContainer(A) | 是否容器组件 | Boolean | +| **isMinimalRenderUnit** | **默认值:false** +**是否是最小渲染单元** | **Boolean** | +| ... | | | + +#### 配置示例 +##### 标准配置文件 +configure.component.isMinimalRenderUnit +```json +{ + "componentName": "Table", + "title": "表格", + "category": "数据展示", + "props": [], + "configure": { + "supports": { + }, + "props": [], + "component": { + // 添加如下配置 + "isMinimalRenderUnit": true + }, + "combined": [] + }, + "snippets": [], + "npm": {} +} +``` +## 更新机制说明 + +1. 没有任何组件被标识为**最小渲染单元**,则是每个组件都伴随自身属性变更而重新渲染; +2. 将根组件标识为**最小渲染单元**,则是整个页面重新渲染; +3. 组件树的分支节点被标识为**最小渲染单元**,则分支节点之下(包括自身)节点属性变更,触发分支节点整体的重新渲染;(若有多个**最小渲染单元**在同一条路径上,从最外层的**最小渲染单元**开始渲染) diff --git a/docs/docs/faq/faq005.md b/docs/docs/faq/faq005.md new file mode 100644 index 000000000..03efb55b4 --- /dev/null +++ b/docs/docs/faq/faq005.md @@ -0,0 +1,23 @@ +--- +title: 如何通过 this.utils 使用第三方工具扩展 +sidebar_position: 5 +tags: [FAQ] +--- +## 设计器 +### 通过引擎 API 配置 +[https://www.yuque.com/lce/doc/dffghx](https://www.yuque.com/lce/doc/dffghx) + +### 通过资产包 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1657273720962-70045da1-7559-4f7f-a3da-35759778066c.png#averageHue=%231f221e&clientId=ueb9290e1-769e-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=394&id=u698ffae7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=788&originWidth=806&originalType=binary&ratio=1&rotation=0&showTitle=false&size=175357&status=error&style=none&taskId=u2ed6a3cd-5ec4-4b84-a3c0-fd3379b7019&title=&width=403) + +就可以在引擎代码中访问到 moment +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1657273737110-6c064e4c-6435-456a-b168-480058b14da8.png#averageHue=%23fdfdfc&clientId=ueb9290e1-769e-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=126&id=u4ec5fba0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=252&originWidth=1248&originalType=binary&ratio=1&rotation=0&showTitle=false&size=128347&status=error&style=none&taskId=udd229103-04e3-4fc5-9fba-9115354bb9d&title=&width=624) + +PS:需要在 packages 中有相关的资源配置,例如: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1657273863884-2068444e-5653-4b25-ba3a-fd192409fbaa.png#averageHue=%231f1f1d&clientId=ueb9290e1-769e-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=210&id=ud2a4b3c6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=420&originWidth=1322&originalType=binary&ratio=1&rotation=0&showTitle=false&size=113099&status=error&style=none&taskId=u7ba0f438-9c39-4398-b048-9d0556e2079&title=&width=661) + +否则在画布中可能会访问不到对应的资源。 + +## 预览态 +参考:[https://www.yuque.com/lce/doc/nhilce#appHelper](https://www.yuque.com/lce/doc/nhilce#appHelper) diff --git a/docs/docs/faq/faq006.md b/docs/docs/faq/faq006.md new file mode 100644 index 000000000..0553d5122 --- /dev/null +++ b/docs/docs/faq/faq006.md @@ -0,0 +1,6 @@ +--- +title: 如何通过 API 手动调用数据源请求 +sidebar_position: 6 +tags: [FAQ] +--- +参考:[https://www.yuque.com/lce/doc/erckcn](https://www.yuque.com/lce/doc/erckcn) \ No newline at end of file diff --git a/docs/docs/faq/faq007.md b/docs/docs/faq/faq007.md new file mode 100644 index 000000000..175d8c73f --- /dev/null +++ b/docs/docs/faq/faq007.md @@ -0,0 +1,9 @@ +--- +title: 设置面板中的高级tab如何配置 +sidebar_position: 7 +tags: [FAQ] +--- +![93DC003C-D9A1-4F4F-98A1-F22C0A89EA92.png](https://cdn.nlark.com/yuque/0/2022/png/1053439/1657161085153-a26657ae-2c6e-4124-b9ab-6f8cf8126d1f.png#clientId=u300df630-5bbe-4&crop=0&crop=0&crop=1&crop=1&from=ui&height=591&id=u2ff7824e&margin=%5Bobject%20Object%5D&name=93DC003C-D9A1-4F4F-98A1-F22C0A89EA92.png&originHeight=1714&originWidth=960&originalType=binary&ratio=1&rotation=0&showTitle=false&size=107040&status=done&style=none&taskId=ub377dc1d-db5a-4234-980f-66f7143950d&title=&width=331) + +默认这个tab下的内容为引擎内置,如需要定制,可以使用以下API +[https://lowcode-engine.cn/docV2/mu7lml#lIK37](https://lowcode-engine.cn/docV2/mu7lml#lIK37) diff --git a/docs/docs/faq/faq008.md b/docs/docs/faq/faq008.md new file mode 100644 index 000000000..c64a09ae2 --- /dev/null +++ b/docs/docs/faq/faq008.md @@ -0,0 +1,7 @@ +--- +title: 某某npm包对应的源码在哪里? +sidebar_position: 8 +tags: [FAQ] +--- + +详见 [NPM包对应源码位置汇总](https://www.yuque.com/lce/doc/ngm44i) \ No newline at end of file diff --git a/docs/docs/faq/faq009.md b/docs/docs/faq/faq009.md new file mode 100644 index 000000000..131dcba6f --- /dev/null +++ b/docs/docs/faq/faq009.md @@ -0,0 +1,74 @@ +--- +title: 物料出现 Component Not Found 相关报错 +sidebar_position: 9 +tags: [FAQ] +--- +## 预览态,antd 资产包按顺序加载,但是没有按顺序执行 +资产包按顺序加载,但是没有按顺序执行,导致部分 js 执行的时候,依赖的资源没有准备好,报错了。 +传给 @alilc/lowcode-react-renderer 的 components 值为空。 + +**解决方案** +LowCodeEngine 升级到 1.0.8 +> 相关PR:[https://github.com/alibaba/lowcode-engine/pull/383](https://github.com/alibaba/lowcode-engine/pull/383) + + +## 编辑态,snippets 和注入组件不对应 +1.在控制台中输入 +```json +AliLowCodeEngine.material.componentsMap +``` +查看物料配置是否正常。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1655277296065-40eeae64-1323-4f7d-89c3-bc48c928aca4.png#clientId=u21fd51ec-6ae9-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=408&id=u21239467&margin=%5Bobject%20Object%5D&name=image.png&originHeight=816&originWidth=1640&originalType=binary&ratio=1&rotation=0&showTitle=false&size=379372&status=done&style=none&taskId=u12a736de-695e-45b9-817b-0f9f6b3a601&title=&width=820) + +如果正常继续。 +LowCodeEngine 需要升级到 1.0.10 +```json +AliLowCodeEngine.project.simulator.renderer.components +``` +看看对应的物料是否存在,如果不存在,排查物料问题。 + +2.选中组件,在控制台中输入 +```json +AliLowCodeEngine.project.currentDocument.selection.getNodes()[0].exportSchema('render') +``` +查看 componentName 是否匹配。 + +3.调用 rerender 方法 +```json +AliLowCodeEngine.project.simulator.rerender() +``` +看一下问题是否恢复。 + +## 排查物料问题 +找到对应组件的资产包,比如下图的资产包。 +```json +{ + "package": "@yingzhi8/lowcode-public-package", + "version": "0.1.2", + "library": "BizComps", + "urls": [ + "https://unpkg.com/@yingzhi8/lowcode-public-package@0.1.2/build/lowcode/render/default/view.js", + "https://unpkg.com/@yingzhi8/lowcode-public-package@0.1.2/build/lowcode/render/default/view.css" + ], + "editUrls": [ + "https://unpkg.com/@yingzhi8/lowcode-public-package@0.1.2/build/lowcode/view.js", + "https://unpkg.com/@yingzhi8/lowcode-public-package@0.1.2/build/lowcode/view.css" + ], + "advancedUrls": { + "default": [ + "https://unpkg.com/@yingzhi8/lowcode-public-package@0.1.2/build/lowcode/render/default/view.js", + "https://unpkg.com/@yingzhi8/lowcode-public-package@0.1.2/build/lowcode/render/default/view.css" + ] + }, + "advancedEditUrls": {} +} +``` + +### 查看 urls 是否加载 +通过查看控制台,看加载的 urls +### library 配置是否正确 +library 是可以在画布上访问到全局变量,确定 library 是否正确,在控制台输入: +```json +AliLowCodeEngine.project.simulator.contentWindow.${library} +``` diff --git a/docs/docs/faq/faq010.md b/docs/docs/faq/faq010.md new file mode 100644 index 000000000..e98ac7f68 --- /dev/null +++ b/docs/docs/faq/faq010.md @@ -0,0 +1,18 @@ +--- +title: 插件面板如何调整位置 +sidebar_position: 10 +tags: [FAQ] +--- +使用 index 配置来定义位置,内置的默认都是 0 +```json +AliLowCodeEngine.skeleton.add({ + area: 'leftArea', + type: 'PanelDock', + name: 'xxx', + content: () => 'xxx', + index: -1, // 使用 index 来定义位置,内置的默认都是 0 + props: { icon: () => 'x' /* ReactElement 也可以 */ }, +}); +``` +这里设置 index 为负数,可以将其调整到第一的位置。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1653964459415-694283f6-9c5f-4143-b6d4-51b5aa37f719.png#clientId=uaed53506-efef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=579&id=u99009edf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1158&originWidth=1614&originalType=binary&ratio=1&rotation=0&showTitle=false&size=390356&status=done&style=none&taskId=ue153a8c6-13b0-48fa-bfe9-a0d9f7b6cc3&title=&width=807) diff --git a/docs/docs/faq/faq011.md b/docs/docs/faq/faq011.md new file mode 100644 index 000000000..793ff7af1 --- /dev/null +++ b/docs/docs/faq/faq011.md @@ -0,0 +1,29 @@ +--- +title: 如何获取物料当前处于编辑态还是渲染态 +sidebar_position: 11 +tags: [FAQ] +--- +## 简单场景 +可以利用 props.__designMode +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1653962601596-46e21531-9b8c-45bd-84a7-7522856eb3c9.png#clientId=u8f8d6439-2532-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=220&id=u4d7df210&margin=%5Bobject%20Object%5D&name=image.png&originHeight=440&originWidth=1616&originalType=binary&ratio=1&rotation=0&showTitle=false&size=232329&status=done&style=none&taskId=u85deaa53-ef14-4ce2-a5a2-ac201aa60f5&title=&width=808) + +设计态中,__designMode 值为 "design" + +渲染态中,__designMode 没有设置值 + +## 复杂场景 +在资产包里定义 editUrls +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1653962648722-ac8ff758-a0c3-4323-8312-11ac9b511ecf.png#clientId=u8f8d6439-2532-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=269&id=uad79aeaf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=538&originWidth=1590&originalType=binary&ratio=1&rotation=0&showTitle=false&size=257391&status=done&style=none&taskId=u3d8d556b-2da7-40b4-b7e6-cd3600cfbd4&title=&width=795) + +### editUrls +在 lowcode/xx/ 下新建一个 view.tsx +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1653971463822-3335e539-6b97-43a7-adf9-cba221a68d87.png#clientId=u89265c24-4294-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=77&id=u466ed474&margin=%5Bobject%20Object%5D&name=image.png&originHeight=154&originWidth=598&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31756&status=done&style=none&taskId=u1f7eeec2-323c-45c7-a1f7-e18bfa3a1db&title=&width=299) + +再执行 +```json +npm run lowcode:build +``` + +之后,build/lowcode 目录下既有 view.js,可作为 editUrls 配置在资产包中。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1653971523747-c15e63e6-9fb7-481e-bf5b-c4bd6cb25927.png#clientId=u89265c24-4294-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=493&id=ucaf08028&margin=%5Bobject%20Object%5D&name=image.png&originHeight=986&originWidth=1082&originalType=binary&ratio=1&rotation=0&showTitle=false&size=235573&status=done&style=none&taskId=ue21c1ab4-0d1b-49fb-8889-b7210b90d41&title=&width=541) diff --git a/docs/docs/faq/faq012.md b/docs/docs/faq/faq012.md new file mode 100644 index 000000000..6c0e61db0 --- /dev/null +++ b/docs/docs/faq/faq012.md @@ -0,0 +1,90 @@ +--- +title: Procode 物料如何调用数据源方法 +sidebar_position: 12 +tags: [FAQ] +--- +## 解决方案 +给物料插入如下配置,可以默认给物料提供 reloadDataSource 的参数。 +```json +{ + title: { + label: { + type: 'i18n', + 'en-US': 'reloadDataSource', + 'zh-CN': 'reloadDataSource', + }, + }, + name: 'reloadDataSource', + setter: 'StringSetter', + initialValue: () => ( + { + "type": "JSFunction", + "value": "function(){ return this.reloadDataSource; }" + } + ), +}, +``` + +在物料组件中,即可掉用如下代码来获取到相关方法。 +```json +const reloadDataSource = props.getReloadDataSource() +``` + +## FAQ +### 希望该配置在配置面板中不展示 +在配置中加上 +```json +condition: () => false, +``` + +完整示例 +```json +{ + title: { + label: { + type: 'i18n', + 'en-US': 'reloadDataSource', + 'zh-CN': 'reloadDataSource', + }, + }, + name: 'reloadDataSource', + setter: 'StringSetter', + condition: () => false, + initialValue: () => ( + { + "type": "JSFunction", + "value": "function(){ return this.reloadDataSource; }" + } + ), +}, +``` + +### 配置没有生效 +查看组件中的 schema,对应的配置是否已经正确设置。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1653894934869-963be13a-1d6a-458a-a1e1-fd21fa9fc765.png#clientId=uc4e9cec0-362f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=577&id=u956bc36a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1154&originWidth=1046&originalType=binary&ratio=1&rotation=0&showTitle=false&size=339838&status=done&style=none&taskId=uf64dae1b-23e9-4444-ab20-9e5f17a5449&title=&width=523) + +没有正确设置上可能的原因是 +1.snippets 中没有默认值 +需要按照如下的代码中,加上默认的参数配置 +```json +const snippets: Snippet[] = [ + { + title: 'Field', + screenshot: '', + schema: { + componentName: 'ProField', + props: { + type: 'textarea', + value: '我是测试', + getReloadDataSource: { + "type": "JSFunction", + "value": "function(){ return this.reloadDataSource; }" + } + }, + }, + }, +]; +``` + +### 如何全局生效 +通过 [https://www.yuque.com/lce/doc/mu7lml#LRXhp](https://www.yuque.com/lce/doc/mu7lml#LRXhp) 来修改元数据信息,注意如果有 snippets 相关配置也需要修改相关的配置。 diff --git a/docs/docs/faq/faq013.md b/docs/docs/faq/faq013.md new file mode 100644 index 000000000..2d8f6f703 --- /dev/null +++ b/docs/docs/faq/faq013.md @@ -0,0 +1,22 @@ +--- +title: Modal 类组件 hidden 属性被强制设置 true +sidebar_position: 13 +tags: [FAQ] +--- +## 注意 +弹窗的正确弹出方式请参考:[https://www.yuque.com/lce/usage/ozsg2m](https://www.yuque.com/lce/usage/ozsg2m) +## 问题原因 +由于 hidden 属性,导致 Modal 组件在预览的时候不渲染,也就无法获取到实例。 +## 处理方式 +### 【推荐】升级到 Engine Verison 1.0.11 以上 +### 新增 save propsReducer +通过新增 Save 态的 propsReducer,将 hidden props 去掉。 +参考: +[https://github.com/alibaba/lowcode-demo/blob/main/src/sample-plugins/delete-hidden-transducer/index.ts](https://github.com/alibaba/lowcode-demo/blob/main/src/sample-plugins/delete-hidden-transducer/index.ts) + +### 导出 schema 使用 Save 态 +```typescript +import { TransformStage } from '@alilc/lowcode-types'; + +const schema = project.exportSchema(TransformStage.Save) +``` diff --git a/docs/docs/faq/faq014.md b/docs/docs/faq/faq014.md new file mode 100644 index 000000000..9c72b2a2a --- /dev/null +++ b/docs/docs/faq/faq014.md @@ -0,0 +1,20 @@ +--- +title: VERSION_PLACEHOLDER is not defined +sidebar_position: 14 +tags: [FAQ] +--- +# 问题原因 +由于 lowcode-engine 目前只提供 cdn 的使用方式。如果是自己创建的项目,遇到这个报错了,主要是因为将 npm 包打包进去了。 + +# 解决方案 + +## engine-demo 项目 +在项目的 externals 配置里加[一行配置](https://github.com/alibaba/lowcode-demo/blob/f8afad0df3190565caccc0a1dfd750dbf84c680f/build.json#L16) + +## 其他项目 +[相关文档](https://lowcode-engine.cn/docV2/start-with-lce#OMRA2) +### webpack +[https://webpack.docschina.org/configuration/externals/](https://webpack.docschina.org/configuration/externals/) + +### 使用文档 +待补充 diff --git a/docs/docs/faq/faq015.md b/docs/docs/faq/faq015.md new file mode 100644 index 000000000..02ca13915 --- /dev/null +++ b/docs/docs/faq/faq015.md @@ -0,0 +1,7 @@ +--- +title: 已有组件如何快速接入引擎 +sidebar_position: 15 +tags: [FAQ] +--- +你可以通过在线工具 「Parts造物」生产物料描述协议,然后使用到你的项目中去。 +文档地址:[https://www.yuque.com/lce/xhk5hf/qa9pbx](https://www.yuque.com/lce/xhk5hf/qa9pbx) diff --git a/docs/docs/faq/faq016.md b/docs/docs/faq/faq016.md new file mode 100644 index 000000000..bb72e17ec --- /dev/null +++ b/docs/docs/faq/faq016.md @@ -0,0 +1,19 @@ +--- +title: Cannot read property 'Icon' of Undefined +sidebar_position: 16 +tags: [FAQ] +--- +@alifd/next 是 React 画布下必须的资源,不能省略。 +需要在资产包中检查是否有下列代码: +```typescript +{ + "title": "fusion组件库", + "package": "@alifd/next", + "version": "1.23.0", + "urls": [ + "https://g.alicdn.com/code/lib/alifd__next/1.23.18/next.min.css", + "https://g.alicdn.com/code/lib/alifd__next/1.23.18/next-with-locales.min.js" + ], + "library": "Next" +}, +``` diff --git a/docs/docs/faq/faq017.md b/docs/docs/faq/faq017.md new file mode 100644 index 000000000..f2c7afa27 --- /dev/null +++ b/docs/docs/faq/faq017.md @@ -0,0 +1,11 @@ +--- +title: vue 画布支持说明 +sidebar_position: 17 +tags: [FAQ] +--- +#### 低代码引擎官方是否支持 Vue 画布 +短期没有支持的计划 + +#### 社区研发的 Vue 画布 +##### 肯耐珂萨团队实现的 Vue 画布 +github:[https://github.com/KNXCloud/lowcode-engine-vue](https://github.com/KNXCloud/lowcode-engine-vue) diff --git a/docs/docs/faq/faq018.md b/docs/docs/faq/faq018.md new file mode 100644 index 000000000..a7b79e44d --- /dev/null +++ b/docs/docs/faq/faq018.md @@ -0,0 +1,10 @@ +--- +title: 是否可以生成 Vue 页面代码? +sidebar_position: 18 +tags: [FAQ] +--- +低代码引擎在架构上是和具体语言无关的,通过一定的扩展和插件是可以生成 Vue 页面代码的。 +如果只是用现有的基于 React 的 fusion 物料来搭建,只是在最终出码的时候生成 Vue 页面代码,那您需要准备一套和 fusion 兼容的 vue 物料,并定制个出码方案,将[下面的一些出码插件](https://github.com/alibaba/lowcode-engine/blob/main/modules/code-generator/src/solutions/icejs.ts)替换成生成 Vue 框架的即可: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/263300/1648542644942-c019ffd4-1312-4d31-ad61-4e487a47df71.png#clientId=ue0e8d3cc-f9f8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=517&id=u64b1f996&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1368&originWidth=974&originalType=binary&ratio=1&rotation=0&showTitle=false&size=771387&status=done&style=none&taskId=u51022017-4248-49b8-b8b3-be9a7bb48fb&title=&width=368) +详细定制方案可以参考下[《自定义出码》](https://lowcode-engine.cn/docV2/cplfv0#857ba793)。 +如果您希望在搭建的时候也使用 Vue 的物料,则还需要扩展定制入料、画布和渲染器等模块,详细方案请参考下[《扩展低代码编辑器》](https://lowcode-engine.cn/docV2/srdo3s) diff --git a/docs/docs/faq/faq019.md b/docs/docs/faq/faq019.md new file mode 100644 index 000000000..9808360d1 --- /dev/null +++ b/docs/docs/faq/faq019.md @@ -0,0 +1,10 @@ +--- +title: windows 下运行出现报错 +sidebar_position: 19 +tags: [FAQ] +--- +由于阿里内部研发人员没有 windows 开发的诉求,windows 环境下相关的技术兼容短期内暂时没有支持计划。 + +辛苦使用 [WSL](https://docs.microsoft.com/zh-cn/windows/wsl/install) 在 windows 下进行低代码引擎相关的开发。 + +如果可以的话,欢迎大佬们提 PR 对 windows 开发环境进行兼容方面的支持。 diff --git a/docs/docs/faq/faq020.md b/docs/docs/faq/faq020.md new file mode 100644 index 000000000..641e64eb1 --- /dev/null +++ b/docs/docs/faq/faq020.md @@ -0,0 +1,44 @@ +--- +title: Can't import the named export from non ECMAScript module +sidebar_position: 20 +tags: [FAQ] +--- +如果您是自己配置的引擎打包,那么可能会遇到这个问题。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644896737710-a746e04d-bf4a-40a3-b917-a09235363c81.png#clientId=u627d7b4e-5fe3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=492&id=u06a9f219&margin=%5Bobject%20Object%5D&name=image.png&originHeight=984&originWidth=1912&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1210174&status=done&style=none&taskId=u2b829db8-026d-472a-baf4-d4660fb5a4a&title=&width=956) + +问题的根源是 code-editor 插件运行时直接依赖了 babel 来完成 jsx 编译,babel 从 7.17.0 开始依赖了使用 ESM 编写的 @ampproject/remapping@2.1.0。如果打包工具无法正确处理 ESM,则可能报错。 + +解决方案 1:锁定 babel 版本 + +如果您使用了 yarn,那么可以在 package.json 中: + +```typescript +"resolutions": { + "@babel/core": "~7.16.7", + "@babel/parser": "~7.16.7", + "@babel/preset-env": "~7.16.7", + "@babel/preset-react": "~7.16.7", + "@babel/standalone": "~7.16.7", + "@babel/traverse": "~7.16.7", + "@babel/types": "~7.16.7" +} +``` + +解决方案 2:编译层面配置。本例使用 build-script 配置,您可以用类似方法来配置您的 webpack: + +```typescript +module.exports = ({ onGetWebpackConfig }) => { + // see: https://github.com/ice-lab/build-scripts#%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91 + onGetWebpackConfig((config) => { + config.module // fixes https://github.com/graphql/graphql-js/issues/1272 + .rule('mjs$') + .test(/\.mjs$/) + .include + .add(/node_modules/) + .end() + .type('javascript/auto'); + return config; + }); +}; +``` diff --git a/docs/docs/faq/index.md b/docs/docs/faq/index.md new file mode 100644 index 000000000..ed31bad28 --- /dev/null +++ b/docs/docs/faq/index.md @@ -0,0 +1,7 @@ +--- +title: FAQ 概述 +sidebar_position: 1 +tags: [FAQ] +--- + +不定期将社区常见问题及答案维护到此处 \ No newline at end of file diff --git a/docs/docs/guide/appendix/_category_.json b/docs/docs/guide/appendix/_category_.json new file mode 100644 index 000000000..c5c96d856 --- /dev/null +++ b/docs/docs/guide/appendix/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "附录", + "position": 5, + "collapsed": false, + "collapsible": true +} diff --git a/docs/docs/guide/appendix/glossary.md b/docs/docs/guide/appendix/glossary.md new file mode 100644 index 000000000..e044f3e74 --- /dev/null +++ b/docs/docs/guide/appendix/glossary.md @@ -0,0 +1,5 @@ +--- +title: 名词解释 +sidebar_position: 0 +--- +![glossary](https://cdn.nlark.com/yuque/0/2022/jpeg/2622706/1648103397469-00227a70-e7ab-4a90-8378-c4da977250b2.jpeg) diff --git a/docs/docs/guide/appendix/metaSpec.md b/docs/docs/guide/appendix/metaSpec.md new file mode 100644 index 000000000..6adcc5907 --- /dev/null +++ b/docs/docs/guide/appendix/metaSpec.md @@ -0,0 +1,7 @@ +--- +title: 搭建组件协议结构 +sidebar_position: 1 +--- +完整协议请查看:[http://lowcode-engine.cn/material](http://lowcode-engine.cn/material) + +![](https://cdn.nlark.com/yuque/0/2022/jpeg/2622706/1646040400808-9a4a4d7d-6ad2-43be-8c97-d88f0cab7cc6.jpeg) diff --git a/docs/docs/guide/appendix/npms.md b/docs/docs/guide/appendix/npms.md new file mode 100644 index 000000000..9334140f4 --- /dev/null +++ b/docs/docs/guide/appendix/npms.md @@ -0,0 +1,49 @@ +--- +title: NPM包对应源码位置汇总 +sidebar_position: 3 +--- +| 包名 | 仓库 | 路径 | +| --- | --- | --- | +| @alilc/lowcode-code-generator | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | modules/code-generator | +| @alilc/lowcode-material-parser | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | modules/material-parser | +| @alilc/lowcode-designer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/designer | +| @alilc/lowcode-editor-core | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/editor-core | +| @alilc/lowcode-editor-skeleton | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/editor-skeleton | +| @alilc/lowcode-engine | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/engine | +| @alilc/lowcode-plugin-designer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/plugin-designer | +| @alilc/lowcode-plugin-outline-pane | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/plugin-outline-pane | +| @alilc/lowcode-rax-renderer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/rax-renderer | +| @alilc/lowcode-rax-simulator-renderer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/rax-simulator-renderer | +| @alilc/lowcode-react-renderer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/react-renderer | +| @alilc/lowcode-react-simulator-renderer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/react-simulator-renderer | +| @alilc/lowcode-renderer-core | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/renderer-core | +| @alilc/lowcode-shell | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/shell | +| @alilc/lowcode-types | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/types | +| @alilc/lowcode-utils | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/utils | +| @alilc/lowcode-datasource-engine | [https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) | packages/datasource-engine | +| @alilc/lowcode-datasource-fetch-handler | [https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) | packages/datasource-fetch-handler | +| @alilc/lowcode-datasource-jsonp-handler | [https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) | packages/datasource-jsonp-handler | +| @alilc/lowcode-datasource-mopen-handler | [https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) | packages/datasource-mopen-handler | +| @alilc/lowcode-datasource-mtop-handler | [https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) | packages/datasource-mtop-handler | +| @alilc/lowcode-datasource-types | [https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) | packages/datasource-types | +| @alilc/lowcode-datasource-universal-mtop-handler | [https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) | packages/datasource-universal-mtop-handler | +| @alilc/lowcode-datasource-url-params-handler | [https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) | packages/datasource-url-params-handler | +| @alilc/build-plugin-alt | [https://github.com/alibaba/lowcode-tools](https://github.com/alibaba/lowcode-tools) | packages/build-plugin-alt | +| @alilc/create-element | [https://github.com/alibaba/lowcode-tools](https://github.com/alibaba/lowcode-tools) | packages/create-element | +| @alilc/lowcode-plugin-inject | [https://github.com/alibaba/lowcode-tools](https://github.com/alibaba/lowcode-tools) | packages/lowcode-plugin-inject | +| @alilc/action-block | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/action-block | +| @alilc/lowcode-plugin-base-monaco-editor | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-base-monaco-editor | +| @alilc/lowcode-plugin-block | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-block | +| @alilc/lowcode-plugin-code-editor | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-code-editor | +| @alilc/lowcode-plugin-components-pane | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-components-pane | +| @alilc/lowcode-plugin-datasource-pane | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-datasource-pane | +| @alilc/lowcode-plugin-manual | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-manual | +| @alilc/lowcode-plugin-schema | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-schema | +| @alilc/lowcode-plugin-undo-redo | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-undo-redo | +| @alilc/lowcode-plugin-zh-en | [https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) | packages/plugin-zh-en | +| @alifd/fusion-ui | [https://github.com/alibaba/lowcode-materials](https://github.com/alibaba/lowcode-materials) | packages/fusion-ui | +| @alilc/lowcode-materials | [https://github.com/alibaba/lowcode-materials](https://github.com/alibaba/lowcode-materials) | packages/fusion-lowcode-materials | +| @alilc/antd-lowcode-materials | [https://github.com/alibaba/lowcode-materials](https://github.com/alibaba/lowcode-materials) | packages/antd-lowcode-materials | +| | | | +| | | | +| | | | diff --git a/docs/docs/guide/appendix/repos.md b/docs/docs/guide/appendix/repos.md new file mode 100644 index 000000000..b4dbca7ac --- /dev/null +++ b/docs/docs/guide/appendix/repos.md @@ -0,0 +1,87 @@ +--- +title: 低代码仓库列表 +sidebar_position: 2 +--- +### 1. 引擎主包 +包含引擎的 4 大模块,入料、编排、渲染和出码。 + +仓库地址:[https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) +子包明细: + +1. designer +2. editor-core +3. editor-skeleton +4. engine +5. ignitor +6. plugin-designer +7. plugin-outline-pane +8. rax-renderer +9. rax-simulator-renderer +10. react-renderer +11. react-simulator-renderer +12. renderer-core +13. types +14. utils +15. material-parser +16. code-generator + +### 2. 引擎官方扩展包 +包含了常用的设置器(setter)、跟 setter 绑定的插件等 + +仓库地址:[https://github.com/alibaba/lowcode-engine-ext](https://github.com/alibaba/lowcode-engine-ext) +子包明细: + +- 设置器 setter + - array-setter + - bool-setter + - classname-setter + - color-setter + - events-setter + - expression-setter + - function-setter + - i18n-setter + - icon-setter + - json-setter + - mixed-setter + - number-setter + - object-setter + - out.txt + - radiogroup-setter + - select-setter + - slot-setter + - string-setter + - style-setter + - textarea-setter + - variable-setter +- 插件 plugin + - plugin-event-bind-dialog 事件绑定浮层 + - plugin-variable-bind-dialog 变量绑定浮层 +### 3. 低代码插件 +包含了常用的插件等 + +仓库地址:[https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) +子包明细: + +- base-monaco-editor 基础代码编辑器 +- plugin-code-editor 源码编辑面板 +- plugin-datasource-pane 数据源面板 +- plugin-manual 产品使用手册面板 +- plugin-schema 页面数据面板 +- plugin-undo-redo 前进/后退功能 +- plugin-zh-cn 中英文切换功能 + +### 4. 引擎 demo +展示使用引擎编排和渲染等模块以及相应的依赖资源配置基础 demo + +仓库地址:[https://github.com/alibaba/lowcode-demo](https://github.com/alibaba/lowcode-demo) +### 5. 工具链包 +包含生成引擎生态元素(setter、物料、插件)的脚手架,启动脚本,调试插件等 + +仓库地址:[https://github.com/alibaba/lowcode-tools](https://github.com/alibaba/lowcode-tools) +### 6. 低代码数据源引擎 +负责在渲染&出码两种运行时实现数据源管理,承担低代码搭建数据请求的能力; +仓库地址:[https://github.com/alibaba/lowcode-datasource](https://github.com/alibaba/lowcode-datasource) +### 7. 基础物料 & 物料描述 +仓库地址:[https://github.com/alibaba/lowcode-materials](https://github.com/alibaba/lowcode-materials) +### 8. 出码 demo +仓库地址:[https://github.com/alibaba/lowcode-code-generator-demo](https://github.com/alibaba/lowcode-code-generator-demo) diff --git a/docs/docs/guide/appendix/setterDetails/_category_.json b/docs/docs/guide/appendix/setterDetails/_category_.json new file mode 100644 index 000000000..facb7b377 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "预置设置器详情", + "position": 5 +} diff --git a/docs/docs/guide/appendix/setterDetails/array.md b/docs/docs/guide/appendix/setterDetails/array.md new file mode 100644 index 000000000..bbbd4498b --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/array.md @@ -0,0 +1,68 @@ +--- +title: ArraySetter +--- + +#### 简介 +用来展示属性类型为数组的setter +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395220128-b5d948e3-6a5a-420f-9a7a-a29be25c507d.png#clientId=ud56bf956-0414-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=181&id=u27259ecd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=362&originWidth=584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27221&status=done&style=none&taskId=u72065990-9557-4dbc-a0ba-eada448e228&title=&width=292) +#### 配置示例 +```javascript +"setter": { + "componentName": "ArraySetter", + "props": { + "itemSetter": { + "componentName": "ObjectSetter", + "props": { + "config": { + "items": [{ + "name": "title", + "description": "标题", + "setter": "StringSetter", + }, + { + "name": "callback", + "description": "callback", + "setter": { + "componentName": "FunctionSetter" + } + } + ] + } + }, + "initialValue": { + "title": "I am title", + "callback": null, + } + } + } +} +``` +#### ArraySetter 配置 +| **属性名** | **类型** | **说明** | +| --- | --- | --- | +| itemSetter | ObjectSetter | ArraySetter的子节点内容必须用ObjectSetter包裹 | + +#### itemSetter 配置 +| **属性名** | **类型** | **说明** | +| --- | --- | --- | +| componentName | String | + | +| props | | | +| initialValue | Object | 新增一项的初始值 | + +#### ObjectSetter 配置 +| **属性名** | **类型** | **说明** | +| --- | --- | --- | +| descriptor | String | Item在列表中展示的item.key名,需要和 config.items[] 中key对应 默认为 “项目X” +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448651683-6c44787a-cb6c-4066-9a47-2b22f862cb9c.png#clientId=u05af0495-3e67-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=186&id=ufb6e3681&margin=%5Bobject%20Object%5D&name=image.png&originHeight=372&originWidth=640&originalType=binary&ratio=1&rotation=0&showTitle=false&size=103250&status=done&style=none&taskId=u7a61b6f7-4e26-4d8b-a9e6-a30b5e9e73d&title=&width=320) | +| config | Object | 配置项 | +| config.items | Array | 子属性列表数据 | +| config.items[].name | String | 子属性名称 | +| config.items[].description | String | 子属性描述 | +| config.items[].setter | Object | String | 子属性setter配置 | 子属性setter组件名 | +| config.items[].isRequired | Boolean | 子属性是否开启快捷编辑,最多开启4个 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448651860-f4f80e87-4e80-463d-a1e0-99be8bf2a84f.png#clientId=u6ba2ab37-e0fb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=211&id=ueea652b0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=422&originWidth=614&originalType=binary&ratio=1&rotation=0&showTitle=false&size=32465&status=done&style=none&taskId=u343405fd-5773-4ebd-b6fc-1367a769fe2&title=&width=307) | +| config.items[].condition | Boolean | () => Boolean | 是否展示 | +| config.items[].getValue | (target, value) => value | 数据获取的 hook,可修改获取数据 | +| config.items[].setValue | (target, value) => value | 数据获取的 hook,可修改设置数据 | diff --git a/docs/docs/guide/appendix/setterDetails/behavior.md b/docs/docs/guide/appendix/setterDetails/behavior.md new file mode 100644 index 000000000..cbb5cfc1f --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/behavior.md @@ -0,0 +1,5 @@ +--- +title: BehaviorSetter +--- + +详见 [npm 包说明](https://g.alicdn.com/code/npm/@ali/lowcode-setter-behavior/0.1.1/build/index.html) \ No newline at end of file diff --git a/docs/docs/guide/appendix/setterDetails/bool.md b/docs/docs/guide/appendix/setterDetails/bool.md new file mode 100644 index 000000000..d29274893 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/bool.md @@ -0,0 +1,15 @@ +--- +title: BoolSetter +--- +#### 简介 +开关选择器 +#### 展示 +![](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448646757-b61019f4-d502-473a-8a11-4974479c55dc.png#from=url&id=dnn2b&margin=%5Bobject%20Object%5D&originHeight=82&originWidth=320&originalType=binary&ratio=1&status=done&style=none) +#### setter 配置 +| 属性名 | 说明 | +| --- | --- | +| disabled | 是否可选 | +| defaultValue | 默认值 | + +#### 返回类型 +Boolean diff --git a/docs/docs/guide/appendix/setterDetails/color.md b/docs/docs/guide/appendix/setterDetails/color.md new file mode 100644 index 000000000..8c09f5883 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/color.md @@ -0,0 +1,14 @@ +--- +title: ColorSetter +--- +用来选择颜色。 +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448646203-3eb11d27-0195-4608-91f3-0f3cfb6b7140.png#clientId=u09a8f665-5383-4&from=paste&height=416&id=u417e185d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=728&originWidth=590&originalType=binary&ratio=1&size=230281&status=done&style=none&taskId=u3d246b17-94ab-4eb3-af66-3ffb7fd3145&width=337) +#### setter 配置 +| 属性名 | 说明 | +| --- | --- | +| defaultValue | 默认值 | + +#### 返回类型 +String +会返回options中的value值 diff --git a/docs/docs/guide/appendix/setterDetails/event.md b/docs/docs/guide/appendix/setterDetails/event.md new file mode 100644 index 000000000..26405e908 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/event.md @@ -0,0 +1,73 @@ +--- +title: EventSetter +--- +#### 简介 +可以将事件绑定在物料上 +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394906292-0eb3ab0e-0bb0-4c8d-bbc5-7217b33cdcab.png#clientId=ub4e2d6f6-4877-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=507&id=u2a295c86&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1014&originWidth=1202&originalType=binary&ratio=1&rotation=0&showTitle=false&size=293824&status=done&style=none&taskId=u37e95d95-4425-450a-b4aa-9805d9dcf97&title=&width=601) + +#### 组件自带事件列表 +在物料协议的configure.supports.events 中声明 +```json +{ + "configure ": { + "supports": { + "style": true, + "events": [{ + "name": "onChange", + }, { + "name": "onExpand" + }, { + "name": "onVisibleChange" + }] + } + } +} +``` + +#### 事件绑定 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448650540-8b403233-44a5-4b1f-9379-2c55d4694f12.png#clientId=uf9b6db87-aae9-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=621&id=u95bb9c9a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1242&originWidth=2540&originalType=binary&ratio=1&rotation=0&showTitle=false&size=356836&status=done&style=none&taskId=u13bc14bd-d85c-46c9-aebd-586dcb32f96&title=&width=1270) +可以选择已有的事件(schema中的**methods**节点)进行绑定,也可以选择新建事件,选择新建事件默认会增加_new的事件后缀命名,点确定以后会跳转到对应代码插件对应区块 +#### +#### 参数设置 +如果需要额外传参,需要将扩展参数设置打开,在代码面板中,编辑参数内容 +注意: + +- 额外参数必须被包装成一个对象,如参数模板中所示 +- 可以使用动态变量例如 (this.items,this.state.xxx) +```json +{ + "testKey":this.state.text +} +``` + +- 该参数是额外参数,会加在原有参数后面,例如在onClick中加入扩展传参,最终函数消费的时候应该如下所示 +```javascript +// e为onClick原有函数传参,extParams为自定义传参 +onClick(e, extParams) { + this.setState({ + isShowDialog: extParams.isShowDialog + }) +} +``` +#### 事件新建函数模板 +有时候我们创建的函数会有用到一些通用的函数模板,我们可以在物料协议的events.template中创建一个模板,如下 +```javascript +{ + "configure ": { + "supports": { + "style": true, + "events": [{ + "name": "onChange", + "template": "templeteTest(e,${extParams}){this.setState({isShowDialog: false})}" + }, { + "name": "onExpand" + }, { + "name": "onVisibleChange" + }] + } + } +} +``` +其中 ${extParams} 为扩展参数占位符,如果用户没有声明扩展参数,会移除对应的参数声明,定义模板后,每次创建完函数会自动生成模板函数,如下图 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448650786-62270a89-65d5-42b1-8efd-90b090155c82.png#clientId=uf9b6db87-aae9-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=141&id=u4bb4387b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=282&originWidth=1292&originalType=binary&ratio=1&rotation=0&showTitle=false&size=84790&status=done&style=none&taskId=u2b911f77-a018-4f17-a5df-36c2c142d18&title=&width=646) diff --git a/docs/docs/guide/appendix/setterDetails/icon.md b/docs/docs/guide/appendix/setterDetails/icon.md new file mode 100644 index 000000000..1e98059c3 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/icon.md @@ -0,0 +1,81 @@ +--- +title: IconSetter +--- +#### 简介 +用来选择图标 +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394747068-9b8f47e1-06f7-48de-ba73-9ed3d389f913.png#clientId=u144a54e7-b111-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=290&id=uae8bb869&margin=%5Bobject%20Object%5D&name=image.png&originHeight=579&originWidth=1172&originalType=binary&ratio=1&rotation=0&showTitle=false&size=148927&status=done&style=none&taskId=ud281e100-e277-493d-8d4a-0e7b2c1b8f2&title=&width=586) +#### setter 配置 +| **属性名** | **类型** | **说明** | +| --- | --- | --- | +| type | String | 选择器返回类型 +**可选值**: +"string" | "node" | +| defaultValue | String | ReactNode | 默认值 | +| hasClear | Boolean | 选择器是否显示清除按钮 | +| icons | Array | 自定义icon集合;默认值详见[图标可选值](#SWnNn) | +| placeholder | String | 没有值的时候的占位符 | + +#### 返回类型 +String | ReactNode +#### 图标可选值 +```json +[ + 'smile', + 'cry', + 'success', + 'warning', + 'prompt', + 'error', + 'help', + 'clock', + 'success-filling', + 'delete-filling', + 'favorites-filling', + 'add', + 'minus', + 'arrow-up', + 'arrow-down', + 'arrow-left', + 'arrow-right', + 'arrow-double-left', + 'arrow-double-right', + 'switch', + 'sorting', + 'descending', + 'ascending', + 'select', + 'semi-select', + 'loading', + 'search', + 'close', + 'ellipsis', + 'picture', + 'calendar', + 'ashbin', + 'upload', + 'download', + 'set', + 'edit', + 'refresh', + 'filter', + 'attachment', + 'account', + 'email', + 'atm', + 'copy', + 'exit', + 'eye', + 'eye-close', + 'toggle-left', + 'toggle-right', + 'lock', + 'unlock', + 'chart-pie', + 'chart-bar', + 'form', + 'detail', + 'list', + 'dashboard', +] +``` diff --git a/docs/docs/guide/appendix/setterDetails/mixed.md b/docs/docs/guide/appendix/setterDetails/mixed.md new file mode 100644 index 000000000..6ce3df024 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/mixed.md @@ -0,0 +1,13 @@ +--- +title: MixedSetter +--- +#### 简介 +可以让属性同时支持多个setter + +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394866233-4b9127cd-3825-4763-9e2a-526ea2b48140.png#clientId=u7c96c5f7-4dd4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=240&id=u7ca1df47&margin=%5Bobject%20Object%5D&name=image.png&originHeight=480&originWidth=1552&originalType=binary&ratio=1&rotation=0&showTitle=false&size=272344&status=done&style=none&taskId=u07037884-fbf4-411a-be82-29296ad1fb2&title=&width=776) + +#### 配置 +| **属性名** | **类型** | **说明** | +| --- | --- | --- | +| setters | Array | SetterName | diff --git a/docs/docs/guide/appendix/setterDetails/number.md b/docs/docs/guide/appendix/setterDetails/number.md new file mode 100644 index 000000000..cd4d95ab0 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/number.md @@ -0,0 +1,19 @@ +--- +title: NumberSetter +--- +#### 简介 +用于输入数字。 +#### 展示 +![](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448648108-fae36b4c-fb3d-4a4e-b83e-c5233e8bae5d.png#from=url&id=czSyt&margin=%5Bobject%20Object%5D&originHeight=402&originWidth=576&originalType=binary&ratio=1&status=done&style=none) +#### setter 配置 +| 属性名 | 说明 | +| --- | --- | +| min, max | 指定最大最小值 | +| defaultValue | 默认值 | +| step | 指定步长 number | +| units | 指定单位 string | +| precision | 设置小数位数 number | + +#### 返回类型 +#### Number +会返回value值 diff --git a/docs/docs/guide/appendix/setterDetails/radioGroup.md b/docs/docs/guide/appendix/setterDetails/radioGroup.md new file mode 100644 index 000000000..10bbc13f4 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/radioGroup.md @@ -0,0 +1,24 @@ +--- +title: RadioGroupSetter +--- +#### 简介 +用于直观的展示选择并选择。 +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395469783-17a5f8b5-112a-420b-a64f-09fffea55067.png#clientId=u8044d585-4c1d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=49&id=ucafe75f2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=98&originWidth=564&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9843&status=done&style=none&taskId=ud3ff8182-f29c-4b81-b4de-e23baa325c1&title=&width=282) +#### setter 配置 +| 属性名 | 说明 | +| --- | --- | +| defaultValue | 默认值 | +| options | 传入的数据源, +**参数格式**: +[ +{img: 'url', +value: 'text', +label/title: 'text'}, ... +] +|| +[ 'text', 'text', ...] | + +#### 返回类型 +String | Number | Boolean +会返回options中的value值 diff --git a/docs/docs/guide/appendix/setterDetails/select.md b/docs/docs/guide/appendix/setterDetails/select.md new file mode 100644 index 000000000..296bafebf --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/select.md @@ -0,0 +1,24 @@ +--- +title: SelectSetter +--- +#### 简介 +用来选择组件。在限定的可选性内进行选择,核心能力是选择 +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395925308-538eb962-f035-43b9-bdb3-ecc5bc9d1e85.png#clientId=u8b43103b-f292-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=301&id=u7a9a7786&margin=%5Bobject%20Object%5D&name=image.png&originHeight=602&originWidth=574&originalType=binary&ratio=1&rotation=0&showTitle=false&size=36601&status=done&style=none&taskId=u089007a6-76ec-44e8-94b5-127a8ba1a51&title=&width=287) +#### setter 配置 +| 属性名 | 说明 | +| --- | --- | +| mode | 选择器模式 + +可选值: +'single', 'multiple', 'tag' | +| defaultValue | 默认值 | +| options | 传入的数据源, +**参数格式**: +[ +{label/title: '文字', value: 'text'}, ... +] | + +#### 返回类型 +String | Number | Boolean +会返回options中的value值 diff --git a/docs/docs/guide/appendix/setterDetails/slot.md b/docs/docs/guide/appendix/setterDetails/slot.md new file mode 100644 index 000000000..52e3f9f3b --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/slot.md @@ -0,0 +1,93 @@ +--- +title: SlotSetter +--- +## 简介 +通过一个开启一个slot(插槽),可以在物料特定的一个位置渲染一个或者多个节点。slot比较适合物料的局部自定义渲染。 + +## 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448654034-6f527cc2-cf65-4e79-b904-21416800b5b8.png#clientId=u091bb73f-2e93-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=227&id=gAVIU&margin=%5Bobject%20Object%5D&name=image.png&originHeight=454&originWidth=588&originalType=binary&ratio=1&rotation=0&showTitle=false&size=103838&status=done&style=none&taskId=u45d2e179-54ea-40d1-a654-66151c337ff&title=&width=294) + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395677385-84c39b6d-2356-4d86-a741-edbb7daffd6c.png#clientId=udcc199c0-6236-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=82&id=u999c2367&margin=%5Bobject%20Object%5D&name=image.png&originHeight=164&originWidth=644&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18486&status=done&style=none&taskId=u6c13f469-173f-4ba0-9bfa-866122ef7a4&title=&width=322) + +## setter 配置 + +| 属性名 | 类型 | 说明 | +| --- | --- | --- | +| initialValue | Object | 默认值 +{ +"type": "JSSlot", +"params": [ +"module" + ], +"value": [] +} + +params:接收函数的入参,可以直接在slot节点中消费,通过this.module (这里module是示例值,可根据实际函数入参更改) +value:可以定义一个节点,每次打开插槽的时候默认填充一个节点 + + | +| hideParams | boolean | 是否隐藏入参,注意该值只能隐藏入参的输入框,适合单行展示,实际渲染的时候,还是会传入params的参数,和params:[]不同 | +| checkedText | string | switch选中文案,默认显示"启用" | +| unCheckedText | string | switch取消文案,默认显示"关闭" | + +## 配置示例 +### 普通示例 +#### 配置 +```typescript +{ + name: "propName", + title: "propTitle", + setter: { + componentName: "SlotSetter", + isRequired: true, + title: "组件坑位", + initialValue: { + type: "JSSlot", + value: [] + }, + } + } +``` +#### 组件 +```typescript +function A(props) { + return props.propName; +} +``` +### 带参数的插槽示例 +#### 配置 +```typescript +{ + name: "propName", + title: "propTitle", + setter: { + componentName: "SlotSetter", + isRequired: true, + title: "组件坑位", + initialValue: { + type: "JSSlot", + params: [ + "module" + ], + value: [] + }, + } + } +``` +#### 组件 +组件需要传参数进行渲染,和普通示例的使用不一样。 +```typescript +function A(props) { + const module = [] + return props.propName(module); +} +``` +#### param 使用示例 +1.开启插槽 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652877673290-7a377a36-7da9-40c1-baff-1c7c8ad04a67.png#clientId=udd177887-e136-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=900&id=u9ba2344a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1800&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1291582&status=done&style=none&taskId=u60418282-46e2-46b4-95d2-156288bcbd7&title=&width=1792) +2.拖拽组件到插槽中 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652877759606-6de16048-a5d9-477c-b38b-962690a39254.png#clientId=udd177887-e136-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=903&id=u43b38264&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1806&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1230647&status=done&style=none&taskId=u96d44a1e-033f-4cd0-ac81-df6cac8ea18&title=&width=1792) + +3.在插槽内组件中使用变量绑定,绑定 this.xxx +xxx 入参的配置 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652877586491-904d6b18-a41a-4ba2-8664-088cd5feca72.png#clientId=udd177887-e136-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=903&id=u165f1564&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1806&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1278387&status=done&style=none&taskId=uc060e73a-b190-480a-8aad-8c20b27290c&title=&width=1792) diff --git a/docs/docs/guide/appendix/setterDetails/string.md b/docs/docs/guide/appendix/setterDetails/string.md new file mode 100644 index 000000000..77e5aacf4 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/string.md @@ -0,0 +1,14 @@ +--- +title: StringSetter +--- +#### 简介 +用来展示和修改字符串类型的属性值,不可换行 +#### 展示 +![](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448645493-4a30f02e-1869-4963-8d39-40501891ae84.png#from=url&id=mEkyy&margin=%5Bobject%20Object%5D&originHeight=88&originWidth=714&originalType=binary&ratio=1&status=done&style=none) +#### setter 配置 +| 属性名 | 说明 | +| --- | --- | +| placeholder | 输入提示 | + +#### 返回类型 +String diff --git a/docs/docs/guide/appendix/setterDetails/style.md b/docs/docs/guide/appendix/setterDetails/style.md new file mode 100644 index 000000000..f733baaa3 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/style.md @@ -0,0 +1,49 @@ +--- +title: StyleSetter +--- +## 简介 +通过开启StyleSetter,我们可以将样式配置面板来配置样式属性。 + +## 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1658650544358-3d97f6b1-6269-4627-ab4a-62a43219fd08.png#clientId=u5d06f6c0-ba35-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=739&id=u3f141635&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1478&originWidth=596&originalType=binary&ratio=1&rotation=0&showTitle=false&size=75996&status=done&style=none&taskId=u16f49c5d-a32e-4cf8-95ab-e0c1059fbef&title=&width=298) + +## setter 配置 + +| 属性名 | 类型 | 说明 | +| --- | --- | --- | +| unit | String | 默认值 px +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1658650635878-fd920e86-ea28-4e08-8676-238ac367a0ee.png#clientId=u5d06f6c0-ba35-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=49&id=u3f243c11&margin=%5Bobject%20Object%5D&name=image.png&originHeight=98&originWidth=576&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9796&status=done&style=none&taskId=ucc541aa4-0765-4da9-820c-cee48ed0635&title=&width=288) + | +| placeholderScale | Number | 默认计算尺寸缩放 默认值为1 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1658650773475-7ecba070-c81e-4a6c-b346-7aad7dd6a897.png#clientId=u5d06f6c0-ba35-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=49&id=u27842257&margin=%5Bobject%20Object%5D&name=image.png&originHeight=98&originWidth=250&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7116&status=done&style=none&taskId=u8ac18c79-d14b-49a6-8788-476524e69da&title=&width=125) +在没有设定数值的时候,系统会通过window.getComputedStyle来计算展示的数值。 +在某些场景下,例如手机场景,在编辑器展示的是375的实际宽度,但是实际设计尺寸是750的宽度,这个时候需要对这个计算尺寸设成2 | +| showModuleList | String[] | 默认值 +['background', 'border', 'font', 'layout', 'position'] +分别对应背景、边框、文字、布局、位置五个区块,可以针对不同的场景按需进行展示。 +例如文字的组件,我不需要修改边框的样式,就可以把边框模块隐藏掉 | +| isShowCssCode | Boolean | 默认值: true, 是否展示css源码编辑 | +| layoutPropsConfig | Object | 布局样式设置 | +| layoutPropsConfig.showDisPlayList | String[] | 默认值 ['inline', 'flex', 'block', 'inline-block', 'none'] +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1658651295786-48ca3773-1b4e-4f2e-9521-0ebbfcfe5361.png#clientId=u5d06f6c0-ba35-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=36&id=u50dae00c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=72&originWidth=474&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7621&status=done&style=none&taskId=ub5804835-d0b2-45d7-a419-66ac1005afa&title=&width=237) +可按需展示 + | +| layoutPropsConfig.isShowPadding | String | 默认值 true +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1658651496157-c84348d4-a47f-44b4-b2c5-74b21e97747a.png#clientId=u5d06f6c0-ba35-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=191&id=u70235603&margin=%5Bobject%20Object%5D&name=image.png&originHeight=382&originWidth=548&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20439&status=done&style=none&taskId=udae33336-ce05-41a9-89c5-361a63d061a&title=&width=274) +是否展示内边距 (四个边) | +| layoutPropsConfig.isShowMargin | Boolean | 默认值 true +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1658651539776-090de3e1-6293-4660-b74f-9bcf027381c6.png#clientId=u5d06f6c0-ba35-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=241&id=uab3771c7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=482&originWidth=536&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29325&status=done&style=none&taskId=uc287d55d-5363-4b37-978b-6e150f36141&title=&width=268) +是否展示外边距 (四个边) | +| layoutPropsConfig.isShowWidthHeight | Boolean | 默认值 true +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1658651621765-06e81934-9a90-4290-89dd-75ffb56808a5.png#clientId=u5d06f6c0-ba35-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=51&id=u02aa5918&margin=%5Bobject%20Object%5D&name=image.png&originHeight=102&originWidth=546&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9945&status=done&style=none&taskId=u394451ef-266c-440d-a86e-fc1a01320ea&title=&width=273) +是否展示宽高 | +| fontPropsConfig | Object | 文字样式设置 | +| fontPropsConfig.fontFamilyList | Array | [ + { value: 'Helvetica', label: 'Helvetica' }, + { value: 'Arial', label: 'Arial' }, + { value: 'serif', label: 'serif' }, + ] +可以定制文字字体选项 | +| positionPropsConfig | Object | 位置样式设置 | +| positionPropsConfig.isShowFloat | Boolean | 默认true 是否展示浮动 | +| positionPropsConfig.isShowClear | Boolean | 默认true 是否展示清除浮动 | diff --git a/docs/docs/guide/appendix/setterDetails/textArea.md b/docs/docs/guide/appendix/setterDetails/textArea.md new file mode 100644 index 000000000..9188012f8 --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/textArea.md @@ -0,0 +1,14 @@ +--- +title: StyleSetter +--- +#### 简介 +表单输入组件。 +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395839676-9feaa4d6-dbec-40c0-a93c-6014006a50c5.png#clientId=u01f23b13-e688-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=146&id=u29c456c5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=292&originWidth=1026&originalType=binary&ratio=1&rotation=0&showTitle=false&size=304205&status=done&style=none&taskId=u83ca85c2-3a04-4c41-a055-31d1bb86d84&title=&width=513) +#### setter 配置 +| **属性名** | **类型** | **说明** | +| --- | --- | --- | +| placeholder | String | 输入提示 | + +#### 返回类型 +String diff --git a/docs/docs/guide/appendix/setterDetails/variable.md b/docs/docs/guide/appendix/setterDetails/variable.md new file mode 100644 index 000000000..88e46e0cd --- /dev/null +++ b/docs/docs/guide/appendix/setterDetails/variable.md @@ -0,0 +1,12 @@ +--- +title: VariableSetter +--- +#### 简介 +用来给属性值设定变量 + +#### 展示 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395534301-67aaf74b-2561-4682-a0b2-dcde642a8d7c.png#clientId=u46178fa3-bc0c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=46&id=ucd7e6f91&margin=%5Bobject%20Object%5D&name=image.png&originHeight=92&originWidth=578&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10671&status=done&style=none&taskId=u854e606f-bbd1-42f4-81a2-d161aa4e2d3&title=&width=289) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643448653288-1b5b46c8-5ea1-455d-9ce4-19abade13b31.png#clientId=udec6352b-e220-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=595&id=u2395a6a5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1190&originWidth=1564&originalType=binary&ratio=1&rotation=0&showTitle=false&size=256705&status=done&style=none&taskId=u3f3418f8-b6ad-464e-8fe6-89586fbc07d&title=&width=782) +#### +#### 变量列表 +包含所有的在协议中的**state**(state属性)节点数据和**methods**(自定义处理函数)节点数据 diff --git a/docs/docs/guide/appendix/setters.md b/docs/docs/guide/appendix/setters.md new file mode 100644 index 000000000..381ef8198 --- /dev/null +++ b/docs/docs/guide/appendix/setters.md @@ -0,0 +1,41 @@ +--- +title: 预置设置器列表 +sidebar_position: 4 +--- +| 预置 Setter | 返回类型 | 用途 | 截图 | +| --- | --- | --- | --- | +| [ArraySetter](https://www.yuque.com/lce/doc/eiegwf?view=doc_embed&from=kb&from=kb&outline=1&title=1) | T[] | 列表数组行数据设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395227012-97d27c4d-e053-4aae-9de1-726015081a4d.png#crop=0&crop=0&crop=1&crop=1&from=url&id=MHREr&margin=%5Bobject%20Object%5D&originHeight=362&originWidth=584&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [BoolSetter](https://www.yuque.com/lce/doc/mdxryw?view=doc_embed&from=kb&from=kb&outline=1&title=1) | boolean | 布尔型数据设置器, | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394538926-81adaab2-fe44-4cf4-93a0-4f46d790cfc6.png#crop=0&crop=0&crop=1&crop=1&from=url&id=yVcL4&margin=%5Bobject%20Object%5D&originHeight=82&originWidth=320&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| ClassNameSetter | string | 样式名设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/2553587/1644417220734-fd13e249-43f0-4caa-8122-9c1a791cd30f.png#crop=0&crop=0&crop=1&crop=1&from=url&id=RNQQQ&margin=%5Bobject%20Object%5D&originHeight=180&originWidth=502&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [ColorSetter](https://www.yuque.com/lce/doc/hu5ir6?view=doc_embed&from=kb&from=kb&outline=1&title=1) | string | 颜色设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394553552-2c8c3982-1671-47b3-a569-297bcb838b79.png#crop=0&crop=0&crop=1&crop=1&from=url&id=CxWyj&margin=%5Bobject%20Object%5D&originHeight=728&originWidth=590&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| DateMonthSetter | + | 日期型-月数据设置器 | + | +| DateRangeSetter | + | 日期型数据设置器,可选择时间区间 | + | +| DateSetter | + | 日期型数据设置器 | + | +| DateYearSetter | + | 日期型-年数据设置器 | + | +| [EventSetter](https://www.yuque.com/lce/doc/fiu8cz?view=doc_embed&from=kb&from=kb&outline=1&title=1) | function | 事件绑定设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394913684-e50d5fb4-50f4-4f6a-b5f8-5a6269b56676.png#crop=0&crop=0&crop=1&crop=1&from=url&id=lCP5h&margin=%5Bobject%20Object%5D&originHeight=1014&originWidth=1202&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [IconSetter](https://www.yuque.com/lce/doc/ry138x?view=doc_embed&from=kb&from=kb&outline=1&title=1) | string | 图标设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394768487-d7b9a492-254e-46bd-a755-f8f73d76b932.png#crop=0&crop=0&crop=1&crop=1&from=url&id=pbQvJ&margin=%5Bobject%20Object%5D&originHeight=579&originWidth=1172&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| FunctionSetter | any | 函数型数据设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/2553587/1644417386132-50215eb0-ca7e-499b-8ca8-bd0b19ce89e1.png#crop=0&crop=0&crop=1&crop=1&from=url&id=YZ16c&margin=%5Bobject%20Object%5D&originHeight=110&originWidth=794&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| JsonSetter | object | json型数据设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/2553587/1644418044457-a50b1621-090a-440d-9735-b3e9b7b3abd8.png#crop=0&crop=0&crop=1&crop=1&from=url&id=OywYO&margin=%5Bobject%20Object%5D&originHeight=1068&originWidth=1076&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [MixedSetter](https://www.yuque.com/lce/doc/ah6o2c?view=doc_embed&from=kb&from=kb&outline=1&title=1) | any | 混合型数据设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394872594-bb8f8de1-824a-4ba7-8b83-e408434a7d29.png#crop=0&crop=0&crop=1&crop=1&from=url&id=it6Xz&margin=%5Bobject%20Object%5D&originHeight=480&originWidth=1552&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [NumberSetter](https://www.yuque.com/lce/doc/hk65u5?view=doc_embed&from=kb&from=kb&outline=1&title=1) | number | 数值型数据设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644394982613-b1a43863-fd63-4bb4-b5fe-2f36109c449b.png#crop=0&crop=0&crop=1&crop=1&from=url&id=bEZjH&margin=%5Bobject%20Object%5D&originHeight=328&originWidth=1152&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| ObjectSetter | Record | 对象数据设置器,一般内嵌在ArraySetter中 | + | +| [RadioGroupSetter](https://www.yuque.com/lce/doc/cmpf0b?view=doc_embed&from=kb&from=kb&outline=1&title=1) | string | number | boolean | 枚举型数据设置器,采用tab选择的形式展现 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395460363-1fdb034c-69d7-404d-9226-37f1d4562210.png#crop=0&crop=0&crop=1&crop=1&from=url&id=XSyl3&margin=%5Bobject%20Object%5D&originHeight=98&originWidth=564&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [SelectSetter](https://www.yuque.com/lce/doc/po1t1r?view=doc_embed&from=kb&from=kb&outline=1&title=1) | string | number | boolean | 枚举型数据设置器,采用下拉的形式展现 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395970016-dfe95495-53b1-4f86-afc6-a0c621917440.png#crop=0&crop=0&crop=1&crop=1&from=url&id=NPzvg&margin=%5Bobject%20Object%5D&originHeight=282&originWidth=582&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [SlotSetter](https://www.yuque.com/lce/doc/af5vba?view=doc_embed&from=kb&from=kb&outline=1&title=1) | Element | Element[] | 节点型数据设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395692198-ca0cb611-0b7b-43b2-9f3b-3f6983cc3b37.png#crop=0&crop=0&crop=1&crop=1&from=url&id=SLNSR&margin=%5Bobject%20Object%5D&originHeight=164&originWidth=644&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [StringSetter](https://www.yuque.com/lce/doc/qogni4?view=doc_embed&from=kb&from=kb&outline=1&title=1) | string | 短文本型数据设置器,不可换行 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395613012-e7e5db32-9281-4f19-a18d-284765cd1184.png#crop=0&crop=0&crop=1&crop=1&from=url&id=OySEz&margin=%5Bobject%20Object%5D&originHeight=102&originWidth=414&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| StyleSetter | + | 样式设置器 | ![](https://cdn.nlark.com/yuque/0/2022/png/2553587/1644416984306-00aca829-3611-4f4c-b6b6-a20d0da0bfe7.png#crop=0&crop=0&crop=1&crop=1&from=url&id=aWlAc&margin=%5Bobject%20Object%5D&originHeight=1214&originWidth=788&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| [TextAreaSetter](https://www.yuque.com/lce/doc/gp36z6?view=doc_embed&from=kb&from=kb&outline=1&title=1) | string | 长文本型数据设置器,可换行 | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395843901-4d9c3e04-f6fc-4d50-93ed-44e6d8a99b9a.png#crop=0&crop=0&crop=1&crop=1&from=url&id=grvEZ&margin=%5Bobject%20Object%5D&originHeight=292&originWidth=1026&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | +| TimePicker | + | 时间型数据设置器 | + | +| [VariableSetter](https://www.yuque.com/lce/doc/lkvb36?view=doc_embed&from=kb&from=kb&outline=1&title=1) | any | 变量型数据设置器, | ![](https://cdn.nlark.com/yuque/0/2022/png/242652/1644395524553-dba00e02-f44c-429f-a8f4-c0e5f6d8b35f.png#crop=0&crop=0&crop=1&crop=1&from=url&id=L5BHL&margin=%5Bobject%20Object%5D&originHeight=92&originWidth=578&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) | diff --git a/docs/docs/guide/create/_category_.json b/docs/docs/guide/create/_category_.json new file mode 100644 index 000000000..a5b30e063 --- /dev/null +++ b/docs/docs/guide/create/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "创建低代码应用", + "position": 1, + "collapsed": false, + "collapsible": true +} diff --git a/docs/docs/guide/create/useEditor.md b/docs/docs/guide/create/useEditor.md new file mode 100644 index 000000000..aca9faf68 --- /dev/null +++ b/docs/docs/guide/create/useEditor.md @@ -0,0 +1,184 @@ +--- +title: 接入编辑器 +sidebar_position: 0 +--- + +您有两种方式初始化低代码编辑器: + +1. clone 低代码项目的官方 demo,直接启动项目。适合普通人。 +2. 手工引入低代码 UMD 包,手工配置、打包和启动。适合 Webpack 配置工程师。 + +# 方法 1: Clone 并启动 + +可以通过两种方式之一获取低代码编辑器的示例代码: + +1. 直接[在 github 仓库下](https://github.com/alibaba/lowcode-demo)进行下载 + +![Rectangle 2.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1645178084931-b81f6960-f0be-4695-ae38-e2632c859629.png#clientId=u6721b06e-9fb2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=259&id=ud829c08c&margin=%5Bobject%20Object%5D&name=Rectangle%202.png&originHeight=517&originWidth=1500&originalType=binary&ratio=1&rotation=0&showTitle=false&size=163331&status=done&style=none&taskId=ua56b6104-b23f-4dd6-a95c-4fa8ac8cb3c&title=&width=750) + +2. 如果本地安装了 git,可以通过 git clone 方式进行下载 + +(这个方法的好处是 demo 有了更新,可以通过 merge 方式跟上) +```typescript +git clone https://github.com/alibaba/lowcode-demo.git +``` + +拉取仓库代码后,需要进行如下配置或安装过程: + +1. 确保本地安装了 Node.js 和 npm,如果没有,[您可以通过 nvm 进行快捷的安装](https://github.com/nvm-sh/nvm) +2. 确保为 npm [设置了可以访问的 npm 源,保证安装过程无网络问题](https://npmmirror.com/) +3. 执行 `npm install` + +依赖完全安装完成后,执行 `npm start`,如果看到这个界面,说明项目启动成功。您可以继续看后续章节了。本章节后续内容均为高级配置方式。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644982015764-35bb5f58-fbd6-4838-9792-3c5b2136162d.png#clientId=ub335956d-fdf2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=817&id=u01bca493&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1634&originWidth=3060&originalType=binary&ratio=1&rotation=0&showTitle=false&size=216709&status=done&style=stroke&taskId=u467c43dc-35c5-4c84-907d-d6db9a0b839&title=&width=1530) + +# 方法 2: 手工引入低代码 UMD 包,手工配置打包和启动 + +如果您不是从零开始的项目,您可能需要手工引入低代码引擎。 + +## 引入 UMD 包资源 + +我们需要在启动前,正确在项目中通过 UMD 包方式直接依赖如下内容: +(亦可使用异步加载工具,如果您按照正确的顺序进行加载) +```html + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +> 注:如果 unpkg 的服务比较缓慢,您可以使用 alicdn 来获得确定版本的低代码引擎,如对于引擎的 1.0.1 版本,可用 [https://alifd.alicdn.com/npm/@alilc/lowcode-engine@1.0.1/dist/js/engine-core.js](https://alifd.alicdn.com/npm/@alilc/lowcode-engine@1.0.1/dist/js/engine-core.js) + + +## 配置打包 + +因为这些资源已经通过 UMD 方式引入,所以在 Webpack 等构建工具中需要配置它们为 external,不再重复打包: + +```javascript +{ + "externals": { + "react": "var window.React", + "react-dom": "var window.ReactDOM", + "prop-types": "var window.PropTypes", + "@alifd/next": "var window.Next", + "@alilc/lowcode-engine": "var window.AliLowCodeEngine", + "@alilc/lowcode-editor-core": "var window.AliLowCodeEngine.common.editorCabin", + "@alilc/lowcode-editor-skeleton": "var window.AliLowCodeEngine.common.skeletonCabin", + "@alilc/lowcode-designer": "var window.AliLowCodeEngine.common.designerCabin", + "@alilc/lowcode-engine-ext": "var window.AliLowCodeEngineExt", + "@ali/lowcode-engine": "var window.AliLowCodeEngine", + "moment": "var window.moment", + "lodash": "var window._" + } +} +``` + +## 初始化低代码编辑器 + +### 方法 2.1 使用 init 进行初始化 + +正确引入后,我们可以直接通过 window 上的变量进行引用,如 `window.AliLowCodeEngine.init`。您可以直接通过此方式初始化低代码引擎: + +```javascript +// 确保在执行此命令前,在 中已有一个 id 为 lce-container 的

+window.AliLowCodeEngine.init(document.getElementById('lce-container'), { + enableCondition: true, + enableCanvasLock: true, +}); +``` + +如果您的项目中使用了 TypeScript,您可以通过如下 devDependencies 引入相关包,并获得对应的类型推断。 +```javascript +// package.json +{ + "devDependencies": { + "@alilc/lowcode-engine": "beta" + } +} +``` +```javascript +import { init } from '@alilc/lowcode-engine'; + +init(document.getElementById('lce-container'), { + enableCondition: true, + enableCanvasLock: true, +}); +``` + +init 的功能包括但不限于: + +1. 传递 options 并设置 config 对象; +2. 传递 preference 并设置 plugins 入参; +3. 初始化 Workbench; + +> 本节中的低代码编辑器例子可以在 demo 中找到:[https://github.com/alibaba/lowcode-demo/blob/main/src/index.ts#L21-L34](https://github.com/alibaba/lowcode-demo/blob/main/src/index.ts#L21-L34) + + +### 方法 2.2 使用 skeletonCabin.Workbench 方式初始化 + +`init()` 内部会调用 `ReactDOM.render()` 函数,因此这样初始化的内容没有办法与外部的 React 组件进行通信,也就没有办法在一些自定义的 plugin 中获取 redux 上的全局数据等内容。 +因此,这种场景下您可以通过 `skeletonCabin.Workbench` 进行初始化。 + +> 注:您不需要同时使用 2.1 和 2.2 的方法。根据使用场景,只有需要低代码引擎插件和外界进行一定通信时,2.2 提供的方法才是必要的。 + + +```javascript +import React, { useState, useEffect } from 'react' +import { project, plugins, common, skeleton } from '@alilc/lowcode-engine' + +// 此 schema 参考 demo 中的默认 schema 书写 +import userSchema from './schema.json' + +export default function EditorView() { + /** 插件是否已初始化成功,因为必须要等插件初始化后才能渲染 Workbench */ + const [hasPluginInited, setHasPluginInited] = useState(false); + + useEffect(() => { + plugins.init().then(() => { + setHasPluginInited(true) + }).catch(err => console.error(err)) + }, []); + + useEffect(() => { + project.importSchema(userSchema) + }, [userSchema]); + + if (!hasPluginInited) { + return null; + } + + return ( + + ); +} +``` + +> 本节中的低代码编辑器类似的例子可以在 demo 中找到:[https://github.com/alibaba/lowcode-demo/blob/main/src/scenarios/custom-initialization/index.tsx](https://github.com/alibaba/lowcode-demo/blob/main/src/scenarios/custom-initialization/index.tsx) + + +# 配置低代码编辑器 +详见“低代码扩展简述“章节。 diff --git a/docs/docs/guide/create/useRenderer.md b/docs/docs/guide/create/useRenderer.md new file mode 100644 index 000000000..4f7a0a08b --- /dev/null +++ b/docs/docs/guide/create/useRenderer.md @@ -0,0 +1,100 @@ +--- +title: 接入运行时 +sidebar_position: 1 +--- + +低代码引擎的编辑器将产出两份数据: + +- 资产包数据 assets:包含物料名称、包名及其获取方式,对应协议中的[《低代码引擎资产包协议规范》](https://lowcode-engine.cn/assets) +- 页面数据 schema:包含页面结构信息、生命周期和代码信息,对应协议中的[《低代码引擎搭建协议规范》](https://lowcode-engine.cn/lowcode) + +经过上述两份数据,可以直接交由渲染模块或者出码模块来运行,二者的区别在于: + +- 渲染模块:使用资产包数据、页面数据和低代码运行时,并且允许维护者在低代码编辑器中用 Low Code 的方式继续维护; +- 出码模块:不依赖低代码运行时和页面数据,直接生成可直接运行的代码,并且允许维护者用 Pro Code 的方式继续维护,但无法再利用用低代码编辑器; + +## 渲染模块 + +[在 Demo 中](https://lowcode-engine.cn/demo),右上角有渲染模块的示例使用方式: +![Mar-13-2022 16-52-49.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1647161579197-20c72ea4-6d9a-4692-9b23-005182f6387e.gif#clientId=u244806d0-100a-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=u9b403d3d&margin=%5Bobject%20Object%5D&name=Mar-13-2022%2016-52-49.gif&originHeight=514&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=755539&status=done&style=stroke&taskId=u14f0f4c2-4d6c-4296-b2df-ccda870faff&title=) + +基于官方提供的渲染模块 [@alifd/lowcode-react-renderer](https://github.com/alibaba/lowcode-engine/tree/main/packages/react-renderer),你可以在 React 上下文渲染低代码编辑器产出的页面。 + +### 构造渲染模块所需数据 + +渲染模块所需要的数据需要通过编辑器产出的数据进行一定的转换,规则如下: + +- schema:从编辑器产出的 projectSchema 中拿到 componentsTree 中的首项,即 `projectSchema.componentsTree[0]`; +- components:需要根据编辑器产出的资产包 assets 中,根据页面 projectSchema 中声明依赖的 componentsMap,来加载所有依赖的资产包,最后获取资产包的实例并生成物料 - 资产包的键值对 components。 + +这个过程可以参考 demo 项目中的 `src/preview.tsx`: +```typescript +async function getSchemaAndComponents() { + const packages = JSON.parse(window.localStorage.getItem('packages') || ''); + const projectSchema = JSON.parse(window.localStorage.getItem('projectSchema') || ''); + const { componentsMap: componentsMapArray, componentsTree } = projectSchema; + const componentsMap: any = {}; + componentsMapArray.forEach((component: any) => { + componentsMap[component.componentName] = component; + }); + const schema = componentsTree[0]; + + const libraryMap = {}; + const libraryAsset = []; + packages.forEach(({ package: _package, library, urls, renderUrls }) => { + libraryMap[_package] = library; + if (renderUrls) { + libraryAsset.push(renderUrls); + } else if (urls) { + libraryAsset.push(urls); + } + }); + + const vendors = [assetBundle(libraryAsset, AssetLevel.Library)]; + + const assetLoader = new AssetLoader(); + await assetLoader.load(libraryAsset); + const components = await injectComponents(buildComponents(libraryMap, componentsMap)); + + return { + schema, + components, + }; +} +``` + +### 进行渲染 + +拿到 schema 和 components 以后,您可以借由资产包数据和页面数据来完成页面的渲染: +```tsx +import React from 'react'; +import ReactRenderer from '@alilc/lowcode-react-renderer'; + +const SamplePreview = () => { + return ( + + ) +} +``` + +> 注:您可以注意到,此处是依赖了 React 进行渲染的,对于 Vue 形态的渲染或编辑器支持,详见[对应公告](https://github.com/alibaba/lowcode-engine/issues/236)。 +> 本节示例可在 Demo 代码里找到:[https://github.com/alibaba/lowcode-demo/blob/main/src/preview.tsx](https://github.com/alibaba/lowcode-demo/blob/main/src/preview.tsx#L54-L58) + + +## 出码模块 + +[在 Demo 中](https://lowcode-engine.cn/demo),右上角有出码模块的示例使用方式: +![Mar-13-2022 16-55-56.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1647161777243-b16045c4-3cac-4920-8e68-ce064a90fe26.gif#clientId=u244806d0-100a-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=ud7bfd5a2&margin=%5Bobject%20Object%5D&name=Mar-13-2022%2016-55-56.gif&originHeight=514&originWidth=1536&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1727314&status=done&style=stroke&taskId=u4e079100-d6a0-4ad2-ac0c-938ab8e7759&title=) + +> 本节示例可在出码插件里找到:[https://github.com/alibaba/lowcode-code-generator-demo](https://github.com/alibaba/lowcode-code-generator-demo) + + +## 低代码的生产和消费 + +经过“接入编辑器” - “接入运行时” 这两节的介绍,我们已经可以了解到低代码所构建的生产和消费流程了,梳理如下图: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644405393410-1c54fa37-74de-4c48-a4a9-1cbce359feeb.png#clientId=ua752ee55-c225-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=u4ceefadb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1786&originWidth=3206&originalType=binary&ratio=1&rotation=0&showTitle=false&size=312489&status=done&style=none&taskId=uae8eacd1-4c05-4689-bb6a-24ceb76327d&title=&width=710) + +如上述流程所示,您一般需要一个后端项目来保存页面数据信息,如果资产包信息是动态的,也需要保存资产包信息。 diff --git a/docs/docs/guide/design/_category_.json b/docs/docs/guide/design/_category_.json new file mode 100644 index 000000000..1868732be --- /dev/null +++ b/docs/docs/guide/design/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "引擎设计原理", + "position": 3, + "collapsed": false, + "collapsible": true +} diff --git a/docs/docs/guide/design/datasourceEngine.md b/docs/docs/guide/design/datasourceEngine.md new file mode 100644 index 000000000..1ea076866 --- /dev/null +++ b/docs/docs/guide/design/datasourceEngine.md @@ -0,0 +1,153 @@ +--- +title: 数据源引擎设计 +sidebar_position: 7 +--- +## 核心原理 + +考虑之后的扩展性和兼容性,核心分为了 2 类包,一个是 **datasource-engine** ,另一个是 **datasource-engine-x-handler** ,x 的意思其实是对应数据源的 type,比如说 **datasource-engine-mtop-handler**,也就是说我们会将真正的请求工具放在 handler 里面去处理,engine 在使用的时候由使用方自身来决定需要注册哪些 handler,这样的目的有 2 个,一个是如果将所有的 handler 都放到一个包,对于端上来说这个包过大,有一些浪费资源和损耗性能的问题,另一个是如果有新的类型的数据源出现,只需要按照既定的格式去新增一个对应的 handler 处理器即可,达到了高扩展性的目的; + +![](https://intranetproxy.alipay.com/skylark/lark/0/2020/png/275191/1599545889374-73acbe09-3bb6-4df9-b6f9-80a86764afa2.png?x-oss-process=image%2Fresize%2Cw_720#crop=0&crop=0&crop=1&crop=1&id=zq0Rr&originHeight=370&originWidth=720&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +### DataSourceEngine + +- engine: engine 主要分 2 类,一类是面向 render 引擎的,可以从 engine/interpret 引入,一类是面向出码或者说直接单纯使用数据源引擎的场景,可以从 engine/runtime 引入,代码如下 + +```javascript +import { createInterpret, createRuntime } from '@alilc/lowcode-datasource-engine'; +``` + +create 方法定义如下 + +```javascript +interface IDataSourceEngineFactory { + create(dataSource: DataSource, context: Omit, extraConfig?: { + requestHandlersMap: RequestHandlersMap; + [key: string]: any; + }): IDataSourceEngine; +} +``` + +create 接收三个参数,第一个是 DataSource,对于运行时渲染和出码来说,DataSource 的定义分别如下: + +```javascript +/** + * 数据源对象--运行时渲染 + * @see https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5 + */ +export interface DataSource { + list: DataSourceConfig[]; + dataHandler?: JSFunction; +} +/** + * 数据源对象 + * @see https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5 + */ +export interface DataSourceConfig { + id: string; + isInit: boolean | JSExpression; + type: string; + requestHandler?: JSFunction; + dataHandler?: JSFunction; + options?: { + uri: string | JSExpression; + params?: JSONObject | JSExpression; + method?: string | JSExpression; + isCors?: boolean | JSExpression; + timeout?: number | JSExpression; + headers?: JSONObject | JSExpression; + [option: string]: CompositeValue; + }; + [otherKey: string]: CompositeValue; +} +``` + +但是对于出码来说,create 和 DataSource 定义如下: + +```javascript +export interface IRuntimeDataSourceEngineFactory { + create(dataSource: RuntimeDataSource, context: Omit, extraConfig?: { + requestHandlersMap: RequestHandlersMap; + [key: string]: any; + }): IDataSourceEngine; +} + +export interface RuntimeOptionsConfig { + uri: string; + params?: Record; + method?: string; + isCors?: boolean; + timeout?: number; + headers?: Record; + shouldFetch?: () => boolean; + [option: string]: unknown; +} +export declare type RuntimeOptions = () => RuntimeOptionsConfig; // 考虑需要动态获取值的情况,这里在运行时会真正的转为一个 function + +export interface RuntimeDataSourceConfig { + id: string; + isInit: boolean; + type: string; + requestHandler?: () => {}; + dataHandler: (data: unknown, err?: Error) => {}; + options?: RuntimeOptions; + [otherKey: string]: unknown; +} +/** + * 数据源对象 + * @see https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5 + */ +export interface RuntimeDataSource { + list: RuntimeDataSourceConfig[]; + dataHandler?: (dataMap: DataSourceMap) => void; +} +``` + +2 者的区别还是比较明显的,一个是带 js 表达式一类的字符串,另一个是真正转为直接可以运行的 js 代码,对于出码来说,转为可执行的 js 代码的过程是出码自身负责的,对于渲染引擎来说,它只能接受到初始的 schema json 所以需要数据源引擎来做转化 + +- context:数据源引擎内部有一些使用了 this 的表达式,这些表达式需要求值的时候依赖上下文,因此需要将当前的上下文丢给数据源引擎,另外在 handler 里面去赋值的时候,也会用到诸如 setState 这种上下文里面的 api,当然,这个是可选的,我们后面再说。 + +```javascript +/** + * 运行时上下文--暂时是参考 react,当然可以自己构建,完全没问题 + */ +export interface IRuntimeContext> { + /** 当前容器的状态 */ + readonly state: TState; + /** 设置状态(浅合并) */ + setState(state: Partial): void; + /** 自定义的方法 */ + [customMethod: string]: any; + /** 数据源, key 是数据源的 ID */ + dataSourceMap: Record; + /** 重新加载所有的数据源 */ + reloadDataSource(): Promise; + /** 页面容器 */ + readonly page: IRuntimeContext & { + readonly props: Record; + }; + /** 低代码业务组件容器 */ + readonly component: IRuntimeContext & { + readonly props: Record; + }; +} +``` + +- extraConfig:这个字段是为了留着扩展用的,除了一个必填的字段 **requestHandlersMap** + +```javascript +export declare type RequestHandler = (ds: RuntimeDataSourceConfig, context: IRuntimeContext) => Promise>; +export declare type RequestHandlersMap = Record; +``` + +RequestHandlersMap 是一个把数据源以及对应的数据源 handler 关联起来的桥梁,它的 key 对应的是数据源 DataSourceConfig 的 type,比如 mtop/http/jsonp ... ,每个类型的数据源在真正使用的时候会调用对应的 type-handler,并将当前的参数和上下文带给对应的 handler。 + +create 调用结束后,可以获取到一个 DataSourceEngine 实例 + +```javascript +export interface IDataSourceEngine { + /** 数据源, key 是数据源的 ID */ + dataSourceMap: Record; + /** 重新加载所有的数据源 */ + reloadDataSource(): Promise; +} +``` diff --git a/docs/docs/guide/design/editor.md b/docs/docs/guide/design/editor.md new file mode 100644 index 000000000..d861ae7d6 --- /dev/null +++ b/docs/docs/guide/design/editor.md @@ -0,0 +1,283 @@ +--- +title: 编排模块设计 +sidebar_position: 3 +--- +本篇重点介绍如何从零开始设计编排模块,设计思路是什么?思考编排的本质是什么?围绕着本质,如何设计并实现对应的功能模块。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644397577227-99a77c7d-6a6e-4d92-b222-eaac0ee7988e.png#clientId=u45d0137b-8a6f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=540&id=u4613aa03&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1080&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=911801&status=done&style=none&taskId=u449864e5-516f-41eb-b86a-7c026b797a8&title=&width=960) + +# 编排是什么 +所谓编排,即将设计器中的所有物料,进行布局设置、组件设置、交互设置(JS编写/逻辑编排)后,形成符合业务诉求的 schema 描述。 +# 编排的本质 +首先,思考编排的本质是什么? +编排的本质是生产符合《阿里巴巴中后台前端搭建协议规范》的数据**,**在这个场景里,协议是通过 JSON 来承载的。如: +```json +{ + "componentName": "Page", + "props": { + "layout": "wide", + }, + "children": [ + { + "componentName": "Button", + "props": { + "size": "large" + } + } + ] +} +``` + +可是在真实场景,节点数可能有成百上千,每个节点都具有新增、删除、修改、移动、插入子节点等操作,同时还有若干约束,JSON 结构操作起来不是很便利,于是我们仿 DOM 设计了 **节点模型 & 属性模型,**用更具可编程性的方式来编排,这是**编排系统的基石**。 +其次,每次编排动作后(CRUD),都需要实时的渲染出视图。广义的视图应该包括各种平台上的展现,浏览器、Rax、小程序、Flutter 等等,所以使用何种渲染器去渲染 JSON 结构应该可以由用户去扩展,我们定义一种机制去衔接设计态和渲染态。 +至此,我们已经完成了**编排模块最基础的功能**,接下来,就是完善细节,逐步丰满功能。比如: +1)编排面板的整体功能区划分设计; +2)节点属性设计;节点删除、移动等操作设计;容器节点设计; +3)节点拖拽功能、拖拽定位设计和实现; +4)节点在画布上的辅助功能,比如 hover、选中、选中时的操作项、resize、拖拽占位符等; +5)设计态和渲染态的坐标系转换,滚动监听等; +6)快捷键机制; +7)历史功能,撤销和重做; +8)结构化的插件扩展机制; +9)原地编辑功能; +有非常多模块,但只要记住一点,这些功能的目的都是辅助用户在画布上有更好的编排体验、扩展能力而逐个增加设计的。 +# 编排功能模块 +## 模型设计 +编排实际上操作 schema,但是实际代码运行的过程中,我们将 schema 分成了很多层,每一层有各自的职责,他们所负责的功能是明确清晰的。这就是低代码引擎中的模型设计。 +我们通过将 schema 和常用的操作等结合起来,最终将低代码引擎的模型分为节点模型、属性模型、文档模型和项目模型。 +### 项目模型(`Project`) +项目模型提供项目管理能力。通常一个引擎启动会默认创建一个 `Project` 实例,有且只有一个。项目模型实例下可以持有多个文档模型的实例,而当前处于设计器设计状态的文档模型,我们将其添加 active 标识,也将其称为 `currentDocument`,可以通过 `project.currentDocument` 获得。 +一个 `Project` 包含若干个 `DocumentModel` 实例,即项目模型和文档模型的关系是 1 对 n,如下图所示: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644397244733-2492a7bf-20bf-4610-a335-99cc047037b7.png#clientId=u45d0137b-8a6f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=824&id=uadf98e25&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1648&originWidth=1226&originalType=url&ratio=1&rotation=0&showTitle=false&size=1260603&status=done&style=none&taskId=u50b01e40-96f2-4629-a1b9-1b5a22967fc&title=&width=613) +### 文档模型(`DocumentModel`) +文档模型提供文档管理的能力,每一个页面即一个文档流,对应一个文档模型。文档模型包含了一组 Node 组成的一颗树,类似于 DOM。我们可以通过文档模型来操作 `Node` 树,来达到管理文档模型的能力。每一个文档模型对应多个 `Node`,但是根 `Node` 只有一个,即 `rootNode` 和 `nodes`。 +文档模型可以通过 `Node` 树,通过 `doc.schema` 来导出文档的 `schema`,并使用其进行渲染。 +他们的关系如下图: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644397244327-d931aff8-40d4-47df-8b1c-b81c06c40c48.png#clientId=u45d0137b-8a6f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=745&id=L5LAf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1490&originWidth=960&originalType=url&ratio=1&rotation=0&showTitle=false&size=1110316&status=done&style=none&taskId=u7d873681-f61a-40ea-baaa-8f7b76c769c&title=&width=480) +### 节点模型(`Node`) +我们先看一下一个 `Node` 在 `schema` 中对应的示例: +```json +{ + componentName: 'Text', + id: 'node_k1ow3cbf', + props: { + showTitle: false, + behavior: 'NORMAL', + content: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '个人信息', + type: 'i18n', + }, + fieldId: 'text_k1ow3h1j', + maxLine: 0, + }, + condition: true, +} +``` +上面的示例是一个 `Text` 的 `Node` 节点,而我们的 `Node` 节点模型就是负责这一层级的 `Schema` 管理。它的功能聚焦于单层级的 schema 相关操作。我们可以看一下节点模型的一些方法,了解其功能。 +```typescript +declare class Node { + // Props + props: Props; + get propsData(): PropsMap | PropsList | null; + getProp(path: string, stash?: boolean): Prop | null; + getPropValue(path: string): any; + setPropValue(path: string, value: any): void; + clearPropValue(path: string): void; + mergeProps(props: PropsMap): void; + setProps(props?: PropsMap | PropsList | Props | null): void; + + // Node + get parent(): ParentalNode | null; + get children(): NodeChildren | null; + get nextSibling(): Node | null; + get prevSibling(): Node | null; + remove(useMutator?: boolean, purge?: boolean): void; + select(): void; + hover(flag?: boolean): void; + replaceChild(node: Node, data: any): Node; + mergeChildren(remover: () => any, adder: (children: Node[]) => NodeData[] | null, sorter: () => any): void; + removeChild(node: Node): void; + insert(node: Node, ref?: Node, useMutator?: boolean): void; + insertBefore(node: any, ref?: Node, useMutator?: boolean): void; + insertAfter(node: any, ref?: Node, useMutator?: boolean): void; + + // Schema + get schema(): Schema; + set schema(data: Schema); + export(stage?: TransformStage): Schema; + replaceWith(schema: Schema, migrate?: boolean): any; +} +``` +这里没有展示全部的方法,但是我们可以发现,`Node` 节点模型核心功能点有三个 +1)`Props` 管理:通过 `Props` 实例管理所有的 `Prop`,包括新增、设置、删除等 `Prop` 相关操作。 +2)`Node` 管理:管理 `Node` 树的关系,修改当前 `Node` 节点或者 `Node` 子节点等。 +3)`Schema` 管理:可以通过 `Node` 获取当前层级的 `Schema` 描述协议内容,并且也可以修改它。 +通过 `Node` 这一层级,对 `Props`、`Node` 树和 `Schema` 的管理粒度控制到最低,这样扩展性也就更强。 +### 属性模型(Prop) +一个 `Props` 对应多个 `Prop`,每一个 `Prop` 对应 schema 的 `props` 下的一个字段。 +`Props` 管理的是 `Node` 节点模型中的 `props` 字段下的内容。而 `Prop` 管理的是 `props` 下的每一个 `key` 的内容,例如下面的示例中,一个 `Props` 管理至少 6 个 `Prop`,而其中一个 `Prop` 管理的是 `showTitle` 的结果。 +```typescript +{ + props: { + showTitle: false, + behavior: 'NORMAL', + content: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '个人信息', + type: 'i18n', + }, + fieldId: 'text_k1ow3h1j', + maxLine: 0, + }, +} +``` +### 组件描述模型(ComponentMeta) +编排已经等价于直接操作节点 & 属性了,而一个节点和一组对应的属性相当于一个真实的组件,而真实的组件一定是有约束的,比如组件名、组件类型、支持哪些属性以及属性类型、组件能否拖动、支持哪些扩展操作、组件是否是容器型组件、A 组件中能否放入 B 组件等等。 +于是,我们设计了一份协议专门负责组件描述,即《中后台搭建组件描述协议》,而编排模块中也有负责解析和使用符合描述协议规范的模块。 +每一个组件对应一个 `ComponentMeta` 的实例,其属性和方法就是描述协议中的所有字段,所有 `ComponentMeta` 都由设计器器的 `designer` 模块进行创建和管理,其他模块通过 `designer` 来获取指定的 `ComponentMeta` 实例,尤其是每个 `Node` 实例上都会挂载对应的 `ComponentMeta` 实例。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644397244032-8aa176d5-a74e-49c9-a309-251dba81b37c.png#clientId=u45d0137b-8a6f-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u044bdacd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=756&originWidth=998&originalType=url&ratio=1&rotation=0&showTitle=false&size=294191&status=done&style=none&taskId=uee9dfeef-f646-41db-afd4-a0b1acf8019&title=) +组件描述模型是后续编排辅助的基础,包括设置面板、拖拽定位机制等。 +### 项目、文档、节点和属性模型关系 +整体来看,一个 Project 包含若干个 DocumentModel 实例,每个 DocumentModel 包含一组 Node 构成一颗树(类似 DOM 树),每个 Node 通过 Props 实例管理所有 Prop。整体的关系图如下。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1645066478805-57802de7-382e-4105-99cb-161b8c07f117.png#clientId=uf22d1b91-761d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=678&id=uc2db9afa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1356&originWidth=1694&originalType=binary&ratio=1&rotation=0&showTitle=false&size=285816&status=done&style=none&taskId=u6526eafb-f082-46dd-8584-9dd9b1bb395&title=&width=847) +节点 & 属性模型是引擎基石,几乎贯穿所有模块,相信从上面的类图已经能看出几个基础类的职责以及依赖关系。 +节点 & 属性模型等价于 JSON 数据结构,而编排的本质是产出 JSON 数据结构,现在可以重新表述为编排的本质是操作节点 & 属性模型了。 +```typescript +// 一段编排的示例代码 +rootNode.insertAfter({ componentName: 'Button', props: { size: 'medium' } }); +rootNode.insertAfter({ componentName: 'Button', props: { size: 'medium' } }); +rootNode.children.get(1).getProp('size').setValue('large'); +rootNode.children.get(2).remove(); +rootNode.export(); +// => 产出 schema +``` + +## 画布渲染 +画布渲染使用了设计态与渲染态的双层架构。![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644397244328-61bb9ce7-a27c-4917-baee-c0a93bd0fd36.png#clientId=u45d0137b-8a6f-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u3c9164cd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=710&originWidth=1416&originalType=url&ratio=1&rotation=0&showTitle=false&size=287707&status=done&style=none&taskId=u6c993606-238b-478f-9317-397eb0708eb&title=) +如上图,设计器和渲染器其实处在不同的 Frame 下,渲染器以单独的 `iframe` 嵌入。这样做的好处,一是为了给渲染器一个更纯净的运行环境,更贴近生产环境,二是扩展性考虑,让用户基于接口约束自定义自己的渲染器。 + +### xxx-renderer +xxx-renderer 是一个纯 renderer,即一个渲染器,通过给定输入 schema、依赖组件和配置参数之后完成渲染。 + +### xxx-simulator-renderer +xxx-simulator-renderer 通过和 host进行通信来和设计器打交道,提供了 `DocumentModel` 获取 schema 和组件。将其传入 xxx-renderer 来完成渲染。 +另外其提供了一些必要的接口,来帮助设计器完成交互,比如点击渲染画布任意一个位置,需要能计算出点击的组件实例,继而找到设计器对应的 Node 实例,以及组件实例的位置/尺寸信息,让设计器完成辅助 UI 的绘制,如节点选中。 + +### react-simulator-renderer +以官方提供的 react-simulator-renderer 为例,我们看一下点击一个 DOM 节点后编排模块是如何处理的。 +首先在初始化的时候,renderer 渲染的时候会给每一个元素添加 ref,通过 ref 机制在组件创建时将其存储起来。在存储的时候我们给实例添加 `Symbol('_LCNodeId')` 的属性。 +当点击之后,会去根据 `__reactInternalInstance$` 查找相应的 fiberNode,通过递归查找到对应的 React 组件实例。找到一个挂载着 `Symbol('_LCNodeId')` 的实例,也就是上面我们初始化添加的属性。 +通过 `Symbol('_LCNodeId')` 属性,我们可以获取 Node 的 id,这样我们就可以找到 Node 实例。 +通过 `getBoundingClientRect` 我们可以获取到 Node 渲染出来的 DOM 的相关信息,包括 `x`、`y`、`width`、`height` 等。 +通过 DOM 信息,我们将 focus 节点所需的标志渲染到对应的地方。hover、拖拽占位符、resize handler 等辅助 UI 都是类似逻辑。 + +### 通信机制 +既然设计器和渲染器处于两个 Frame,它们之间的事件通信、方法调用是通过各自的代理对象进行的,不允许其他方式,避免代码耦合。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644397245794-927fa812-ea1c-4bc1-967b-4c1dff924fd1.png#clientId=u45d0137b-8a6f-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u22088dc8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=648&originWidth=1290&originalType=url&ratio=1&rotation=0&showTitle=false&size=223251&status=done&style=none&taskId=uf720b921-07c2-4cda-a101-74e863ce10d&title=) +**host** +host 可以访问设计器的所有模块,由于 renderer 层不负责与设计器相关的交互。所以增加了一层 host,作为通信的中间层。host 可以访问到设计器中所有模块,并提供相关方法供 simulator-renderer 层调用。例如schema 的获取、组件获取等。 +simulator-renderer 通过调用 host 的方法,将 schema、components 等参数传给 renderer,让 renderer 进行渲染。 + +**xxx-simulator-renderer** +为了完成双向交互,simulator-renderer 也需要提供一些方法来供 host 层调用,之后当设计器和用户有交互,例如上述提到的节点选中。这里需要提供的方法有: + +- getClientRects +- getClosestNodeInstance +- findDOMNodes +- getComponent +- setNativeSelection +- setDraggingState +- setCopyState +- clearState + +这样,host 和 simulator-renderer 之间便通过相关方法实现了双向通信,能在隔离设计器的基础上完成设计器到画布和画布到设计器的通信流程。 + +## 编排辅助的核心 +### 设置面板与设置器 +当在渲染画布上点击一个 DOM 节点,我们可以通过 xxx-simulator-renderer 获取 `Node` 节点,我们在 `Node` 上挂载了 `ComponentMeta` 实例。通过 `ComponentMeta` 我们获取到当前组件的描述模型。通过描述模型,我们即可获得组件、即当前 Node 支持的所有属性配置。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644397246381-ed265cc3-b6c1-4a38-985a-df1898862fe8.png#clientId=u45d0137b-8a6f-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u1422ee5e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=985&originWidth=1500&originalType=url&ratio=1&rotation=0&showTitle=false&size=332497&status=done&style=none&taskId=u2bbbb9ae-7834-48ba-b87f-fa5ccf8fa39&title=) +#### 设置面板 +设置面板对于配置项的呈现结构是通过 `ComponentMeta.configure` 来确定的。 +```json +{ + "component": { + "isContainer": true + }, + "props": { + "isExtends": true, + "override": [ + { + "name": "count", + "title": { + "label": "展示的数字", + "tip": "count|大于 overflowCount 时显示为 ${overflowCount}+,为 0 时默认隐藏", + "docUrl": "https://fusion.alibaba-inc.com/pc/component/basic/badge" + }, + "setter": { + "componentName": "MixedSetter", + "props": { + "setters": [ + "StringSetter", + "ExpressionSetter" + ] + } + } + } + ] + } +} +``` +上述的 `component.isContainer` 描述了这个组件是否是一个容器组件。而 props 下的属性就是我们在设置面板中展示的属性,包含了这个属性的名称、使用的设置器、配置之后影响的是哪个属性等。 +而这只是描述,编排模块的 `SettingTopEntry` 便是管理设置面板的实现模块。 +`SettingTopEntry` 包含了 n 个 `SettingField`,每一个 `SettingField` 就对应下面要将的设置器。即 `SettingTopEntry` 负责管理多个 `SettingField`。 +#### 设置器 +选中节点可供配置的属性都有相应的设置器配置,比如文本、数字、颜色、JSON、Choice、I18N、表达式 等等,或者混合多种。 +设置器本质上是一个 React 组件,但是设置面板在渲染时会传入当前配置项对应的 `SettingField` 实例,`SettingField` 本质上就是包裹了 `Prop` 实例,设置器内部的行为以及 UI 变化都由设置器自己把控,但当属性值发生变化时需要通过 `SettingField` 下的 `Prop` 来修改值,因为修改 `Prop` 实例就相当于修改了 schema。一方面这样的设置器设置之后,保存的 schema 才是正确的,另外一方面,只有 schema 变化了,才能触发渲染画布重新渲染。 +### 拖拽引擎 & 拖拽定位机制 +![](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644397245744-af8b951c-ee22-4fcb-9175-6f1b0eb2ba37.gif#clientId=u45d0137b-8a6f-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uf8b286a2&margin=%5Bobject%20Object%5D&originHeight=917&originWidth=1425&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u9b4475cb-bb5c-4c86-9b03-b824bc46b12&title=) +拖拽引擎(`Dragon`)核心完成的工作是将被拖拽对象拖拽到目标位置,涉及到几个概念: + +- 被拖拽对象 - `DragObject` +- 拖拽到的目标位置 - `DropLocation` +- 拖拽感应区 - `ISensor` +- 定位事件 - `LocateEvent` + +#### Sensor +在引擎初始化的时候,我们监听 `document` 和 iframe `contentDocument` 的 `mouse`、`keyboard`、`drag` 事件来感知拖拽的发生。而这些监听的区域我们又称为拖拽感应区,也就是 `Sensor`。`Sensor` 会有多个,因为感应器有多个,默认设置器和设置面板是没有 `Sensor`,但是他们是可以注册 `Sensor` 来增加感应区域,例如大纲树就注册了自己的 `Sensor`。 + +`Sensor` 有两个关键职责: +1)用于事件对象转换,比如坐标系换算 +2)根据拖拽过程中提供的位置信息,结合每一层 `Node` 也就是组件包含的描述信息,知道其是否能作为容器等限制条件,来进行进一步的定位,最后计算出精准信息来进行视图渲染。 + +**拖拽流程** +在引擎初始化的时候,初始化多个 `Sensor`。 +当拖拽开始的时候,开启 `mousemove`、`mouseleave`、`mouseover` 等事件的监听。 +拖拽过程中根据 `mousemove` 的 `MouseEvent` 对象封装出 `LocateEvent` 对象,继而交给相应 `sensor` 做进一步定位处理。 +拖拽结束时,根据拖拽的结果进行 schema 变更和视图渲染。 +最后关闭拖拽开始时的事件监听。 + +#### 拖拽方式 +根据拖拽的对象不同,我们将拖拽分为几种方式: +1)**画布内拖拽:**此时 sensor 是 simulatorHost,拖拽完成之后,会根据拖拽的位置来完成节点的精确插入。 +2)**从组件面板拖拽到画布**:此时的 sensor 还是 simulatorHost,因为拖拽结束的目标还是画布。 +3)**大纲树面板拖拽到画布中**:此时有两个 sensor,一个是大纲树,当我们拖拽到画布区域时,画布区域内的 simulatorHost 开始接管。 +4)画布拖拽到画布中:从画布中开始拖拽时,最新生效的是 simulatorHost,当离开画布到大纲树时,大纲树 sensor 开始接管生效。当拖拽到大纲树的某一个节点下时,大纲树会将大纲树中的信息转化为 schema,然后渲染到画布中。 +## 其他 +引擎的编排能力远远不止上述所描述的功能,这里只描述了其核心和关键的功能。在整个引擎的迭代和设计过程中还有很多细节来使我们的引擎更好用、更容易扩展。 +**schema 处理的管道机制** +通过PropsReducer 的管道机制,用户可以定制自己需要的逻辑,来修改 Schema。 +**组件 metadata 处理的管道机制** +组件的描述信息都收拢在各自的 ComponentMeta 实例内,涉及到的消费方几乎遍及整个编排过程,包括但不限于 组件拖拽、拖拽辅助 UI、设置区、原地编辑、大纲树 等等。 +在用户需要自定义的场景,开放 ComponentMeta 的修改能力至关重要,因此我们设计了 metadata 初始化/修改的管道机制。 +**hotkey & builtin-hotkey** +快捷键的实现,以及引擎内核默认绑定的快捷键行为。 +**drag resize 引擎** +对于布局等类型的组件,支持拖拽改变大小。resize 拖拽引擎根据组件 ComponentMeta 声明来开启,拖拽后,触发组件的钩子函数(`onResizeStart` / `onResize` / `onResizeEnd`),完成 resize 过程。 +**OffsetObserver** +设计态的辅助 UI 需要根据渲染态的视图变化而变化,比如渲染容器滚动了,此时通过 OffsetObserver 做一个动态的监听。 +**插件机制** +我们希望保持引擎内核足够小,但拥有足够强的扩展能力,所有扩展功能都通过插件机制来承载。 diff --git a/docs/docs/guide/design/generator.md b/docs/docs/guide/design/generator.md new file mode 100644 index 000000000..19d34560b --- /dev/null +++ b/docs/docs/guide/design/generator.md @@ -0,0 +1,100 @@ +--- +title: 出码模块设计 +sidebar_position: 5 +--- +本篇主要讲解了出码模块实现的基本思路与一些概念。如需接入出码和定制出码方案,可以参考《[使用出码功能](https://www.yuque.com/lce/doc/cplfv0)》一节。 +# npm 包与仓库信息 +| **NPM 包** | **代码仓库** | **说明** | +| --- | --- | --- | +| [@alilc/lowcode-code-generator](https://www.npmjs.com/package/@alilc/lowcode-code-generator) | [alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) +(子目录:modules/code-generator) | 出码模块核心库,支持在 node 环境下运行,也提供了浏览器下运行的 standalone 模式 | +| [@alilc/lowcode-plugin-code-generator](https://www.npmjs.com/package/@alilc/lowcode-plugin-code-generator) | [alibaba/lowcode-code-generator-demo](https://github.com/alibaba/lowcode-code-generator-demo) | 出码示例 -- 浏览器端出码插件 | + +# 出码模块原理 + +出码模块的输入和输出很简单: +![](https://cdn.nlark.com/yuque/0/2022/jpeg/263300/1644825891969-1777dbe4-5ffc-4c94-a022-3aba0c116021.jpeg) + +这里有几个概念: + +- schema: 搭建协议内容,指符合《阿里巴巴中后台前端搭建协议规范》的 schema +- solution:出码方案,指具体的项目框架(如 Rax,Ice.js) +- Source Codes:生成的源代码,以目录树的形式进行描述 + +可以看出,这是一个与用户基本没有交互,通过既定的流程完成整个功能链路的模块。其核心暴露的是一个将搭建协议 schema 按既定的 solution 转换为代码的函数。对于使用者来说就是一个输入输出都确定的黑盒系统。 + +## 出码流程概述 +出码模块和编译器很类似,都是将代码的一种表现形式转换成另一种表现形式,如: + +**编译器流程** +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644403720547-e5021c3c-0c63-47ea-b8f0-1af79bbae3ef.png#clientId=u70bc6751-fff0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=246&id=u3b3e3126&margin=%5Bobject%20Object%5D&name=image.png&originHeight=492&originWidth=3228&originalType=binary&ratio=1&rotation=0&showTitle=false&size=110319&status=done&style=none&taskId=u32f55257-a18c-4caa-9334-0b3ca5b0bc0&title=&width=1614) + +**出码模块流程** +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644402753768-02d402da-dd1a-4783-9021-606e276d4c68.png#clientId=u70bc6751-fff0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=182&id=GlAz4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=182&originWidth=1536&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23743&status=done&style=none&taskId=ubbea9289-cb03-4fcf-9ce7-22da1ce077b&title=&width=1536) + +## 出码流程详解 +### 协议解析 +协议解析主要是将输入的 schema 解析成更适合出码模块内部使用的数据结构的过程。这样在后面的代码生成过程中就可以直接用这些数据,不必重复解析了。 +![](https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/154771/1636960093808-624b5e50-18d6-476d-a951-556912832cdb.jpeg) +主要步骤如下: + +- 解析三方组件依赖 +- 分析 ref API 的使用情况 +- 建立容器之间的依赖关系索引 +- 分析容器内的组件依赖关系 +- 分析路由配置 +- 分析 utils 和 NPM 包依赖关系 +- 其他兼容处理 + +### 前置优化 +前置优化是计划基于策略对 schema 做一些优化。 +主要逻辑分为分析、规则和优化三个部分,组合为一个支持通过配置进行一定程度定制化的策略包。每个策略包会先执行分析器,对输入进行特征提取,然后通过规则对特征进行判断,决定是否执行优化动作: +![](https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/154771/1636960832211-0db6925b-c4b5-4be4-a883-fdd52a47e19a.jpeg) + +### 代码生成 +代码生成的流程如下: +![](https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/154771/1636975834791-e3fe89f9-cd2d-446f-aa9f-fca0bb1d56c3.jpeg) +如果简单粗暴地拼字符串生成源代码将难以扩展和维护,因此出码模块在代码生成过程中将代码进行了一些抽象化。 +日常开发中,我们常常是基于某一个特定的项目框架,将一些配置、UI 代码、逻辑代码放到他们应该在的地方,最终形成一套可以 run 起来的业务系统。那么其实对于出码这件事,我们也可以层层拆解,**项目 -> 插槽 -> 模块 -> 文件 -> 代码块**(代码片段)。这样就能将复杂的项目产出问题,拆分为一个个相对专注且单一的代码块产出问题,同时也支持组合复用。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644402753919-1f063db3-5ad9-406b-9b45-6eeef79ea38b.png#clientId=u70bc6751-fff0-4&crop=0&crop=0&crop=1&crop=1&height=305&id=LgGUo&margin=%5Bobject%20Object%5D&name=image.png&originHeight=454&originWidth=892&originalType=binary&ratio=1&rotation=0&showTitle=false&size=230321&status=done&style=none&taskId=u50d38d84-3a3e-4c77-af05-8bc5f794643&title=&width=600) +注:中间表达结构即为对 Schema 解析后的结构化产物 + +**插槽** +首先来看下插槽,插槽描述了对应模块在项目中相对路径,并且可以对模块做固定的命名。每个插槽都有一系列插件来完成代码产出工作。生成的一个或多个文件,最终会依照插槽的描述放入项目中。 +```typescript +// 项目模版 +export interface IProjectTemplate { + slots: Record; +} + +// 插槽 +interface IProjectSlot { + path: string[]; + fileName?: string; +} + +// 插槽出码插件配置 +interface IProjectPlugins { + [slotName: string]: BuilderComponentPlugin[]; +} +``` +**代码块** +代码块是出码产物的最小单元,由出码模块插件产出,多个代码块最后会被组装为代码文件。每个代码块通过 name 描述自己,再通过 linkAfter 描述应该跟在哪些 name 的代码块后面。 +```typescript +interface ICodeChunk { + type: ChunkType; // 处理类型 ast | string | json + fileType: string; // 文件类型 js | css | ts ... + name: string; // 代码块名称,与 linkAfter 相关 + subModule?: string; // 模块内文件名,默认是 index + content: ChunkContent; // 代码块内容,数据格式与 type 相关 + linkAfter: string[]; // +} +``` + +### 后置优化 +后置优化分为文件级别和项目级别两种: + +- 文件级别:在生成完一个文件后进行处理 +- 项目级别:在所有文件都生成完了之后进行处理 + +文件级别的后置优化目前主要是有 prettier 这个代码格式化工具。 diff --git a/docs/docs/guide/design/materialParser.md b/docs/docs/guide/design/materialParser.md new file mode 100644 index 000000000..42aaadcf0 --- /dev/null +++ b/docs/docs/guide/design/materialParser.md @@ -0,0 +1,70 @@ +--- +title: 入料模块设计 +sidebar_position: 2 +--- +## 介绍 +入料模块负责物料接入,通过自动扫描、解析源码组件,产出一份符合《中后台低代码组件描述协议》的** **JSON Schema。这份 Schema 包含基础信息和属性描述信息部分,低代码引擎会基于它们在运行时自动生成一份configure 配置,用作设置面板展示。 + +## npm 包与仓库信息 + +- npm 包:@alilc/lowcode-material-parser +- 仓库:[https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) 下的 modules/material-parser + +## 原理 +入料模块使用动静态分析结合的方案,动态胜在真实,静态胜在细致,不过全都依赖源码中定义的属性,若未定义,或者定义错误,则无法正确入料。 + +### 整体流程 +大体分为本地化、扫描、解析、转换、校验 5 部分,如下图所示。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644396777217-58246e52-b5b9-4509-8bac-db2820535c39.png#clientId=u41c9ba96-15b6-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u8b5a70fd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=206&originWidth=2116&originalType=url&ratio=1&rotation=0&showTitle=false&size=51634&status=done&style=none&taskId=u76c5b45a-6fa8-404e-8d8b-f1d6f38438e&title=) + +### 静态解析 +在静态分析时,分为 JS 和 TS 两种情况。 + +#### 静态解析 JS +在 JS 情况下,基于 react-docgen 进行扩展,自定义了 resolver 及 handler,前者用于寻找组件定义,后者用于解析 propTypes、defaultProps 等信息,整体流程图如下: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644396777192-c9d3c3b3-c7d2-4780-b7dc-632349b00edb.png#clientId=u41c9ba96-15b6-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u3edfb33c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=478&originWidth=2176&originalType=url&ratio=1&rotation=0&showTitle=false&size=93513&status=done&style=none&taskId=ua23bd89a-3d0c-43cf-936b-13080cdc5ef&title=) + +react-docgen 使用 babel 生成语法树,再使用 ast-types 进行遍历去寻找组件节点及其属性类型定义。原本的 react-docgen 只能解析单文件,且不能解析 IIFE、逗号表达式等语法结构(一般出现在转码后的代码中)。笔者对其进行改造,使之可以递归解析多文件去查找组件定义,且能够解开 IIFE,以及对逗号表达式进行转换,以方便后续的组件解析。另外,还增加了子组件解析的功能,即类似 `Button.Group = Group` 这种定义。 + +#### 静态解析 TS +在 TS 情况下,还要再细分为 TS 源码和 TS 编译后的代码。 +TS 源码中,React 组件具有类型签名;TS 编译后的代码中,dts 文件(如有)包含全部的 class / interface / type 类型信息。可以从这些类型信息中获取组件属性描述。整体流程图如下: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644396777193-14c74287-d0cb-4864-ba64-9259a88c8b99.png#clientId=u41c9ba96-15b6-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ud7fc6e1a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=240&originWidth=2280&originalType=url&ratio=1&rotation=0&showTitle=false&size=59331&status=done&style=none&taskId=u711959ae-241a-48c9-a446-59b9fcdc52e&title=) + +react-docgen 内置了 TypeScript 的 babel 插件,所以也具备解析 interface 的能力,可惜能力有限,babel 只能解析 TS 代码,但没法做类型检查,类型处理是由 react-docgen 实现的,它对于extends/implements/utility 的情况处理不好,并且没有类型推断,虽然可以对其功能进行完善,不过这种情况下,应该借助 TypeScript Compiler 的能力,而非自己造轮子。通过调研,发现市面上有 typescript-react-docgen 这个项目。它在底层依赖了 TypeScript,且产出的数据格式与 react-docgen 一致,所以我们选择基于它进行解析。 + +TypeScript Compiler 会递归解析某个文件中出现及引用的全部类型,当然,前提是已经定义或安装了相应的类型声明。typescript-react-docgen 会调用 TypeScript Compiler 的 API,获取每个文件输出的类型,判断其是否为 React 组件。满足下列条件之一的,会被判定为 React 组件: + +1. 获取其函数签名,如果只有一个入参,或者第一个入参名称为 props ,会被判定为函数式组件; +2. 获取其 `constructor` 方法,如果其返回值包含 props 属性,会被判定为有状态组件。 + +然后,遍历组件的 props 类型,获取每个属性的类型签名字符串,比如 `(a: string) => void`。typescript-react-docgen 可以克服 react-docgen 解析 TypeScirpt 类型的问题,但是每个类型都以字符串的形式来呈现,不利于后续的解析。所以,笔者对其进行了扩展,递归解析每一层的属性值。此外,在函数式组件的判定上,笔者做了完善,会看函数的返回值是否为 `ReactElement` ,若是,才为函数式组件。 + +下面讲对于一些特殊情况的处理。 +**循环定义** +TypeScript 类型可以循环定义,比如下面的 JSON 类型: +```typescript +interface Json { + [x: string]: string | number | boolean | Json | JsonArray; +} +type JsonArray = Array; +``` +因为低代码组件描述协议中没有引用功能,而且也不方便在界面上展示出来,所以这种循环定义无需完全解析,入料模块会在检测到循环定义的时候,把类型简化为 `object` 。对于特殊的类型,如 JSON,可以用相应的 Setter 来编辑。 + +**复杂类型** +TypeScript Compiler 会将合成类型的所有属性展开,比如 `boolean | string`,会被展开为 `true | false | string`,这带来了不必要的精确,我们需要的只是 `boolean | string` 而已。当然,对于这个例子,我们很容易把它还原回 `boolean | string`,然而,对于诸如 `React.ButtonHTMLAttributes & {'data-name': string}` 这种类型,它会把 `ButtonHTMLAttributes` 中众多的属性和 `data-name` 混杂在一起,完全无法分辨,只能以展开的形式提供。这 100 多个属性,如果都放在设置面板,绝对是使用者的噩梦,所以,其结果会被简化为 `object` 。当然,即使没有 `{'data-name': string}`,`ButtonHTMLAttributes` 也是没有单独的 Setter 的,同样会被简化为 `object` 。 + +### 动态解析 +当一个组件,使用静态解析无法入料时,会使用动态解析。 +整体流程图如下: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644396776984-d390ec0c-33c6-4468-b68c-555a263b097e.png#clientId=u41c9ba96-15b6-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ube12dcbd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=449&originWidth=2516&originalType=url&ratio=1&rotation=0&showTitle=false&size=84334&status=done&style=none&taskId=ube349a5b-94c0-4a77-9bf3-ffe453226e1&title=) +基本思想很简单,require 组件进来,然后读取其组件类上定义的 propTypes 和 defaultProps 属性。这里使用了 parse-prop-types 库,使用它的时候必须在组件之前引用,因为它会先对 prop-types 库进行修改,在每个 PropTypes 透出的函数上挂上类型,比如 string, number 等等,然后再去遍历。动态解析可以解析出全部的类型信息,因为 PropTypes 有可能引入依赖组件的一些类型定义,这在静态解析中很难做到,或者成本较高,而对于动态解析来说,都由运行时完成了。 + +##### 技术细节 +值得注意的是,有些 js 文件里还会引入 css 文件,而且从笔者了解的情况来看,这种情况在集团内部不在少数。这种组件不配合 webpack 使用,肯定会报错,但是使用 webpack 会明显拖慢速度,所以笔者采用了sandbox 的方式,对 require 进来的类 css 文件进行 mock。这里,笔者使用了 vm2 这个库,它对 node 自带的 vm 进行了封装,可以劫持文件中的 require 方法。因为 parse-prop-types 的修改在沙箱中会失效,所以笔者也 mock 了组件中的 prop-types 库。 + +### 整体大图 +把上述的静态解析和动态解析流程结合起来,可以得到以下大图。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644396777248-9e26dd26-51ac-473e-ae66-c08f8bed3aeb.png#clientId=u41c9ba96-15b6-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ue6806530&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1072&originWidth=2658&originalType=url&ratio=1&rotation=0&showTitle=false&size=184784&status=done&style=none&taskId=u0d982b36-25b8-442f-8d98-43b9e1629bb&title=) diff --git a/docs/docs/guide/design/renderer.md b/docs/docs/guide/design/renderer.md new file mode 100644 index 000000000..74674bcdb --- /dev/null +++ b/docs/docs/guide/design/renderer.md @@ -0,0 +1,210 @@ +--- +title: 渲染模块设计 +sidebar_position: 4 +--- +# 低代码渲染介绍 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644398134750-7b352564-a400-460e-a189-e72ef551624f.png#clientId=udd4eb4ee-bdb1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=198&id=OSLhL&margin=%5Bobject%20Object%5D&name=image.png&originHeight=872&originWidth=1440&originalType=binary&ratio=1&rotation=0&showTitle=false&size=193396&status=done&style=none&taskId=ua5d2ae84-0117-4744-a49c-bd3dd1b8a5e&title=&width=327) +基于 Schema 和物料组件,如何渲染出我们的页面?这一节描述的就是这个。 + +# npm 包与仓库信息 + +- React 框架渲染 npm 包:@alilc/lowcode-react-renderer +- Rax 框架渲染 npm 包:@alilc/lowcode-rax-renderer +- 仓库:[https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) 下的 + - packages/renderer-core + - packages/react-renderer + - packages/react-simulator-renderer + +# 渲染框架原理 +## 整体架构 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644398135242-d83a9d48-c244-4869-b61e-70e825006727.png#clientId=udd4eb4ee-bdb1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=531&id=udb49df4c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1062&originWidth=1686&originalType=binary&ratio=1&rotation=0&showTitle=false&size=821823&status=done&style=none&taskId=u90381546-a7a8-4099-b24f-844b8489f4d&title=&width=843) + +- 协议层:基于标准的《阿里巴巴中后台前端搭建协议规范》产出的 Schema 作为我们的规范协议。 +- 能力层:提供组件、区块、页面等渲染所需的核心能力,包括 Props 解析、样式注入、条件渲染等。 +- 适配层:由于我们使用的运行时框架不是统一的,所以统一使用适配层将不同运行框架的差异部分,通过接口对外,让渲染层注册/适配对应所需的方法。能保障渲染层和能力层直接通过适配层连接起来,能起到独立可扩展的作用。 +- 渲染层:提供核心的渲染方法,由于不同运行时框架提供的渲染方法是不同的,所以其通过适配层进行注入,只需要提供适配层所需的接口,即可实现渲染。 +- 应用层:根据渲染层所提供的方法,可以应用到项目中,根据使用的方法和规模即可实现应用、页面、区块的渲染。 + +## 核心解析 +这里主要解析一下刚刚提到的架构中的适配层和渲染层。 +### 适配层 +适配层提供的是各个框架之间的差异项。比如 `React.createElement` 和 `Rax.createElement` 方法是不同的。所以需要在适配层对 API 进行抹平。 + +#### React +```typescript +import { createElement } from 'react'; +import { + adapter, +} from '@ali/lowcode-renderer-core'; + +adapter.setRuntime({ + createElement, +}); +``` +#### Rax +```typescript +import { createElement } from 'rax'; +import { + adapter, +} from '@ali/lowcode-renderer-core'; + +adapter.setRuntime({ + createElement, +}); +``` +这时,在核心层使用的 `createElement` 会基于使用不同的 renderer 而使用不同的方法,自动适配框架所需的运行时方法。 + +所需的方法包括: + +- `setRuntime`:设置运行时相关方法 + - `Component`:组件类,参考 React 的 `Component`。 + - `PureComponent`:组件类,参考 React 的 `PureComponent`。 + - `createContext`:创建一个 `Context` 对象的方法。例如,当 React 渲染一个订阅了这个 `Context` 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 `Provider` 中读取到当前的 `context` 值。 + - `createElement`:创建 `Component` 元素,例如在 React 中即为创建 React 元素。 + - `forwardRef`:ref 转发的方法。Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。 + - `findDOMNode`:是一个访问底层 DOM 节点的方法。如果组件已经被挂载到 DOM 上,此方法会返回浏览器中相应的原生 DOM 元素。 +- `setRenderers` + - `PageRenderer`:页面渲染的方法。可以定制页面渲染的生命周期,定制导航,定制路由等。 + - `ComponentRenderer`:组件渲染的方法。 + - `BlockRenderer`:区块渲染的方法。 + +### 渲染层 +#### React Renderer +内部的技术栈统一都是 React,大多数适配层的 API 都是按照 React 来设计的,所以对于 React Renderer 来说,需要做的不多。 +React Renderer 的代码量很少,主要是将 React API 注册到适配层中。 +```typescript +import React, { Component, PureComponent, createElement, createContext, forwardRef, ReactInstance, ContextType } from 'react'; +import ReactDOM from 'react-dom'; +import { + adapter, + pageRendererFactory, + componentRendererFactory, + blockRendererFactory, + addonRendererFactory, + tempRendererFactory, + rendererFactory, + types, +} from '@ali/lowcode-renderer-core'; +import ConfigProvider from '@alifd/next/lib/config-provider'; + +window.React = React; +(window as any).ReactDom = ReactDOM; + +adapter.setRuntime({ + Component, + PureComponent, + createContext, + createElement, + forwardRef, + findDOMNode: ReactDOM.findDOMNode, +}); + +adapter.setRenderers({ + PageRenderer: pageRendererFactory(), + ComponentRenderer: componentRendererFactory(), + BlockRenderer: blockRendererFactory(), + AddonRenderer: addonRendererFactory(), + TempRenderer: tempRendererFactory(), + DivRenderer: blockRendererFactory(), +}); + +adapter.setConfigProvider(ConfigProvider); +``` + +#### Rax Renderer +Rax 的大多数 API 和 React 基本也是一致的,差异点在于重写了一些方法。 +```typescript +import { Component, PureComponent, createElement, createContext, forwardRef } from 'rax'; +import findDOMNode from 'rax-find-dom-node'; +import { + adapter, + addonRendererFactory, + tempRendererFactory, + rendererFactory, +} from '@ali/lowcode-renderer-core'; +import pageRendererFactory from './renderer/page'; +import componentRendererFactory from './renderer/component'; +import blockRendererFactory from './renderer/block'; +import CompFactory from './hoc/compFactory'; + +adapter.setRuntime({ + Component, + PureComponent, + createContext, + createElement, + forwardRef, + findDOMNode, +}); + +adapter.setRenderers({ + PageRenderer: pageRendererFactory(), + ComponentRenderer: componentRendererFactory(), + BlockRenderer: blockRendererFactory(), + AddonRenderer: addonRendererFactory(), + TempRenderer: tempRendererFactory(), +}); +``` + +## 多模式渲染 +### 预览模式渲染 +预览模式的渲染,主要是通过 Schema、components 即可完成上述的页面渲染能力。 +```typescript +import ReactRenderer from '@ali/lowcode-react-renderer'; +import ReactDOM from 'react-dom'; +import { Button } from '@alifd/next'; + +const schema = { + componentName: 'Page', + props: {}, + children: [ + { + componentName: 'Button', + props: { + type: 'primary', + style: { + color: '#2077ff' + }, + }, + children: '确定', + }, + ], +}; + +const components = { + Button, +}; + +ReactDOM.render(( + +), document.getElementById('root')); +``` + +### 设计模式渲染(Simulator) +设计模式渲染就是将编排生成的《搭建协议》渲染成视图的过程,视图是可以交互的,所以必须要处理好内部数据流、生命周期、事件绑定、国际化等等。也称为画布的渲染,画布是 UI 编排的核心,它一般融合了页面的渲染以及组件/区块的拖拽、选择、快捷配置。 +画布的渲染和预览模式的渲染的区别在于,画布的渲染和设计器之间是有交互的。所以在这里我们新增了一层 `Simulator` 作为设计器和渲染的连接器。 +`Simulator` 是将设计器传入的 `DocumentModel` 和组件/库描述转成相应的 Schema 和 组件类。再调用 Render 层完成渲染。我们这里介绍一下它提供的能力。 +#### 整体架构 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644398136330-0f48202b-b581-4b1f-af79-72a667a194d9.png#clientId=udd4eb4ee-bdb1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=432&id=u734b5c16&margin=%5Bobject%20Object%5D&name=image.png&originHeight=864&originWidth=1500&originalType=binary&ratio=1&rotation=0&showTitle=false&size=572012&status=done&style=none&taskId=u7d7cf569-d5a9-4bea-9b2d-1121b85728f&title=&width=750) + +- `Project`:位于顶层的 Project,保留了对所有文档模型的引用,用于管理应用级 Schema 的导入与导出。 +- `Document`:文档模型包括 Simulator 与数据模型两部分。Simulator 通过一份 Simulator Host 协议与数据模型层通信,达到画布上的 UI 操作驱动数据模型变化。通过多文档的设计及多 Tab 交互方式,能够实现同时设计多个页面,以及在一个浏览器标签里进行搭建与配置应用属性。 +- `Simulator`:模拟器主要承载特定运行时环境的页面渲染及与模型层的通信。 +- `Node`:节点模型是对可视化组件/区块的抽象,保留了组件属性集合 Props 的引用,封装了一系列针对组件的 API,比如修改、编辑、保存、拖拽、复制等。 +- `Props`:描述了当前组件所维系的所有可以「设计」的属性,提供一系列操作、遍历和修改属性的方法。同时保持对单个属性 Prop 的引用。 +- `Prop`:属性模型 Prop 与当前可视化组件/区块的某一具体属性想映射,提供了一系列操作属性变更的 API。 +- `Settings`:`SettingField` 的集合。 +- `SettingField`:它连接属性设置器 `Setter` 与属性模型 `Prop`,它是实现多节点属性批处理的关键。 +- 通用交互模型:内置了拖拽、活跃追踪、悬停探测、剪贴板、滚动、快捷键绑定。 + +#### 模拟器介绍 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644398137096-260646a0-f264-48af-9600-6f7141a6a1d8.png#clientId=udd4eb4ee-bdb1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=370&id=ubfb08f11&margin=%5Bobject%20Object%5D&name=image.png&originHeight=740&originWidth=1500&originalType=binary&ratio=1&rotation=0&showTitle=false&size=353179&status=done&style=none&taskId=u3cd764bb-52f6-47a6-8026-fee6a36d08d&title=&width=750) + +- 运行时环境:从运行时环境来看,目前我们有 React 生态、Rax 生态。而在对外的历程中,我们也会拥有 Vue 生态、Angular 生态等。 +- 布局模式:不同于 C 端营销页的搭建,中后台场景大多是表单、表格,流式布局是主流的选择。对于设计师、产品来说,是需要绝对布局的方式来进行页面研发的。 +- 研发场景:从研发场景来看,低代码搭建不仅有页面编排,还有诸如逻辑编排、业务编排的场景。 + +基于以上思考,我们通过基于沙箱隔离的模拟器技术来实现了多运行时环境(如 React、Rax、小程序、Vue)、多模式(如流式布局、自由布局)、多场景(如页面编排、关系图编排)的 UI 编排。通过注册不同的运行时环境的渲染模块,能够实现编辑器从 React 页面搭建到 Rax 页面搭建的迁移。通过注册不同的模拟器画布,你可以基于 G6或者 mxgraph 来做关系图编排。你可以定制一个流式布局的画布,也可以定制一个自由布局的画布。 diff --git a/docs/docs/guide/design/setter.md b/docs/docs/guide/design/setter.md new file mode 100644 index 000000000..66de03bb1 --- /dev/null +++ b/docs/docs/guide/design/setter.md @@ -0,0 +1,86 @@ +--- +title: 设置器设计 +sidebar_position: 6 +--- +设置器,又称为 Setter,是作为物料属性和用户交互的重要途径,在编辑器日常使用中有着非常重要的作用,本文重点介绍 Setter 的设计原理和使用方式,帮助用户更好的理解 Setter。 +在编辑器的右边区域,Setter 的区块就展现在这里,如下图: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644404887956-8ffc8c8c-380c-4843-8bc9-512be77a9b18.png#clientId=u4cc5b992-0df5-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=865&id=wNOuZ&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1730&originWidth=3836&originalType=binary&ratio=1&rotation=0&showTitle=false&size=947162&status=done&style=none&taskId=ue623b488-8774-401a-b80c-c102e8aac8f&title=&width=1918) +其中包含 属性、样式、事件、高级: + +- 属性:展示该物料常规的属性; +- 样式:展示该物料样式的属性; +- 事件:如果该物料有声明事件,则会出现事件面板,用于绑定事件; +- 高级:两个逻辑相关的属性,**条件渲染**和**循环。** +# npm 包与仓库信息 + +- npm 包:@alilc/lowcode-engine-ext +- 仓库:[https://github.com/alibaba/lowcode-engine-ext](https://github.com/alibaba/lowcode-engine-ext) + +# 设置器模块原理 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644404909143-eede0a27-e990-4333-816a-d2d0cf2594b3.png#clientId=u4cc5b992-0df5-4&crop=0&crop=0&crop=1&crop=1&height=482&id=PyO6v&name=image.png&originHeight=964&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=273494&status=done&style=none&taskId=ufb317b56-de50-4693-8c39-8faa9b38307&title=&width=767) + +设置面板依赖于以下三块抽象 + +- 编辑器上下文 `editor`,主要包含:消息通知、插件引用等 +- 设置对象 `settingTarget`,主要包含:选中的节点、是否同一值、值的储存等 +- 设置列 `settingField`,主要和当前设置视图相关,包含视图的 `ref`、以及设置对象 `settingTarget` + +#### SettingTarget 抽象 +如果不是多选,可以直接暴露 `Node` 给到这,但涉及多选编辑的时候,大家的值时通常是不一样的,设置的时候需要批量设置进去,这里主要封装这些逻辑,把多选编辑的复杂性屏蔽掉。 + +所选节点所构成的**设置对象**抽象如下: + +```typescript +interface SettingTarget { + // 所设置的节点集,至少一个 + readonly nodes: Node[]; + // 所有属性值数据 + readonly props: object; + // 设置属性值 + setPropValue(propName: string, value: any): void; + // 获取属性值 + getPropValue(propName: string): any; + // 设置多个属性值,替换原有值 + setProps(data: object): void; + // 设置多个属性值,和原有值合并 + mergeProps(data: object): void; + // 绑定属性值发生变化时 + onPropsChange(fn: () => void): () => void; +} +``` + +基于设置对象所派生的**设置目标属性**抽象如下: + +```typescript +interface SettingTargetProp extends SettingTarget { + // 当前属性名称 + readonly propName: string; + // 当前属性值 + value: any; + // 是否设置对象的值一致 + isSameValue(): boolean; + // 是否是空值 + isEmpty(): boolean; + // 设置属性值 + setValue(value: any): void; + // 移除当前设置 + remove(): void; +} +``` + +#### SettingField 抽象 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644396933393-779423e2-29a7-4e97-9ef9-e3c7964a5412.png#clientId=u41c9ba96-15b6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=201&id=uc71z&margin=%5Bobject%20Object%5D&name=image.png&originHeight=402&originWidth=2022&originalType=binary&ratio=1&rotation=0&showTitle=false&size=218611&status=done&style=none&taskId=u534089f2-363d-464e-8c69-ac79f268f5a&title=&width=1011) + +```typescript +interface SettingField extends SettingTarget { + // 当前 Field 设置的目标属性,为 group 时此值为空 + readonly prop?: SettingTargetProp; + + // 当前设置项的 ref 引用 + readonly ref?: ReactInstance; + + // 属性配置描述传入的配置 + readonly config: SettingConfig; + // others.... +} +``` diff --git a/docs/docs/guide/design/specs.md b/docs/docs/guide/design/specs.md new file mode 100644 index 000000000..be449e3dc --- /dev/null +++ b/docs/docs/guide/design/specs.md @@ -0,0 +1,60 @@ +--- +title: 低代码引擎协议栈简介 +sidebar_position: 1 +--- +# 什么是低代码协议 +这是两份中后台低代码领域的标准协议,即《低代码引擎物料协议规范》和《低代码引擎搭建协议规范》。它们保障了低代码领域的标准化,成为了生态建设和流通的基石。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1648642508161-64dd3a2c-d2d2-43ed-92e4-ead40ab62498.png#clientId=ub4563dfd-c5b1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=532&id=u60966832&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1064&originWidth=1928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=974317&status=done&style=none&taskId=u88a8512e-a57c-43f6-a01f-19f6878b7a0&title=&width=964) +# 为什么需要协议 +首先,我们做一个不恰当的类比,我们将低代码引擎和 JavaScript 语言做一下类别。还记得之前,大家都被浏览器兼容性支配的恐惧,特别是 IE 和其他浏览器,对上层 API 实现的不一致,导致一份代码需要运行在两端需要做适配。当浏览器 / JavaScript 相关的标准出现之后,各个浏览器进行了 API 的统一,使得我们终于可以从这部分工作中解放出来(PS:Babel 对于语言特性的转换是另一个方面的问题)。 +而在《低代码引擎搭建协议规范》出现之前,低代码领域也有类似的问题。 +## 概念不通 +在交流的过程中,一些对于搭建产品的术语的不一致,导致了一些沟通成本,不管是在文章分享、技术分享、交流会上,都会有这个问题。 +## 物料孤岛 +由于低代码产品实现的方式不同,物料的消费方式也各不相同。这里分为两种物料,低代码物料和 ProCode 物料。 +对于低代码物料来说,A 平台创建的物料无法使用到 B 平台上,如果想在 B 平台实现同样的物料,需要按照 B 平台的标准搭建一份物料。 +对于 ProCode 物料来说,需要在低代码平台进行消费,是需要进行转换的,包括搭建配置项的生成、物料搭建试图等,可能还需要特殊的描述文件进行描述。由于这一层没有统一,同一份 ProCode 物料每接入一个低代码,可能需要的描述文件格式不同,转换的代码不同,使用的工具也不同。 +## 生态隔离 +不同低代码平台的生态体系也不相同,有的低代码平台的物料生态不错,有的低代码平台的搭建体验生态不错。但是这些利好的生态,都是无法互通的,甚至就算知道了代码,也无法复用,因为底层是不一致的。对于集团来说,每一个平台都创建一份自己的生态,这并不是利好的。 +## 技术深度不够 +大家可能觉得,以上问题对于自己造轮子来说,其实也是有利的,因为自己得到了技术上的成长。 +但是对于低代码的平台方,实际上更多的工作,在物料的转化、物料的生成、搭建体验的小优化、部分其他平台生态的实现。这些的技术深度其实并不高。 +## 价值不高 +如果每个业务都要从 0 开始做,做自己的平台,会花费大量的时间来构建底层基础设施,对业务本身而言并不是一件好事;而且前端领域的底层基础设施都大同小异,不同团队重复构建造成了极大的资源浪费。 +这样的建设,会导致从 0 到 1 都需要花费大量的时间,往往在内部人力不足、投入有限时,产品很容易在未发展壮大的时候就面临了死亡相关的决策。 +设想一下,如果可以开发一份全集团低代码平台都可以使用的物料,是否更有成就感呢?如果可以基于已有生态进行低代码平台的快速落地,而不是花费 1-2 年搭建一个可用的低代码平台,再验证市场。在快速的验证之后,再进行更深入的打磨,这其中的思考和技术含量是否更优于之前的模式呢? +比如不同平台的低代码物料 + +1. vc-deep — vc 协议 + Deep 组件库(企业智能基于 Fusion Next 定制); +2. Iceluna 协议 + Fusion Next; +3. AIMake 物料; +4. vc-fusion-basic + 业务改造 — vc 协议 + Fusion Next(各业务 Fork 定制); +5. vision 魔改 + vc 协议扩展 + fusion 业务组件; +6. vc 协议 + antd; + +可以看到,各个搭建平台都需要维护一套自己的基础组件库,这是非常不合理的,对基础组件库的维护会分散开发同学完成业务目标的精力。 +建立统一的低代码领域标准化,是百利而无一害的。于是,在阿里巴巴集团2020财年进行了讨论,建立了搭建治理&物料流通战役,此战役便产出了《低代码引擎物料协议规范》、《低代码引擎搭建协议规范》两份规范,成为了低代码引擎和其生态的基础。 +# 协议的作用 +基于统一的协议,我们完成业务组件、区块、模板等各类物料的标准统一,基于统一的协议,我们完成业务组件、区块、模板等各类物料的标准统一,集团各类中后台研发系统生产的物料可借助物料中心进行跨系统流通,通过丰富物料生态的共享提升阿里巴巴中后台研发系统的效率。同时完成低代码引擎的标准统一以及低代码搭建中台能力的输出,帮助业务方快速孵化本业务域中后台研发系统。 + +## 打破物料孤岛 +### 物料中心 +在《低代码引擎物料协议规范》成立了之后,规范先行建立了阿里巴巴各个中后台研发平台沟通、对话的基础,物料流通的先决条件已经成熟,这个时候我们还需要一个统一的物料源,用于管理物料的上传、存储、检索、分发,一个典型的中心化架构,类似 npm 的管理,这便是我们物料中心。 +Fusion Market 是物料中心的前身,它提供了业务组件的存储、文档展示和全局透出的功能,由于 fusion 体系在集团内的广泛使用,Fusion Market 沉淀了不少的业务组件,但是这个项目却一直不温不火,只看到业务组件数量的增加,却未看到物料流通起来。其中一个原因是,没有阿里巴巴前端委员会的背书,规范很难统一,规范如果不统一,物料就很难流通; +在规范成立之后,物料中心也将有了建设的基础,最终于 2019 年建立了物料中心,提供了物料流通的平台能力。 +### 低代码基础物料 +就像 AntD、Element 之于源码研发模式,在低代码研发模式下各个搭建平台也需要一套统一的、开箱即用的低代码基础组件库。基于低代码描述协议完成了两份低代码基础物料的建设,即“Fusion 低代码基础组件库”和“AntD 低代码基础组件库”。 +### 源码组件低代码化 +将源码组件一键转化为低代码物料,符合低代码物料规范,可以在低代码平台进行流通。 +### 低代码物料中心 +当低代码物料积累到一定的量级之后,所有的搭建平台的业务物料越来越多。这些物料通过低代码物料中心进行统一的管理和消费。 +## Setter 生态的基础 + +Snippet(组件默认搭建 schema )由《低代码引擎搭建协议规范》定义,低代码引擎会按照规范对组件进行渲染,Configure 由《低代码引擎物料协议规范》定义,它描述了组件的 props 以及每个 prop 对应的 setter (Prop 配置面板),低代码引擎提供了 14 个内置 Setter,但如果我们组件的 props 超出了引擎内置 setter 的范围,就需要我们自己来开发对应 Setter。 +setter 最终也慢慢形成了自己的生态,这使得开发物料更加容易,可以使用已有的 setter 生态,进行物料配置描述。 +## 低代码引擎实现标准 +低代码引擎是以上生态的消费端,它是实现了标准协议的低代码引擎。这是不可或缺的部分,低代码引擎这里就相当于一个标准浏览器,一方面给其他的低代码平台提供了一个 Demo,其他平台可以参考低代码引擎进行实现,满足官方协议,便也可以消费相关的物料生态和其他生态。 +通过物料中心实现集团各业务域物料跨系统流通是第一步,通过低代码引擎快速搭建出来的各业务域低代码平台平滑高效地使用各业务域物料,提升集团中后台研发系统的效率是关键的第二步。 + + +其中入料模块,使得任意符合物料协议的物料都可以被消费,也就打破了物料孤岛,实现物料之间的流通。 diff --git a/docs/docs/guide/design/summary.md b/docs/docs/guide/design/summary.md new file mode 100644 index 000000000..604886e8b --- /dev/null +++ b/docs/docs/guide/design/summary.md @@ -0,0 +1,57 @@ +--- +title: 低代码引擎架构综述 +sidebar_position: 0 +--- +## 分层架构描述 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644392855407-44040c3e-f98e-4e93-a7ba-7efc0a7927fb.png#clientId=ue3f00c22-d0cc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=540&id=u0d1fdc91&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1080&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=177518&status=done&style=none&taskId=u0ac7d0a3-e838-4982-ad41-e830af33545&title=&width=960) +我们设计了这样一套分层架构,自下而上分别是协议 - 引擎 - 生态 - 平台。 +底层协议栈定义的是标准,**标准的统一让上层产物的互通成为可能**, +**引擎是对协议的实现,同时通过能力的输出,向上支撑生态开放体系,提供各种生态扩展能力,** +那么生态就好理解了,是基于引擎核心能力上扩展出来的,比如物料、设置器、插件等,还有工具链支撑开发体系, +最后,各个平台基于引擎内核以及生态中的产品组合、衔接形成满足其需求的低代码平台。 +**每一层都明确自身的定位,各司其职,协议不会去思考引擎如何实现,引擎也不会实现具体上层平台功能,上层平台的定制化均通过插件来实现,这些理念将会贯穿我们体系设计、实现的过程。** + +## 引擎内核简述 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644393521380-2b5dda70-cd35-4cc2-aeae-6d0ba98deccd.png#clientId=ue3f00c22-d0cc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=540&id=u6b0dd5f3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1080&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=330340&status=done&style=none&taskId=u39127ebd-dbac-4636-9cb5-5c4833146a1&title=&width=960) + +低代码引擎分为 4 大模块,入料 - 编排 - 渲染 - 出码。 +入料模块就是将外部的物料,比如海量的 npm 组件,按照《物料描述协议》进行描述。 +**注意,这里仅是增加描述,而非重写一套,这样我们能最大程度复用ProCode体系已沉淀的组件。** +将描述后的数据通过引擎 API 注册后,在编辑器中使用。 +编排,本质上来讲,就是**不断在生成符合《搭建协议》的页面描述,将编辑器中的所有物料,进行布局设置、组件 CRUD 操作、以及 JS/CSS编写/逻辑编排**等,最终转换成页面描述,技术细节待会儿我们再展开讲讲。 +渲染,顾名思义,就是**将编排生成的页面描述结构渲染成视图的过程**,视图是面向用户的,所以必须处理好内部数据流、生命周期、事件绑定、国际化等。 +出码,就是**将页面描述结构解析和转换成应用代码的机制**。 + +## 引擎生态简述 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644393489755-b9a6a471-c099-480b-b40b-3094b793394d.png#clientId=ue3f00c22-d0cc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=540&id=u81ccc9e2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1080&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=504671&status=done&style=none&taskId=u52008ac0-e9c6-407b-a59e-7dbf4c02c0c&title=&width=960) + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644397483218-2b58bfca-94b1-474e-8983-afc757f20e59.png#clientId=uafdaa655-f89e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=540&id=u3aeacdac&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1080&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=394834&status=done&style=none&taskId=udcd28484-1df2-484c-9f98-87175972d65&title=&width=960) + +引擎生态主要分为 3 部分,物料、设置器和插件。 +### 物料生态 +物料是低代码平台的生产资料,没有物料低代码平台则变成了无源之水无本之木。低代码平台的物料即低代码组件。因此低代码物料生态指的是: +1)低代码物料生产能力和规范。 +2)对低代码物料进行统一管理的物料中心。 +3)基于 Fusion Next 的低代码基础组件库。 + +### 设置器生态 +对于已接入物料的属性配置,需要不同的设置器。 +比如配置数值类型的 age,需要一个数值设置器,配置对象类型的 hobby,需要一个对象设置器,依次类推。 +每个设置器本质上都是一个 React 组件,接受由引擎传入的参数,比如 value 和 onChange,value 是初始传入的值,onChange 是在设置器的值变化时的回传函数,将值写回到引擎中。 +```json +// 一个最简单的文本设置器示例 +class TextSetter extends Component { + render() { + const { value, onChange } = this.props; + return onChange(e.target.value)} />; + } +} +``` +大多数组件所使用的设置器都是一致或相似的。如同建设低代码基础组件库一样,设置器生态是一组基础的设置器,供大多数组件配置场景使用。 +同时提供了设置器的定制功能。 + +### 插件生态 +低代码引擎本身只包含了最小的内核,而我们所能看到的设计器上的按钮、面板等都是插件提供的。插件是组成设计器的必要部分。 +因此我们提供了一套官方的插件生态,提供最基础的设计器功能。帮助用户通过使用插件,快速完成自己的设计器。 diff --git a/docs/docs/guide/expand/_category_.json b/docs/docs/guide/expand/_category_.json new file mode 100644 index 000000000..36116999b --- /dev/null +++ b/docs/docs/guide/expand/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "扩展低代码应用", + "position": 2, + "collapsed": false, + "collapsible": true +} diff --git a/docs/docs/guide/expand/editor/_category_.json b/docs/docs/guide/expand/editor/_category_.json new file mode 100644 index 000000000..b0c35885c --- /dev/null +++ b/docs/docs/guide/expand/editor/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "扩展低代码编辑器", + "position": 1 +} diff --git a/docs/docs/guide/expand/editor/cli.md b/docs/docs/guide/expand/editor/cli.md new file mode 100644 index 000000000..7f46c7b2e --- /dev/null +++ b/docs/docs/guide/expand/editor/cli.md @@ -0,0 +1,181 @@ +--- +title: 低代码生态脚手架 & 调试机制 +sidebar_position: 7 +--- +## 脚手架简述 + +在 fork 低代码编辑器 demo 项目后,您可以直接在项目中任意扩展低代码编辑器。如果您想要将自己的组件/插件/设置器封装成一个独立的 npm 包并提供给社区,您可以使用我们的低代码脚手架建立低代码扩展。 + +> Windows 开发者请在 WSL 环境下使用开发工具 +> WSL 中文 doc:[https://docs.microsoft.com/zh-cn/windows/wsl/install](https://docs.microsoft.com/zh-cn/windows/wsl/install) +中文教程:[https://blog.csdn.net/weixin_45027467/article/details/106862520](https://blog.csdn.net/weixin_45027467/article/details/106862520) + + +## 脚手架功能 +### 脚手架初始化 +```shell +$ npm init @alilc/element your-element-name +``` +不写 your-element-name 的情况下,则在当前目录创建。 + +> 觉得安装速度比较慢的同学,可以设置 npm 国内镜像,如 + +```bash +$ npm init @alilc/element your-element-name --registry=https://registry.npmmirror.com +``` + +选择对应的元素类型,并填写对应的问题,即可完成创建。 +![截屏2022-02-09 下午8.15.07.png](https://cdn.nlark.com/yuque/0/2022/png/134449/1644408912640-ae7a9a9b-54a4-49c3-a5d8-ccac1db7da0b.png#averageHue=%23f0f0ef&clientId=ue2be1de5-5d30-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=drop&height=82&id=uaff32f98&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2022-02-09%20%E4%B8%8B%E5%8D%888.15.07.png&originHeight=148&originWidth=688&originalType=binary&ratio=1&rotation=0&showTitle=false&size=72918&status=error&style=none&taskId=uf08c7e98-b502-416d-be39-0029f765203&title=&width=382) +### 脚手架本地环境调试 +```shell +cd your-element-name +npm install +npm start +``` + +### 脚手架构建 +```shell +$ npm run build +``` +### 脚手架发布 +修改版本号后,执行如下指令即可: +```shell +$ npm publish +``` + +# 🔥🔥🔥 调试物料/插件/设置器 + +> 📢📢 📢 低代码生态脚手架提供的调试利器,在启动 setter/插件/物料 项目后,直接在已有的低代码平台就可以调试,不需要 npm link / 手改 npm main 入口等传统方式,轻松上手,强烈推荐使用!! + + +注:若控制台出现如下错误,直接访问一次该 url 即可~ +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1652408638502-0509191d-1cd6-435c-9196-5c7abac7cc4d.png#averageHue=%23c8e1be&clientId=u0b1196f0-7f06-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=113&id=tjF5F&margin=%5Bobject%20Object%5D&name=image.png&originHeight=226&originWidth=1418&originalType=binary&ratio=1&rotation=0&showTitle=false&size=180782&status=error&style=none&taskId=u57eb2bdc-6dfd-4332-b176-c453947be2d&title=&width=709) + +## 组件/插件/Setter 侧 + +1. 插件/setter 在原有 alt 的配置中添加相关的调试配置 +```json +// build.json 中 +{ + "plugins": [ + [ + "@alilc/build-plugin-alt", + { + "type": "plugin", + "inject": true, // 开启注入调试 + // 配置要打开的页面,在注入调试模式下,不配置此项的话不会打开浏览器 + // 支持直接使用官方 demo 项目:https://lowcode-engine.cn/demo/index.html + "openUrl": "https://lowcode-engine.cn/demo/index.html?debug" + } + ], + ] +} +``` + +2. 组件需先安装 @alilc/build-plugin-alt,再将组件内的 `build.lowcode.js`文件修改如下 +```javascript +const { library } = require('./build.json'); + +module.exports = { + alias: { + '@': './src', + }, + plugins: [ + [ + // lowcode 的配置保持不变,这里仅为示意。 + '@alifd/build-plugin-lowcode', + { + library, + engineScope: "@alilc" + }, + ], + [ + '@alilc/build-plugin-alt', + { + type: 'component', + inject: true, + library, + // 配置要打开的页面,在注入调试模式下,不配置此项的话不会打开浏览器 + // 支持直接使用官方 demo 项目:https://lowcode-engine.cn/demo/index.html + openUrl: "https://lowcode-engine.cn/demo/index.html?debug" + } + ]], +}; +``` + +3. 本地组件/插件/Setter正常启动调试,在项目的访问地址增加 debug,即可开启注入调试。 +```typescript +https://lowcode-engine.cn/demo/index.html?debug +``` +## 项目侧的准备 +> 如果你的低代码项目 fork 自官方 demo,那么项目侧的准备已经就绪,不用再看以下内容~ + +1. 安装 @alilc/lowcode-plugin-inject +```shell +npm i @alilc/lowcode-plugin-inject --save-dev +``` + +2. 在引擎初始化侧引入插件 +```json +import Inject, { injectAssets } from '@alilc/lowcode-plugin-inject'; + +export default async () => { + // 注意 Inject 插件必须在其他插件前注册,且所有插件的注册必须 await + await plugins.register(Inject); + await plugins.register(OtherPlugin); + await plugins.register((ctx: ILowCodePluginContext) => { + return { + name: "editor-init", + async init() { + // 设置物料描述前,使用插件提供的 injectAssets 进行处理 + const { material, project } = ctx; + material.setAssets(await injectAssets(assets)); + }, + }; + }); +} +``` + +3. 在 saveSchema 时过滤掉插入的url,避免影响渲染态 +```javascript +import { filterPackages } from '@alilc/lowcode-plugin-inject'; +export const saveSchema = async () => { + // ... + const packages = await filterPackages(editor.get('assets').packages); + window.localStorage.setItem( + 'packages', + JSON.stringify(packages), + ); + // ... +}; + +``` + +4. 如果希望预览态也可以注入调试组件,则需要在 preview 逻辑里插入组件 +```javascript +import { injectComponents } from '@alilc/lowcode-plugin-inject'; + +async function init() { + // 在传递给 ReactRenderer 前,先通过 injectComponents 进行处理 + const components = await injectComponents(buildComponents(libraryMap, componentsMap)); + // ... +} +``` + +# Meta 信息 +meta 信息是放在生态元素 package.json 中的一小段 json,用户可以通过 meta 了解到这个元素的一些基本信息,如元素类型,一些入口信息等。 + +```typescript +interface LcMeta { + type: 'plugin' | 'setter' | 'component'; // 元素类型,尚未实现 + pluginName: string; // 插件名,仅插件包含 + meta: { + dependencies: string[]; // 插件依赖的其他插件列表,仅插件包含 + engines: { + lowcodeEngine: string; // 适配的引擎版本 + } + prototype: string; // 物料描述入口,仅组件包含,尚未实现 + prototypeView: string; // 物料设计态入口,仅组件包含,尚未实现 + } +} +``` diff --git a/docs/docs/guide/expand/editor/material.md b/docs/docs/guide/expand/editor/material.md new file mode 100644 index 000000000..88ea8b908 --- /dev/null +++ b/docs/docs/guide/expand/editor/material.md @@ -0,0 +1,271 @@ +--- +title: 物料扩展 +sidebar_position: 1 +--- +## 物料简述 +物料是页面搭建的原料,按照粒度可分为组件、区块和模板: + +1. 组件:组件是页面搭建最小的可复用单元,其只对外暴露配置项,用户无需感知其内部实现; +2. 区块:区块是一小段符合低代码协议的 schema,其内部会包含一个或多个组件,用户向设计器中拖入一个区块后可以随意修改其内部内容; +3. 模板:模板和区块类似,也是一段符合低代码协议的 schema,不过其根节点的 componentName 需固定为 Page,它常常用于初始化一个页面; + +低代码编辑器中的物料需要进行一定的配置和处理,才能让用户在低代码平台使用起来。这个过程中,需要一份一份配置文件,也就是资产包。资产包文件中,针对每个物料定义了它们在低代码编辑器中的使用描述。 +## 资产包配置 +### 什么是低代码资产包 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1647671718994-e013a162-37be-4fa7-bd3b-3af06878c3c2.png#clientId=uf20508c2-6786-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=579&id=u7a0c3dae&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1646&originWidth=3068&originalType=binary&ratio=1&rotation=0&showTitle=false&size=635660&status=done&style=stroke&taskId=uc304cc2b-24bf-449d-8e2e-fd25c88b189&title=&width=1080) +在低代码 Demo 中,我们可以看到,组件面板不只提供一个组件,组件是以集合的形式提供给低代码平台的,而低代码资产包正是这些组件构成集合的形式。 +**_它背后的 Interface,_**[**_在引擎中的定义摘抄如下_**](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/assets.ts)**_:_** + +```typescript +export interface Assets { + version: string; // 资产包协议版本号 + packages?: Array; // 大包列表,external与package的概念相似,融合在一起 + components: Array | Array; // 所有组件的描述协议列表 + sort: ComponentSort; // 新增字段,用于描述组件面板中的 tab 和 category +} + +export interface ComponentSort { + groupList?: String[]; // 用于描述组件面板的 tab 项及其排序,例如:["精选组件", "原子组件"] + categoryList?: String[]; // 组件面板中同一个 tab 下的不同区间用 category 区分,category 的排序依照 categoryList 顺序排列; +} + +export interface RemoteComponentDescription { + exportName: string; // 组件描述导出名字,可以通过 window[exportName] 获取到组件描述的 Object 内容; + url: string; // 组件描述的资源链接; + package: { // 组件(库)的 npm 信息; + npm: string; + } +} +``` +资产包协议 TS 描述 +### Demo 中的资产包 +在 Demo 项目中,自带了一份默认的资产包: +> [https://github.com/alibaba/lowcode-demo/blob/main/src/universal/assets.json](https://github.com/alibaba/lowcode-demo/blob/main/src/universal/assets.json) + +这份资产包里的物料是我们内部沉淀出的,用户可以通过这套资产包体验引擎提供的搭建、配置能力。 +**_在项目中正常注册资产包:_** +```json +import { material } from '@alilc/lowcode-engine' +// 以任何方式引入 assets +material.setAssets(assets) +``` +**_以支持调试的方式注册资产包:_** +> 这样启动并部署出来的项目,可以通过在预览地址加上 ?debug 来调试本地物料。 +> 例如: +> - 通过插件初始化一个物料 +> - 按照参考文章配置物料支持调试 +> - 启动物料 +> - 访问:[https://lowcode-engine.cn/demo?debug](https://lowcode-engine.cn/demo?debug) +> +详细参考:[https://www.yuque.com/lce/doc/ulvlkz](https://www.yuque.com/lce/doc/ulvlkz) + +```javascript +import { material } from '@alilc/lowcode-engine' +import Inject, { injectAssets } from '@alilc/lowcode-plugin-inject'; +await material.setAssets(await injectAssets(assets)); +``` + +### 手工配置资产包 +参考 Demo 中的[基础 Fusion Assets 定义](https://github.com/alibaba/lowcode-demo/blob/main/src/scenarios/basic-fusion/assets.json),如果我们修改 assets.json,我们就能做到配置资产包: + +- packages 对象:我们需要在其中定义这个包的获取方式,如果不定义,就不会被低代码引擎动态加载并对应上组件实例。定义方式是 UMD 的包,低代码引擎会尝试在 window 上寻找对应 library 的实例; +- components 对象:我们需要在其中定义物料描述,物料描述我们将在下一节继续讲解。 +## 物料描述配置 +### 什么是物料描述 +在低代码平台中,用户是不同的,有可能是开发、测试、运营、设计,也有可能是销售、行政、HR 等等各种角色。他们大多数不具备专业的前端开发知识,对于低代码平台来说,我们使用组件的流程如下: + +1. 用户通过拖拽/选择组件,在画布中看到组件; +2. 选中组件,出现组件的配置项; +3. 修改组件配置项; +4. 画布更新生效。 + +**_当我们选中一个组件,我们可以看到面板右侧会显示组件的配置项。_** +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644312295732-5e0df2b5-065a-4d80-a66c-b5b20c8c32af.png#clientId=ue1d4eb8d-3b5c-4&crop=0&crop=0&crop=1&crop=1&from=url&height=535&id=ML9vP&margin=%5Bobject%20Object%5D&name=image.png&originHeight=743&originWidth=1500&originalType=binary&ratio=1&rotation=0&showTitle=false&size=212807&status=done&style=stroke&taskId=u2458e0f7-6bea-40d8-bb9b-9811562c6fe&title=&width=1080) +**_它包含以下内容:_** + +1. 基础信息:描述组件的基础信息,通常包含包信息、组件名称、标题、描述等。 +2. 组件属性信息:描述组件属性信息,通常包含参数、说明、类型、默认值 4 项内容。 +3. 能力配置/体验增强:推荐用于优化搭建产品编辑体验,定制编辑能力的配置信息。 + +因此,我们设计了[**《中后台低代码组件描述协议》**](http://lowcode-engine.cn/material)来描述一个低代码编辑器中可被配置的内容。 +### Demo 中的物料描述 +我们可以从 Demo 中的 assets.json 找到如下三个物料描述: + +- @alifd/pro-layout:布局组件,放在`window.AlifdProLayoutMeta`,[meta 文件地址](https://alifd.alicdn.com/npm/@alifd/pro-layout@1.0.1-beta.5/build/lowcode/meta.js); +- @alifd/fusion-ui:精选组件,放在`window.AlifdFusionUiMeta`,[meta 文件地址](https://alifd.alicdn.com/npm/@alifd/fusion-ui@1.0.5-beta.1/build/lowcode/meta.js); +- @alilc/lowcode-materials:原子组件,放在 `window.AlilcLowcodeMaterialsMeta`,[meta 文件地址](https://alifd.alicdn.com/npm/@alilc/lowcode-materials@1.0.1/build/lowcode/meta.js); + +**_引擎中,会尝试调用对应 meta 文件,并注入到全局:_** +```tsx +const src = 'https://alifd.alicdn.com/npm/@alifd/pro-layout@1.0.1-beta.5/build/lowcode/meta.js' +const script = document.createElement('script') +script.src = src +document.head.appendChild(script) +``` +然后在 window 上就能拿到对应的物料描述内容了: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1647672326187-ec19ed1e-645a-4086-8384-ccca19b9f36c.png#clientId=uf20508c2-6786-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=648&id=ue7a84d56&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1138&originWidth=1896&originalType=binary&ratio=1&rotation=0&showTitle=false&size=582492&status=done&style=stroke&taskId=u11707d78-e1c5-4368-9de0-e98b7597815&title=&width=1080) +手工配置物料描述时,可以用这样的方式参考一下 Demo 中的物料描述是如何实现的。 +### 手工配置物料描述 +详见:“物料描述详解”章节。 +## 物料的低代码开发 +> _**注意:引擎提供的 cli 并未对 windows 系统做适配,windows 环境必须使用 **_[_**WSL**_](https://docs.microsoft.com/zh-cn/windows/wsl/install)_**,其他终端不保证能正常运行**_ + +您可以通过本节内容,完成一个组件在低代码编辑器中的配置和调试。 +### 前言(必读) +引擎提供的物料开发脚手架内置了**_入料模块_**,初始化的时候会自动根据源码解析出一份_**低代码描述**_,但是从源码解析出来的低代码描述让用户直接使用是不够精细的,因为源码包含的信息不够,它没办法完全包含配置项的交互; +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1650539267595-c15e6200-9747-46bf-a61d-2a635d295406.png?x-oss-process=image/format,png#clientId=u97daa023-2ae2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=856&id=u5b22ab2d&name=image.png&originHeight=1830&originWidth=802&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4406602&status=done&style=stroke&taskId=ued90eb0c-b714-401a-bbfe-9f0b04794f6&title=&width=375) +比如设计师出了上面的设计稿,这里面除了有哪些 props 可被配置,通过哪个设置器配置,还包含了 props 之间的聚合、排序,甚至有自定义 setter ,这些信息源码里是不具备的,需要在低代码描述里进行开发; +**_因此我们建议只把 cli 初始化的低代码描述作为启动,要根据用户习惯对配置项进行设计,然后人工地去开发调试直接的低代码描述。_** +### 新开发组件 +#### 组件项目初始化 +```json +npm init @alilc/element your-material-name +``` +#### 选择组件类型 +> 组件 -> <组件组织方式> + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647569723981-d0cb5f94-c137-4abf-8165-947b49595c8c.png#clientId=u6b9f3678-e2a3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=174&id=ud7ad63de&margin=%5Bobject%20Object%5D&name=image.png&originHeight=464&originWidth=1596&originalType=binary&ratio=1&rotation=0&showTitle=false&size=298505&status=done&style=stroke&taskId=ud5e35ff9-c823-41bf-b441-db9e06a1f29&title=&width=600) +这里我们选择 react-组件库,之后便生出我们的组件库项目,目录结构如下: +``` +my-materials +├── README.md +├── components (业务组件目录) +│ ├── ExampleComponent // 业务组件1 +│ │ ├── build // 【编译生成】【必选】 +│ │ │ └── index.html // 【编译生成】【必选】可直接预览文件 +│ │ ├── lib // 【编译生成】【必选】 +│ │ │ ├── index.js // 【编译生成】【必选】js 入口文件 +│ │ │ ├── index.scss // 【编译生成】【必选】css 入口文件 +│ │ │ └── style.js // 【编译生成】【必选】js 版本 css 入口文件,方便去重 +│ │ ├── demo // 【必选】组件文档,用于生成组件开发预览,以及生成组件文档 +│ │ │ └── basic.md +│ │ ├── src // 【必选】组件源码 +│ │ │ ├── index.js // 【必选】,组件出口文件 +│ │ │ └── main.scss // 【必选】,仅包含组件自身样式的源码文件 +│ │ ├── README.md // 【必选】,组件说明及API +│ │ └── package.json // 【必选】 +└── └── ExampleComponent2 // 业务组件2 +``` +#### 组件开发与调试 +``` +# 安装依赖 +npm install + +# 启动 lowcode 环境进行调试预览 +npm run lowcode:dev + +# 构建低代码产物 +npm run lowcode:build +``` +执行上述命令后会在组件(库)根目录生成一个 `lowcode` 文件夹,里面会包含每个组件的低代码描述: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644314663918-b2464be7-a65b-447c-af2a-12ea326a7558.png#clientId=ue1d4eb8d-3b5c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=376&id=AYq7T&margin=%5Bobject%20Object%5D&name=image.png&originHeight=906&originWidth=1446&originalType=binary&ratio=1&rotation=0&showTitle=false&size=347634&status=done&style=stroke&taskId=u25ffbb86-6681-427f-b199-69a22560a9c&title=&width=600) + +在 src/components 目录新增一个组件并在 src/index.tsx 中导出,然后再执行 npm run lowcode:dev 时,低代码插件会在 lowcode/ 目录自动生成新增组件的低代码描述(meta.ts)。 + +用户可以直接修改低代码描述来修改组件的配置: + +- 设置组件的 setter;(上一个章节介绍的设置器,也可以定制设置器用到物料中) +- 新增组件配置项 +- 更改当前配置项; +#### 配置示例 +隐藏一个 prop +```typescript +{ + name: 'dataSource', + condition: () => false, +} +``` +展示样式 +```typescript +{ + name: 'dataSource', + display: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry' // 常用的是 inline(默认), block、entry +} +``` +发布组件 +``` +# 在组件根目录下,执行 +$ npm publish +``` +### 现存组件低代码化 +组件低代码化是指,在引入低代码平台之前,我们大多数都是使用源码开发的组件,也就是 ProCode 组件。 +在引入低代码平台之后,原来的源码组件是需要转化为低代码物料,这样才能在低代码平台进行消费。 +所以接下来会说明,对于已有的源码组件,我们如何把它低代码化。 +#### 配置低代码开发环境 +在您的组件开发环境中,安装 [build-scripts](https://github.com/ice-lab/build-scripts) 和它的低代码开发插件: +```shell +npm install -D @alifd/build-plugin-lowcode @alib/build-scripts --save-dev +``` +新增 build-scripts 配置文件:build.lowcode.js +```javascript +module.exports = { + alias: { + '@': './src', + }, + plugins: [ + [ + "@alifd/build-plugin-lowcode", + { + engineScope: '@alilc', + } + ] + ], +}; + +``` +在 package.json 中定义低代码开发相关命令 +```javascript +"lowcode:dev": "build-scripts start --config ./build.lowcode.js", +"lowcode:build": "build-scripts build --config ./build.lowcode.js", +``` +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644314665584-018b6675-ca7c-4bf5-b755-15a9b629f78f.png#clientId=ue1d4eb8d-3b5c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=270&id=brUY9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=822&originWidth=1830&originalType=binary&ratio=1&rotation=0&showTitle=false&size=972113&status=done&style=stroke&taskId=u09d05c9e-cf05-417e-bf32-8268d004134&title=&width=600) +#### 开发调试 +```bash +# 启动低代码开发调试环境 +npm run lowcode:dev +``` +组件开发形式还和原来的保持一致,但是新增了一份组件的配置文件,其中配置方式和低代码物料的配置是一样的。 +#### 构建 +```bash +# 构建低代码产物 +npm run lowcode:build +``` +#### 发布组件 +```bash +# 在组件根目录下,执行 +npm publish +``` +## 在项目中引入组件(库) +> 以下内容可观看[《阿里巴巴低代码引擎项目实战(3)-自定义组件接入》](https://www.bilibili.com/video/BV1dZ4y1m76S/)直播回放 + +对于平台或者用户来说,可能所需要的组件集合是不同的。如果需要自定义组件集合,就需要定制资产包,定制的资产包是配置了一系列组件的,将这份资产包用于引擎即可在引擎中使用自定义的组件集合。 +### 管理一份资产包 +项目中使用的组件相关资源都需要在资产包中定义,那么我们自己开发的组件库如果要在项目中使用,只需要把组件构建好的相关资源 merge 到 assets.json 中就可以; +#### 自定义组件加入到资产包 +通过官方脚手架自定义组件构建发布之后,npm 包里会出现一个 `build/lowcode/assets-prod.json`文件,我们只需要把该文件的内容 merge 到项目的 assets.json 中就可以; +#### 资产包托管 + +- 最简单的方式就是类似[引擎 demo 项目](https://github.com/alibaba/lowcode-demo/blob/main/src/universal/assets.json)的做法,在项目中维护一份 assets.json,新增组件或者组件版本更新都需要修改这份资产包; +- 灵活一点的做法是通过 oss 等服务维护一份远程可配置的 assets.json ,新增组件或者组件更新只需要修改这份远程的资产包,项目无需更新; +- 再高级一点的做法是实现一个资产包管理的服务,能够通过用户界面去更新资产包的内容; +### 在项目中引入资产包 +```javascript +import { ILowCodePluginContext, material, plugins } from '@alilc/lowcode-engine' + +// 动态加载 assets +plugins.register((ctx: ILowCodePluginContext) => { + return { + name: 'ext-assets', + async init() { + try { + // 将下述链接替换为您的物料即可。无论是通过 utils 从物料中心引入,还是通过其他途径如直接引入物料描述 + const res = await window.fetch('https://fusion.alicdn.com/assets/default@0.1.95/assets.json') + const assets = await res.text() + material.setAssets(assets) + } catch (err) { + console.error(err) + } + }, + } +}).catch(err => console.error(err)) +``` diff --git a/docs/docs/guide/expand/editor/metaSpec.md b/docs/docs/guide/expand/editor/metaSpec.md new file mode 100644 index 000000000..b1c41dfd9 --- /dev/null +++ b/docs/docs/guide/expand/editor/metaSpec.md @@ -0,0 +1,535 @@ +--- +title: 物料描述详解 +sidebar_position: 2 +--- +## 物料描述概述 + +中后台前端体系中,存在大量的组件,程序员可以通过阅读文档,知悉组件的用法。可是搭建平台无法理解 README,而且很多时候,README 里并没有属性列表。这时,我们需要一份额外的描述,来告诉低代码搭建平台,组件接受哪些属性,又是该用怎样的方式来配置这些属性,于是,[**《中后台低代码组件描述协议》**](http://lowcode-engine.cn/material)应运而生。协议主要包含三部分:基础信息、属性信息 props、能力配置/体验增强 configure。 + +物料配置,就是产出一份符合[**《中后台低代码组件描述协议》**](http://lowcode-engine.cn/material)的 JSON Schema。如果需要补充属性描述信息,或需要定制体验增强部分(如修改 Setter、调整展示顺序等),就可以通过修改这份 Schema 来实现。目前有自动生成、手工配置这两种方式生成物料描述配置。 + +## 可视化生成物料描述 + +使用Parts造物平台:[https://www.yuque.com/lce/xhk5hf/qa9pbx](https://www.yuque.com/lce/xhk5hf/qa9pbx) + +## 自动生成物料描述 + +可以使用官方提供的 `@alilc/lowcode-material-parser` 解析本地组件,自动生成物料描述。把物料描述放到资产包定义中,就能让低代码引擎理解如何制作物料。详见上一个章节“物料扩展”。 + +下面以某个组件代码片段为例: +```typescript +// /path/to/component +import { PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +export default class FusionForm extends PureComponent { + static displayName = 'FusionForm'; + + static defaultProps = { + name: '张三', + age: 18, + friends: ['李四','王五','赵六'] + } + + static propTypes = { + /** + * 这是用于描述姓名 + */ + name: PropTypes.string.isRequired, + /** + * 这是用于描述年龄 + */ + age: PropTypes.number, + /** + * 这是用于描述好友列表 + */ + friends: PropTypes.array + }; + + render() { + return
dumb
+ } +} +``` +引入 parse 工具自动解析 +```typescript +import parse from '@alilc/lowcode-material-parser'; +(async () => { + const result = await parse({ entry: '/path/to/component' }); + console.log(JSON.stringify(result, null, 2)); +})(); +``` +因为一个组件可能输出多个子组件,所以解析结果是个数组。 +```json +[ + { + "componentName": "FusionForm", + "title": "", + "docUrl": "", + "screenshot": "", + "devMode": "proCode", + "npm": { + "package": "", + "version": "", + "exportName": "default", + "main": "", + "destructuring": false, + "subName": "" + }, + "props": [ + { + "name": "name", + "propType": "string", + "description": "这是用于描述姓名", + "defaultValue": "张三" + }, + { + "name": "age", + "propType": "number", + "description": "这是用于描述年龄", + "defaultValue": 18 + }, + { + "name": "friends", + "propType": "array", + "description": "这是用于描述好友列表", + "defaultValue": [ + "李四", + "王五", + "赵六" + ] + } + ] + } +] +``` +## 手工配置物料描述 + +如果自动生成的物料无法满足需求,我们就需要手动配置物料描述。本节将分场景描述物料配置的内容。 + +### 常见配置 + +#### 组件的属性只有有限的值 + +增加一个 size 属性,只能从 'large'、'normal'、'small' 这个候选值中选择。 + +以上面自动解析的物料为例,在此基础上手工加上 size 属性: +```json +[ + { + "componentName": "FusionForm", + "title": "", + "docUrl": "", + "screenshot": "", + "devMode": "proCode", + "npm": { + "package": "", + "version": "", + "exportName": "default", + "main": "", + "destructuring": false, + "subName": "" + }, + "props": [ + { + "name": "name", + "propType": "string", + "description": "这是用于描述姓名", + "defaultValue": "张三" + }, + { + "name": "age", + "propType": "number", + "description": "这是用于描述年龄", + "defaultValue": 18 + }, + { + "name": "friends", + "propType": "array", + "description": "这是用于描述好友列表", + "defaultValue": [ + "李四", + "王五", + "赵六" + ] + } + ], + // 手工增加的 size 属性 + "configure": { + "isExtend": true, + "props": [ + { + "title": "尺寸", + "name": "size", + "setter": { + "componentName": 'RadioGroupSetter', + "isRequired": true, + "props": { + "options": [ + { "title": "大", "value": "large" }, + { "title": "中", "value": "normal" }, + { "title": "小", "value": "small" }, + ] + }, + } + } + ] + } + } +] +``` + +#### 组件的属性既可以设置固定值,也可以绑定到变量 +我们知道一种属性形式就需要一种 setter 来设置,如果想要将 value 属性允许输入字符串,那就需要设置为 `StringSetter`,如果允许绑定变量,就需要设置为 `VariableSetter`,具体设置器请参考[预置 Setter 列表](https://www.yuque.com/lce/doc/oc220p) + +那如果都想要呢?可以使用 `MixedSetter` 来实现 +```json +{ + ..., + configure: { + isExtend: true, + props: [ + { + title: "输入框的值", + name: "activeValue", + setter: { + componentName: 'MixedSetter', + isRequired: true, + props: { + setters: [ + 'StringSetter', + 'NumberSetter', + 'VariableSetter', + ], + }, + } + } + ] + } +} +``` +设置后,就会出现 “切换设置器” 的操作项了 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/189077/1647590065530-b50ed66a-8d24-40fc-91a9-13561663537b.png#clientId=ubd9972cd-765c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=126&id=ub0e036f6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=252&originWidth=598&originalType=binary&ratio=1&rotation=0&showTitle=false&size=62314&status=done&style=none&taskId=u6545c47c-0fed-44eb-bfab-03694941981&title=&width=299) ![image.png](https://cdn.nlark.com/yuque/0/2022/png/189077/1647590197192-cd0071cf-a90c-4882-9b65-4b46bff13ce9.png#clientId=ubd9972cd-765c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=154&id=u67de127d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=308&originWidth=244&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24027&status=done&style=none&taskId=u1a44a2d7-3680-4018-8709-9832cd03ad0&title=&width=122) + +#### 开启组件样式设置 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571003600-48ef05cd-dbac-4aad-b7a5-012727fe1c6f.png#clientId=uad16fa90-b520-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u467d584c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=772&originWidth=820&originalType=url&ratio=1&rotation=0&showTitle=false&size=128316&status=done&style=none&taskId=ub01cb8bb-e784-485b-b2a6-aead3302c4f&title=) + +```tsx +{ + configure: { + // ..., + supports: { + style: true, + }, + // ... + } +} +``` + +#### 设置组件的默认事件 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571003649-c0da562f-220c-415e-83ea-e07b71c07552.png#clientId=uad16fa90-b520-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7b452a11&margin=%5Bobject%20Object%5D&name=image.png&originHeight=800&originWidth=776&originalType=url&ratio=1&rotation=0&showTitle=false&size=120022&status=done&style=none&taskId=u6805e481-897b-4929-86c8-9321791a21a&title=) + +```tsx +{ + configure: { + // ..., + supports: { + events: ['onPressEnter', 'onClear', 'onChange', 'onKeyDown', 'onFocus', 'onBlur'], + }, + // ... + } +} +``` + +#### 设置 prop 标题的 tip + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571003618-4a1bb1c4-da39-437b-8510-a121329aa91d.png#clientId=uad16fa90-b520-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7fe57bc7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=176&originWidth=908&originalType=url&ratio=1&rotation=0&showTitle=false&size=39688&status=done&style=none&taskId=u7e9e26eb-a4c3-423c-b7f1-f096d654d4e&title=) + +```tsx +{ + name: 'label', + setter: 'StringSetter', + title: { + label: { + type: 'i18n', + zh_CN: '标签文本', + en_US: 'Label', + }, + tip: { + type: 'i18n', + zh_CN: '属性: label | 说明: 标签文本内容', + en_US: 'prop: label | description: label content', + }, + }, +} +``` + +#### 配置 prop 对应 setter 在配置面板的展示方式 + +inline:![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571004529-c879ec4c-18af-46fd-8231-4ab80c937399.png#clientId=uad16fa90-b520-4&crop=0.0174&crop=0.0597&crop=0.9933&crop=0.3909&from=paste&height=260&id=u8cdcc718&margin=%5Bobject%20Object%5D&name=image.png&originHeight=266&originWidth=790&originalType=url&ratio=1&rotation=0&showTitle=false&size=40667&status=done&style=none&taskId=u9390a3bb-0290-46c7-b487-7380f162fd0&title=&width=771) + +```tsx +{ + configure: { + props: [{ + description: '标签文本', + display: 'inline' + }] + } +} +``` + +block: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571004690-22e7dc4f-db0d-43fe-b837-48ed1145bde7.png#clientId=uad16fa90-b520-4&crop=0&crop=0&crop=0.996&crop=1&from=paste&height=273&id=ua1717366&margin=%5Bobject%20Object%5D&name=image.png&originHeight=274&originWidth=792&originalType=url&ratio=1&rotation=0&showTitle=false&size=31246&status=done&style=none&taskId=u9e678772-1217-4c64-ac75-c5928b48834&title=&width=789) +```tsx +{ + configure: { + props: [{ + description: '高级', + display: 'block' + }] + } +} +``` + +accordion + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571005189-552ef14d-6043-48fa-a526-4565d42fa581.png#clientId=uad16fa90-b520-4&crop=0&crop=0.0159&crop=1&crop=1&from=paste&height=740&id=u53a75049&margin=%5Bobject%20Object%5D&name=image.png&originHeight=740&originWidth=798&originalType=url&ratio=1&rotation=0&showTitle=false&size=163685&status=done&style=none&taskId=ub42fca77-545e-435f-bafe-88e2b2ddfd1&title=&width=798) +```tsx +{ + configure: { + props: [{ + description: '表单项配置', + display: 'accordion' + }] + } +} +``` + +entry + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571005244-fb508efb-a2d8-4064-8ff3-d6140e4c20a1.png#clientId=uad16fa90-b520-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u16645b5c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=424&originWidth=796&originalType=url&ratio=1&rotation=0&showTitle=false&size=91418&status=done&style=none&taskId=u38c7b284-f480-4440-baac-9f7c985104f&title=) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571005468-1c7f4b24-4330-45e2-b6c9-5bf5362874b4.png#clientId=uad16fa90-b520-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u2fad6ab5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=632&originWidth=794&originalType=url&ratio=1&rotation=0&showTitle=false&size=158094&status=done&style=none&taskId=u7c356adc-4286-46b8-9a2c-d33b4268ddc&title=) + +```tsx +{ + configure: { + props: [{ + description: '风格与样式', + display: 'entry' + }] + } +} +``` + +plain + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571005702-ad979f93-cc47-4c6f-8de7-454cc6305614.png#clientId=uad16fa90-b520-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u6aa6d230&margin=%5Bobject%20Object%5D&name=image.png&originHeight=438&originWidth=776&originalType=url&ratio=1&rotation=0&showTitle=false&size=133070&status=done&style=none&taskId=u1db8205a-79ed-4d60-91b4-6e7f5bfaff3&title=) + +```tsx +{ + configure: { + props: [{ + description: '返回上级', + display: 'plain' + }] + } +} +``` + + +### 进阶配置 + +#### 组件的 children 属性允许传入 ReactNode +例如有一个如下的 Tab 选项卡组件,每个 TabPane 的 children 都是一个组件 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/189077/1647588145478-fb8b7296-a8ee-4698-9851-846c78de301e.png#clientId=ubd9972cd-765c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=167&id=bi43p&margin=%5Bobject%20Object%5D&name=image.png&originHeight=334&originWidth=2332&originalType=binary&ratio=1&rotation=0&showTitle=false&size=55470&status=done&style=none&taskId=ub8c8b04a-e2e9-4b5d-9be7-c7ad7154864&title=&width=1166) +只需要增加 `isContainer` 配置即可 +```json +{ + ..., + configure: { + ..., + component: { + // 新增,设置组件为容器组件,可拖入组件 + isContainer: true, + }, + } +} +``` +假设我们希望只允许拖拽 Table、Button 等内容放在 TabPane 里。配置白名单 `childWhitelist` 即可 +```json +{ + ..., + configure: { + ..., + component: { + isContainer: true, + nestingRule: { + // 允许拖入的组件白名单 + childWhitelist: ['Table', 'Button'], + // 同理也可以设置该组件允许被拖入哪些父组件里 + parentWhitelist: ['Tab'], + }, + }, + }, +} +``` +#### 组件的非 children 属性允许传入 ReactNode +这就需要使用 `SlotSetter` 开启插槽了,如下面示例,给 Tab 的 title 开启插槽,允许拖拽组件 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/189077/1647590398244-479c820e-3b2f-4d7e-8742-37cf896bcafb.png#clientId=ubd9972cd-765c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=290&id=Utp8Y&margin=%5Bobject%20Object%5D&name=image.png&originHeight=580&originWidth=3016&originalType=binary&ratio=1&rotation=0&showTitle=false&size=254405&status=done&style=none&taskId=u0c8f777c-3559-455a-b136-c884312bb67&title=&width=1508) +```json +{ + // ..., + configure: { + isExtend: true, + props: [ + { + title: "选项卡标题", + name: "title", + setter: { + componentName: 'MixedSetter', + props: { + setters: [ + 'StringSetter', + 'SlotSetter', + 'VariableSetter', + ], + }, + } + } + ] + } +} +``` + +#### 屏蔽组件在设计器中的操作按钮 + +正常情况下,组件允许复制: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/231502/1647571003626-06d80381-4d97-4d5b-8621-331674832c82.png#clientId=uad16fa90-b520-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=Sp6IN&margin=%5Bobject%20Object%5D&name=image.png&originHeight=226&originWidth=1158&originalType=url&ratio=1&rotation=0&showTitle=false&size=54949&status=done&style=none&taskId=u7e4b2cbe-5acf-467f-950b-ee48deb9502&title=) +如果希望禁止组件的复制行为,我们可以这样做: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1647673808399-2708ff56-70d1-4c58-b93b-aa65269fb179.png#clientId=ufbfe731c-4217-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=150&id=A304J&margin=%5Bobject%20Object%5D&name=image.png&originHeight=300&originWidth=1176&originalType=binary&ratio=1&rotation=0&showTitle=false&size=90147&status=done&style=none&taskId=uf8da0392-c584-4d27-b664-95b3e908103&title=&width=588) +```tsx +{ + configure: { + component: { + disableBehaviors: ['copy'], + } + } +} +``` + +#### 实现一个 BackwardSetter + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1647674621328-6b0a5afc-eafc-43cc-95ce-bbe00981ac20.png#clientId=ufbfe731c-4217-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=219&id=u9c11597c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=438&originWidth=776&originalType=binary&ratio=1&rotation=0&showTitle=false&size=125336&status=done&style=none&taskId=u01853245-46a8-42dd-9c62-6cdbb909afa&title=&width=388) + +```tsx +{ + name: 'back', + title: ' ', + virtual: () => true, + display: 'plain', + setter: BackwardSetter, +} + +// BackwardSetter +import { SettingTarget, DynamicSetter } from '@alilc/lowcode-types'; +const BackwardSetter: DynamicSetter = (target: SettingTarget) => { + return { + componentName: ( + + ), + }; +}; +``` + +### 高级配置 + +#### 不展现一个 prop 配置 + +- 始终隐藏当前 prop + +```tsx +{ + // 始终隐藏当前 prop 配置 + condition: () => false +} +``` + +- 根据其它 prop 的值展示/隐藏当前 prop + +```tsx +{ + // direction 为 hoz 则展示当前 prop 配置 + condition: (target) => { + return target.getProps().getPropValue('direction') === 'hoz'; + } +} +``` + +#### props 联动 + +```tsx +// 根据当前 prop 的值动态设置其它 prop 的值 +{ + name: 'labelAlign', + // ... + extraProps: { + setValue: (target, value) => { + if (value === 'inset') { + target.getProps().setPropValue('labelCol', null); + target.getProps().setPropValue('wrapperCol', null); + } else if (value === 'left') { + target.getProps().setPropValue('labelCol', { fixedSpan: 4 }); + target.getProps().setPropValue('wrapperCol', null); + } + return target.getProps().setPropValue('labelAlign', value); + }, + }, +} +// 根据其它 prop 的值来设置当前 prop 的值 +{ + name: 'status', + // ... + extraProps: { + getValue: (target) => { + const isPreview = target.getProps().getPropValue('isPreview'); + return isPreview ? 'readonly' : 'editable'; + } + } +} +``` + +#### 动态 setter 配置 + +可以通过 DynamicSetter 传入的 target 获取一些引擎暴露的数据,例如当前有哪些组件被加载到引擎中,将这个数据作为 SelectSetter 的选项,让用户选择: + +```tsx +{ + setter: (target) => { + return { + componentName: 'SelectSetter', + props: { + options: target.designer.props.componentMetadatas.filter( + (item) => item.isFormItemComponent).map( + (item) => { + return { + title: item.title || item.componentName, + value: item.componentName, + }; + } + ), + ), + } + }; + } +} +``` diff --git a/docs/docs/guide/expand/editor/partsIntro.md b/docs/docs/guide/expand/editor/partsIntro.md new file mode 100644 index 000000000..6e56ae1e6 --- /dev/null +++ b/docs/docs/guide/expand/editor/partsIntro.md @@ -0,0 +1,100 @@ +--- +title: 利用Parts造物快速使用react组件 +sidebar_position: 3 +--- +## 介绍 +大家在使用[低代码引擎](https://lowcode-engine.cn/)构建低代码应用平台时,遇到的一个主要问题是如何让已有的 React 组件能够快速低成本地接入进来。这个问题拆解下来主要包括两个子问题:1. 如何给已有组件[配置物料描述](https://lowcode-engine.cn/material),2. 如何构建出一个低代码引擎能够识别的资产包(Assets)。 +我们的产品 「[Parts·造物](https://parts.lowcode-engine.cn/)」 可以帮助大家解决这个问题。我们通过在线可视化的方式完成物料描述配置,并且提供一键打包的功能生成引擎可以识别的资产包。 + +## 导入物料 +首先,我们需要在 [物料管理](https://parts.lowcode-engine.cn/material#/) 页面导入我们需要进行在线物料描述配置的物料。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434800993-0fbf5ed5-63e5-492b-85ab-feafd663ad2d.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=196&id=u918deb34&margin=%5Bobject%20Object%5D&name=image.png&originHeight=342&originWidth=1399&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33102&status=done&style=stroke&taskId=u95c39b84-836c-45f8-aee6-0effc1ccfd1&title=&width=800) + +- 点击列表左上方的 导入已有物料 按钮 +- 在弹框中输入 npm包名 +- 点击 获取包信息 按钮,获取npm包基本信息 +- 点击确定,导入成功 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434801003-7bd783f0-8804-445e-b508-8601501dfa60.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u825d698a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=315&originWidth=640&originalType=binary&ratio=1&rotation=0&showTitle=false&size=21969&status=done&style=stroke&taskId=ued992c2e-822b-4c32-81b5-9c9add84954&title=) +## 配置管理 +第二步:物料导入以后,我们就可以为导入的物料新增[物料描述配置](https://lowcode-engine.cn/material),点击右侧的组件配置开始配置。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434801125-979e6348-b78a-47b4-bb2e-fa8f1bb4ff90.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=216&id=u7fb954eb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=261&originWidth=965&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15305&status=done&style=stroke&taskId=uc1e18ffd-fe76-4fe4-83a4-c907f308b14&title=&width=800) +### 新增配置 + +- 点击配置管理右上角的 新增配置 + - 选择组件的版本号 + - 填写组件路径,一般和 npm 包的 package.json 里的 main 字段相同 (如果填写错误,后面会渲染不出来) + - 描述字段用于给这份配置增加一些备注信息。 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434801095-1957da7f-5d9d-4c17-a762-c576bf0f763f.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=380&id=u9ad0ec47&margin=%5Bobject%20Object%5D&name=image.png&originHeight=418&originWidth=596&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26130&status=done&style=stroke&taskId=u2b592498-195a-4fec-9853-ec5c3b95ef7&title=&width=541.8181700745893) +为了降低配置成本,第一次新增配置的时候会自动解析组件代码,生成一份初始化组件物料描述。所以需要等待片刻,用于代码解析。解析完成后,点击配置按钮即可进入在线配置界面。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434801053-1a48b598-e987-4cd5-b657-030d345e0a99.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=193&id=ud384a13d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=232&originWidth=963&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23541&status=done&style=stroke&taskId=ud2efc4d3-6d52-4b77-adbd-14dd5ee4b11&title=&width=800) +### 组件描述配置 +操作界面如下,接下来讲具体的配置流程 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434802081-6546d0f5-19da-475e-8dec-93ea324cc4e3.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=522&id=uf73c4753&margin=%5Bobject%20Object%5D&name=image.png&originHeight=938&originWidth=1438&originalType=binary&ratio=1&rotation=0&showTitle=false&size=111984&status=done&style=stroke&taskId=u0ce37d2b-8ca3-48b5-ac67-8fb461d17b5&title=&width=800) +#### 新增组件 +如果新增配置的过程中,代码自动解析失败或者解析出来的组件列表不满足开发要求,我们可以点击左侧组件列表插件 新增 按钮,添加新的组件,具体的字段描述可以参考提示内容,以 [react-color](https://github.com/casesandberg/react-color) 为例: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434802087-eaf4e2f1-2028-4415-b696-9788a6b2d0ed.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=560&id=u4341eb1b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1005&originWidth=1436&originalType=binary&ratio=1&rotation=0&showTitle=false&size=147918&status=done&style=stroke&taskId=ud921b52d-1961-4be9-b4ec-77d6364b213&title=&width=800) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434802555-bbd14a55-89a6-42cd-a4b3-76c98febf00c.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=472&id=u06e0b78f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=704&originWidth=1193&originalType=binary&ratio=1&rotation=0&showTitle=false&size=240470&status=done&style=stroke&taskId=u77603c5d-9d14-4379-86d2-deb4deaba50&title=&width=800) +#### 给组件增加物料描述 +选中刚刚新增的BlockPicker组件,然后给它增加描述: + +- 打开左侧 Sette r面板 +- 按照组件的属性拖入需要 Setter 类型 (如图中组件的width属性,拖入数字Setter) +- 各种 Setter 的介绍可以参看这篇文档:[https://www.yuque.com/lce/doc/grfylu](https://www.yuque.com/lce/doc/grfylu) +- 配置属性的基本信息(如图所示) +- 配置完成后点击右上角的保存 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434802458-b0fb8a0e-307e-458c-a9f9-af3d2697024c.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=539&id=udeb647da&margin=%5Bobject%20Object%5D&name=image.png&originHeight=967&originWidth=1434&originalType=binary&ratio=1&rotation=0&showTitle=false&size=158958&status=done&style=stroke&taskId=u2950484f-659b-4643-af5e-75d04f14346&title=&width=800) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434802443-cdc533bf-1b08-4c11-b3d2-7cfd7fe0a5dd.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=311&id=uaaaa88fb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=360&originWidth=925&originalType=binary&ratio=1&rotation=0&showTitle=false&size=64587&status=done&style=stroke&taskId=u7139e8ef-eee3-468b-833c-a42d8f3cb56&title=&width=800) +#### 高级配置(属性联动) +举个栗子:如图所示,如果期望 “设置器” 这个配置项的值 “被修改”的时候,下面的 “默认值” 跟着变化。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434803379-009a9783-ec24-4a08-8a46-55ae775ce7ba.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=520&id=u005ad05e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=572&originWidth=371&originalType=binary&ratio=1&rotation=0&showTitle=false&size=96588&status=done&style=stroke&taskId=u97330f9d-6728-4a05-a842-55df114ccee&title=&width=337.27271996253796) +如何使用 +组件的属性配置目前支持3个基本的联动函数: + +- 显示状态:返回true | false,如果返回true,表示组件配置显示,否则配置时不显示 +- 获取值:当调用该配置节点的getValue方法时触发的方法 +- 值变化:当调用该配置节点的setValue方法时触发的方法 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434803522-85aed489-4e00-4787-a496-54cc73e25bc5.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=129&id=u0a782260&margin=%5Bobject%20Object%5D&name=image.png&originHeight=142&originWidth=316&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29086&status=done&style=stroke&taskId=u95864da5-4ccf-4e4b-b903-1ce26af4f66&title=&width=287.2727210462587) +方法的第一个参数都是当前配置节点的对象,常用到的有以下几个: + +- getValue(): 获取当前节点的值,如果当前节点是子节点的话,否则为undefined +- setValue(): 设置当前节点的值,如果当前节点是子节点的话 +- parent: 当前节点的父节点 +- getPropValue(propName): 父节点获取子节点的属性值,propName为子节点的属性名称 +- setPropValue(propName, value): 父节点设置子节点的属性值,propName为子节点的属性名称, value 为设置的值 +- getConfig: 获取当前节点的配置,如title、setter等 +#### 调试物料描述 +点击右上角的预览按钮,开始调试我们刚刚配置的属性,如果是组件的首次预览,会有一段组件构建的过程(构建出umd包的过程),构建完成后就可以调试我们的配置了。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434804408-717e49bd-26b3-4a28-b3e5-bd1d67cdab00.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=209&id=ucf92cc3e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=373&originWidth=1431&originalType=binary&ratio=1&rotation=0&showTitle=false&size=46363&status=done&style=stroke&taskId=u501edca5-bbef-4fde-b341-b42c28b125a&title=&width=800) +#### 发布物料描述 +物料描述调试没问题后,就可以到项目中去使用了,使用前需要先发布物料描述 + +- 点击右上角的发布按钮 +- 选择需要发布的组件 +- 点击确定发布完成 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434804305-276f03e2-4dd2-41e9-9375-1c3bd0c7092a.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=410&id=uf879e7fd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=734&originWidth=1431&originalType=binary&ratio=1&rotation=0&showTitle=false&size=103858&status=done&style=stroke&taskId=udc267585-ffb7-4247-b1f5-b7aca386e10&title=&width=800) +## 资产包构建 +第三步:物料描述发布完成后,接下来我们就需要构建出可用的资产包用于低代码应用中。 +#### 资产包构建 + +- 选择需要构建的组件 +- 点击构建资产包按钮 +- 选择刚刚的物料描述配置 +- 开始构建,构建完成后你将得到一份json文件(里面包含了物料描述和umd包),就可以到项目中使用了 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434804769-6f6f60f1-9ee3-4561-972d-610f0616576e.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=430&id=ue119fa2b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=770&originWidth=1431&originalType=binary&ratio=1&rotation=0&showTitle=false&size=93492&status=done&style=stroke&taskId=ubfd97421-964b-4823-adc8-b056a588924&title=&width=800) +#### 资产包使用 +**方式一、在 **[**lowcode-demo**](https://github.com/alibaba/lowcode-demo)**中直接引用,可直接替换demo中原来的资产包文件:** +例如,在basic-fusion demo中,直接用你的资产包文件替换文件[assets.json](https://github.com/alibaba/lowcode-demo/blob/main/src/scenarios/basic-fusion/assets.json),即可快速使用自己的物料了。 +**方式二、将新的资产包内容和现有的资产包内容融合:** +将上面构建完成的资产包与你项目中的[assets.json文件](https://github.com/alibaba/lowcode-demo/blob/main/src/scenarios/basic-fusion/assets.json)合并,主要合并packages 和 components + +- packages中是构建好的umd包 +- components中是上面配置好的[物料描述](https://lowcode-engine.cn/material),你也可以在基础上二次加工 + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/12718919/1652434804944-860abc0c-057c-46d5-a6e5-8d33fde8a762.png#clientId=u0f780a28-b8dc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=676&id=u5499b1c9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=744&originWidth=1140&originalType=binary&ratio=1&rotation=0&showTitle=false&size=116233&status=done&style=stroke&taskId=u7be27934-77ce-4dd7-a406-1d402acef2c&title=&width=1036.36361390106) diff --git a/docs/docs/guide/expand/editor/pluginContextMenu.md b/docs/docs/guide/expand/editor/pluginContextMenu.md new file mode 100644 index 000000000..603aa09c0 --- /dev/null +++ b/docs/docs/guide/expand/editor/pluginContextMenu.md @@ -0,0 +1,69 @@ +--- +title: 插件扩展-编排扩展 +sidebar_position: 6 +--- +## 场景一:扩展选中节点操作项 +### 增加节点操作项 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1647693318212-173890bc-b0b5-437b-9802-4b1fd9f74c5a.png#clientId=u2eca2bba-d284-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=254&id=u55228975&margin=%5Bobject%20Object%5D&name=image.png&originHeight=292&originWidth=1240&originalType=binary&ratio=1&rotation=0&showTitle=false&size=38144&status=done&style=none&taskId=u426cac9f-24ad-4d06-adbe-faca1896eaa&title=&width=1080) +选中节点后,在选中框的右上角有操作按钮,编排模块默认实现了查看组件直系父节点、复制节点和删除节点按钮外,还可以通过相关 API 来扩展更多操作,如下代码: +```typescript +import { plugins } from '@alilc/lowcode-engine'; +import { Icon, Message } from '@alifd/next'; + +const addHelloAction = (ctx: ILowCodePluginContext) => { + return { + async init() { + const { addBuiltinComponentAction } = ctx.material; + addBuiltinComponentAction({ + name: 'hello', + content: { + icon: , + title: 'hello', + action(node: Node) { + Message.show('Welcome to Low-Code engine'); + }, + }, + condition: (node: Node) => { + return node.componentMeta.componentName === 'NextTable'; + }, + important: true, + }); + } + }; +} +addHelloAction.pluginName = 'addHelloAction'; +await plugins.register(addHelloAction); +``` +**_效果如下:_** +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1647694920149-b8d9a534-b943-45d2-b67e-cc42b906f827.png#clientId=u2eca2bba-d284-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=282&id=ua20a09c8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=343&originWidth=1315&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35131&status=done&style=none&taskId=u3f47b55d-15ff-495c-8615-31e3ccb0222&title=&width=1080) +具体 API 参考:[https://www.yuque.com/lce/doc/mu7lml#ieJzi](https://www.yuque.com/lce/doc/mu7lml#ieJzi) +### 删除节点操作项 +```typescript +import { plugins } from '@alilc/lowcode-engine'; + +const removeCopyAction = (ctx: ILowCodePluginContext) => { + return { + async init() { + const { removeBuiltinComponentAction } = ctx.material; + removeBuiltinComponentAction('copy'); + } + } +} +removeCopyAction.pluginName = 'removeCopyAction'; +await plugins.register(removeCopyAction); +``` +**_效果如下:_** +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1647695353667-e22bef51-3c6a-4b6a-87d2-c144ddb68115.png#clientId=u2eca2bba-d284-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=237&id=ufa1f9434&margin=%5Bobject%20Object%5D&name=image.png&originHeight=290&originWidth=1319&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22495&status=done&style=none&taskId=u73e01acc-96e8-45e7-9d42-a31edca193e&title=&width=1080) +具体 API 参考:[https://www.yuque.com/lce/doc/mu7lml#va9mb](https://www.yuque.com/lce/doc/mu7lml#va9mb) +## 实际案例 +### 区块管理 + +- 仓库地址:[https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) +- 具体代码:[https://github.com/alibaba/lowcode-plugins/tree/main/packages/action-block](https://github.com/alibaba/lowcode-plugins/tree/main/packages/action-block) +- 直播回放: + - [低代码引擎项目实战(9)-区块管理(1)-保存为区块](https://www.bilibili.com/video/BV1YF411M7RK/) + - [低代码引擎项目实战(10)-区块管理-区块面板](https://www.bilibili.com/video/BV1FB4y1S7tu/) + - [阿里巴巴低代码引擎项目实战(11)-区块管理- ICON优化](https://www.bilibili.com/video/BV1zr4y1H7Km/) + - [阿里巴巴低代码引擎项目实战(11)-区块管理-自动截图](https://www.bilibili.com/video/BV1GZ4y117VH/) + - [阿里巴巴低代码引擎项目实战(11)-区块管理-样式优化](https://www.bilibili.com/video/BV1Pi4y1S7ZT/) + - [阿里低代码引擎项目实战(12)-区块管理(完结)-给引擎插件提个 PR](https://www.bilibili.com/video/BV1hB4y1277o/) diff --git a/docs/docs/guide/expand/editor/pluginWidget.md b/docs/docs/guide/expand/editor/pluginWidget.md new file mode 100644 index 000000000..fd63167e9 --- /dev/null +++ b/docs/docs/guide/expand/editor/pluginWidget.md @@ -0,0 +1,170 @@ +--- +title: 插件扩展-面板扩展 +sidebar_position: 5 +--- +## 插件简述 +插件功能赋予低代码引擎更高的灵活性,低代码引擎的生态提供了一些官方的插件,但是无法满足所有人的需求,所以提供了强大的插件定制功能。 +通过定制插件,在和低代码引擎解耦的基础上,我们可以和引擎核心模块进行交互,从而满足多样化的功能。不仅可以自定义插件的 UI,还可以实现一些非 UI 的逻辑: +1)调用编辑器框架提供的 API 进行编辑器操作或者 schema 操作; +2)通过插件类的生命周期函数实现一些插件初始化的逻辑; +3)通过实现监听编辑器内的消息实现特定的切片逻辑(例如面板打开、面板关闭等) +> 本文仅介绍面板层面的扩展,编辑器插件层面的扩展可以参考 "插件扩展 - 编排扩展" 章节。 + +## 注册插件 API +```typescript +import { plugins, ILowCodePluginContext } from '@alilc/lowcode-engine'; + +const pluginA = (ctx: ILowCodePluginContext, options: any) => { + return { + init() { + console.log(options.key); + // 往引擎增加面板 + ctx.skeleton.add({ + // area 配置见下方说明 + area: 'leftArea', + // type 配置见下方说明 + type: 'PanelDock', + content:
demo
, + }); + ctx.logger.log('打个日志'); + }, + destroy() { + console.log('我被销毁了~'); + } + } +} + +pluginA.pluginName = 'pluginA'; + +plugins.register(pluginA, { key: 'test' }); +``` +> 如果您想了解抽取出来的插件如何封装成为一个 npm 包并提供给社区,可以参考“扩展低代码应用 - 扩展低代码编辑器 - 低代码插件脚手架”章节。 +插件系统的详细设计,可参考“参与低代码引擎开发 - 低代码引擎设计文档 - 插件”章节。 + +## 面板插件配置说明 +面板插件是作用于设计器的,主要是通过按钮、图标等展示在设计器的骨架中。设计器的骨架我们分为下面的几个区域,而我们的插件大多数都是作用于这几个区域的。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644393006009-165e36cd-fa7b-4ee0-b3e3-dc7ba9d80d55.png#averageHue=%237cac76&clientId=u45843f36-7f71-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=608&id=u9e018f89&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1080&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=149463&status=done&style=stroke&taskId=u74f952e4-c783-47ae-b11c-be48d3c52be&title=&width=1080) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644320581783-b8fcd29c-45c2-48df-be2c-7101b12474e3.png#averageHue=%23edf6d4&clientId=u221f0bd4-c19e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=580&id=ixlrN&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1648&originWidth=3068&originalType=binary&ratio=1&rotation=0&showTitle=false&size=165621&status=done&style=stroke&taskId=u030d9faf-015f-4475-b34a-ba1fbf8868b&title=&width=1080) +### 展示区域 area +#### topArea +展示在设计器的顶部区域,常见的相关区域的插件主要是: +1)注册设计器 Logo; +2)设计器操作回退和撤销按钮; +3)全局操作按钮,例如:保存、预览等; +#### leftArea +左侧区域的展示形式大多数是 Icon 和对应的面板,通过点击 Icon 可以展示对应的面板并隐藏其他的面板。 +该区域相关插件的主要有: +1)大纲树展示,展示该设计器设计页面的大纲。 +2)组件库,展示注册到设计器中的组件,点击之后,可以从组件库面板中拖拽到设计器的画布中。 +3)数据源面板 +4)JS 等代码面板。 +可以发现,这个区域的面板大多数操作时是不需要同时并存的,且交互比较复杂的,需要一个更整块的区域来进行操作。 +#### centerArea +画布区域,由于画布大多数是展示作用,所以一般扩展的种类比较少。常见的扩展有: +1)画布大小修改 +2)物料选中扩展区域修改 +#### rightArea +右侧区域,常用于组件的配置。常见的扩展有:统一处理组件的配置项,例如统一删除某一个配置项,统一添加某一个配置项的。 +#### toolbar +跟 topArea 类似,按需放置面板插件~ +### 展示形式 type +#### PanelDock +PanelDock 是以面板的形式展示在设计器的左侧区域的。其中主要有两个部分组成,一个是图标,一个是面板。当点击图标时可以控制面板的显示和隐藏。 +下图是组件库插件的展示效果。 +![Feb-08-2022 19-44-15.gif](https://cdn.nlark.com/yuque/0/2022/gif/242652/1644320663827-ee9c54a1-f684-40e2-8a6b-875103d04b31.gif#averageHue=%23eaf6d2&clientId=u221f0bd4-c19e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=555&id=u5292d9cc&margin=%5Bobject%20Object%5D&name=Feb-08-2022%2019-44-15.gif&originHeight=790&originWidth=1536&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1381641&status=done&style=stroke&taskId=ub28a13a4-3d80-4a02-bcaa-cc9d6127243&title=&width=1080) +其中右上角可以进行固定,可以对弹出的宽度做设定 +接入可以参考代码 +```javascript +import { skeleton } from "@alilc/lowcode-engine"; + +skeleton.add({ + area: "leftArea", // 插件区域 + type: "PanelDock", // 插件类型,弹出面板 + name: "sourceEditor", + content: SourceEditor, // 插件组件实例 + props: { + align: "left", + icon: "wenjian", + description: "JS面板", + }, + panelProps: { + floatable: true, // 是否可浮动 + height: 300, + hideTitleBar: false, + maxHeight: 800, + maxWidth: 1200, + title: "JS面板", + width: 600, + }, +}); +``` +#### Widget +Widget 形式是直接渲染在当前编辑器的对应位置上。如 demo 中在设计器顶部的所有组件都是这种展现形式。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644320068765-47efc836-30c2-452f-8104-b98b1ea3533d.png#averageHue=%23fefefb&clientId=u221f0bd4-c19e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=51&id=u68c58cad&margin=%5Bobject%20Object%5D&name=image.png&originHeight=94&originWidth=1988&originalType=binary&ratio=1&rotation=0&showTitle=false&size=58410&status=done&style=stroke&taskId=u4eadd643-2e63-4be7-8736-b27b9c82b81&title=&width=1080) +接入可以参考代码: +```javascript +import {skeleton} from "@alilc/lowcode-engine"; +// 注册 logo 面板 +skeleton.add({ + area: "topArea", + type: "Widget", + name: "logo", + content: Logo, // Widget 组件实例 + contentProps: { // Widget 插件props + logo: + "https://img.alicdn.com/tfs/TB1_SocGkT2gK0jSZFkXXcIQFXa-66-66.png", + href: "/", + }, + props: { + align: "left", + width: 100, + }, +}); +``` +#### Dock +一个图标的表现形式,可以用于语言切换、跳转到外部链接、打开一个 widget 等场景 +```javascript +import { skeleton } from "@alilc/lowcode-engine"; + +skeleton.add({ + area: "leftArea", + type: "Dock", + name: "opener", + content: Opener, // Widget 组件实例 + contentProps: { // Widget 插件props + xxx: "1", + }, + props: { + align: "bottom", + }, + onClick: function() { + // 打开外部链接 + window.open('https://lowcode-engine.cn'); + // 显示 widget + skeleton.showWidget('xxx'); + } +}); +``` +#### Panel +一般不建议单独使用,通过 PanelDock 使用~ +## 实际案例 +### 页面管理面板 + +- 仓库地址:[https://github.com/mark-ck/lowcode-portal](https://github.com/mark-ck/lowcode-portal) +- 具体代码:[https://github.com/mark-ck/lowcode-portal/blob/master/src/plugins/pages-plugin/index.tsx](https://github.com/mark-ck/lowcode-portal/blob/master/src/plugins/pages-plugin/index.tsx) +- 直播回放: + - [低代码引擎项目实战(4)-自定义插件-页面管理](https://www.bilibili.com/video/BV17a411i73f/) + - [低代码引擎项目实战(4)-自定义插件-页面管理-后端](https://www.bilibili.com/video/BV1uZ4y1U7Ly/) + - [低代码引擎项目实战(4)-自定义插件-页面管理-前端](https://www.bilibili.com/video/BV1Yq4y1a74P/) + - [低代码引擎项目实战(4)-自定义插件-页面管理-完结](https://www.bilibili.com/video/BV13Y4y1e7EV/) +### 区块面板 + +- 仓库地址:[https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins) +- 具体代码:[https://github.com/alibaba/lowcode-plugins/tree/main/packages/plugin-block](https://github.com/alibaba/lowcode-plugins/tree/main/packages/plugin-block) +- 直播回放: + - [低代码引擎项目实战(9)-区块管理(1)-保存为区块](https://www.bilibili.com/video/BV1YF411M7RK/) + - [低代码引擎项目实战(10)-区块管理-区块面板](https://www.bilibili.com/video/BV1FB4y1S7tu/) + - [阿里巴巴低代码引擎项目实战(11)-区块管理- ICON优化](https://www.bilibili.com/video/BV1zr4y1H7Km/) + - [阿里巴巴低代码引擎项目实战(11)-区块管理-自动截图](https://www.bilibili.com/video/BV1GZ4y117VH/) + - [阿里巴巴低代码引擎项目实战(11)-区块管理-样式优化](https://www.bilibili.com/video/BV1Pi4y1S7ZT/) + - [阿里低代码引擎项目实战(12)-区块管理(完结)-给引擎插件提个 PR](https://www.bilibili.com/video/BV1hB4y1277o/) diff --git a/docs/docs/guide/expand/editor/setter.md b/docs/docs/guide/expand/editor/setter.md new file mode 100644 index 000000000..846c60b3e --- /dev/null +++ b/docs/docs/guide/expand/editor/setter.md @@ -0,0 +1,202 @@ +--- +title: 设置器扩展 +sidebar_position: 4 +--- +## 设置器简述 +设置器主要用于低代码组件属性值的设置,顾名思义叫"设置器",又称为 Setter。由于组件的属性有各种类型,需要有与之对应的设置器支持,每一个设置器对应一个值的类型。 +### 设计器展示位置 +设置器展示在编辑器的右边区域,如下图: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644387351052-0be9546e-9e46-41ff-bbb4-a1effe650d7f.png#clientId=u39aebc41-90a1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=487&id=pi5XH&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1730&originWidth=3836&originalType=binary&ratio=1&rotation=0&showTitle=false&size=947162&status=done&style=stroke&taskId=u4d4deed8-40f5-40a6-b20d-d092c90775c&title=&width=1080) +其中包含四类设置器: + +- 属性:展示该物料常规的属性 +- 样式:展示该物料样式的属性 +- 事件:如果该物料有声明事件,则会出现事件面板,用于绑定事件。 +- 高级:两个逻辑相关的属性,**条件渲染**和**循环** +### 设置器类型 +上述区域中是有多项设置器的,对于一个组件来说,每一项配置都对应一个设置器,比如我们的配置是一个文本,我们需要的是文本设置器,我们需要配置的是数字,我们需要的就是数字设置器。 +下图中的标题和按钮类型配置就分别是文本设置器和下拉框设置器。 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1644387350762-7337e729-53e9-4a6c-8da1-8f17260e1347.png#clientId=u39aebc41-90a1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=744&id=ztLvk&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1460&originWidth=2120&originalType=binary&ratio=1&rotation=0&showTitle=false&size=489840&status=done&style=stroke&taskId=u7375a322-b6c8-43f1-a096-07b204656aa&title=&width=1080) +我们提供了常用的设置器作为内置设置器,也提供了定制能力帮助大家开发特定需求的设置器。 +## 为物料配置设置器 +我们提供了[常用的设置器](https://www.yuque.com/lce/doc/oc220p?view=doc_embed&from=kb&from=kb&outline=1&title=1)作为内置设置器。 +我们可以将目标组件的属性值类型值配置到物料资源配置文件中: +```json +{ + "componentName": "Message", + "title": "Message", + "configure": { + "props": [ + { + "name": "type", + "setter": "InputSetter" + } + ] + } +} +``` +props 字段是入料模块扫描自动填入的类型,用户可以通过 configure 节点进行配置通过 override 节点对属性的声明重新定义,setter 就是注册在引擎中的 setter。 +为物料配置引擎内置的 setter 时,均可以使用对应 setter 的高级功能,对应功能参考“全部内置设置器”章节下的对应 setter 文章。 +**_对高级功能的配置如下:_** +例如我们需要在NumberSetter中配置units属性,可以在asset.json中声明 +```json +"configure": { + "component": { + "isContainer": true, + "nestingRule": { + "parentWhitelist": [ + "NextP" + ] + } + }, + "props": [ + { + "name": "width", + "title": "宽度", + "initialValue": "auto", + "defaultValue": "auto", + "condition": { + "type": "JSFunction", + "value": "() => false" + }, + "setter": { + "componentName": "NumberSetter", + "props": { + "units": [ + { + "type": "px", + "list": true + }, + { + "type": "%", + "list": true + } + ] + } + } + }, + ], + "supports": { + "style": true + } +}, +``` +## 自定义设置器 +### 编写 AltStringSetter +我们编写一个简单的 Setter,它的功能如下: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2553587/1644764687180-0121f0c0-d113-4907-a86d-e4f3a04ff221.png#clientId=ucb27c83c-48cf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=45&id=u32dc8cd0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=90&originWidth=720&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17539&status=done&style=stroke&taskId=u0f886bda-a93e-4b10-ad7e-9ba9a38a3fb&title=&width=360) +**_代码如下:_** +```typescript +import * as React from "react"; +import { Input } from "@alifd/next"; + +import "./index.scss"; +interface AltStringSetterProps { + // 当前值 + value: string; + // 默认值 + defaultValue: string; + // setter唯一输出 + onChange: (val: string) => void; + // AltStringSetter 特殊配置 + placeholder: string; +} +export default class AltStringSetter extends React.PureComponent { + componentDidMount() { + const { onChange, value, defaultValue } = this.props; + if (value == undefined && defaultValue) { + onChange(defaultValue); + } + } + + // 声明Setter的title + static displayName = 'AltStringSetter'; + + render() { + const { onChange, value, placeholder } = this.props; + return ( + onChange(val)} + > + ); + } +} +``` +#### setter 和 setter/plugin 之间的联动 +我们采用 emit 来进行相互之前的通信,首先我们在 A setter 中进行事件注册: +```javascript +import { event } from '@alilc/lowcode-engine'; + +componentDidMount() { + // 这里由于面板上会有多个setter,这里我用field.id来标记setter名 + this.emitEventName = `${SETTER_NAME}-${this.props.field.id}`; + event.on(`${this.emitEventName}.bindEvent`, this.bindEvent) +} + +bindEvent = (eventName) => { + // do someting +} + +componentWillUnmount() { + // setter是以实例为单位的,每个setter注销的时候需要把事件也注销掉,避免事件池过多 + event.off(`${this.emitEventName}.bindEvent`, this.bindEvent) +} +``` +在 B setter 中触发事件,来完成通信: +```javascript +import { event } from '@alilc/lowcode-engine'; + +bindFunction = () => { + const { field, value } = this.props; + // 这里展示的和插件进行通信,事件规则是插件名+方法 + event.emit('eventBindDialog.openDialog', field.name, this.emitEventName); +} +``` +#### 修改同级 props 的其他属性值 +setter 本身只影响其中一个 props 的值,如果需要影响其他组件的 props 的值,需要使用 field 的 props: +```json +bindFunction = () => { + const { field, value } = this.props; + const propsField = field.parent; + // 获取同级其他属性showJump的值 + const otherValue = propsField.getPropValue('showJump'); + // set同级其他属性showJump的值 + propsField.setPropValue('showJump', false); +} +``` +### 注册 AltStringSetter +我们需要在低代码引擎中注册 Setter,这样就可以通过 AltStringSetter 的名字在物料中使用了。 +```typescript +import AltStringSetter from './AltStringSetter'; +const registerSetter = window.AliLowCodeEngine.setters.registerSetter; +registerSetter('AltStringSetter', AltStringSetter); +``` +### 物料中使用 +我们需要将目标组件的属性值类型值配置到物料资源配置文件中,其中核心配置如下: +```json +{ + "props": [ + { + "name": "type", + "setter": "AltStringSetter" + } + ] +} +``` +在物料中的相关配置如下: +```json +{ + "componentName": "Message", + "title": "Message", + "configure": { + "props": [ + { + "name": "type", + "setter": "AltStringSetter" + } + ] + } +} +``` diff --git a/docs/docs/guide/expand/editor/summary.md b/docs/docs/guide/expand/editor/summary.md new file mode 100644 index 000000000..e8487213c --- /dev/null +++ b/docs/docs/guide/expand/editor/summary.md @@ -0,0 +1,90 @@ +--- +title: 低代码扩展简述 +sidebar_position: 0 +--- +## 扩展点简述 + +我们可以从 Demo 的项目中看到页面中有很多的区块: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643447049972-e324320a-7f97-4e48-bef3-a4c5d2b06517.png#clientId=udea0fe9a-4e7e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1080&id=rGr7U&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2160&originWidth=3840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=518455&status=done&style=none&taskId=u872d1136-0f18-41b3-900d-710e9fc9eea&title=&width=1920) +这些功能点背后都是可扩展项目,如下图所示: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643447052089-8e340da7-3c2c-4a88-9ed8-c89516dccf75.png#clientId=udea0fe9a-4e7e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=957&id=lL1sN&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1914&originWidth=3838&originalType=binary&ratio=1&rotation=0&showTitle=false&size=538736&status=done&style=none&taskId=u43e8a14e-0d52-4a22-bd19-e5083814daf&title=&width=1919) + +- 插件定制:可以配置低代码编辑器的功能和面板 +- 物料定制:可以配置能够拖入的物料 +- 操作辅助区定制:可以配置编辑器画布中的操作辅助区功能 +- 设置器定制:可以配置编辑器中组件的配置表单 + +我们从可扩展项目的视角,可以把低代码引擎架构理解为下图: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643447051959-7abb91ea-44af-46e0-b73a-dd2127648b32.png#clientId=udea0fe9a-4e7e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1080&id=M07o7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2160&originWidth=3840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=779021&status=done&style=none&taskId=u640e4616-d38d-45fb-a560-e4a98cd1605&title=&width=1920) +(注:引擎内核中大量数据交互的细节被简化,这张图仅仅强调编辑器和外部扩展的交互) + +## 配置扩展点 + +### 配置物料 +通过配置注入物料,这里的配置是物料中心根据物料资产包协议生成的,后面“物料扩展”章节会有详细说明。 +```typescript +import { material } from '@alilc/lowcode-engine' +// 假设您已把物料配置在本地 +import assets from './assets.json' + +// 静态加载 assets +material.setAssets(assets) +``` + +也可以通过异步加载物料中心上的物料。 +```typescript +import { ILowCodePluginContext, material, plugins } from '@alilc/lowcode-engine' + +// 动态加载 assets +plugins.register((ctx: ILowCodePluginContext) => { + return { + name: 'ext-assets', + async init() { + try { + // 将下述链接替换为您的物料即可。无论是通过 utils 从物料中心引入,还是通过其他途径如直接引入物料描述 + const res = await window.fetch('https://fusion.alicdn.com/assets/default@0.1.95/assets.json') + const assets = await res.text() + material.setAssets(assets) + } catch (err) { + console.error(err) + } + }, + } +}).catch(err => console.error(err)) +``` + +### 配置插件 +可以通过 npm 包的方式引入社区插件,配置如下所示: +```typescript +import { ILowCodePluginContext, plugins } from '@alilc/lowcode-engine' +import PluginIssueTracker from '@alilc/lowcode-plugin-issue-tracker' + +// 注册一个提 issue 组件到您的编辑器中,方位默认在左栏下侧 +plugins.register(PluginIssueTracker) + .catch(err => console.error(err)) +``` +后续“插件扩展”章节会详细说明。 + +### 配置设置器 +低代码引擎默认内置了设置器(详见“配置设置器”章节)。您可以通过 npm 包的方式引入自定义的设置器,配置如下所示: +```typescript +import { setters } from '@alilc/lowcode-engine' +// 假设您自定义了一个 setter +import MuxMonacoEditorSetter from './components/setters/MuxMonacoEditorSetter' + +// 注册设置器 +setters.registerSetter({ + MuxMonacoEditorSetter: { + component: MuxMonacoEditorSetter, + title: 'Textarea', + condition: (field) => { + const v = field.getValue() + return typeof v === 'string' + }, + }, +}) +``` +后续“设置器扩展”章节会详细说明。 + +> 本章节所有可扩展配置内容在 demo 中均可找到:[https://github.com/alibaba/lowcode-demo/blob/main/src/universal/plugin.tsx](https://github.com/alibaba/lowcode-demo/blob/main/src/universal/plugin.tsx) diff --git a/docs/docs/guide/expand/runtime/_category_.json b/docs/docs/guide/expand/runtime/_category_.json new file mode 100644 index 000000000..14aafc939 --- /dev/null +++ b/docs/docs/guide/expand/runtime/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "扩展低代码运行时", + "position": 2 +} diff --git a/docs/docs/guide/expand/runtime/codeGeneration.md b/docs/docs/guide/expand/runtime/codeGeneration.md new file mode 100644 index 000000000..e1d9dc3d8 --- /dev/null +++ b/docs/docs/guide/expand/runtime/codeGeneration.md @@ -0,0 +1,132 @@ +--- +title: 使用出码功能 +sidebar_position: 1 +--- + +## 出码简述 +所谓出码,即将低代码编排出的 schema 进行解析并转换成最终可执行的代码的过程。 +## 出码的适用范围 +出码是为了更高效的运行和更灵活地定制渲染,相对而言,基于 Schema 的运行时渲染,有着能实时响应内容的变化和接入成本低的优点,但是也存在着实时解析运行的性能开销比较大和包大小比较大的问题,而且无法自由地进行扩展二次开发,功能自由度受到一定程度限制。 +当然,出码也会存在一些限制:一方面需要额外的接入成本,另一方面通常需要额外的生成代码和打包构建的时间,难以做到基于 Schema 的运行时渲染那样保存即预览的效果。 + +所以不是所有场景都建议做出码,一般来说以下 3 个场景可以考虑使用出码进行优化。 + +### 场景一:想要极致的打开速度,降低 LCP/FID +这种场景比较常见的是 C 端应用,比如手淘上的页面和手机钉钉上的页面,要求能够尽快得响应用户操作,不要出现卡死的情况。当一个流入协议大小比较大的时候,前端进行解析时的开销也比较大。如果能把这部分负担转移到编译时去完成的话,前端依赖包大小就会减少许多。从而也提升了加载速度,降低了带宽消耗。页面越简单,这其中的 gap 就会越明显。 + +### 场景二:老项目 + 新需求,想用搭建产出 +这是一个很常见的场景,毕竟迁移或者重构都是有一个过程的,阿里的业务都是一边跑一边换发动机。在这种场景中,我们不可能要求使用运行时方案来做实现,因为运行时是一个项目级别的能力,最好是项目中统一使用他这一种方式,保证体验的一致性与连贯性。所以我们可以只在低代码平台上搭建新的业务页面,然后通过出码模块导出这些页面的源码,连同一些全局依赖模块,一起 Merge 到老项目中。完成开发体验的优化。 + +### 场景三:协议不能描述部分代码逻辑(协议功能不足或特别定制化的逻辑) +当我们发现一些逻辑诉求不能在目前协议中很好地表达的时候,这其实是项目复杂度较高的一个信号。比较好的方式就是将低代码研发和源码研发结合起来。这种模式下最大的诉求点之一就是,需要将搭建的内容输出为可读性和确定性都比较良好的代码模块。这也就是出码模块需要支持好的使用场景了。 + +## 如何使用 +### 1) 通过命令行快速体验 + +欢迎使用命令行工具快速体验:`npx @alilc/lowcode-code-generator -i example-schema.json -o generated -s icejs` + +--其中 example-schema.json 可以从[这里下载](https://unpkg.com/@alilc/lowcode-code-generator@beta/example-schema.json) + +### 2) 通过设计器插件快速体验 + +1. 安装依赖: `npm install --save @alilc/lowcode-plugin-code-generator` +2. 注册插件: + +```typescript +import { plugins } from '@alilc/lowcode-engine'; +import CodeGenPlugin from '@alilc/lowcode-plugin-code-generator'; + +// 在你的初始化函数中: +await plugins.register(CodeGenPlugin); + +// 如果您不希望自动加上出码按钮,则可以这样注册 +await plugins.register(CodeGenPlugin, { disableCodeGenActionBtn: true }); +``` + +然后运行你的低代码编辑器项目即可 -- 在设计器的右上角会出现一个“出码”按钮,点击即可在浏览器中出码并预览。 + +### 3)服务端出码接入 + +此代码生成器一开始就是为服务端出码设计的,你可以直接这样来在 node.js 环境中使用: + +1. 安装依赖: `npm install --save @alilc/lowcode-code-generator` +2. 引入代码生成器: + +```javascript +import CodeGenerator from '@alilc/lowcode-code-generator'; +``` + +3. 创建项目构建器: + +```javascript +const projectBuilder = CodeGenerator.solutions.icejs(); +``` + +4. 生成代码 + +```javascript +const project = await projectBuilder.generateProject( + schema, // 编排搭建出来的 schema +); +``` + +5. 将生成的代码写入到磁盘中(也可以生成一个 zip 包) + +```javascript +// 写入磁盘 +await CodeGenerator.publishers.disk().publish({ + project, // 上一步生成的 project + outputPath: '/path/to/your/output/dir', // 输出目录 + projectSlug: 'your-project-slug', // 项目标识 +}); + +// 写入到 zip 包 +await CodeGenerator.publishers.zip().publish({ + project, // 上一步生成的 project + outputPath: '/path/to/your/output/dir', // 输出目录 + projectSlug: 'your-project-slug', // 项目标识 -- 对应生成 your-project-slug.zip 文件 +}); +``` + +注:一般来说在服务端出码可以跟 github/gitlab, CI 和 CD 流程等一起串起来使用,通常用于优化性能。 + +### 4)浏览器中出码接入 + +随着现在电脑性能和浏览器技术的发展,出码其实已经不必非得在服务端做了,借助于 Web Worker 特性,可以在浏览器中进行出码: + +1. 安装依赖: `npm install --save @alilc/lowcode-code-generator` +2. 引入代码生成器: + +```javascript +import * as CodeGenerator from '@alilc/lowcode-code-generator/standalone-loader'; +``` + +3. 【可选】提前初始化代码生成器: + +```javascript +// 提前初始化下,这样后面用的时候更快(这个 init 内部会提前准备好创建 worker 的一些资源) +await CodeGenerator.init(); +``` + +4. 出码 + +```javascript +const result = await CodeGenerator.generateCode({ + solution: 'icejs', // 出码方案 (目前内置有 icejs 和 rax ) + schema, // 编排搭建出来的 schema +}); + +console.log(result); // 出码结果(默认是递归结构描述的,可以传 flattenResult: true 以生成扁平结构的结果) +``` + +注:一般来说在浏览器中出码适合做即时预览功能。 + +### 5)自定义出码 +前端框架灵活多变,默认内置的出码方案很难满足所有人的需求,好在此代码生成器支持非常灵活的插件机制 -- 内置功能大多都是通过插件完成的(在 `src/plugins`下),比如: +![image.png](https://cdn.nlark.com/yuque/0/2022/png/263300/1644824565650-584c2be5-4be3-4c9a-96d9-e27990111b0b.png#averageHue=%232b2b2e&clientId=u8b65d964-7bef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=376&id=u3e0a61a8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=376&originWidth=457&originalType=binary&ratio=1&rotation=0&showTitle=false&size=151355&status=done&style=none&taskId=ueef52494-2e18-45a2-8409-7f68e200f63&title=&width=457) +所以您可以通过添加自己的插件或替换掉默认内置的插件来实现您的自定义功能。 +为了方便自定义出码方案,出码模块还提供自定义出码方案的脚手架功能,即执行下面脚本即可生成一个自定义出码方案: +```shell +npx @alilc/lowcode-code-generator init-solution +``` +里面内置了一个示例的插件(在 `src/plugins/example.ts`中),您可以根据注释引导来完善相关插件,从而组合生成您的专属出码方案(`src/index.ts`)。您所生成的出码方案可以发布成 NPM 包,从而能按上文 1~4 中的使用方案进行使用。 diff --git a/docs/docs/guide/expand/runtime/renderer.md b/docs/docs/guide/expand/runtime/renderer.md new file mode 100644 index 000000000..20cc35bae --- /dev/null +++ b/docs/docs/guide/expand/runtime/renderer.md @@ -0,0 +1,349 @@ +--- +title: 使用渲染模块 +sidebar_position: 0 +--- +## 快速使用 +渲染依赖于 schema 和 components。其中 schema 和 components 需要一一对应,schema 中使用到的组件都需要在 components 中进行声明,否则无法正常渲染。 +### 简单示例 + +```jsx +import ReactRenderer from '@alilc/lowcode-react-renderer'; +import ReactDOM from 'react-dom'; +import { Button } from '@alifd/next'; + +const schema = { + componentName: 'Page', + props: {}, + children: [ + { + componentName: 'Button', + props: { + type: 'primary', + style: { + color: '#2077ff' + }, + }, + children: '确定', + }, + ], +}; + +const components = { + Button, +}; + +ReactDOM.render(( + +), document.getElementById('root')); +``` + +- rax-renderer:npm 包替换为 @alilc/lowcode-rax-renderer +#### +### 项目使用示例 +> 设计器 demo:[https://lowcode-engine.cn/demo](https://lowcode-engine.cn/demo) +> 项目代码完整示例:[https://github.com/alibaba/lowcode-demo](https://github.com/alibaba/lowcode-demo) + +**step 1:在设计器中获取组件列表** +```typescript +import { material, project } from '@alilc/lowcode-engine'; +const packages = material.getAssets().packages +``` +**step 2:在设计器中获取当前配置页面的 schema** +```typescript +import { material, project } from '@alilc/lowcode-engine'; + +const schema = project.exportSchema(); +``` + + +**step 3:以某种方式存储 schema 和 packages** +这里用 localStorage 作为存储示例,真实项目中使用数据库或者其他存储方式。 +```typescript +window.localStorage.setItem( + 'projectSchema', + JSON.stringify(project.exportSchema()) +); +const packages = await filterPackages(material.getAssets().packages); +window.localStorage.setItem( + 'packages', + JSON.stringify(packages) +); +``` +**step 4:预览时,获取存储的 schema 和 packages** +```typescript +const packages = JSON.parse(window.localStorage.getItem('packages') || ''); +const projectSchema = JSON.parse(window.localStorage.getItem('projectSchema') || ''); +const { componentsMap: componentsMapArray, componentsTree } = projectSchema; +``` +**step 5:通过整合 schema 和 packages 信息,进行渲染** +```typescript +import ReactDOM from 'react-dom'; +import React, { useState } from 'react'; +import { Loading } from '@alifd/next'; +import { buildComponents, assetBundle, AssetLevel, AssetLoader } from '@alilc/lowcode-utils'; +import ReactRenderer from '@alilc/lowcode-react-renderer'; +import { injectComponents } from '@alilc/lowcode-plugin-inject'; + +const SamplePreview = () => { + const [data, setData] = useState({}); + + async function init() { + // 渲染前置处理,初始化项目 schema 和资产包为渲染模块所需的 schema prop 和 components prop + const packages = JSON.parse(window.localStorage.getItem('packages') || ''); + const projectSchema = JSON.parse(window.localStorage.getItem('projectSchema') || ''); + const { componentsMap: componentsMapArray, componentsTree } = projectSchema; + const componentsMap: any = {}; + componentsMapArray.forEach((component: any) => { + componentsMap[component.componentName] = component; + }); + const schema = componentsTree[0]; + + const libraryMap = {}; + const libraryAsset = []; + packages.forEach(({ package: _package, library, urls, renderUrls }) => { + libraryMap[_package] = library; + if (renderUrls) { + libraryAsset.push(renderUrls); + } else if (urls) { + libraryAsset.push(urls); + } + }); + + const vendors = [assetBundle(libraryAsset, AssetLevel.Library)]; + + const assetLoader = new AssetLoader(); + await assetLoader.load(libraryAsset); + const components = await injectComponents(buildComponents(libraryMap, componentsMap)); + + setData({ + schema, + components, + }); + } + + const { schema, components } = data; + + if (!schema || !components) { + init(); + return ; + } + + return ( +
+ +
+ ); +}; + +ReactDOM.render(, document.getElementById('ice-container')); + +``` +### 国际化示例 +```typescript +class Demo extends PureComponent { + static displayName = 'renderer-demo'; + render() { + return ( +
+ +
+ ); + } +} +``` + +## API + +| 参数 | 说明 | 类型 | 必选 | +| --- | --- | --- | --- | +| schema | 符合[搭建协议](https://lowcode-engine.cn/lowcode)的数据 | Object | 是 | +| components | 组件依赖的实例 | Object | 是 | +| componentsMap | 组件的配置信息 | Object | 否 | +| appHelper | 渲染模块全局上下文 | Object | 否 | +| designMode | 设计模式,可选值:extend、border、preview | String | 否 | +| suspended | 是否挂起 | Boolean | 否 | +| onCompGetRef | 组件 ref 回调(schema, ref)=> {} | Function | 否 | +| onCompGetCtx | 组件 ctx 更新回调 (schema, ctx) => {} | Function | 否 | +| rendererName | 渲染类型,标识当前模块是以什么类型进行渲染的 | string | 否 | +| customCreateElement | 自定义创建 element 的钩子 +(Component, props, children) => {} | Function | 否 | +| notFoundComponent | 当组件找不到时,可以通过这个参数自定义展示文案。 | Component | 否 | +| thisRequiredInJSE | 为 true 的情况下 JSExpression 仅支持通过 this 来访问。假如需要兼容原来的 'state.xxx',则设置为 false,推荐使用 true。 | Boolean | 否 | +| locale | 国际化语言类型 | string | 否 | +| messages | 国际化语言对象 | Object | 否 | + + +### schema + +搭建基础协议数据,渲染模块将基于 schema 中的内容进行实时渲染。 + +### messages +国际化内容,需要配合 locale 使用 +messages 格式示例: +```typescript +{ + 'zh-CN': { + 'hello-world': '你好,世界!', + }, + 'en-US': { + 'hello-world': 'Hello world!', + }, +} +``` + +### locale +当前语言类型 +示例:'zh-CN' | 'en-US' + +### components + +渲染模块渲染页面需要用到的组件依赖的实例,`components` 对象中的 Key 需要和搭建 schema 中的`componentName` 字段对应。 + +### componentsMap + +> 在生产环境下不需要设置。 + + +配置规范参见[《低代码引擎搭建协议规范》](https://lowcode-engine.cn/lowcode),主要在搭建场景中使用,用于提升用户搭建体验。 + +- 属性配置校验:用户可以配置组件特定属性的 `propTypes`,在搭建场景中用户输入的属性值不满足 `propType` 配置时,渲染模块会将当前属性设置为 `undefined`,避免组件抛错导致编辑器崩溃; +- `isContainer` 标记:当组件被设置为容器组件且当前容器组件内没有其他组件时,用户可以通过拖拽方式将组件直接添加到容器组件内部; +- `parentRule` 校验:当用户使用的组件未出现在组件配置的 `parentRule` 组件内部时,渲染模块会使用 `visualDom` 组件进行占位,避免组件抛错的同时在下钻编辑场景也能够不阻塞用户配置,典型的场景如`Step.Item`、`Table.Column`、`Tab.Item` 等等。 + +### appHelper + +appHelper 主要用于设置渲染模块的全局上下文,目前 appHelper 支持设置以下上下文: + +- `utils`:全局公共函数 +- `constants`:全局常量 +- `location`:react-router 的 `location` 实例 +- `history`:react-router 的 `history` 实例 + +设置了 appHelper 以后,上下文会直接挂载到容器组件的 this 上,用户可以在搭建协议中的 function 及变量表达式场景使用上下文,具体使用方式如下所示: +**schema:** + +```javascript +export default { + "componentName": "Page", + "fileName": "test", + "props": {}, + "children": [{ + "componentName": "Div", + "props": {}, + "children": [{ + "componentName": "Text", + "props": { + "text": { + "type": "JSExpression", + "value": "this.location.pathname" + } + } + }, { + "componentName": "Button", + "props": { + "type": "primary", + "style": { + "marginLeft": 10 + }, + "onClick": { + "type": "JSExpression", + "value": "function onClick(e) { this.utils.xxx(this.constants.yyy);}" + } + }, + "children": "click me" + }] + }] +} +``` + +```typescript +import ReactRenderer from '@alilc/lowcode-react-renderer'; +import ReactDOM from 'react-dom'; +import { Button } from '@alifd/next'; +import schema from './schema' + +const components = { + Button, +}; + +ReactDOM.render(( + {} + } + }} + /> +), document.getElementById('root')); +``` +### designMode + +> 在生产环境下不需要设置。 + + +designMode 属性主要在搭建场景中使用,主要有以下作用: + +- 当 `designMode` 改变时,触发当前 schema 中所有组件重新渲染 +- 当 `designMode` 设置为 `design` 时,渲染模块会为 `Dialog`、`Overlay` 等初始状态无 dom 渲染的组件外层包裹一层 div,使其在画布中能够展示边框供用户选中 + +### suspended + +渲染模块是否挂起,当设置为 `true` 时,渲染模块最外层容器的 `shouldComponentUpdate` 将始终返回false,在下钻编辑或者多引擎渲染的场景会用到该参数。 + +### onCompGetRef + +组件 ref 的回调,在搭建场景下编排模块可以根据该回调获取组件实例并实现生命周期注入或者组件 DOM 操作等功能,回调函数主要包含两个参数: + +- `schema`: 当前组件的 schema 模型结构 +- `ref`:当前组件的 ref 实例 + +### onCompGetCtx +组件 ctx 更新的回调,在组件每次 render 渲染周期我们都会为组件构造新的上下文环境,因此该回调函数会在组件每次 render 过程中触发,主要包含两个参数: + +- `schema`:当前组件的 schema 模型结构 +- `ctx`:当前组件的上下文信息,主要包含以下内容: + - `page`:当前页面容器实例 + - `this`: 当前组件所属的容器组件实例 + - `item`/`index`: 循环上下文(属性 key 可以根据 loopArgs 进行定制) + - `form`: 表单上下文 + +### rendererName +渲染类型,标识当前模块是以什么类型进行渲染的 + +- `LowCodeRenderer`: 低代码组件 +- `PageRenderer`: 页面 + +### customCreateElement +自定义创建 element 的钩子,用于在渲染前后对组件进行一些处理,包括但不限于增加 props、删除部分 props。主要包含三个参数: + +- `Component`:要渲染的组件 +- `props`:要渲染的组件的 props +- `children`:要渲染的组件的子元素 + +### thisRequiredInJSE +> 版本 >= 1.0.11 + +默认值:true +为 true 的情况下 JSExpression 仅支持通过 this 来访问。假如需要兼容原来的 'state.xxx',则设置为 false,推荐使用 true。 diff --git a/docs/docs/guide/quickStart/_category_.json b/docs/docs/guide/quickStart/_category_.json new file mode 100644 index 000000000..0a47c9da5 --- /dev/null +++ b/docs/docs/guide/quickStart/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "入门", + "position": 0, + "collapsed": false, + "collapsible": true +} diff --git a/docs/docs/guide/quickStart/demo.md b/docs/docs/guide/quickStart/demo.md new file mode 100644 index 000000000..536c8efab --- /dev/null +++ b/docs/docs/guide/quickStart/demo.md @@ -0,0 +1,47 @@ +--- +title: 试用低代码引擎 Demo +sidebar_position: 1 +--- +# 访问地址 + +低代码引擎的 Demo 可以通过如下永久链接访问到: + +[https://lowcode-engine.cn/demo](https://lowcode-engine.cn/demo) + +> 注意我们会经常更新 demo,所以您可以通过上述链接得到最新版地址。 + + +# 低代码引擎 Demo 功能概览 + +我们可以从 Demo 的项目中看到页面中有很多的区块:
![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643447049972-e324320a-7f97-4e48-bef3-a4c5d2b06517.png#clientId=udea0fe9a-4e7e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1080&id=rGr7U&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2160&originWidth=3840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=518455&status=done&style=none&taskId=u872d1136-0f18-41b3-900d-710e9fc9eea&title=&width=1920)
它主要包含这些功能点:
![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1643447051103-de714f27-ec70-4982-b180-e1ebe444b0fe.png#clientId=udea0fe9a-4e7e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1080&id=lD0YM&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2160&originWidth=3840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=616472&status=done&style=none&taskId=u61de17fa-6df0-43c8-9171-96388fda32e&title=&width=1920) + +- 顶部:操作区 + - 右侧:撤回和重做、保存到本地、重置页面、预览、异步加载资源 +- 左侧:面板与操作区 + - 大纲面板:可以调整页面内的组件树结构 + - 物料面板:可以查找组件,并在此拖动组件到编辑器画布中 + - 源码面板:可以编辑页面级别的 JavaScript 代码和 CSS 配置 + - 提交 Issue:可以给引擎开发提 bug + - Schema 编辑:可以编辑页面的底层数据 + - 中英文切换:可以切换编辑器的语言 +- 中部:可视化页面编辑画布区域 + - 点击组件在右侧面板中能够显示出对应组件的属性配置选项 + - 拖拽修改组件的排列顺序 + - 将组件拖拽到容器类型的组件中 + - 复制组件:点击组件右上角的复制按钮 + - 删除组件:点击组件右上角的 X 或者直接使用 `Delete` 键 +- 右侧:组件级别配置 + - 选中的组件:从页面开始一直到当前选中的组件位置,点击对应的名称可以切换到对应的组件上 + - 选中组件的配置:当前组件的大类目选项,根据组件类型不同,包含如下子类目: + - 属性:组件的基础属性值设置 + - 样式:组件的样式配置 + - 事件:绑定组件对外暴露的事件 + - 高级:循环、条件渲染与 key 设置 + +# 深入使用低代码引擎 Demo + +我们在低代码引擎 Demo 中直接内置了产品使用文档,对常见场景中的使用进行了向导,它的入口如下: + +![image.png](https://cdn.nlark.com/yuque/0/2022/png/242652/1647163471895-a12d0f5d-e09e-462d-bd0b-b633c64afb15.png#clientId=uecc3752b-3539-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=825&id=u86d6fa24&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1650&originWidth=3070&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1040703&status=done&style=none&taskId=u54aeddda-78e0-4259-b184-d06e2dba10b&title=&width=1535) + +如果暂时没有看到对应的产品使用文档,可以通过此永久链接直接访问:[https://www.yuque.com/lce/usage](https://www.yuque.com/lce/usage) diff --git a/docs/docs/guide/quickStart/intro.md b/docs/docs/guide/quickStart/intro.md new file mode 100644 index 000000000..67750b2a9 --- /dev/null +++ b/docs/docs/guide/quickStart/intro.md @@ -0,0 +1,48 @@ +--- +title: 简介 +sidebar_position: 0 +--- + +# 阿里低代码引擎简介 + +## 低代码介绍 + +零代码、低代码的概念在整个全球行业内已经流行了很长一段时间。通常意义上的低代码定义会有三个关键点: + +1. 一个用于生产软件的可视化编辑器 +2. 中间包含了一些用于组装的物料,可以通过编排、组合和配置它们以生成丰富的功能或表现 +3. 最后的实施结果是成本降低 + +通常情况下低代码平台会具备以下的几个能力: + +- **可视化页面搭建**,通过简单的拖拽完成应用页面开发,对前端技能没有要求或不需要特别专业的了解; +- **可视化模型设计**,与业务相关的数据存储变得更容易理解,甚至大多数简单场景可以做到表单即模型,模型字段的类型更加业务化; +- **可视化流程设计**,不管是业务流程还是审批流程,都可以通过简单的点线连接来进行配置; +- **可视化报表及数据分析**,BI数据分析能力成为标配,随时随地通过拖拽选择来定义自定义分析报表; +- **可视化服务与数据开放、集成**,具备与其他系统互联互通的配置; +- **权限、角色设置标准化和业务化**,通过策略规则配置来将数据、操作的权限进行精细化管理; +- **无需关心服务器、数据库等底层运维、计算设施设备、网络等等复杂技术概念**,具备安全、性能的统一解决方案,开发者只需要专注于业务本身; + +有了上面这些,你会发现即使是个技术小白,只要你了解业务,就能不受束缚的完成大多数业务应用的搭建。但低代码本身也不仅仅是为技术小白准备的。在实践中,低代码因为通过组件化、模块化的思路让业务的抽象更加容易,而且在扩展及配置化上带来了更加新鲜的模式探索,技术人员的架构设计成本和实施成本也就降了很多。 + +市面上常见的低代码产品[可以看 Golden 的梳理](https://golden.com/wiki/No-code_%2F_low-code_development-NMGMEA6)。 + +## 低代码引擎介绍 + +**低代码引擎是一款为低代码平台开发者提供的,具备强大定制扩展能力的低代码设计器研发框架。** + +下面简单描述定义中的子部分: + +**低代码设计器** +现如今低代码平台越来越多,而每一个低代码平台中都会有的一个能力就是搭建和配置页面、模块的页面,这个页面我们称为设计器。例如,下图是中后台低代码平台的设计器。 +![image.png](https://img.alicdn.com/imgextra/i4/O1CN01LunuQh23b5NtP8k86_!!6000000007273-2-tps-1682-969.png?originHeight=1914&originWidth=3838&originalType=binary&ratio=1&rotation=0&showTitle=false&size=538736&status=done&style=stroke&taskId=u9a19d4d1-4d87-4b4e-b7cc-3aedfb00aaa&title=&width=1080) +设计器承载着低代码平台的核心功能,包括入料、编排、组件配置、画布渲染等等。由于其功能多,打磨精细难,也是低代码平台建设最耗时的地方。 + +**定制扩展能力** + +什么是扩展能力呢,一方面我们可以快速拥有一份标准的低代码设计器,另外一方面如果有业务独特的功能需要,我们可以不用看它的源码、不用关心其实现,可以使用 API、插件等方式快速完成能力的开发。 +而低代码引擎对于设计器的扩展能力支持基本上覆盖了低代码设计器的所有功能点。下图是针对标准的设计器提供了扩展功能的区域。 +![](https://cdn.nlark.com/yuque/0/2022/png/242652/1643446752531-8b1493d4-ea8a-463b-9631-6bb4fc681719.png#clientId=u2b839b63-1827-4&crop=0&crop=0&crop=1&crop=1&from=drop&height=539&id=ucff2881c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1914&originWidth=3838&originalType=binary&ratio=1&rotation=0&showTitle=false&size=538736&status=done&style=stroke&taskId=u9a19d4d1-4d87-4b4e-b7cc-3aedfb00aaa&title=&width=1080) +**低代码设计器研发框架** + +低代码引擎的核心是设计器,通过扩展、周边生态等可以产出各式各样的设计器。它不是一套可以适合所有人的低代码平台,而是帮助低代码平台的开发者,快速生产低代码平台的工具。 diff --git a/docs/docs/guide/quickStart/start.md b/docs/docs/guide/quickStart/start.md new file mode 100644 index 000000000..001e4e789 --- /dev/null +++ b/docs/docs/guide/quickStart/start.md @@ -0,0 +1,296 @@ +--- +sidebar_position: 2 +title: 快速开始 +--- +# 前置知识 +我们假定你已经对 HTML 和 JavaScript 都比较熟悉了。即便你之前使用其他编程语言,你也可以跟上这篇教程的。除此之外,我们假定你也已经熟悉了一些编程的概念,例如,函数、对象、数组,以及 class 的一些内容。
如果你想回顾一下 JavaScript,你可以阅读[这篇教程](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/A_re-introduction_to_JavaScript)。注意,我们也用到了一些 ES6(较新的 JavaScript 版本)的特性。在这篇教程里,我们主要使用了[箭头函数(arrow functions)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions)、[class](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes)、[let](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let) 语句和 [const](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/const) 语句。你可以使用 [Babel REPL](https://babeljs.io/repl/#?presets=react&code_lz=MYewdgzgLgBApgGzgWzmWBeGAeAFgRgD4AJRBEAGhgHcQAnBAEwEJsB6AwgbgChRJY_KAEMAlmDh0YWRiGABXVOgB0AczhQAokiVQAQgE8AkowAUAcjogQUcwEpeAJTjDgUACIB5ALLK6aRklTRBQ0KCohMQk6Bx4gA) 在线预览 ES6 的编译结果。 + +# 环境准备 +## WSL(Window 电脑) +Window 环境需要使用 WSL 在 windows 下进行低代码引擎相关的开发。安装教程 ➡️ [WSL 安装教程](https://docs.microsoft.com/zh-cn/windows/wsl/install)。
**对于 Window 环境来说,之后所有需要执行命令的操作都是在 WSL 终端执行的。** +## Node +node 版本推荐 14.17.0。 + +### 查看 Node 版本 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660653856191-128d8e3f-9636-4b73-94ab-c03cf6965365.png#clientId=ubcf63daf-5747-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=35&id=u44a9af04&margin=%5Bobject%20Object%5D&name=image.png&originHeight=70&originWidth=238&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11948&status=done&style=none&taskId=udb616117-a27c-409d-9e1c-1b89931a714&title=&width=119) + +### 变更 node 版本 +可以安装 [n](https://www.npmjs.com/package/n) 来管理和变更 node 版本。 + +#### 安装 +```json +npm install -g n +``` + +#### 变更 node 版本 +```json +n 14.17.0 +``` + +## React +低代码引擎的扩展能力都是基于 React 来研发的,在继续阅读之前最好有一定的 React 基础,React 学习教程 ➡️ [React 快速开始教程](https://zh-hans.reactjs.org/docs/getting-started.html)。 + +## 下载 Demo +可以前往 github(https://github.com/alibaba/lowcode-demo)将 DEMO 下载到本地。 + +### git clone +#### HTTPS +需要使用到 git 工具 +```json +git clone https://github.com/alibaba/lowcode-demo.git +``` +#### SSH +需要配置 SSH key,如果没有配置可以 +```json +git clone git@github.com:alibaba/lowcode-demo.git +``` + +### 下载 Zip 包 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660653725650-ab734ba4-64a7-4801-9d2f-5c496879054f.png#clientId=ubcf63daf-5747-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=897&id=uc1b07458&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1794&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1306258&status=done&style=stroke&taskId=ubaa4eb12-0e87-464e-b3da-306ed9685b7&title=&width=1792) + +## 安装依赖 +在 lowcode-demo 目录下执行: +```json +npm install +``` + +## 启动 demo +在 lowcode-demo 目录下执行: +```json +npm run start +``` + +之后就可以通过 [http://localhost:5556/](http://localhost:5556/) 来访问我们的 DEMO 了。 +# 认识 Demo +我们的 Demo 是一个**低代码平台的设计器**。它是一个低代码平台中最重要的一环,用户可以在这里通过拖拽、配置、写代码等等来完成一个页面的开发。由于用户的人群不同、场景不同、诉求不同等等,这个页面的功能就会有所差异。
这里记住**设计器**这个词,它描述的就是下面的这个页面,后面我们会经常看到它。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660654189055-45fd0c7e-75cb-4072-a4d4-7cf244405c7d.png#clientId=ubcf63daf-5747-4&crop=0&crop=0.008&crop=1&crop=1&from=paste&height=904&id=LBi2j&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1808&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=763122&status=done&style=stroke&taskId=u82753ad5-3b5d-43b2-b309-a99c2a7bb24&title=&width=1792) + +## 场景介绍 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660654738730-490fc94a-8b42-4c48-b21e-4c0694416b07.png#clientId=ubcf63daf-5747-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=903&id=ub700edc2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1806&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=802013&status=done&style=stroke&taskId=u317bff98-636d-402a-98f2-f9e0b08293b&title=&width=1792) + +Demo 根据**不同的设计器所需要的物料不同**,分为了下面的 8 个场景: + +- 综合场景 +- 基础 fusion 组件 +- 基础 fusion 组件 + 单自定义组件 +- 基础 antd 组件 +- 自定义初始化引擎 +- 扩展节点操作项 +- 基于next实现的高级表单低代码物料 +- antd 高级组件 + formily 表单组件 + +可以点开不同的场景,看看他们使用的物料。 + +## 目录介绍 +仓库下的 src/scenarios 目录就对应刚刚介绍的场景。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660655237007-fddd8534-d4ed-4a25-ba2f-f335f8ac3c36.png#clientId=ubcf63daf-5747-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=348&id=ubf68019d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=696&originWidth=696&originalType=binary&ratio=1&rotation=0&showTitle=false&size=148777&status=done&style=stroke&taskId=u68648c51-7648-494e-bd41-4f29fb144f9&title=&width=348) + +不同场景的目录结构实际上都是类似的,这里我们主要介绍一下综合场景的目录结构即可。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660655399364-b40d206a-977d-4000-9be1-681823f8a995.png#clientId=ubcf63daf-5747-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1010&id=ub727a7fa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2020&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1373541&status=done&style=stroke&taskId=ue55dc86f-375d-4c7f-a63f-d5208683035&title=&width=1792)
综合场景目录下只有一个文件,这个文件做了几个事情: + +- 通过 plugins.register 注册「切换场景」的插件,也就是上面介绍的切换场景的功能。 +- 通过 registerPlugins 注册更多的插件: + - ManualPlugin:增加弹出「低代码产品使用文档」按钮 + - Inject:支持调试功能 + - registerRefProp:支持给每个组件注入 ref + - ... +- 通过 init 初始化低代码设计器 + +做了这些事情之后,我们的低代码设计器就已经有了基本的能力了。也就是最开始我们看到的这样。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660654189055-45fd0c7e-75cb-4072-a4d4-7cf244405c7d.png#clientId=ubcf63daf-5747-4&crop=0&crop=0.008&crop=1&crop=1&from=paste&height=904&id=MZdfk&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1808&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=763122&status=done&style=stroke&taskId=u82753ad5-3b5d-43b2-b309-a99c2a7bb24&title=&width=1792) + +接下来我们就根据我们自己的诉求通过对设计器进行扩展,改动成我们需要的设计器功能。 +# 开发一个插件 +## 方式1:在 DEMO 中直接新增插件 +![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660656064139-8da57c37-7e0b-4e8d-9f2d-8ea86d5af134.png#clientId=ubcf63daf-5747-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=280&id=uc7f75c37&margin=%5Bobject%20Object%5D&name=image.png&originHeight=560&originWidth=820&originalType=binary&ratio=1&rotation=0&showTitle=false&size=125690&status=done&style=stroke&taskId=u53c07118-a2ca-4a77-a27f-3b2b20085ac&title=&width=410) + +可以在 demo/sample-plugins 直接新增插件,这里我新增的插件目录是 plugin-demo。并且新增了 index.tsx 文件,将下面的代码粘贴到 index.tsx 中。 +```javascript +import * as React from 'react'; +import { ILowCodePluginContext } from '@alilc/lowcode-engine'; + +const LowcodePluginPluginDemo = (ctx: ILowCodePluginContext) => { + return { + // 插件对外暴露的数据和方法 + exports() { + return { + data: '你可以把插件的数据这样对外暴露', + func: () => { + console.log('方法也是一样'); + }, + } + }, + // 插件的初始化函数,在引擎初始化之后会立刻调用 + init() { + // 你可以拿到其他插件暴露的方法和属性 + // const { data, func } = ctx.plugins.pluginA; + // func(); + + // console.log(options.name); + + // 往引擎增加面板 + ctx.skeleton.add({ + area: 'leftArea', + name: 'LowcodePluginPluginDemoPane', + type: 'PanelDock', + props: { + description: 'Demo', + }, + content:
这是一个 Demo 面板
, + }); + + ctx.logger.log('打个日志'); + }, + }; +}; + +// 插件名,注册环境下唯一 +LowcodePluginPluginDemo.pluginName = 'LowcodePluginPluginDemo'; +LowcodePluginPluginDemo.meta = { + // 依赖的插件(插件名数组) + dependencies: [], + engines: { + lowcodeEngine: '^1.0.0', // 插件需要配合 ^1.0.0 的引擎才可运行 + }, +} + +export default LowcodePluginPluginDemo; +``` + +在 src/scenarios/index/index.ts 中新增下面代码
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660656497051-49e08633-2d78-428c-becc-c282905cdb90.png#clientId=ubcf63daf-5747-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1010&id=u426edc7b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2020&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1458910&status=done&style=stroke&taskId=uf1e42399-caf7-4d82-a797-698fa730486&title=&width=1792) + +这样在我们的设计器中就新增了一个 Demo 面板。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660656566260-86dfed37-60d0-45ca-967e-5df8ea7a34d0.png#clientId=ubcf63daf-5747-4&crop=0&crop=0.0053&crop=1&crop=1&from=paste&height=903&id=u17565cd3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1806&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=734227&status=done&style=stroke&taskId=u5dbe00f3-447b-451e-ac48-9b02281afc3&title=&width=1792) +## 方式2:在新的仓库下开发插件 +初始化 +```json +npm init @alilc/element your-plugin-name +``` + +选择设计器插件(plugin)
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660702297326-ccfe60f9-ee22-4a24-a293-26351d107663.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=107&id=ub2bf5248&margin=%5Bobject%20Object%5D&name=image.png&originHeight=214&originWidth=730&originalType=binary&ratio=1&rotation=0&showTitle=false&size=82091&status=done&style=stroke&taskId=u82628265-73f0-4d57-b4ba-1b18600a1f0&title=&width=365) + +根据操作完善信息
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660702439529-867a893f-f27a-4e48-8a5a-ee45aa97e355.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=109&id=uc9b09fec&margin=%5Bobject%20Object%5D&name=image.png&originHeight=218&originWidth=866&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102705&status=done&style=stroke&taskId=ue4a95f21-43d3-4da8-9c0e-b21dfe239bf&title=&width=433) + +插件项目就初始化完成了
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660702464438-3d7e07eb-53c7-417c-9e6d-06fdf9acfb86.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=280&id=u0ee65b4e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2020&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1197702&status=done&style=stroke&taskId=u5f9fdae3-1adc-4b02-969c-5c43c3d4c9c&title=&width=496) + +在插件项目下安装依赖 +```json +npm install +``` + +启动项目 +```json +npm run start +``` + +调试项目
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660705712773-f2446689-2b5f-42e7-9e85-30857270dfbb.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=346&id=u448649c5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1936&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=757713&status=done&style=stroke&taskId=u0b617456-826e-4993-951e-303da417172&title=&width=641) + +在 Demo 中调试项目
在 build.json 下面新增 "inject": true,就可以在 [https://lowcode-engine.cn/demo/index.html?debug](https://lowcode-engine.cn/demo/index.html?debug) 页面下进行调试了。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660705860117-5a11a5fa-9215-4b94-84b7-497899cafe10.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1010&id=u3a36c42f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2020&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=887101&status=done&style=stroke&taskId=u747bc337-5212-4a8b-a88f-127c53ea621&title=&width=1792) +# 开发一个自定义物料 +## 初始化物料 +```json +npm init @alilc/element your-material-demo +``` +选择组件/物料栏
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660706045985-db73ca55-925a-446b-ace4-b59fa1e18469.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=104&id=kuWIf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=208&originWidth=824&originalType=binary&ratio=1&rotation=0&showTitle=false&size=88910&status=done&style=stroke&taskId=u92f5fa65-386a-4f52-a093-bcbbebdc2d7&title=&width=412) + +配置其他信息
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660706116845-9b3b938f-c132-426b-81bd-d49283ebf9e8.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=124&id=u941c9808&margin=%5Bobject%20Object%5D&name=image.png&originHeight=248&originWidth=800&originalType=binary&ratio=1&rotation=0&showTitle=false&size=111864&status=done&style=stroke&taskId=ue4ff4dab-3a53-4811-bf70-7fa6fc0c8b6&title=&width=400) + +这样我们就初始化好了一个 React 物料。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660706173968-3e5db25a-e08d-4852-90c9-ffaa0968fd62.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1010&id=u854b37cc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2020&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1080400&status=done&style=stroke&taskId=u10e21350-23d4-4d8f-8c16-0c5a221fc2e&title=&width=1792) +## 启动并调试物料 +### 安装依赖 +```json +npm i +``` +### 启动 +```json +npm run lowcode:dev +``` + +我们就可以通过 [http://localhost:3333/](http://localhost:3333/) 看到我们的研发的物料了。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660706484894-73798e35-1e58-4ffe-bb3c-bfba7b014433.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=895&id=zVMiy&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1790&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=678753&status=done&style=stroke&taskId=u241f50c0-1a43-4854-8443-7e972f15623&title=&width=1792) +### 在 Demo 中调试 +```json +npm i @alilc/build-plugin-alt +``` + +修改 build.lowcode.js
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660706733149-0170b2fb-88de-40e3-8204-f510d7e42f77.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=311&id=u8a1a8bea&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1046&originWidth=1388&originalType=binary&ratio=1&rotation=0&showTitle=false&size=291155&status=done&style=stroke&taskId=u61ede7d2-f92d-43b9-8582-a2362a9ea14&title=&width=413) + +如图,新增如下代码 +```json +[ + '@alilc/build-plugin-alt', + { + type: 'component', + inject: true, + library, + // 配置要打开的页面,在注入调试模式下,不配置此项的话不会打开浏览器 + // 支持直接使用官方 demo 项目:https://lowcode-engine.cn/demo/index.html + openUrl: 'https://lowcode-engine.cn/demo/index.html?debug', + }, +], +``` + +我们重新启动项目,就可以在 Demo 中找到我们的自定义组件。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660706823666-ca28a08d-6241-4112-bc78-b9078c81fb75.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=906&id=u31bdc31a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1812&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=788013&status=done&style=stroke&taskId=uaa4789a2-452f-4b04-8894-759989e4568&title=&width=1792) + +## 发布 +首先进行构建 +```json +npm run lowcode:build +``` + +发布组件 +```json +npm publish +``` + +这里我发布的组件是 [my-material-demo](https://www.npmjs.com/package/my-material-demo)。在发布之后我们就会有两个重要的文件: + +- 低代码描述:[https://unpkg.com/my-material-demo@0.1.0/build/lowcode/meta.js](https://unpkg.com/my-material-demo@0.1.0/build/lowcode/meta.js) +- 组件代码:[https://unpkg.com/my-material-demo@0.1.0/build/lowcode/render/default/view.js](https://unpkg.com/my-material-demo@0.1.0/build/lowcode/render/default/view.js) + +我们也可以从 [https://unpkg.com/my-material-demo@0.1.0/build/lowcode/assets-prod.json](https://unpkg.com/my-material-demo@0.1.0/build/lowcode/assets-prod.json) 找到我们的资产包描述。 +```json +{ + "packages": [ + { + "package": "my-material-demo", + "version": "0.1.0", + "library": "BizComp", + "urls": [ + "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/render/default/view.js", + "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/render/default/view.css" + ], + "editUrls": [ + "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/view.js", + "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/view.css" + ], + "advancedUrls": { + "default": [ + "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/render/default/view.js", + "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/render/default/view.css" + ] + }, + "advancedEditUrls": {} + } + ], + "components": [ + { + "exportName": "MyMaterialDemoMeta", + "npm": { + "package": "my-material-demo", + "version": "0.1.0" + }, + "url": "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/meta.js", + "urls": { + "default": "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/meta.js" + }, + "advancedUrls": { + "default": [ + "https://unpkg.com/my-material-demo@0.1.0/build/lowcode/meta.js" + ] + } + } + ], +} +``` + +## 使用 +我们将刚刚发布的组件的 assets-prod.json 的内容放到 demo 的 src/universal/assets.json 中。
*最好放到最后,防止因为资源加载顺序问题导致出现报错。
如图,新增 packages 配置
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660707789785-ea75a399-6845-45a8-8c53-08402749c9a8.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1010&id=uf0f75c17&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2020&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1327766&status=done&style=stroke&taskId=ub053d846-69fd-4a55-8e9e-b41d1e06e47&title=&width=1792)
如图,新增 components 配置
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2622706/1660707811725-a0e36f48-910d-45b5-b162-3aa4e2f87e9b.png#clientId=udb19495f-ea47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1010&id=uf8c9506f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=2020&originWidth=3584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1222080&status=done&style=stroke&taskId=u10147f1f-27ed-4cec-bfc9-2cb3d61d6a2&title=&width=1792) + +这时候再启动 DEMO 项目,就会有新的低代码物料了。接下来就按照你们的需求,继续扩展物料吧。 +# 总结 +这里只是简单的介绍了一些低代码引擎的基础能力,带大家简单的对低代码 DEMO 进行扩展,定制一些新的功能。低代码引擎的能力还有很多很多,可以继续去探索更多的功能。 diff --git a/docs/docs/participate/config.md b/docs/docs/participate/config.md new file mode 100644 index 000000000..4e032ad1b --- /dev/null +++ b/docs/docs/participate/config.md @@ -0,0 +1,80 @@ +--- +title: 引擎的工程化配置 +sidebar_position: 3 +--- +目前引擎体系共包含 3 个 js 文件,即: +```html + + + + +``` + +工程化配置我们进行了统一,具体如下: +```shell +{ + "entry": { + ... + }, + "library": "...", + "libraryTarget": "umd", + "externals": { + "react": "var window.React", + "react-dom": "var window.ReactDOM", + "prop-types": "var window.PropTypes", + "rax": "var window.Rax", + "@alilc/lowcode-engine": "var window.AliLowCodeEngine", + "@alilc/lowcode-engine-ext": "var window.AliLowCodeEngineExt", + "moment": "var moment", + "lodash": "var _", + "@alifd/next": "var Next" + }, + "polyfill": false, + "outputDir": "dist", + "vendor": false, + "ignoreHtmlTemplate": true, + "sourceMap": true, + "plugins": [ + "build-plugin-react-app", + ["build-plugin-fusion", { + }], + ["build-plugin-moment-locales", { + "locales": ["zh-cn"] + }], + "./build.plugin.js" + ] +} + +``` +总结一下,有 2 点: + +1. **都不包含 polyfill,**需要应用级别单独引入 polyfill,推荐动态 polyfill +2. **都不包含 lodash / moment / next** + + +#### 前置依赖资源: +```html + + + + + + +``` + + +#### 所有资源: +```html + + + + + + + + + + + + +``` diff --git a/docs/docs/participate/flow.md b/docs/docs/participate/flow.md new file mode 100644 index 000000000..d80e779ee --- /dev/null +++ b/docs/docs/participate/flow.md @@ -0,0 +1,96 @@ +--- +title: 关于引擎的研发协作流程 +sidebar_position: 2 +--- +## 代码风格 +引擎项目配置了 eslint 和 stylelint,在每次 git commit 前都会检查代码风格,假如有报错,请修改后再提交。(**严禁 -n 提交,-n 也逃脱不了 github workflow 的 lint 检查,放弃吧,骚年~**) + +## 测试机制 +每次提交代码前,务必本地跑一次单元测试,通过后再提交 MR。 +假如涉及新的功能,需要**补充相应的单元测试,**目前引擎核心模块的单测覆盖率都在 80%+,假如降低了覆盖率,将会不予以通过。 + +跑单测流程: + +1. 项目根目录下执行 npm run build +2. 只改了一个包,比如 designer,则在 designer 目录下,执行 npm test +3. (or)改了多个包,则在根目录下执行 npm test +## commit 风格 +几点要求: + +1. commit message 格式遵循 [ConvensionalCommits](https://www.conventionalcommits.org/en/v1.0.0/#summary) +![image.png](https://cdn.nlark.com/yuque/0/2022/png/110793/1645066644352-4de1c64c-bff6-4482-90d1-1fb610aa91f2.png#averageHue=%23eceef0&clientId=u6dcee4f0-35df-4&crop=0&crop=0&crop=1&crop=1&height=297&id=CfpQy&margin=%5Bobject%20Object%5D&name=image.png&originHeight=594&originWidth=2070&originalType=binary&ratio=1&rotation=0&showTitle=false&size=341605&status=done&style=none&taskId=u4499b752-5e24-42f6-9186-280fd5a51aa&title=&width=1035) +2. 请按照一个 bugfix / feature 对应一个 commit,假如不是,请 rebase 后再提交 MR,不要一堆无用的、试验性的 commit + +好处:从引擎的整体 commit 历史来看,会很清晰,**每个 commit 完成一件确定的事,changelog 也能自动生成。**另外,假如因为某个 commit 导致了 bug,也很容易通过 rebase drop 等方式快速修复。 +## 引擎发布机制 +日常迭代先从 develop 拉分支,然后自测、单测通过后,提交 MR 到 develop 分支,由发布负责人基于 develop 拉 release/1.0.z 分支~ + +### 分支用途 + +- main 分支,最稳定的分支,跟 npm latest 包的内容保持一致 +- develop 分支,开发分支,拥有最新的、已经验证过的 feature / bugfix,Pull Request 的**目标合入分支** +- release 分支 + - 正式发布分支,命名规则为 release/x.y.z,一般从 develop 拉出来进行发布,x.y.z 为待发布的版本号 + - beta 发布分支,命名规则为 release/x.y.z-beta(\.\d+)?,可以快速验证修改,发布 npm beta 版本。 +验证通过后,因为 beta 发布分支上会存在无用的 commit(比如 lerna 修改 package.json 这种),所以不直接 PR 到 develop,而是从 develop 拉分支,从 beta 发布分支 cherry pick 有用的 commit 到新分支,然后 PR 到 develop。 + +### 发布步骤 +> **发布需要权限,如果提 PR 之后着急发布可以**[**加入贡献者交流群**](https://www.yuque.com/lce/doc/pctr1f#d5WKy)**。** + +如果是发布正式版本,步骤如下(以发布 1.0.0 版本为例): + +1. git checkout develop +2. git checkout -b release/1.0.0 +3. npm run build +4. npm run pub +5. tnpm run sync(此步骤将发布在 npm 源的包同步到阿里内网源,因为 alifd cdn 将依赖内网 npm 源) +6. 更新[发布日志](https://github.com/alibaba/lowcode-engine/releases) +7. 合并 release/x.x.x 到 main 分支 +8. 合并 main 分支到 develop 分支 + +如果是发布beta 版本,步骤如下(以发布 1.0.1 版本为例): + +1. git checkout develop +2. git checkout -b release/1.0.1-beta +3. npm run build +4. npm run pub:prepatch(将 lerna 版本号从 1.0.0 改到 1.0.1-beta.0,若是从 1.0.1-beta.0 改到 1.0.1-beta.1,则用 npm run pub:prerelease) +5. tnpm run sync + +注:在 release/1.0.1-beta 上可以直接提交,以便快速测试和验证,不过如何合入 develop,参考 [分支用途](#uem7W) 一节说明。 + +### 发布周期 +**发布周期暂时不固定,按需发布~** + +## 物料发布机制 + + +## DEMO 发布机制 +**修改版本号** +手动修改 package.json 的版本号 + +**build** +```typescript +npm run build +``` + +**publish** +```typescript +npm run pub +``` +需要权限 + +*发布 beta 版本 +```typescript +npm publish --tag beta +``` + +**同步** +```typescript +tnpm run sync +``` +缺少这一步相关的 cdn 地址可能 404 + + + +**官网生效** +需要在通过阿里内部系统更新 demo 版本 diff --git a/docs/docs/participate/index.md b/docs/docs/participate/index.md new file mode 100644 index 000000000..7a652e59a --- /dev/null +++ b/docs/docs/participate/index.md @@ -0,0 +1,26 @@ +--- +title: 低代码引擎贡献者指南 +sidebar_position: 0 +--- +### 首个 Pull Request +在写第一个 Pull Request?你可以从这一系列视频中学习怎么做: +[How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) +为了使你能够快速上手和熟悉贡献流程,我们这里有个列表 [good first issues](https://github.com/alibaba/lowcode-engine/issues?q=is:open+is:issue+label:%22good+first+issue%22),里面有相对没那么笼统的漏洞,从这开始是个不错的选择。 +如果你想解决一个 issue,请确定检查了该 issue 下的评论以防有人正在处理它。如果目前没人在处理该 issue,那么请留下评论去表明你想处理该 issue 以便其他人不会意外重复你的工作。 +如果有人留言表明要处理该 issue 但是超过两周没有跟进,你可以接手工作,不过也应该留言说明。 + +### 提交 Pull Request +核心团队时刻关注 pull requests,我们会先评审你的 pull request,之后可能会合并,可能会要求再次更改,也可能会关闭该 pull request 并对此作出解释。我们会尽力全程更新和反馈。 + +**提交 pull request 前**,请确保完成以下步骤: + +1. Fork [此仓库](https://github.com/alibaba/lowcode-engine),从 main 创建分支。 +2. 在仓库根目录下执行 yarn。 +3. 如果你修复了 bug 或者添加了代码,而这些内容需要测试,请添加测试! +4. 确保通过测试套件(yarn test)。 +5. 请签订贡献者许可证协议(Contributor License Agreement)。 + +### 核心贡献者交流 +如果你想长期参与到项目维护中,我们提供了一个核心贡献者交流群。 +1.可以通过[填写问卷](https://survey.taobao.com/apps/zhiliao/4YEtu9gHF)的方式,参与到其中。 +2.填写问卷后加微信号 wxidvlalalalal,说明一下 diff --git a/docs/docs/participate/prepare.md b/docs/docs/participate/prepare.md new file mode 100644 index 000000000..f0fbeb394 --- /dev/null +++ b/docs/docs/participate/prepare.md @@ -0,0 +1,61 @@ +--- +title: 如何配置引擎调试环境 +sidebar_position: 1 +--- +低代码引擎的核心仓库是不包含任何物料、插件、setter 的,它本身用于生成低代码引擎的主包。 +如果您需要对低代码的主包进行开发和调试,需要用到本文里介绍的知识。 +如果您需要对低代码编辑器进行定制,您可能只需要 clone [lowcode-demo 项目](https://github.com/alibaba/lowcode-demo)并进行修改,参考“[配置低代码扩展点](https://www.yuque.com/lce/doc/srdo3s#oPhoE)”章节。 + +> 前置条件: +> node >= 14 + +### 1. 拉取代码,启动项目 +```bash +git clone git@github.com:alibaba/lowcode-engine.git +cd lowcode-engine +npm install && npm run setup +npm start + + +git clone git@github.com:alibaba/lowcode-demo.git +cd lowcode-demo +npm install && npm start +``` + +### 2. 配置资源代理 +本质上是将 demo 页面引入的几个 js/css 代理到 engine 项目,可以使用趁手的代理工具,这里推荐 [XSwitch](https://chrome.google.com/webstore/detail/xswitch/idkjhjggpffolpidfkikidcokdkdaogg?hl=en-US)。 + +本地开发代理规则如下: +```json +{ + "proxy": [ + [ + "https://alifd.alicdn.com/npm/@alilc/lowcode-engine@(.*)/dist/js/engine-core.js", + "http://localhost:5555/js/engine-core.js" + ], + [ + "https://alifd.alicdn.com/npm/@alilc/lowcode-engine@(.*)/dist/css/engine-core.css", + "http://localhost:5555/css/engine-core.css" + ], + [ + "https?://alifd.alicdn.com/npm/@alilc/lowcode-engine@(.*)/dist/js/react-simulator-renderer.js", + "http://localhost:5555/js/react-simulator-renderer.js" + ], + [ + "https?://alifd.alicdn.com/npm/@alilc/lowcode-engine@(.*)/dist/css/react-simulator-renderer.css", + "http://localhost:5555/css/react-simulator-renderer.css" + ], + [ + "https?://alifd.alicdn.com/npm/@alilc/lowcode-engine@(.*)/dist/js/rax-simulator-renderer.js", + "http://localhost:5555/js/rax-simulator-renderer.js" + ], + [ + "https?://alifd.alicdn.com/npm/@alilc/lowcode-engine@(.*)/dist/css/rax-simulator-renderer.css", + "http://localhost:5555/css/rax-simulator-renderer.css" + ], + ] +} +``` + +### 3. 本地调试物料/插件/设置器 +[详见](https://www.yuque.com/lce/doc/ulvlkz#Ioc87) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js new file mode 100644 index 000000000..68728ca21 --- /dev/null +++ b/docs/docusaurus.config.js @@ -0,0 +1,132 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +// Note: type annotations allow type checking and IDEs autocompletion + +const lightCodeTheme = require('prism-react-renderer/themes/github'); +const darkCodeTheme = require('prism-react-renderer/themes/dracula'); +const navbar = require('./config/navbar'); + +/** @type {import('@docusaurus/types').Config} */ +const config = { + title: 'Low-Code Engine', + tagline: 'Low-Code Engine is awesome!', + url: 'https://lowcode-engine.cn', + baseUrl: '/site/', + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', + favicon: + 'https://img.alicdn.com/imgextra/i2/O1CN01TNJDDg20pKniPOkN4_!!6000000006898-2-tps-66-78.png', + + organizationName: 'alibaba', // Usually your GitHub org/user name. + projectName: 'lowcode-engine', // Usually your repo name. + + i18n: { + defaultLocale: 'zh-Hans', + locales: ['zh-Hans'], + }, + + plugins: [ + [ + '@docusaurus/plugin-content-docs', + { + id: 'community', + path: 'community', + routeBasePath: 'community', + sidebarPath: require.resolve('./config/sidebarsCommunity.js'), + }, + ], + ], + + presets: [ + [ + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + sidebarPath: require.resolve('./config/sidebars.js'), + // lastVersion: 'current', + editUrl: + 'https://github.com/alibaba/lowcode-engine/tree/develop/docs/', + }, + theme: { + customCss: require.resolve('./src/css/custom.css'), + }, + }), + ], + ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + docs: { + sidebar: { + hideable: true, + }, + }, + navbar, + footer: { + // style: 'dark', + links: [ + {}, + { + title: '低代码引擎协议栈', + items: [ + { + label: '《低代码引擎搭建协议规范》', + href: 'https://lowcode-engine.cn/lowcode', + }, + { + label: '《低代码引擎物料协议规范》', + href: 'https://lowcode-engine.cn/material', + }, + { + label: '《低代码引擎资产包协议规范》', + href: 'https://lowcode-engine.cn/assets', + }, + ], + }, + {}, + { + title: '案例产品', + items: [ + { + label: '钉钉宜搭', + href: 'https://www.aliwork.com/', + }, + { + label: 'Parts 造物', + href: 'https://parts.lowcode-engine.cn/', + }, + { + label: 'UIPaaS 低代码平台孵化器', + href: 'https://uipaas.net', + }, + ], + }, + {}, + ], + copyright: `Copyright © ${new Date().getFullYear()} 阿里巴巴集团, Inc. Built with Docusaurus.`, + }, + // 主题切换 + prism: { + theme: lightCodeTheme, + darkTheme: darkCodeTheme, + }, + // 语雀文档导出的图片,会进行 referrer 校验,这里设置关闭,不然加载不了语雀的图片 + metadata: [{ name: 'referrer', content: 'no-referrer' }], + }), + + themes: [ + [ + require.resolve('@easyops-cn/docusaurus-search-local'), + { + hashed: true, + // For Docs using Chinese, The `language` is recommended to set to: + // ``` + language: ['en', 'zh'], + // ``` + }, + ], + ], +}; + +module.exports = config; diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..306b58cac --- /dev/null +++ b/docs/package.json @@ -0,0 +1,62 @@ +{ + "name": "@alilc/lowcode-engine-docs", + "version": "0.0.1-beta.3", + "description": "低代码引擎版本化文档", + "license": "MIT", + "files": [ + "build" + ], + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start --host 0.0.0.0", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids", + "typecheck": "tsc" + }, + "dependencies": { + "@docusaurus/core": "^2.2.0", + "@docusaurus/preset-classic": "^2.2.0", + "@easyops-cn/docusaurus-search-local": "^0.32.0", + "@mdx-js/react": "^1.6.22", + "axios": "^1.1.3", + "clsx": "^1.2.1", + "fs-extra": "^10.1.0", + "prism-react-renderer": "^1.3.5", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "^2.2.0", + "@tsconfig/docusaurus": "^1.0.5", + "typescript": "^4.7.4" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "engines": { + "node": ">=16.14" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "http", + "url": "https://github.com/alibaba/lowcode-engine/tree/main" + }, + "gitHead": "2669f179e6f899d395ce1942d0fe04f9c5ed48a6" +} diff --git a/docs/scripts/getDocsFromDir.js b/docs/scripts/getDocsFromDir.js new file mode 100644 index 000000000..1d3236fe6 --- /dev/null +++ b/docs/scripts/getDocsFromDir.js @@ -0,0 +1,60 @@ +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); +const matter = require('gray-matter'); + +module.exports = function getDocsFromDir(dir, cateList) { + // docs/ + const baseDir = path.join(__dirname, '../docs/'); + const docsDir = path.join(baseDir, dir); + + function getMarkdownOrder(filepath) { + return (matter(fs.readFileSync(filepath, 'utf-8')).data || {}).order || 100; + } + + const docs = glob.sync('*.md?(x)', { + cwd: docsDir, + // ignore: 'README.md', + }); + + const result = docs + .filter(doc => !/^index.md(x)?$/.test(doc)) + .map(doc => { + return path.join(docsDir, doc); + }) + .sort((a, b) => { + const orderA = getMarkdownOrder(a); + const orderB = getMarkdownOrder(b); + + return orderA - orderB; + }) + .map(filepath => { + // /Users/xxx/site/docs/guide/basic/router.md => guide/basic/router + const id = path + .relative(baseDir, filepath) + .replace(/\\/g, '/') + .replace(/\.mdx?/, ''); + return id; + }); + + (cateList || []).forEach(item => { + const { dir, subCategory, ...otherConfig } = item; + const indexList = glob.sync('index.md?(x)', { + cwd: path.join(baseDir, dir), + }); + if (indexList.length > 0) { + otherConfig.link = { + type: 'doc', + id: `${dir}/index`, + }; + } + result.push({ + type: 'category', + collapsed: false, + ...otherConfig, + items: getDocsFromDir(dir, subCategory), + }); + }); + + return result; +}; diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css new file mode 100644 index 000000000..9b929baae --- /dev/null +++ b/docs/src/css/custom.css @@ -0,0 +1,103 @@ + +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ +:root { + --ifm-font-size-base: 14px; + --ifm-color-primary: #0089ff; + --ifm-color-primary-dark: #007be6; + --ifm-color-primary-darker: #0074d9; + --ifm-color-primary-darkest: #0060b3; + --ifm-color-primary-light: #1a95ff; + --ifm-color-primary-lighter: #269bff; + --ifm-color-primary-lightest: #4dacff; + --ifm-code-font-size: 95%; + --ifm-container-width-xl: 2000px; + --aa-search-input-height: 32px; + --ifm-font-family-base: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji'; + --ifm-font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; + --ifm-global-spacing: 1.5rem; + --ifm-line-height-base: 1.85; + /* --ifm-font-color-base: #333; */ +} + +.header-github-link::before { + content: ''; + width: 24px; + height: 24px; + display: flex; + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") + no-repeat; +} + +[data-theme='dark'] .header-github-link::before { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") + no-repeat; +} + +.docusaurus-highlight-code-line { + background-color: rgba(0, 0, 0, 0.1); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); +} + +html[data-theme='dark'] .docusaurus-highlight-code-line { + background-color: rgba(0, 0, 0, 0.3); +} + +.navbar__logo, +.navbar__search { + margin-right: 2rem; +} + +.hero { + padding: 5rem 0 !important; + box-shadow: var(--ifm-navbar-shadow); +} + +.homepage-content { + max-width: 1400px; + margin: 0 auto; +} + +.heroBanner { + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; +} + +.hero__title{ + font-size: 2.4rem; + background: -webkit-linear-gradient(315deg,#0089ff 25%,#30e724); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + display: inline-block; +} + +.pagination-nav__link { + padding: 0.5rem 1.5rem; +} + +.markdown h1:first-child { + --ifm-h1-font-size: 2rem; +} + +.markdown > h2{ + --ifm-h2-font-size: 1.5rem; +} + +.markdown > h3{ + --ifm-h3-font-size: 1.25rem; +} + +.markdown img { + box-shadow: 9px 8px 10px 0px rgb(0 0 0 / 15%); +} diff --git a/docs/src/pages/index.module.css b/docs/src/pages/index.module.css new file mode 100644 index 000000000..ac3d44996 --- /dev/null +++ b/docs/src/pages/index.module.css @@ -0,0 +1,29 @@ +/** + * CSS files with the .module.css suffix will be treated as CSS modules + * and scoped locally. + */ + +.heroBanner { + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; + height: 60rem; +} + +.heroTitle { + color: #30e724; + font-size: 3rem; +} + +@media screen and (max-width: 996px) { + .heroBanner { + padding: 2rem; + } +} + +.buttons { + display: flex; + align-items: center; + justify-content: center; +} \ No newline at end of file diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx new file mode 100644 index 000000000..13be38e6e --- /dev/null +++ b/docs/src/pages/index.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Layout from '@theme/Layout'; + +import styles from './index.module.css'; + +function HomepageHeader() { + const { siteConfig } = useDocusaurusContext(); + return ( +
+
+

{siteConfig.title}

+

欢迎光临 低代码引擎文档站

+

{siteConfig.tagline}

+
+ + 快速开始 + +
+
+
+ ); +} + +export default function Home(): JSX.Element { + const { siteConfig } = useDocusaurusContext(); + return ( + + + + ); +} diff --git a/docs/src/pages/markdown-page.md b/docs/src/pages/markdown-page.md new file mode 100644 index 000000000..7d2421c8a --- /dev/null +++ b/docs/src/pages/markdown-page.md @@ -0,0 +1,15 @@ + + +# 文档能力介绍 + +这是一个使用 Markdown 编写的任意页面,访问地址为 /markdown-page + +Product Docs Capability Introduction. + +## 功能 + +- ✅ 支持本地离线搜搜 +- ✅ 版本化文档管理 +- ✅ 离线静态部署 diff --git a/docs/static/img/docusaurus.png b/docs/static/img/docusaurus.png new file mode 100644 index 0000000000000000000000000000000000000000..f458149e3c8f53335f28fbc162ae67f55575c881 GIT binary patch literal 5142 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 F{0H{b<4^zq literal 0 HcmV?d00001 diff --git a/docs/static/img/logo.svg b/docs/static/img/logo.svg new file mode 100644 index 000000000..9db6d0d06 --- /dev/null +++ b/docs/static/img/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static/img/undraw_docusaurus_mountain.svg b/docs/static/img/undraw_docusaurus_mountain.svg new file mode 100644 index 000000000..af961c49a --- /dev/null +++ b/docs/static/img/undraw_docusaurus_mountain.svg @@ -0,0 +1,171 @@ + + Easy to Use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/img/undraw_docusaurus_react.svg b/docs/static/img/undraw_docusaurus_react.svg new file mode 100644 index 000000000..94b5cf08f --- /dev/null +++ b/docs/static/img/undraw_docusaurus_react.svg @@ -0,0 +1,170 @@ + + Powered by React + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/img/undraw_docusaurus_tree.svg b/docs/static/img/undraw_docusaurus_tree.svg new file mode 100644 index 000000000..d9161d339 --- /dev/null +++ b/docs/static/img/undraw_docusaurus_tree.svg @@ -0,0 +1,40 @@ + + Focus on What Matters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 000000000..6f4756980 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,7 @@ +{ + // This file is not used in compilation. It is here just for a nice editor experience. + "extends": "@tsconfig/docusaurus/tsconfig.json", + "compilerOptions": { + "baseUrl": "." + } +}