├── docs
└── .gitkeep
├── scripts
└── .gitkeep
├── packages
├── admin
│ └── .gitkeep
├── editor
│ ├── src
│ │ ├── services
│ │ │ ├── appServices.ts
│ │ │ ├── index.ts
│ │ │ ├── getPropsSelectOptions.ts
│ │ │ ├── componentInstanceMap.ts
│ │ │ ├── idGenerator.ts
│ │ │ ├── appActions.ts
│ │ │ ├── createStyles.ts
│ │ │ ├── appPages.ts
│ │ │ └── widget.ts
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ ├── dom.ts
│ │ │ └── pathMath.ts
│ │ ├── plugins
│ │ │ ├── index.ts
│ │ │ ├── vant.ts
│ │ │ └── element-plus.ts
│ │ ├── styles
│ │ │ ├── transition.scss
│ │ │ ├── index.scss
│ │ │ ├── vars-app.scss
│ │ │ ├── layout.scss
│ │ │ ├── vars-dark.scss
│ │ │ └── vars.scss
│ │ ├── common
│ │ │ ├── index.ts
│ │ │ ├── preExpose.ts
│ │ │ ├── lib.ts
│ │ │ └── enum.ts
│ │ ├── components
│ │ │ ├── Formidable
│ │ │ │ ├── hooks
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── useFormData.ts
│ │ │ │ │ └── useDotProp.ts
│ │ │ │ ├── README.md
│ │ │ │ ├── Tips.tsx
│ │ │ │ ├── components
│ │ │ │ │ ├── DateProp.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── TextViewProp.tsx
│ │ │ │ │ └── ObjectProp.tsx
│ │ │ │ ├── index.module.scss
│ │ │ │ └── PropItem.tsx
│ │ │ ├── Stage
│ │ │ │ ├── README.md
│ │ │ │ ├── slot.module.scss
│ │ │ │ ├── PlaceHolder.tsx
│ │ │ │ └── Blocks.tsx
│ │ │ ├── LeftSidebar
│ │ │ │ ├── Basis
│ │ │ │ │ ├── index.module.scss
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Modules
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Business
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Container
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Data
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── WidgetPreview
│ │ │ │ │ └── index.module.scss
│ │ │ │ ├── PageTree
│ │ │ │ │ └── index.module.scss
│ │ │ │ ├── tabs.ts
│ │ │ │ └── index.tsx
│ │ │ ├── Icons
│ │ │ │ ├── IconBase.tsx
│ │ │ │ ├── Modules.tsx
│ │ │ │ ├── Platform.tsx
│ │ │ │ ├── Info.tsx
│ │ │ │ ├── Promotion.tsx
│ │ │ │ ├── File.tsx
│ │ │ │ ├── Undo.tsx
│ │ │ │ ├── Redo.tsx
│ │ │ │ ├── DataSource.tsx
│ │ │ │ ├── BasisComponent.tsx
│ │ │ │ ├── Close.tsx
│ │ │ │ ├── Save.tsx
│ │ │ │ ├── Add.tsx
│ │ │ │ ├── Home.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── Edit.tsx
│ │ │ │ ├── ContainerComponent.tsx
│ │ │ │ ├── Template.tsx
│ │ │ │ ├── BusinessComponent.tsx
│ │ │ │ ├── Page.tsx
│ │ │ │ ├── Folder.tsx
│ │ │ │ └── Arrow.tsx
│ │ │ ├── Home
│ │ │ │ ├── index.module.scss
│ │ │ │ └── index.tsx
│ │ │ ├── RightController
│ │ │ │ ├── PageConfig
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── ConfigHeader.tsx
│ │ │ │ ├── BlockTree
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── Blocks.tsx
│ │ │ │ ├── tabs.ts
│ │ │ │ └── index.module.scss
│ │ │ └── Navbar
│ │ │ │ └── index.module.scss
│ │ ├── stores
│ │ │ ├── index.ts
│ │ │ ├── store.ts
│ │ │ └── appConfig.ts
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useThemeConfig.ts
│ │ │ └── useBlock.ts
│ │ ├── widgets
│ │ │ ├── index.ts
│ │ │ └── gird
│ │ │ │ └── index.tsx
│ │ ├── router
│ │ │ ├── index.ts
│ │ │ └── routes.ts
│ │ ├── main.ts
│ │ ├── App.tsx
│ │ └── env.d.ts
│ ├── public
│ │ ├── favicon.ico
│ │ ├── favicon_32.ico
│ │ ├── favicon_48.ico
│ │ ├── favicon_128.ico
│ │ └── images
│ │ │ ├── logo_128.jpg
│ │ │ ├── logo_128.png
│ │ │ └── logo_64.png
│ ├── tsconfig.node.json
│ ├── index.html
│ ├── tsconfig.build.json
│ └── windi.config.ts
├── core
│ ├── src
│ │ ├── loader
│ │ │ ├── index.ts
│ │ │ └── loadAssert.ts
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ └── path.ts
│ │ ├── renderer
│ │ │ ├── store
│ │ │ │ ├── index.ts
│ │ │ │ └── appConfig.ts
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ └── useCurrentPage.ts
│ │ │ ├── index.ts
│ │ │ ├── components
│ │ │ │ └── ApplicationView.tsx
│ │ │ ├── setupStore.ts
│ │ │ ├── setupRenderer.ts
│ │ │ └── setupRouter.ts
│ │ ├── core.ts
│ │ ├── types
│ │ │ ├── index.ts
│ │ │ ├── AppPages.ts
│ │ │ └── AppConfig.ts
│ │ ├── App.tsx
│ │ ├── env.d.ts
│ │ └── main.ts
│ ├── index.html
│ ├── tsconfig.build.json
│ ├── README.md
│ ├── windi.config.ts
│ └── vite.config.ts
├── cli
│ ├── src
│ │ ├── commands
│ │ │ ├── config
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── dev
│ │ │ │ ├── index.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── watchUserConfigFile.ts
│ │ │ ├── build
│ │ │ │ ├── index.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── createBuild.ts
│ │ │ ├── publish
│ │ │ │ ├── index.ts
│ │ │ │ ├── generateId.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── resolvePackage.ts
│ │ │ │ ├── http.ts
│ │ │ │ └── version.ts
│ │ │ └── index.ts
│ │ ├── vite
│ │ │ ├── index.ts
│ │ │ ├── config
│ │ │ │ ├── index.ts
│ │ │ │ ├── resolveDevConfig.ts
│ │ │ │ ├── resolveBasicConfig.ts
│ │ │ │ └── resolveBuildConfig.ts
│ │ │ ├── createDevApp.ts
│ │ │ └── createBuildApp.ts
│ │ ├── cliConfig
│ │ │ ├── index.ts
│ │ │ ├── resolveConfigPath.ts
│ │ │ └── loadConfig.ts
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ ├── allowTs.ts
│ │ │ ├── esbuild.ts
│ │ │ └── rcPath.ts
│ │ ├── index.ts
│ │ └── userConfig
│ │ │ ├── defineConfig.ts
│ │ │ ├── index.ts
│ │ │ ├── loadUserConfig.ts
│ │ │ ├── resolveDefaultConfig.ts
│ │ │ ├── resolveUserConfigPath.ts
│ │ │ └── types.ts
│ ├── bin
│ │ └── spear.js
│ ├── preview
│ │ ├── main.ts
│ │ ├── render-entry.ts
│ │ ├── App.tsx
│ │ ├── editor-entry.ts
│ │ ├── index.html
│ │ ├── shim.d.ts
│ │ └── widget.tsx
│ ├── tsconfig.preview.json
│ ├── tsconfig.build.json
│ └── README.md
├── create
│ ├── bin
│ │ └── index.js
│ ├── src
│ │ ├── types.ts
│ │ ├── index.ts
│ │ └── normalizeArgv.ts
│ ├── tsconfig.build.json
│ ├── template-service
│ │ └── _gitignore
│ ├── template-component-ts
│ │ ├── _gitignore
│ │ ├── src
│ │ │ ├── widget.config.ts
│ │ │ ├── render.tsx
│ │ │ └── editor.tsx
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── template-component
│ │ ├── _gitignore
│ │ ├── widget.config.js
│ │ ├── package.json
│ │ └── src
│ │ │ ├── render.jsx
│ │ │ └── editor.jsx
│ ├── template-service-ts
│ │ └── _gitignore
│ ├── package.json
│ └── README.md
├── server
│ ├── src
│ │ ├── application
│ │ │ ├── dto
│ │ │ │ ├── index.ts
│ │ │ │ └── create.ts
│ │ │ ├── application.module.ts
│ │ │ ├── application.controller.ts
│ │ │ ├── __test__
│ │ │ │ └── application.controller.spec.ts
│ │ │ └── application.service.ts
│ │ ├── shared
│ │ │ ├── filters
│ │ │ │ ├── index.ts
│ │ │ │ └── exceptions.ts
│ │ │ ├── pipes
│ │ │ │ ├── index.ts
│ │ │ │ └── mustNumber.pipe.ts
│ │ │ ├── exceptions
│ │ │ │ ├── index.ts
│ │ │ │ └── fetchException.ts
│ │ │ ├── interceptors
│ │ │ │ ├── index.ts
│ │ │ │ └── transformResponse.ts
│ │ │ ├── globalPipe.ts
│ │ │ ├── globalInterceptor.ts
│ │ │ ├── globalFilter.ts
│ │ │ ├── index.ts
│ │ │ ├── globalMiddleware.ts
│ │ │ └── httpCode.ts
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ ├── getDirname.ts
│ │ │ └── generateId.ts
│ │ ├── widget
│ │ │ ├── dto
│ │ │ │ ├── index.ts
│ │ │ │ ├── upload.dto.ts
│ │ │ │ └── widget.dto.ts
│ │ │ ├── widget.module.ts
│ │ │ └── widget.controller.ts
│ │ ├── entities
│ │ │ ├── index.ts
│ │ │ ├── Base.ts
│ │ │ └── applications.entity.ts
│ │ ├── main.ts
│ │ ├── config
│ │ │ └── index.ts
│ │ └── app.module.ts
│ ├── nest-cli.json
│ ├── tsconfig.build.json
│ ├── test
│ │ ├── jest-e2e.json
│ │ └── app.e2e-spec.ts
│ ├── .env
│ └── tsconfig.esm.json
├── shared
│ ├── src
│ │ ├── index.ts
│ │ ├── utils
│ │ │ ├── hasOwn.ts
│ │ │ ├── index.ts
│ │ │ ├── defineConfig.ts
│ │ │ ├── is.ts
│ │ │ ├── global.ts
│ │ │ └── loadAssert.ts
│ │ ├── types
│ │ │ ├── index.ts
│ │ │ ├── platform.ts
│ │ │ ├── version.ts
│ │ │ └── defineConfig.ts
│ │ └── css
│ │ │ └── app.css
│ ├── tsconfig.build.json
│ ├── tsup.config.ts
│ ├── tsconfig.dts.json
│ └── package.json
├── utils
│ ├── src
│ │ ├── hasExportDefault.ts
│ │ ├── index.ts
│ │ ├── withSpinner.ts
│ │ └── logger.ts
│ ├── tsconfig.build.json
│ └── package.json
├── tsconfig.base.json
└── tsconfig.build.json
├── widgets
├── demo
│ ├── src
│ │ ├── render.module.scss
│ │ ├── editor.module.scss
│ │ ├── description.module.scss
│ │ ├── description.tsx
│ │ ├── preview.tsx
│ │ ├── render.tsx
│ │ └── editor.ts
│ ├── widget.config.ts
│ └── package.json
├── vant
│ ├── cell
│ │ ├── src
│ │ │ └── render.tsx
│ │ ├── widget.config.ts
│ │ └── package.json
│ ├── cell-group
│ │ ├── src
│ │ │ ├── render.tsx
│ │ │ └── editor.tsx
│ │ ├── widget.config.ts
│ │ └── package.json
│ └── button
│ │ ├── src
│ │ └── render.tsx
│ │ ├── widget.config.ts
│ │ └── package.json
└── element-plus
│ ├── button-group
│ ├── src
│ │ ├── render.tsx
│ │ └── editor.tsx
│ ├── widget.config.ts
│ └── package.json
│ ├── link
│ ├── src
│ │ ├── render.tsx
│ │ └── editor.tsx
│ ├── widget.config.ts
│ └── package.json
│ ├── button
│ ├── widget.config.ts
│ ├── src
│ │ └── render.tsx
│ └── package.json
│ ├── input
│ ├── widget.config.ts
│ ├── package.json
│ └── src
│ │ └── render.tsx
│ └── layout
│ ├── widget.config.ts
│ └── package.json
├── .mind
├── spearJs.rp
├── 思维导图.md
├── 想法2.md
├── 想法4.md
└── pseudo-code
│ ├── widget.js
│ ├── loadWidget.js
│ └── formidable.ts
├── .stylelintrc.json
├── .husky
├── pre-commit
└── commit-msg
├── .stylelintignore
├── pnpm-workspace.yaml
├── .gitattributes
├── .eslintignore
├── .editorconfig
├── .eslintrc.cjs
├── tsconfig.base.json
├── .gitignore
├── tsconfig.json
├── commitlint.config.cjs
├── README.md
├── .cz-config.cjs
├── LICENSE
└── .vscode
└── settings.json
/docs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scripts/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/admin/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/editor/src/services/appServices.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/core/src/loader/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Loader'
2 |
--------------------------------------------------------------------------------
/packages/core/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './path'
2 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/store/index.ts:
--------------------------------------------------------------------------------
1 | export * from './appConfig'
2 |
--------------------------------------------------------------------------------
/widgets/demo/src/render.module.scss:
--------------------------------------------------------------------------------
1 | .txt {
2 | width: 100%;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/config/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createConfig'
2 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useCurrentPage'
2 |
--------------------------------------------------------------------------------
/packages/create/bin/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('./dist/')
3 |
--------------------------------------------------------------------------------
/packages/server/src/application/dto/index.ts:
--------------------------------------------------------------------------------
1 | export * from './create.js'
2 |
--------------------------------------------------------------------------------
/widgets/demo/src/editor.module.scss:
--------------------------------------------------------------------------------
1 | .txt {
2 | font-size: 16px;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/server/src/shared/filters/index.ts:
--------------------------------------------------------------------------------
1 | export * from './exceptions.js'
2 |
--------------------------------------------------------------------------------
/packages/server/src/shared/pipes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './validation.pipe.js'
2 |
--------------------------------------------------------------------------------
/widgets/demo/src/description.module.scss:
--------------------------------------------------------------------------------
1 | .txt {
2 | font-weight: bold;
3 | }
4 |
--------------------------------------------------------------------------------
/.mind/spearJs.rp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/.mind/spearJs.rp
--------------------------------------------------------------------------------
/packages/server/src/shared/exceptions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './fetchException.js'
2 |
--------------------------------------------------------------------------------
/packages/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types'
2 | export * from './utils'
3 |
--------------------------------------------------------------------------------
/.mind/思维导图.md:
--------------------------------------------------------------------------------
1 | 
2 |
--------------------------------------------------------------------------------
/packages/editor/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dom'
2 | export * from './pathMath'
3 |
--------------------------------------------------------------------------------
/packages/server/src/shared/interceptors/index.ts:
--------------------------------------------------------------------------------
1 | export * from './transformResponse.js'
2 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "@pengzhanbo/stylelint-config"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/dev/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createDev'
2 | export * from './types'
3 |
--------------------------------------------------------------------------------
/packages/editor/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | export * from './element-plus'
2 | export * from './vant'
3 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm lint-staged
5 |
--------------------------------------------------------------------------------
/packages/cli/bin/spear.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { cli } from '../dist'
4 |
5 | cli()
6 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/build/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types'
2 | export * from './createBuild'
3 |
--------------------------------------------------------------------------------
/packages/cli/src/vite/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createDevApp'
2 | export * from './createBuildApp'
3 |
--------------------------------------------------------------------------------
/packages/cli/src/cliConfig/index.ts:
--------------------------------------------------------------------------------
1 | export * from './resolveConfigPath'
2 | export * from './loadConfig'
3 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/publish/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createPublish'
2 | export * from './types'
3 |
--------------------------------------------------------------------------------
/packages/editor/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | .flip-list-move {
2 | transition: transform 0.3s;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/server/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './generateId.js'
2 | export * from './getDirname.js'
3 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm commitlint --edit
5 |
--------------------------------------------------------------------------------
/packages/core/src/core.ts:
--------------------------------------------------------------------------------
1 | export * from './renderer'
2 | export * from './loader'
3 | export * from './types'
4 |
--------------------------------------------------------------------------------
/packages/server/src/widget/dto/index.ts:
--------------------------------------------------------------------------------
1 | export * from './upload.dto.js'
2 | export * from './widget.dto.js'
3 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './allowTs'
2 | export * from './esbuild'
3 | export * from './rcPath'
4 |
--------------------------------------------------------------------------------
/packages/editor/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/packages/editor/public/favicon.ico
--------------------------------------------------------------------------------
/packages/editor/src/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './enum'
2 | export * from './lib'
3 | export * from './preExpose'
4 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useFormData'
2 | export * from './useDotProp'
3 |
--------------------------------------------------------------------------------
/packages/editor/src/stores/index.ts:
--------------------------------------------------------------------------------
1 | export * from './store'
2 | export * from './pages'
3 | export * from './appConfig'
4 |
--------------------------------------------------------------------------------
/packages/core/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AppConfig'
2 | export * from './AppPages'
3 | export * from './AppBlocks'
4 |
--------------------------------------------------------------------------------
/packages/editor/public/favicon_32.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/packages/editor/public/favicon_32.ico
--------------------------------------------------------------------------------
/packages/editor/public/favicon_48.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/packages/editor/public/favicon_48.ico
--------------------------------------------------------------------------------
/packages/editor/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/packages/editor/src/assets/logo.png
--------------------------------------------------------------------------------
/packages/editor/public/favicon_128.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/packages/editor/public/favicon_128.ico
--------------------------------------------------------------------------------
/packages/editor/public/images/logo_128.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/packages/editor/public/images/logo_128.jpg
--------------------------------------------------------------------------------
/packages/editor/public/images/logo_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/packages/editor/public/images/logo_128.png
--------------------------------------------------------------------------------
/packages/editor/public/images/logo_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengzhanbo/spearjs/HEAD/packages/editor/public/images/logo_64.png
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/dist
3 | **/public
4 | **/.vscode
5 |
6 | **/*.js
7 | **/*.jsx
8 | **/*.ts
9 | **/*.tsx
10 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Stage/README.md:
--------------------------------------------------------------------------------
1 | # Stage
2 |
3 | Editor 舞台组件
4 |
5 | 负责 widget component 的渲染、 用户对 widget component 的交互操作。
6 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - packages/*
3 | - widgets/demo
4 | - widgets/element-plus/*
5 | - widgets/vant/*
6 | - docs
7 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dev'
2 | export * from './build'
3 | export * from './publish'
4 | export * from './config'
5 |
--------------------------------------------------------------------------------
/packages/shared/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.dts.json",
3 | "compilerOptions": {
4 | "composite": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/cli/src/vite/config/index.ts:
--------------------------------------------------------------------------------
1 | export * from './resolveBasicConfig'
2 | export * from './resolveDevConfig'
3 | export * from './resolveBuildConfig'
4 |
--------------------------------------------------------------------------------
/packages/server/src/entities/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Base.js'
2 |
3 | export * from './applications.entity.js'
4 | export * from './widget.entity.js'
5 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/Basis/index.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | @apply w-1/3;
3 |
4 | min-width: 320px;
5 | max-width: 450px;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/shared/src/utils/hasOwn.ts:
--------------------------------------------------------------------------------
1 | export const hasOwn = (obj: object, key: string) => {
2 | return Object.prototype.hasOwnProperty.call(obj, key)
3 | }
4 |
--------------------------------------------------------------------------------
/packages/create/src/types.ts:
--------------------------------------------------------------------------------
1 | export interface ArgvOptions {
2 | targetDir: string
3 | template: 'component' | 'service' | ''
4 | typescript: boolean
5 | }
6 |
--------------------------------------------------------------------------------
/packages/server/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/nest-cli",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "src"
5 | }
6 |
--------------------------------------------------------------------------------
/packages/cli/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './cli'
2 | export * from './commands'
3 | export * from './utils'
4 | export * from './vite'
5 | export * from './userConfig'
6 |
--------------------------------------------------------------------------------
/widgets/demo/src/description.tsx:
--------------------------------------------------------------------------------
1 | import styles from './description.module.scss'
2 |
3 | export default () => {
4 | return
这是一段文字描述
5 | }
6 |
--------------------------------------------------------------------------------
/packages/cli/src/userConfig/defineConfig.ts:
--------------------------------------------------------------------------------
1 | import type { UserConfig } from './types'
2 |
3 | export function defineConfig(config: UserConfig) {
4 | return config
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/index.ts:
--------------------------------------------------------------------------------
1 | import ApplicationView from './components/ApplicationView'
2 |
3 | export * from './setupRenderer'
4 |
5 | export { ApplicationView }
6 |
--------------------------------------------------------------------------------
/packages/core/src/utils/path.ts:
--------------------------------------------------------------------------------
1 | export const normalizePath = (cwd: string, relative: string) => {
2 | return `/${cwd}/${relative}`.replace(/\/+/g, '/').replace(/\/+$/, '')
3 | }
4 |
--------------------------------------------------------------------------------
/packages/shared/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './hasOwn'
2 | export * from './is'
3 | export * from './global'
4 | export * from './defineConfig'
5 | export * from './loadAssert'
6 |
--------------------------------------------------------------------------------
/packages/shared/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './platform'
2 | export * from './widget'
3 | export * from './widgetProps'
4 | export * from './version'
5 | export * from './defineConfig'
6 |
--------------------------------------------------------------------------------
/packages/shared/src/types/platform.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Platform Support to SpearJs lowCode
3 | * SpearJs 低代码开发平台支持的平台
4 | */
5 | export type Platform = 'pc' | 'mobile'
6 | // | 'miniProgram'
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.txt text eol=crlf
3 |
4 | *.png binary
5 | *.jpg binary
6 | *.jpeg binary
7 | *.ico binary
8 | *.tff binary
9 | *.woff binary
10 | *.woff2 binary
11 |
--------------------------------------------------------------------------------
/packages/cli/preview/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App'
3 |
4 | import '@spearjs/shared/app.css'
5 |
6 | const app = createApp(App)
7 |
8 | app.mount('#app')
9 |
--------------------------------------------------------------------------------
/packages/cli/src/cliConfig/resolveConfigPath.ts:
--------------------------------------------------------------------------------
1 | import { getRcPath } from '../utils'
2 |
3 | const rcFilename = '.spearjslowcoderc'
4 |
5 | export const getRCFilePath = () => getRcPath(rcFilename)
6 |
--------------------------------------------------------------------------------
/packages/editor/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './vars';
2 | @import './vars-dark';
3 | @import './vars-app';
4 |
5 | @import 'normalize.css';
6 |
7 | @import './layout';
8 | @import './transition';
9 |
--------------------------------------------------------------------------------
/packages/editor/src/plugins/vant.ts:
--------------------------------------------------------------------------------
1 | import 'vant/lib/index.css'
2 | import Vant from 'vant'
3 | import type { App } from 'vue'
4 |
5 | export const setupVant = (app: App) => {
6 | app.use(Vant)
7 | }
8 |
--------------------------------------------------------------------------------
/packages/editor/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "esnext",
5 | "moduleResolution": "node"
6 | },
7 | "include": ["./vite.config.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/widgets/demo/src/preview.tsx:
--------------------------------------------------------------------------------
1 | import styles from './editor.module.scss'
2 |
3 | export default () => {
4 | return (
5 | <>
6 |
7 | >
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.sh
2 | node_modules
3 | **/node_modules
4 | *.md
5 | *.woff
6 | *.ttf
7 | dist
8 | **/dist
9 | .husky
10 | .local
11 | .DS_Store
12 | .mind
13 | **/*.log*
14 |
15 | packages/create/template-*
16 |
--------------------------------------------------------------------------------
/packages/cli/src/userConfig/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types'
2 | export * from './defineConfig'
3 | export * from './resolveUserConfigPath'
4 | export * from './loadUserConfig'
5 | export * from './resolveDefaultConfig'
6 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/README.md:
--------------------------------------------------------------------------------
1 | # Formidable
2 |
3 | 一个简单的,为了满足本 low-code platform 而设计的 表单生成模块。
4 |
5 | 根据 配置 规则,自动生成表单。
6 |
7 | 配置规则 参考 [Widget Props](../../../../shared/src/types/widgetProps.ts)
8 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/publish/generateId.ts:
--------------------------------------------------------------------------------
1 | import { customAlphabet } from 'nanoid'
2 |
3 | export const generateId = customAlphabet(
4 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
5 | 16,
6 | )
7 |
--------------------------------------------------------------------------------
/packages/server/src/utils/getDirname.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { fileURLToPath } from 'node:url'
3 |
4 | export const getDirname = (importMetaUrl: string) => {
5 | return path.dirname(fileURLToPath(importMetaUrl))
6 | }
7 |
--------------------------------------------------------------------------------
/packages/server/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.esm.json",
3 | "compilerOptions": {
4 | "composite": true
5 | },
6 | "include": ["./src"],
7 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/shared/src/css/app.css:
--------------------------------------------------------------------------------
1 | :root {
2 | /* application color */
3 | --app-c-brand: #666;
4 | --app-c-bg: var(--c-bg-container);
5 | }
6 |
7 | html.dark {
8 | /* application dark color */
9 | --app-c-brand: #999;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/Modules/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 |
3 | export default defineComponent({
4 | name: 'ModulesTab',
5 | setup: () => {
6 | return () => modules tab
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/Business/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 |
3 | export default defineComponent({
4 | name: 'BusinessTab',
5 | setup: () => {
6 | return () => business tab
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/Container/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 |
3 | export default defineComponent({
4 | name: 'ContainerTab',
5 | setup: () => {
6 | return () => container tab
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/Data/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 |
3 | export default defineComponent({
4 | name: 'DataTab',
5 | setup: () => {
6 | return () => data source/model tab
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/packages/server/src/shared/globalPipe.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common'
2 | import { validation } from './pipes/index.js'
3 |
4 | export const useGlobalPipe = (app: INestApplication) => {
5 | app.useGlobalPipes(validation())
6 | }
7 |
--------------------------------------------------------------------------------
/packages/editor/src/stores/store.ts:
--------------------------------------------------------------------------------
1 | import { createPinia } from 'pinia'
2 | import type { App, Plugin } from 'vue'
3 |
4 | export const store = createPinia()
5 |
6 | export const setupStore = (app: App) => {
7 | app.use(store as unknown as Plugin)
8 | }
9 |
--------------------------------------------------------------------------------
/packages/server/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 | import { ApplicationView } from './renderer'
3 |
4 | export default defineComponent({
5 | name: 'App',
6 | setup: () => {
7 | return () =>
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/cli/tsconfig.preview.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "module": "ESNext",
6 | "composite": true,
7 | "jsx": "preserve"
8 | },
9 | "include": ["./preview"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/create/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "module": "CommonJs",
5 | "rootDir": "./src",
6 | "outDir": "./dist"
7 | },
8 | "include": ["./src"],
9 | "exclude": ["./dist"]
10 | }
11 |
--------------------------------------------------------------------------------
/widgets/vant/cell/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig } from '@spearjs/shared'
2 | import { Cell } from 'vant'
3 |
4 | export default defineRenderConfig({
5 | render({ props, action }) {
6 | return action('click')} />
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/packages/server/.env:
--------------------------------------------------------------------------------
1 | # 在本地开发时,需要新建 `.env.local` 文件进行 本地配置
2 |
3 | # mysql database config
4 | MYSQL_HOST=localhost
5 | MYSQL_PORT=YOUR_MYSQL_PROT
6 | MYSQL_USERNAME=YOUR_MYSQL_USERNAME
7 | MYSQL_PASSWORD=YOUR_MYSQL_PASSWORD
8 | MYSQL_DATABASE=db_spearjs
9 |
10 | SERVER_PORT=4396
11 |
--------------------------------------------------------------------------------
/widgets/vant/cell-group/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig } from '@spearjs/shared'
2 | import { CellGroup } from 'vant'
3 |
4 | export default defineRenderConfig({
5 | render({ props, slots }) {
6 | return {slots.default?.()}
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/components/ApplicationView.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 | import { RouterView } from 'vue-router'
3 |
4 | export default defineComponent({
5 | name: 'ApplicationView',
6 | setup: () => {
7 | return () =>
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = false
12 | insert_final_newline = true
13 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/setupStore.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig } from '@core/types'
2 | import { useAppConfigStore } from './store'
3 |
4 | export const setupStore = (appConfig: AppConfig) => {
5 | const store = useAppConfigStore()
6 | store.$state = JSON.parse(JSON.stringify(appConfig))
7 | }
8 |
--------------------------------------------------------------------------------
/packages/utils/src/hasExportDefault.ts:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from '@spearjs/shared'
2 |
3 | export const hasExportDefault = (
4 | mod: unknown,
5 | ): mod is { default: T } =>
6 | isPlainObject(mod) &&
7 | !!mod.__esModule &&
8 | Object.prototype.hasOwnProperty.call(mod, 'default')
9 |
--------------------------------------------------------------------------------
/widgets/element-plus/button-group/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig } from '@spearjs/shared'
2 | import { ElButtonGroup } from 'element-plus'
3 |
4 | export default defineRenderConfig({
5 | render({ slots }) {
6 | return {slots.default?.()}
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/packages/cli/preview/render-entry.ts:
--------------------------------------------------------------------------------
1 | import { registerWidget } from '@spearjs/shared'
2 | import widgetConfig from 'spearjs/widget/config'
3 | import render from 'spearjs/widget/render'
4 |
5 | registerWidget({
6 | id: widgetConfig.id,
7 | version: widgetConfig.version,
8 | ...render,
9 | })
10 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/allowTs.ts:
--------------------------------------------------------------------------------
1 | import { transformTsFile } from './esbuild'
2 |
3 | export const allowTs = (): void => {
4 | // eslint-disable-next-line n/no-deprecated-api
5 | require.extensions['.ts'] = (m: any, filename) => {
6 | m._compile(transformTsFile(filename), filename)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/server/src/widget/dto/upload.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsString } from 'class-validator'
2 | import { WidgetDto } from './widget.dto.js'
3 |
4 | export class UploadWidgetDto extends WidgetDto {
5 | @IsString()
6 | editorAssert!: string
7 |
8 | @IsString()
9 | renderAssert!: string
10 | }
11 |
--------------------------------------------------------------------------------
/packages/shared/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | clean: true,
5 | dts: true,
6 | entry: ['src/index.ts'],
7 | format: ['cjs', 'esm'],
8 | splitting: false,
9 | sourcemap: false,
10 | tsconfig: 'tsconfig.dts.json',
11 | })
12 |
--------------------------------------------------------------------------------
/packages/server/src/utils/generateId.ts:
--------------------------------------------------------------------------------
1 | import { customAlphabet } from 'nanoid'
2 |
3 | const hashLetter =
4 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
5 |
6 | export const generateAppId = customAlphabet(hashLetter, 12)
7 |
8 | export const generateWidgetId = customAlphabet(hashLetter, 16)
9 |
--------------------------------------------------------------------------------
/packages/cli/preview/App.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 | import Test from './test'
3 |
4 | export default defineComponent({
5 | name: 'App',
6 | setup: () => {
7 | return () => (
8 | <>
9 | 111
10 |
11 | >
12 | )
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/build/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type of `build` command function
3 | */
4 | export type BuildCommand = (
5 | commandOptions?: BuildCommandOptions,
6 | ) => Promise
7 |
8 | export interface BuildCommandOptions {
9 | dest?: string
10 | config?: string
11 | debug?: boolean
12 | }
13 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Stage/slot.module.scss:
--------------------------------------------------------------------------------
1 | .block-slot {
2 | contain: layout;
3 | outline: dashed 1px;
4 | outline-offset: 0;
5 |
6 | @apply block outline-gray-300;
7 | }
8 |
9 | .slot-placeholder {
10 | @apply min-h-10 text-center text-xs py-1 border border-dashed border-gray-300;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/editor/src/styles/vars-app.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * editor css variables 配置
3 | * 用于在 application 的配置
4 | * 这部分配置,在后面会转到 shared 包中,
5 | * 因为这部分配置需要共享到 widget、render、editor中使用
6 | *
7 | * 使用 --app-* 作为命名空间
8 | */
9 |
10 | :root {
11 | --app-c-brand: var(--c-brand);
12 | --app-c-bg: var(--c-bg);
13 | }
14 |
--------------------------------------------------------------------------------
/packages/server/src/shared/globalInterceptor.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common'
2 | import { TransformResponseInterceptor } from './interceptors/index.js'
3 |
4 | export const useGlobalInterceptor = (app: INestApplication) => {
5 | app.useGlobalInterceptors(new TransformResponseInterceptor())
6 | }
7 |
--------------------------------------------------------------------------------
/packages/cli/preview/editor-entry.ts:
--------------------------------------------------------------------------------
1 | import { registerWidget } from '@spearjs/shared'
2 | import widgetConfig from 'spearjs/widget/config'
3 | import editor from 'spearjs/widget/editor'
4 | import render from 'spearjs/widget/render'
5 |
6 | registerWidget({
7 | ...widgetConfig,
8 | ...editor,
9 | ...render,
10 | })
11 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/publish/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type of `build` command function
3 | */
4 | export type PublishCommand = (
5 | commandOptions?: PublishCommandOptions,
6 | ) => Promise
7 |
8 | export interface PublishCommandOptions {
9 | target?: string
10 | dest?: string
11 | config?: string
12 | }
13 |
--------------------------------------------------------------------------------
/widgets/element-plus/link/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig } from '@spearjs/shared'
2 | import { ElLink } from 'element-plus'
3 |
4 | export default defineRenderConfig({
5 | render({ props }) {
6 | const { text, ...otherProps } = props
7 | return {text}
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/editor/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useStoreCache'
2 | export * from './useAppLayout'
3 | export * from './useThemeConfig'
4 |
5 | export * from './useBlocksDrop'
6 | export * from './useBlockDnD'
7 | export * from './useContextMenu'
8 | export * from './useDropPlaceHolder'
9 |
10 | export * from './useBlock'
11 |
--------------------------------------------------------------------------------
/packages/utils/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "composite": true,
6 | "lib": ["ESNext"],
7 | "rootDir": "./src",
8 | "outDir": "./dist"
9 | },
10 | "include": ["./src"],
11 | "exclude": ["dist", "node_modules"]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/config/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type of `config` command function
3 | */
4 | export type ConfigCommand = (
5 | commandOptions?: ConfigCommandOptions,
6 | ) => Promise
7 |
8 | export interface ConfigCommandOptions {
9 | list: boolean
10 | addRepository: string
11 | deleteRepository: string
12 | }
13 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/IconBase.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 |
3 | export const IconBase: FunctionalComponent<{
4 | viewBox?: string
5 | }> = ({ viewBox = '0 0 512 512' }, { slots }) => (
6 |
9 | )
10 |
--------------------------------------------------------------------------------
/packages/server/src/shared/globalFilter.ts:
--------------------------------------------------------------------------------
1 | import { ConsoleLogger, INestApplication } from '@nestjs/common'
2 | import { GlobalExceptionFilter } from './filters/index.js'
3 |
4 | export const useGlobalFilter = (app: INestApplication) => {
5 | app.useGlobalFilters(
6 | new GlobalExceptionFilter(new ConsoleLogger('global filter')),
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/packages/server/src/application/dto/create.ts:
--------------------------------------------------------------------------------
1 | import { IsString, MaxLength } from 'class-validator'
2 | import { httpCode } from '../../shared/index.js'
3 |
4 | export class CreateApplicationDto {
5 | @MaxLength(50, {
6 | message: '应用名称长度不能超过25',
7 | context: { code: httpCode.paramsError.code },
8 | })
9 | @IsString()
10 | name!: string
11 | }
12 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/dev/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * type of `dev` command functions
3 | */
4 | export type DevCommand = (commandOptions?: DevCommandOptions) => Promise
5 |
6 | /**
7 | * Cli options of `dev` command
8 | */
9 | export interface DevCommandOptions {
10 | port?: number
11 | host?: string
12 | open?: boolean
13 |
14 | config?: string
15 | }
16 |
--------------------------------------------------------------------------------
/packages/cli/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "outDir": "./dist",
6 | "target": "ES2020",
7 | "rootDir": "./src",
8 | "composite": true
9 | },
10 | "include": ["./src"],
11 | "exclude": ["./dist"],
12 | "references": [{ "path": "./tsconfig.preview.json" }]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/shared/src/types/version.ts:
--------------------------------------------------------------------------------
1 | type VersionSort = `${number}.${number}.${number}`
2 |
3 | type VersionPreReleaseType = 'Alpha' | 'Beta' | 'Rc'
4 |
5 | type VersionPreRelease = `${VersionPreReleaseType}.${number}`
6 |
7 | /**
8 | * 版本号
9 | * example: 1.0.0 | 1.0.0-Alpha.1
10 | */
11 | export type WidgetVersion = VersionSort | `${VersionSort}-${VersionPreRelease}`
12 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/esbuild.ts:
--------------------------------------------------------------------------------
1 | import { fs } from '@spearjs/utils'
2 | import { transformSync } from 'esbuild'
3 |
4 | export const transformTsFile = (filename: string): string =>
5 | transformSync(fs.readFileSync(filename).toString(), {
6 | format: 'cjs',
7 | loader: 'ts',
8 | sourcefile: filename,
9 | sourcemap: 'inline',
10 | target: 'node14',
11 | }).code
12 |
--------------------------------------------------------------------------------
/packages/core/src/types/AppPages.ts:
--------------------------------------------------------------------------------
1 | import type { AppBlocks } from './AppBlocks'
2 |
3 | export type AppPageList = AppPage[]
4 |
5 | export interface AppPage {
6 | path: string
7 | title: string
8 | isHome?: boolean
9 | config: AppPageConfig
10 | blocks: AppBlocks
11 | children?: AppPageList
12 | }
13 |
14 | export interface AppPageConfig {
15 | [prop: string]: any
16 | }
17 |
--------------------------------------------------------------------------------
/packages/server/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './httpCode.js'
2 |
3 | export * from './interceptors/index.js'
4 | export * from './exceptions/index.js'
5 | export * from './pipes/index.js'
6 | export * from './filters/index.js'
7 |
8 | export * from './globalMiddleware.js'
9 | export * from './globalInterceptor.js'
10 | export * from './globalPipe.js'
11 | export * from './globalFilter.js'
12 |
--------------------------------------------------------------------------------
/widgets/vant/button/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig } from '@spearjs/shared'
2 | import { Button } from 'vant'
3 |
4 | export default defineRenderConfig({
5 | render({ props, action }) {
6 | const { text, ...otherProps } = props
7 | return (
8 |
11 | )
12 | },
13 | })
14 |
--------------------------------------------------------------------------------
/packages/create/template-service/_gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/packages/editor/src/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './idGenerator'
2 |
3 | export * from './widgetComponent'
4 | export * from './widget'
5 |
6 | export * from './appBlocks'
7 | export * from './createProps'
8 | export * from './getPropsSelectOptions'
9 | export * from './createStyles'
10 |
11 | export * from './appActions'
12 |
13 | export * from './appPages'
14 |
15 | export * from './Loader'
16 |
--------------------------------------------------------------------------------
/packages/create/template-component-ts/_gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/packages/create/template-component/_gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/packages/create/template-service-ts/_gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Home/index.module.scss:
--------------------------------------------------------------------------------
1 | .editor-wrapper {
2 | @apply relative w-full h-full overflow-auto;
3 |
4 | .editor-container {
5 | @apply relative;
6 | }
7 |
8 | &::-webkit-scrollbar {
9 | width: 0;
10 | height: 7px;
11 | }
12 |
13 | &::-webkit-scrollbar-thumb {
14 | background-color: rgba(0, 0, 0, 0.55);
15 | border-radius: 3.5px;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/editor/src/components/RightController/PageConfig/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 |
3 | export default defineComponent({
4 | name: 'PageConfig',
5 | setup: () => {
6 | return () => (
7 |
8 | 页面路由配置: 路由参数配置
9 | 初始化数据配置: 通过 services 获取数据,并挂载在 store上。
10 | 组件可以通过数据模型映射获取store上的数据
11 |
12 | )
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/widgets/demo/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig } from '@spearjs/shared'
2 | import styles from './render.module.scss'
3 |
4 | export default defineRenderConfig({
5 | setup: () => {
6 | return {
7 | a: 1,
8 | }
9 | },
10 | render({ props }) {
11 | return (
12 |
15 | )
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/packages/create/src/index.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '@spearjs/utils'
2 | import * as minimist from 'minimist'
3 | import { cli } from './cli'
4 | import { normalizeArgv } from './normalizeArgv'
5 |
6 | const init = async () => {
7 | const argv = minimist(process.argv.slice(2), { string: ['_'] })
8 | await cli(normalizeArgv(argv), process.cwd())
9 | }
10 |
11 | init().catch((e) => {
12 | logger.error(e)
13 | })
14 |
--------------------------------------------------------------------------------
/packages/shared/src/utils/defineConfig.ts:
--------------------------------------------------------------------------------
1 | import type { EditorConfig, RenderConfig } from '../types'
2 |
3 | export function defineEditorConfig(config: EditorConfig) {
4 | return config
5 | }
6 |
7 | export function defineRenderConfig<
8 | Props = Record,
9 | RawBindings = Record,
10 | >(config: RenderConfig): RenderConfig {
11 | return config
12 | }
13 |
--------------------------------------------------------------------------------
/packages/cli/src/vite/config/resolveDevConfig.ts:
--------------------------------------------------------------------------------
1 | import { path } from '@spearjs/utils'
2 | import type { InlineConfig } from 'vite'
3 |
4 | export const resolveDevConfig = (config: InlineConfig = {}): InlineConfig => {
5 | config.root = path.resolve(__dirname, '../../../preview/')
6 | config.build!.rollupOptions = {
7 | input: path.resolve(__dirname, '../../../preview/index.html'),
8 | }
9 |
10 | return config
11 | }
12 |
--------------------------------------------------------------------------------
/packages/server/src/entities/Base.ts:
--------------------------------------------------------------------------------
1 | import { Exclude } from 'class-transformer'
2 | import { Column, PrimaryGeneratedColumn } from 'typeorm'
3 |
4 | export class BaseEntity {
5 | @PrimaryGeneratedColumn()
6 | id!: number
7 |
8 | @Exclude()
9 | @Column('datetime', { name: 'create_time' })
10 | createTime!: Date
11 |
12 | @Exclude()
13 | @Column('datetime', { name: 'update_time' })
14 | updateTime!: Date
15 | }
16 |
--------------------------------------------------------------------------------
/packages/server/src/shared/exceptions/fetchException.ts:
--------------------------------------------------------------------------------
1 | import { HttpException, HttpStatus } from '@nestjs/common'
2 | import { HttpCode, httpCode } from '../httpCode.js'
3 |
4 | export class FetchException extends HttpException {
5 | constructor(excData: HttpCode) {
6 | if (typeof excData.code === 'undefined') {
7 | excData.code = httpCode.error.code
8 | }
9 | super(excData, HttpStatus.OK)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/utils/src/index.ts:
--------------------------------------------------------------------------------
1 | import debug from 'debug'
2 | import fastGlob from 'fast-glob'
3 | import inquirer from 'inquirer'
4 | import ora from 'ora'
5 | import colors from 'picocolors'
6 |
7 | export { debug, colors, fastGlob, ora, inquirer }
8 |
9 | export * as fs from 'fs-extra'
10 | export * as path from 'upath'
11 |
12 | export * from './withSpinner'
13 | export * from './logger'
14 | export * from './hasExportDefault'
15 |
--------------------------------------------------------------------------------
/widgets/demo/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: 'demo',
6 | type: 'component',
7 | componentType: 'basis',
8 | dependence: 'vant',
9 | platform: 'mobile',
10 | editorFiles: path.resolve(__dirname, './src/editor.ts'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/packages/core/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue'
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
10 | declare module 'virtual:*' {
11 | const result: any
12 | export default result
13 | }
14 |
--------------------------------------------------------------------------------
/widgets/vant/button/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: '按钮',
6 | type: 'component',
7 | componentType: 'basis',
8 | dependence: 'vant',
9 | platform: 'mobile',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/widgets/vant/cell/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: 'Cell单元格',
6 | type: 'component',
7 | componentType: 'basis',
8 | dependence: 'vant',
9 | platform: 'mobile',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/packages/cli/preview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | SpearJs Widget Preview
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/editor/src/common/preExpose.ts:
--------------------------------------------------------------------------------
1 | import type { WidgetExposeList } from '@spearjs/shared'
2 |
3 | /**
4 | * widget 类型为 component 时,预设 expose
5 | */
6 | export const preExposeList: WidgetExposeList = [
7 | {
8 | type: 'method',
9 | label: '显示组件',
10 | name: 'show',
11 | global: true,
12 | },
13 | {
14 | type: 'method',
15 | label: '隐藏组件',
16 | name: 'hide',
17 | global: true,
18 | },
19 | ]
20 |
--------------------------------------------------------------------------------
/widgets/element-plus/button/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: '按钮',
6 | type: 'component',
7 | componentType: 'basis',
8 | dependence: 'element-plus',
9 | platform: 'pc',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/widgets/element-plus/input/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: '输入框',
6 | type: 'component',
7 | componentType: 'basis',
8 | dependence: 'element-plus',
9 | platform: 'pc',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/widgets/element-plus/link/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: '链接',
6 | type: 'component',
7 | componentType: 'basis',
8 | dependence: 'element-plus',
9 | platform: 'pc',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@pengzhanbo/eslint-config-vue'],
4 | overrides: [
5 | {
6 | files: [
7 | './packages/server/src/**/*.ts',
8 | './packages/server/test/**/*.ts',
9 | ],
10 | rules: {
11 | '@typescript-eslint/consistent-type-imports': 'off',
12 | '@typescript-eslint/explicit-module-boundary-types': 'off',
13 | },
14 | },
15 | ],
16 | }
17 |
--------------------------------------------------------------------------------
/packages/create/template-component/widget.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | /**
4 | * @type {import('@spearjs/cli').UserConfig}
5 | */
6 | export default {
7 | name: 'my-widget',
8 | type: 'component',
9 | componentType: 'basis',
10 | dependence: '',
11 | platform: 'mobile',
12 | editorFiles: path.resolve(__dirname, './src/editor.jsx'),
13 | renderFiles: path.resolve(__dirname, './src/render.jsx'),
14 | dest: 'dist',
15 | }
16 |
--------------------------------------------------------------------------------
/widgets/vant/cell-group/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: 'Cell单元格分组容器',
6 | type: 'component',
7 | componentType: 'container',
8 | dependence: 'vant',
9 | platform: 'mobile',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/packages/shared/tsconfig.dts.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "moduleResolution": "node",
5 | "strict": true,
6 | "declaration": true,
7 | "noUnusedLocals": true,
8 | "esModuleInterop": true,
9 | "module": "ESNext",
10 | "lib": ["DOM", "ESNext"],
11 | "resolveJsonModule": true,
12 | "noEmitOnError": true
13 | },
14 | "include": ["./src"],
15 | "exclude": ["dist", "node_modules"]
16 | }
17 |
--------------------------------------------------------------------------------
/widgets/element-plus/button-group/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: '按钮组',
6 | type: 'component',
7 | componentType: 'container',
8 | dependence: 'element-plus',
9 | platform: 'pc',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/widgets/element-plus/layout/widget.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: '三栏布局容器',
6 | type: 'component',
7 | componentType: 'container',
8 | dependence: 'element-plus',
9 | platform: 'pc',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/packages/create/template-component-ts/src/widget.config.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import { defineConfig } from '@spearjs/cli'
3 |
4 | export default defineConfig({
5 | name: 'my-widget',
6 | type: 'component',
7 | componentType: 'basis',
8 | dependence: '',
9 | platform: 'mobile',
10 | editorFiles: path.resolve(__dirname, './src/editor.tsx'),
11 | renderFiles: path.resolve(__dirname, './src/render.tsx'),
12 | dest: 'dist',
13 | })
14 |
--------------------------------------------------------------------------------
/packages/core/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SpearJs
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "rootDir": "./src",
6 | "composite": true,
7 | "emitDeclarationOnly": true,
8 | "types": ["vite/client"],
9 | "lib": ["DOM", "ESNext"],
10 | "jsx": "preserve",
11 | "paths": {
12 | "@core/*": ["./src/*"]
13 | }
14 | },
15 | "include": ["src/**/*"],
16 | "exclude": ["node_modules", "dist"]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/editor/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SpearJs
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.mind/想法2.md:
--------------------------------------------------------------------------------
1 | 这个低代码开发平台,从整体上来看,需要有哪些部分组成?
2 |
3 | ## 应用编辑器 editor
4 |
5 | 用户可以在这个编辑器上,进行应用编辑,包括 添加页面,编辑页面,编辑数据,应用配置等。
6 |
7 | ## 应用渲染器 client
8 |
9 | 通过应用配置,进行应用渲染,可在线访问。
10 |
11 | ## 应用打包器 builder
12 |
13 | 通过应用配置,将应用打包为一个 独立的可部署的应用包。
14 |
15 | ## 管理系统 admin
16 |
17 | 提供给用户管理 应用、widget、模块、模板等
18 |
19 | ## 后台服务
20 |
21 | 用户服务、应用服务、widget 服务、打包服务、部署服务等。
22 |
23 | ## 平台使用手册
24 |
25 | 使用指南、开发规范等。
26 |
27 | ## widget 开发工具
28 |
29 | 创建 widget、更新 widget、发布 widget。
30 |
--------------------------------------------------------------------------------
/packages/editor/src/widgets/index.ts:
--------------------------------------------------------------------------------
1 | import { registerWidget } from '@spearjs/shared'
2 | import type { ComponentWidget } from '@spearjs/shared'
3 | import Button from './button'
4 | import Flex from './flex'
5 | import Gird from './gird'
6 | import VantButton from './vant/Button'
7 | import VantCell from './vant/Cell'
8 |
9 | const widgetList: ComponentWidget[] = [Button, Gird, Flex, VantButton, VantCell]
10 |
11 | widgetList.forEach((widget) => registerWidget(widget))
12 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Modules.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const ModulesIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/editor/src/components/RightController/ConfigHeader.tsx:
--------------------------------------------------------------------------------
1 | import type { AppBlock } from '@spearjs/core'
2 | import type { FunctionalComponent } from 'vue'
3 |
4 | export const BlockHeader: FunctionalComponent<{ block: AppBlock }> = ({
5 | block,
6 | }) => {
7 | return (
8 |
9 | 组件名:{block.label}
10 | 组件ID: {block.bid}
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/packages/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "strict": true,
5 | "module": "ESNext",
6 | "resolveJsonModule": true,
7 | "noEmitOnError": true,
8 | "newLine": "lf",
9 | "skipLibCheck": true,
10 | "sourceMap": false,
11 | "strictNullChecks": true,
12 | "noUnusedLocals": true,
13 | "esModuleInterop": true,
14 | "allowSyntheticDefaultImports": true,
15 | "declaration": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/widgets/demo/src/editor.ts:
--------------------------------------------------------------------------------
1 | import { defineEditorConfig } from '@spearjs/shared'
2 | import description from './description'
3 | import preview from './preview'
4 |
5 | export default defineEditorConfig({
6 | preview,
7 | description,
8 | layer: {
9 | display: 'inline-block',
10 | },
11 | props: [
12 | {
13 | type: 'text',
14 | key: 'text',
15 | label: '按钮文本',
16 | defaultValue: '按钮',
17 | },
18 | ],
19 | actions: [],
20 | slots: [],
21 | })
22 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/setupRenderer.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig } from '@core/types'
2 | import type { App } from 'vue'
3 | import type { Router } from 'vue-router'
4 | import { setupRouter } from './setupRouter'
5 | import { setupStore } from './setupStore'
6 |
7 | export const setupRenderer = async ({
8 | router,
9 | appConfig,
10 | }: {
11 | app: App
12 | router: Router
13 | appConfig: AppConfig
14 | }) => {
15 | setupStore(appConfig)
16 | setupRouter(router, appConfig)
17 | }
18 |
--------------------------------------------------------------------------------
/packages/create/template-component/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "widget-project",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "spearjs build",
7 | "dev": "spearjs dev",
8 | "publish:release": "spearjs publish"
9 | },
10 | "dependencies": {
11 | "@spearjs/shared": "^1.0.0",
12 | "vue": "^3.3.4"
13 | },
14 | "devDependencies": {
15 | "@spearjs/cli": "^1.0.0",
16 | "postcss": "^8.4.24",
17 | "sass": "^1.63.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Platform.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const PlatformIcon: FunctionalComponent = () => (
5 |
6 |
7 |
11 |
12 | )
13 |
--------------------------------------------------------------------------------
/packages/editor/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import type { App } from 'vue'
2 | import type { Router } from 'vue-router'
3 | import { createRouter, createWebHistory } from 'vue-router'
4 | import { setupGlobalGuards } from './globalGuard'
5 | import { routes } from './routes'
6 |
7 | export const router: Router = createRouter({
8 | history: createWebHistory(),
9 | routes,
10 | })
11 |
12 | export const setupRouter = (app: App) => {
13 | app.use(router as any)
14 | setupGlobalGuards(router)
15 | }
16 |
--------------------------------------------------------------------------------
/packages/create/template-component-ts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "widget-project",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "spearjs build",
7 | "dev": "spearjs dev",
8 | "publish:release": "spearjs publish"
9 | },
10 | "dependencies": {
11 | "@spearjs/shared": "^1.0.0",
12 | "vue": "^3.3.4"
13 | },
14 | "devDependencies": {
15 | "@spearjs/cli": "^1.0.0",
16 | "postcss": "^8.4.24",
17 | "sass": "^1.63.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/create/src/normalizeArgv.ts:
--------------------------------------------------------------------------------
1 | import type { ArgvOptions } from './types'
2 | import { formatTargetDir } from './utils'
3 |
4 | export const normalizeArgv = (argv: Record): ArgvOptions => {
5 | const service = (argv.s || argv.service) && 'service'
6 | const component = (argv.c || argv.component) && 'component'
7 | return {
8 | targetDir: formatTargetDir(argv._[0]),
9 | template: component || service || '',
10 | typescript: argv.t || argv.typescript,
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/editor/src/services/getPropsSelectOptions.ts:
--------------------------------------------------------------------------------
1 | import type { WidgetProps } from '@spearjs/shared'
2 |
3 | interface OptionsItem {
4 | value: string
5 | label: string
6 | }
7 |
8 | export const getPropSelectOptions = (
9 | props: WidgetProps,
10 | options: OptionsItem[] = [],
11 | ) => {
12 | props.forEach((prop) => {
13 | if (prop.type === 'group') {
14 | getPropSelectOptions(prop.props, options)
15 | } else {
16 | // todo props select options
17 | }
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/packages/create/template-component-ts/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig, useBlock } from '@spearjs/shared'
2 |
3 | export default defineRenderConfig({
4 | setup(props, { expose }) {
5 | const block = useBlock()
6 | const onClick = () => {
7 | block.action('click')
8 | }
9 | // 对外暴露 组件的 public method/props
10 | expose({ onClick })
11 |
12 | return { onClick }
13 | },
14 | render({ props }) {
15 | return {props.text}
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/packages/cli/src/userConfig/loadUserConfig.ts:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | import { hasExportDefault } from '@spearjs/utils'
3 | import type { UserConfig } from './types'
4 |
5 | const require = createRequire(process.cwd())
6 |
7 | export const loadUserConfig = async (
8 | configFile: string,
9 | ): Promise => {
10 | const required = require(configFile)
11 |
12 | const config = hasExportDefault(required) ? required.default : required
13 |
14 | return config as UserConfig
15 | }
16 |
--------------------------------------------------------------------------------
/widgets/element-plus/button/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig } from '@spearjs/shared'
2 | import { ElButton } from 'element-plus'
3 |
4 | export default defineRenderConfig({
5 | render({ props, action }) {
6 | const { buttonText, ...otherProps } = props
7 | return buttonText ? (
8 | action('click')}>
9 | {buttonText}
10 |
11 | ) : (
12 | action('click')} />
13 | )
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/packages/cli/src/userConfig/resolveDefaultConfig.ts:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | import { path } from '@spearjs/utils'
3 | import type { UserConfig } from './types'
4 |
5 | const require = createRequire(process.cwd())
6 |
7 | export const resolveDefaultConfig = (): UserConfig => {
8 | const cwd = process.cwd()
9 | const pkg = require(path.resolve(cwd, 'package.json'))
10 | return {
11 | name: pkg.name,
12 | type: 'component',
13 | componentType: 'basis',
14 | platform: 'mobile',
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/server/src/widget/dto/widget.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsNumberString, IsString } from 'class-validator'
2 |
3 | export class WidgetDto {
4 | @IsString()
5 | widgetId!: string
6 |
7 | @IsString()
8 | name!: string
9 |
10 | @IsString()
11 | type!: string
12 |
13 | @IsString()
14 | componentType!: string
15 |
16 | @IsString()
17 | componentSubType!: string
18 |
19 | @IsString()
20 | platform!: string
21 |
22 | @IsString()
23 | version!: string
24 |
25 | @IsNumberString()
26 | latest!: number
27 | }
28 |
--------------------------------------------------------------------------------
/packages/utils/src/withSpinner.ts:
--------------------------------------------------------------------------------
1 | import ora from 'ora'
2 |
3 | export const withSpinner =
4 | (msg: string) =>
5 | async (target: () => Promise): Promise => {
6 | if (process.env.DEBUG) {
7 | return target()
8 | }
9 |
10 | const spinner = ora()
11 |
12 | try {
13 | spinner.start(msg)
14 | const result = await target()
15 | spinner.succeed(`${msg} - done`)
16 | return result
17 | } catch (e) {
18 | spinner.fail(`${msg} - failed`)
19 | throw e
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Info.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const InfoIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/editor/src/plugins/element-plus.ts:
--------------------------------------------------------------------------------
1 | import 'element-plus/dist/index.css'
2 | import {
3 | Check,
4 | Delete,
5 | Edit,
6 | Message,
7 | Search,
8 | Star,
9 | } from '@element-plus/icons-vue'
10 | import ElementPlus from 'element-plus'
11 | import type { App, Plugin } from 'vue'
12 |
13 | const iconList = [Search, Delete, Edit, Check, Message, Star]
14 |
15 | export function setupElementPlus(app: App) {
16 | app.use(ElementPlus as unknown as Plugin)
17 | iconList.forEach((icon) => app.component(icon.name, icon))
18 | }
19 |
--------------------------------------------------------------------------------
/packages/create/template-component/src/render.jsx:
--------------------------------------------------------------------------------
1 | import { useBlock } from '@spearjs/shared'
2 |
3 | /**
4 | * @type {import('@spearjs/shared').RenderConfig}
5 | */
6 | export default {
7 | setup(props, { expose }) {
8 | const block = useBlock()
9 | const onClick = () => {
10 | block.action('click')
11 | }
12 | // 对外暴露 组件的 public method/props
13 | expose({ onClick })
14 |
15 | return { onClick }
16 | },
17 | render({ props }) {
18 | return {props.text}
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/packages/editor/src/router/routes.ts:
--------------------------------------------------------------------------------
1 | import Home from '@editor/components/Home'
2 | import type { RouteRecordRaw } from 'vue-router'
3 |
4 | /**
5 | * TODO 更改动态路由匹配,改用 addRoute 新增路由
6 | * 这里缺少考虑场景,通常一个应用,是支持子路由嵌套的,
7 | * 因为需要 一个应用存在 多个路由共享某些组件。
8 | * 如 PC 管理类,使用左侧菜单切换路由;移动端,使用底栏切换路由页面等。
9 | */
10 | export const routes: Array = [
11 | {
12 | path: '/:appId/:pathMath(.*)*',
13 | name: 'appPage',
14 | component: Home,
15 | },
16 | {
17 | path: '/',
18 | name: 'home',
19 | component: Home,
20 | },
21 | ]
22 |
--------------------------------------------------------------------------------
/packages/server/src/application/application.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { TypeOrmModule } from '@nestjs/typeorm'
3 | import { ApplicationEntity } from '../entities/index.js'
4 | import { ApplicationController } from './application.controller.js'
5 | import { ApplicationService } from './application.service.js'
6 |
7 | @Module({
8 | imports: [TypeOrmModule.forFeature([ApplicationEntity])],
9 | controllers: [ApplicationController],
10 | providers: [ApplicationService],
11 | })
12 | export class ApplicationModule {}
13 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Promotion.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const PromotionIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true
4 | },
5 | "references": [
6 | { "path": "./utils/tsconfig.build.json" },
7 | { "path": "./shared/tsconfig.build.json" },
8 | { "path": "./cli/tsconfig.build.json" },
9 | { "path": "./cli/tsconfig.preview.json" },
10 | { "path": "./core/tsconfig.build.json" },
11 | { "path": "./create/tsconfig.build.json" },
12 | { "path": "./editor/tsconfig.build.json" },
13 | { "path": "./server/tsconfig.build.json" }
14 | ],
15 | "files": []
16 | }
17 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/rcPath.ts:
--------------------------------------------------------------------------------
1 | import * as os from 'node:os'
2 | import { fs, path } from '@spearjs/utils'
3 |
4 | const xdgConfigPath = (file: string) => {
5 | const xdgConfigHome = process.env.XDG_CONFIG_HOME
6 | if (xdgConfigHome) {
7 | const rcDir = path.join(xdgConfigHome, 'spearjs-low-code')
8 | if (!fs.existsSync(rcDir)) {
9 | fs.ensureDirSync(rcDir, 0o700)
10 | }
11 | return path.join(rcDir, file)
12 | }
13 | }
14 |
15 | export const getRcPath = (file: string) =>
16 | xdgConfigPath(file) || path.join(os.homedir(), file)
17 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/File.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const FileIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "declaration": true,
5 | "declarationMap": false,
6 | "allowSyntheticDefaultImports": true,
7 | "experimentalDecorators": true,
8 | "lib": ["DOM", "ES2020"],
9 | "moduleResolution": "node",
10 | "newLine": "lf",
11 | "noEmitOnError": true,
12 | "noImplicitAny": false,
13 | "resolveJsonModule": true,
14 | "skipLibCheck": true,
15 | "sourceMap": false,
16 | "strict": true,
17 | "strictNullChecks": true,
18 | "target": "ES2020"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Undo.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const UndoIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/core/src/types/AppConfig.ts:
--------------------------------------------------------------------------------
1 | import type { AppPageList } from './AppPages'
2 |
3 | export type Platform = 'pc' | 'mobile'
4 |
5 | export type AppService = any
6 |
7 | export interface AppConfig {
8 | appId: string
9 | name: string
10 | platform: Platform
11 | description: string
12 | dependence: string
13 | services: AppService[]
14 | themeConfig: Record
15 | /**
16 | * 仅当platform为 pc时, layout有效,
17 | * 配置页面内容布局。指定网页内容宽度,是否居中
18 | */
19 | layout: {
20 | width: string
21 | center: boolean
22 | }
23 | pages: AppPageList
24 | }
25 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Redo.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const RedoIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/server/src/shared/globalMiddleware.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common'
2 | import { ConfigService } from '@nestjs/config'
3 | import cookieParser from 'cookie-parser'
4 |
5 | // import * as csurf from 'csurf'
6 | import helmet from 'helmet'
7 |
8 | export const useGlobalMiddleware = (app: INestApplication) => {
9 | const config: ConfigService = app.get(ConfigService)
10 | app.use(helmet())
11 |
12 | // inject csurf
13 | app.enableCors()
14 | // app.use(csurf(config.get('csurf')))
15 |
16 | app.use(cookieParser(config.get('cookieSecret')))
17 | }
18 |
--------------------------------------------------------------------------------
/packages/server/src/shared/httpCode.ts:
--------------------------------------------------------------------------------
1 | export interface HttpCode {
2 | code: number
3 | message: string
4 | }
5 |
6 | const define = (code: number, message: string): HttpCode => ({ code, message })
7 |
8 | export const httpCode: Record = {
9 | success: define(200, '请求成功'),
10 | error: define(400, '服务器错误, 请重试'),
11 | unknownError: define(401, '未知错误'),
12 | requestError: define(402, '请求不合法'),
13 | forbidden: define(403, '没有执行操作权限'),
14 | notFound: define(404, '找不到请求资源'),
15 | tokenError: define(405, '验证失败'),
16 | paramsError: define(1000, '参数错误'),
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/store/appConfig.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig } from '@core/types'
2 | import { defineStore } from 'pinia'
3 |
4 | export type AppConfigStore = Omit
5 |
6 | export const useAppConfigStore = defineStore('appConfig', {
7 | state: (): AppConfigStore => {
8 | return {
9 | appId: '',
10 | name: '',
11 | themeConfig: {},
12 | platform: 'pc',
13 | dependence: '',
14 | services: [],
15 | layout: {
16 | width: '100%',
17 | center: true,
18 | },
19 | pages: [],
20 | }
21 | },
22 | })
23 |
--------------------------------------------------------------------------------
/packages/editor/src/styles/layout.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | scroll-behavior: smooth;
3 | }
4 |
5 | html,
6 | body {
7 | color: var(--c-text);
8 | background-color: var(--c-bg);
9 | transition: var(--t-color);
10 | transition-property: background-color, color;
11 | }
12 |
13 | #app {
14 | position: relative;
15 | width: 100vw;
16 | height: 100vh;
17 | font-family: Avenir, Helvetica, Arial, sans-serif;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 |
21 | &::-webkit-scrollbar {
22 | width: 0;
23 | height: 0;
24 | opacity: 0;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/DataSource.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const DataSourceIcon: FunctionalComponent = () => (
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 |
--------------------------------------------------------------------------------
/packages/server/src/application/application.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Get, Post } from '@nestjs/common'
2 | import { ApplicationService } from './application.service.js'
3 | import { CreateApplicationDto } from './dto/index.js'
4 |
5 | @Controller('/application')
6 | export class ApplicationController {
7 | constructor(private readonly applicationService: ApplicationService) {}
8 | @Get()
9 | list() {}
10 |
11 | @Post('/add')
12 | async add(@Body() appDto: CreateApplicationDto) {
13 | const app = await this.applicationService.create(appDto)
14 | return { appId: app.appId }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/widgets/vant/cell/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-vant-cell",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "vant": "^4.5.0",
23 | "vue": "^3.3.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/editor/src/styles/vars-dark.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * editor css variables 配置 深色模式
3 | */
4 | html.dark {
5 | // color brand
6 | --c-brand: #0094c8;
7 | --c-brand-light: #007bbb;
8 |
9 | // color background
10 | --c-bg: #22272e;
11 | --c-bg-light: #2b313a;
12 | --c-bg-lighter: #262c34;
13 | --c-bg-container: rgb(38, 44, 52);
14 | --c-bg-navbar: rgba(38, 44, 52, 0.95);
15 |
16 | // color text
17 | --c-text: #adbac7;
18 | --c-text-light: #96a7b7;
19 | --c-text-lighter: #8b94a8;
20 | --c-text-lightest: #8094a8;
21 |
22 | // color border
23 | --c-border: #3e4c5a;
24 | --c-border-dark: #34404c;
25 | }
26 |
--------------------------------------------------------------------------------
/widgets/vant/button/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-vant-button",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "vant": "^4.5.0",
23 | "vue": "^3.3.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/publish/resolvePackage.ts:
--------------------------------------------------------------------------------
1 | import { fs, path } from '@spearjs/utils'
2 | import sortPackageJson from 'sort-package-json'
3 |
4 | export const resolvePackageJson = () => {
5 | const pkgPath = path.join(process.cwd(), 'package.json')
6 | if (fs.existsSync(pkgPath)) {
7 | return JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
8 | } else {
9 | return {}
10 | }
11 | }
12 |
13 | export const writePackageJson = (pkgJson: Record) => {
14 | const content = sortPackageJson(JSON.stringify(pkgJson, null, 2))
15 | fs.writeFileSync(path.join(process.cwd(), 'package.json'), content, 'utf-8')
16 | }
17 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/Tips.tsx:
--------------------------------------------------------------------------------
1 | import { ElIcon, ElTooltip } from 'element-plus'
2 | import { InfoIcon } from '../Icons'
3 |
4 | export const tips = (tips?: string) => {
5 | return tips ? (
6 |
7 | {{
8 | default: () => (
9 |
10 |
11 |
12 | ),
13 | content: () => (
14 |
15 | {tips}
16 |
17 | ),
18 | }}
19 |
20 | ) : null
21 | }
22 |
--------------------------------------------------------------------------------
/packages/server/src/shared/pipes/mustNumber.pipe.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'
2 | import { FetchException } from '../exceptions/index.js'
3 | import { httpCode } from '../httpCode.js'
4 |
5 | @Injectable()
6 | export class MustNumberPipe implements PipeTransform {
7 | transform(value: any, metadata: ArgumentMetadata) {
8 | if (metadata.type !== 'param' && metadata.type !== 'query') {
9 | return value
10 | }
11 | const val = Number(value)
12 | if (isNaN(val)) {
13 | throw new FetchException(httpCode.paramsError)
14 | }
15 | return val
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/widgets/vant/cell-group/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-vant-cell-group",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "vant": "^4.5.0",
23 | "vue": "^3.3.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/widgets/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-element-plus-button",
3 | "version": "1.0.1",
4 | "private": true,
5 | "description": "",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "sass": "^1.63.3"
23 | },
24 | "widgetId": "hcN96K0OguiSKJBO"
25 | }
26 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/BasisComponent.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const BasisComponentIcon: FunctionalComponent = () => (
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 |
--------------------------------------------------------------------------------
/packages/server/src/shared/interceptors/transformResponse.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CallHandler,
3 | ExecutionContext,
4 | Injectable,
5 | NestInterceptor,
6 | } from '@nestjs/common'
7 | import { Observable, map } from 'rxjs'
8 | import { httpCode } from '../httpCode.js'
9 |
10 | @Injectable()
11 | export class TransformResponseInterceptor implements NestInterceptor {
12 | intercept(context: ExecutionContext, next: CallHandler): Observable {
13 | return next.handle().pipe(
14 | map(async (data: any) => {
15 | return {
16 | ...httpCode.success,
17 | data,
18 | }
19 | }),
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/widgets/element-plus/button/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-element-plus-button",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "element-plus": "^2.3.6",
23 | "vue": "^3.3.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/widgets/element-plus/input/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-element-plus-input",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "element-plus": "^2.3.6",
23 | "vue": "^3.3.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/widgets/element-plus/link/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-element-plus-link",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "element-plus": "^2.3.6",
23 | "vue": "^3.3.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Close.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const CloseIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 | *.tsbuildinfo
15 |
16 | packages/server/upload
17 | packages/server/static
18 |
19 | # Editor directories and files
20 | .vscode/*
21 | !.vscode/extensions.json
22 | !.vscode/settings.json
23 | .idea
24 | .DS_Store
25 | *.suo
26 | *.ntvs*
27 | *.njsproj
28 | *.sln
29 | *.sw?
30 | *.eslintcache
31 |
32 | # Tests
33 | **/coverage
34 | **/.nyc_output
35 |
36 | # IDEs and editors
37 | .project
38 | .classpath
39 | .c9/
40 | *.launch
41 | .settings/
42 | *.sublime-workspace
43 |
--------------------------------------------------------------------------------
/widgets/element-plus/button-group/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-element-plus-button-group",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "element-plus": "^2.3.6",
23 | "vue": "^3.3.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Save.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const SaveIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Add.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const AddIcon: FunctionalComponent = () => (
5 |
6 |
14 |
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/widgets/element-plus/layout/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/widget-element-plus-tree-column-layout",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "页面布局容器-三栏布局",
6 | "keywords": [
7 | "spearjs",
8 | "low-code",
9 | "widget"
10 | ],
11 | "license": "ISC",
12 | "author": "pengzhanbo",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "spearjs build",
16 | "dev": "spearjs dev",
17 | "publish:release": "spearjs publish"
18 | },
19 | "dependencies": {
20 | "@spearjs/cli": "workspace:*",
21 | "@spearjs/shared": "workspace:*",
22 | "element-plus": "^2.3.6",
23 | "vue": "^3.3.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/create/template-component-ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "declaration": true,
5 | "declarationMap": false,
6 | "allowSyntheticDefaultImports": true,
7 | "experimentalDecorators": true,
8 | "lib": ["DOM", "ES2020"],
9 | "moduleResolution": "node",
10 | "newLine": "lf",
11 | "noEmitOnError": true,
12 | "noImplicitAny": false,
13 | "resolveJsonModule": true,
14 | "skipLibCheck": true,
15 | "sourceMap": false,
16 | "strict": true,
17 | "strictNullChecks": true,
18 | "target": "ES2018",
19 | "baseUrl": ".",
20 | "module": "esnext",
21 | "jsx": "preserve"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/editor/src/utils/dom.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取元素相对于其祖先定位元素的 计算定位信息、计算宽高
3 | * @param el 当前元素
4 | * @param root 祖先定位元素,默认为 body
5 | */
6 | export const getElOffset = (
7 | el: HTMLElement,
8 | root: HTMLElement = document.body,
9 | ) => {
10 | let { offsetLeft, offsetTop } = el
11 | const { offsetHeight, offsetWidth } = el
12 | let _el: HTMLElement | null = el
13 | // eslint-disable-next-line no-cond-assign
14 | while ((_el = _el.offsetParent as any) && _el !== root) {
15 | offsetLeft += _el.offsetLeft
16 | offsetTop += _el.offsetTop
17 | }
18 |
19 | return {
20 | offsetLeft,
21 | offsetTop,
22 | offsetHeight,
23 | offsetWidth,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/editor/src/utils/pathMath.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * pathMath 主要是为了处理 路由中,
3 | * 例如: 当使用了 /:pathMath(.*)* 做动态匹配时,
4 | * 从params.pathMath 获取值
5 | * 目的是为了获取 匹配路径
6 | * 在 params中获取到的结果为一个 数据,需要重新将其解析为一个完整的路径字符串
7 | * ['list'] -> /list ; ['list', 'subList'] -> /list/subList
8 | * 并且需要实现两者之间的相互转换
9 | */
10 | import { isArray } from '@spearjs/shared'
11 |
12 | export const parsePathMath = (pathMath: string | string[]): string => {
13 | return isArray(pathMath)
14 | ? `/${pathMath.join('/')}`
15 | : pathMath
16 | ? `/${pathMath}`
17 | : ''
18 | }
19 |
20 | export const toPathMath = (pathMath = ''): string[] => {
21 | return pathMath.trim().replace(/^\/+/, '').split('/')
22 | }
23 |
--------------------------------------------------------------------------------
/.mind/想法4.md:
--------------------------------------------------------------------------------
1 | # pages
2 |
3 | 一个常见的场景, 在一个单页应用中, 比如导航组件、菜单组件、 底栏导航组件等,
4 | 通常都是跨多个路由页面共享使用的。
5 |
6 | 在当前的 pages 实现中,如果要 完全实现 嵌套路由的功能, 需要有一个特殊的路由容器组件。
7 |
8 | 这里同时需要首先分类讨论可能的场景:
9 |
10 | 1. 在应用顶层,始终有多个路由共享的组件
11 | 2. 在应用顶层,有部分路由共享组件,另外部分路由共享另一部分组件
12 | 3. 多层嵌套的子路由
13 |
14 | ## 方案一: 页面母版
15 |
16 | 通过页面母版, 首先在母版上编辑 共享的 blocks, 并嵌入表示可编辑区域的 特殊容器。
17 | 在页面选择对应的母版后, 母版部分的内容不可编辑,仅能在 容器中添加内容。
18 | 同时,母版通过引用的方式,应用在某个页面中。
19 |
20 | 方案需要考虑的问题为, 如何 创建母版, 如何编辑母版; 多个页面间,母版如何保存和共享状态。
21 |
22 | 这与 vue-router 中 路由嵌套实现 , 在一定程度上有冲突。
23 |
24 | 故待深入考量。
25 |
26 | ## 方案二:
27 |
28 | 以 page.children 的方式来创建 子路由, 更改当前 动态路由匹配的方式,改为通过 addRoute 添加路由的方式;
29 |
30 | 当 某个主路由设置开启 子路由时,往当前 page blocks 中插入一个特殊的 子路由 容器,用于存放 blocks。
31 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Home.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const HomeIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/server/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "moduleResolution": "NodeNext",
10 | "target": "ES2020",
11 | "sourceMap": true,
12 | "outDir": "./dist",
13 | "baseUrl": "./",
14 | "incremental": true,
15 | "skipLibCheck": true,
16 | "strictNullChecks": false,
17 | "noImplicitAny": false,
18 | "strictBindCallApply": false,
19 | "forceConsistentCasingInFileNames": false,
20 | "noFallthroughCasesInSwitch": false
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/widgets/element-plus/button-group/src/editor.tsx:
--------------------------------------------------------------------------------
1 | import { defineEditorConfig } from '@spearjs/shared'
2 | import { ElButton, ElButtonGroup } from 'element-plus'
3 |
4 | export default defineEditorConfig({
5 | preview() {
6 | return (
7 |
8 | 按钮1
9 | 按钮2
10 |
11 | )
12 | },
13 | description() {
14 | return (
15 |
16 | Element-Plus 按钮组合容器,适用于PC端。
17 | 将多个按钮拖拽到容器中,形成按钮组合。
18 |
19 | )
20 | },
21 | layer: {
22 | display: 'inline-block',
23 | },
24 | slots: ['default'],
25 | props: [],
26 | })
27 |
--------------------------------------------------------------------------------
/.mind/pseudo-code/widget.js:
--------------------------------------------------------------------------------
1 | const componentWidget = {
2 | id: '',
3 | name: '',
4 | version: '',
5 | type: 'component',
6 | componentType: 'basis',
7 | componentSubType: '',
8 | props: [],
9 | styles: [],
10 | actions: [],
11 | slots: ['default'],
12 | enhance() {},
13 | setup() {},
14 | preview() {},
15 | render() {},
16 | }
17 |
18 | const serviceWidget = {
19 | id: '',
20 | name: '',
21 | version: '',
22 | type: 'service',
23 | enhance() {},
24 | services: [
25 | {
26 | name: '{name}',
27 | type: 'global',
28 | fn({ app, router }) {
29 | return () => {}
30 | },
31 | paramModel: [],
32 | returnModel: [],
33 | },
34 | ],
35 | }
36 |
--------------------------------------------------------------------------------
/packages/editor/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App'
3 | import { setupStoreCache } from './hooks'
4 | import { setupElementPlus, setupVant } from './plugins'
5 | import { router, setupRouter } from './router'
6 | import { setupStore } from './stores'
7 | import './widgets'
8 |
9 | import 'virtual:windi-devtools'
10 | import 'virtual:windi.css'
11 | import './styles/index.scss'
12 |
13 | const bootstrap = async () => {
14 | const app = createApp(App)
15 |
16 | setupElementPlus(app)
17 | setupVant(app)
18 |
19 | setupStore(app)
20 | await setupStoreCache()
21 |
22 | setupRouter(app)
23 |
24 | await router.isReady()
25 | app.mount('#app')
26 | }
27 |
28 | bootstrap()
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./packages/tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "jsx": "preserve",
6 | "paths": {
7 | "@editor/*": [
8 | "./packages/editor/src/*",
9 | "./package/editor/src/*/index.ts"
10 | ],
11 | "@core/*": ["./packages/core/src/*"],
12 | "@spearjs/core": ["./packages/core/src/core.ts"],
13 | "@spearjs/*": ["./packages/*/src/index.ts"]
14 | }
15 | },
16 | "include": [
17 | "./packages/**/src",
18 | "./package/cli/preview",
19 | "./docs",
20 | "./widgets"
21 | ],
22 | "exclude": ["**/node_modules/**", "**/dist/**"],
23 | "references": [{ "path": "./packages/tsconfig.build.json" }]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/index.ts:
--------------------------------------------------------------------------------
1 | export * from './IconBase'
2 |
3 | export * from './Redo'
4 | export * from './Undo'
5 |
6 | export * from './BasisComponent'
7 | export * from './ContainerComponent'
8 | export * from './BusinessComponent'
9 | export * from './Modules'
10 | export * from './Page'
11 | export * from './DataSource'
12 |
13 | export * from './Eye'
14 | export * from './Save'
15 | export * from './Info'
16 | export * from './Home'
17 | export * from './Promotion'
18 |
19 | export * from './Arrow'
20 | export * from './Folder'
21 | export * from './File'
22 |
23 | export * from './Template'
24 | export * from './Platform'
25 | export * from './Edit'
26 | export * from './Close'
27 | export * from './Add'
28 |
--------------------------------------------------------------------------------
/packages/cli/preview/shim.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'spearjs/widget/*' {
2 | const result: any
3 | export default result
4 | }
5 |
6 | declare module 'spearjs/widget/config' {
7 | import type {
8 | WidgetComponentType,
9 | WidgetDependence,
10 | WidgetPlatform,
11 | WidgetType,
12 | WidgetVersion,
13 | } from '@spearjs/shared'
14 | const config: {
15 | id: string
16 | version: WidgetVersion
17 | name: string
18 | platform: WidgetPlatform
19 | type: WidgetType
20 | componentType: WidgetComponentType
21 | dependence: WidgetDependence
22 | }
23 | export default config
24 | }
25 |
26 | declare module 'spearjs/widget/editor' {
27 | const editor: any
28 | export default editor
29 | }
30 |
--------------------------------------------------------------------------------
/packages/editor/src/services/componentInstanceMap.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentInternalInstance } from 'vue'
2 |
3 | const instanceMap: Map = new Map()
4 |
5 | export const addComponentInstance = (
6 | bid: string,
7 | instance: ComponentInternalInstance,
8 | ): void => {
9 | instanceMap.set(bid, instance)
10 | }
11 |
12 | export const deleteComponentInstance = (bid: string): void => {
13 | instanceMap.delete(bid)
14 | }
15 |
16 | export const hasComponentInstance = (bid: string): boolean => {
17 | return instanceMap.has(bid)
18 | }
19 |
20 | export const getComponentInstance = (
21 | bid: string,
22 | ): ComponentInternalInstance | undefined => {
23 | return instanceMap.get(bid)
24 | }
25 |
--------------------------------------------------------------------------------
/packages/editor/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { ElConfigProvider } from 'element-plus'
2 | import zhCN from 'element-plus/lib/locale/lang/zh-cn'
3 | import { HTML5Backend } from 'react-dnd-html5-backend'
4 | import { defineComponent } from 'vue'
5 | import { RouterView } from 'vue-router'
6 | import { DndProvider } from 'vue3-dnd'
7 | import { useThemeConfig } from './hooks'
8 |
9 | export default defineComponent({
10 | name: 'App',
11 | setup() {
12 | const { load } = useThemeConfig()
13 | load()
14 | return () => (
15 |
16 |
17 |
18 |
19 |
20 | )
21 | },
22 | })
23 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/components/DateProp.tsx:
--------------------------------------------------------------------------------
1 | import type { WidgetDateProp } from '@spearjs/shared'
2 | import type { PropType } from 'vue'
3 | import { defineComponent } from 'vue'
4 | import type { FormInjectKey } from '../hooks'
5 |
6 | // TODO DateProp
7 | export default defineComponent({
8 | name: 'FormidableDateProp',
9 | props: {
10 | config: {
11 | type: Object as PropType,
12 | required: true,
13 | },
14 | injectKey: {
15 | type: Symbol as PropType,
16 | required: true,
17 | },
18 | show: {
19 | type: Boolean,
20 | default: true,
21 | },
22 | },
23 | setup() {
24 | return () => date
25 | },
26 | })
27 |
--------------------------------------------------------------------------------
/packages/editor/src/components/RightController/BlockTree/index.tsx:
--------------------------------------------------------------------------------
1 | import { useAppPagesStore } from '@editor/stores'
2 | import { storeToRefs } from 'pinia'
3 | import { defineComponent } from 'vue'
4 | import Blocks from './Blocks'
5 | import styles from './index.module.scss'
6 |
7 | export default defineComponent({
8 | name: 'BlockTree',
9 | setup() {
10 | const pageStore = useAppPagesStore()
11 |
12 | const { blocks } = storeToRefs(pageStore)
13 |
14 | return () => (
15 |
16 | 组件树
17 |
18 |
19 |
20 |
21 | )
22 | },
23 | })
24 |
--------------------------------------------------------------------------------
/packages/cli/src/vite/config/resolveBasicConfig.ts:
--------------------------------------------------------------------------------
1 | import vue from '@vitejs/plugin-vue'
2 | import vueJsx from '@vitejs/plugin-vue-jsx'
3 | import type { InlineConfig } from 'vite'
4 | import type { UserConfig } from '../../userConfig'
5 | import { resolveWidgetPlugin } from '../plugins/resolveWidgetPlugin'
6 |
7 | export const resolveBasicConfig = (
8 | config: InlineConfig = {},
9 | userConfig: UserConfig,
10 | ): InlineConfig => {
11 | config.base = '/'
12 | config.plugins = [resolveWidgetPlugin(userConfig), vue(), vueJsx()]
13 | config.build = {}
14 | config.css = {
15 | modules: {
16 | localsConvention: 'camelCase',
17 | },
18 | }
19 | config.configFile = false
20 | config.server = {}
21 | return config
22 | }
23 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Edit.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const EditIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/server/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common'
2 | import { Test, TestingModule } from '@nestjs/testing'
3 | import request from 'supertest'
4 | import { AppModule } from './../src/app.module.js'
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile()
13 |
14 | app = moduleFixture.createNestApplication()
15 | await app.init()
16 | })
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!')
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/ContainerComponent.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const ContainerComponentIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 |
15 |
16 |
17 | )
18 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Template.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const TemplateIcon: FunctionalComponent = () => (
5 |
6 |
10 |
14 |
18 |
19 | )
20 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/hooks/useFormData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 在一个支持无限层级的 Form 结构中
3 | * 比较理想的方式是使用 provide/inject 来将 formData注入到各个item中
4 | * 然后使用 dotKey 的方式来进行读写
5 | */
6 | import type { InjectionKey, Ref, WritableComputedRef } from 'vue'
7 | import { inject, provide } from 'vue'
8 |
9 | export type FormData =
10 | | Ref>
11 | | WritableComputedRef>
12 |
13 | export type FormInjectKey = InjectionKey
14 |
15 | export const useFormDataProvide = (formData: FormData): FormInjectKey => {
16 | const key: FormInjectKey = Symbol('formData')
17 | provide(key, formData)
18 |
19 | return key
20 | }
21 |
22 | export const useFormData = (key: FormInjectKey): FormData =>
23 | inject(key)!
24 |
--------------------------------------------------------------------------------
/packages/editor/src/services/idGenerator.ts:
--------------------------------------------------------------------------------
1 | import { customAlphabet } from 'nanoid'
2 |
3 | const LETTER_CHAR = 'abcdefghijklmnopqrstuvwxyz'
4 | const NUMBER_CHAR = '1234567890'
5 | const HASH_CHAR = '1234567890abcdef'
6 |
7 | const letterId = customAlphabet(LETTER_CHAR, 10)
8 | const numberId = customAlphabet(NUMBER_CHAR, 8)
9 | const letterSortId = customAlphabet(LETTER_CHAR, 6)
10 | const hashId = customAlphabet(HASH_CHAR, 8)
11 |
12 | export const generateBid = () => `com_${letterId()}`
13 |
14 | export const generateWidgetName = () => {
15 | const name = letterSortId()
16 | return `Widget${name[0].toUpperCase()}${name.slice(1)}`
17 | }
18 |
19 | export const generateBlockGroupKey = () => numberId()
20 |
21 | export const generateHashId = () => hashId()
22 |
--------------------------------------------------------------------------------
/commitlint.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ignores: [(commit) => commit.includes('init')],
3 | extends: ['@commitlint/config-conventional'],
4 | rules: {
5 | 'body-leading-blank': [2, 'always'],
6 | 'footer-leading-blank': [1, 'always'],
7 | 'header-max-length': [2, 'always', 108],
8 | 'subject-empty': [2, 'never'],
9 | 'type-empty': [2, 'never'],
10 | 'subject-case': [0],
11 | 'type-enum': [
12 | 2,
13 | 'always',
14 | [
15 | 'feat',
16 | 'fix',
17 | 'perf',
18 | 'style',
19 | 'docs',
20 | 'test',
21 | 'refactor',
22 | 'build',
23 | 'ci',
24 | 'chore',
25 | 'revert',
26 | 'release',
27 | ],
28 | ],
29 | },
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpearJs
2 |
3 | 一个 低代码平台
4 |
5 | 使用 vite 构建。
6 |
7 | 前端基于 vue
8 |
9 | 后端基于 nestjs
10 |
11 | [](https://app.netlify.com/sites/spearjs/deploys)
12 |
13 | ## Usage
14 |
15 | ```sh
16 | # 项目初始化
17 | pnpm bootstrap
18 | # 启动开发
19 | pnpm dev
20 | ```
21 |
22 | ## 现阶段
23 |
24 | - [ ] 原型设计阶段
25 | - [ ] Editor 开发中
26 | - [ ] Cli 开发中
27 | - [ ] Create 开发中
28 | - [ ] Server 开发中
29 | - [ ] Admin 未开始
30 |
31 | ## 理念
32 |
33 | - 可视化操作的方式,通过各种丰富的 widget, 来组装一个 完整的应用
34 | - 尽可能的少引入复杂的概念,让组装过程符合直觉
35 | - 尽可能的减少用户学习成本、使用成本
36 | - 尽可能的减少 widget 开发者的学习成本、开发成本
37 |
38 | ## 参与
39 |
40 | 如果你对本项目感兴趣,期望能给一个 star;
41 |
42 | 如果你有兴趣参与到本项目,可以在本项目的 Discussions 中留言,写下您的意见或者建议。
43 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/WidgetPreview/index.module.scss:
--------------------------------------------------------------------------------
1 | .preview {
2 | background-color: var(--c-bg);
3 |
4 | @apply relative p-5 pb-10 flex justify-center items-center mb-5;
5 | @apply rounded-md overflow-hidden;
6 |
7 | &:last-of-type {
8 | @apply mb-0;
9 | }
10 | }
11 |
12 | .widget-label {
13 | @apply absolute bottom-0 right-0 px-2 flex items-center justify-center;
14 | @apply text-center bg-blue-500 text-white text-sm;
15 | @apply rounded-tl-md;
16 |
17 | span {
18 | @apply mr-2;
19 | }
20 | }
21 |
22 | .widget-preview {
23 | @apply relative max-w-full;
24 |
25 | &::after {
26 | @apply absolute top-0 right-0 bottom-0 left-0 cursor-move shadow-md;
27 |
28 | z-index: 2;
29 | content: '';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/editor/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "rootDir": "./",
5 | "composite": true,
6 | "target": "esnext",
7 | "useDefineForClassFields": true,
8 | "module": "esnext",
9 | "moduleResolution": "node",
10 | "strict": true,
11 | "jsx": "preserve",
12 | "sourceMap": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "esModuleInterop": true,
16 | "lib": ["esnext", "dom"],
17 | "skipLibCheck": true,
18 | "types": ["vite/client"],
19 | "paths": {
20 | "@editor/*": ["./src/*"]
21 | }
22 | },
23 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
1 | # `@spearjs/cli`
2 |
3 | `SpearJs` 低代码开发平台, `widget` 开发 cli。
4 |
5 | 提供 开发环境、打包构建,发布到 `SpearJs` 平台的服务。
6 |
7 | ## Install
8 |
9 | ```sh
10 | # npm
11 | npm install --save-dev @spearjs/cli
12 | # yarn
13 | yarn add --save-dev @spearjs/cli
14 | # pnpm
15 | pnpm add --save-dev @spearjs/cli
16 | ```
17 |
18 | ## Usage
19 |
20 | ```json
21 | {
22 | "scripts": {
23 | "dev": "spearjs dev",
24 | "build": "spearjs build",
25 | "publish": "spearjs publish"
26 | }
27 | }
28 | ```
29 |
30 | ## Configuration
31 |
32 | `widget.config.ts`
33 |
34 | ```ts
35 | import { defineConfig } from '@spearjs/cli'
36 |
37 | export default defineConfig({
38 | // ...config
39 | })
40 | ```
41 |
42 | ## Create Widget Project
43 |
44 | See `@spearjs/create-app`
45 |
--------------------------------------------------------------------------------
/widgets/element-plus/input/src/render.tsx:
--------------------------------------------------------------------------------
1 | import { defineRenderConfig } from '@spearjs/shared'
2 | import { ElInput } from 'element-plus'
3 | import { ref } from 'vue'
4 |
5 | export default defineRenderConfig({
6 | setup: (_, { expose }) => {
7 | const inputValue = ref('')
8 |
9 | expose({
10 | inputValue,
11 | })
12 |
13 | return { inputValue }
14 | },
15 | render({ props, action }) {
16 | return (
17 | action('focus')}
21 | onBlur={() => action('blur')}
22 | onChange={() => action('change')}
23 | onInput={() => action('input')}
24 | onClear={() => action('clear')}
25 | />
26 | )
27 | },
28 | })
29 |
--------------------------------------------------------------------------------
/packages/core/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createPinia } from 'pinia'
2 | import { createApp } from 'vue'
3 | import type { Router } from 'vue-router'
4 | import { createRouter, createWebHistory } from 'vue-router'
5 | import App from './App'
6 | import { appConfig } from './mock'
7 | import { setupRenderer } from './renderer'
8 |
9 | const bootstrap = async () => {
10 | const app = createApp(App)
11 |
12 | const router: Router = createRouter({
13 | history: createWebHistory(),
14 | routes: [],
15 | })
16 |
17 | const store = createPinia()
18 |
19 | app.use(store)
20 |
21 | await setupRenderer({
22 | app,
23 | router,
24 | appConfig,
25 | })
26 |
27 | app.use(router)
28 |
29 | await router.isReady()
30 | app.mount('#app')
31 | }
32 |
33 | bootstrap()
34 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/BusinessComponent.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const BusinessComponentIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/publish/http.ts:
--------------------------------------------------------------------------------
1 | import type { AxiosInstance } from 'axios'
2 | import axios from 'axios'
3 | import type * as FormData from 'form-data'
4 | import { loadCliConfig } from '../../cliConfig'
5 |
6 | export const getHttp = (target?: string) => {
7 | const config = loadCliConfig()!
8 | const http = axios.create({
9 | baseURL: target || config.repository,
10 | headers: {
11 | 'Content-Type': 'application/json;charset=utf-8',
12 | },
13 | })
14 | http.interceptors.response.use((response) => response.data)
15 | return http
16 | }
17 |
18 | export const updateWidget = async (http: AxiosInstance, formData: FormData) => {
19 | const res = await http.post('/widget/publish', formData, {
20 | headers: formData.getHeaders(),
21 | } as any)
22 | return res
23 | }
24 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Navbar/index.module.scss:
--------------------------------------------------------------------------------
1 | .site-brand {
2 | @apply flex items-center mr-8 select-none;
3 |
4 | img {
5 | width: 32px;
6 | }
7 |
8 | span {
9 | @apply ml-4 text-2xl font-bold;
10 |
11 | color: var(--c-brand);
12 | }
13 | }
14 |
15 | .spear-header {
16 | @apply flex items-center fixed z-50 top-0 left-0 w-full h-12 px-9 shadow-md;
17 |
18 | background-color: var(--c-bg-navbar);
19 |
20 | .header-item {
21 | @apply flex items-center mr-8 text-sm cursor-pointer font-bold;
22 |
23 | color: var(--c-text);
24 | transition: color var(--t-color);
25 |
26 | &:hover {
27 | color: var(--c-brand);
28 | }
29 |
30 | &.disabled {
31 | color: var(--c-text-quote);
32 |
33 | @apply cursor-not-allowed;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/create/template-component-ts/src/editor.tsx:
--------------------------------------------------------------------------------
1 | import { defineEditorConfig } from '@spearjs/shared'
2 |
3 | export default defineEditorConfig({
4 | preview() {
5 | return 预览
6 | },
7 | description() {
8 | return 组件使用说明
9 | },
10 | layer: {
11 | display: 'inline-block',
12 | },
13 | props: [
14 | {
15 | key: 'text',
16 | label: '文本',
17 | type: 'text',
18 | defaultValue: '文本',
19 | tips: '文本',
20 | placeholder: '请输入文本',
21 | },
22 | ],
23 | expose: [
24 | {
25 | type: 'method',
26 | label: '点击',
27 | name: 'onClick',
28 | global: true,
29 | },
30 | ],
31 | actions: [
32 | {
33 | label: '点击事件',
34 | action: 'click',
35 | tips: '按钮点击事件',
36 | },
37 | ],
38 | })
39 |
--------------------------------------------------------------------------------
/packages/create/template-component/src/editor.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@spearjs/shared').EditorConfigByComponent}
3 | */
4 | export default {
5 | preview() {
6 | return 预览
7 | },
8 | description() {
9 | return 组件使用说明
10 | },
11 | layer: {
12 | display: 'inline-block',
13 | },
14 | props: [
15 | {
16 | key: 'text',
17 | label: '文本',
18 | type: 'text',
19 | defaultValue: '文本',
20 | tips: '文本',
21 | placeholder: '请输入文本',
22 | },
23 | ],
24 | expose: [
25 | {
26 | type: 'method',
27 | label: '点击',
28 | name: 'onClick',
29 | global: true,
30 | },
31 | ],
32 | actions: [
33 | {
34 | label: '点击事件',
35 | action: 'click',
36 | tips: '按钮点击事件',
37 | },
38 | ],
39 | }
40 |
--------------------------------------------------------------------------------
/packages/editor/src/styles/vars.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * editor css variables 配置
3 | */
4 | :root {
5 | // brand color
6 | --c-brand: #0095d9;
7 | --c-brand-light: #2ca9e1;
8 |
9 | // background color
10 | --c-bg: #f3f4f6;
11 | --c-bg-light: #e5e7eb;
12 | --c-bg-lighter: #d1d5db;
13 | --c-bg-container: #fff;
14 | --c-bg-navbar: rgba(255, 255, 255, 0.9);
15 | --c-bg-sidebar: var(--c-bg-container);
16 | --c-bg-arrow: #ccc;
17 |
18 | // text color-scheme
19 | --c-text: #2c3e50;
20 | --c-text-accent: var(--c-brand);
21 | --c-text-light: #3a5169;
22 | --c-text-lighter: #4e6e8e;
23 | --c-text-lightest: #6a8bad;
24 | --c-text-quote: #999;
25 |
26 | // border colors
27 | --c-border: #eaecef;
28 | --c-border-dark: #dfe2e5;
29 |
30 | // transition timing
31 | --t-color: 0.3s ease;
32 | }
33 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Page.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const PageIcon: FunctionalComponent = () => (
5 |
6 |
7 |
11 |
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/packages/editor/src/hooks/useThemeConfig.ts:
--------------------------------------------------------------------------------
1 | import { useAppConfigStore } from '@editor/stores'
2 | import { useStyleTag } from '@vueuse/core'
3 | import { watch } from 'vue'
4 |
5 | export const useThemeConfig = () => {
6 | const { css, load, unload } = useStyleTag('')
7 | const appConfig = useAppConfigStore()
8 |
9 | watch(
10 | () => appConfig.themeConfig,
11 | (themeConfig) => {
12 | const ruleList: string[] = []
13 | const { CssVars } = themeConfig
14 | ruleList.push(':root {')
15 | Object.keys(CssVars).forEach((key) => {
16 | const value = CssVars[key]
17 | value && ruleList.push(` ${key}: ${value};`)
18 | })
19 | ruleList.push('}')
20 |
21 | css.value = ruleList.join('')
22 | },
23 | { immediate: true, deep: true },
24 | )
25 |
26 | return { load, unload }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/server/src/application/__test__/application.controller.spec.ts:
--------------------------------------------------------------------------------
1 | // import type { TestingModule } from '@nestjs/testing'
2 | // import { Test } from '@nestjs/testing'
3 | // import { AppController } from './app.controller'
4 | // import { AppService } from './app.service'
5 |
6 | // describe('AppController', () => {
7 | // let appController: AppController
8 |
9 | // beforeEach(async () => {
10 | // const app: TestingModule = await Test.createTestingModule({
11 | // controllers: [AppController],
12 | // providers: [AppService],
13 | // }).compile()
14 |
15 | // appController = app.get(AppController)
16 | // })
17 |
18 | // describe('root', () => {
19 | // it('should return "Hello World!"', () => {
20 | // expect(appController.getHello()).toBe('Hello World!')
21 | // })
22 | // })
23 | // })
24 |
--------------------------------------------------------------------------------
/packages/cli/src/vite/createDevApp.ts:
--------------------------------------------------------------------------------
1 | import type { InlineConfig, ViteDevServer } from 'vite'
2 | import { createServer } from 'vite'
3 | import type { DevCommandOptions } from '../commands'
4 | import type { UserConfig } from '../userConfig'
5 | import { resolveBasicConfig, resolveDevConfig } from './config'
6 |
7 | export const createDevApp = async (
8 | commandOptions: DevCommandOptions,
9 | userConfig: UserConfig,
10 | ): Promise => {
11 | const config: InlineConfig = {}
12 |
13 | resolveBasicConfig(config, userConfig)
14 | resolveDevConfig(config)
15 |
16 | // 是否打开 浏览器
17 | config.server!.open = commandOptions.open
18 |
19 | // TODO resolve userConfig
20 | // 这里暂时还没考虑好 方案
21 |
22 | const app = await createServer(config)
23 |
24 | await app.listen(commandOptions.port || 8989)
25 |
26 | app.printUrls()
27 |
28 | return app
29 | }
30 |
--------------------------------------------------------------------------------
/packages/cli/src/userConfig/resolveUserConfigPath.ts:
--------------------------------------------------------------------------------
1 | import { colors, fs, logger, path } from '@spearjs/utils'
2 |
3 | const configFiles = [
4 | 'widget.config.ts',
5 | 'widget.config.js',
6 | 'config.ts',
7 | 'config.js',
8 | ]
9 |
10 | export const resolveUserConfigPath = (config?: string): string | undefined => {
11 | const cwd = process.cwd()
12 | let configPath: string | undefined
13 | if (config) {
14 | configPath = path.resolve(cwd, config)
15 | if (fs.pathExistsSync(configPath)) {
16 | return configPath
17 | } else {
18 | throw logger.createError(
19 | `config file does not exist: ${colors.magenta(config)}`,
20 | )
21 | }
22 | }
23 | configPath = configFiles
24 | .map((filename: string) => path.resolve(cwd, filename))
25 | .find((file: string) => fs.pathExistsSync(file))
26 |
27 | return configPath
28 | }
29 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/Basis/index.tsx:
--------------------------------------------------------------------------------
1 | import type { WidgetComponentItem } from '@editor/services/widget'
2 | import { getWidgetComponentList } from '@editor/services/widget'
3 | import { defineComponent, ref } from 'vue'
4 | import WidgetPreview from '../WidgetPreview'
5 | import styles from './index.module.scss'
6 |
7 | export default defineComponent({
8 | name: 'BasisTab',
9 | setup: () => {
10 | const widgetList = ref([])
11 |
12 | const initWidgetList = async () => {
13 | widgetList.value = await getWidgetComponentList({ _type: 'basis' })
14 | }
15 | initWidgetList()
16 |
17 | return () => (
18 |
19 | {widgetList.value.map((widget) => (
20 |
21 | ))}
22 |
23 | )
24 | },
25 | })
26 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/publish/version.ts:
--------------------------------------------------------------------------------
1 | import { inc, valid } from 'semver'
2 |
3 | export const getVersionList = (version: string) => {
4 | if (valid(version)) {
5 | version = '1.0.0'
6 | }
7 |
8 | const major = inc(version, 'major')
9 | const patch = inc(version, 'patch')
10 | const minor = inc(version, 'minor')
11 | const alpha = inc(version, 'prerelease', 'Alpha')
12 | const beta = inc(version, 'prerelease', 'Beta')
13 | const rc = inc(version, 'prerelease', 'Rc')
14 |
15 | return [
16 | { name: `current: ${version}`, value: version },
17 | { name: `patch: ${patch}`, value: patch },
18 | { name: `minor: ${minor}`, value: minor },
19 | { name: `prerelease-alpha: ${alpha}`, value: alpha },
20 | { name: `prerelease-beta: ${beta}`, value: beta },
21 | { name: `prerelease-rc: ${rc}`, value: rc },
22 | { name: `major: ${major}`, value: major },
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/server/src/application/application.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common'
2 | import { InjectRepository } from '@nestjs/typeorm'
3 | import { Repository } from 'typeorm'
4 | import { ApplicationEntity } from '../entities/applications.entity.js'
5 | import { generateAppId } from '../utils/generateId.js'
6 | import { CreateApplicationDto } from './dto/index.js'
7 |
8 | @Injectable()
9 | export class ApplicationService {
10 | constructor(
11 | @InjectRepository(ApplicationEntity)
12 | private readonly applicationEntity: Repository,
13 | ) {}
14 |
15 | async create(appDto: CreateApplicationDto) {
16 | const app = new ApplicationEntity()
17 | app.appId = generateAppId()
18 | app.name = appDto.name
19 | app.config = ''
20 | app.createTime = new Date()
21 | app.updateTime = app.createTime
22 | return await this.applicationEntity.save(app)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/server/src/main.ts:
--------------------------------------------------------------------------------
1 | import { ConfigService } from '@nestjs/config'
2 | import { NestFactory } from '@nestjs/core'
3 | import { AppModule } from './app.module.js'
4 | import {
5 | useGlobalFilter,
6 | useGlobalInterceptor,
7 | useGlobalMiddleware,
8 | useGlobalPipe,
9 | } from './shared/index.js'
10 |
11 | async function bootstrap() {
12 | const app = await NestFactory.create(AppModule, {
13 | // logger: ['warn', 'error'],
14 | })
15 | const config = app.get(ConfigService)
16 |
17 | // 注入全局中间件
18 | useGlobalMiddleware(app)
19 | // 注入全局拦截器
20 | useGlobalInterceptor(app)
21 | // 注入全局管道
22 | useGlobalPipe(app)
23 | // 注入全局过滤器
24 | useGlobalFilter(app)
25 |
26 | const port = config.get('SERVER_PORT') || 3000
27 | await app.listen(port)
28 |
29 | // eslint-disable-next-line no-console
30 | console.log(`Spearjs server is running on: http://localhost:${port}/`)
31 | }
32 |
33 | bootstrap()
34 |
--------------------------------------------------------------------------------
/packages/editor/src/common/lib.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 目前阶段,可以选择支持以下 四大UI框架,也可以不使用任意一个。
3 | */
4 | export const thirdLibList = [
5 | {
6 | name: 'element-plus',
7 | platform: 'pc',
8 | assert: {
9 | js: ['//unpkg.com/element-plus'],
10 | css: ['//unpkg.com/element-plus/dist/index.css'],
11 | },
12 | },
13 | {
14 | name: 'vant',
15 | platform: 'mobile',
16 | assert: {
17 | js: ['//unpkg.com/vant'],
18 | css: ['//unpkg.com/vant/lib/index.css'],
19 | },
20 | },
21 | // ant-design-vue 有 dayjs的依赖,所以需要全局安装dayjs
22 | {
23 | name: 'ant-design-vue',
24 | platform: 'pc',
25 | assert: {
26 | js: ['//unpkg.com/ant-design-vue'],
27 | css: ['//unpkg.com/ant-design-vue/antd.min.css'],
28 | },
29 | },
30 | {
31 | name: 'naive-ui',
32 | platform: 'pc',
33 | asserts: {
34 | js: ['//unpkg.com/naive-ui/dist/index.prod.js'],
35 | },
36 | },
37 | ]
38 |
--------------------------------------------------------------------------------
/packages/server/src/widget/widget.module.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path'
2 | import { Module } from '@nestjs/common'
3 | import { MulterModule } from '@nestjs/platform-express'
4 | import { TypeOrmModule } from '@nestjs/typeorm'
5 | import { diskStorage } from 'multer'
6 | import { WidgetEntity, WidgetHistoryEntity } from '../entities/index.js'
7 | import { WidgetController } from './widget.controller.js'
8 | import { WidgetService } from './widget.service.js'
9 |
10 | @Module({
11 | imports: [
12 | TypeOrmModule.forFeature([WidgetEntity, WidgetHistoryEntity]),
13 | MulterModule.register({
14 | storage: diskStorage({
15 | destination: path.join(process.cwd(), 'upload'),
16 | filename: (req, file, cb) => {
17 | return cb(null, file.originalname)
18 | },
19 | }),
20 | }),
21 | ],
22 | controllers: [WidgetController],
23 | providers: [WidgetService],
24 | })
25 | export class WidgetModule {}
26 |
--------------------------------------------------------------------------------
/packages/cli/src/vite/createBuildApp.ts:
--------------------------------------------------------------------------------
1 | import { path } from '@spearjs/utils'
2 | import type { InlineConfig } from 'vite'
3 | import { build } from 'vite'
4 | import type { BuildCommandOptions } from '../commands'
5 | import type { UserConfig } from '../userConfig'
6 | import { resolveBasicConfig, resolveBuildConfig } from './config'
7 |
8 | export const createBuildApp = async (
9 | commandOptions: BuildCommandOptions,
10 | userConfig: UserConfig,
11 | { name, fileName, entry }: { name: string; fileName: string; entry: string },
12 | ) => {
13 | const config: InlineConfig = {}
14 |
15 | resolveBasicConfig(config, userConfig)
16 | resolveBuildConfig(config, { name, fileName, entry })
17 |
18 | config.build!.outDir = path.resolve(
19 | process.cwd(),
20 | commandOptions.dest || userConfig.dest || 'dist',
21 | name,
22 | )
23 |
24 | config.build!.assetsDir = ''
25 | config.build!.emptyOutDir = false
26 |
27 | await build(config)
28 | }
29 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Folder.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const FolderIcon: FunctionalComponent = () => (
5 |
6 |
10 |
11 | )
12 |
13 | export const FolderOpenIcon: FunctionalComponent = () => (
14 |
15 |
19 |
20 | )
21 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/PageTree/index.module.scss:
--------------------------------------------------------------------------------
1 | .page-tree {
2 | width: 240px;
3 | }
4 |
5 | .page-tree-list {
6 | @apply mt-5;
7 |
8 | li {
9 | @apply flex justify-start items-center py-2 px-2 my-2;
10 |
11 | transition: color var(--t-color), background-color var(--t-color);
12 |
13 | :global {
14 | .el-icon {
15 | @apply mr-1 cursor-pointer opacity-0;
16 |
17 | transition: opacity var(--t-color);
18 |
19 | &:last-of-type {
20 | @apply mr-0;
21 | }
22 |
23 | &.is-home {
24 | @apply opacity-100;
25 | }
26 | }
27 | }
28 |
29 | &:hover :global {
30 | .el-icon {
31 | @apply opacity-100;
32 | }
33 | }
34 |
35 | &.current {
36 | color: var(--c-brand);
37 | background-color: var(--c-bg-light);
38 | border-radius: 5px;
39 | }
40 | }
41 |
42 | .list-head {
43 | @apply font-bold border-b pb-2;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/hooks/useCurrentPage.ts:
--------------------------------------------------------------------------------
1 | import type { AppPage, AppPageList } from '@core/types'
2 | import { computed } from 'vue'
3 | import { useRoute } from 'vue-router'
4 | import { normalizePath } from '../../utils'
5 | import { useAppConfigStore } from '../store'
6 |
7 | const getCurrentPage = (
8 | pageList: AppPageList,
9 | path: string,
10 | ): AppPage | undefined => {
11 | for (const page of pageList) {
12 | if (page.path === path) {
13 | return page
14 | }
15 | if (page.children) {
16 | const current = getCurrentPage(
17 | page.children,
18 | normalizePath(path, page.path),
19 | )
20 | if (current) return current
21 | }
22 | }
23 | }
24 |
25 | export const useCurrentPage = () => {
26 | const route = useRoute()
27 | const appConfig = useAppConfigStore()
28 |
29 | const currentPage = computed(() =>
30 | getCurrentPage(appConfig.pages, route.meta.path as string),
31 | )
32 |
33 | return currentPage
34 | }
35 |
--------------------------------------------------------------------------------
/packages/shared/src/utils/is.ts:
--------------------------------------------------------------------------------
1 | const checkType = (arg: unknown): string => {
2 | return Object.prototype.toString.call(arg).slice(8, -1)
3 | }
4 |
5 | export const isArray = (val: unknown): val is any[] => Array.isArray(val)
6 |
7 | export const isFunction = (val: unknown): val is Function =>
8 | typeof val === 'function'
9 |
10 | export const isObject = (val: unknown): val is object =>
11 | val !== null && typeof val === 'object'
12 |
13 | export const isMap = (val: unknown): boolean => checkType(val) === 'Map'
14 |
15 | export const isSet = (val: unknown): boolean => checkType(val) === 'Set'
16 |
17 | export const isDate = (val: unknown): val is Date => val instanceof Date
18 |
19 | export const isEmpty = (val: unknown): boolean =>
20 | val === undefined || val === '' || val === null
21 |
22 | /**
23 | * Check if a value is plain object
24 | */
25 | export const isPlainObject = = Record>(
26 | val: unknown,
27 | ): val is T => checkType(val) === 'Object'
28 |
--------------------------------------------------------------------------------
/widgets/vant/cell-group/src/editor.tsx:
--------------------------------------------------------------------------------
1 | import { defineEditorConfig } from '@spearjs/shared'
2 | import { Cell, CellGroup } from 'vant'
3 |
4 | export default defineEditorConfig({
5 | description: () => {
6 | return vant Cell 组件
7 | },
8 | preview: () => {
9 | return (
10 |
11 | |
12 | |
13 |
14 | )
15 | },
16 | layer: {
17 | display: 'block',
18 | },
19 | slots: ['default'],
20 | props: [
21 | {
22 | type: 'text',
23 | key: 'title',
24 | defaultValue: '',
25 | label: '分组标题',
26 | },
27 | {
28 | type: 'switch',
29 | key: 'inset',
30 | defaultValue: false,
31 | label: '圆角卡片',
32 | },
33 | {
34 | type: 'switch',
35 | key: 'border',
36 | defaultValue: true,
37 | label: '显示边框',
38 | },
39 | ],
40 | })
41 |
--------------------------------------------------------------------------------
/packages/editor/src/services/appActions.ts:
--------------------------------------------------------------------------------
1 | import type { AppBlockActions } from '@spearjs/core'
2 | import type { WidgetActions } from '@spearjs/shared'
3 | import { isFunction } from '@spearjs/shared'
4 | import { getComponentInstance } from './componentInstanceMap'
5 |
6 | export const createBlockActions = (actions: WidgetActions): AppBlockActions => {
7 | const blockActions: AppBlockActions = {}
8 | actions.forEach(({ action }) => {
9 | blockActions[action] = []
10 | })
11 | return blockActions
12 | }
13 |
14 | export const emitAction = (actions: AppBlockActions, name: string) => {
15 | if (!actions[name]) return
16 | const list = actions[name]
17 | list.forEach(async ({ type, bid, name }) => {
18 | if (type === 'block') {
19 | const instance = getComponentInstance(bid!)
20 | const expose =
21 | instance && instance.exposed && instance.exposed[name]
22 | ? instance.exposed[name]
23 | : null
24 | isFunction(expose) && (await expose())
25 | }
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/packages/editor/src/components/RightController/tabs.ts:
--------------------------------------------------------------------------------
1 | import type { Component } from 'vue'
2 | import ActionConfig from './ActionsConfig'
3 | import AppConfig from './AppConfig'
4 | import AttrsConfig from './AttrsConfig'
5 | import PageConfig from './PageConfig'
6 | import StylesConfig from './StylesConfig'
7 |
8 | export interface RightControllerTabItem {
9 | key: string
10 | label: string
11 | tab: Component
12 | }
13 |
14 | export type RightControllerTabs = RightControllerTabItem[]
15 |
16 | export const tabs: RightControllerTabs = [
17 | {
18 | key: 'attrs-config',
19 | label: '属性',
20 | tab: AttrsConfig,
21 | },
22 | {
23 | key: 'styles-config',
24 | label: '样式',
25 | tab: StylesConfig,
26 | },
27 | {
28 | key: 'action-config',
29 | label: '动作',
30 | tab: ActionConfig,
31 | },
32 | {
33 | key: 'page-config',
34 | label: '页面设置',
35 | tab: PageConfig,
36 | },
37 | {
38 | key: 'app-config',
39 | label: '应用设置',
40 | tab: AppConfig,
41 | },
42 | ]
43 |
--------------------------------------------------------------------------------
/packages/utils/src/logger.ts:
--------------------------------------------------------------------------------
1 | import colors from 'picocolors'
2 |
3 | export const info = (...args: any[]): void => {
4 | // eslint-disable-next-line no-console
5 | console.log(colors.cyan('info'), ...args)
6 | }
7 |
8 | export const tip = (...args: any[]): void => {
9 | // eslint-disable-next-line no-console
10 | console.log(colors.blue('tip'), ...args)
11 | }
12 |
13 | export const success = (...args: any[]): void => {
14 | // eslint-disable-next-line no-console
15 | console.log(colors.green('success'), ...args)
16 | }
17 |
18 | export const warn = (...args: any[]): void => {
19 | console.warn(colors.yellow('warning'), ...args)
20 | }
21 |
22 | export const error = (...args: any[]): void => {
23 | console.error(colors.red('error'), ...args)
24 | }
25 |
26 | export const createError = (message?: string | undefined): Error => {
27 | error(message)
28 | return new Error(message)
29 | }
30 |
31 | export const logger = {
32 | info,
33 | tip,
34 | success,
35 | warn,
36 | error,
37 | createError,
38 | }
39 |
--------------------------------------------------------------------------------
/packages/editor/src/services/createStyles.ts:
--------------------------------------------------------------------------------
1 | import type { AppBlockStyles } from '@spearjs/core'
2 | import type { ComponentWidget } from '@spearjs/shared'
3 |
4 | export const createLayerStyles = (
5 | layer: AppBlockStyles = {},
6 | ): AppBlockStyles => {
7 | return Object.assign(
8 | {
9 | display: 'block',
10 | zIndex: 1,
11 | paddingTop: '',
12 | paddingRight: '',
13 | paddingBottom: '',
14 | paddingLeft: '',
15 | marginTop: '',
16 | marginRight: '',
17 | marginBottom: '',
18 | marginLeft: '',
19 | border: '',
20 | borderTop: '',
21 | borderRight: '',
22 | borderBottom: '',
23 | borderLeft: '',
24 | position: '',
25 | top: '',
26 | right: '',
27 | bottom: '',
28 | left: '',
29 | opacity: 1,
30 | width: '',
31 | height: '',
32 | } as AppBlockStyles,
33 | layer,
34 | )
35 | }
36 |
37 | export const createStyles = (widget: ComponentWidget) => {
38 | return createLayerStyles(widget.layer)
39 | }
40 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/dev/watchUserConfigFile.ts:
--------------------------------------------------------------------------------
1 | import { colors, logger } from '@spearjs/utils'
2 | import * as chokidar from 'chokidar'
3 | import type { FSWatcher } from 'chokidar'
4 |
5 | export const watchUserConfigFile = ({
6 | userConfigPath,
7 | userConfigDeps,
8 | restart,
9 | }: {
10 | userConfigPath: string
11 | userConfigDeps: string[]
12 | restart: () => Promise
13 | }): FSWatcher[] => {
14 | const cwd = process.cwd()
15 |
16 | const configWatcher = chokidar.watch(userConfigPath, {
17 | cwd,
18 | ignoreInitial: true,
19 | })
20 |
21 | configWatcher.on('change', (configFile) => {
22 | logger.info(`config ${colors.magenta(configFile)} is modified`)
23 | restart()
24 | })
25 |
26 | const depsWatcher = chokidar.watch(userConfigDeps, {
27 | cwd,
28 | ignoreInitial: true,
29 | })
30 |
31 | depsWatcher.on('change', (depFile) => {
32 | logger.info(`config dependency ${colors.magenta(depFile)} is modified`)
33 | restart()
34 | })
35 |
36 | return [configWatcher, depsWatcher]
37 | }
38 |
--------------------------------------------------------------------------------
/packages/create/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-spearjs",
3 | "version": "1.0.0",
4 | "description": "create spearjs widget",
5 | "keywords": [],
6 | "homepage": "https://github.com/pengzhanbo/spearjs#readme",
7 | "bugs": {
8 | "url": "https://github.com/pengzhanbo/spearjs/issues"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+git@github.com:pengzhanbo/spearjs.git",
13 | "directory": "packages/create"
14 | },
15 | "license": "ISC",
16 | "author": "pengzhanbo",
17 | "main": "dist/index.js",
18 | "bin": {
19 | "create-spearjs": "bin/index.js"
20 | },
21 | "files": [
22 | "dist",
23 | "bin",
24 | "template-*"
25 | ],
26 | "scripts": {
27 | "build": "tsc -b tsconfig.build.json",
28 | "clean": "rimraf dist *.tsbuildinfo"
29 | },
30 | "dependencies": {
31 | "@spearjs/utils": "workspace:*",
32 | "minimist": "^1.2.8"
33 | },
34 | "devDependencies": {
35 | "@types/minimist": "^1.2.2",
36 | "rimraf": "^5.0.1",
37 | "typescript": "^5.1.3"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/shared/src/utils/global.ts:
--------------------------------------------------------------------------------
1 | import type { Widget } from '../types'
2 |
3 | declare global {
4 | interface globalThis {
5 | __spearjs_low_code__: {
6 | widgetMap: Record
7 | registerWidget: (widget: Widget) => void
8 | }
9 | }
10 | }
11 |
12 | // eslint-disable-next-line @typescript-eslint/no-extra-semi
13 | ;(globalThis as any).__spearjs_low_code__ =
14 | (globalThis as any).__spearjs_low_code__ || {}
15 |
16 | const spearjs = (globalThis as any).__spearjs_low_code__
17 | spearjs.global = spearjs.global || {}
18 | spearjs.widgetMap = spearjs.widgetMap || {}
19 |
20 | export const widgetMap = spearjs.widgetMap
21 |
22 | export const registerWidget = (widget: Widget): void => {
23 | const key = `${widget.id}-${widget.version}`
24 | if (!widgetMap[key]) {
25 | widgetMap[key] = widget
26 | }
27 | }
28 |
29 | export const hasWidget = ({
30 | id,
31 | version,
32 | }: {
33 | id: string
34 | version: string
35 | }): boolean => !!widgetMap[`${id}-${version}`]
36 |
37 | spearjs.registerWidget = registerWidget
38 |
--------------------------------------------------------------------------------
/packages/server/src/entities/applications.entity.ts:
--------------------------------------------------------------------------------
1 | import { Column, Entity } from 'typeorm'
2 | import { BaseEntity } from './Base.js'
3 |
4 | @Entity({ name: 'tb_application' })
5 | export class ApplicationEntity extends BaseEntity {
6 | constructor(options?: Partial) {
7 | super()
8 | Object.assign(this, options || {})
9 | }
10 |
11 | @Column('varchar', { length: 8, comment: 'appId', name: 'app_id' })
12 | appId!: string
13 |
14 | @Column('varchar', { length: 50, comment: '应用名称' })
15 | name!: string
16 |
17 | @Column('mediumtext')
18 | description!: string
19 |
20 | @Column('varchar', { length: 25 })
21 | platform!: string
22 |
23 | @Column('varchar', { length: 50, comment: '应用依赖的库,必须是预设的依赖' })
24 | dependence!: string
25 |
26 | @Column('mediumtext')
27 | config!: string
28 | }
29 |
30 | @Entity({ name: 'tb_application_history' })
31 | export class ApplicationHistoryEntity extends ApplicationEntity {}
32 |
33 | @Entity({ name: 'tb_application_draft' })
34 | export class ApplicationDraftEntity extends ApplicationEntity {}
35 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Icons/Arrow.tsx:
--------------------------------------------------------------------------------
1 | import type { FunctionalComponent } from 'vue'
2 | import { IconBase } from './IconBase'
3 |
4 | export const ArrowDoubleLeftIcon: FunctionalComponent = () => (
5 |
6 |
10 |
14 |
15 | )
16 |
17 | export const ArrowDoubleRightIcon: FunctionalComponent = () => (
18 |
19 |
23 |
27 |
28 | )
29 |
30 | export const ArrowMiniRightIcon: FunctionalComponent = () => (
31 |
32 |
33 |
34 | )
35 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # @spearjs/core
2 |
3 | - 应用渲染器
4 | - 资源加载器
5 | - 类型
6 |
7 | ## Usage
8 |
9 | `main.ts`:
10 |
11 | ```ts
12 | import { createPinia } from 'pinia'
13 | import { createApp } from 'vue'
14 | import type { Router } from 'vue-router'
15 | import { createRouter, createWebHistory } from 'vue-router'
16 | import App from './App'
17 | import { appConfig } from './appConfig'
18 | import { setupRenderer } from '@spearjs/core'
19 |
20 | const bootstrap = async () => {
21 | const app = createApp(App)
22 |
23 | const router: Router = createRouter({
24 | history: createWebHistory(),
25 | routes: [],
26 | })
27 |
28 | const store = createPinia()
29 |
30 | app.use(store)
31 |
32 | await setupRenderer({
33 | app,
34 | router,
35 | appConfig,
36 | })
37 |
38 | app.use(router)
39 |
40 | await router.isReady()
41 | app.mount('#app')
42 | }
43 |
44 | bootstrap()
45 | ```
46 |
47 | `App.vue`:
48 |
49 | ```vue
50 |
53 |
54 |
55 |
56 | ```
57 |
--------------------------------------------------------------------------------
/.cz-config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | types: [
3 | { value: 'feat', name: 'feat: 新增功能' },
4 | { value: 'fix', name: 'fix: 修复 bug' },
5 | { value: 'docs', name: 'docs: 文档变更' },
6 | { value: 'style', name: 'style: 代码格式 (不影响功能,例如空格,分号等格式修正) ' },
7 | { value: 'refactor', name: 'refactor: 代码重构 (不包括bug修复、功能新增) ' },
8 | { value: 'perf', name: 'perf: 新增优化' },
9 | { value: 'test', name: 'test: 添加、修改测试用例' },
10 | { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改构建配置等)' },
11 | { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
12 | { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
13 | { value: 'revert', name: 'revert: 回滚 commit' },
14 | ],
15 | messages: {
16 | type: '确保本次提交遵循 Angular 规范! \n选择你要提交的类型: ',
17 | customScope: '请输入自定的scope: ',
18 | subject: '填写剪短精炼的变更描述',
19 | body: '填写更加详细的变更描述(可选)。使用 "|" 换行: \n',
20 | breaking: '列举非兼容性重大变更(可选): \n',
21 | footer: '列举所有变更的 ISSUES CLOSED (可选)。例如: #22, #35: \n',
22 | confirmCommit: '确认提交?',
23 | },
24 | allowBreakingChanges: ['feat', 'fix'],
25 | subjectLimit: 100,
26 | breaklineChar: '|',
27 | }
28 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/index.module.scss:
--------------------------------------------------------------------------------
1 | .form-wrapper {
2 | & > :last-child {
3 | @apply border-b-0 mb-0;
4 | }
5 | }
6 |
7 | .group-wrapper {
8 | @apply relative border-t border-b mt-5 py-5;
9 |
10 | .group-label {
11 | @apply absolute top-0 left-5 px-2 text-sm font-bold flex items-center cursor-pointer;
12 |
13 | background-color: var(--c-bg-container);
14 | transform: translateY(-50%);
15 |
16 | span:first-of-type {
17 | @apply mr-2;
18 | }
19 | }
20 |
21 | .group-icon {
22 | transform: rotate(0deg);
23 | @apply transition transition-transform;
24 |
25 | &.open {
26 | transform: rotate(90deg);
27 | }
28 | }
29 |
30 | & + & {
31 | @apply mt-0 border-t-0;
32 | }
33 | }
34 |
35 | .object-wrapper,
36 | .array-wrapper {
37 | @apply relative flex justify-start items-center bg-light-400 pt-4 pl-4 pr-2 mb-4 border-b;
38 |
39 | .object-title,
40 | .array-title {
41 | @apply flex justify-start items-center mb-2 text-sm font-bold;
42 | }
43 |
44 | & .object-wrapper,
45 | & .array-wrapper {
46 | @apply pt-2 pr-1;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 pengzhanbo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/cli/src/cliConfig/loadConfig.ts:
--------------------------------------------------------------------------------
1 | import { fs } from '@spearjs/utils'
2 | import { getRCFilePath } from './resolveConfigPath'
3 |
4 | export interface CliConfig {
5 | username: string
6 | token: string
7 | repository: string
8 | repositoryList: string[]
9 | }
10 | const defaultConfig = {
11 | username: '',
12 | token: '',
13 | repository: '',
14 | repositoryList: [],
15 | }
16 |
17 | let configCache: CliConfig | null = null
18 |
19 | export const loadCliConfig = () => {
20 | if (configCache) return configCache
21 | const filepath = getRCFilePath()
22 | if (fs.existsSync(filepath)) {
23 | const content = fs.readFileSync(filepath, 'utf-8') || ''
24 | configCache = JSON.parse(content) || defaultConfig
25 | } else {
26 | configCache = defaultConfig
27 | }
28 | return configCache
29 | }
30 |
31 | export const writeCliConfig = (config: Partial) => {
32 | configCache = Object.assign({}, loadCliConfig(), config)
33 | const content = JSON.stringify(configCache, null, 2)
34 | const filepath = getRCFilePath()
35 | fs.writeFileSync(filepath, content, 'utf-8')
36 | return configCache
37 | }
38 |
--------------------------------------------------------------------------------
/packages/create/README.md:
--------------------------------------------------------------------------------
1 | # create-spearjs
2 |
3 | 用于创建 spearjs 低代码平台的 widget 项目 脚手架 。
4 |
5 | ## Usage
6 |
7 | With NPM
8 |
9 | ```sh
10 | npm create spearjs
11 | ```
12 |
13 | With Yarn
14 |
15 | ```sh
16 | yarn create spearjs
17 | ```
18 |
19 | With PNPM
20 |
21 | ```sh
22 | pnpm create spearjs
23 | ```
24 |
25 | 然后跟随提示进行操作即可。
26 |
27 | 你也可以在终端中使用命令行参数指定 目录、widget 类型,是否使用 Typescript。
28 |
29 | **示例:**
30 |
31 | ```sh
32 | # npm 6.x
33 | npm create spearjs my-widget -c -t
34 | npm create spearjs my-widget -c
35 |
36 | # npm 7+
37 | npm create spearjs my-widget -- -c
38 |
39 | # yarn
40 | yarn create spearjs my-widget -c
41 |
42 | # pnpm
43 | pnpm create spearjs my-widget -c
44 | ```
45 |
46 | ## Commands
47 |
48 | ```sh
49 | npm create spearjs [targetDir] [options]
50 |
51 | ```
52 |
53 | **targetDir:**
54 |
55 | 指定 widget 目录
56 |
57 | 使用 `.` 指定为在当前目录。
58 |
59 | **options:**
60 |
61 | - `-c,--component`: 指定 widget 类型为 component
62 | - `-s,--service`: 指定 widget 类型为 service
63 | - `-t,--typescript`: 是否使用 Typescript
64 |
65 | ## 说明
66 |
67 | **强烈建议使用 typescript 来开发 widget。**
68 |
69 | 我们为 项目提供了非常丰富的类型检查,使用 typescript 进行开发能够获得更好的开发体验!
70 |
--------------------------------------------------------------------------------
/packages/cli/src/vite/config/resolveBuildConfig.ts:
--------------------------------------------------------------------------------
1 | import type { InlineConfig } from 'vite'
2 |
3 | export const resolveBuildConfig = (
4 | config: InlineConfig = {},
5 | { name, fileName, entry }: { name: string; fileName: string; entry: string },
6 | ): InlineConfig => {
7 | config.build!.target = ['es2018']
8 | config.build!.minify = 'terser'
9 | config.build!.terserOptions = {
10 | ecma: 2018,
11 | compress: {
12 | drop_console: true,
13 | },
14 | }
15 | config.build!.lib = {
16 | entry,
17 | formats: ['iife'],
18 | name,
19 | fileName,
20 | }
21 | config.build!.rollupOptions = {
22 | external: [
23 | '@spearjs/shared',
24 | 'vue',
25 | 'element-plus',
26 | 'vant',
27 | 'ant-design-vue',
28 | 'cube-ui',
29 | 'naive-ui',
30 | ],
31 | output: {
32 | globals: {
33 | '@spearjs/shared': 'SpearjsShared',
34 | 'vue': 'Vue',
35 | 'element-plus': 'ElementPlus',
36 | 'vant': 'Vant',
37 | 'ant-design-vue': 'AntDesignVue',
38 | 'naive-ui': 'NaiveUI',
39 | },
40 | },
41 | }
42 |
43 | return config
44 | }
45 |
--------------------------------------------------------------------------------
/.mind/pseudo-code/loadWidget.js:
--------------------------------------------------------------------------------
1 | const widgetList = [
2 | {
3 | id: '',
4 | name: '',
5 | version: '',
6 | url: '',
7 | },
8 | ]
9 |
10 | const cacheMap = {}
11 | function loadWidget(widget) {
12 | const key = `${widget.id}-${widget.version}`
13 | if (cacheMap[key]) {
14 | return Promise.resolve(cacheMap[key])
15 | }
16 | return new Promise((resolve, reject) => {
17 | const script = document.createElement('script')
18 | script.src = widget.url
19 | script.type = 'text/javascript'
20 | script.setAttribute('data-widget', key)
21 | script.setAttribute('data-widget-name', widget.name)
22 | document.head.appendChild(script)
23 | script.onload = function () {
24 | cacheMap[key] = window.__spearjs_low_code__.widgetMap[key]
25 | resolve(cacheMap[key])
26 | }
27 | script.onerror = function (e) {
28 | reject(e)
29 | }
30 | })
31 | }
32 |
33 | async function loadAllWidget() {
34 | const result = await Promise.all(widgetList.map(loadWidget))
35 |
36 | // widget enhance 扩展,为 widget做前置准备
37 | result.forEach(async (widget) => {
38 | if (widget.enhance) await widget.enhance({ app, router })
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/packages/editor/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue'
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
10 | declare module 'virtual:*' {
11 | const result: any
12 | export default result
13 | }
14 |
15 | declare module 'nprogress' {
16 | interface Settings {
17 | minimum: number
18 | easing: string
19 | positionUsing: string
20 | speed: number
21 | trickle: boolean
22 | trickleRate: number
23 | trickleSpeed: number
24 | showSpinner: boolean
25 | barSelector: string
26 | spinnerSelector: string
27 | parent: string
28 | template: string
29 | }
30 | const nprogress: {
31 | version: string
32 | status: null | number
33 | settings: Settings
34 | configure: (option: Partial) => this
35 | start: () => this
36 | done: (force?: boolean) => this
37 | set: (step: number) => this
38 | inc: (step: number) => this
39 | trickle: () => this
40 | }
41 | export default nprogress
42 | }
43 |
--------------------------------------------------------------------------------
/packages/editor/src/hooks/useBlock.ts:
--------------------------------------------------------------------------------
1 | import { emitAction, findBlockByBid } from '@editor/services'
2 | import { useAppPagesStore } from '@editor/stores'
3 | import type { AppBlock } from '@spearjs/core'
4 | import type { CSSProperties } from 'vue'
5 | import { getCurrentInstance, mergeProps, readonly } from 'vue'
6 |
7 | export const useBlock = (bid?: string) => {
8 | const pageStore = useAppPagesStore()
9 | if (!bid) {
10 | const instance = getCurrentInstance()!
11 | bid = instance.props.bid as string
12 | }
13 | if (!bid) {
14 | throw new Error(`Block not found.
15 | Don't use useBlock() in global services.
16 | But you can useBlock(bid) to get a block.
17 | `)
18 | }
19 | const block = findBlockByBid(pageStore.currentPage.blocks, bid) as AppBlock
20 |
21 | const setProps = (props: Record) => {
22 | block.props = mergeProps(block.props, props || {})
23 | }
24 |
25 | const setStyles = (styles: CSSProperties) =>
26 | Object.assign(block.styles, styles)
27 |
28 | const action = (name: string) => emitAction(block.actions, name)
29 |
30 | return {
31 | props: readonly(block.props),
32 | setProps,
33 | setStyles,
34 | action,
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/editor/src/services/appPages.ts:
--------------------------------------------------------------------------------
1 | import type { AppPageList } from '@editor/stores'
2 | import type { AppBlocks } from '@spearjs/core'
3 | import { findWidget } from './widget'
4 |
5 | const findBlocksDependencies = (
6 | blocks: AppBlocks,
7 | result: string[] = [],
8 | ): string[] => {
9 | for (let i = 0, l = blocks.length; i < l; i++) {
10 | const block = blocks[i]
11 | if (block.type === 'group') {
12 | findBlocksDependencies(block.blocks, result)
13 | } else {
14 | const widget = findWidget(block.widget.id, block.widget.version)
15 | widget && widget.dependence && result.push(widget.dependence)
16 | const slots = Object.keys(block.slots).map(
17 | (key: string) => block.slots[key],
18 | )
19 | if (slots.length) {
20 | for (let s = 0, sl = slots.length; s < sl; s++) {
21 | findBlocksDependencies(slots[s], result)
22 | }
23 | }
24 | }
25 | }
26 | return result
27 | }
28 |
29 | export const getDependencies = (pages: AppPageList): string[] => {
30 | const result: string[] = []
31 | pages.forEach((page) => findBlocksDependencies(page.blocks, result))
32 | return Array.from(new Set(result))
33 | }
34 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/components/index.ts:
--------------------------------------------------------------------------------
1 | import type { WidgetPropsType } from '@spearjs/shared'
2 | import type { Component } from 'vue'
3 | import ArrayProp from './ArrayProp'
4 | import BorderProp from './BorderProp'
5 | import CheckboxProp from './CheckboxProp'
6 | import ColorProp from './ColorProp'
7 | import DateProp from './DateProp'
8 | import NumberProp from './NumberProp'
9 | import ObjectProp from './ObjectProp'
10 | import RadioProp from './RadiosProp'
11 | import RichTextProp from './RichTextProp'
12 | import SelectProp from './SelectProp'
13 | import SlideProp from './SlideProp'
14 | import SwitchProp from './SwitchProp'
15 | import TextProp from './TextProp'
16 | import TextViewProp from './TextViewProp'
17 |
18 | export const components: Record<
19 | Exclude,
20 | Component
21 | > = {
22 | textView: TextViewProp,
23 | text: TextProp,
24 | number: NumberProp,
25 | select: SelectProp,
26 | switch: SwitchProp,
27 | radio: RadioProp,
28 | checkbox: CheckboxProp,
29 | date: DateProp,
30 | color: ColorProp,
31 | border: BorderProp,
32 | slider: SlideProp,
33 | richText: RichTextProp,
34 | array: ArrayProp,
35 | object: ObjectProp,
36 | }
37 |
--------------------------------------------------------------------------------
/packages/cli/src/userConfig/types.ts:
--------------------------------------------------------------------------------
1 | import type { BaseWidget, ComponentWidget } from '@spearjs/shared'
2 |
3 | export interface UserBasicConfig {
4 | /**
5 | * widget 名称
6 | */
7 | name: string
8 |
9 | /**
10 | * widget 适用的平台
11 | */
12 | platform: BaseWidget['platform']
13 |
14 | /**
15 | * 在编辑器中使用的,定义 widget的 预览、props、styles、actions 等
16 | */
17 | editorFiles?: string
18 | /**
19 | * widget 在 渲染器中正式渲染时使用,可以是一个 vue文件、或者 jsx/tsx 返回一个 vue组件或者 渲染函数
20 | */
21 | renderFiles?: string
22 |
23 | /**
24 | * widget 打包构建输出目录
25 | *
26 | * 默认: dist
27 | */
28 | dest?: string
29 | }
30 |
31 | export interface UserConfigByComponent extends UserBasicConfig {
32 | /**
33 | * widget 类型, 当前支持 component 和 service
34 | */
35 | type: 'component'
36 | /**
37 | * widget 为组件时,组件的分类, 默认: basis
38 | */
39 | componentType: ComponentWidget['componentType']
40 | /**
41 | * widget 为组件时,组件的二级分类
42 | *
43 | * 比如,vant 组件,elementPlus 组件
44 | */
45 | dependence?: ComponentWidget['dependence']
46 | }
47 |
48 | export interface UserConfigByService extends UserBasicConfig {
49 | type: 'service'
50 | }
51 |
52 | export type UserConfig = UserConfigByComponent | UserConfigByService
53 |
--------------------------------------------------------------------------------
/.mind/pseudo-code/formidable.ts:
--------------------------------------------------------------------------------
1 | export const jsonSchema = [
2 | {
3 | key: 'name',
4 | type: 'text',
5 | defaultValue: '',
6 | maxLength: 10,
7 | description: '',
8 | validator: '', // (name) => boolean || RegExp
9 | },
10 | {
11 | key: 'age',
12 | type: 'number',
13 | defaultValue: 18,
14 | min: 0,
15 | max: 100,
16 | },
17 | {
18 | key: 'sex',
19 | type: 'select',
20 | defaultValue: 1,
21 | multiple: false,
22 | options: [
23 | { label: '男', value: 1 },
24 | { label: '女', value: 0 },
25 | ],
26 | },
27 | {
28 | key: '是否已婚',
29 | type: 'switch',
30 | defaultValue: true,
31 | options: { truly: 1, falsely: 0 },
32 | },
33 | {
34 | key: '颜色',
35 | type: 'color',
36 | defaultValue: '#000',
37 | format: 'hex|rgb', // 颜色格式
38 | },
39 | {
40 | key: '日期',
41 | type: 'date',
42 | defaultValue: new Date(),
43 | },
44 | {
45 | type: 'group',
46 | label: '',
47 | props: [], // 支持进一步的分组
48 | },
49 | {
50 | key: 'obj',
51 | type: 'object',
52 | props: [],
53 | },
54 | {
55 | key: 'arr',
56 | type: 'array',
57 | items: {
58 | type: '',
59 | },
60 | },
61 | ]
62 |
--------------------------------------------------------------------------------
/packages/cli/preview/widget.tsx:
--------------------------------------------------------------------------------
1 | // import description from 'spearjs/widget/description'
2 | // import preview from 'spearjs/widget/editor'
3 | // import render from 'spearjs/widget/render'
4 | // import widgetConfig from 'spearjs/widget/config'
5 | // import type { WidgetMapItem } from '@spearjs/shared'
6 |
7 | // export default {
8 | // ...widgetConfig,
9 | // description,
10 | // preview,
11 | // ...render,
12 | // } as WidgetMapItem
13 | export default {
14 | id: 'widgetDemo',
15 | name: 'WidgetDemo',
16 | version: '1.0.0',
17 | type: 'component',
18 | componentType: 'basis',
19 | componentSubType: '',
20 | props: [
21 | {
22 | name: 'message',
23 | form: {
24 | type: 'input',
25 | defaultValue: 'demo',
26 | },
27 | },
28 | ],
29 | description: () => {
30 | return 详情描述
31 | },
32 | preview: () => {
33 | return Widget Demo Preview
34 | },
35 | enhance: () => {
36 | // do
37 | },
38 | setup: () => {
39 | return {
40 | message2: 333,
41 | }
42 | },
43 | render: ({ props }: any) => {
44 | return (
45 |
46 | Widget Demo Render message: {props.message}, message2: {props.message2}
47 |
48 | )
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/packages/editor/src/common/enum.ts:
--------------------------------------------------------------------------------
1 | export const enum WIDGET_DND_TYPE {
2 | Block = 'block',
3 | Component = 'component',
4 | }
5 |
6 | export const stylesPosition = [
7 | { label: '默认', value: '' },
8 | { label: '相对定位', value: 'relative' },
9 | { label: '绝对定位', value: 'absolute' },
10 | { label: '窗口定位', value: 'fixed' },
11 | { label: '粘性定位', value: 'sticky' },
12 | ]
13 |
14 | export const stylesDisplay = [
15 | { label: '块', value: 'block' },
16 | { label: '行内块', value: 'inline-block' },
17 | ]
18 |
19 | export const stylesUnit = [
20 | { label: 'px', value: 'px' },
21 | { label: 'rem', value: 'rem' },
22 | { label: '%', value: '%' },
23 | ]
24 |
25 | export const stylesBorerStyle = [
26 | { label: '', value: '' },
27 | { label: 'none', value: 'none' },
28 | { label: 'solid', value: 'solid' },
29 | { label: 'dashed', value: 'dashed' },
30 | { label: 'dotted', value: 'dotted' },
31 | { label: 'double', value: 'double' },
32 | { label: 'groove', value: 'groove' },
33 | { label: 'ridge', value: 'ridge' },
34 | { label: 'inset', value: 'inset' },
35 | { label: 'outset', value: 'outset' },
36 | ]
37 |
38 | export const actionTypeList = [
39 | { label: '组件', value: 'block' },
40 | { label: '全局', value: 'global' },
41 | ]
42 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Stage/PlaceHolder.tsx:
--------------------------------------------------------------------------------
1 | import { setupDropPlaceholder } from '@editor/hooks'
2 | import type { PropType } from 'vue'
3 | import { Transition, computed, defineComponent, watch } from 'vue'
4 | import styles from './index.module.scss'
5 |
6 | export default defineComponent({
7 | name: 'StagePlaceholder',
8 | props: {
9 | rootRef: {
10 | type: Object as PropType,
11 | default: document.body,
12 | },
13 | },
14 | setup: (props) => {
15 | const { rectBound, setPlaceholderRoot, showPlaceholder } =
16 | setupDropPlaceholder()
17 |
18 | watch(
19 | () => props.rootRef,
20 | (root) => setPlaceholderRoot(root),
21 | )
22 |
23 | const style = computed(() => {
24 | const { left, top, width, height } = rectBound.value
25 | return {
26 | left: `${left}px`,
27 | top: `${top}px`,
28 | width: `${width}px`,
29 | height: `${height}px`,
30 | }
31 | })
32 | return () => (
33 |
34 |
39 |
40 | )
41 | },
42 | })
43 |
--------------------------------------------------------------------------------
/packages/core/windi.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite-plugin-windicss'
2 | import colors from 'windicss/colors'
3 | import typography from 'windicss/plugin/typography'
4 |
5 | export default defineConfig({
6 | darkMode: 'class',
7 | // attributify: true,
8 | plugins: [typography()],
9 | theme: {
10 | extend: {
11 | typography: {
12 | DEFAULT: {
13 | css: {
14 | maxWidth: '65ch',
15 | color: 'inherit',
16 | a: {
17 | 'color': 'inherit',
18 | 'opacity': 0.75,
19 | 'fontWeight': '500',
20 | 'textDecoration': 'underline',
21 | '&:hover': {
22 | opacity: 1,
23 | color: colors.teal[600],
24 | },
25 | },
26 | b: { color: 'inherit' },
27 | strong: { color: 'inherit' },
28 | em: { color: 'inherit' },
29 | h1: { color: 'inherit' },
30 | h2: { color: 'inherit' },
31 | h3: { color: 'inherit' },
32 | h4: { color: 'inherit' },
33 | h5: { color: 'inherit' },
34 | h6: { color: 'inherit' },
35 | code: { color: 'inherit' },
36 | },
37 | },
38 | },
39 | },
40 | },
41 | })
42 |
--------------------------------------------------------------------------------
/packages/editor/windi.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite-plugin-windicss'
2 | import colors from 'windicss/colors'
3 | import typography from 'windicss/plugin/typography'
4 |
5 | export default defineConfig({
6 | darkMode: 'class',
7 | // attributify: true,
8 | plugins: [typography()],
9 | theme: {
10 | extend: {
11 | typography: {
12 | DEFAULT: {
13 | css: {
14 | maxWidth: '65ch',
15 | color: 'inherit',
16 | a: {
17 | 'color': 'inherit',
18 | 'opacity': 0.75,
19 | 'fontWeight': '500',
20 | 'textDecoration': 'underline',
21 | '&:hover': {
22 | opacity: 1,
23 | color: colors.teal[600],
24 | },
25 | },
26 | b: { color: 'inherit' },
27 | strong: { color: 'inherit' },
28 | em: { color: 'inherit' },
29 | h1: { color: 'inherit' },
30 | h2: { color: 'inherit' },
31 | h3: { color: 'inherit' },
32 | h4: { color: 'inherit' },
33 | h5: { color: 'inherit' },
34 | h6: { color: 'inherit' },
35 | code: { color: 'inherit' },
36 | },
37 | },
38 | },
39 | },
40 | },
41 | })
42 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/tabs.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BasisComponentIcon,
3 | BusinessComponentIcon,
4 | ContainerComponentIcon,
5 | DataSourceIcon,
6 | ModulesIcon,
7 | PageIcon,
8 | } from '../Icons'
9 | import BasisComponent from './Basis'
10 | import BusinessComponent from './Business'
11 | import ContainerComponent from './Container'
12 | import DataModel from './Data'
13 | import ModuleComponent from './Modules'
14 | import PageTree from './PageTree'
15 |
16 | export const tabs = [
17 | {
18 | label: '基础组件',
19 | key: 'basisComponents',
20 | icon: BasisComponentIcon,
21 | tab: BasisComponent,
22 | },
23 | {
24 | label: '容器组件',
25 | key: 'containerComponents',
26 | icon: ContainerComponentIcon,
27 | tab: ContainerComponent,
28 | },
29 | {
30 | label: '业务组件',
31 | key: 'businessComponents',
32 | icon: BusinessComponentIcon,
33 | tab: BusinessComponent,
34 | },
35 | {
36 | label: '模块',
37 | key: 'moduleComponents',
38 | icon: ModulesIcon,
39 | tab: ModuleComponent,
40 | },
41 | {
42 | label: '页面',
43 | key: 'pageTree',
44 | icon: PageIcon,
45 | tab: PageTree,
46 | },
47 | {
48 | label: '数据源',
49 | key: 'dataModel',
50 | icon: DataSourceIcon,
51 | tab: DataModel,
52 | },
53 | ]
54 |
--------------------------------------------------------------------------------
/packages/editor/src/components/RightController/index.module.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | @apply fixed right-0 top-14 bottom-5 z-50 -shadow-md;
3 | @apply w-1/3 rounded-tl-md rounded-bl-md;
4 |
5 | contain: layout;
6 | min-width: 320px;
7 | max-width: 450px;
8 | background-color: var(--c-bg-container);
9 | transition: transform 0.5s ease-in-out;
10 | transform: translate3d(100%, 0, 0);
11 |
12 | &.open {
13 | transform: translate3d(0, 0, 0);
14 | }
15 |
16 | .btn-arrow {
17 | @apply absolute top-1/2 left-0 cursor-pointer;
18 | @apply flex justify-center items-center;
19 | @apply rounded-tl-md rounded-bl-md -shadow-md;
20 |
21 | width: 20px;
22 | height: 80px;
23 | color: var(--c-text-lightest);
24 | background-color: var(--c-bg-container);
25 | transform: translate(-100%, -50%);
26 |
27 | &::after {
28 | position: absolute;
29 | top: 0;
30 | right: -4px;
31 | display: block;
32 | width: 4px;
33 | height: 80px;
34 | content: '';
35 | background-color: var(--c-bg-container);
36 | }
37 | }
38 | }
39 |
40 | .tabs {
41 | height: calc(100% - 240px);
42 | border: none;
43 |
44 | :global {
45 | .el-tabs__content {
46 | height: calc(100% - 40px);
47 | overflow-y: auto;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/core/src/loader/loadAssert.ts:
--------------------------------------------------------------------------------
1 | let head: HTMLHeadElement | null
2 | export const scriptLoader = (
3 | url: string,
4 | name?: string,
5 | ): [HTMLScriptElement, () => Promise] => {
6 | const script = document.createElement('script')
7 | script.src = url
8 | script.type = 'text/javascript'
9 | script.async = true
10 | if (name) {
11 | script.setAttribute('data-name', name)
12 | }
13 | if (!head) {
14 | head = document.head
15 | }
16 |
17 | const promise = () =>
18 | new Promise((resolve, reject) => {
19 | head!.appendChild(script)
20 | script.onload = () => resolve()
21 | script.onerror = (e) => reject(e)
22 | })
23 | return [script, promise]
24 | }
25 |
26 | export const styleSheetLoader = (
27 | url: string,
28 | name?: string,
29 | ): [HTMLLinkElement, () => Promise] => {
30 | const link = document.createElement('link')
31 | link.href = url
32 | link.rel = 'stylesheet'
33 | if (name) {
34 | link.setAttribute('data-name', name)
35 | }
36 | if (!head) {
37 | head = document.head
38 | }
39 |
40 | const promise = () =>
41 | new Promise((resolve, reject) => {
42 | head!.appendChild(link)
43 | link.onload = () => resolve()
44 | link.onerror = (e) => reject(e)
45 | })
46 | return [link, promise]
47 | }
48 |
--------------------------------------------------------------------------------
/packages/server/src/shared/filters/exceptions.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ArgumentsHost,
3 | Catch,
4 | ExceptionFilter,
5 | HttpStatus,
6 | LoggerService,
7 | } from '@nestjs/common'
8 | import { FetchException } from '../exceptions/index.js'
9 | import { httpCode } from '../httpCode.js'
10 |
11 | @Catch()
12 | export class GlobalExceptionFilter implements ExceptionFilter {
13 | constructor(private readonly logger: LoggerService) {}
14 |
15 | catch(exception: any, host: ArgumentsHost) {
16 | const ctx = host.switchToHttp()
17 | const response = ctx.getResponse()
18 | // csrf 验证,原路返回
19 | if (exception.code === 'EBADCSRFTOKEN') {
20 | response.status(HttpStatus.FORBIDDEN).json({
21 | code: httpCode.forbidden.code,
22 | message: 'invalid csrf token',
23 | })
24 | } else if (exception.getStatus) {
25 | // 自定义错误
26 | const httpException: FetchException = exception as FetchException
27 | const httpResponse: any = httpException.getResponse()
28 | response.status(HttpStatus.OK).json({
29 | code: httpResponse.statusCode,
30 | message: httpResponse.message,
31 | })
32 | } else {
33 | // 未知错误
34 | this.logger.warn(exception)
35 | response.status(HttpStatus.OK).json({
36 | ...httpCode.error,
37 | })
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/shared/src/utils/loadAssert.ts:
--------------------------------------------------------------------------------
1 | let head: HTMLHeadElement | null
2 | export const scriptLoader = (
3 | url: string,
4 | name?: string,
5 | ): [HTMLScriptElement, () => Promise] => {
6 | const script = document.createElement('script')
7 | script.src = url
8 | script.type = 'text/javascript'
9 | script.async = true
10 | if (name) {
11 | script.setAttribute('data-name', name)
12 | }
13 | if (!head) {
14 | head = document.head
15 | }
16 |
17 | const promise = () =>
18 | new Promise((resolve, reject) => {
19 | head!.appendChild(script)
20 | script.onload = () => resolve()
21 | script.onerror = (e) => reject(e)
22 | })
23 | return [script, promise]
24 | }
25 |
26 | export const styleSheetLoader = (
27 | url: string,
28 | name?: string,
29 | ): [HTMLLinkElement, () => Promise] => {
30 | const link = document.createElement('link')
31 | link.href = url
32 | link.rel = 'stylesheet'
33 | if (name) {
34 | link.setAttribute('data-name', name)
35 | }
36 | if (!head) {
37 | head = document.head
38 | }
39 |
40 | const promise = () =>
41 | new Promise((resolve, reject) => {
42 | head!.appendChild(link)
43 | link.onload = () => resolve()
44 | link.onerror = (e) => reject(e)
45 | })
46 | return [link, promise]
47 | }
48 |
--------------------------------------------------------------------------------
/widgets/element-plus/link/src/editor.tsx:
--------------------------------------------------------------------------------
1 | import { defineEditorConfig } from '@spearjs/shared'
2 | import { ElLink } from 'element-plus'
3 |
4 | export default defineEditorConfig({
5 | preview() {
6 | return 跳转链接
7 | },
8 | description() {
9 | return (
10 |
13 | )
14 | },
15 | layer: {
16 | display: 'inline-block',
17 | },
18 | props: [
19 | {
20 | type: 'text',
21 | key: 'text',
22 | label: '文本',
23 | defaultValue: '跳转链接',
24 | },
25 | {
26 | type: 'select',
27 | key: 'type',
28 | label: '文本类型',
29 | defaultValue: 'default',
30 | options: [
31 | { label: '默认', value: 'default' },
32 | { label: '主要', value: 'primary' },
33 | { label: '提示', value: 'info' },
34 | { label: '成功', value: 'success' },
35 | { label: '警告', value: 'warning' },
36 | { label: '危险', value: 'dander' },
37 | ],
38 | },
39 | {
40 | type: 'text',
41 | key: 'href',
42 | label: '链接地址',
43 | placeholder: 'https://example.com',
44 | defaultValue: '',
45 | },
46 | {
47 | type: 'switch',
48 | key: 'underline',
49 | label: '下划线',
50 | defaultValue: true,
51 | },
52 | ],
53 | })
54 |
--------------------------------------------------------------------------------
/packages/server/src/config/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path'
2 | import { TypeOrmModuleOptions } from '@nestjs/typeorm'
3 | import { CookieOptions } from 'express'
4 | import { getDirname } from '../utils/index.js'
5 |
6 | interface ConfigOptions {
7 | staticDir: string
8 | database: TypeOrmModuleOptions
9 | token: {
10 | name: string
11 | options: CookieOptions
12 | }
13 | cookieSecret: string
14 | csurf: {
15 | cookie: boolean
16 | ignoreMethods: string[]
17 | }
18 | }
19 |
20 | export default (): ConfigOptions => {
21 | return {
22 | staticDir: path.join(process.cwd(), 'static'),
23 | database: {
24 | type: 'mysql',
25 | entities: [
26 | path.resolve(
27 | getDirname(import.meta.url),
28 | '../entities',
29 | '*.entity.{ts,js}',
30 | ),
31 | ],
32 | synchronize: true,
33 | },
34 | cookieSecret: 'faa61cf511218fe7c693be7c2b57bb21',
35 | token: {
36 | name: 'spearjs_access_token',
37 | options: {
38 | maxAge: 7 * 24 * 60 * 60 * 1000,
39 | domain: '',
40 | path: '/',
41 | // secure: true, // if user https ,use true
42 | httpOnly: false,
43 | },
44 | },
45 | csurf: {
46 | cookie: true,
47 | ignoreMethods: ['GET', 'HEAD', 'OPTIONS'],
48 | },
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/server/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { Module } from '@nestjs/common'
3 | import { ConfigModule, ConfigService } from '@nestjs/config'
4 | import { ServeStaticModule } from '@nestjs/serve-static'
5 | import { TypeOrmModule } from '@nestjs/typeorm'
6 | import { ApplicationModule } from './application/application.module.js'
7 | import AppConfig from './config/index.js'
8 | import { WidgetModule } from './widget/widget.module.js'
9 |
10 | @Module({
11 | imports: [
12 | ConfigModule.forRoot({
13 | envFilePath: ['.env.local', '.env'],
14 | load: [AppConfig],
15 | isGlobal: true,
16 | }),
17 | ServeStaticModule.forRoot({
18 | rootPath: path.join(process.cwd(), 'static'),
19 | }),
20 | TypeOrmModule.forRootAsync({
21 | inject: [ConfigService],
22 | useFactory: (config: ConfigService) => {
23 | const database = config.get('database')
24 | return {
25 | ...database,
26 | port: config.get('MYSQL_PORT'),
27 | database: config.get('MYSQL_DATABASE'),
28 | host: config.get('MYSQL_HOST'),
29 | username: config.get('MYSQL_USERNAME'),
30 | password: config.get('MYSQL_PASSWORD'),
31 | }
32 | },
33 | }),
34 | ApplicationModule,
35 | WidgetModule,
36 | ],
37 | })
38 | export class AppModule {}
39 |
--------------------------------------------------------------------------------
/packages/server/src/widget/widget.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Body,
3 | ClassSerializerInterceptor,
4 | Controller,
5 | Get,
6 | ParseIntPipe,
7 | Post,
8 | Query,
9 | UploadedFile,
10 | UseInterceptors,
11 | } from '@nestjs/common'
12 | import { FileInterceptor } from '@nestjs/platform-express'
13 | import { UploadWidgetDto } from './dto/index.js'
14 | import { WidgetService } from './widget.service.js'
15 |
16 | @Controller('/widget')
17 | export class WidgetController {
18 | constructor(private readonly widgetService: WidgetService) {}
19 |
20 | @Post('/publish')
21 | @UseInterceptors(FileInterceptor('file'))
22 | async publish(
23 | @UploadedFile() file: Express.Multer.File,
24 | @Body() body: UploadWidgetDto,
25 | ) {
26 | const asserts = await this.widgetService.uploadFile(file, body)
27 | await this.widgetService.updateWidget(body, asserts)
28 | return {}
29 | }
30 |
31 | @Get('/list')
32 | @UseInterceptors(ClassSerializerInterceptor)
33 | async list(
34 | @Query('page', ParseIntPipe) page: number,
35 | @Query('pageSize', ParseIntPipe) pageSize: number,
36 | @Query('type') type: string,
37 | @Query('componentType') componentType: string,
38 | ) {
39 | return await this.widgetService.getWidgetList(
40 | page,
41 | pageSize,
42 | type,
43 | componentType,
44 | )
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/core/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import vue from '@vitejs/plugin-vue'
3 | import vueJsx from '@vitejs/plugin-vue-jsx'
4 | import type { UserConfigExport } from 'vite'
5 | import { defineConfig } from 'vite'
6 | import windicss from 'vite-plugin-windicss'
7 |
8 | export default defineConfig({
9 | resolve: {
10 | alias: [{ find: '@core', replacement: resolve(__dirname, './src') }],
11 | },
12 | plugins: [vue(), vueJsx(), windicss()],
13 | css: {
14 | modules: {
15 | localsConvention: 'camelCase',
16 | },
17 | },
18 | build: {
19 | cssCodeSplit: false,
20 | sourcemap: false,
21 | chunkSizeWarningLimit: 550,
22 | assetsInlineLimit: 4096,
23 | minify: 'terser',
24 | terserOptions: {
25 | compress: {
26 | drop_console: true,
27 | drop_debugger: true,
28 | },
29 | },
30 | lib: {
31 | entry: resolve('src/core.ts'),
32 | name: 'SpearjsCore',
33 | fileName: 'core',
34 | },
35 | rollupOptions: {
36 | external: ['vue', 'vue-router', 'pinia', '@spearjs/shared'],
37 | output: {
38 | globals: {
39 | 'vue': 'Vue',
40 | 'vue-router': 'VueRouter',
41 | 'pinia': 'Pinia',
42 | },
43 | },
44 | },
45 | },
46 | optimizeDeps: {
47 | include: ['lodash-es'],
48 | },
49 | }) as UserConfigExport
50 |
--------------------------------------------------------------------------------
/packages/editor/src/widgets/gird/index.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentWidget } from '@spearjs/shared'
2 | import { ElCol, ElRow } from 'element-plus'
3 |
4 | export default {
5 | label: '栅格布局',
6 | id: 'gird-container',
7 | version: '1.0.0',
8 | type: 'component',
9 | componentType: 'basis',
10 | platform: 'mobile',
11 | preview: () => {
12 | const cols = [8, 8, 8]
13 | return (
14 |
15 | {cols.map((col) => (
16 |
17 |
18 |
19 | ))}
20 |
21 | )
22 | },
23 | description: () => {
24 | return 这是一个布局容器
25 | },
26 | render: ({ props, slots }) => {
27 | return (
28 |
29 | {props.cols.map((col: number, i: number) => (
30 | {slots[`col-${i}`]?.()}
31 | ))}
32 |
33 | )
34 | },
35 | props: [
36 | {
37 | key: 'cols',
38 | label: '列',
39 | type: 'array',
40 | defaultValue: [12, 12],
41 | items: {
42 | type: 'number',
43 | label: '列',
44 | defaultValue: 1,
45 | },
46 | },
47 | ],
48 | slots: (props) => {
49 | return props.cols.map((_: number, i: number) => `col-${i}`)
50 | },
51 | } as ComponentWidget
52 |
--------------------------------------------------------------------------------
/packages/utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/utils",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [
6 | "SpearJs",
7 | "utils"
8 | ],
9 | "homepage": "https://github.com/pengzhanbo/spearjs#readme",
10 | "bugs": {
11 | "url": "https://github.com/pengzhanbo/spearjs/issues"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+git@github.com:pengzhanbo/spearjs.git",
16 | "directory": "packages/utils"
17 | },
18 | "license": "MIT",
19 | "author": "pengzhanbo",
20 | "type": "module",
21 | "exports": {
22 | ".": {
23 | "import": "./dist/index.js"
24 | }
25 | },
26 | "module": "dist/index.js",
27 | "types": "dist/index.d.ts",
28 | "files": [
29 | "dist"
30 | ],
31 | "scripts": {
32 | "build": "tsc -b tsconfig.build.json",
33 | "clean": "rimraf ./dist ./*.tsbuildinfo"
34 | },
35 | "dependencies": {
36 | "@spearjs/shared": "workspace:*",
37 | "debug": "^4.3.4",
38 | "fast-glob": "^3.2.12",
39 | "fs-extra": "^11.1.1",
40 | "inquirer": "9.2.7",
41 | "ora": "6.3.1",
42 | "picocolors": "^1.0.0",
43 | "upath": "^2.0.1"
44 | },
45 | "devDependencies": {
46 | "@types/debug": "^4.1.8",
47 | "@types/fs-extra": "^11.0.1",
48 | "@types/inquirer": "^9.0.3",
49 | "rimraf": "^5.0.1"
50 | },
51 | "publishConfig": {
52 | "access": "public"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/build/createBuild.ts:
--------------------------------------------------------------------------------
1 | import { debug, fs, logger, path } from '@spearjs/utils'
2 | import { loadUserConfig, resolveUserConfigPath } from '../../userConfig'
3 | import { createBuildApp } from '../../vite'
4 | import type { BuildCommand } from './types'
5 |
6 | export const builder: BuildCommand = async (commandOptions = {}) => {
7 | const log = debug('spearjs:cli/build')
8 | log('commandOptions:', commandOptions)
9 |
10 | if (process.env.NODE_ENV === undefined) {
11 | process.env.NODE_ENV = 'production'
12 | }
13 |
14 | const userConfigPath = resolveUserConfigPath(commandOptions.config)
15 |
16 | log('userConfigPath:', userConfigPath)
17 |
18 | const userConfig = await loadUserConfig(userConfigPath!)
19 |
20 | const dest = commandOptions.dest || userConfig.dest || 'dist'
21 | const outputDir = path.join(process.cwd(), dest)
22 |
23 | await fs.remove(outputDir)
24 |
25 | logger.info('Building SpearJs Widget...')
26 |
27 | await createBuildApp(commandOptions, userConfig, {
28 | name: 'render',
29 | fileName: 'index',
30 | entry: path.resolve(__dirname, '../../../preview/render-entry.ts'),
31 | })
32 | await createBuildApp(commandOptions, userConfig, {
33 | name: 'editor',
34 | fileName: 'index',
35 | entry: path.resolve(__dirname, '../../../preview/editor-entry.ts'),
36 | })
37 | }
38 |
39 | export const createBuild = (): BuildCommand => builder
40 |
--------------------------------------------------------------------------------
/packages/editor/src/stores/appConfig.ts:
--------------------------------------------------------------------------------
1 | import type { Platform } from '@spearjs/shared'
2 | import { defineStore } from 'pinia'
3 |
4 | /**
5 | * 一个应用,包括描述这个应用的APPID,app name,
6 | * 支持的平台,依赖的基础UI框架,依赖的UI框架决定了可以使用哪些组件,
7 | * 在这个基础上,还支持通过设置CSS变量,从细节上更细致的进行个性化配置。
8 | *
9 | * lib 实际上也是 widget的一部分
10 | */
11 | export const useAppConfigStore = defineStore('appConfig', {
12 | state: (): AppConfig => ({
13 | appId: 'test-1',
14 | name: '',
15 | platform: 'mobile',
16 | description: '描述信息',
17 | dependence: '',
18 | services: [],
19 | layout: {
20 | width: '100%',
21 | center: true,
22 | },
23 | themeConfig: {
24 | CssVars: {
25 | '--app-c-bg': '#fff',
26 | '--app-c-text': '#000',
27 | '--app-c-brand': '',
28 | },
29 | },
30 | }),
31 | actions: {
32 | updateConfig(config: Partial) {
33 | Object.assign(this, config)
34 | },
35 | },
36 | })
37 |
38 | export interface AppConfig {
39 | appId: string
40 | name: string
41 | platform: Platform
42 | description: string
43 | dependence: string
44 | services: AppService[]
45 | themeConfig: Record
46 | /**
47 | * 仅当platform为 pc时, layout有效,
48 | * 配置页面内容布局。指定网页内容宽度,是否居中
49 | */
50 | layout: {
51 | width: string
52 | center: boolean
53 | }
54 | }
55 |
56 | export interface AppService {
57 | id: string
58 | version: string
59 | label: string
60 | }
61 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/PropItem.tsx:
--------------------------------------------------------------------------------
1 | import type { WidgetPropItem } from '@spearjs/shared'
2 | import { isFunction } from '@spearjs/shared'
3 | import { computed, defineComponent, h, readonly } from 'vue'
4 | import type { PropType } from 'vue'
5 | import { components } from './components'
6 | import type { FormInjectKey } from './hooks'
7 | import { useFormData } from './hooks'
8 |
9 | export default defineComponent({
10 | name: 'FormidablePropItem',
11 | props: {
12 | config: {
13 | type: Object as PropType,
14 | required: true,
15 | },
16 | injectKey: {
17 | type: Symbol as PropType,
18 | required: true,
19 | },
20 | dotKey: {
21 | type: String,
22 | default: '',
23 | },
24 | },
25 | setup(props, { slots }) {
26 | const model = useFormData(props.injectKey)
27 | const show = computed(() => {
28 | const showProp =
29 | typeof props.config.showProp === 'undefined'
30 | ? true
31 | : props.config.showProp
32 | return isFunction(showProp) ? showProp(readonly(model.value)) : showProp
33 | })
34 |
35 | return () =>
36 | h(
37 | components[props.config.type],
38 | {
39 | config: props.config,
40 | injectKey: props.injectKey,
41 | dotKey: props.dotKey,
42 | show: show.value,
43 | },
44 | slots,
45 | )
46 | },
47 | })
48 |
--------------------------------------------------------------------------------
/packages/editor/src/components/RightController/BlockTree/Blocks.tsx:
--------------------------------------------------------------------------------
1 | import type { AppBlocks } from '@spearjs/core'
2 | import { TransitionGroup, defineComponent } from 'vue'
3 | import type { PropType } from 'vue'
4 | import Block from './Block'
5 | import BlockGroup from './BlockGroup'
6 | import styles from './index.module.scss'
7 |
8 | export default defineComponent({
9 | name: 'TreeBlocks',
10 | props: {
11 | blocks: {
12 | type: Array as PropType,
13 | required: true,
14 | },
15 | roadMap: {
16 | type: String,
17 | default: '',
18 | },
19 | },
20 | setup(props) {
21 | return () => (
22 |
23 |
24 | {props.blocks.map((block, index) => {
25 | if (block.type === 'group') {
26 | return (
27 |
33 | )
34 | } else {
35 | return (
36 |
42 | )
43 | }
44 | })}
45 |
46 |
47 | )
48 | },
49 | })
50 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/hooks/useDotProp.ts:
--------------------------------------------------------------------------------
1 | import type { WidgetPropItem } from '@spearjs/shared'
2 | import { isEmpty } from '@spearjs/shared'
3 | import type { ComputedRef } from 'vue'
4 | import { computed } from 'vue'
5 | import type { FormData } from './useFormData'
6 |
7 | function setDotProp(
8 | model: Record,
9 | dotKey: string,
10 | value?: any,
11 | ): void {
12 | const dotList = dotKey.split('.')
13 | const key = dotList.pop()!
14 | while (dotList.length) {
15 | const dot = dotList.shift()!
16 | model = model[dot]
17 | }
18 | model[key] = value
19 | }
20 | function getDotProp(model: Record, dotKey: string): any {
21 | const dotList = dotKey.split('.')
22 | while (dotList.length) {
23 | const dot = dotList.shift()!
24 | model = model[dot]
25 | }
26 | return model
27 | }
28 |
29 | export const useDotProp = (
30 | model: FormData,
31 | dotKey: ComputedRef,
32 | ) => {
33 | const binding = computed({
34 | set(data) {
35 | setDotProp(model.value, dotKey.value, data)
36 | },
37 | get() {
38 | return getDotProp(model.value, dotKey.value)
39 | },
40 | })
41 |
42 | return binding
43 | }
44 |
45 | export const useDotKey = (props: {
46 | dotKey: string
47 | config: WidgetPropItem
48 | }) => {
49 | return computed(() => {
50 | const dotKey = props.dotKey
51 | return !isEmpty(dotKey) ? `${dotKey}.${props.config.key}` : props.config.key
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import LeftSidebar from '@editor/components/LeftSidebar'
2 | import Navbar from '@editor/components/Navbar'
3 | import RightController from '@editor/components/RightController'
4 | import Stage from '@editor/components/Stage'
5 | import { useAppLayout, useContextMenu } from '@editor/hooks'
6 | import { useAppPagesStore } from '@editor/stores'
7 | import type { Ref } from 'vue'
8 | import { defineComponent, ref, withModifiers } from 'vue'
9 | import styles from './index.module.scss'
10 |
11 | export default defineComponent({
12 | name: 'Home',
13 | setup() {
14 | const pageStore = useAppPagesStore()
15 | const { close } = useContextMenu()
16 |
17 | const blurBlockHandle = () => {
18 | pageStore.setFocusBlock(null)
19 | close()
20 | }
21 |
22 | const wrapperEl: Ref = ref(null)
23 | const { containerLayout, stageLayout } = useAppLayout(wrapperEl)
24 |
25 | return () => (
26 | (wrapperEl.value = el as HTMLElement)}
28 | class={styles.editorWrapper}
29 | >
30 |
31 |
32 |
37 |
38 |
39 |
40 |
41 | )
42 | },
43 | })
44 |
--------------------------------------------------------------------------------
/packages/editor/src/components/LeftSidebar/index.tsx:
--------------------------------------------------------------------------------
1 | import { ElIcon, ElTabPane, ElTabs } from 'element-plus'
2 | import { defineComponent, h, ref } from 'vue'
3 | import { ArrowDoubleLeftIcon, ArrowDoubleRightIcon } from '../Icons'
4 | import styles from './index.module.scss'
5 | import { tabs } from './tabs'
6 |
7 | export default defineComponent({
8 | name: 'LeftSidebar',
9 | setup: () => {
10 | const activeTab = ref(tabs[0].key)
11 |
12 | const isOpen = ref(true)
13 | const handleOpen = () => {
14 | isOpen.value = !isOpen.value
15 | }
16 |
17 | return () => (
18 |
42 | )
43 | },
44 | })
45 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@spearjs/shared",
3 | "version": "1.0.0",
4 | "description": "Utils that shared between SpearJs node(cli/server) and client(editor/render/widget/admin)",
5 | "keywords": [
6 | "SpearJs",
7 | "shared",
8 | "utils"
9 | ],
10 | "homepage": "https://github.com/pengzhanbo/spearjs#readme",
11 | "bugs": {
12 | "url": "https://github.com/pengzhanbo/spearjs/issues"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+git@github.com:pengzhanbo/spearjs.git",
17 | "directory": "packages/shared"
18 | },
19 | "license": "MIT",
20 | "author": "pengzhanbo",
21 | "type": "module",
22 | "exports": {
23 | ".": {
24 | "import": "./dist/index.js",
25 | "require": "./dist/index.cjs"
26 | },
27 | "./app.css": {
28 | "import": "./dist/css/app.css"
29 | },
30 | "./package.json": "./package.json"
31 | },
32 | "main": "./dist/index.cjs",
33 | "module": "./dist/index.js",
34 | "types": "./dist/index.d.ts",
35 | "files": [
36 | "dist"
37 | ],
38 | "scripts": {
39 | "build": "tsup && pnpm copy",
40 | "clean": "rimraf ./dist",
41 | "copy": "cpx \"src/**/*.css\" dist"
42 | },
43 | "dependencies": {
44 | "element-plus": "^2.3.6",
45 | "vue": "^3.3.4",
46 | "vue-router": "^4.2.2"
47 | },
48 | "devDependencies": {
49 | "cpx2": "^4.2.3",
50 | "rimraf": "^5.0.1",
51 | "tsup": "^6.7.0"
52 | },
53 | "publishConfig": {
54 | "access": "public"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/packages/core/src/renderer/setupRouter.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig, AppPageList } from '@core/types'
2 | import NProgress from 'nprogress'
3 | import type { RouteRecordRaw, Router } from 'vue-router'
4 | import { normalizePath } from '../utils'
5 | import Page from './components/Page'
6 | import 'nprogress/nprogress.css'
7 |
8 | NProgress.configure({ showSpinner: false })
9 |
10 | const addRoutes = (
11 | router: Router,
12 | children: AppPageList,
13 | parentPath = '',
14 | parentName?: symbol | string,
15 | ) => {
16 | children.forEach((item) => {
17 | const name = Symbol(item.title)
18 | const path = parentPath
19 | ? item.path.replace(/^\//, '')
20 | : item.path.startsWith('/')
21 | ? item.path
22 | : `/${item.path}`
23 | parentPath = normalizePath(parentPath, path)
24 | const route: RouteRecordRaw = {
25 | name,
26 | path,
27 | meta: { path: parentPath || path },
28 | component: Page,
29 | }
30 | parentName ? router.addRoute(parentName, route) : router.addRoute(route)
31 | if (item.children) {
32 | addRoutes(router, item.children, parentPath, name)
33 | }
34 | })
35 | }
36 |
37 | const setupRouterGuard = (router: Router) => {
38 | router.beforeEach(() => {
39 | NProgress.start()
40 | return true
41 | })
42 | router.afterEach(() => {
43 | NProgress.done()
44 | })
45 | }
46 |
47 | export const setupRouter = (router: Router, appConfig: AppConfig) => {
48 | addRoutes(router, appConfig.pages)
49 | setupRouterGuard(router)
50 | }
51 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/components/TextViewProp.tsx:
--------------------------------------------------------------------------------
1 | import type { WidgetTextViewProp } from '@spearjs/shared'
2 | import { ElFormItem } from 'element-plus'
3 | import { defineComponent } from 'vue'
4 | import type { PropType } from 'vue'
5 | import type { FormInjectKey } from '../hooks'
6 | import { useDotKey, useDotProp, useFormData } from '../hooks'
7 | import { tips } from '../Tips'
8 |
9 | export default defineComponent({
10 | name: 'FormidableTextViewProp',
11 | props: {
12 | config: {
13 | type: Object as PropType,
14 | required: true,
15 | },
16 | injectKey: {
17 | type: Symbol as PropType,
18 | required: true,
19 | },
20 | dotKey: {
21 | type: String,
22 | default: '',
23 | },
24 | show: {
25 | type: Boolean,
26 | default: true,
27 | },
28 | },
29 | setup(props, { slots }) {
30 | const model = useFormData(props.injectKey)
31 |
32 | const dotKey = useDotKey(props)
33 |
34 | const text = useDotProp(model, dotKey)
35 |
36 | return () => (
37 |
44 |
45 | {text.value}
46 | {tips(props.config.tips)}
47 | {slots.default?.()}
48 |
49 |
50 | )
51 | },
52 | })
53 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Formidable/components/ObjectProp.tsx:
--------------------------------------------------------------------------------
1 | import type { WidgetObjectProp } from '@spearjs/shared'
2 | import { defineComponent } from 'vue'
3 | import type { PropType } from 'vue'
4 | import type { FormInjectKey } from '../hooks'
5 | import { useDotKey } from '../hooks'
6 | import styles from '../index.module.scss'
7 | import PropItem from '../PropItem'
8 | import { tips } from '../Tips'
9 |
10 | export default defineComponent({
11 | name: 'FormidableObjectProp',
12 | props: {
13 | config: {
14 | type: Object as PropType,
15 | required: true,
16 | },
17 | injectKey: {
18 | type: Symbol as PropType,
19 | required: true,
20 | },
21 | dotKey: {
22 | type: String,
23 | default: '',
24 | },
25 | show: {
26 | type: Boolean,
27 | default: true,
28 | },
29 | },
30 | setup(props, { slots }) {
31 | const dotKey = useDotKey(props)
32 | return () => (
33 |
34 |
35 |
36 | {props.config.label}
37 | {tips(props.config.tips)}
38 |
39 | {props.config.props.map((prop) => (
40 |
45 | ))}
46 |
47 | {slots.default?.()}
48 |
49 | )
50 | },
51 | })
52 |
--------------------------------------------------------------------------------
/packages/editor/src/components/Stage/Blocks.tsx:
--------------------------------------------------------------------------------
1 | import type { AppBlock, AppBlockGroup, AppBlocks } from '@spearjs/core'
2 | import { TransitionGroup, defineComponent } from 'vue'
3 | import type { PropType } from 'vue'
4 | import Block from './Block'
5 | import BlockGroup from './BlockGroup'
6 |
7 | export default defineComponent({
8 | name: 'AppBlocks',
9 | props: {
10 | blocks: {
11 | type: Array as PropType,
12 | required: true,
13 | },
14 | preview: {
15 | type: Boolean,
16 | default: false,
17 | },
18 | roadMap: {
19 | type: String,
20 | default: '',
21 | },
22 | },
23 | setup(props) {
24 | return () => (
25 |
26 | {props.blocks.map((block, index) => {
27 | if (block && (block as AppBlockGroup).blocks) {
28 | return (
29 |
36 | )
37 | } else {
38 | return (
39 |
46 | )
47 | }
48 | })}
49 |
50 | )
51 | },
52 | })
53 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.insertSpaces": true,
3 | "editor.tabSize": 2,
4 | "files.encoding": "utf8",
5 | "files.eol": "\n",
6 | "files.trimFinalNewlines": true,
7 | "files.trimTrailingWhitespace": true,
8 | "npm.packageManager": "pnpm",
9 | "typescript.tsdk": "node_modules/typescript/lib",
10 | "eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"],
11 | "stylelint.validate": ["css", "less", "scss", "postcss", "vue"],
12 | "scss.validate": false,
13 | "css.validate": false,
14 | "cSpell.words": [
15 | "attributify",
16 | "autosize",
17 | "bumpp",
18 | "Cascader",
19 | "commitlint",
20 | "datetime",
21 | "esbuild",
22 | "iife",
23 | "mediumtext",
24 | "nprogress",
25 | "pinia",
26 | "pnpm",
27 | "preinstall",
28 | "spearjs",
29 | "spearjslowcoderc",
30 | "tsbuildinfo",
31 | "tsup",
32 | "unplugin",
33 | "vant",
34 | "vitejs",
35 | "windi",
36 | "windicss"
37 | ],
38 | "search.exclude": {
39 | "**/node_modules": true,
40 | "**/bower_components": true,
41 | "**/*.code-search": true,
42 | "**/*.log": true,
43 | "**/*.log*": true,
44 | "**/dist": true,
45 | "**/.git": true,
46 | "**/.gitignore": true,
47 | "**/.DS_Store": true,
48 | "**/.pnpm-lock.yaml": true,
49 | "**/CHANGELOG.md": true
50 | },
51 | "todo-tree.highlights.enabled": true,
52 | "[markdown]": {
53 | "files.trimTrailingWhitespace": false
54 | },
55 | "editor.defaultFormatter": "esbenp.prettier-vscode"
56 | }
57 |
--------------------------------------------------------------------------------
/packages/shared/src/types/defineConfig.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentPublicInstance, RenderFunction, SetupContext } from 'vue'
2 | import type {
3 | ComponentWidget,
4 | ServiceWidget,
5 | WidgetActions,
6 | WidgetComponentLayer,
7 | WidgetExposeList,
8 | WidgetSlots,
9 | } from './widget'
10 | import type { WidgetProps } from './widgetProps'
11 |
12 | export interface EditorConfigByComponent {
13 | preview: () => ReturnType
14 | description: string | ComponentWidget['description']
15 | props?: WidgetProps
16 | slots?: ((props: Record) => string[]) | string[]
17 | actions?: WidgetActions
18 | layer?: WidgetComponentLayer
19 | expose?: WidgetExposeList
20 | }
21 | export interface EditorConfigByService {
22 | description: string | (() => ReturnType)
23 | }
24 |
25 | export type EditorConfig = EditorConfigByComponent | EditorConfigByService
26 |
27 | export interface RenderConfigByComponent {
28 | setup?: (
29 | props: Readonly,
30 | ctx: SetupContext & { bid: string },
31 | ) => RawBindings
32 | render: (
33 | this: RawBindings & ComponentPublicInstance,
34 | options: {
35 | props: Readonly
36 | slots: WidgetSlots
37 | action: (name: string) => void
38 | },
39 | ) => ReturnType
40 | }
41 |
42 | export interface RenderConfigByService {
43 | enhance?: ServiceWidget['enhance']
44 | }
45 |
46 | export type RenderConfig =
47 | | RenderConfigByComponent
48 | | RenderConfigByService
49 |
--------------------------------------------------------------------------------
/packages/editor/src/services/widget.ts:
--------------------------------------------------------------------------------
1 | import { widgetMap } from '@spearjs/shared'
2 | import type { ComponentWidget } from '@spearjs/shared'
3 |
4 | export interface WidgetComponentItem {
5 | id: string
6 | label: string
7 | version: string
8 | type: 'component'
9 | componentType: string
10 | url: string
11 | }
12 |
13 | export const getWidgetComponentList = async ({
14 | _type = 'basis',
15 | }): Promise => {
16 | return [
17 | {
18 | id: 'button',
19 | label: 'button',
20 | version: '1.0.0',
21 | type: 'component',
22 | componentType: 'basis',
23 | url: '',
24 | },
25 | {
26 | id: 'gird-container',
27 | version: '1.0.0',
28 | label: '栅格布局',
29 | type: 'component',
30 | componentType: 'basis',
31 | url: '',
32 | },
33 | {
34 | id: 'widget-flex',
35 | version: '1.0.0',
36 | label: 'flex布局',
37 | type: 'component',
38 | componentType: 'basis',
39 | url: '',
40 | },
41 | {
42 | id: 'vant-button',
43 | label: 'vant-button',
44 | version: '1.0.0',
45 | type: 'component',
46 | componentType: 'basis',
47 | url: '',
48 | },
49 | {
50 | id: 'vant-cell',
51 | label: 'vant-cell',
52 | version: '1.0.0',
53 | type: 'component',
54 | componentType: 'basis',
55 | url: '',
56 | },
57 | ]
58 | }
59 |
60 | export const findWidget = (id: string, version: string): ComponentWidget => {
61 | const key = `${id}-${version}`
62 | return widgetMap[key]
63 | }
64 |
--------------------------------------------------------------------------------
|