Compare commits

...

133 Commits

Author SHA1 Message Date
Wells 897fe0a5a1 chore(release): publish
- @music163/tango-context@1.0.2
 - @music163/tango-core@1.0.2
 - @music163/tango-designer@1.0.3
 - @music163/tango-helpers@1.0.0
 - @music163/tango-sandbox@1.0.2
 - @music163/tango-setting-form@1.1.0
 - @music163/tango-ui@1.0.3
2024-05-09 10:54:49 +08:00
Wells b52bbad547 fix: disable toggle variable for expSetter & add isValid to workspace (#146)
* fix: add isValid to test if valid project

* fix: disable toggle variable for expSetter
2024-05-08 20:11:22 +08:00
864907600cc f7c837a8a9 feat: support validate on formItem (#145)
Co-authored-by: ccloli <8115912+ccloli@users.noreply.github.com>
2024-05-08 19:57:32 +08:00
Wells f64d178ab8 chore: up 2024-05-06 15:32:10 +08:00
maxwls f2dcc942ed chore: support start playground in different os (#139)
Co-authored-by: 王龙生 <wanglongsheng@pansoft.com>
2024-05-06 15:31:09 +08:00
Wells ac48a8880e fix: update setting form styles & sort deprecated props to the bottom (#142)
* fix: sort the deprecated props to the bottom

* fix: merge undefined props

* fix: update form label color
2024-05-06 15:30:29 +08:00
Wells 0f1711868d chore(release): publish
- @music163/tango-context@1.0.1
 - @music163/tango-core@1.0.1
 - @music163/tango-designer@1.0.2
 - @music163/tango-helpers@1.0.0-alpha.7
 - @music163/tango-sandbox@1.0.1
 - @music163/tango-setting-form@1.0.2
 - @music163/tango-ui@1.0.2
2024-04-22 11:36:47 +08:00
Wells 06cd3b3a18 chore: generate setting-form docs 2024-04-22 11:34:20 +08:00
Wells 2664613ab2 fix: allow reload state tree & display pageStore in variable tree (#135)
* fix: update state tree display

* fix: allow reload state tree

* fix: update action props

* fix: display pageStore in variable tree
2024-04-19 19:55:40 +08:00
Wells e39a913a87 chore(release): publish
- @music163/tango-designer@1.0.1
 - @music163/tango-setting-form@1.0.1
 - @music163/tango-ui@1.0.1
2024-04-16 15:29:34 +08:00
Wells 52864b5400 fix: variable tree remove button event handling (#134)
* docs: add StrictMode

* fix: variable tree remove button event handling
2024-04-16 15:25:09 +08:00
BoBoooooo 27166ec949 fix: render-setter expression value bug (#133)
* Create sync.yml

* Update sync.yml

* fix: render-setter expression value bug

* ci: remove sync.yml
2024-04-16 10:33:54 +08:00
Wells 1a8d15c9da chore: up 2024-04-15 14:47:52 +08:00
Wells 43b99de01c chore(release): publish
- @music163/tango-context@1.0.0
 - @music163/tango-core@1.0.0
 - @music163/tango-designer@1.0.0
 - @music163/tango-sandbox@1.0.0
 - @music163/tango-setting-form@1.0.0
 - @music163/tango-ui@1.0.0
2024-04-15 14:41:25 +08:00
BoBoooooo 1c1ad9e5eb feat: support traverse native dom (#132)
* Create sync.yml

* Update sync.yml

* feat: support nativeDom

* ci: remove sync.yml

* fix: update test case

* fix: update isValidComponentName logic
2024-04-15 14:22:34 +08:00
BoBoooooo 776837079a feat: update ui & add dual screen mode (#131)
* fix: change view source icon

* fix: setting-form collapsed panel default collapsed

* fix: selectAction font size larger

* feat: add dual screen mode

* fix: trigger bug
2024-04-11 10:20:07 +08:00
Wells 14375284e4 chore(release): publish
- @music163/tango-context@1.0.0-alpha.10
 - @music163/tango-core@1.0.0-alpha.10
 - @music163/tango-designer@1.0.0-alpha.18
 - @music163/tango-helpers@1.0.0-alpha.6
 - @music163/tango-sandbox@1.0.0-alpha.10
 - @music163/tango-setting-form@1.0.0-alpha.14
 - @music163/tango-ui@1.0.0-alpha.12
2024-04-09 14:37:56 +08:00
Wells cb6aabf6ce fix: check is in design mode 2024-04-08 18:02:43 +08:00
BoBoooooo 3f42516816 fix: update SettingForm style (#125)
* fix: auto focus expression panel

* fix: form control group level 1 sticky head

* fix: expression-setter add showOptionsDropDown

* fix: update icon

* fix: revert toggleIsVariable

* fix: revert comment
2024-04-03 12:41:10 +08:00
Wells 93608d1037 fix: update setters and use tabOptions to filter props (#129)
* docs: update

* fix: update actionSelect

* fix: update ToggleButton text shape style

* fix: update InputCode minHeight and placeholder

* fix: show value in renderSetter

* docs: update

* fix: update groupOptions

* fix: update types

* chore: update
2024-04-02 20:08:54 +08:00
Wells 41a61cce4e fix: render invalid icon in inserted dropdown (#128) 2024-04-02 14:01:36 +08:00
Wells 47364e4219 chore(release): publish
- @music163/tango-context@1.0.0-alpha.9
 - @music163/tango-core@1.0.0-alpha.9
 - @music163/tango-designer@1.0.0-alpha.17
 - @music163/tango-helpers@1.0.0-alpha.5
 - @music163/tango-sandbox@1.0.0-alpha.9
 - @music163/tango-setting-form@1.0.0-alpha.13
 - @music163/tango-ui@1.0.0-alpha.11
2024-03-26 19:43:53 +08:00
Wells 9ed6a7d1a4 feat: quick add sibling nodes (#127)
* docs: add mail example

* fix: update SettingForm style

* fix: add tooltip to quick add action

* feat: quick add sibling nodes

* fix: update view
2024-03-26 19:36:28 +08:00
Wells 6f2c8f239a chore(release): publish
- @music163/tango-designer@1.0.0-alpha.16
2024-03-21 16:06:40 +08:00
Wells 6215ed96f2 fix: copy node path in variable tree 2024-03-21 15:33:23 +08:00
Wells 190aa1d8ee chore(release): publish
- @music163/tango-designer@1.0.0-alpha.15
2024-03-20 17:42:17 +08:00
Wells bceb7a5c9c fix: update selection helper style 2024-03-20 17:40:12 +08:00
Wells d64042922b fix: quick insert child 2024-03-20 17:10:52 +08:00
Wells 9ada3fecb3 chore(release): publish
- @music163/tango-designer@1.0.0-alpha.14
 - @music163/tango-setting-form@1.0.0-alpha.12
 - @music163/tango-ui@1.0.0-alpha.10
2024-03-19 19:19:42 +08:00
Wells 07febb385f fix: select outline node & render dependency list (#118)
* fix: select outline tree

* fix: update selection tip

* fix: refactor ColorTag
2024-03-19 19:16:05 +08:00
Wells ddbda0e6fb chore(release): publish
- @music163/tango-context@1.0.0-alpha.8
 - @music163/tango-core@1.0.0-alpha.8
 - @music163/tango-designer@1.0.0-alpha.13
 - @music163/tango-helpers@1.0.0-alpha.4
 - @music163/tango-sandbox@1.0.0-alpha.8
 - @music163/tango-setting-form@1.0.0-alpha.11
 - @music163/tango-ui@1.0.0-alpha.9
2024-03-18 16:54:59 +08:00
Wells 9f7441c134 fix: add deprecated and data group to ComponentPropType (#116)
* fix: add deprecated and data group to ComponentPropType

* fix: render choiceSetter with icon
2024-03-18 16:49:41 +08:00
Wells 9fd9673bf4 chore(release): publish
- @music163/tango-context@1.0.0-alpha.7
 - @music163/tango-core@1.0.0-alpha.7
 - @music163/tango-designer@1.0.0-alpha.12
 - @music163/tango-helpers@1.0.0-alpha.3
 - @music163/tango-sandbox@1.0.0-alpha.7
 - @music163/tango-setting-form@1.0.0-alpha.10
 - @music163/tango-ui@1.0.0-alpha.8
2024-03-18 11:46:36 +08:00
Wells 489118b88a fix: refactor action, formHeader, selectionDropdown (#114)
* fix: add new action component

* fix: refactor form header component and add documentation link

* fix: refactor dropdown menu for selected item

* fix: update

* fix: add store variable in modelSetter

* fix: code styles
2024-03-18 11:43:45 +08:00
xmin 23c2e2e9b6 fix: add both mode to routerSetter (#112)
Co-authored-by: liumin16 <liumin16@corp.netease.com>
2024-03-15 18:58:00 +08:00
Wells 6c65362a5d feat: support code id (#111)
* fix: parse tid in code

* fix: inject tango env variable

* fix: clone node with new tid

* docs: update resources

* fix: update node tree

* feat: add autoGenerateComponentId to designerConfig

* feat: add showIdentifier to set code id

* fix: support set tid in setting form

* fix: ignore duplicate codeId

* fix: remove FormValue component

* fix: show code id first in designer view
2024-03-13 11:01:57 +08:00
Wells 7470190e0b Deploy docs to pages (#104)
* chore: update

* chore: update

* chore: remove website
2024-02-19 20:08:36 +08:00
Wells b2293ba9f4 chore: Create deploy workflow 2024-02-19 17:54:32 +08:00
Wells 63dca2a9b7 docs: update (#95)
* docs: update

* docs: update

* docs: update

* docs: add setters

* docs: update

* chore: add typedoc workflow
2024-02-19 16:58:59 +08:00
Wells eaff3604f3 chore(release): publish
- @music163/tango-designer@1.0.0-alpha.11
2024-02-02 11:34:57 +08:00
Wells 5b121939e6 feat: allow add extra node to sandbox navigator (#100) 2024-02-02 11:32:37 +08:00
Wells 62b39daf7e chore(release): publish
- @music163/tango-designer@1.0.0-alpha.10
 - @music163/tango-setting-form@1.0.0-alpha.9
 - @music163/tango-ui@1.0.0-alpha.7
2024-01-29 13:57:19 +08:00
Wells 32962d47fb feat: add showItemSubtitle and renderItemExtra to SettingForm (#99)
* feat: add showItemSubtitle to SettingFormItem

* feat: add renderItemExtra to SettingForm
2024-01-29 13:32:27 +08:00
Wells d5e42c93bb fix: remove load iconfont (#97)
* fix: update icons

* fix: remove load iconfont in designer
2024-01-26 16:51:33 +08:00
Wells fc93f4424a chore(release): publish
- @music163/tango-designer@1.0.0-alpha.9
 - @music163/tango-setting-form@1.0.0-alpha.8
 - @music163/tango-ui@1.0.0-alpha.6
2024-01-24 16:52:08 +08:00
Wells d6a08eecd2 fix: update service preview panel (#94)
* fix: update setting form layout

* fix: update service preview

* chore: update editor

* fix: update components entry
2024-01-24 16:49:26 +08:00
Wells 181bfee2d2 chore(release): publish
- @music163/tango-designer@1.0.0-alpha.8
 - @music163/tango-setting-form@1.0.0-alpha.7
 - @music163/tango-ui@1.0.0-alpha.5
2024-01-23 11:13:29 +08:00
Wells 8fd085d4fd fix: update FormObject style (#91) 2024-01-23 11:11:34 +08:00
Wells 35e258b32a chore(release): publish
- @music163/tango-context@1.0.0-alpha.6
 - @music163/tango-core@1.0.0-alpha.6
 - @music163/tango-designer@1.0.0-alpha.7
 - @music163/tango-helpers@1.0.0-alpha.2
 - @music163/tango-sandbox@1.0.0-alpha.6
 - @music163/tango-setting-form@1.0.0-alpha.6
 - @music163/tango-ui@1.0.0-alpha.4
2024-01-16 15:31:02 +08:00
Wells 62d403f80a fix: 优化变量树实现 (#90)
* docs: update milestone

* fix: add new properties and methods to interfaces and models

* chore: add CI workflow for pull requests

* chore: add prebuild step to CI workflow
2024-01-16 15:28:55 +08:00
Wells 73aded3672 chore(release): publish
- @music163/tango-context@1.0.0-alpha.5
 - @music163/tango-core@1.0.0-alpha.5
 - @music163/tango-designer@1.0.0-alpha.6
 - @music163/tango-helpers@1.0.0-alpha.1
 - @music163/tango-sandbox@1.0.0-alpha.5
 - @music163/tango-setting-form@1.0.0-alpha.5
 - @music163/tango-ui@1.0.0-alpha.3
2023-12-29 17:41:58 +08:00
Wells 8c07821d93 fix: refactor VariableTree & Workspace (#83)
* fix: refactor VariableTree

* fix: update

* fix: update

* fix: update

* fix: update

* fix: refactor IVariableTreeNode

* fix: update

* fix: refactor workspace

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-29 17:30:11 +08:00
wwsun 765ea07cd5 fix: check if store value is valid 2023-12-26 15:43:20 +08:00
wwsun a27c241d9c chore(release): publish
- @music163/tango-context@1.0.0-alpha.4
 - @music163/tango-core@1.0.0-alpha.4
 - @music163/tango-designer@1.0.0-alpha.5
 - @music163/tango-sandbox@1.0.0-alpha.4
 - @music163/tango-setting-form@1.0.0-alpha.4
2023-12-26 14:38:54 +08:00
wwsun 4d6b67eecd fix: list overflow in expSetter 2023-12-26 14:36:13 +08:00
wwsun 6fc889a161 chore(release): publish
- @music163/tango-context@1.0.0-alpha.3
 - @music163/tango-core@1.0.0-alpha.3
 - @music163/tango-designer@1.0.0-alpha.4
 - @music163/tango-sandbox@1.0.0-alpha.3
 - @music163/tango-setting-form@1.0.0-alpha.3
2023-12-25 11:08:29 +08:00
wwsun c07b554360 chore: up 2023-12-25 11:06:30 +08:00
wwsun 149002dddd fix: export componentEntry module 2023-12-25 11:00:04 +08:00
wwsun 45b0d856c1 chore(release): publish
- @music163/tango-context@1.0.0-alpha.2
 - @music163/tango-core@1.0.0-alpha.2
 - @music163/tango-designer@1.0.0-alpha.3
 - @music163/tango-helpers@1.0.0-alpha.0
 - @music163/tango-sandbox@1.0.0-alpha.2
 - @music163/tango-setting-form@1.0.0-alpha.2
 - @music163/tango-ui@1.0.0-alpha.2
2023-12-19 15:17:19 +08:00
Wells 6fc0498901 feat: support local components (#78)
* docs: update

* feat: parse local components

* fix: update

* refactor: module exports to exportList

* fix: update workspace

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-19 15:07:44 +08:00
wwsun f2919c1aa0 chore(release): publish
- @music163/tango-designer@1.0.0-alpha.2
 - tango-spec@1.0.2
2023-12-13 15:59:20 +08:00
wwsun 5dd5ab62a8 chore: up 2023-12-13 15:57:54 +08:00
Wells 5e7e285452 fix: update variable tree with help messages and styling & add tangoConfig schema
* fix: update variable tree with help messages and styling

* chore: add spec package

* fix: update schema

* chore: disabled umi https2

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-13 14:03:07 +08:00
wwsun b3a458a914 chore(release): publish
- @music163/tango-context@1.0.0-alpha.1
 - @music163/tango-core@1.0.0-alpha.1
 - @music163/tango-designer@1.0.0-alpha.1
 - @music163/tango-sandbox@1.0.0-alpha.1
 - @music163/tango-setting-form@1.0.0-alpha.1
 - @music163/tango-ui@1.0.0-alpha.1
2023-12-12 11:12:09 +08:00
Wells 1acfa6864b fix: render form item without setter & enhance tools (#76)
* fix: render form item without setter

* fix: update toggle-button style

* fix: update tools

* fix: remove useless code

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-12 11:10:17 +08:00
Wells baf071f130 chore: use umi4 (#75)
Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-08 15:46:51 +08:00
Wells 6b1d55a11f RC (#74)
* chore: up

* chore: update release scripts

* chore: up

* chore: update lerna config

* chore(release): publish

 - @music163/tango-context@1.0.0-alpha.0
 - @music163/tango-core@1.0.0-alpha.0
 - @music163/tango-designer@1.0.0-alpha.0
 - @music163/tango-sandbox@1.0.0-alpha.0
 - @music163/tango-setting-form@1.0.0-alpha.0
 - @music163/tango-ui@1.0.0-alpha.0

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-07 12:40:18 +08:00
Wells a8357f45b8 fix: add showSearch and showGroups to SettingForm & update internal icons (#73)
* fix: add internal setters & fix ui

* feat: add showSearch and showGroups to SettingForm

* fix: update icons

* fix: use antd form

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-07 10:31:19 +08:00
Wells 9db6358588 fix: disable search keymap of InputCode (#72)
Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-06 15:33:47 +08:00
wwsun 5bc2fb2d5a chore: up 2023-12-06 11:33:05 +08:00
wwsun 05a0f8f490 chore(release): publish
- @music163/tango-context@0.2.12
 - @music163/tango-core@0.2.11
 - @music163/tango-designer@0.5.14
 - @music163/tango-sandbox@0.1.16
 - @music163/tango-setting-form@0.2.16
2023-12-04 18:43:14 +08:00
wwsun c884712331 fix: update FileType 2023-12-04 18:38:38 +08:00
wwsun e35ff792a0 chore(release): publish
- @music163/tango-context@0.2.11
 - @music163/tango-core@0.2.10
 - @music163/tango-designer@0.5.13
 - @music163/tango-sandbox@0.1.15
 - @music163/tango-setting-form@0.2.15
2023-12-04 12:54:30 +08:00
Wells 138cbe9b20 fix: add filesFormatter to sandbox & fix update props with imports (#71)
* fix: update types

* fix: add filesFormatter to sandbox

* fix: update node attributes with imports

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-12-04 12:52:10 +08:00
wwsun 979aa34259 chore(release): publish
- @music163/tango-context@0.2.10
 - @music163/tango-core@0.2.9
 - @music163/tango-designer@0.5.12
 - @music163/tango-sandbox@0.1.14
 - @music163/tango-setting-form@0.2.14
2023-11-30 15:46:50 +08:00
wwsun 76a5a2c659 fix: fix render components tree 2023-11-30 15:44:42 +08:00
wwsun 53ebd89c9a chore(release): publish
- @music163/tango-context@0.2.9
 - @music163/tango-core@0.2.8
 - @music163/tango-designer@0.5.11
 - @music163/tango-sandbox@0.1.13
 - @music163/tango-setting-form@0.2.13
2023-11-29 14:50:40 +08:00
wwsun 3980810502 docs: update milestone 2023-11-29 14:47:40 +08:00
Wells accd826376 fix: workspace onFilesChange (#70)
* fix: workspace onFilesChange

* fix: remove appJson parse logic

* style: remove comments

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-29 14:43:06 +08:00
wwsun 5ca1118f88 chore(release): publish
- @music163/tango-designer@0.5.10
2023-11-28 14:55:20 +08:00
wwsun 14cd4c54a1 fix: update default theme 2023-11-28 14:51:10 +08:00
wwsun 7f46137c56 chore(release): publish
- @music163/tango-designer@0.5.9
 - @music163/tango-setting-form@0.2.12
 - @music163/tango-ui@0.1.12
2023-11-28 13:51:35 +08:00
wwsun 1db52948db fix: update type 2023-11-28 13:49:44 +08:00
wwsun 2550411c1c fix: update theme 2023-11-28 13:45:17 +08:00
wwsun 640859d36e chore(release): publish
- @music163/tango-context@0.2.8
 - @music163/tango-core@0.2.7
 - @music163/tango-designer@0.5.8
 - @music163/tango-sandbox@0.1.12
 - @music163/tango-setting-form@0.2.11
2023-11-27 14:02:30 +08:00
wwsun d91763379c fix: parse tango variables in view file 2023-11-27 13:55:53 +08:00
wwsun 588861f649 fix: event setter 2023-11-27 13:46:45 +08:00
wwsun 91b53da38e fix: list service functions 2023-11-24 09:50:14 +08:00
wwsun 9b0ada94a0 chore(release): publish
- @music163/tango-designer@0.5.7
2023-11-23 18:50:57 +08:00
Wells f604996e56 fix: update varTreeNode style (#69)
Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-23 18:46:56 +08:00
wwsun fb26540f27 chore(release): publish
- @music163/tango-context@0.2.7
 - @music163/tango-core@0.2.6
 - @music163/tango-designer@0.5.6
 - @music163/tango-helpers@0.1.8
 - @music163/tango-sandbox@0.1.11
 - @music163/tango-setting-form@0.2.10
 - @music163/tango-ui@0.1.11
2023-11-23 15:56:48 +08:00
Wells 558f2cd0a6 fix: refactor update import specifiers (#68)
* fix: refactor update import declaration

* fix: rename module methods

* fix: update types

* fix: update

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-23 15:54:22 +08:00
Wells 8a1234e565 fix: prototype to import declaration (#67)
Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-22 20:00:32 +08:00
wwsun 630c486eb8 chore(release): publish
- @music163/tango-designer@0.5.5
2023-11-20 17:20:18 +08:00
wwsun 324f450851 docs: update timeline 2023-11-20 17:15:59 +08:00
wwsun d6ffb50e85 fix: pass down sandbox props 2023-11-20 17:05:44 +08:00
青岚 2906efc8fc docs: add seal d2c intro (#63) 2023-11-20 17:02:27 +08:00
wwsun 2259fe77fc chore(release): publish
- @music163/tango-context@0.2.6
 - @music163/tango-core@0.2.5
 - @music163/tango-designer@0.5.4
 - @music163/tango-helpers@0.1.7
 - @music163/tango-sandbox@0.1.10
 - @music163/tango-setting-form@0.2.9
 - @music163/tango-ui@0.1.10
2023-11-20 14:04:52 +08:00
Wells dbbd1dddc7 fix: refactor parse expression (#61)
* fix: update interfaces

* test: update

* fix: test if code has expression wrapper

* fix: parse code to expression node

* fix: update long text style in variableTree

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-20 14:01:01 +08:00
wwsun 14e17ebbed chore(release): publish
- @music163/tango-context@0.2.5
 - @music163/tango-core@0.2.4
 - @music163/tango-designer@0.5.3
 - @music163/tango-helpers@0.1.6
 - @music163/tango-sandbox@0.1.9
 - @music163/tango-setting-form@0.2.8
 - @music163/tango-ui@0.1.9
2023-11-16 14:35:52 +08:00
Wells 4a06eb20cb refactor: update workspace and viewModule
* docs: update example

* fix: refactor workspace

* fix: refactor viewModule

* fix: update interfaces

* fix: always switch to the view matched code file

* fix: keep sidebar state

* test: update

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-16 14:31:10 +08:00
wwsun 089a482f75 fix: parse variables from view 2023-11-14 11:30:45 +08:00
wwsun afb94d6a30 fix: fix sandbox props 2023-11-14 11:10:55 +08:00
wwsun b9403cfec6 fix: isVariableString 2023-11-07 20:20:07 +08:00
wwsun 6cf673e7f6 fix: allow override existing file via addFile 2023-11-07 17:23:02 +08:00
wwsun 653386dcc0 fix: update types 2023-11-06 19:21:35 +08:00
BoBoooooo 208e275bb1 fix: export isJSXElementById from traverse (#54)
* feat: traverse helper add findJSXParentsById func
* fix: update findJSXParentsById def
* feat: export isJSXElementById from traverse
2023-11-06 11:31:33 +08:00
wwsun 77f4145adf chore(release): publish
- @music163/tango-context@0.2.4
 - @music163/tango-core@0.2.3
 - @music163/tango-designer@0.5.2
 - @music163/tango-helpers@0.1.5
 - @music163/tango-sandbox@0.1.8
 - @music163/tango-setting-form@0.2.7
 - @music163/tango-ui@0.1.8
2023-11-02 15:28:40 +08:00
wwsun 11f166a2a6 fix: update style of components panel 2023-11-02 15:26:13 +08:00
wwsun 0452fdb7f1 chore: fix types 2023-11-02 15:17:22 +08:00
wwsun 0068d3426e docs: update 2023-11-02 11:25:22 +08:00
wwsun c5840f29b7 fix: limit outline tree node icon 2023-11-02 11:25:22 +08:00
wwsun 963bfe1ab8 fix(core): logging drop action 2023-11-02 11:21:02 +08:00
wwsun ae806aee35 fix(core): create store without entry file 2023-11-02 11:21:02 +08:00
BoBoooooo b29e23599b fix: outline-panel tree expand bug & add props (#52)
* fix: outline-panel tree expand bug

* feat: collpase-panel add showBottomBorder prop

* feat: outline-panel add props

* fix: rename onPageNodeSelect to onSelect
2023-11-02 11:10:39 +08:00
wwsun f53ce94c6e fix: refactor workspace onFilesChange 2023-10-31 17:24:18 +08:00
BoBoooooo d775a5003a fix: add onFilesChange to workspace (#50)
* feat: workspace add onFileChange event callback

* fix: rename onFileChange to onFilesChange

* revert: sandbox ts def
2023-10-31 17:05:00 +08:00
wwsun 46d5f59b4c fix: update types 2023-10-31 16:54:47 +08:00
wwsun 0c0d95fe58 chore: up 2023-10-31 16:50:18 +08:00
BoBoooooo 9dd11f7a6d fix: designer model add defaultActiveSidebarPanel (#51)
* feat: designer add component support click mode & support set defaultActivePanel

* revert: remove defaultAddComponentType

* revert: remove addComponentType ts def
2023-10-31 16:49:14 +08:00
BoBoooooo fd2fa943b0 fix: resizable-box support horizontal and vertical dragging (#48) 2023-10-31 15:44:03 +08:00
wwsun 70fc6f8030 fix: optional context config 2023-10-25 11:24:18 +08:00
Jiyu Shao 4f7233bd6c chore: fix tsconfig paths (#44) 2023-10-25 11:22:01 +08:00
wwsun 564d50b168 chore(release): publish
- @music163/tango-context@0.2.3
 - @music163/tango-core@0.2.2
 - @music163/tango-designer@0.5.1
 - @music163/tango-helpers@0.1.4
 - @music163/tango-sandbox@0.1.7
 - @music163/tango-setting-form@0.2.6
 - @music163/tango-ui@0.1.7
2023-10-23 12:56:20 +08:00
wwsun 0b3e632745 fix: update interface 2023-10-23 12:11:30 +08:00
wwsun 3f073eeb43 fix: custom store template 2023-10-23 11:35:31 +08:00
wwsun 6bbbe634ad fix: add listImportSources to viewModule 2023-10-23 11:30:29 +08:00
wwsun 8fefa6344f fix: add url validator 2023-10-23 11:12:20 +08:00
Wells 5ebbb428fb fix: enhance expSetter (#43)
* docs: Update

* fix: register history tool

* fix: add eslint to InputCode

* fix: enhance expSetter

* fix: transform value to expression code

* fix: value change of expSetter

* fix: input code without wrapper

* fix: disable eslint for InputCode by default

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-10-20 16:48:49 +08:00
wwsun c240c1824b chore(release): publish
- @music163/tango-context@0.2.2
 - @music163/tango-designer@0.5.0
 - @music163/tango-setting-form@0.2.5
 - @music163/tango-ui@0.1.6
2023-10-12 15:24:21 +08:00
Wells d63517ecb9 fix: refactor expSetter (#41)
* fix: refactor expSetter

* feat: support pass custom variable list

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-10-11 23:51:27 -05:00
Samson Yang 1a17ee1e94 docs: fix typo (#39)
修改错别字
2023-10-09 21:24:01 -05:00
864907600cc e1bab66819 feat: add umd resources config when modifying dependency (#36)
* feat: mark dependency when it's in both package.json and tango.config.json

* fix: remove mark for internal resources

* feat: add resource config when adding dependency

* feat: reuse add dependency form modal when editing dependency

* fix: tidy up

---------

Co-authored-by: ccloli <8115912+ccloli@users.noreply.github.com>
2023-10-09 05:58:05 -05:00
282 changed files with 13285 additions and 122967 deletions
+2 -1
View File
@@ -2,11 +2,12 @@
"extends": ["eslint-config-ali/typescript/react", "prettier"],
"ignorePatterns": ["**/dist/**/*", "**/lib/**/*", "**/node_modules/**/*", "scripts/**/*"],
"rules": {
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/no-unused-vars": "warn",
"import/no-cycle": "off",
"no-nested-ternary": "off",
"no-useless-return":"off",
"no-useless-return": "off",
"no-param-reassign": "off",
"prefer-destructuring": "off"
}
+49
View File
@@ -0,0 +1,49 @@
name: CI
# Event设置为main分支的pull request事件,
# 这里的main分支相当于master分支,github项目新建是把main设置为默认分支,我懒得改了所以就保持这样吧
on:
pull_request:
branches: main
jobs:
# 只需要定义一个job并命名为CI
CI:
runs-on: ubuntu-latest
steps:
# 拉取项目代码
- name: Checkout repository
uses: actions/checkout@v2
# 给当前环境下载node
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
# 检查缓存
# 如果key命中缓存则直接将缓存的文件还原到 path 目录,从而减少流水线运行时间
# 若 key 没命中缓存时,在当前Job成功完成时将自动创建一个新缓存
- name: Cache
# 缓存命中结果会存储在steps.[id].outputs.cache-hit里,该变量在继后的step中可读
id: cache-dependencies
uses: actions/cache@v3
with:
# 缓存文件目录的路径
path: |
**/node_modules
# key中定义缓存标志位的生成方式。runner.OS指当前环境的系统。外加对yarn.lock内容生成哈希码作为key值,如果yarn.lock改变则代表依赖有变化。
# 这里用yarn.lock而不是package.json是因为package.json中还有version和description之类的描述项目但和依赖无关的属性
key: ${{runner.OS}}-${{hashFiles('**/yarn.lock')}}
# 安装依赖
- name: Installing Dependencies
# 如果缓存标志位没命中,则执行该step。否则就跳过该step
if: steps.cache-dependencies.outputs.cache-hit != 'true'
run: yarn install
# 预构建
- name: Prebuild
run: yarn build
# 运行代码扫描
- name: Running Lint
# 通过前面章节定义的命令行执行代码扫描
run: yarn eslint
# 运行自动化测试
- name: Running Test
# 通过前面章节定义的命令行执行自动化测试
run: yarn test
+53
View File
@@ -0,0 +1,53 @@
# Simple workflow for deploying static content to GitHub Pages
name: Deploy Github Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ['main']
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: 'pages'
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: yarn install
- name: Build
run: yarn build
- name: Build docs
run: yarn typedoc
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
# Upload dist repository
path: './docs'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
+2
View File
@@ -0,0 +1,2 @@
registry=https://registry.npmmirror.com/
+29 -19
View File
@@ -5,7 +5,7 @@
<h1 align="center">Tango LowCode Builder</h1>
<div align="center">
A source code based low-code builder, developed by NetEase Cloud Music Develop Team.
A source code based low-code builder.
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/NetEase/tango/blob/main/LICENSE)
[![NPM version](https://img.shields.io/npm/v/@music163/tango-designer.svg?style=flat-square)](http://npmjs.org/package/@music163/tango-designer)
@@ -20,7 +20,7 @@ English | [简体中文](/README.zh-CN.md)
You can view the detailed usage guide through the following links:
- Document site: <https://netease.github.io/tango/>
- Document site: <https://netease.github.io/tango-site/>
- Playground application: <https://tango-demo.musicfe.com/designer/>
## ✨ Features
@@ -31,32 +31,34 @@ You can view the detailed usage guide through the following links:
- Out-of-the-box front-end low-code designer, providing flexible and easy-to-use designer React components
- Developed using TypeScript, providing complete type definition files
## 🌐 Compatibility
- Modern browsersChrome >= 80, Edge >= 80, last 2 safari versions, last 2 firefox versions
## 🎯 Milestone
```mermaid
gantt
dateFormat YYYY-MM-DD
title Tango Release Milestone
title Tango 1.0 Milestones
excludes weekends
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
section Builder Release
Alpha version :active, des1, 2023-08-10, 2023-08-30
Beta version : des2, 2023-09-01, 2023-09-30
1.0 RC : des3, after des2, 40d
1.0 version : des4, after des3, 21d
1.0 Alpha : m1, 2023-10-01, 2024-01-31
1.0 RC : m2, 2024-02-01, 2024-04-30
1.0 Stable : m3, 2024-04-30, 2024-12-31
```
> [!NOTE]
> Alpha versions will continue to optimize some implementation protocols and continuously integrate new features from the internal version of NetEase; RC versions will provide a stable parsing protocol and implementation; The 1.0 official version is planned to be released in Q2 2024. After the release of the 1.0 official version, we will continue to iterate on versions and gradually incorporate core capabilities such as process design, component library, and AIGC from the internal version.
## 🌐 Compatibility
- Modern browsersChrome >= 80, Edge >= 80, last 2 safari versions, last 2 firefox versions
## 💻 Development
### Recommended Development Environment
### Environment
- Node.js >= 16.0.0
- Yarn >= 1.22.0
- Node `>= 18`
- Yarn `>= 1.22 && < 2`
### Development Quick Start
@@ -79,6 +81,7 @@ yarn start
Join NetEase Tango Community to share your ideas, suggestions, or questions and connect with other users and contributors.
- Discord: <https://discord.gg/B6hkGTe4Rz>
- [Usage Trends](https://npm-compare.com/@music163/tango-helpers,@music163/tango-context,@music163/tango-core,@music163/tango-setting-form,@music163/tango-sandbox,@music163/tango-ui,@music163/tango-designer)
## 🤝 Contributing
@@ -89,16 +92,23 @@ Please read the [github contribution guide](https://docs.github.com/en/get-start
- Commit and push your code
- Open a Pull Request
## 📈 Usage Trends
[Usage Trends of Tango Packages](https://npm-compare.com/@music163/tango-helpers,@music163/tango-context,@music163/tango-core,@music163/tango-setting-form,@music163/tango-sandbox,@music163/tango-ui,@music163/tango-designer)
## 💗 Acknowledgments
Thanks to the NetEase Cloud Music Front-end team, Public Technology team, Live Broadcasting Technology team, and all the colleagues who participated in the Tango project.
Thanks to the NetEase Cloud Music Front-end team, Public Technology team, Live Broadcasting Technology team, and all the developers who participated in the Tango project.
Thank you to CodeSandbox for providing the [Sandpack](https://sandpack.codesandbox.io/) project, which provides powerful online code execution capabilities for Tango.
## 📣 Product Promotion
![](https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/31629770956/da9e/3a74/4e00/7c69cf46a713f1b008bd1243b5b1ab1c.png)
Don't waste your time restoring the UI, try the NetEase Cloud Music "Seal D2C" development tool! Easily turn your design into code, support React, RN, Vue, WeChat apps and other multi-end scenarios, what you see is what you get!
Experience "Seal D2C" now:
- I'm a Figma user: <https://www.figma.com/community/plugin/1174548852019950797/seal-figma-to-code-d2c/>
- I'm a MasterGo user: <https://mastergo.com/community/plugin/98956774428196/>
## 📄 License
This project is licensed under the terms of the [MIT license](./LICENSE)
+28 -18
View File
@@ -5,7 +5,7 @@
<h1 align="center">Tango 低代码设计器</h1>
<div align="center">
一个源码驱动的低代码设计器,来自网易云音乐技术团队
一个源码驱动的低代码设计器框架
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/NetEase/tango/blob/main/LICENSE)
[![NPM version](https://img.shields.io/npm/v/@music163/tango-designer.svg?style=flat-square)](http://npmjs.org/package/@music163/tango-designer)
@@ -20,7 +20,7 @@
可以通过下面的链接查看详细的使用指南:
- 官方文档站点: <https://netease.github.io/tango/>
- 官方文档站点: <https://netease.github.io/tango-site/>
- 演示应用: <https://tango-demo.musicfe.com/designer/>
## ✨ 特性
@@ -31,32 +31,34 @@
- 开箱即用的前端低代码设计器,提供灵活易用的设计器 React 组件
- 使用 TypeScript 开发,提供完整的类型定义文件
## 🌐 兼容环境
- 现代浏览器(Chrome >= 80, Edge >= 80, last 2 safari versions, last 2 firefox versions
## 🎯 里程碑
```mermaid
gantt
dateFormat YYYY-MM-DD
title Tango Release Milestone
title Tango 1.0 Milestones
excludes weekends
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
section Builder Release
Alpha version :active, des1, 2023-08-10, 2023-08-30
Beta version : des2, 2023-09-01, 2023-09-30
1.0 RC : des3, after des2, 40d
1.0 version : des4, after des3, 21d
1.0 Alpha : m1, 2023-10-01, 2024-01-31
1.0 RC : m2, 2024-02-01, 2024-04-30
1.0 Stable : m3, 2024-04-30, 2024-12-31
```
> [!NOTE]
> Alpha 版本会持续对一些实现协议进行优化,并持续融合网易内网版本中的新增特性;RC 版本将会提供基本稳定的解析协议和实现;1.0 正式版本计划在 2024 年Q2发布。1.0 正式版发布后我们将会持续进行版本迭代,并逐步融入内网版本中的流程设计、组件库、AIGC 等核心基础能力。
## 🌐 兼容环境
- 现代浏览器(Chrome >= 80, Edge >= 80, last 2 safari versions, last 2 firefox versions
## 💻 开发
### 推荐开发环境
- Node.js >= 16.0.0
- Yarn >= 1.22.0
- Node `>= 18`
- Yarn `>= 1.22 && < 2`
### 本地开发调试方法
@@ -79,6 +81,7 @@ yarn start
参与 NetEase Tango 的社区,以分享您的想法、建议或问题,并与其他用户和贡献者建立联系。
- Discord: <https://discord.gg/B6hkGTe4Rz>
- [使用趋势](https://npm-compare.com/@music163/tango-helpers,@music163/tango-context,@music163/tango-core,@music163/tango-setting-form,@music163/tango-sandbox,@music163/tango-ui,@music163/tango-designer)
## 🤝 参与共建
@@ -90,16 +93,23 @@ yarn start
- 合并修改 git rebase master
- 发起 Pull Request
## 📈 使用量趋势
[Tango 使用量趋势](https://npm-compare.com/@music163/tango-helpers,@music163/tango-context,@music163/tango-core,@music163/tango-setting-form,@music163/tango-sandbox,@music163/tango-ui,@music163/tango-designer)
## 💗 致谢
感谢网易云音乐公共技术团队,大前端团队,直播技术团队,以及所有参与过 Tango 项目的同学们
感谢网易云音乐公共技术团队,大前端团队,直播技术团队,以及所有参与过 Tango 项目的开发者
感谢 CodeSandbox 提供的 [Sandpack](https://sandpack.codesandbox.io/) 项目,为 Tango 提供了强大的基于浏览器的代码构建与执行能力。
## 📣 产品推广
![](https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/31629770956/da9e/3a74/4e00/7c69cf46a713f1b008bd1243b5b1ab1c.png)
不要再浪费时间还原 UI 啦,快来试试网易云音乐「海豹 D2C」研发工具吧!轻松将设计稿转为代码,支持 React、RN、Vue、微信小程序等多端场景,所见即所得!
立即体验「海豹 D2C」:
- 我是 Figma 用户:<https://www.figma.com/community/plugin/1174548852019950797/seal-figma-to-code-d2c/>
- 我是 MasterGo 用户:<https://mastergo.com/community/plugin/98956774428196/>
## 📄 开源协议
此项目遵循 [MIT 开源协议](./LICENSE)
-2
View File
@@ -1,2 +0,0 @@
PORT=6006
HOST=local.netease.com
+9
View File
@@ -0,0 +1,9 @@
/node_modules
/.env.local
/.umirc.local.ts
/config/config.local.ts
/src/.umi
/src/.umi-production
/src/.umi-test
/dist
.swc
@@ -1,33 +1,18 @@
import path from 'path';
import { defineConfig } from 'umi';
const resolvePackageIndex = (relativeEntry: string) =>
path.resolve(__dirname, '../../../packages/', relativeEntry);
path.resolve(__dirname, '../../packages/', relativeEntry);
export default {
export default defineConfig({
routes: [
{
exact: true,
path: '/',
component: 'index',
name: '首页',
},
{ path: '/', component: 'index' },
{ path: '/mail', component: 'mail' },
{ path: '/docs', component: 'docs' },
],
devServer: {
host: 'local.netease.com',
port: 8008,
https: {
key: path.resolve(__dirname, 'local.netease.com-key.pem'),
cert: path.resolve(__dirname, 'local.netease.com.pem'),
},
headers: { 'Origin-Agent-Cluster': '?0' },
},
targets: {
chrome: 79,
firefox: false,
safari: false,
edge: false,
ios: false,
},
npmClient: 'yarn',
mfsu: false,
codeSplitting: false,
alias: {
'@music163/tango-helpers': resolvePackageIndex('helpers/src/index.ts'),
'@music163/tango-core': resolvePackageIndex('core/src/index.ts'),
@@ -44,6 +29,21 @@ export default {
moment: 'moment',
antd: 'antd',
},
headScripts: [
'https://unpkg.com/react@18.2.0/umd/react.development.js',
'https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js',
'https://unpkg.com/react-is@18.2.0/umd/react-is.production.min.js',
'https://unpkg.com/moment/min/moment-with-locales.js',
'https://unpkg.com/styled-components@5.3.11/dist/styled-components.js',
'https://unpkg.com/antd@4.24.13/dist/antd-with-locales.min.js',
'https://unpkg.com/prettier@2.6.0/standalone.js',
'https://unpkg.com/prettier@2.6.0/parser-babel.js',
],
https: {
key: path.resolve(__dirname, 'local.netease.com-key.pem'),
cert: path.resolve(__dirname, 'local.netease.com.pem'),
http2: false,
},
chainWebpack: (config: any) => {
// @see https://github.com/graphql/graphql-js/issues/1272#issuecomment-393903706
config.module
@@ -61,4 +61,5 @@ export default {
config.module.rule('js').include.add(resolvePackageIndex('ui/src'));
return config;
},
};
monorepoRedirect: {},
});
-5
View File
@@ -1,5 +0,0 @@
# `playground`
搭建平台演示示例
## 说明
+9 -14
View File
@@ -2,24 +2,19 @@
"name": "playground",
"version": "0.0.0",
"private": true,
"description": "playground of tango-apps",
"license": "MIT",
"author": "wwsun <ww.sun@outlook.com>",
"files": [
"lib"
],
"author": "Wells <ww.sun@outlook.com>",
"scripts": {
"build": "COMPRESS=none umi build",
"start": "umi dev"
"dev": "cross-env HOST=local.netease.com PORT=8001 umi dev",
"build": "umi build",
"postinstall": "umi setup",
"setup": "umi setup",
"start": "npm run dev"
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/tango-designer": "*",
"@music163/tango-helpers": "*",
"@music163/antd": "^0.2.4",
"antd": "^4.24.2",
"coral-system": "^1.0.5"
},
"devDependencies": {
"umi": "^3.5.24"
"coral-system": "^1.0.5",
"umi": "^4.0.89"
}
}
+10
View File
@@ -0,0 +1,10 @@
import type { IApi } from 'umi';
export default (api: IApi) => {
api.addMiddlewares(() => {
return (req, res, next) => {
res.setHeader('Origin-Agent-Cluster', '?0');
next();
};
});
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

@@ -3,115 +3,39 @@ import { Box, Group } from 'coral-system';
import { Avatar, Space, Switch } from 'antd';
import { BranchesOutlined, MenuOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { registerSetter } from '@music163/tango-designer';
import type { ComponentPrototypeType } from '@music163/tango-helpers';
import type { IVariableTreeNode } from '@music163/tango-helpers';
import { FooSetter } from '../components';
// folder-name
// 物料列表定义
const bizToggleButtonPrototype: ComponentPrototypeType = {
name: 'CtPcToggleButton',
exportType: 'defaultExport',
title: '示例业务组件',
icon: 'icon-tupian',
type: 'element',
docs: 'https://redstone.fn.netease.com/mt/fe-comp/w8bq8px7n5/toggle-button',
hasChildren: false,
props: [
{
name: 'checked',
title: '是否选中',
setter: 'boolSetter',
defaultValue: false,
},
{
name: 'children',
title: '文本',
setter: 'textSetter',
initValue: '按钮',
},
],
package: '@music/ct-pc-toggle-button',
};
export * from './mock-files';
const sampleBlockCode = `
<Section>
<Result
status="success"
title="Successfully Purchased Cloud Server ECS!"
subTitle="Order number: 2017182818828182881 Cloud server configuration takes 1-5 minutes, please wait."
extra={[
<Button type="primary" key="console">
Go Console
</Button>,
<Button key="buy">Buy Again</Button>,
]}
/>
</Section>
`;
const SnippetSuccessResult: ComponentPrototypeType = {
name: 'SnippetSuccessResult',
title: '成功结果',
icon: 'icon-tupian',
type: 'snippet',
package: '@music163/antd',
initChildren: sampleBlockCode,
relatedImports: ['Section', 'Result', 'Button'],
};
const Snippet2ColumnLayout: ComponentPrototypeType = {
name: 'Snippet2ColumnLayout',
title: '两列布局',
icon: 'icon-columns',
type: 'snippet',
package: '@music163/antd',
initChildren: `
<Columns columns={12}>
<Column colSpan={6}></Column>
<Column colSpan={6}></Column>
</Columns>
`,
relatedImports: ['Columns', 'Column'],
};
const Snippet3ColumnLayout: ComponentPrototypeType = {
name: 'Snippet3ColumnLayout',
title: '三列布局',
icon: 'icon-column3',
type: 'snippet',
package: '@music163/antd',
initChildren: `
<Columns columns={12}>
<Column colSpan={4}></Column>
<Column colSpan={4}></Column>
<Column colSpan={4}></Column>
</Columns>
`,
relatedImports: ['Columns', 'Column'],
};
const SnippetButtonGroup: ComponentPrototypeType = {
name: 'SnippetButtonGroup',
title: '按钮组',
icon: 'icon-anniuzu',
type: 'snippet',
package: '@music163/antd',
initChildren: `
<Space>
<Button type="primary">1</Button>
<Button>2</Button>
</Space>
`,
relatedImports: ['Space', 'Button'],
};
export const prototypes = {
CtPcToggleButton: bizToggleButtonPrototype,
SnippetSuccessResult,
Snippet2ColumnLayout,
Snippet3ColumnLayout,
SnippetButtonGroup,
};
export const bootHelperVariables: IVariableTreeNode[] = [
{
key: '$helpers',
title: '工具函数',
children: [
{
title: '设置变量值',
key: '() => tango.setStoreValue("variableName", "variableValue")',
type: 'function',
help: '设置状态值,tango.setStoreValue("variableName", "variableValue")',
},
{
title: '获取变量值',
key: '() => tango.getStoreValue("variableName")',
type: 'function',
help: '获取状态值',
},
{ title: '打开弹窗', key: '() => tango.openModal("")', type: 'function' },
{ title: '关闭弹窗', key: '() => tango.closeModal("")', type: 'function' },
{ title: '切换路由', key: '() => tango.navigateTo("/")', type: 'function' },
{
title: '拷贝到剪贴板',
key: '() => tango.copyToClipboard("hello")',
type: 'function',
},
],
},
];
// 注册自定义 setter
registerSetter({
+180
View File
@@ -0,0 +1,180 @@
const packageJson = {
name: 'demo',
private: true,
dependencies: {
'@music163/tango-mail': '0.1.1',
'@music163/tango-boot': '0.2.5',
react: '17.0.2',
'react-dom': '17.0.2',
'prop-types': '15.7.2',
tslib: '2.5.0',
},
};
const tangoConfigJson = {
designerConfig: {
autoGenerateComponentId: true,
},
packages: {
react: {
version: '17.0.2',
library: 'React',
type: 'dependency',
resources: ['https://unpkg.com/react@{{version}}/umd/react.development.js'],
},
'react-dom': {
version: '17.0.2',
library: 'ReactDOM',
type: 'dependency',
resources: ['https://unpkg.com/react-dom@{{version}}/umd/react-dom.development.js'],
},
'react-is': {
version: '16.13.1',
library: 'ReactIs',
type: 'dependency',
resources: ['https://unpkg.com/react-is@{{version}}/umd/react-is.production.min.js'],
},
'@music163/tango-boot': {
description: '云音乐低代码运行时框架',
version: '0.2.5',
library: 'TangoBoot',
type: 'baseDependency',
resources: ['https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js'],
// resources: ['http://localhost:9001/boot.js'],
},
'@music163/tango-mail': {
description: 'TangoMail 基础物料',
version: '0.1.1',
library: 'TangoMail',
type: 'baseDependency',
resources: ['https://unpkg.com/@music163/tango-mail@{{version}}/dist/index.js'],
designerResources: ['https://unpkg.com/@music163/tango-mail@{{version}}/dist/designer.js'],
},
},
};
const routesCode = `
import Mail from "./pages/mail";
const routes = [
{
path: '/',
exact: true,
component: Mail,
},
];
export default routes;
`;
const entryCode = `
import { runApp } from '@music163/tango-boot';
import routes from './routes';
runApp({
boot: {
mountElement: document.querySelector('#root'),
qiankun: false,
},
router: {
type: 'browser',
config: routes,
},
});
`;
const viewHomeCode = `
import React from 'react';
import {
Body,
Button,
Container,
Head,
Hr,
Html,
Preview,
Section,
Text,
} from '@music163/tango-mail';
const WelcomeEmail = () => (
<Html>
<Head />
<Preview>The sales intelligence platform that helps you uncover qualified leads.</Preview>
<Body style={main}>
<Container style={container}>
<Text style={paragraph}>Hi wells,</Text>
<Text style={paragraph}>
Welcome to Koala, the sales intelligence platform that helps you uncover qualified leads
and close deals faster.
</Text>
<Section style={btnContainer}>
<Button style={button} href="https://getkoala.com">
Get started
</Button>
</Section>
<Text style={paragraph}>
Best,
<br />
The Koala team
</Text>
<Hr style={hr} />
<Text style={footer}>470 Noor Ave STE B #1148, South San Francisco, CA 94080</Text>
</Container>
</Body>
</Html>
);
const main = {
backgroundColor: '#ffffff',
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
};
const container = {
margin: '0 auto',
padding: '20px 0 48px',
};
const paragraph = {
fontSize: '16px',
lineHeight: '26px',
};
const btnContainer = {
textAlign: 'center',
};
const button = {
backgroundColor: '#5F51E8',
borderRadius: '3px',
color: '#fff',
fontSize: '16px',
textDecoration: 'none',
textAlign: 'center',
display: 'block',
padding: '12px',
};
const hr = {
borderColor: '#cccccc',
margin: '20px 0',
};
const footer = {
color: '#8898aa',
fontSize: '12px',
};
export default WelcomeEmail;
`;
export const mailFiles = [
{ filename: '/package.json', code: JSON.stringify(packageJson) },
{ filename: '/tango.config.json', code: JSON.stringify(tangoConfigJson) },
{ filename: '/README.md', code: '# readme' },
{ filename: '/src/index.js', code: entryCode },
{ filename: '/src/pages/mail.js', code: viewHomeCode },
{ filename: '/src/routes.js', code: routesCode },
];
@@ -2,8 +2,8 @@ const packageJson = {
name: 'demo',
private: true,
dependencies: {
'@music163/antd': '0.1.6',
'@music163/tango-boot': '0.1.3',
'@music163/antd': '0.2.2',
'@music163/tango-boot': '0.2.5',
react: '17.0.2',
'react-dom': '17.0.2',
'prop-types': '15.7.2',
@@ -12,6 +12,9 @@ const packageJson = {
};
const tangoConfigJson = {
designerConfig: {
autoGenerateComponentId: true,
},
packages: {
react: {
version: '17.0.2',
@@ -44,39 +47,41 @@ const tangoConfigJson = {
resources: ['https://unpkg.com/moment@{{version}}/moment.js'],
},
'@music163/tango-boot': {
version: '0.2.1',
description: '云音乐低代码运行时框架',
version: '0.2.5',
library: 'TangoBoot',
type: 'baseDependency',
resources: ['https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js'],
// resources: ['http://localhost:9001/boot.js'],
description: '云音乐低代码运行时框架',
},
'@music163/antd': {
version: '0.1.7',
description: '云音乐低代码中后台应用基础物料',
version: '0.2.2',
library: 'TangoAntd',
type: 'baseDependency',
resources: [
'https://unpkg.com/@music163/antd@{{version}}/dist/index.js',
'https://unpkg.com/antd@4.24.13/dist/antd.css',
],
description: '云音乐低代码中后台应用基础物料',
designerResources: [
'https://unpkg.com/@music163/antd@{{version}}/dist/designer.js',
// 'http://localhost:9002/designer.js',
'https://unpkg.com/antd@4.24.13/dist/antd.css',
],
},
},
};
const appJson: any = {
pages: [
{
path: '/',
name: '首页',
},
],
};
const helperCode = `
export function registerComponentPrototype(proto) {
if (!proto) return;
if (!window.localTangoComponentPrototypes) {
window.localTangoComponentPrototypes = {};
}
if (proto.name) {
window.localTangoComponentPrototypes[proto.name] = proto;
}
}
`;
const routesCode = `
import Index from "./pages/list";
@@ -144,16 +149,83 @@ import {
Button,
Input,
FormilyForm,
FormilyFormItem,
} from "@music163/antd";
import { Space } from "@music163/antd";
import { LocalButton } from "../components";
class App extends React.Component {
render() {
return (
<Page title={tango.stores.app.title}>
<Section>
</Section>
<Section>
<Button>button</Button>
<Input />
<Section tid="section1" title="Section Title">
your input: <Input tid="input1" defaultValue="hello" />
copy input: <Input value={tango.page.input1?.value} />
</Section>
<Section tid="section2">
<Space tid="space1">
<LocalButton />
<Button tid="button1">button</Button>
</Space>
</Section>
<Section title="区块标题" tid="section3">
<FormilyForm tid="formilyForm1">
<FormilyFormItem name="input1" component="Input" label="表单项" />
<FormilyFormItem name="select1" component="Select" label="表单项" />
</FormilyForm>
</Section>
<Section title="原生 DOM" tid="section4">
<div
style={{
border: "1px solid #ccc",
borderRadius: "5px",
backgroundColor: "#f4f4f4",
}}
>
<form onSubmit={tango.stores.app.submitForm} style={{
padding: "10px",
}}>
<div>
<label>Username:</label>
<input
type="text"
id="username"
name="username"
style={{ margin: "5px" }}
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
id="password"
name="password"
style={{ margin: "5px" }}
/>
</div>
<div>
<label>Role:</label>
<select id="role" name="role" style={{ margin: "5px" }}>
<option value="admin">Admin</option>
<option value="user">User</option>
</select>
</div>
<div>
<button
type="submit"
style={{
marginTop: '5px',
padding: "3px 10px",
backgroundColor: "#007bff",
color: "#fff",
border: "none",
borderRadius: "5px",
}}
>
Submit
</button>
</div>
</form>
</div>
</Section>
</Page>
);
@@ -162,14 +234,69 @@ class App extends React.Component {
export default definePage(App);
`;
const componentsButtonCode = `
import React from 'react';
import { registerComponentPrototype } from '../utils';
export default function MyButton(props) {
return <button {...props}>my button</button>
}
registerComponentPrototype({
name: 'LocalButton',
title: 'Local Button',
exportType: 'namedExport',
package: '/src/components',
props: [
{ name: 'background', title: '背景色', setter: 'colorSetter' },
],
});
`;
const componentsInputCode = `
import React from 'react';
import { registerComponentPrototype } from '../utils';
export default function MyInput(props) {
return <input {...props} />;
}
registerComponentPrototype({
name: 'LocalInput',
title: 'Local Input',
exportType: 'namedExport',
package: '/src/components',
props: [
{ name: 'color', title: '文本色', setter: 'colorSetter' },
],
});
`;
const componentsEntryCode = `
export { default as LocalButton } from './button';
export { default as LocalInput } from './input';
`;
const storeApp = `
import { defineStore } from '@music163/tango-boot';
export default defineStore({
title: 'Page Title',
array: [1, 2, 3],
test: function() {
console.log('test');
},
submitForm: function(event) {
event.preventDefault();
const formData = new FormData(event.target);
const username = formData.get("username");
const password = formData.get("password");
const role = formData.get("role");
console.log("Submitted Data:", { username, password, role });
}
}, 'app');
`;
@@ -178,6 +305,13 @@ import { defineServices } from '@music163/tango-boot';
import './sub';
export default defineServices({
longLongLongLongLongLongLongLongGet: {
url: 'https://nei.hz.netease.com/api/apimock-v2/cc974ffbaa7a85c77f30e4ce67deb67f/api/getUserProfile',
formatter: res => res.data,
headers: {
'Content-Type': 'application/json',
}
},
get: {
url: 'https://nei.hz.netease.com/api/apimock-v2/cc974ffbaa7a85c77f30e4ce67deb67f/api/getUserProfile',
formatter: res => res.data,
@@ -233,20 +367,22 @@ p {
export const sampleFiles = [
{ filename: '/package.json', code: JSON.stringify(packageJson) },
{ filename: '/appJson.json', code: JSON.stringify(appJson) },
{ filename: '/tango.config.json', code: JSON.stringify(tangoConfigJson) },
{ filename: '/README.md', code: '# readme' },
{ filename: '/src/index.less', code: lessCode },
{ filename: '/src/style.css', code: cssCode },
{ filename: '/src/index.js', code: entryCode },
{ filename: '/src/pages/list.js', code: viewHomeCode },
{ filename: '/src/components/button.js', code: componentsButtonCode },
{ filename: '/src/components/input.js', code: componentsInputCode },
{ filename: '/src/components/index.js', code: componentsEntryCode },
{ filename: '/src/routes.js', code: routesCode },
{ filename: '/src/stores/index.js', code: storeIndexCode },
{ filename: '/src/stores/app.js', code: storeApp },
{ filename: '/src/stores/counter.js', code: storeCounter },
{ filename: '/src/services/index.js', code: serviceCode },
{ filename: '/src/services/sub.js', code: subServiceCode },
{ filename: '/src/utils/index.js', code: `export function foo() {}` },
{ filename: '/src/utils/index.js', code: helperCode },
];
export const genDefaultPage = (index: number) => ({
+239
View File
@@ -0,0 +1,239 @@
import * as basePrototypes from '@music163/antd/prototypes';
import type { IComponentPrototype, Dict } from '@music163/tango-helpers';
const sampleBlockCode = `
<Section>
<Result
status="success"
title="Successfully Purchased Cloud Server ECS!"
subTitle="Order number: 2017182818828182881 Cloud server configuration takes 1-5 minutes, please wait."
extra={[
<Button type="primary" key="console">
Go Console
</Button>,
<Button key="buy">Buy Again</Button>,
]}
/>
</Section>
`;
const SnippetSuccessResult: IComponentPrototype = {
name: 'SnippetSuccessResult',
title: '成功结果',
icon: 'icon-tupian',
type: 'snippet',
package: '@music163/antd',
initChildren: sampleBlockCode,
relatedImports: ['Section', 'Result', 'Button'],
};
const Snippet2ColumnLayout: IComponentPrototype = {
name: 'Snippet2ColumnLayout',
title: '两列布局',
icon: 'icon-columns',
type: 'snippet',
package: '@music163/antd',
initChildren: `
<Columns columns={12}>
<Column colSpan={6}></Column>
<Column colSpan={6}></Column>
</Columns>
`,
relatedImports: ['Columns', 'Column'],
};
const Snippet3ColumnLayout: IComponentPrototype = {
name: 'Snippet3ColumnLayout',
title: '三列布局',
icon: 'icon-column3',
type: 'snippet',
package: '@music163/antd',
initChildren: `
<Columns columns={12}>
<Column colSpan={4}></Column>
<Column colSpan={4}></Column>
<Column colSpan={4}></Column>
</Columns>
`,
relatedImports: ['Columns', 'Column'],
};
const SnippetButtonGroup: IComponentPrototype = {
name: 'SnippetButtonGroup',
title: '按钮组',
icon: 'icon-anniuzu',
type: 'snippet',
package: '@music163/antd',
initChildren: `
<Space>
<Button type="primary">按钮1</Button>
<Button>按钮2</Button>
</Space>
`,
relatedImports: ['Space', 'Button'],
};
// hack some prototypes
basePrototypes['Section'].siblingNames = [
'SnippetButtonGroup',
'Section',
'Section',
'Section',
'Section',
'Section',
'Section',
'Section',
];
export const nativeDomPrototypes = () => {
const doms = [
'div',
'span',
'h1',
'h2',
'p',
'a',
'img',
'ul',
'ol',
'li',
'input',
'button',
'form',
'table',
'tr',
'td',
'header',
'footer',
'nav',
'section',
'article',
'aside',
'main',
'video',
'audio',
'label',
'select',
'option',
'textarea',
'iframe',
];
const componentPrototypes: Dict<IComponentPrototype> = doms.reduce(
(acc: Dict<IComponentPrototype>, tag) => {
acc[tag] = {
name: tag,
title: tag,
hasChildren: true,
package: '',
type: 'element',
props: [
{
name: 'style',
title: '样式',
group: 'style',
setter: 'expressionSetter',
},
{
name: 'className',
title: '类名',
setter: 'textSetter',
},
{
name: 'id',
title: 'ID',
setter: 'textSetter',
},
{
name: 'onClick',
title: '点击事件',
setter: 'actionSetter',
group: 'event',
},
],
};
return acc;
},
{},
);
return componentPrototypes;
};
// iconfont: https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=2891794
const prototypes: Dict<IComponentPrototype> = {
...nativeDomPrototypes(),
...(basePrototypes as any),
SnippetSuccessResult,
Snippet2ColumnLayout,
Snippet3ColumnLayout,
SnippetButtonGroup,
Box: {
name: 'Box',
title: '盒子',
icon: 'icon-mianban',
type: 'container',
package: '@music163/antd',
hasChildren: true,
siblingNames: ['Box'],
props: [
{
name: 'aaa',
title: 'aaa',
setter: 'textSetter',
},
{
name: 'bbb',
title: 'bbb',
setter: 'textSetter',
deprecated: true,
},
{
name: 'ccc',
title: 'ccc',
setter: 'textSetter',
deprecated: true,
},
{
name: 'd',
title: 'd',
setter: 'textSetter',
},
],
},
Columns: {
name: 'Columns',
type: 'container',
icon: 'icon-column-4',
package: '@music163/antd',
hasChildren: true,
childrenNames: ['Column'],
},
Column: {
name: 'Column',
type: 'container',
icon: 'icon-juxing',
package: '@music163/antd',
hasChildren: true,
siblingNames: ['Column'],
},
Text: {
name: 'Text',
type: 'element',
icon: 'icon-wenzi',
package: '@music163/antd',
initChildren: '文本内容',
},
Placeholder: {
name: 'Placeholder',
type: 'element',
package: '@music163/antd',
},
ButtonGroup: {
name: 'ButtonGroup',
type: 'element',
package: '@music163/antd',
hasChildren: true,
childrenNames: ['Button'],
},
};
export default prototypes;
@@ -2,4 +2,5 @@
@import '~antd/dist/antd.less'; // 引入官方提供的 less 样式入口文件
@primary-color: #2f54eb;
@border-radius-base: 2px;
@border-radius-base: 2px;
+11
View File
@@ -0,0 +1,11 @@
import React, { StrictMode } from 'react';
import { Outlet } from 'umi';
import './index.less';
export default function Layout() {
return (
<StrictMode>
<Outlet />
</StrictMode>
);
}
+9
View File
@@ -0,0 +1,9 @@
const DocsPage = () => {
return (
<div>
<p>This is umi docs.</p>
</div>
);
};
export default DocsPage;
-21
View File
@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tango Playground</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@16.14.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-is@16.13.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/moment/min/moment-with-locales.js"></script>
<script src="https://unpkg.com/styled-components@5.3.11/dist/styled-components.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.15.6/babel.js"></script>
<script src="https://unpkg.com/prettier@2.6.0/standalone.js"></script>
<script src="https://unpkg.com/prettier@2.6.0/parser-babel.js"></script>
<script src="https://unpkg.com/antd@4.24.13/dist/antd-with-locales.min.js"></script>
</body>
</html>
+68 -19
View File
@@ -1,4 +1,3 @@
import React, { useState } from 'react';
import { Box } from 'coral-system';
import { Button, Space } from 'antd';
import {
@@ -12,25 +11,30 @@ import {
CodeEditor,
Sandbox,
DndQuery,
themeLight,
} from '@music163/tango-designer';
import { createEngine, Workspace } from '@music163/tango-core';
import { Logo, ProjectDetail } from './share';
import { sampleFiles } from '../mock/project';
import './index.less';
import prototypes from '../helpers/prototypes';
import { Logo, ProjectDetail, bootHelperVariables, sampleFiles } from '../helpers';
import {
ApiOutlined,
AppstoreAddOutlined,
BuildOutlined,
ClusterOutlined,
FunctionOutlined,
createFromIconfontCN,
} from '@ant-design/icons';
// 1. 实例化工作区
const workspace = new Workspace({
entry: '/src/index.js',
files: sampleFiles,
prototypes,
});
// inject workspace to window for debug
(window as any).__workspace__ = workspace;
// 2. 引擎初始化
const engine = createEngine({
workspace,
@@ -39,18 +43,60 @@ const engine = createEngine({
// @ts-ignore
window.__workspace__ = workspace;
// 3. 沙箱初始化
const sandboxQuery = new DndQuery({
context: 'iframe',
});
// 4. 图标库初始化(物料面板和组件树使用了 iconfont 里的图标)
createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_2891794_6d4hj5u0bjx.js',
});
const menuData = {
common: [
{
title: '常用',
items: [
'Button',
'Section',
'Columns',
'Column',
'Box',
'Text',
'Space',
'Typography',
'Title',
'Paragraph',
'Table',
'Each',
],
},
{
title: '输入',
items: ['Input', 'InputNumber', 'Select'],
},
{
title: 'Formily表单',
items: ['FormilyForm', 'FormilyFormItem', 'FormilySubmit', 'FormilyReset'],
},
],
};
/**
* 3. 平台初始化,访问 https://local.netease.com:6006/
* 5. 平台初始化,访问 https://local.netease.com:6006/
*/
export default function App() {
const [menuLoading, setMenuLoading] = useState(true);
const [menuData, setMenuData] = useState(false);
return (
<Designer engine={engine} sandboxQuery={sandboxQuery}>
<Designer
theme={themeLight}
engine={engine}
config={{
customActionVariables: bootHelperVariables,
customExpressionVariables: bootHelperVariables,
}}
sandboxQuery={sandboxQuery}
>
<DesignerPanel
logo={<Logo />}
description={<ProjectDetail />}
@@ -58,6 +104,7 @@ export default function App() {
<Box px="l">
<Toolbar>
<Toolbar.Item key="routeSwitch" placement="left" />
<Toolbar.Item key="history" placement="left" />
<Toolbar.Item key="preview" placement="left" />
<Toolbar.Item key="modeSwitch" placement="right" />
<Toolbar.Item key="togglePanel" placement="right" />
@@ -72,16 +119,15 @@ export default function App() {
}
>
<Sidebar>
<Sidebar.Item key="outline" label="结构" icon={<BuildOutlined />} />
<Sidebar.Item
key="components"
label="组件"
icon={<AppstoreAddOutlined />}
widgetProps={{
menuData: menuData as any,
loading: menuLoading,
menuData,
}}
/>
<Sidebar.Item key="outline" label="结构" icon={<BuildOutlined />} />
<Sidebar.Item
key="variables"
label="变量"
@@ -104,17 +150,20 @@ export default function App() {
onMessage={(e) => {
if (e.type === 'done') {
const sandboxWindow: any = sandboxQuery.window;
if (sandboxWindow.TangoAntd) {
if (sandboxWindow.TangoAntd.menuData) {
setMenuData(sandboxWindow.TangoAntd.menuData);
}
if (sandboxWindow.TangoAntd.prototypes) {
workspace.setComponentPrototypes(sandboxWindow.TangoAntd.prototypes);
}
// if (sandboxWindow.TangoAntd) {
// if (sandboxWindow.TangoAntd.menuData) {
// setMenuData(sandboxWindow.TangoAntd.menuData);
// }
// if (sandboxWindow.TangoAntd.prototypes) {
// workspace.setComponentPrototypes(sandboxWindow.TangoAntd.prototypes);
// }
// }
if (sandboxWindow.localTangoComponentPrototypes) {
workspace.setComponentPrototypes(sandboxWindow.localTangoComponentPrototypes);
}
setMenuLoading(false);
}
}}
navigatorExtra={<Button size="small">hello world</Button>}
/>
</WorkspaceView>
<WorkspaceView mode="code">
+134
View File
@@ -0,0 +1,134 @@
import React, { useState } from 'react';
import { Box } from 'coral-system';
import { Button, Space } from 'antd';
import {
Designer,
DesignerPanel,
SettingPanel,
Sidebar,
Toolbar,
WorkspacePanel,
WorkspaceView,
CodeEditor,
Sandbox,
DndQuery,
themeLight,
} from '@music163/tango-designer';
import { createEngine, Workspace } from '@music163/tango-core';
import { Logo, ProjectDetail, bootHelperVariables } from '@/helpers';
import {
AppstoreAddOutlined,
BuildOutlined,
ClusterOutlined,
createFromIconfontCN,
} from '@ant-design/icons';
import { mailFiles } from '@/helpers/mail-files';
// 1. 实例化工作区
const workspace = new Workspace({
entry: '/src/index.js',
files: mailFiles,
});
// 2. 引擎初始化
const engine = createEngine({
workspace,
});
// @ts-ignore
window.__mailWorkspace__ = workspace;
// 3. 沙箱初始化
const sandboxQuery = new DndQuery({
context: 'iframe',
});
// 4. 图标库初始化(物料面板和组件树使用了 iconfont 里的图标)
createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_2891794_cou9i7556tl.js',
});
/**
* 5. 平台初始化,访问 https://local.netease.com:6006/
*/
export default function App() {
const [menuLoading, setMenuLoading] = useState(true);
const [menuData, setMenuData] = useState(false);
return (
<Designer
theme={themeLight}
engine={engine}
config={{
customActionVariables: bootHelperVariables,
customExpressionVariables: bootHelperVariables,
}}
sandboxQuery={sandboxQuery}
>
<DesignerPanel
logo={<Logo />}
description={<ProjectDetail />}
actions={
<Box px="l">
<Toolbar>
<Toolbar.Item key="history" placement="left" />
<Toolbar.Item key="preview" placement="left" />
<Toolbar.Item key="modeSwitch" placement="right" />
<Toolbar.Item key="togglePanel" placement="right" />
<Toolbar.Separator />
<Toolbar.Item placement="right">
<Space>
<Button type="primary"></Button>
</Space>
</Toolbar.Item>
</Toolbar>
</Box>
}
>
<Sidebar>
<Sidebar.Item
key="components"
label="组件"
icon={<AppstoreAddOutlined />}
widgetProps={{
menuData: menuData as any,
loading: menuLoading,
}}
/>
<Sidebar.Item key="outline" label="结构" icon={<BuildOutlined />} />
<Sidebar.Item
key="dependency"
label="依赖"
icon={<ClusterOutlined />}
isFloat
width={800}
/>
</Sidebar>
<WorkspacePanel>
<WorkspaceView mode="design">
<Sandbox
onMessage={(e) => {
if (e.type === 'done') {
const sandboxWindow: any = sandboxQuery.window;
if (sandboxWindow.TangoMail) {
if (sandboxWindow.TangoMail.menuData) {
setMenuData(sandboxWindow.TangoMail.menuData);
}
if (sandboxWindow.TangoMail.prototypes) {
workspace.setComponentPrototypes(sandboxWindow.TangoMail.prototypes);
}
}
setMenuLoading(false);
}
}}
navigatorExtra={<Button size="small">hello world</Button>}
/>
</WorkspaceView>
<WorkspaceView mode="code">
<CodeEditor />
</WorkspaceView>
</WorkspacePanel>
<SettingPanel />
</DesignerPanel>
</Designer>
);
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"extends": "../../tsconfig.json"
"extends": "./src/.umi/tsconfig.json"
}
+1
View File
@@ -0,0 +1 @@
import 'umi/typings';
+4 -2
View File
@@ -16,14 +16,16 @@
"dependencies": {
"@music163/tango-setting-form": "*",
"@music163/tango-ui": "*",
"mobx": "6.9.0",
"mobx-react-lite": "4.0.2"
"mobx": "6.12.0",
"mobx-react-lite": "4.0.5"
},
"devDependencies": {
"@ant-design/icons": "^4.8.0",
"@storybook/addon-actions": "^6.5.14",
"@storybook/addon-essentials": "^6.5.14",
"@storybook/addon-links": "^6.5.14",
"@storybook/react": "^6.5.14",
"antd": "^4.24.2",
"babel-plugin-styled-components": "^2.0.7",
"tsconfig-paths-webpack-plugin": "^3.5.2"
}
+70
View File
@@ -0,0 +1,70 @@
import React from 'react';
import { Box } from 'coral-system';
import { CodeSandbox } from '@music163/tango-sandbox';
import { JsonView } from '@music163/tango-ui';
export default {
title: 'CodeSandbox',
};
const entryFilename = '/index.js';
const packageJson = {
name: 'demo',
private: true,
main: entryFilename,
dependencies: {
react: '^17.0.1',
'react-dom': '^17.0.1',
},
};
const entryFile = `
import React from 'react';
import ReactDOM from "react-dom";
import App from './src';
const rootElement = document.getElementById("root");
ReactDOM.render(
<App />,
rootElement
);
`;
const srcAppFile = `
import React from 'react';
export default function App() {
return <div style={{ background: 'yellow', color: 'red' }}>hello world</div>;
}
`;
const files = {
'/package.json': {
code: JSON.stringify(packageJson),
},
[entryFilename]: {
code: entryFile,
},
'/src/index.js': { code: srcAppFile },
};
export function Basic() {
return (
<Box display="flex" height="100vh">
<CodeSandbox
template="create-react-app"
bundlerURL="https://codesandbox.fn.netease.com"
files={files}
entry={entryFilename}
style={{
flex: 1,
}}
/>
<Box width="30%">
<JsonView src={files} />
</Box>
</Box>
);
}
+120 -138
View File
@@ -1,60 +1,57 @@
import React, { useState } from 'react';
import { EditableVariableTree, FormModel, SettingForm } from '@music163/tango-setting-form';
import { ComponentPrototypeType, getValue } from '@music163/tango-helpers';
import { createServices } from '@music163/request';
import React from 'react';
import { FormModel, SettingForm, register } from '@music163/tango-setting-form';
import { IComponentPrototype } from '@music163/tango-helpers';
import { BorderSetter, DisplaySetter } from '@music163/tango-designer/src/setters/style-setter';
import { JsxSetter } from '@music163/tango-designer/src/setters/jsx-setter';
import { RenderSetter, TableCellSetter } from '@music163/tango-designer/src/setters/render-setter';
import { NumberSetter } from '@music163/tango-designer/src/setters/number-setter';
import { Box } from 'coral-system';
import { JsonView } from '@music163/tango-ui';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import { Card } from 'antd';
import { createFromIconfontCN } from '@ant-design/icons';
// 这里按需注入,因为部分 setter 依赖 Designer 的上下文
register({
name: 'borderSetter',
component: BorderSetter,
});
register({
name: 'displaySetter',
component: DisplaySetter,
});
register({
name: 'jsxSetter',
component: JsxSetter,
});
register({
name: 'renderSetter',
component: RenderSetter,
});
register({
name: 'tableCellSetter',
component: TableCellSetter,
});
register({
name: 'numberSetter',
component: NumberSetter,
});
createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_2891794_cou9i7556tl.js',
});
export default {
title: 'SettingForm/ Setters',
title: 'SettingForm',
};
const modelVariables = [
{
title: 'stores',
key: 'stores',
selectable: false,
children: [
{
title: 'app',
key: 'stores.app',
selectable: false,
children: [
{ title: 'app.title', key: 'stores.app.title', raw: '"hello"' },
{ title: 'app.age', key: 'stores.app.age', raw: '20' },
{ title: 'app.detail', key: 'stores.app.detail', raw: '{ foo: "foo" }' },
{ title: 'app.list', key: 'stores.app.list', type: 'function', raw: '() => {}' },
],
},
{
title: 'user',
key: 'stores.user',
selectable: false,
children: [
{ title: 'name', key: 'stores.user.name' },
{ title: 'age', key: 'stores.user.age' },
],
},
],
},
{
title: 'services',
key: 'services',
selectable: false,
children: [
{
title: 'listUsers',
key: 'services.listUsers',
type: 'function',
},
],
},
];
const prototype: ComponentPrototypeType = {
const prototype: IComponentPrototype = {
name: 'Test',
exportType: 'namedExport',
title: '测试',
@@ -68,6 +65,41 @@ const prototype: ComponentPrototypeType = {
name: 'text',
title: 'textSetter',
setter: 'textSetter',
tip: '这是一个文本属性',
docs: 'https://music-one.fn.netease.com/docs/button',
deprecated: '使用 text2 替代',
},
{
name: 'display',
title: 'displaySetter',
setter: 'displaySetter',
},
{
name: 'border',
title: 'borderSetter',
setter: 'borderSetter',
},
{
name: 'onClick',
title: 'eventSetter',
tip: '当点击按钮时',
setter: 'eventSetter',
group: 'event',
},
{
name: 'children',
title: 'jsxSetter',
setter: 'jsxSetter',
},
{
name: 'render',
title: 'renderSetter',
setter: 'renderSetter',
},
{
name: 'cell',
title: 'tableCellSetter',
setter: 'tableCellSetter',
},
{
name: 'router',
@@ -140,13 +172,7 @@ const prototype: ComponentPrototypeType = {
title: 'iconTypeSetter',
setter: 'iconTypeSetter',
},
{
name: 'onClick',
title: 'eventSetter',
tip: '当点击按钮时',
setter: 'eventSetter',
group: 'event',
},
{
name: 'onClick2',
title: 'actionSetter',
@@ -254,48 +280,23 @@ const prototype: ComponentPrototypeType = {
title: 'invalidSetter',
setter: 'invalidSetter',
},
],
};
const deerService = createServices(
{
listMy: {
url: '/my/upload/list',
},
listFav: {
url: '/my/star/list',
},
listPub: {
url: '/list',
},
},
{
baseURL: 'https://febase-openapi.fn.netease.com/deer/api/deer/pic',
withCredentials: false, // 解决跨域时必须非*问题
headers: {
'Febase-Auth': 'dskPVkIRnQ2dEn1DbyxURmUj4rl4BsCh53xFAsJnvVs=',
},
},
);
const context = {
stores: {
app: {
title: 'Sample App',
age: 20,
detail: {
foo: 'foo',
bar: 'bar',
biz: {
x: 'xxx',
},
{
name: 'validate',
title: 'validate',
setter: 'numberSetter',
validate: (value) => {
if (!value && value !== 0) {
return '必填';
}
if (value < 0) {
return '必须大于 0';
}
if (value > 10) {
return '必须小于等于 10';
}
},
newKey: 'xxx',
},
foo: {
test: 'test string',
},
},
],
};
/**
@@ -310,9 +311,7 @@ export function Basic() {
const model = new FormModel(
{
router: 'www.163.com',
expression: {
foo: 'foo',
},
expression: `{ foo: 'foo' }`,
object: {
name: 'Alice',
},
@@ -324,19 +323,15 @@ export function Basic() {
return (
<Box display="flex">
<SettingForm
model={model}
remoteServices={{
ImageService: deerService as any,
}}
prototype={prototype}
evaluateContext={{
tango: context,
__UNSAFE_TANGO_CURRENT_PAGE_RENDER_RUNTIME__: { routeData: { params: {}, query: {} } },
}}
modelVariables={modelVariables}
expressionVariables={modelVariables}
/>
<Box flex="0 0 400px" overflow="hidden">
<SettingForm
model={model}
prototype={prototype}
showIdentifier={{
identifierKey: 'tid',
}}
/>
</Box>
<Box position="relative">
<Card title="表单状态预览" style={{ position: 'sticky', top: 0 }}>
<FormValuePreview model={model} />
@@ -346,6 +341,20 @@ export function Basic() {
);
}
export function Lite() {
return (
<Box width={320} border="solid">
<SettingForm
showSearch={false}
showGroups={false}
showItemSubtitle={false}
prototype={prototype}
disableSwitchExpressionSetter
/>
</Box>
);
}
export function NoExpressionSwitch() {
const model = new FormModel({});
return (
@@ -354,30 +363,3 @@ export function NoExpressionSwitch() {
</Box>
);
}
export function Binding() {
const [data, setData] = useState<any>({});
return (
<Box>
<Box as="code" bg="highlight" color="white" fontSize="24px">
selected: {data?.key}
</Box>
<EditableVariableTree
dataSource={modelVariables as any}
getPreviewValue={(node) => {
if (!node || !node.key) {
return;
}
if (node.type === 'function') {
return node.raw;
}
const keyPath = node.key.replaceAll('?', '');
return getValue(context, keyPath);
}}
onSelect={setData}
onAddVariable={console.log}
onSave={console.log}
/>
</Box>
);
}
+68
View File
@@ -0,0 +1,68 @@
import React from 'react';
import { Action } from '@music163/tango-ui';
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { Space } from 'antd';
export default {
title: 'UI/Action',
component: Action,
};
export const Basic = {
args: {
icon: <PlusOutlined />,
tooltip: '添加服务函数',
},
};
export const Link = {
args: {
icon: <QuestionCircleOutlined />,
tooltip: '查看帮助文档',
href: 'https://music.163.com',
},
};
export const Outline = {
args: {
icon: <PlusOutlined />,
tooltip: '添加服务函数',
shape: 'outline',
},
};
export const Small = {
args: {
size: 'small',
icon: <PlusOutlined />,
tooltip: '添加服务函数',
},
};
export const Disabled = () => {
return (
<Space>
<Action icon={<PlusOutlined />} tooltip="添加服务函数" disabled />
<Action icon={<PlusOutlined />} tooltip="添加服务函数" disabled shape="outline" />
<Action
icon={<QuestionCircleOutlined />}
tooltip="查看帮助文档"
href="https://music.163.com"
disabled
/>
</Space>
);
};
export const List = () => {
return (
<Space>
<Action icon={<PlusOutlined />} tooltip="添加服务函数" />
<Action
icon={<QuestionCircleOutlined />}
tooltip="查看帮助文档"
href="https://music.163.com"
/>
</Space>
);
};
+37 -2
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { IconButton, InputCode } from '@music163/tango-ui';
import { Action, InputCode } from '@music163/tango-ui';
import { BlockOutlined } from '@ant-design/icons';
export default {
@@ -22,12 +22,47 @@ const context = {
export function Basic() {
return (
<InputCode
suffix={<IconButton icon={<BlockOutlined />} />}
suffix={<Action icon={<BlockOutlined />} size="small" />}
autoCompleteContext={{ tango: context }}
/>
);
}
const code = `
function foo() {
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
console.log("test");
}
`;
export function Readonly() {
return <InputCode shape="inset" readOnly value={code} />;
}
export function Inset() {
return <InputCode shape="inset" />;
}
+57 -12
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { ToggleButton, IconFont } from '@music163/tango-ui';
import { Box, Flex } from 'coral-system';
import { ToggleButton, UndoOutlined, RedoOutlined } from '@music163/tango-ui';
import { Box, Group, Flex } from 'coral-system';
export default {
title: 'UI/ToggleButton',
@@ -9,28 +9,73 @@ export default {
export function Basic() {
return (
<Box>
<Flex gap="l" bg="#efefef" p="l">
<Box display="flex" columnGap="l" p="l">
<ToggleButton>
<IconFont type="icon-undo" />
<UndoOutlined />
</ToggleButton>
<ToggleButton selected>
<IconFont type="icon-redo" />
<RedoOutlined />
</ToggleButton>
<ToggleButton disabled>
<IconFont type="icon-redo" />
<RedoOutlined />
</ToggleButton>
</Box>
<Box display="flex" columnGap="l" p="l">
<ToggleButton type="primary">
<UndoOutlined />
</ToggleButton>
<ToggleButton type="primary" selected>
<RedoOutlined />
</ToggleButton>
<ToggleButton type="primary" disabled>
<RedoOutlined />
</ToggleButton>
</Box>
</Box>
);
}
export function DarkMode() {
return (
<Box>
<Flex gap="l" bg="#222" p="l">
<ToggleButton shape="ghost">
<UndoOutlined />
</ToggleButton>
<ToggleButton shape="ghost" selected>
<RedoOutlined />
</ToggleButton>
<ToggleButton shape="ghost" disabled>
<UndoOutlined />
</ToggleButton>
</Flex>
<Flex gap="l" bg="#222" p="l">
<ToggleButton shape="ghost">
<IconFont type="icon-undo" />
<ToggleButton shape="text">
<UndoOutlined />
</ToggleButton>
<ToggleButton shape="ghost" selected>
<IconFont type="icon-redo" />
<ToggleButton shape="text" selected>
<RedoOutlined />
</ToggleButton>
<ToggleButton shape="ghost" disabled>
<IconFont type="icon-undo" />
<ToggleButton shape="text" disabled>
<RedoOutlined />
</ToggleButton>
</Flex>
</Box>
);
}
export function ButtonGroup() {
return (
<Group attached>
<ToggleButton>
<UndoOutlined />
</ToggleButton>
<ToggleButton selected>
<RedoOutlined />
</ToggleButton>
<ToggleButton disabled>
<RedoOutlined />
</ToggleButton>
</Group>
);
}
@@ -0,0 +1,72 @@
import React from 'react';
import { VariableTree } from '@music163/tango-designer/src/components';
import { Box } from 'coral-system';
export default {
title: 'Designer/VariableTree',
};
const data = [
{
title: 'Stores',
key: 'stores',
selectable: false,
children: [
{
title: 'user',
key: 'stores.user',
children: [
{
title: 'name',
key: 'stores.user.name',
raw: '"Tom"',
},
{
title: 'age',
key: 'stores.user.age',
raw: '18',
},
],
},
{
title: 'book',
key: 'stores.book',
children: [
{
title: 'name',
key: 'stores.book.name',
raw: '"JavaScript 高级程序设计"',
},
{
title: 'price',
key: 'stores.book.price',
raw: '99.99',
},
],
},
],
},
{
title: 'Services',
key: 'services',
selectable: false,
children: [
{
title: 'add',
key: 'services.add',
},
{
title: 'multiply',
key: 'services.multiply',
},
],
},
];
export function Basic() {
return (
<Box width={600} border="solid">
<VariableTree dataSource={data} />
</Box>
);
}
-20
View File
@@ -1,20 +0,0 @@
# 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*
-41
View File
@@ -1,41 +0,0 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ 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.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
-3
View File
@@ -1,3 +0,0 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

@@ -1,76 +0,0 @@
---
title: Tango LowCode Builder Released!
authors: [wwsun]
tags: [netease, tango, open-source]
---
## 📝 Tango 简介
Tango 是一个用于快速构建低代码平台的低代码设计器框架,借助 Tango 只需要数行代码就可以完成一个基本的低代码平台前端系统的搭建。Tango 低代码设计器直接读取前端项目的源代码,并以源代码为中心,执行和渲染前端视图,并为用户提供低代码可视化搭建能力,用户的搭建操作会转为对源代码的修改。借助于 Tango 构建的低代码工具或平台,可以实现 源码进,源码出的效果,无缝与企业内部现有的研发体系进行集成。
### ✨ 核心特性
- 经历网易云音乐内网生产环境的实际检验,可灵活集成应用于低代码平台,本地开发工具等
- 基于源码 AST 驱动,无私有 DSL 和协议
- 提供实时出码能力,支持源码进,源码出
- 开箱即用的前端低代码设计器,提供灵活易用的设计器 React 组件
- 使用 TypeScript 开发,提供完整的类型定义文件
### 🏗️ 基于源码的低代码搭建方案
Tango 低代码引擎不依赖私有搭建协议和 DSL,而是直接使用源代码驱动,引擎内部将源码转为 AST,用户的所有的搭建操作转为对 AST 的遍历和修改,进而将 AST 重新生成为代码,将代码同步给在线沙箱执行。与传统的 [基于 Schema 驱动的低代码方案](https://mp.weixin.qq.com/s/yqYey76qLGYPfDtpGkVFfA) 相比,不受私有 DSL 和协议的限制,能够完美的实现低代码搭建与源码开发的无缝集成。
<img src="https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13140534982/ee2e/f42c/cc9a/184e2918a011b57d46e6c64a2722fa44.png" />
### 📄 源码进,源码出
由于引擎内核完全基于源代码驱动实现,Tango 低代码引擎能够实现源代码进,源代码出的可视化搭建能力,不提供任何私有的中间产物。如果公司内部已经有了一套完善的研发体系(代码托管、构建、部署、CDN),那么可以直接使用 Tango 低代码引擎与现有的服务集成构建低代码开发平台。
![code in, code out](./codein-codeout.png)
### 🏆 产品优势
与基于私有 Schema 的低代码搭建方案相比,Tango 低代码引擎具有如下优势:
| 对比项 | 基于 Schema 的低代码搭建方案 | Tango(基于源码 AST 转换) |
| ------------ | -------------------------------------------------- | --------------------------------------------------------------------- |
| 适用场景 | 面向特定的垂直搭建场景,例如表单,营销页面等 | 🔥 面面向以源码为中心的应用搭建场景 |
| 语言能力 | 依赖私有协议扩展,不灵活,且难以与编程语言能力对齐 | 🔥 直接基于 JavaScript 语言,可以使用所有的语言特性,不存在扩展性问题 |
| 开发能力 | LowCode | 🔥 LowCode + ProCode |
| 源码导出 | 以 Schema 为中心,单向出码,不可逆 | 🔥 以源码为中心,双向转码 |
| 自定义依赖 | 需要根据私有协议扩展封装,定制成本高 | 🔥 原有组件可以无缝低成本接入 |
| 集成研发设施 | 定制成本高,需要额外定制 | 🔥 低成本接入,可以直接复用原有的部署发布能力 |
## 📐 技术架构
Tango 低代码引擎在实现上进行了分层解藕,使得上层的低代码平台与底层的低代码引擎可以独立开发和维护,快速集成部署。此外,Tango 低代码引擎定义了一套开放的物料生态体系,开发者可以自由的贡献扩展组件配置能力的属性设置器,以及扩展低代码物料的二方三方业务组件。
具体的技术架构如下图所示:
![low-code engine](./lowocode-engine.png)
## ⏰ 开源里程碑
Tango 低代码引擎是网易云音乐内部低代码平台的核心构件,开源涉及到大量的核心逻辑解藕的工作,这将给我们正常的工作带来大量的额外工作,因此我们计划分阶段推进 Tango 低代码引擎的开源事项。
1. 今天我们正式发布 Tango 低代码引擎的第一个社区版本,该版本将会包括 Tango 低代码引擎的核心代码库,TangoBoot 应用框架,以及基于 antd v4 适配的低代码组件库。
2. 我们计划在今年的 **9 月 30 日** 发布低代码引擎的 **1.0 Beta** 版本,该版本将会对核心的实现面向社区场景重构,移除掉我们在云音乐内部的一些兼容代码,并将核心的实现进行重构和优化。
3. 我们计划在今年的 **10 月 30 日** 发布低代码引擎的 **1.0 RC** 版本,该版本将会保证核心 API 基本稳定,不再发生 BREAKING CHANGE,同时我们将会提供完善翔实的开发指南、部署文档、和演示应用。
4. **正式版**本我们将在 **2023 年 Q4 结束前** 发布,届时我们会进一步完善我们的开源社区运营机制。
![milestones](./tango-milestones.png)
## 🤝 社区建设
我们的开源工作正在积极推进中,可以通过如下的信息了解到我们的最新进展:
- Github 仓库:<https://github.com/NetEase/tango>
- 文档站点:<https://netease.github.io/tango/>
欢迎大家加入到我们的社区中来,一起参与到 Tango 低代码引擎的开源建设中来。有任何问题都可以通过 [Github Issues](https://github.com/NetEase/tango/issues) 反馈给我们,我们会及时跟进处理。
## 💗 致谢
感谢网易云音乐公共技术团队,大前端团队,直播技术团队,以及所有参与过 Tango 项目的同学们。
感谢 CodeSandbox 提供的 [Sandpack](https://sandpack.codesandbox.io/) 项目,为 Tango 提供了强大的基于浏览器的代码构建与执行能力。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

-5
View File
@@ -1,5 +0,0 @@
wwsun:
name: Wells SUN
title: Core maintainer
url: https://github.com/wwsun
image_url: https://github.com/wwsun.png
-38
View File
@@ -1,38 +0,0 @@
# 目录结构
## 目录结构规范
在目录结构上,我们约定了一套通用的目录结构
```md
── src
│ +── assets
│ +── pages
│ +── components
│ +── services/index.js
│ +── stores
│ ├── routes.js // 路由配置
│ ├── global.less // 全局样式
| |-- index.js 应用启动配置
└── package.json
|---tango.config.json 设计器和 external 配置
```
## `src/components` 自定义组件
该目录放置本地开发的组件,例如
```jsx
// src/components/sample.jsx
import React from 'react';
import { Box } from '@music163/antd';
// 如果需要被搭建,使用 Box 代替 div,并且支持透出属性
export function Sample(props) {
return <Box {...props}>sample component</Box>;
}
```
:::info
注意,如果需要 Sample 组件能够被设计器解析和搭建,则一定要使用 MusicOne 的 `Box` 组件代替普遍的 `div` 元素,你也可以使用 `<Box as="span" />` 来改变渲染的 html 元素。
:::
-134
View File
@@ -1,134 +0,0 @@
# 应用入口
框架通过 `runApp` 创建渲染整个应用,在创建应用时可以传入应用的全局配置。
## 基本配置
通过 `src/index.js` 对应用进行全局配置,设置路由、运行时环境、服务函数、状态模型等。
```js
import { runApp, tangoBootConfig } from '@music163/tango-boot';
import { message } from '@music163/antd';
// 配置请求错误发生时消息默认的展现方式,此处使用 message 组件进行消息浮层的展示
tangoBootConfig.toast = message;
runApp({
// 启动项配置
boot: {},
// 应用容器注入
providers: [],
// 状态模型
stores: {},
// 服务函数
services: {},
// 路由配置
router: {},
});
```
## 启动项配置
`boot` 配置项用于应用的启动项配置:
```js
runApp({
boot: {
// 应用挂载的 dom 结点
mountElement: document.querySelector('#root'),
// 是否开启 qiankun,如开启则对应为 `qiankun: { appName: string }`
qiankun: false,
},
});
```
## 路由配置
`router` 配置项用于配置应用的前端路由,底层基于 `react-router` 实现:
```js
import routes from './routes';
runApp({
router: {
// 路由类型: hash | browser
type: 'hash',
// 路由配置信息
config: routes,
// basename,
},
});
```
TangoBoot 使用的是 ReactRouter 的[静态路由配置方案](https://github.com/remix-run/react-router/tree/v5/packages/react-router-config)。具体的路由配置文件如下:
```js
// routes.js
import Index from './pages/index';
import About from './pages/about';
const routes = [
{
path: '/',
exact: true,
component: Index,
},
{
path: '/about',
component: About,
},
{
path: '/user/:id',
component: About,
},
];
export default routes;
```
## 应用容器配置
由于 `runApp` 封装了根组件的渲染逻辑,某些时候,你可能想要为根组件包裹特定的容器类组件,例如多语言配置,状态容器等等,可以借助 `providers` 配置项进行:
```js
runApp({
// 传入 providers 组件实例,按照传入顺序进行包裹
providers: [<ConfigProvider />, <LocaleProvider />],
});
```
## 设置环境变量 {#env}
某些时候你可能需要自定义一些环境变量,以便在应用运行在不同的环境中的时候进行快速的判断。你可以借助 `tango.env` 来快速的获取到应用的环境信息。需要注意的是,默认情况下不会有任何的环境信息,你可以在需要的时候通过在 `runApp` 中传入 `getEnv` 设置的方式来设置你想要的环境变量。
例如:
```js
runApp({
// 自定义应用的环境信息,你可以返回对象
getEnv() {
if (location.origin.includes('tango')) {
// tango 设计器中
return 'development';
}
if (location.origin.includes('localhost')) {
// 本地开发
return 'local';
}
return 'production';
},
});
```
在任意地方消费环境变量,例如:
```jsx
<Box isRender={tango.env === 'development'}>一个仅在开发态显示的区域</Box>
```
-87
View File
@@ -1,87 +0,0 @@
# 全局单例
TangoBoot 默认提供全局单例模式,意味着,您可以在项目中始终使用该全局单例来进行必要的应用运行时信息的访问。
:::tip
单例意味着单一实例,也叫单例模式,是一种比较常见的设计模式。限制类实例化次数只能一次,一个类只有一个实例,并提供一个访问它的全局访问点。TangoBoot 单例意味着您的应用各种始终只有一个 TangoBoot 实例,并且可以借助该单一实例,一致的获取到应用到运行时信息。
:::
## 如何获取全局单例
```js
// 从模块包引入
import tango from '@music163/tango-boot';
// 或直接从 window 获取
window.tango;
// 两者是对等的
console.log(tango === window.tango); // true
```
### 可以从 `tango` 单例中获取的信息
```js
// tango
{
// 自定义配置信息
config: {},
// 自定义辅助函数
helpers: {},
// History
history: History;
// 服务函数集合
services: {},
// 状态模型集合
stores: {},
// builtin helpers
closeModal: Function;
openModal: Function;
setStoreValue: Function;
getStoreValue: Function;
// ...
}
```
## 如何使用
### 在模型定义中使用
```jsx
import tango, { defineModel } from '@music163/tango-boot';
export default defineModel(
{
title: 'hello',
async listUsers() {
const data = await tango.services.listUsers();
this.title = data;
},
},
'app'
);
```
### 在视图中使用
```jsx
import tango, { definePage } from '@music163/tango-boot';
function App() {
return (
<Box>
{tango.stores.app.title}
<Button onClick={tango.services.list}>request</Button>
</Box>
);
}
export default definePage(App);
```
-130
View File
@@ -1,130 +0,0 @@
# 常用的工具函数
tango-boot 提供了一组便捷的工具函数,用于应对日常开发过程中的典型操作逻辑。
### `getUser()`
> 从 v1.15.0 开始提供
获取当前的登录用户
```js
const user = tango.getUser();
```
### `checkPrivilege({ appCode, privilegeCode })`
> 从 v1.15.0 开始提供
检查 PMS 权限
```js
const test = await tango.checkPrivilege({ appCode, privilegeCode });
```
### `copyToClipboard(text: string)`
复制文本到剪贴板
```js
tango.copyToClipboard('hello world');
```
### `navigateTo(link: string, data?: object)`
导航到其他前端路由
```js
// 基本用法
tango.navigateTo('/about');
// 带路由参数
tango.navigateTo('/about', { the: 'query' }); // /about?the=query
```
### `showToast(text: string, type: string, time: number)`
- text 消息正文
- type 消息类型 `success | error | warn`
- time 消息展示的时间,单位秒,默认为 3 秒
消息提示
```js
tango.showToast('hello world');
tango.showToast('something is error', 'error', 3);
```
### `openModal(modalId: string)`
根据弹层 ID 唤起目标弹层
```js
tango.openModal('modal123');
```
### `closeModal(modalId: string)`
根据弹层 ID 关闭目标弹层
```js
tango.closeModal('modal123');
```
### `formatDate(date, format)`
- date: `number | string | Moment` 日期值
- format: `string` 格式化,取值参考 https://momentjs.com/docs/#/displaying/format/
格式化日期值
```js
tango.formatDate(1659496225323, 'YYYY-MM-DD HH:mm:ss'); // 2022-08-03 11:10:25
tango.formatDate('2022-11-11', 'X'); // 1668096000
```
### `formatNumber(number, locale, options)`
- number: `number`
- locale: `string`
- options: `Intl.NumberFormatOptions`,参考取值 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
格式化数字
```js
tango.formatNumber(99999); // 99,999
```
### `getCurrentLanguage`
获取当前语种
```js
tango.getCurrentLanguage(); // en-US
```
### `setLanguage(localeCode)`
设置当前语种
```js
tango.setLanguage('en-US');
```
### `getLanguageList`
获取当前应用可用语种列表
```js
tango.getLanguageList();
```
### `trans(module:key)`
根据多语言 key 获取当前语种文案
```js
tango.trans('common:add'); // 新增
```
-24
View File
@@ -1,24 +0,0 @@
# 国际化
TangoBoot 本身不提供国际化功能,国际化和您的 UI 组件库相关,在云音乐,我们采用的是基于 `@music163/antd` 的 UI 层解决方案。可以借助应用入口的容器配置功能进行国际化配置。
:::tip
如果您还不了解应用入口配置,可以先访问 [应用入口](./app) 一节进行了解。
:::
## 配置示例
```js
import { runApp } from '@music163/tango-boot';
import { ConfigProvider } from '@music163/antd';
import locale from '@music163/antd/es/locale/zh_CN';
import 'moment/dist/locale/zh-cn';
// 设置 moment 的文案
moment.locale('zh-cn');
runApp({
// 设置 UI 组件库的文案配置
providers: [<ConfigProvider locale={locale} />],
});
```
-139
View File
@@ -1,139 +0,0 @@
# TangoBoot
TangoBoot 是一个服务于 Tango 低代码应用开发体系的前端应用框架,提供了标准的数据请求,状态管理,路由方案,并提供了通用的运行时工具函数,让开发者通过简单的配置即可生成单页应用。
## 应用架构
TangoBoot 的应用架构使用的是 View-Model-Service 三层模型,其中模型层定义了 Observable States,视图层观察 Model 的变化而进行自动更新,服务层用来创建一组服务函数,供视图层和模型层消费。图如下图所示:
<img
alt="image"
width="600px"
src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13760951704/985c/9706/7f18/be713816a143d3a054f51c9d1fc59b36.png"
/>
## 核心 API
- `runApp` 创建应用入口
- `definePage` 定义 Reactive 视图
- `defineStore` 定义可观察的状态模型
- `defineServices` 定义服务函数
### 创建应用入口
其中`index.js` 为应用的入口文件,一个基本的实现为
```jsx
import { runApp } from '@music163/tango-boot';
import routes from './routes';
import services from './services';
import home from './stores/home';
import counter from './stores/counter';
// 全局样式
import './global.less';
const { mount, unmount, bootstrap } = runApp({
boot: {
mountElement: document.querySelector('#root'),
qiankun: false,
},
stores: {
home,
counter,
},
services,
router: {
type: 'browser',
config: routes,
},
});
export { mount, unmount, bootstrap };
```
### 创建视图模型
通过 defineStore 来进行视图模型的定义非常简单,只需简单的声明状态和动作即可
```jsx
import { defineStore } from '@music163/tango-boot';
const counter = defineStore({
num: 0,
get() {},
decrement: function () {
counter.num--;
},
increment: () => counter.num++,
});
export default counter;
```
### 创建 Reactive 视图
视图层如果要监听状态的变化,只需要 `definePage` 对视图组件进行一层包裹即可。
```jsx
import React from 'react';
import tango, { definePage } from '@music163/tango-boot';
class App extends React.Component {
increment = () => {
tango.stores.counter.increment();
};
render() {
return (
<div>
<h1>Counter: {tango.stores.counter.num}</h1>
<button type="primary" onClick={this.increment}>
+1
</button>
</div>
);
}
}
export default definePage(App);
```
### 创建数据服务函数
`defineServices` 提供了低成本声明异步服务函数的能力。
```jsx
import { defineServices } from '@music163/tango-boot';
export default defineServices({
list: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
formatter: (res) => {
const { data, message } = res;
return {
code: 200,
list: data,
total: data.length,
message,
};
},
},
add: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
method: 'post',
},
update: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
method: 'post',
},
delete: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users?id=1',
},
});
```
-66
View File
@@ -1,66 +0,0 @@
# 双向绑定
TangoBoot 支持快捷实现组件与视图双向绑定。
## 借助 `withModel` 实现双向绑定
`withModel` 是 TangoBoot 内置的模型绑定 HOC,支持将组件的内部状态同步到某个 store 定义的状态中。基本的用法如下:
```jsx
class Input extends React.Component {
foo() {}
render() {
return <input {...this.props} />;
}
}
const ModelInput = withModel({
// 设置从组件同步到 store 中的状态值
getValueFromEvent(e: any) {
return e.target.value;
},
})(Input);
```
现在 `ModelInput` 获取了双向绑定的能力,其 `value` 变化后的值将会通过 `onChange` 事件同步给绑定的模型变量
```jsx
const Store = defineStore(
{
name: 'alice',
},
'user'
);
const ModelApp = defineView((props) => {
return (
<div>
<ModelInput model="user.name" />
</div>
);
});
```
## 配置选项
`withModel` 的定义如下:
```jsx
withModel(options)(Component);
```
其中 options 的配置如下:
| 属性名 | 类型 | 默认值 | 说明 |
| ----------------- | ------------------ | ---------- | -------------------------- |
| name | string | | 组件的 displayName |
| valuePropName | string | "value" | 组件的受控值 |
| trigger | string | "onChange" | 组件值变化时的回调函数 |
| getValueFromEvent | `(...args) => any` | | 从回调函数参数中的取值方法 |
借助 `withModel` 增强后,Component 组件将会获得如下属性:
| 属性名 | 类型 | 默认值 | 说明 |
| ------ | ------ | ------ | ------------------------ |
| model | string | | 绑定的模型变量的变量路径 |
-45
View File
@@ -1,45 +0,0 @@
---
sidebar_position: 4
---
# 创建页面
TangoBoot 内置提供了一个创建 reactive 视图的模型,用户无需关注复杂的依赖关系,只需要将视图组件使用 `definePage` 包裹即可。
## definePage
基于传递进来的组件创建一个 reactive 视图。reactive 视图会根据 store 数据的变化来自动触发视图重新渲染。例如
```jsx
import React from 'react';
import { definePage, defineStore } from '@music163/tango-boot';
const user = defineStore({ name: 'Bob' });
export default definePage(() => <div>Hello {user.name}!</div>);
```
`definePage` 内部为被包裹的组件实现了一个最佳的 `shouldComponentUpdate` 或 `memo`。
使用 `definePage` 包裹后的组件还会默认接收一些额外的属性,以便于开发者便捷的获取部分状态信息。具体包括:
- `routerData` 路由数据:包括 `params` 路由参数,`query` 查询参数,`pathname` 路径名
- `stores` 模型索引
- `services` 服务索引
## defineView
`defineView` 是一个更加轻量的实现 reactive 视图的包裹器,推荐所有在视图中使用的本地组件都使用它来进行包裹。与 `definePage` 不同的是,他没有额外的属性透传进来。
```jsx
export default defineView(() => (
<div>
<div>Hello {user.name}!</div>
<div>Your first post is: {timeline.posts[0]}</div>
</div>
));
```
:::info
`defineView` 的实现依赖了 [react-easy-state](https://github.com/RisingStack/react-easy-state#creating-reactive-views),感兴趣的同学可以访问对应的链接了解更详细的内容。
:::
-33
View File
@@ -1,33 +0,0 @@
# 组件实例获取
TangoBoot 支持快捷设置和获取组件的实例。
## 借助 `withRef` 注册组件实例
`withRef` 是 TangoBoot 内置的实例注册 HOC,可以快捷的进行组件实例的注册和获取。基本的用法如下:
```tsx
class Input extends React.Component {
foo() {}
render() {
return <input {...this.props} />;
}
}
const ClassInput = withRef()(Input);
```
借助 `withRef` 包裹组件后,当新的组件实例被设置 `id` 属性时,会自动将对应的实例注册到 `tango.refs` 中。例如:
```tsx
export function Basic() {
const inputRef = useRef();
useEffect(() => {
console.log(tango.refs.classInput, inputRef.current);
}, []);
// 设置 id 属性后,会自动组件实例到 `tango.refs` 中
return <ClassInput id="classInput" ref={inputRef} />;
}
```
-90
View File
@@ -1,90 +0,0 @@
# 消费异步数据服务
TangoBoot 提供一个配置式的创建数据服务函数的方法,通过 `defineServices` 可以非常轻松的声明一组异步服务函数,供视图层或模型层消费。
## 简单用法
```js
import { defineServices } from '@music163/tango-boot';
const services = defineServices({
list: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
},
add: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
method: 'post',
},
});
```
调用异步服务函数
```js
async () => {
// 最简单的调用方法
await services.list();
// 用可以快捷传递参数,参数1为数据载荷,参数2为请求配置
await services.add(payload, config);
};
```
## 请求配置
默认情况下,`defineServices` 底层采用 [axios](https://github.com/axios/axios) 发起请求。
```js
import { defineServices } from '@music163/tango-boot';
export default defineServices({
list: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
formatter: (res) => {
const { data, message } = res;
return {
code: 200,
list: data,
total: data.length,
message,
};
},
// 这里你也可以传入 axios 的其他配置
},
});
```
常用的配置如下:
- url 请求地址
- method 请求方法,默认为 `GET`
- `GET`
- `POST`
- `PUT`
- `DELETE`
- formatter 格式化响应数据
- requestType 请求体编码类型(content-type),默认值为 `json`
- `json` 默认将 JavaScript 对象序列化为 JSON
- `x-www-form-urlencoded` 对应为 `application/x-www-form-urlencoded`
- headers 自定义请求头
- timeout 超时的毫秒数
更多的配置选项可以参考 [axios 请求配置文档](https://axios-http.com/zh/docs/req_config)。
:::tip 请求体编码
默认情况下 TangoBoot 将 JavaScript 对象序列化为 JSON。如果要以 `application/x-www-form-urlencoded` 格式发送数据,可以将 `requestType` 设置为 `x-www-form-urlencoded`。
`application/x-www-form-urlencoded` 数据被编码成以 '&' 分隔的键 - 值对,同时以 '=' 分隔键和值。非字母或数字的字符会被 [percent-encoding](https://developer.mozilla.org/zh-CN/docs/Glossary/percent-encoding): 这也就是为什么这种类型不支持二进制数据 (应使用 multipart/form-data 代替).
:::
## 配置错误消息展示
配置请求错误发生时消息默认的展现方式,此处使用 `message` 组件进行消息浮层的展示。推荐在应用的入口文件进行配置:
```js
import { tangoBootConfig } from '@music163/tango-boot';
import { message } from '@music163/antd';
tangoBootConfig.toast = message;
```
-214
View File
@@ -1,214 +0,0 @@
# 管理前端应用状态
TangoBoot 采用了一个轻量级的使用 Reactive 的状态管理方案,开发者可以非常轻松的进行页面状态的管理。一个最基本的示例如下所示:
```js
import React from 'react';
import { defineStore, definePage } from '@music163/tango-boot';
const counter = defineStore({
num: 0,
increment: () => counter.num++,
});
// 当状态变化的时候,视图会自动刷新
export default definePage(() => <button onClick={counter.increment}>{counter.num}</button>);
```
值得注意的是,开发者必须借助 `defineStore` 来定义状态,它会帮助你创建一个 observable 的状态对象。借助 `definePage` 来定义视图,它会帮你订一个 reactive 的视图组件,并且在自动监听状态的变化,按需进行 UI 更新。
## 创建状态模型 Stores
基本语法为 `defineStore(storeObject: object, namespace?: string)`
- `storeObject` 为原始的状态对象
- `namespace` 为该 Store 对应的命名空间,如果你定义了,可以借助 `tango.stores[namespace]` 来获取该引用
一个基本的例子如下:
```js
import { defineStore } from '@music163/tango-boot';
const user = defineStore({ name: 'Rick' });
// stores behave like normal JS objects
user.name = 'Bob';
```
### 复杂的数据结构
```js
// stores can include any valid JS structure
// including nested data, arrays, Maps, Sets, getters, setters, inheritance, ...
const user = defineStore({
profile: {
firstName: 'Bob',
lastName: 'Smith',
get name() {
return `${user.profile.firstName} ${user.profile.lastName}`;
},
},
hobbies: ['programming', 'sports'],
friends: new Map(),
});
// stores may be mutated in any syntactically valid way
user.profile.firstName = 'Bob';
delete user.profile.lastName;
user.hobbies.push('reading');
user.friends.set('id', otherUser);
```
### 异步方法
```js
const userStore = defineStore({
user: {},
async fetchUser() {
userStore.user = await fetch('/user');
// or use this
// this.user = await fetch('/user');
},
});
```
### 多个 Store
useStore.js
```js
import { store } from '@music163/tango-boot';
const userStore = defineStore(
{
user: {},
async fetchUser() {
userStore.user = await fetch('/user');
},
},
'userStore',
);
export default userStore;
```
recipesStore.js
```js
import tango, { store } from '@music163/tango-boot';
import userStore from './userStore';
const recipesStore = defineStore(
{
recipes: [],
// 方式1:直接用调用
async fetchRecipes() {
recipesStore.recipes = await fetch(`/recipes?user=${userStore.user.id}`);
},
// 方法2:使用 this 调用,仅支持 method property,不支持箭头函数
async fetchRecipes2() {
this.recipes = await fetch(`/recipes?user=${userStore.user.id}`);
},
// 方法3:使用 tango 全局变量调用,你也可以使用这种方式引入其他的 store
async fetchRecipes3() {
tango.stores.recipesStore.recipes = await fetch(
`/recipes?user=${tango.stores.userStore.user.id}`,
);
},
},
'recipesStore',
);
export default recipesStore;
```
## 创建 Reactive 视图
你可以借助 `definePage` 创建视图,也可以直接包裹您的原始组件。基本用法为 `definePage(Component)`,借助 `definePage` 可以帮你自动监听状态的变化,并在变化时自动触发视图的重新渲染。
一个简单的例子如下:
```js
import React from 'react';
import { view, store } from '@music163/tango-boot';
// this is a global state store
const user = defineStore({ name: 'Bob' });
// this is re-rendered whenever user.name changes
export default definePage(() => (
<div>
<input value={user.name} onChange={(ev) => (user.name = ev.target.value)} />
<div>Hello {user.name}!</div>
</div>
));
```
## 实现视图与状态双向绑定
TangoBoot 提供了一个名为 `withModel` 的 [HOC](https://zh-hans.reactjs.org/docs/higher-order-components.html),可以借助它来实现视图与状态的双向绑定。
使用方法如下:
```js
withModel(options)(BaseComponent);
```
### 参数配置
其中可配置的 options 参数包括:
- `name` 用于配置包裹后组件的 displayName,可选
- `valuePropName` 绑定的 value 属性名,用于实现双向绑定的受控逻辑,默认为 `value`
- `trigger` 设置收集字段值变更的时机,默认为 `onChange`
- `getValueFromEvent` 设置从事件回调中获取 value 值的方法,默认为 `val => val`,直接返回 trigger 的第一个参数
嵌套 `whitModel` 后,组件将会获得两个新增的属性:
- `model` 用于绑定 Store 中的状态
- `innerRef` 用于获取内部组件的 [ref](https://zh-hans.reactjs.org/docs/refs-and-the-dom.html) 引用
### 双向绑定示例
基本用法如下:
```jsx
import tango, { withModel, defineView, defineStore } from '@music163/tango-boot';
// 定义一个基本的 Input 组件
class Input extends React.Component {
foo() {}
render() {
return <input {...this.props} />;
}
}
// 实现双向绑定
const ModelInput = withModel({
getValueFromEvent(e) {
return e.target.value;
},
})(Input);
// 定义一个 Store
defineStore(
{
name: 'alice',
},
'user',
);
// 双向绑定验证
const ModelApp = defineView((props) => {
return (
<div>
<ModelInput model="user.name" />
<div>{tango.stores.user.name}</div>
</div>
);
});
```
-96
View File
@@ -1,96 +0,0 @@
# tango.config.json
`tango.config.json` 是 Tango 项目的核心配置文件,当您的项目使用 Tango 设计器在线开发时会读取该文件进行线上开发环境的初始化。
## entry
非必填。`entry` 是项目的入口文件,用于在线设计器的初始化,默认取值为 `/src/index.js`。
## packages
项目使用的 npm 包依赖信息,用于定义依赖的类型以及外置依赖资源。该配置项为一个 map,key 为对应的 npm 包名,value 为该依赖的配置信息。
- version 包的版本号,必选
- library UMD 包中的全局变量名,必选
- type 依赖的类型
- baseDependency 基础依赖包
- bizDependency 业务组件依赖包
- dependency 其他依赖包
- resources 外置依赖资源列表
- designerResources 在设计器视图载入的外置资源列表,用来需要区别加载时使用
- description 描述信息
依赖的类型 `type` 可以是 `baseDependency` 即项目的基础依赖,用于获取项目基础物料列表的信息;也可以是 `bizDependency` 即业务组件依赖,用于获取合并到物料面板业务组件菜单中的业务组件列表;也可以是 `depenency` 即项目的其他依赖,只用于提供 external 信息与版本信息等,不会和物料面板产生关系。
当配置了外置依赖资源 `resources` 与其对应的全局变量 `library` 时,当这些依赖被 import 时,相关资源不会被打包到 bundle 中,而是在运行时再去外部获取这些扩展依赖,其等效于之前的 `sandbox.externalResources` 与 `sandbox.externals` 配置项。此外,可以将 `resource` 中的版本号替换为 `{{version}}`,其会在平台上构建时替换为版本号。特殊的,可以定义 `designerResources` 用于适配平台的设计器视图。
```json
{
"react": {
"version": "17.0.2",
"library": "React",
"type": "dependency",
"resources": ["https://unpkg.com/react@{{version}}/umd/react.development.js"]
},
"react-dom": {
"version": "17.0.2",
"library": "ReactDOM",
"type": "dependency",
"resources": ["https://unpkg.com/react-dom@{{version}}/umd/react-dom.development.js"]
},
"@music163/tango-boot": {
"version": "0.1.3",
"library": "TangoBoot",
"type": "baseDependency",
"resources": ["https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js"],
"description": "云音乐低代码运行时框架"
},
"@music163/antd": {
"version": "0.1.2",
"library": "TangoAntd",
"type": "baseDependency",
"resources": [
"https://unpkg.com/@music163/antd/{{version}}/dist/index.js",
"https://unpkg.com/@music163/antd/{{version}}/dist/index.css"
],
"description": "云音乐低代码中后台应用基础物料",
"designerResources": [
"https://unpkg.com/@music163/antd/{{version}}/dist/designer.js",
"https://unpkg.com/@music163/antd/{{version}}/dist/index.css"
]
},
"@music163/biz-comp": {
"type": "bizDependency",
"version": "0.0.2"
}
}
```
:::tip 注意依赖的先后顺序
由于依赖是按照定义的先后顺序注入至沙箱内,因此需要确保 `packages` 内依赖的先后顺序,否则可能会导致应用加载依赖失败(例如将 `react` 放置在组件包之后,会导致组件包无法正常加载)。
:::
## externalResources
除了上述 `packages` 定义的外置依赖资源外,在这里可以定义沙箱启动时引入的非 npm 包的外置依赖资源列表,可以是 `js` 或 `css` 等静态文件。例如:
```json
{
"externalResources": ["https://tango.netease.io/script.js"]
}
```
## proxy
非必填。项目的代理规则。
```json
{
"proxy": {
"/api/pms": {
"target": "http://cms.qa.igame.163.com",
"changeOrigin": true
}
}
}
```
@@ -1,58 +0,0 @@
# 设计器扩展概览
主要介绍如何扩展 tango 设计器。tango 设计器提供了三个部位的自定义扩展能力,分别是标题栏,工具栏,和侧边栏。可以通过下面这张图进行简要的了解:
<img src="https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13359732115/c5ca/c5ae/bbd4/852daf849ac39162baf3bc3a985de6de.png" />
## 标题栏扩展
`DesignerPanel` 提供了 logo, description, actions 三个扩展点,分别对应于 平台标识、应用描述、行动点区域,开发者可以按照需求进行扩展。例如:
```jsx
<DesignerPanel logo={<Logo />} description={<ProjectDetail />} actions={<Box>新版沙箱</Box>}>
</SidebarPanel>
```
## 工具栏扩展
工具栏面板支持任意组合和设置渲染的位置,开发者可以通过 `ToolbarPanel.Item` 添加自定义的工具栏选项,例如下面的代码扩展了一个 newPage 按钮,用于支持在设计器中创建新页面的需求。
```jsx
<ToolbarPanel>
<ToolbarPanel.Item key="history" />
<ToolbarPanel.Item key="viewportRefresh" />
<ToolbarPanel.Item key="preview" />
<ToolbarPanel.Item key="routeSwitch" />
<ToolbarPanel.Item key="newPage">
<Button
onClick={() => {
const { name, code } = genDefaultPage(index);
engine.workspace.addViewPage(`${name}${index++}`, code);
message.success('页面新建成功');
}}
>
添加新页面
</Button>
</ToolbarPanel.Item>
<ToolbarPanel.Item key="modeSwitch" placement="center" />
<ToolbarPanel.Item key="viewportSwitch" placement="right" />
</ToolbarPanel>
```
## 侧边栏扩展
侧边栏面板支持任意组合和调换顺序,用户可以通过 `SidebarPanel.Item` 组件进行自定义扩展,默认情况下根据一些内置的 key 标识,设计器会自动渲染对应的面板,用户也可以根据自己的需求采用自定义渲染子节点的方案进行扩展。
例如,下面的示例代码中扩展了一个 “自定义面板”。
```jsx
<SidebarPanel>
<SidebarPanel.Item key="components" />
<SidebarPanel.Item key="outline" />
<SidebarPanel.Item key="dataSource" />
<SidebarPanel.Item key="dependency" />
<SidebarPanel.Item key="history" />
<SidebarPanel.Item key="custom" title="自定义面板" icon={<BulbOutlined />}>
<Box p="m">这里是一个自定义的面板,你可以任意添加</Box>
</SidebarPanel.Item>
</SidebarPanel>
```
@@ -1,67 +0,0 @@
# 外部数据源
如果您的扩展部件需要使用外部数据源,可以参考本文的做法。
## 存在部件共享
如果数据源需要在多个扩展部件中共享,我们推荐您借助 tango 的 `remoteServices` 规范进行接口的实现。
### 外部数据源定义
例如,我们需要调用一组图片素材的接口,此时我们可以定义一个 `ImageService`,如下:
```tsx
import { createServices } from '@music/request';
export const remoteServices = {
ImageService: createServices(
{
listMy: {
url: '/my/upload/list',
},
listFav: {
url: '/my/star/list',
},
listPub: {
url: '/list',
},
},
{
baseURL: 'https://febase-openapi.fn.netease.com/deer/api/deer/pic',
withCredentials: false, // 解决跨域时必须非*问题
}
),
};
```
### 外部数据源引入
我们在设计器初始化的时候可以通过 `remoteServices` 属性进行外部数据源的传入。
```tsx
<Designer remoteServices={remoteServices}></Designer>
```
### 在设计器扩展中使用定义的外部数据源
```tsx
import { useRemoteServices } from '@music163/tango-designer';
export function CustomWidget() {
const remoteServices = useRemoteServices();
}
```
## 仅在单个组件
如果您的外部数据服务没有额外的共享需求,您也可以直接在组件内部进行数据服务的发起。
```tsx
import request from '@music/request';
export function CustomWidget() {
useEffect(() => {
request('//some.domain/get');
}, []);
}
```
@@ -1,141 +0,0 @@
---
sidebar_position: 3
---
# 物料接入
Tango 提供了低成本的物料接入方式,支持直接复用现有的组件体系。只需要在现有组件包基础上简单的提供配置说明问题,即可轻松接入到 Tango 低代码生态体系中。
import Link from '@docusaurus/Link';
## 预览视图和设计器视图
- 预览视图:是组件默认的渲染模式,通常我们只需要关注此视图行为。
- 设计器视图:为低代码平台定制的渲染视图,当我们需要自定义组件在 Tango 设计器中的部分渲染行为时,可以通过修改设计器视图的渲染逻辑实现。
### 设计器视图和 `withDnd`
通常情况下我们不需要太关注于设计器视图,只需通过 `withDnd` 简单的进行包装即可。例如:
```jsx
// 将 antd Button 包装一层提供设计器视图
export Button = withDnd({
name: 'Button', // 必须,组件的名字,用来正确设置组件的 displayName
isFunctionComponent: true, // 可选,如果包裹的组件为函数组件,可以在此设置
overrideProps: {}, // 可选,覆盖掉包裹组件的默认属性值
})(AntButton);
```
默认情况下 withDnd 会在组件外层包裹一层 dnd 容器,以便于组件能够在设计器中被拖拽:
- draggable 属性表示该区域可以被拖拽
- data-dnd 用来追踪渲染的 dom 元素
```jsx
<div className="dnd-wrapper" draggable data-dnd="button:123">
<button>hello</button>
</div>
```
### 自定义拖拽容器
由于默认情况下拖拽容器是一个 `div` 节点,为[块级元素](https://www.w3schools.com/cssref/pr_class_display.asp)。部分情况下,你可能期望它渲染为一个[行内容器](https://www.w3schools.com/cssref/pr_class_display.asp),此时可以通过如下方式配置:
```jsx
export Button = withDnd({
name: 'Button',
display: 'inline-block', // 将 dnd 容器渲染为行内元素
wrapperStyle: {}, // 传入 dnd 容器的自定义样式
})(AntButton);
```
### 禁用拖拽容器
某些情况下,你的组件可能会检查子节点的有效性,此时你可能不期望组件被包装额外的容器节点,此时你可以关闭拖拽容器,但你需要保证你的组件能够接收父级元素传入的属性信息并附加到组件的跟节点上,否则将无法正确的设置组件的 dnd 信息。
```jsx
export Button = withDnd({
name: 'Button',
hasWrapper: false, // 禁用拖拽容器
})(MyButton);
const MyButton = (props) => {
const { children, ...rest } = props;
// 此时,需要保证你的组件能够正确的将多余的属性透传到组件的根节点上
return <button {...rest}>{children}</button>
}
```
## 组件包接入
组件包一般包含多个组件导出,例如 `@music163/antd` 就是典型的组件包,它面向中后台场景提供统一的物料层解决方案。组件包接入 Tango 体系,无需修改现有的代码实现,只需要在此基础上提供额外的配置文件即可。如下所示:
```
- src
- button
- designer.tsx # 设计器视图,可以不提供
- index.ts # 默认视图出口
- prototype.ts # 组件可配置描述协议
- index.ts
- prototypes.ts
- designer.ts
```
参考示例:https://g.hz.netease.com/NeteaseMusicUI/music-one/-/tree/master/packages/components/src/action
### 设计器视图的实现
对大部分组件而言,都不需要提供特定的设计器视图,只需要使用 tango 提供的 dnd hoc 进行简单的包裹导出即可。
```tsx
import { Button as Base, ButtonProps } from 'antd';
import { withDnd } from '@music/tango-apps-shared';
export const Button = withDnd<HTMLDivElement, ButtonProps<any>>({
name: 'Button',
})(Base);
```
### 编写 prototypes 文件
可以参考 <Link to="/docs/protocol/material-protocol">物料协议</Link> 编写该文档
### 属性设置器选择
参考属性设置器文档进行选择。
### 可配置项的验证
TangoApps 提供了 SettingFormPlayground 组件用来测试组件的可配置能力,可以直接在组件文档中加入可配置测试示例,例如在 storybook 中进行测试。
```jsx
import React from 'react';
import { SettingFormPlayground } from '@music/tango-apps-setting-form';
import { Input, prototypes } from '@music163/antd';
export default {
title: 'Prototype/Input',
};
export function Basic() {
return (
<SettingFormPlayground prototype={prototypes.Input}>
<Input />
</SettingFormPlayground>
);
}
```
SettingFormPlayground 组件支持实时预览配置结果,预览效果如下:
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13140796879/edbb/5dad/a330/013a98940e487956b605e1e533f54371.png" />
## 单个业务组件接入
单个组件接入可以参考组件包的接入方案。
:::info
考虑到单组件形态的特点,在云音乐业务场景中,TangoStudio 将会与红石物料中心对接,提供业务组件的自动化接入能力,目前该能力正在开发过程中。
:::
@@ -1,9 +0,0 @@
# DesignerPanel 设计器布局容器
设计器的主框架提供了一个基本的低代码设计器的布局容器,你可以很轻松的通过此布局容器来实现自己的设计器布局。
## 属性列表
import TypesTable from '@site/src/components/TypesTable';
<TypesTable name="DesignerPanelProps" />
@@ -1,56 +0,0 @@
# Designer 设计器容器
设计器根节点,注入全局状态。
## 属性列表
import TypesTable from '@site/src/components/TypesTable';
<TypesTable name="DesignerProps" />
## 设计器初始化
```jsx
<Designer engine={engine} sandboxQuery={sandboxQuery} remoteServices={remoteServices}></Designer>
```
## engine
设计器引擎实例,用于管理设计器的核心状态。
### 基本的初始化方式
```js
const engine = createEngine({
entry: '/src/index.js',
files: sampleFiles,
componentPrototypes: prototypes as any,
});
```
### 自定义 workspace 的初始化方式
默认情况下,引擎采用的是源码解析模式,即将源码解析为 ast 树,后续的搭建逻辑转为对 ast 树的操作。如果你想自定义搭建逻辑,可以通过自定义 workspace 的方式来实现。
引擎在 2.0 本本中提供了新的 JsonWorkspace 来支持自定义搭建逻辑,JsonWorkspace 采用的是 json 格式的数据结构,你可以通过自定义 json 来实现自定义搭建逻辑。
```js
const engine = createEngine({
workspace: new JsonWorkspace({
prototypes: prototypes as any,
files: schemaFiles,
}),
});
```
:::tip
按照这种方式,你可以自定义自己的 Workspace 实现。具体可以参考 Workspace 的实现标准。
:::
## sandboxQuery
沙箱的查询实例,用于向沙箱注册 dom 查询能力。
## remoteServices
远程服务实例,用于注册全局共享的数据服务实例。
@@ -1,27 +0,0 @@
# Hooks 钩子方法
设计器提供了一组 Hooks 用于快速获取设计器的各种状态。
import TypesTable from '@site/src/components/TypesTable';
## useWorkspace
获取工作区状态。
```js
// App 需要放置在 Designer 容器中
function App() {
const workspace = useWorkspace();
// do what you want
}
```
## useDesigner
```js
// App 需要放置在 Designer 容器中
function App() {
const designer = useDesigner();
// do what you want
}
```
@@ -1,9 +0,0 @@
# Sandbox 沙箱
设计器的运行时沙箱,用来执行代码,渲染页面。
import TypesTable from '@site/src/components/TypesTable';
默认沙箱是一个基于 CodeSandbox 的沙箱实例,可以直接在浏览器端执行代码。
<TypesTable name="SandboxProps" />
@@ -1,9 +0,0 @@
# SettingPanel 属性设置面板
属性设置器用于在配置面板中展示特定配置项的配置逻辑。
## 属性列表
import TypesTable from '@site/src/components/TypesTable';
<TypesTable name="SettingPanelProps" />
@@ -1,38 +0,0 @@
# SidebarPanel 侧边栏面板
侧边栏面板提供了一个简单易用的主操作面板,可以将一些高频操作和核心部件放置到侧边栏面板中,方便用户快速操作。
## 属性列表
import TypesTable from '@site/src/components/TypesTable';
<TypesTable name="SidebarPanelProps" />
## 创建浮动面板
借助 `isFloat` 和 `width` 属性可以创建浮动自定义宽度面板,脱离框架对侧边栏的宽度限制。
```jsx
<SidebarPanel.Item key="history" isFloat width="40vw" />
```
## 徽标提示
`showBadge` 属性可以设置工具栏是否显示徽标提示,用于某些面板需要对外展现需要被用户注意的时机。
<img
alt="img"
src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/18224398190/2847/b86d/df55/c817e86a0b1b3a6b08f7cc98362caec5.png"
width="300px"
/>
```jsx
<SidebarPanel.Item
key="info"
label="消息"
title="消息列表"
icon={<NotificationOutlined />}
showBadge={{ count: 2 }}>
<Box>info panel</Box>
</SidebarPanel.Item>
```
@@ -1,10 +0,0 @@
# ViewPanel 主视图面板
设计器的主视图面板,用来放置设计器的主视图,包括:沙箱,编辑器,预览等。主视图可以放置多个,但只有一个会在激活状态。
## 属性列表
import TypesTable from '@site/src/components/TypesTable';
<TypesTable name="ViewPanelProps" />
@@ -1,3 +0,0 @@
# WorkspacePanel 工作区面板
主工作区的布局容器,用于放置工作区的多重视图。
@@ -1,71 +0,0 @@
import Link from '@docusaurus/Link';
# 快速开始
Tango 是一个用于快速构建低代码工具的前端框架。借助 Tango 只需要少量代码就可以快速的搭建一个基本的低代码设计器。本文将会帮助你借助 Tango 实现一个基本的低代码可视化设计器。
:::warning 测试版免责声明
基于 Tango 低代码引擎的低代码平台已经在网易云音乐内部服务了内部众多业务开发场景。由于开源工作涉及到大量的代码分离工作,当前开源版本为低代码引擎的 alpha 演示版本,API 可能不稳定,不建议在生产环境中使用。我们预计在 2023 年 Q4 发布可以运行在生产环境的正式版。
:::
## 环境要求
- 安装 node 16.0.0 以上版本: <https://nodejs.org/en/download/>
- 安装 npm 8.x 或 yarn 1.x: <https://classic.yarnpkg.com/en/docs/getting-started>
## 启动 Tango Playground
1. 克隆仓库至本地
```sh
git clone https://github.com/NetEase/tango.git
```
2. 在 hosts 文件下添加本地开发的域名
```hosts
127.0.0.1 local.netease.com
```
3. 安装依赖并启动 Tango Playground
```sh
yarn install
yarn start
```
4. 打开浏览器访问 <https://local.netease.com:6006> 并允许不安全的证书
## 启动本地沙箱
本地开发除了需要启动 Tango 的 playground 外,还需要一个可用的沙箱环境来承载设计器。
如果没有可用的在线沙箱,可以通过本地启动一个沙箱来完成开发;如果已有一个在线沙箱,且沙箱与本地的 playground 共用一级域名,请直接跳至第 7 步修改 `bundlerURL` 为在线沙箱的地址。
1. 克隆仓库至本地
```sh
git clone https://github.com/NetEase/codesandbox-client.git
```
2. 修改 `Caddyfile`,将 `:8080` 修改为 playground 的域名,例如 `local.netease.com`
3. 将上述域名添加至 hosts 文件
```hosts
127.0.0.1 local.netease.com
```
4. 安装依赖并构建沙箱产物
```sh
yarn install
yarn build:deps
yarn build:sandpack
```
你也可以从 [这里](https://github.com/NetEase/codesandbox-client/releases) 下载预构建的沙箱产物,创建 `www` 子文件夹并将压缩包解压到该文件夹内
5. 使用 Caddy 托管构建沙箱后的静态文件,若本地没有 Caddy 可前往 <https://caddyserver.com/download> 下载
```sh
caddy run
```
6. 打开浏览器访问 <https://local.netease.com:8443> 并允许不安全的证书
7. 修改 `apps/playground/src/pages/index.tsx`,找到 `<Sandbox />` 组件,将 `bundlerURL` 修改为 `https://local.netease.com:8443`
```jsx
<Sandbox bundlerURL="https://local.netease.com:8443" />
```
8. 回到 playground 页面并刷新,现在 playground 将使用你指定的沙箱来运行设计器
若你本地有 Docker 也可将上述的第 4-5 步改为使用 Docker 构建镜像并启动:
```sh
docker build -t tango-codesandbox .
docker run -p 8443:8443 tango-codesandbox
```
-81
View File
@@ -1,81 +0,0 @@
# 属性设置器
属性设置器用于在配置面板中展示特定配置项的配置逻辑。Tango 内置了多种标准的属性设置器,对于一些特殊场景,内置的属性设置器可能无法满足你的需要,此时开发者可以扩展自己的属性设置。
## 内置属性设置器
| 设置器名 | 接收值类型 | 设置器说明 | 可配置项 |
| ----------------- | ------------------------------------ | -------------------------- | ------------------ |
| eventSetter | expression | 动作函数或事件监听器设置器 | |
| actionListSetter | | ActionList 的 value 设置器 | |
| boolSetter | boolean | 布尔值设置器 | |
| choiceSetter | `{ label: string, value: string }[]` | RadioGroup 设置器 | - options 选项列表 |
| pickerSetter | `{ label: string, value: string }[]` | 下拉值设置器 | - options 选项列表 |
| colorSetter | string | 色彩 | |
| dateSetter | string | 日期设置器 | |
| dateRangeSetter | `string[]` | 日期范围设置器 | |
| timeSetter | `string` | 时间设置器 | |
| timeRangeSetter | `string[]` | 时间范围设置器 | |
| expressionSetter | expression | 表达式设置器 | |
| jsonSetter | json expression | JSON 表达式设置器 | |
| jsxSetter | jsx expression | JSX 设置器 | |
| iconSetter | string | Icon 组件设置器 | |
| iconTypeSetter | string | Icon 组件类型设置器 | |
| numberSetter | number | 数字类型设置器 | |
| textSetter | string | 文本设置器 | |
| textAreaSetter | string | 文本域设置器 | |
| modelSetter | string | 变量绑定设置器 | |
| enumSetter | enum | 枚举值设置器 | |
| sliderSetter | number | 滑块设置器 | |
| listSetter | `object[]` | 列表值设置器 | |
| renderPropsSetter | Function | render props 设置器 | |
| imageSetter | string | 云鹿图片设置器 | |
## 注册自定义属性设置器
### 属性设置器的实现标准
属性设置器组件是一个标准的受控型表单组件,开发者所开发的组件只需要暴露 `value` 和 `onChange(value)` 两个属性让属性面板的表单容器进行控制即可。
例如,我们可以实现一个简单的字符串设置器 StringSetter,其实现如下:
```jsx
function StringSetter({ value, onChange, ...rest }) {
const handleChange = (e) => {
onChange(e.target.value);
};
return <input value={value} onChange={handleChange} />;
}
```
### 注册属性设置器
注册属性设置器非常简单,只需要借助设置器暴露出来的 `registerSetter` 即可。
```jsx
import { registerSetter } from '@music163/tango-designer';
// 注册自定义 setter
registerSetter({
name: 'stringSetter',
component: StringSetter,
});
```
### 使用自定义的属性设置器
在组件的物料协议中直接声明对应的属性设置器名称即可。例如:
```jsx
const prototype = {
name: 'PageHeader',
title: 'PageHeader',
props: [
{
name: 'title',
title: '标题',
setter: 'stringSetter',
},
],
};
```
-37
View File
@@ -1,37 +0,0 @@
import Link from '@docusaurus/Link';
# 简介
Tango 是一个用于快速构建低代码平台的低代码设计器框架,借助 Tango 只需要数行代码就可以完成一个基本的低代码平台前端系统的搭建。Tango 低代码设计器**直接读取前端项目的源代码,并以源代码为中心,执行和渲染前端视图,并为用户提供低代码可视化搭建能力,用户的搭建操作会转为对代码的修改**。借助 Tango 可以实现 **源码进,源码出**的效果。
## 引擎架构概览
Tango 低代码引擎在实现上进行了分层解藕,使得上层的低代码平台与底层的低代码引擎可以独立开发和维护,快速集成部署。另一方面,Tango 低代码引擎定义了一套开放的物料生态体系,开发者可以自由的贡献扩展组件配置能力的属性设置器,以及扩展低代码物料的二方三方业务组件。
如下图所示:
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/18236990116/aabf/8a16/28ee/f0fd29a84c7aa40bb26383cdef12b88c.png" />
## 引擎设计器构成概览
Tango 低代码引擎设计器用于低成本初始化一个基本的低代码平台,其前端部分主要包括如下几个部分:
- 设计器主框架:进行外框架初始化,状态管理,拖拽引擎绑定等核心逻辑。
- 侧边栏面板:提供可扩展的侧边面板,除了内置通用的面板,用户还可以任意添加自定义的面板。
- 属性设置面板:属性设置器面板,支持用户进行属性配置。
- 运行时沙箱:用于设计器运行时视图的渲染。
- Web IDE:用于进行在线源代码编辑。
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/30108642346/b8cf/e86d/ef5a/514d90b722b5d8dc0e18516ed594a07b.png" />
## 基于源代码的引擎内核
Tango 低代码引擎不依赖私有搭建协议和 DSL,而是直接使用源代码驱动,引擎内部将源码转为 AST,用户的所有的搭建操作转为对 AST 的遍历和修改,进而将 AST 重新生成为代码,将代码同步给在线沙箱执行。与传统的借助 [Schema 驱动的低代码方案](https://mp.weixin.qq.com/s/yqYey76qLGYPfDtpGkVFfA) 相比,不受到私有 DSL 和协议的限制,能够完美的实现低代码搭建与源码开发的无缝集成。
<img src="https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13140534982/ee2e/f42c/cc9a/184e2918a011b57d46e6c64a2722fa44.png" />
## 源码进,源码出
由于引擎内核完全基于源代码驱动实现,Tango 低代码引擎能够实现源代码进,源代码出的可视化搭建能力,不提供任何私有的中间产物。从而使得,Tango 构建的线上研发能力可以与团队现有的研发服务(代码托管、构建、部署、CDN)无缝衔接与集成。
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13208022400/b809/b82e/77b0/5e4fe78a8f11c8ed89c9ec9ced43e845.png" />
@@ -1,136 +0,0 @@
# 基础组件包规范
## 目录结构
```txt
+ src
+ button
- view.tsx // 默认视图文件
- index.ts // 渲染视图入口文件
- designer.ts // 设计器视图入口文件
- prototype.ts // 组件描述文件
+ date-picker
- index.ts // 组件包默认入口文件
- designer.ts // 组件包设计器视图入口文件
```
## 组件包入口
### 默认入口
`src/index.ts` 是组件包的默认出口文件。
### 设计视图入口
`src/designer.ts` 是组件包的设计视图出口文件。您至少应该导出 `menuData` 和 `prototypes` 两个模块:
```ts
export { Button } from './button';
export { Card } from './card';
//...
// 组件的配置描述列表
export const prototypes = [
{
title: '按钮',
name: 'Button',
props: [
{
name: 'size',
setter: 'textSetter',
},
//...
],
},
//...
];
export const menuData = {
// 常用组件
common: [
{
title: '基本',
items: ['Button'],
},
],
};
```
其中 `menuData` 的 key 可选列表如下:
| key | 分类标题 |
| ------- | -------- |
| common | 常用组件 |
| atom | 原子组件 |
| snippet | 组合 |
| block | 区块 |
## 组件入口
### 组件默认视图文件
`src/button/view.tsx`
```tsx
export function Button({ children, ...rest }) {
return <button {...rest}>{children}</button>;
}
```
需要注意的是,你需要将组件多余的属性 `...rest` 透传给跟结点 `<button {...rest}>`,以便于在设计器中时组件能够接收到跟踪信息。
### 组件默认入口文件
`src/button/index.ts`
```ts
export * from './view';
```
### 组件设计器视图文件
设计器视图是您的组件在 tango 设计器中进行展示的视图,大部分情况下,您无需关注此逻辑,特殊情况下,您的组件可能需要在设计器下有不一样的展现和数据透出,此时可以定义该视图文件实现。
`src/button/designer.tsx`
```tsx
import { withDnd } from '@music/tango-apps-shared';
import { Button as ButtonBase } from './view';
export const Button = withDnd({
// 组件名
name: 'Button',
// 是否在设计器中有包裹层,用于设置 dnd 的追踪信息
hasWrapper: false,
})(ButtonBase);
```
### 组件描述文件
`src/button/prototype.ts`
```ts
import { ComponentPrototypeType } from '@music/tango-apps-shared';
export const Button: ComponentPrototypeType = {
title: '按钮',
name: 'Button',
exportType: 'namedExport',
icon: 'icon-anniu',
type: 'element',
package: '@music163/antd',
hasChildren: false,
props: [
{
name: 'children',
title: '文案',
setter: 'textSetter',
initValue: '按钮',
},
],
};
```
## 依赖说明
如果您的组件会视图组件,推荐您的组件基于 `@music163/antd` 或 `@music163/antd` 的最新版本进行开发,且不要依赖其他 UI 组件包,例如 `antd`, `fusion` 等。
@@ -1,148 +0,0 @@
# 基础物料协议
import TypesTable from '@site/src/components/TypesTable';
物料协议定义了一个组件的基本信息,包括组件的名称、属性、拖拽规则等。物料协议是为组件附加的额外描述文件,不会侵入到你的组件代码中,因此你可以在不改动组件代码的情况下,为组件添加物料协议。
## 组件原型 prototype
<TypesTable name="ComponentPrototypeType" />
## 组件属性 ComponentPropType
<TypesTable name="ComponentPropType" />
### 嵌套属性
当某个组件属性为嵌套属性时,例如 `<Table scroll={{ x: 800, y: 400 }} />`,此时可以描述如下:
```jsx
{
name: 'Table',
props: [
{
name: 'scroll',
props: [
{
name: 'x',
setter: 'numberSetter',
},
{
name: 'y',
setter: 'numberSetter',
}
],
}
],
}
```
### 属性的关联展示
当某个属性依赖某个特定的其他属性值时,可以借助 `getVisible` 实现关联展示控制:
```js
{
name: 'Button',
props: [
{
name: 'shape',
},
{
name: 'buttonType',
getVisible: (form) => {
// 配置项 buttonType 仅在配置项 shape 的值为 button 时才展示
return form.getValue('shape') === 'button';
},
},
];
}
```
### 动态属性设置
当组件需要依据某个属性值进行动态设置其他属性时,可以借助 `getProp` 实现控制:
```js
{
name: 'FormItem',
props: [
{
name: 'component',
title: '控件类型',
},
{
name: 'componentProps',
title: '子组件属性',
getProp(form) {
const type = form.getValue('component');
const proto = { ...componentMap[type] };
const props = omitProps(proto.props, [
'placeholder',
'options',
'onChange',
'defaultValue',
'value',
]);
return {
title: proto.title + '属性',
props,
};
},
}
],
}
```
### 属性的输入提示
当某个属性在输入时需要进行输入提示时,例如某个函数属性需要提示其签名的模版,则可以借助属性输入提示实现:
```js
{
name: 'TableColumn',
props: [
{
name: 'render',
setter: 'expressionSetter',
autoCompleteOptions: ['(value, record, index) => { return null; }'],
}
],
}
```
![img](https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/18917075100/9569/09fa/bc62/c418d3de8694ce93977450fb725bb524.gif)
## 组件拖拽规则 ComponentDndRulesType
<TypesTable name="ComponentDndRulesType" />
### canDrag/canDrop
在 onDragStart 的时候执行。
```js
export const Page = {
name: 'Page',
rules: {
canDrag() {
return false;
},
},
};
```
### canMoveIn/canMoveOut
在 onDragEnter 的时候执行。
```js
export const Modal = {
name: 'Modal',
rules: {
canMoveIn(incomingName) {
return !(incomingName === Modal.name);
},
},
};
```
-167
View File
@@ -1,167 +0,0 @@
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */
const config = {
title: '使用 Tango 构建你的低代码生产力工具',
tagline: '基于源代码 AST 实现可视化搭建操作,支持实时出码,不受私有 DSL 和协议限制',
favicon: 'img/favicon.ico',
// Set the production url of your site here
url: 'https://netease.github.io',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/tango',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'netease', // Usually your GitHub org/user name.
projectName: 'tango', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'zh-CN',
locales: ['zh-CN', 'en'],
},
// scripts: [{ src: 'https://buttons.github.io/buttons.js', async: true, defer: true }],
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
blog: {
showReadingTime: true,
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
// Replace with your project's social card
image: 'img/social-card.png',
announcementBar: {
id: 'notion_alert',
content: '🏗 当前版本为测试版,请暂时不要用于生产环境,正式版将于2023年Q4发布!',
backgroundColor: 'var(--ifm-color-primary-contrast-background)',
textColor: 'var(--ifm-color-primary-contrast-foreground)',
isCloseable: false,
},
navbar: {
title: '',
logo: {
alt: 'Tango Logo',
src: 'img/logo.png',
},
items: [
{
type: 'docSidebar',
sidebarId: 'designer',
position: 'left',
label: '引擎文档',
},
{
type: 'docSidebar',
sidebarId: 'boot',
position: 'left',
label: '应用框架',
},
{
type: 'docSidebar',
sidebarId: 'protocol',
position: 'left',
label: '协议',
},
{ to: '/blog', label: '博客', position: 'left' },
{
type: 'html',
position: 'right',
value: `<a class="navbar__link github-button" href="https://github.com/netease/tango" data-color-scheme="no-preference: light; light: light; dark: dark;" data-size="large" data-show-count="true" aria-label="Star netease/tango on GitHub">Github</a><script async defer src="https://buttons.github.io/buttons.js"></script>`,
},
{
href: 'https://tango-demo.musicfe.com/designer/',
label: '演示应用',
position: 'right',
},
{
type: 'localeDropdown',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: '文档',
items: [
{
label: '快速开始',
to: '/docs/designer/quick-start',
},
],
},
{
title: '社区',
items: [
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/netease-tango',
},
{
label: 'Discord',
href: 'https://discordapp.com/invite/docusaurus',
},
],
},
{
title: '更多',
items: [
{
label: '博客',
to: '/blog',
},
{
label: 'GitHub',
href: 'https://github.com/netease/tango',
},
],
},
],
logo: {
alt: 'NetEase Cloud Music',
src: 'https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/15400855445/6c8b/10d4/a8a8/452c8518b0c0a660549996d366cdff77.png',
width: 320,
},
copyright: `Copyright © ${new Date().getFullYear()} NetEase Cloud Music, Inc.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
};
module.exports = config;
-318
View File
@@ -1,318 +0,0 @@
{
"homepage.hero.alert": {
"message": "🏗 The current version is 0.x alpha version. Please do not use it in a production environment. The 1.0 version will be released in 2023Q4."
},
"homepage.hero.title": {
"message": "Develop your low-code tools based on Tango"
},
"homepage.hero.tagline": {
"message": "Source code based low-code engine, support realtime code generation, without private schema and DSL"
},
"homepage.hero.button.document": {
"message": "Document"
},
"homepage.timeline.alpha": {
"message": "Core codebase published, documentation site released"
},
"homepage.timeline.beta": {
"message": "Core API refactoring and optimization for community scenarios"
},
"homepage.timeline.rc": {
"message": "The core API is basically stable and no longer Breaking Changes."
},
"homepage.timeline.stable": {
"message": "API is completely stable, provides good community support, and can be used in production environments."
},
"homepage.features.low-code": {
"message": "LowCode"
},
"homepage.features.low-code-content": {
"message": "Provide a general low-code engine that supports quickly creating and modifying applications through drag-and-drop and configuration, making pattern-based application development easy."
},
"homepage.features.code-driven": {
"message": "Code Driven"
},
"homepage.features.code-driven-content": {
"message": "The underlying low-code engine is driven by source code, without proprietary building protocols and DSL, supporting online low-code and source code dual-mode synchronous development."
},
"homepage.features.outbox": {
"message": "Outbox"
},
"homepage.features.outbox-content": {
"message": "Provide out-of-the-box low-code designer components, support easy gateways, and quickly build low-code productivity tools based on low-code engines."
},
"theme.ErrorPageContent.title": {
"message": "This page crashed.",
"description": "The title of the fallback page when the page crashed"
},
"theme.NotFound.title": {
"message": "Page Not Found",
"description": "The title of the 404 page"
},
"theme.NotFound.p1": {
"message": "We could not find what you were looking for.",
"description": "The first paragraph of the 404 page"
},
"theme.NotFound.p2": {
"message": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.",
"description": "The 2nd paragraph of the 404 page"
},
"theme.admonition.note": {
"message": "note",
"description": "The default label used for the Note admonition (:::note)"
},
"theme.admonition.tip": {
"message": "tip",
"description": "The default label used for the Tip admonition (:::tip)"
},
"theme.admonition.danger": {
"message": "danger",
"description": "The default label used for the Danger admonition (:::danger)"
},
"theme.admonition.info": {
"message": "info",
"description": "The default label used for the Info admonition (:::info)"
},
"theme.admonition.caution": {
"message": "caution",
"description": "The default label used for the Caution admonition (:::caution)"
},
"theme.blog.archive.title": {
"message": "Archive",
"description": "The page & hero title of the blog archive page"
},
"theme.blog.archive.description": {
"message": "Archive",
"description": "The page & hero description of the blog archive page"
},
"theme.BackToTopButton.buttonAriaLabel": {
"message": "Scroll back to top",
"description": "The ARIA label for the back to top button"
},
"theme.blog.paginator.navAriaLabel": {
"message": "Blog list page navigation",
"description": "The ARIA label for the blog pagination"
},
"theme.blog.paginator.newerEntries": {
"message": "Newer Entries",
"description": "The label used to navigate to the newer blog posts page (previous page)"
},
"theme.blog.paginator.olderEntries": {
"message": "Older Entries",
"description": "The label used to navigate to the older blog posts page (next page)"
},
"theme.blog.post.paginator.navAriaLabel": {
"message": "Blog post page navigation",
"description": "The ARIA label for the blog posts pagination"
},
"theme.blog.post.paginator.newerPost": {
"message": "Newer Post",
"description": "The blog post button label to navigate to the newer/previous post"
},
"theme.blog.post.paginator.olderPost": {
"message": "Older Post",
"description": "The blog post button label to navigate to the older/next post"
},
"theme.blog.post.plurals": {
"message": "One post|{count} posts",
"description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
},
"theme.blog.tagTitle": {
"message": "{nPosts} tagged with \"{tagName}\"",
"description": "The title of the page for a blog tag"
},
"theme.tags.tagsPageLink": {
"message": "View All Tags",
"description": "The label of the link targeting the tag list page"
},
"theme.colorToggle.ariaLabel": {
"message": "Switch between dark and light mode (currently {mode})",
"description": "The ARIA label for the navbar color mode toggle"
},
"theme.colorToggle.ariaLabel.mode.dark": {
"message": "dark mode",
"description": "The name for the dark color mode"
},
"theme.colorToggle.ariaLabel.mode.light": {
"message": "light mode",
"description": "The name for the light color mode"
},
"theme.docs.breadcrumbs.navAriaLabel": {
"message": "Breadcrumbs",
"description": "The ARIA label for the breadcrumbs"
},
"theme.docs.DocCard.categoryDescription": {
"message": "{count} items",
"description": "The default description for a category card in the generated index about how many items this category includes"
},
"theme.docs.paginator.navAriaLabel": {
"message": "Docs pages",
"description": "The ARIA label for the docs pagination"
},
"theme.docs.paginator.previous": {
"message": "Previous",
"description": "The label used to navigate to the previous doc"
},
"theme.docs.paginator.next": {
"message": "Next",
"description": "The label used to navigate to the next doc"
},
"theme.docs.tagDocListPageTitle.nDocsTagged": {
"message": "One doc tagged|{count} docs tagged",
"description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
},
"theme.docs.tagDocListPageTitle": {
"message": "{nDocsTagged} with \"{tagName}\"",
"description": "The title of the page for a docs tag"
},
"theme.docs.versionBadge.label": {
"message": "Version: {versionLabel}"
},
"theme.docs.versions.unreleasedVersionLabel": {
"message": "This is unreleased documentation for {siteTitle} {versionLabel} version.",
"description": "The label used to tell the user that he's browsing an unreleased doc version"
},
"theme.docs.versions.unmaintainedVersionLabel": {
"message": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.",
"description": "The label used to tell the user that he's browsing an unmaintained doc version"
},
"theme.docs.versions.latestVersionSuggestionLabel": {
"message": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).",
"description": "The label used to tell the user to check the latest version"
},
"theme.docs.versions.latestVersionLinkLabel": {
"message": "latest version",
"description": "The label used for the latest version suggestion link label"
},
"theme.common.editThisPage": {
"message": "Edit this page",
"description": "The link label to edit the current page"
},
"theme.common.headingLinkTitle": {
"message": "Direct link to {heading}",
"description": "Title for link to heading"
},
"theme.lastUpdated.atDate": {
"message": " on {date}",
"description": "The words used to describe on which date a page has been last updated"
},
"theme.lastUpdated.byUser": {
"message": " by {user}",
"description": "The words used to describe by who the page has been last updated"
},
"theme.lastUpdated.lastUpdatedAtBy": {
"message": "Last updated{atDate}{byUser}",
"description": "The sentence used to display when a page has been last updated, and by who"
},
"theme.navbar.mobileVersionsDropdown.label": {
"message": "Versions",
"description": "The label for the navbar versions dropdown on mobile view"
},
"theme.tags.tagsListLabel": {
"message": "Tags:",
"description": "The label alongside a tag list"
},
"theme.AnnouncementBar.closeButtonAriaLabel": {
"message": "Close",
"description": "The ARIA label for close button of announcement bar"
},
"theme.blog.sidebar.navAriaLabel": {
"message": "Blog recent posts navigation",
"description": "The ARIA label for recent posts in the blog sidebar"
},
"theme.CodeBlock.copied": {
"message": "Copied",
"description": "The copied button label on code blocks"
},
"theme.CodeBlock.copyButtonAriaLabel": {
"message": "Copy code to clipboard",
"description": "The ARIA label for copy code blocks button"
},
"theme.CodeBlock.copy": {
"message": "Copy",
"description": "The copy button label on code blocks"
},
"theme.CodeBlock.wordWrapToggle": {
"message": "Toggle word wrap",
"description": "The title attribute for toggle word wrapping button of code block lines"
},
"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": {
"message": "Toggle the collapsible sidebar category '{label}'",
"description": "The ARIA label to toggle the collapsible sidebar category"
},
"theme.NavBar.navAriaLabel": {
"message": "Main",
"description": "The ARIA label for the main navigation"
},
"theme.navbar.mobileLanguageDropdown.label": {
"message": "Languages",
"description": "The label for the mobile language switcher dropdown"
},
"theme.TOCCollapsible.toggleButtonLabel": {
"message": "On this page",
"description": "The label used by the button on the collapsible TOC component"
},
"theme.blog.post.readMore": {
"message": "Read More",
"description": "The label used in blog post item excerpts to link to full blog posts"
},
"theme.blog.post.readMoreLabel": {
"message": "Read more about {title}",
"description": "The ARIA label for the link to full blog posts from excerpts"
},
"theme.blog.post.readingTime.plurals": {
"message": "One min read|{readingTime} min read",
"description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
},
"theme.docs.breadcrumbs.home": {
"message": "Home page",
"description": "The ARIA label for the home page in the breadcrumbs"
},
"theme.docs.sidebar.collapseButtonTitle": {
"message": "Collapse sidebar",
"description": "The title attribute for collapse button of doc sidebar"
},
"theme.docs.sidebar.collapseButtonAriaLabel": {
"message": "Collapse sidebar",
"description": "The title attribute for collapse button of doc sidebar"
},
"theme.docs.sidebar.navAriaLabel": {
"message": "Docs sidebar",
"description": "The ARIA label for the sidebar navigation"
},
"theme.docs.sidebar.closeSidebarButtonAriaLabel": {
"message": "Close navigation bar",
"description": "The ARIA label for close button of mobile sidebar"
},
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": {
"message": "← Back to main menu",
"description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"
},
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": {
"message": "Toggle navigation bar",
"description": "The ARIA label for hamburger menu button of mobile navigation"
},
"theme.docs.sidebar.expandButtonTitle": {
"message": "Expand sidebar",
"description": "The ARIA label and title attribute for expand button of doc sidebar"
},
"theme.docs.sidebar.expandButtonAriaLabel": {
"message": "Expand sidebar",
"description": "The ARIA label and title attribute for expand button of doc sidebar"
},
"theme.ErrorPageContent.tryAgain": {
"message": "Try again",
"description": "The label of the button to try again rendering when the React error boundary captures an error"
},
"theme.common.skipToMainContent": {
"message": "Skip to main content",
"description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation"
},
"theme.tags.tagsPageTitle": {
"message": "Tags",
"description": "The title of the tag list page"
},
"homepage.hero.button.playground": {
"message": "Playground"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

@@ -1,80 +0,0 @@
---
title: Tango LowCode Builder Released!
authors: [wwsun]
tags: [netease, tango, open-source]
---
## 📝 Introduction
Tango is a low-code builder framework for quickly building low-code platforms or tools. With Tango, you only need a few lines of code to build a basic low-code builder. The Tango low-code builder reads the source code of the frontend project directly, executes and renders the frontend view based on the source code, and provides users with low-code visual building capabilities. User's building operations will be translated into modifications to the source code. With low-code tools or platforms built using Tango, it is possible to achieve the effect of "source code in, source code out" and seamlessly integrate with the existing development flow within the enterprise.
### ✨ Features
- After actual testing in the production environment of NetEase Cloud Music, it can be flexibly integrated and applied to low-code platforms, local development tools, and more.
- Driven by source code AST, with no private DSL and protocols.
- Provides real-time code output capability, supports source code input and output.
- Out-of-the-box front-end low-code designer, providing flexible and easy-to-use designer React components.
- Developed using TypeScript, providing complete type definition files.
### 🏗️ Code-based Low-Code Solution
Due to the fact that the engine kernel is completely based on the source code-driven implementation, Tango low-code engine can achieve the visualization building capability of source code input and source code output, without providing any private intermediate products. If the company already has a complete R&D system (code hosting, building, deployment, CDN), Tango low-code engine can be directly used to integrate with existing services to build a low-code development platform.
![engine flow](./engine-flow.png)
### 📄 Code in, Code out
Due to the engine kernel being completely driven by source code, the Tango low-code engine can achieve the visual construction capability of source code in and source code out, without providing any private intermediate products. If the company already has a complete development system (code hosting, building, deployment, CDN), then it can directly use the Tango low-code engine to integrate with existing services to build a low-code development platform.
![code in, code out](./codein-codeout.png)
### 🏆 Advantages
Compared to the low-code building solution based on private schema, Tango low-code engine has the following advantages:
| Comparison Items | Schema-base low-code solution | Tango (Code-based) |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Applicable Scenarios | Targeted vertical building scenarios, such as forms, marketing pages, etc. | 🔥 Applicable to application building scenarios centered on source code |
| Language Capabilities | Relies on private protocols for extension, inflexible, and difficult to align with programming language capabilities | 🔥 Based directly on JavaScript language, can use all language features, no extensibility issues |
| Development Capability | LowCode | 🔥 LowCode + ProCode |
| Source Code Export | Centered on schema, one-way code output, irreversible | 🔥 Source code-centered, two-way transcoding |
| Custom Dependencies | Need to be encapsulated according to private protocol extension, high customization cost | 🔥 Existing components can be seamlessly and low-cost integrated |
| Integrated R&D Facilities | High customization cost, additional customization required | 🔥 Low-cost integration, can directly reuse existing deployment and publishing capabilities |
The specific technical architecture is shown in the following figure:
## 📐 Architecture
The Tango low-code engine has been layered and decoupled in implementation, enabling independent development and maintenance of the upper-level low-code platform and the lower-level low-code engine, allowing for fast integration and deployment. In addition, Tango's low-code engine defines an open material ecosystem, allowing developers to freely contribute to extension components and configure property setters that enhance low-code materials.
The specific technical architecture is shown in the following diagram.
![low-code engine](./engine-components.png)
## ⏰ Milestones
Tango low-code engine is the core component of NetEase Cloud Music's internal low-code platform. Open sourcing involves a lot of decoupling work of core logic, which will bring us a lot of additional work in our normal work. Therefore, we plan to promote the open source of Tango low-code engine in stages.
1. Today we officially released the first community version of Tango low-code engine, which will include the core code library of Tango low-code engine, TangoBoot application framework, and low-code component library adapted to antd v4.
2. We plan to release the 1.0 Beta version of the low-code engine on September 30th this year. This version will refactor the core implementation for community scenarios, remove some compatibility code we have internally in Cloud Music, and optimize the implementation of the core.
3. We plan to release the 1.0 RC version of the low-code engine on October 30th this year. This version will ensure that the core API is basically stable and no longer has BREAKING CHANGE. At the same time, we will provide comprehensive and detailed development guides, deployment documents, and demonstration applications.
4. The official version will be released before the end of Q4 2023, and we will further improve our open source community operation mechanism.
![milestones](./tango-milestones.png)
## 🤝 Community
Our open source work is actively advancing, and you can learn about our latest progress through the following information:
- Github Repository: <https://github.com/NetEase/tango>
- Documentation Site: <https://netease.github.io/tango/>
Welcome everyone to join our community and participate in the open source construction of the Tango low-code engine. Any questions can be reported to us through [Github Issues](https://github.com/NetEase/tango/issues), and we will follow up and deal with them promptly.
## 💗 Acknowledgements
Thanks to the Netease Cloud Music Public Technical Team, the Front-End Team, the Live Broadcasting Technical Team, and all the colleagues who participated in the Tango project.
Thanks to the Sandpack project provided by CodeSandbox, which provides powerful browser-based code building and execution capabilities for Tango.
Tanks to the Babel project, which provides powerful javascript ast parsing and traversing capabilities for Tango.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

@@ -1,5 +0,0 @@
wwsun:
name: Wells SUN
title: Core maintainer
url: https://github.com/wwsun
image_url: https://github.com/wwsun.png
@@ -1,14 +0,0 @@
{
"title": {
"message": "Blog",
"description": "The title for the blog used in SEO"
},
"description": {
"message": "Blog",
"description": "The description for the blog used in SEO"
},
"sidebar.title": {
"message": "Recent posts",
"description": "The label for the left sidebar"
}
}
@@ -1,6 +0,0 @@
{
"version.label": {
"message": "Next",
"description": "The label for version current"
}
}
@@ -1,37 +0,0 @@
# Directory Structure
## Specification
在目录结构上,我们约定了一套通用的目录结构
```md
── src
│ +── assets
│ +── pages
│ +── components
│ +── services/index.js
│ +── stores
│ ├── routes.js // route config
│ ├── global.less // global styles
| |-- index.js // app entry
└── package.json
|---tango.config.json // config for low-code engine
```
## Custom Components
This directory is used to store locally developed components, such as:
```jsx
// src/components/sample.jsx
import React from 'react';
import { Box } from '@music163/antd';
export function Sample(props) {
return <Box {...props}>sample component</Box>;
}
```
:::info
Note that if you want the Sample component to be parsed and built by the designer, you must use MusicOne's `Box` component instead of the common `div` element. You can also use `<Box as="span" />` to change the rendered HTML element.
:::
@@ -1,134 +0,0 @@
# App Entry
The framework creates and renders the entire application through `runApp`, and global configurations of the application can be passed in during the creation of the application.
## Configuration
Configure the application globally through `src/index.js`, set up routes, runtime environment, service functions, state models, and more.
```js
import { runApp, tangoBootConfig } from '@music163/tango-boot';
import { message } from '@music163/antd';
// Configure the default display method for error messages that occur during requests, using the message component to display the message overlay.
tangoBootConfig.toast = message;
runApp({
// boot config
boot: {},
// container components
providers: [],
// store instances
stores: {},
// service functions
services: {},
// route config
router: {},
});
```
## App Boot
The `boot` configuration item is used for the startup configuration of the application.
```js
runApp({
boot: {
// react-dom mount element
mountElement: document.querySelector('#root'),
// enable qiankun micro-app support, `qiankun: { appName: string }`
qiankun: false,
},
});
```
## Route Config
`router` configuration item is used to configure the front-end routing of the application, which is implemented based on `react-router`.
```js
import routes from './routes';
runApp({
router: {
// 路由类型: hash | browser
type: 'hash',
// 路由配置信息
config: routes,
// basename,
},
});
```
TangoBoot uses the [Static Routing Configuration Scheme](https://github.com/remix-run/react-router/tree/v5/packages/react-router-config) of ReactRouter. The specific routing configuration file is as follows:
```js
// routes.js
import Index from './pages/index';
import About from './pages/about';
const routes = [
{
path: '/',
exact: true,
component: Index,
},
{
path: '/about',
component: About,
},
{
path: '/user/:id',
component: About,
},
];
export default routes;
```
## Container Components
Sometimes, you may want to wrap the root component with specific container components such as multi-language configuration, state container, etc. Since `runApp` encapsulates the rendering logic of the root component, you can use the `providers` configuration option.
```js
runApp({
// Wrap the instance of the providers component in the order in which it is passed in.
providers: [<ConfigProvider />, <LocaleProvider />],
});
```
## Environment Variables {#env}
Sometimes you may need to customize some environment variables to quickly determine when the application is running in different environments. You can use `tango.env` to quickly obtain the application's environment information. It should be noted that by default there will be no environment information, and you can set the desired environment variables by passing `getEnv` in `runApp` when needed.
For example:
```js
runApp({
// You can return an object with the environment information of the custom application.
getEnv() {
if (location.origin.includes('tango')) {
// tango designer
return 'development';
}
if (location.origin.includes('localhost')) {
// local environment
return 'local';
}
return 'production';
},
});
```
Consume environment variables anywhere, for example:
```jsx
<Box isRender={tango.env === 'development'}>A box only render in development environment</Box>
```
@@ -1,87 +0,0 @@
# Global Variable
TangoBoot provides a global singleton mode by default, which means that you can always use this global singleton to access necessary application runtime information in your project.
:::tip
Singleton means a single instance, also called "singleton pattern," which is a common design pattern. It limits the number of times a class can be instantiated to only once, so that a class has only one instance and provides a global access point to it. In the context of TangoBoot, "singleton" means that your application will always have only one instance of TangoBoot, and you can consistently obtain application-to-runtime information through this single instance.
:::
## Get the global variable
```js
// case1: get from package
import tango from '@music163/tango-boot';
// case2: get from window
window.tango;
// both are the same
console.log(tango === window.tango); // true
```
### Get data from the global `tango` variable
```js
// tango
{
// custom configs
config: {},
// custom helper functions
helpers: {},
// History
history: History;
// service functions
services: {},
// store instances
stores: {},
// builtin helpers
closeModal: Function;
openModal: Function;
setStoreValue: Function;
getStoreValue: Function;
// ...
}
```
## Usage Guide
### Use in store
```jsx
import tango, { defineModel } from '@music163/tango-boot';
export default defineModel(
{
title: 'hello',
async listUsers() {
const data = await tango.services.listUsers();
this.title = data;
},
},
'app'
);
```
### Use in page runtime
```jsx
import tango, { definePage } from '@music163/tango-boot';
function App() {
return (
<Box>
{tango.stores.app.title}
<Button onClick={tango.services.list}>request</Button>
</Box>
);
}
export default definePage(App);
```
@@ -1,130 +0,0 @@
# 常用的工具函数
tango-boot 提供了一组便捷的工具函数,用于应对日常开发过程中的典型操作逻辑。
### `getUser()`
> 从 v1.15.0 开始提供
获取当前的登录用户
```js
const user = tango.getUser();
```
### `checkPrivilege({ appCode, privilegeCode })`
> 从 v1.15.0 开始提供
检查 PMS 权限
```js
const test = await tango.checkPrivilege({ appCode, privilegeCode });
```
### `copyToClipboard(text: string)`
复制文本到剪贴板
```js
tango.copyToClipboard('hello world');
```
### `navigateTo(link: string, data?: object)`
导航到其他前端路由
```js
// 基本用法
tango.navigateTo('/about');
// 带路由参数
tango.navigateTo('/about', { the: 'query' }); // /about?the=query
```
### `showToast(text: string, type: string, time: number)`
- text 消息正文
- type 消息类型 `success | error | warn`
- time 消息展示的时间,单位秒,默认为 3 秒
消息提示
```js
tango.showToast('hello world');
tango.showToast('something is error', 'error', 3);
```
### `openModal(modalId: string)`
根据弹层 ID 唤起目标弹层
```js
tango.openModal('modal123');
```
### `closeModal(modalId: string)`
根据弹层 ID 关闭目标弹层
```js
tango.closeModal('modal123');
```
### `formatDate(date, format)`
- date: `number | string | Moment` 日期值
- format: `string` 格式化,取值参考 https://momentjs.com/docs/#/displaying/format/
格式化日期值
```js
tango.formatDate(1659496225323, 'YYYY-MM-DD HH:mm:ss'); // 2022-08-03 11:10:25
tango.formatDate('2022-11-11', 'X'); // 1668096000
```
### `formatNumber(number, locale, options)`
- number: `number`
- locale: `string`
- options: `Intl.NumberFormatOptions`,参考取值 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
格式化数字
```js
tango.formatNumber(99999); // 99,999
```
### `getCurrentLanguage`
获取当前语种
```js
tango.getCurrentLanguage(); // en-US
```
### `setLanguage(localeCode)`
设置当前语种
```js
tango.setLanguage('en-US');
```
### `getLanguageList`
获取当前应用可用语种列表
```js
tango.getLanguageList();
```
### `trans(module:key)`
根据多语言 key 获取当前语种文案
```js
tango.trans('common:add'); // 新增
```
@@ -1,24 +0,0 @@
# 国际化
TangoBoot 本身不提供国际化功能,国际化和您的 UI 组件库相关,在云音乐,我们采用的是基于 `@music163/antd` 的 UI 层解决方案。可以借助应用入口的容器配置功能进行国际化配置。
:::tip
如果您还不了解应用入口配置,可以先访问 [应用入口](./app) 一节进行了解。
:::
## 配置示例
```js
import { runApp } from '@music163/tango-boot';
import { ConfigProvider } from '@music163/antd';
import locale from '@music163/antd/es/locale/zh_CN';
import 'moment/dist/locale/zh-cn';
// 设置 moment 的文案
moment.locale('zh-cn');
runApp({
// 设置 UI 组件库的文案配置
providers: [<ConfigProvider locale={locale} />],
});
```
@@ -1,138 +0,0 @@
# TangoBoot
TangoBoot is a frontend application framework that serves the Tango low-code application development system. It provides standard data requests, state management, and routing solutions, as well as generic runtime utility functions, allowing developers to generate single-page applications through simple configuration.
## Application Architecture
The application architecture of TangoBoot uses the View-Model-Service three-layer model. The model layer defines Observable States, the view layer observes the changes of the model and updates automatically, and the service layer is used to create a set of service functions for the consumption of the view layer and the model layer. The diagram is shown in the figure below:
<img
alt="image"
width="600px"
src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13760951704/985c/9706/7f18/be713816a143d3a054f51c9d1fc59b36.png"
/>
## Core API
- `runApp` create the app entry
- `definePage` define reactive views
- `defineStore` define observable states
- `defineServices` define async service functions
### Create app entry
The `index.js` is the app entry filea simple example is:
```jsx
import { runApp } from '@music163/tango-boot';
import routes from './routes';
import services from './services';
import home from './stores/home';
import counter from './stores/counter';
import './global.less';
const { mount, unmount, bootstrap } = runApp({
boot: {
mountElement: document.querySelector('#root'),
qiankun: false,
},
stores: {
home,
counter,
},
services,
router: {
type: 'browser',
config: routes,
},
});
export { mount, unmount, bootstrap };
```
### Create Observable States
Defining a view model through defineStore is very simple, simply declare the state and actions.
```jsx
import { defineStore } from '@music163/tango-boot';
const counter = defineStore({
num: 0,
get() {},
decrement: function () {
counter.num--;
},
increment: () => counter.num++,
});
export default counter;
```
### Create Reactive Views
If the view layer needs to listen for state changes, it only needs to wrap the view component with `definePage`.
```jsx
import React from 'react';
import tango, { definePage } from '@music163/tango-boot';
class App extends React.Component {
increment = () => {
tango.stores.counter.increment();
};
render() {
return (
<div>
<h1>Counter: {tango.stores.counter.num}</h1>
<button type="primary" onClick={this.increment}>
+1
</button>
</div>
);
}
}
export default definePage(App);
```
### Create Service Functions
Use `defineServices` to define your remote apis as service functions.
```jsx
import { defineServices } from '@music163/tango-boot';
export default defineServices({
list: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
formatter: (res) => {
const { data, message } = res;
return {
code: 200,
list: data,
total: data.length,
message,
};
},
},
add: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
method: 'post',
},
update: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
method: 'post',
},
delete: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users?id=1',
},
});
```
@@ -1,66 +0,0 @@
# 双向绑定
TangoBoot 支持快捷实现组件与视图双向绑定。
## 借助 `withModel` 实现双向绑定
`withModel` 是 TangoBoot 内置的模型绑定 HOC,支持将组件的内部状态同步到某个 store 定义的状态中。基本的用法如下:
```jsx
class Input extends React.Component {
foo() {}
render() {
return <input {...this.props} />;
}
}
const ModelInput = withModel({
// 设置从组件同步到 store 中的状态值
getValueFromEvent(e: any) {
return e.target.value;
},
})(Input);
```
现在 `ModelInput` 获取了双向绑定的能力,其 `value` 变化后的值将会通过 `onChange` 事件同步给绑定的模型变量
```jsx
const Store = defineStore(
{
name: 'alice',
},
'user'
);
const ModelApp = defineView((props) => {
return (
<div>
<ModelInput model="user.name" />
</div>
);
});
```
## 配置选项
`withModel` 的定义如下:
```jsx
withModel(options)(Component);
```
其中 options 的配置如下:
| 属性名 | 类型 | 默认值 | 说明 |
| ----------------- | ------------------ | ---------- | -------------------------- |
| name | string | | 组件的 displayName |
| valuePropName | string | "value" | 组件的受控值 |
| trigger | string | "onChange" | 组件值变化时的回调函数 |
| getValueFromEvent | `(...args) => any` | | 从回调函数参数中的取值方法 |
借助 `withModel` 增强后,Component 组件将会获得如下属性:
| 属性名 | 类型 | 默认值 | 说明 |
| ------ | ------ | ------ | ------------------------ |
| model | string | | 绑定的模型变量的变量路径 |
@@ -1,41 +0,0 @@
# Pages
TangoBoot 内置提供了一个创建 reactive 视图的模型,用户无需关注复杂的依赖关系,只需要将视图组件使用 `definePage` 包裹即可。
## definePage
基于传递进来的组件创建一个 reactive 视图。reactive 视图会根据 store 数据的变化来自动触发视图重新渲染。例如
```jsx
import React from 'react';
import { definePage, defineStore } from '@music163/tango-boot';
const user = defineStore({ name: 'Bob' });
export default definePage(() => <div>Hello {user.name}!</div>);
```
`definePage` 内部为被包裹的组件实现了一个最佳的 `shouldComponentUpdate` 或 `memo`。
使用 `definePage` 包裹后的组件还会默认接收一些额外的属性,以便于开发者便捷的获取部分状态信息。具体包括:
- `routerData` 路由数据:包括 `params` 路由参数,`query` 查询参数,`pathname` 路径名
- `stores` 模型索引
- `services` 服务索引
## defineView
`defineView` 是一个更加轻量的实现 reactive 视图的包裹器,推荐所有在视图中使用的本地组件都使用它来进行包裹。与 `definePage` 不同的是,他没有额外的属性透传进来。
```jsx
export default defineView(() => (
<div>
<div>Hello {user.name}!</div>
<div>Your first post is: {timeline.posts[0]}</div>
</div>
));
```
:::info
`defineView` 的实现依赖了 [react-easy-state](https://github.com/RisingStack/react-easy-state#creating-reactive-views),感兴趣的同学可以访问对应的链接了解更详细的内容。
:::
@@ -1,33 +0,0 @@
# 组件实例获取
TangoBoot 支持快捷设置和获取组件的实例。
## 借助 `withRef` 注册组件实例
`withRef` 是 TangoBoot 内置的实例注册 HOC,可以快捷的进行组件实例的注册和获取。基本的用法如下:
```tsx
class Input extends React.Component {
foo() {}
render() {
return <input {...this.props} />;
}
}
const ClassInput = withRef()(Input);
```
借助 `withRef` 包裹组件后,当新的组件实例被设置 `id` 属性时,会自动将对应的实例注册到 `tango.refs` 中。例如:
```tsx
export function Basic() {
const inputRef = useRef();
useEffect(() => {
console.log(tango.refs.classInput, inputRef.current);
}, []);
// 设置 id 属性后,会自动组件实例到 `tango.refs` 中
return <ClassInput id="classInput" ref={inputRef} />;
}
```
@@ -1,90 +0,0 @@
# Async Services
TangoBoot provides a configuration-based method for creating data service functions. By using `defineServices`, it is very easy to declare a set of asynchronous service functions for consumption by the view layer or model layer.
## Basic Usage
```js
import { defineServices } from '@music163/tango-boot';
const services = defineServices({
list: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
},
add: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
method: 'post',
},
});
```
call the service function:
```js
async () => {
// simple usage
await services.list();
// call with params, payload the the data passed to request body(for 'get' method, will be query parameters), config the axios request config
await services.add(payload, config);
};
```
## Request Config
`defineServices` use [axios](https://github.com/axios/axios) to send requests, so you can pass in the axios request config as the second parameter of the service function.
```js
import { defineServices } from '@music163/tango-boot';
export default defineServices({
list: {
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/api/users',
formatter: (res) => {
const { data, message } = res;
return {
code: 200,
list: data,
total: data.length,
message,
};
},
// you can pass other axios config here
},
});
```
Following is the common configs
- `url` request url
- `method` request methoddefault is `GET`
- `GET`
- `POST`
- `PUT`
- `DELETE`
- `formatter` format the response data
- `requestType` encoding type of request body (content-type), default value is `json`
- `json` By default, serialize JavaScript objects to JSON.
- `x-www-form-urlencoded` equals to `application/x-www-form-urlencoded`
- `headers` request header
- `timeout` timeout of request
more configs should find in [axios documentation](https://axios-http.com/zh/docs/req_config)。
:::tip Encoding Type
By default, TangoBoot serializes JavaScript objects to JSON. To send data in `application/x-www-form-urlencoded` format, you can set the `requestType` to `x-www-form-urlencoded`.
`application/x-www-form-urlencoded` data is encoded as key-value pairs separated by '&', with keys and values separated by '='. Non-alphanumeric characters are [percent-encoded](https://developer.mozilla.org/zh-CN/docs/Glossary/percent-encoding), which is why this type does not support binary data (use multipart/form-data instead).
:::
## Error Messages
The default presentation method of the message when a configuration request error occurs is to use the `message` component for message overlay display. It is recommended to configure this in the application's entry file.
```js
import { tangoBootConfig } from '@music163/tango-boot';
import { message } from '@music163/antd';
tangoBootConfig.toast = message;
```
@@ -1,214 +0,0 @@
# States
TangoBoot 采用了一个轻量级的使用 Reactive 的状态管理方案,开发者可以非常轻松的进行页面状态的管理。一个最基本的示例如下所示:
```js
import React from 'react';
import { defineStore, definePage } from '@music163/tango-boot';
const counter = defineStore({
num: 0,
increment: () => counter.num++,
});
// 当状态变化的时候,视图会自动刷新
export default definePage(() => <button onClick={counter.increment}>{counter.num}</button>);
```
值得注意的是,开发者必须借助 `defineStore` 来定义状态,它会帮助你创建一个 observable 的状态对象。借助 `definePage` 来定义视图,它会帮你订一个 reactive 的视图组件,并且在自动监听状态的变化,按需进行 UI 更新。
## 创建状态模型 Stores
基本语法为 `defineStore(storeObject: object, namespace?: string)`
- `storeObject` 为原始的状态对象
- `namespace` 为该 Store 对应的命名空间,如果你定义了,可以借助 `tango.stores[namespace]` 来获取该引用
一个基本的例子如下:
```js
import { defineStore } from '@music163/tango-boot';
const user = defineStore({ name: 'Rick' });
// stores behave like normal JS objects
user.name = 'Bob';
```
### 复杂的数据结构
```js
// stores can include any valid JS structure
// including nested data, arrays, Maps, Sets, getters, setters, inheritance, ...
const user = defineStore({
profile: {
firstName: 'Bob',
lastName: 'Smith',
get name() {
return `${user.profile.firstName} ${user.profile.lastName}`;
},
},
hobbies: ['programming', 'sports'],
friends: new Map(),
});
// stores may be mutated in any syntactically valid way
user.profile.firstName = 'Bob';
delete user.profile.lastName;
user.hobbies.push('reading');
user.friends.set('id', otherUser);
```
### 异步方法
```js
const userStore = defineStore({
user: {},
async fetchUser() {
userStore.user = await fetch('/user');
// or use this
// this.user = await fetch('/user');
},
});
```
### 多个 Store
useStore.js
```js
import { store } from '@music163/tango-boot';
const userStore = defineStore(
{
user: {},
async fetchUser() {
userStore.user = await fetch('/user');
},
},
'userStore',
);
export default userStore;
```
recipesStore.js
```js
import tango, { store } from '@music163/tango-boot';
import userStore from './userStore';
const recipesStore = defineStore(
{
recipes: [],
// 方式1:直接用调用
async fetchRecipes() {
recipesStore.recipes = await fetch(`/recipes?user=${userStore.user.id}`);
},
// 方法2:使用 this 调用,仅支持 method property,不支持箭头函数
async fetchRecipes2() {
this.recipes = await fetch(`/recipes?user=${userStore.user.id}`);
},
// 方法3:使用 tango 全局变量调用,你也可以使用这种方式引入其他的 store
async fetchRecipes3() {
tango.stores.recipesStore.recipes = await fetch(
`/recipes?user=${tango.stores.userStore.user.id}`,
);
},
},
'recipesStore',
);
export default recipesStore;
```
## 创建 Reactive 视图
你可以借助 `definePage` 创建视图,也可以直接包裹您的原始组件。基本用法为 `definePage(Component)`,借助 `definePage` 可以帮你自动监听状态的变化,并在变化时自动触发视图的重新渲染。
一个简单的例子如下:
```js
import React from 'react';
import { view, store } from '@music163/tango-boot';
// this is a global state store
const user = defineStore({ name: 'Bob' });
// this is re-rendered whenever user.name changes
export default definePage(() => (
<div>
<input value={user.name} onChange={(ev) => (user.name = ev.target.value)} />
<div>Hello {user.name}!</div>
</div>
));
```
## 实现视图与状态双向绑定
TangoBoot 提供了一个名为 `withModel` 的 [HOC](https://zh-hans.reactjs.org/docs/higher-order-components.html),可以借助它来实现视图与状态的双向绑定。
使用方法如下:
```js
withModel(options)(BaseComponent);
```
### 参数配置
其中可配置的 options 参数包括:
- `name` 用于配置包裹后组件的 displayName,可选
- `valuePropName` 绑定的 value 属性名,用于实现双向绑定的受控逻辑,默认为 `value`
- `trigger` 设置收集字段值变更的时机,默认为 `onChange`
- `getValueFromEvent` 设置从事件回调中获取 value 值的方法,默认为 `val => val`,直接返回 trigger 的第一个参数
嵌套 `whitModel` 后,组件将会获得两个新增的属性:
- `model` 用于绑定 Store 中的状态
- `innerRef` 用于获取内部组件的 [ref](https://zh-hans.reactjs.org/docs/refs-and-the-dom.html) 引用
### 双向绑定示例
基本用法如下:
```jsx
import tango, { withModel, defineView, defineStore } from '@music163/tango-boot';
// 定义一个基本的 Input 组件
class Input extends React.Component {
foo() {}
render() {
return <input {...this.props} />;
}
}
// 实现双向绑定
const ModelInput = withModel({
getValueFromEvent(e) {
return e.target.value;
},
})(Input);
// 定义一个 Store
defineStore(
{
name: 'alice',
},
'user',
);
// 双向绑定验证
const ModelApp = defineView((props) => {
return (
<div>
<ModelInput model="user.name" />
<div>{tango.stores.user.name}</div>
</div>
);
});
```
@@ -1,86 +0,0 @@
# tango.config.json
`tango.config.json` is the core configuration file of the Tango project. When your project uses Tango designer for online development, it will read this file to initialize the online development environment.
## entry
Not required. `entry` is the entry file of the project, used for the initialization of the online designer. The default value is `/src/index.js`.
## packages
The npm package dependency information used in the project is used to define the type of dependency and external dependency resources. This configuration item is a map, where the key is the corresponding npm package name, and the value is the configuration information for the dependency.
The dependency type `type` can be `baseDependency`, which is the basic dependency of the project used to obtain the project's basic material list information; it can also be `bizDependency`, which is the business component dependency used to obtain the business component list merged into the material panel business component menu; it can also be `dependency`, which is other dependencies of the project, used only to provide external information and version information, and does not have any relationship with the material panel.
When the external dependency resource `resources` and its corresponding global variable `library` are configured, when these dependencies are imported, the relevant resources will not be packaged into the bundle, but the extension dependencies will be obtained externally at runtime. This is equivalent to the previous `sandbox.externalResources` and `sandbox.externals` configuration items. In addition, the version number in `resource` can be replaced with `{{version}}`, which will be replaced with the version number at the time of platform construction. Specifically, `designerResources` can be defined for adapting to the platform's designer view.
```json
{
"react": {
"version": "17.0.2",
"library": "React",
"type": "dependency",
"resources": ["https://unpkg.com/react@{{version}}/umd/react.development.js"]
},
"react-dom": {
"version": "17.0.2",
"library": "ReactDOM",
"type": "dependency",
"resources": ["https://unpkg.com/react-dom@{{version}}/umd/react-dom.development.js"]
},
"@music163/tango-boot": {
"version": "0.1.3",
"library": "TangoBoot",
"type": "baseDependency",
"resources": ["https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js"],
"description": "tango app runtime framework"
},
"@music163/antd": {
"version": "0.1.2",
"library": "TangoAntd",
"type": "baseDependency",
"resources": [
"https://unpkg.com/@music163/antd/{{version}}/dist/index.js",
"https://unpkg.com/@music163/antd/{{version}}/dist/index.css"
],
"description": "component library for tango apps",
"designerResources": [
"https://unpkg.com/@music163/antd/{{version}}/dist/designer.js",
"https://unpkg.com/@music163/antd/{{version}}/dist/index.css"
]
},
"@music163/biz-comp": {
"type": "bizDependency",
"version": "0.0.2"
}
}
```
:::tip
Since the dependencies are injected into the sandbox according to the order defined, it is necessary to ensure the order of dependencies in `packages`. Otherwise, it may cause the application to fail to load dependencies (for example, placing `react` after the component package will cause the component package to fail to load normally).
:::
## externalResources
In addition to the external dependency resources defined by the `packages` mentioned above, you can define a list of external dependency resources that are not npm packages and are introduced when the sandbox is started. These can be static files such as `js` or `css`. For example:
```json
{
"externalResources": ["https://tango.netease.io/script.js"]
}
```
## proxy
Not required. Proxy rules for the project.
```json
{
"proxy": {
"/api/pms": {
"target": "http://cms.qa.igame.163.com",
"changeOrigin": true
}
}
}
```
@@ -1,58 +0,0 @@
# 设计器扩展概览
主要介绍如何扩展 tango 设计器。tango 设计器提供了三个部位的自定义扩展能力,分别是标题栏,工具栏,和侧边栏。可以通过下面这张图进行简要的了解:
<img src="https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13359732115/c5ca/c5ae/bbd4/852daf849ac39162baf3bc3a985de6de.png" />
## 标题栏扩展
`DesignerPanel` 提供了 logo, description, actions 三个扩展点,分别对应于 平台标识、应用描述、行动点区域,开发者可以按照需求进行扩展。例如:
```jsx
<DesignerPanel logo={<Logo />} description={<ProjectDetail />} actions={<Box>新版沙箱</Box>}>
</SidebarPanel>
```
## 工具栏扩展
工具栏面板支持任意组合和设置渲染的位置,开发者可以通过 `ToolbarPanel.Item` 添加自定义的工具栏选项,例如下面的代码扩展了一个 newPage 按钮,用于支持在设计器中创建新页面的需求。
```jsx
<ToolbarPanel>
<ToolbarPanel.Item key="history" />
<ToolbarPanel.Item key="viewportRefresh" />
<ToolbarPanel.Item key="preview" />
<ToolbarPanel.Item key="routeSwitch" />
<ToolbarPanel.Item key="newPage">
<Button
onClick={() => {
const { name, code } = genDefaultPage(index);
engine.workspace.addViewPage(`${name}${index++}`, code);
message.success('页面新建成功');
}}
>
添加新页面
</Button>
</ToolbarPanel.Item>
<ToolbarPanel.Item key="modeSwitch" placement="center" />
<ToolbarPanel.Item key="viewportSwitch" placement="right" />
</ToolbarPanel>
```
## 侧边栏扩展
侧边栏面板支持任意组合和调换顺序,用户可以通过 `SidebarPanel.Item` 组件进行自定义扩展,默认情况下根据一些内置的 key 标识,设计器会自动渲染对应的面板,用户也可以根据自己的需求采用自定义渲染子节点的方案进行扩展。
例如,下面的示例代码中扩展了一个 “自定义面板”。
```jsx
<SidebarPanel>
<SidebarPanel.Item key="components" />
<SidebarPanel.Item key="outline" />
<SidebarPanel.Item key="dataSource" />
<SidebarPanel.Item key="dependency" />
<SidebarPanel.Item key="history" />
<SidebarPanel.Item key="custom" title="自定义面板" icon={<BulbOutlined />}>
<Box p="m">这里是一个自定义的面板,你可以任意添加</Box>
</SidebarPanel.Item>
</SidebarPanel>
```
@@ -1,67 +0,0 @@
# 外部数据源
如果您的扩展部件需要使用外部数据源,可以参考本文的做法。
## 存在部件共享
如果数据源需要在多个扩展部件中共享,我们推荐您借助 tango 的 `remoteServices` 规范进行接口的实现。
### 外部数据源定义
例如,我们需要调用一组图片素材的接口,此时我们可以定义一个 `ImageService`,如下:
```tsx
import { createServices } from '@music/request';
export const remoteServices = {
ImageService: createServices(
{
listMy: {
url: '/my/upload/list',
},
listFav: {
url: '/my/star/list',
},
listPub: {
url: '/list',
},
},
{
baseURL: 'https://febase-openapi.fn.netease.com/deer/api/deer/pic',
withCredentials: false, // 解决跨域时必须非*问题
}
),
};
```
### 外部数据源引入
我们在设计器初始化的时候可以通过 `remoteServices` 属性进行外部数据源的传入。
```tsx
<Designer remoteServices={remoteServices}></Designer>
```
### 在设计器扩展中使用定义的外部数据源
```tsx
import { useRemoteServices } from '@music163/tango-designer';
export function CustomWidget() {
const remoteServices = useRemoteServices();
}
```
## 仅在单个组件
如果您的外部数据服务没有额外的共享需求,您也可以直接在组件内部进行数据服务的发起。
```tsx
import request from '@music/request';
export function CustomWidget() {
useEffect(() => {
request('//some.domain/get');
}, []);
}
```
@@ -1,141 +0,0 @@
---
sidebar_position: 3
---
# 物料接入
Tango 提供了低成本的物料接入方式,支持直接复用现有的组件体系。只需要在现有组件包基础上简单的提供配置说明问题,即可轻松接入到 Tango 低代码生态体系中。
import Link from '@docusaurus/Link';
## 预览视图和设计器视图
- 预览视图:是组件默认的渲染模式,通常我们只需要关注此视图行为。
- 设计器视图:为低代码平台定制的渲染视图,当我们需要自定义组件在 Tango 设计器中的部分渲染行为时,可以通过修改设计器视图的渲染逻辑实现。
### 设计器视图和 `withDnd`
通常情况下我们不需要太关注于设计器视图,只需通过 `withDnd` 简单的进行包装即可。例如:
```jsx
// 将 antd Button 包装一层提供设计器视图
export Button = withDnd({
name: 'Button', // 必须,组件的名字,用来正确设置组件的 displayName
isFunctionComponent: true, // 可选,如果包裹的组件为函数组件,可以在此设置
overrideProps: {}, // 可选,覆盖掉包裹组件的默认属性值
})(AntButton);
```
默认情况下 withDnd 会在组件外层包裹一层 dnd 容器,以便于组件能够在设计器中被拖拽:
- draggable 属性表示该区域可以被拖拽
- data-dnd 用来追踪渲染的 dom 元素
```jsx
<div className="dnd-wrapper" draggable data-dnd="button:123">
<button>hello</button>
</div>
```
### 自定义拖拽容器
由于默认情况下拖拽容器是一个 `div` 节点,为[块级元素](https://www.w3schools.com/cssref/pr_class_display.asp)。部分情况下,你可能期望它渲染为一个[行内容器](https://www.w3schools.com/cssref/pr_class_display.asp),此时可以通过如下方式配置:
```jsx
export Button = withDnd({
name: 'Button',
display: 'inline-block', // 将 dnd 容器渲染为行内元素
wrapperStyle: {}, // 传入 dnd 容器的自定义样式
})(AntButton);
```
### 禁用拖拽容器
某些情况下,你的组件可能会检查子节点的有效性,此时你可能不期望组件被包装额外的容器节点,此时你可以关闭拖拽容器,但你需要保证你的组件能够接收父级元素传入的属性信息并附加到组件的跟节点上,否则将无法正确的设置组件的 dnd 信息。
```jsx
export Button = withDnd({
name: 'Button',
hasWrapper: false, // 禁用拖拽容器
})(MyButton);
const MyButton = (props) => {
const { children, ...rest } = props;
// 此时,需要保证你的组件能够正确的将多余的属性透传到组件的根节点上
return <button {...rest}>{children}</button>
}
```
## 组件包接入
组件包一般包含多个组件导出,例如 `@music163/antd` 就是典型的组件包,它面向中后台场景提供统一的物料层解决方案。组件包接入 Tango 体系,无需修改现有的代码实现,只需要在此基础上提供额外的配置文件即可。如下所示:
```
- src
- button
- designer.tsx # 设计器视图,可以不提供
- index.ts # 默认视图出口
- prototype.ts # 组件可配置描述协议
- index.ts
- prototypes.ts
- designer.ts
```
参考示例:https://g.hz.netease.com/NeteaseMusicUI/music-one/-/tree/master/packages/components/src/action
### 设计器视图的实现
对大部分组件而言,都不需要提供特定的设计器视图,只需要使用 tango 提供的 dnd hoc 进行简单的包裹导出即可。
```tsx
import { Button as Base, ButtonProps } from 'antd';
import { withDnd } from '@music/tango-apps-shared';
export const Button = withDnd<HTMLDivElement, ButtonProps<any>>({
name: 'Button',
})(Base);
```
### 编写 prototypes 文件
可以参考 <Link to="/docs/protocol/material-protocol">物料协议</Link> 编写该文档
### 属性设置器选择
参考属性设置器文档进行选择。
### 可配置项的验证
TangoApps 提供了 SettingFormPlayground 组件用来测试组件的可配置能力,可以直接在组件文档中加入可配置测试示例,例如在 storybook 中进行测试。
```jsx
import React from 'react';
import { SettingFormPlayground } from '@music/tango-apps-setting-form';
import { Input, prototypes } from '@music163/antd';
export default {
title: 'Prototype/Input',
};
export function Basic() {
return (
<SettingFormPlayground prototype={prototypes.Input}>
<Input />
</SettingFormPlayground>
);
}
```
SettingFormPlayground 组件支持实时预览配置结果,预览效果如下:
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13140796879/edbb/5dad/a330/013a98940e487956b605e1e533f54371.png" />
## 单个业务组件接入
单个组件接入可以参考组件包的接入方案。
:::info
考虑到单组件形态的特点,在云音乐业务场景中,TangoStudio 将会与红石物料中心对接,提供业务组件的自动化接入能力,目前该能力正在开发过程中。
:::
@@ -1,9 +0,0 @@
# DesignerPanel 设计器布局容器
设计器的主框架提供了一个基本的低代码设计器的布局容器,你可以很轻松的通过此布局容器来实现自己的设计器布局。
## 属性列表
import TypesTable from '@site/src/components/TypesTable';
<TypesTable name="DesignerPanelProps" />
@@ -1,56 +0,0 @@
# Designer 设计器容器
设计器根节点,注入全局状态。
## 属性列表
import TypesTable from '@site/src/components/TypesTable';
<TypesTable name="DesignerProps" />
## 设计器初始化
```jsx
<Designer engine={engine} sandboxQuery={sandboxQuery} remoteServices={remoteServices}></Designer>
```
## engine
设计器引擎实例,用于管理设计器的核心状态。
### 基本的初始化方式
```js
const engine = createEngine({
entry: '/src/index.js',
files: sampleFiles,
componentPrototypes: prototypes as any,
});
```
### 自定义 workspace 的初始化方式
默认情况下,引擎采用的是源码解析模式,即将源码解析为 ast 树,后续的搭建逻辑转为对 ast 树的操作。如果你想自定义搭建逻辑,可以通过自定义 workspace 的方式来实现。
引擎在 2.0 本本中提供了新的 JsonWorkspace 来支持自定义搭建逻辑,JsonWorkspace 采用的是 json 格式的数据结构,你可以通过自定义 json 来实现自定义搭建逻辑。
```js
const engine = createEngine({
workspace: new JsonWorkspace({
prototypes: prototypes as any,
files: schemaFiles,
}),
});
```
:::tip
按照这种方式,你可以自定义自己的 Workspace 实现。具体可以参考 Workspace 的实现标准。
:::
## sandboxQuery
沙箱的查询实例,用于向沙箱注册 dom 查询能力。
## remoteServices
远程服务实例,用于注册全局共享的数据服务实例。
@@ -1,27 +0,0 @@
# Hooks 钩子方法
设计器提供了一组 Hooks 用于快速获取设计器的各种状态。
import TypesTable from '@site/src/components/TypesTable';
## useWorkspace
获取工作区状态。
```js
// App 需要放置在 Designer 容器中
function App() {
const workspace = useWorkspace();
// do what you want
}
```
## useDesigner
```js
// App 需要放置在 Designer 容器中
function App() {
const designer = useDesigner();
// do what you want
}
```
@@ -1,9 +0,0 @@
# Sandbox 沙箱
设计器的运行时沙箱,用来执行代码,渲染页面。
import TypesTable from '@site/src/components/TypesTable';
默认沙箱是一个基于 CodeSandbox 的沙箱实例,可以直接在浏览器端执行代码。
<TypesTable name="SandboxProps" />
@@ -1,9 +0,0 @@
# SettingPanel 属性设置面板
属性设置器用于在配置面板中展示特定配置项的配置逻辑。
## 属性列表
import TypesTable from '@site/src/components/TypesTable';
<TypesTable name="SettingPanelProps" />

Some files were not shown because too many files have changed in this diff Show More