├── 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 | ![](https://assets.processon.com/chart_image/6288a53f6376893bcc0d767f.png) 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 | 7 | {slots.default?.()} 8 | 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 | [![Netlify Status](https://api.netlify.com/api/v1/badges/e9844840-9795-40f7-bced-d0e9cc78d2c0/deploy-status)](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 | 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 |
11 |

文字链接,跳转到特定的URL

12 |
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 |
19 |
20 | {h(isOpen.value ? ArrowDoubleLeftIcon : ArrowDoubleRightIcon)} 21 |
22 | 27 | {tabs.map((tab) => ( 28 | 29 | {{ 30 | default: () => h(tab.tab), 31 | label: () => ( 32 | 36 | ), 37 | }} 38 | 39 | ))} 40 | 41 |
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 | --------------------------------------------------------------------------------