├── docs ├── demos.md ├── guide │ ├── effect-picture.gif │ ├── why.md │ ├── index.md │ ├── data-save-and-use.md │ └── custom-logic.md ├── advanced │ ├── logic-run-principle.md │ ├── panel-load-principle.md │ ├── component-picker-principle.md │ ├── logic-run-principle copy.md │ ├── panel-load-principle copy.md │ ├── component-picker-principle copy.md │ └── component-render-principle.md ├── .vuepress │ ├── hooks │ │ └── useRoutes.ts │ ├── theme │ │ ├── layouts │ │ │ └── 404.vue │ │ └── index.ts │ ├── components │ │ ├── demos │ │ │ ├── UseFoo.vue │ │ │ ├── UseComponentRenderFoo.vue │ │ │ ├── Foo.vue │ │ │ ├── UseComponentRenderFooWithDynamicName.vue │ │ │ ├── UseComponentRenderRenderFoo.vue │ │ │ ├── UseComponentRenderRenderFooWithRecursion.vue │ │ │ ├── ComponentRender.vue │ │ │ └── ComponentRendeWithRecursion.vue │ │ ├── DemoLink.vue │ │ ├── DemoTOC.vue │ │ ├── PkgDepList.vue │ │ └── IframeDemo.vue │ ├── utils │ │ ├── addDemosRouteToRoutes.ts │ │ └── addDemosRouteToApp.ts │ ├── public │ │ └── logo.svg │ └── clientAppEnhance.ts ├── thanks-list.md ├── index.md └── api │ ├── built-in.md │ └── hooks.md ├── demos ├── test-pkg │ ├── logic │ │ ├── index.ts │ │ ├── fetch │ │ │ ├── index.ts │ │ │ └── fetch.ts │ │ ├── fetch-with-params │ │ │ ├── index.ts │ │ │ └── fetch.ts │ │ ├── alert │ │ │ └── index.ts │ │ ├── alert-with-receive-params │ │ │ └── index.ts │ │ ├── alert-with-props │ │ │ └── index.ts │ │ ├── alert-with-receive-extra-params │ │ │ └── index.ts │ │ └── fetch-with-props │ │ │ ├── index.ts │ │ │ └── fetch.ts │ ├── components │ │ ├── button │ │ │ ├── Button.vue │ │ │ └── index.ts │ │ ├── button-with-dom-events │ │ │ ├── Button.vue │ │ │ └── index.ts │ │ ├── button-with-slots │ │ │ ├── Button.vue │ │ │ └── index.ts │ │ ├── button-with-props │ │ │ ├── Button.vue │ │ │ └── index.ts │ │ ├── button-fragment-with-dom-events │ │ │ ├── Button.vue │ │ │ └── index.ts │ │ ├── button-with-color-props │ │ │ ├── index.ts │ │ │ └── Button.vue │ │ ├── button-with-custom-events │ │ │ ├── index.ts │ │ │ └── Button.vue │ │ ├── button-with-multi-slots │ │ │ ├── index.ts │ │ │ └── Button.vue │ │ ├── button-with-send-extra-param-to-event │ │ │ ├── index.ts │ │ │ └── Button.vue │ │ ├── emoji │ │ │ └── index.ts │ │ ├── table-with-fetch │ │ │ ├── index.ts │ │ │ └── Table.vue │ │ ├── table-with-fetch-params │ │ │ ├── index.ts │ │ │ └── Table.vue │ │ └── layout │ │ │ ├── index.ts │ │ │ └── Layout.vue │ └── panels │ │ ├── color-panel │ │ ├── index.ts │ │ └── ColorPanel.vue │ │ ├── resource-panel │ │ ├── index.ts │ │ └── ResourcePanel.vue │ │ ├── logic-dragger-panel │ │ ├── index.ts │ │ └── LogicDraggerPanel.vue │ │ ├── component-dragger-panel │ │ ├── index.ts │ │ └── ComponentDraggerPanel.vue │ │ ├── simple-panel │ │ └── index.ts │ │ └── toggle-panel │ │ ├── index.ts │ │ └── TogglePanel.vue ├── pages │ ├── get-started.vue │ ├── custom-panel-with-toggle.vue │ ├── custom-component.vue │ ├── custom-panel.vue │ ├── custom-component-with-props.vue │ ├── custom-panel-with-color-input.vue │ ├── custom-logic.vue │ ├── custom-layout-component.vue │ ├── custom-component-with-slots.vue │ ├── custom-logic-with-props.vue │ ├── custom-logic-with-params.vue │ ├── custom-component-with-dom-events.vue │ ├── custom-component-with-multi-slots.vue │ ├── custom-logic-with-events-props.vue │ ├── custom-component-with-custom-events.vue │ ├── custom-panel-with-resource-panel.vue │ ├── custom-fragment-component-with-dom-event.vue │ ├── custom-component-with-event-receive-param.vue │ ├── custom-component-with-send-extra-param-to-event.vue │ ├── custom-panel-with-logic-dragger.vue │ ├── custom-panel-with-component-dragger.vue │ ├── use-data-in-cook-player.vue │ └── use-data-in-cook-editor.vue └── utils │ ├── fecthPage.ts │ └── fecthPages.ts ├── .vscode ├── extensions.json ├── settings.json └── vue.code-snippets ├── commitlint.config.js ├── src ├── types │ ├── ICookStateType.ts │ ├── IPropOption.ts │ ├── IResourceMakerType.ts │ ├── IComponentSelected.ts │ ├── IPageCookPanelSize.ts │ ├── IEventOption.ts │ ├── ISplitLayoutPaneName.ts │ ├── ISlotOption.ts │ ├── ICookEditorExportData.ts │ ├── IPage.ts │ ├── ICookState.ts │ ├── IPanelConfig.ts │ ├── ICookPlayerState.ts │ ├── ICookStateBase.ts │ ├── ILogicConfig.ts │ ├── IComponentOverlay.ts │ ├── ISplitLayout.ts │ ├── ISplitPaneConfig.ts │ ├── IResourceMaker.ts │ ├── IResourceConfigBase.ts │ ├── ICookEditorState.ts │ ├── IResourceConfig.ts │ ├── ILogicMaker.ts │ ├── ICookPlayerExportData.ts │ ├── IComponentConfig.ts │ ├── IResourceMakerBase.ts │ ├── IComponentMaker.ts │ └── IPanelMaker.ts ├── components │ ├── cook-editor │ │ ├── insider-editor │ │ │ ├── utils │ │ │ │ ├── makeDomId.ts │ │ │ │ ├── useListGroup.ts │ │ │ │ ├── handleDragEnter.ts │ │ │ │ ├── handleDragLeave.ts │ │ │ │ ├── handleDragStart.ts │ │ │ │ ├── isTabsTitleTarget.ts │ │ │ │ └── isTabTarget.ts │ │ │ ├── PanelRender.vue │ │ │ └── index.vue │ │ ├── index.vue │ │ └── insider-player │ │ │ ├── CookPlayerWrapper.vue │ │ │ └── index.vue │ ├── event-dragger │ │ ├── handleDragLeave.ts │ │ ├── handleDragOver.ts │ │ ├── handleDragEnter.ts │ │ ├── handleDrop.ts │ │ └── index.vue │ ├── logic-dragger │ │ ├── handleDragLeave.ts │ │ ├── handleDragOver.ts │ │ ├── handleDragEnter.ts │ │ ├── handleDrop.ts │ │ └── index.vue │ ├── component-dragger │ │ ├── handleDragLeave.ts │ │ ├── handleDragOver.ts │ │ ├── handleDragEnter.ts │ │ ├── handleDrop.ts │ │ └── index.vue │ ├── cook-player │ │ ├── utils │ │ │ ├── isFragment.ts │ │ │ └── getComponentElements.ts │ │ ├── IPlayerConfig.ts │ │ └── index.vue │ └── resource-maker │ │ └── index.vue ├── utils │ ├── getCookEditorExportDataUniqTag.ts │ ├── defineLogicMaker.ts │ ├── definePanelMaker.ts │ ├── defineComponentMaker.ts │ ├── createRenderLoop.ts │ ├── addComponentConfig.ts │ ├── removeComponentConfig.ts │ ├── parseLogicConfig.ts │ ├── const-value.ts │ ├── getCookPlayerExportDataFromWindow.ts │ ├── makeDefaultLogicConfig.ts │ ├── makeDefaultPanelConfig.ts │ ├── getCookEditorExportDataFromWindow.ts │ ├── isLogicConfig.ts │ ├── makeDefaultComponentConfig.ts │ ├── logic-run.ts │ ├── layoutAddTab.ts │ ├── findPanelConfig.ts │ ├── getMakerDataFromDragEvent.ts │ ├── defaultMakerList.ts │ ├── createCookPlayerState.ts │ ├── createCookEditorState.ts │ ├── defaultSplitLayout.ts │ ├── layoutRemoveTab.ts │ ├── findComponentConfig.ts │ └── exportCookEditorData.ts ├── built-in-resources │ ├── panels │ │ ├── page-editor │ │ │ ├── ruler-box │ │ │ │ ├── utils │ │ │ │ │ ├── getRulerUnit.ts │ │ │ │ │ ├── useRulerDivWidth.ts │ │ │ │ │ └── useRulerDivHeight.ts │ │ │ │ ├── RulerVertical.vue │ │ │ │ ├── RulerHorizontal.vue │ │ │ │ └── RulerBox.vue │ │ │ ├── component-overlay │ │ │ │ └── handleClick.ts │ │ │ ├── PageCookPanel.vue │ │ │ ├── utils │ │ │ │ └── PagePanelLinker.ts │ │ │ └── PageCookContent.vue │ │ ├── component-editor │ │ │ ├── index.ts │ │ │ ├── logic-editor │ │ │ │ └── LogicEditorItem.vue │ │ │ ├── props-editor │ │ │ │ └── index.vue │ │ │ ├── slots-editor │ │ │ │ └── index.vue │ │ │ └── events-editor │ │ │ │ ├── index.vue │ │ │ │ ├── EventInfoTips.vue │ │ │ │ └── EventLogicAction.vue │ │ ├── resource-panel │ │ │ ├── index.ts │ │ │ └── ResuorceMaker.vue │ │ └── page-component-tree │ │ │ └── index.ts │ └── components │ │ └── root-app │ │ ├── RootApp.vue │ │ └── index.ts ├── hooks │ ├── useLogicMakerList.ts │ ├── usePanelMakerList.ts │ ├── useComponentMakerList.ts │ ├── useLogicMaker.ts │ ├── usePanelMaker.ts │ ├── useComponentMaker.ts │ ├── useComponentPickerEnable.ts │ ├── useSlotOptions.ts │ ├── usePageEditingUidList.ts │ ├── useComponentSelected.ts │ └── useComponentFocused.ts └── svgs │ ├── panel.svg │ ├── logic.svg │ ├── component.svg │ ├── page-size.svg │ └── url.svg ├── .husky └── commit-msg ├── CONTRIBUTING.md ├── tsconfig.dts.json ├── index.html ├── tsconfig.json ├── env.d.ts ├── tsconfig.base.json ├── vite.config.ts ├── rollup.dts.config.js ├── vite.common.config.ts ├── .github └── workflows │ ├── publish.yml │ └── docs.yml ├── LICENSE ├── README.md ├── .versionrc ├── CHANGELOG.md ├── .gitignore ├── scripts └── release.js └── package.json /docs/demos.md: -------------------------------------------------------------------------------- 1 | # 示例目录 2 | 3 | -------------------------------------------------------------------------------- /demos/test-pkg/logic/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AlertMaker } from "./alert" -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["johnsoncodehk.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | } 4 | -------------------------------------------------------------------------------- /src/types/ICookStateType.ts: -------------------------------------------------------------------------------- 1 | type ICookStateType = "editor" | "player" 2 | 3 | export default ICookStateType -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /demos/test-pkg/components/button/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | 我是按钮 4 | -------------------------------------------------------------------------------- /docs/guide/effect-picture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuWenXing1996/vue-cook/HEAD/docs/guide/effect-picture.gif -------------------------------------------------------------------------------- /src/types/IPropOption.ts: -------------------------------------------------------------------------------- 1 | export default interface IPropOption { 2 | name: string, 3 | value: string 4 | } 5 | -------------------------------------------------------------------------------- /docs/advanced/logic-run-principle.md: -------------------------------------------------------------------------------- 1 | # 逻辑运行机制 2 | 3 | 4 | 5 | 编写中,可以先参考源码`src/utils/logic-run.ts` -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-dom-events/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | 点我 4 | -------------------------------------------------------------------------------- /src/types/IResourceMakerType.ts: -------------------------------------------------------------------------------- 1 | type IResourceMakerType = "component" | "logic" | "panel" 2 | 3 | export default IResourceMakerType -------------------------------------------------------------------------------- /src/types/IComponentSelected.ts: -------------------------------------------------------------------------------- 1 | export default interface IComponentSelected { 2 | componentUid: string, 3 | pageUid: string 4 | } -------------------------------------------------------------------------------- /src/types/IPageCookPanelSize.ts: -------------------------------------------------------------------------------- 1 | export default interface IPageCookPanelSize { 2 | width: number, 3 | height: number, 4 | scale: number 5 | } -------------------------------------------------------------------------------- /docs/advanced/panel-load-principle.md: -------------------------------------------------------------------------------- 1 | # 面板加载机制 2 | 3 | 4 | 5 | 6 | 7 | 编写中,可以先参考源码`src/components/cook-editor/insider-editor/PanelList.vue` -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## 发布 2 | 3 | ```bash 4 | yarn release 5 | ``` 6 | 7 | 指定版本号 8 | 9 | ```bash 10 | yarn release -r major|minor|patch 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /docs/advanced/component-picker-principle.md: -------------------------------------------------------------------------------- 1 | # 组件拾取机制 2 | 3 | 4 | 5 | 6 | 编写中,可以先参考源码`src/built-in-resources/panels/page-editor/ComponentPicker.vue` 7 | 8 | -------------------------------------------------------------------------------- /docs/advanced/logic-run-principle copy.md: -------------------------------------------------------------------------------- 1 | # 逻辑运行机制 2 | 3 | 4 | 5 | ## 基础原理 6 | 7 | ## 可配置的属性`props` 8 | 9 | 10 | 编写中,可以先参考源码`src/utils/logic-run.ts` -------------------------------------------------------------------------------- /src/types/IEventOption.ts: -------------------------------------------------------------------------------- 1 | import ILogicConfig from "./ILogicConfig"; 2 | 3 | export default interface IEventOption { 4 | name: string, 5 | value: ILogicConfig[] 6 | } 7 | -------------------------------------------------------------------------------- /src/types/ISplitLayoutPaneName.ts: -------------------------------------------------------------------------------- 1 | import ISplitLayout from "./ISplitLayout"; 2 | 3 | type ISplitLayoutPaneName = keyof ISplitLayout 4 | export default ISplitLayoutPaneName 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Cmpt", 4 | "Editables", 5 | "pinia", 6 | "uuidv" 7 | ], 8 | "explorer.compactFolders": false 9 | } -------------------------------------------------------------------------------- /src/types/ISlotOption.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from "./IComponentConfig"; 2 | 3 | export default interface ISlotOption { 4 | name: string, 5 | value: IComponentConfig[] 6 | } 7 | -------------------------------------------------------------------------------- /docs/advanced/panel-load-principle copy.md: -------------------------------------------------------------------------------- 1 | # 面板加载机制 2 | 3 | 4 | 5 | 6 | ## 基础原理 7 | 8 | ## 面板配置 9 | 10 | 编写中,可以先参考源码`src/components/cook-editor/insider-editor/PanelList.vue` -------------------------------------------------------------------------------- /src/types/ICookEditorExportData.ts: -------------------------------------------------------------------------------- 1 | import IPage from "./IPage"; 2 | 3 | export default interface ICookEditorExportData { 4 | getPage: () => IPage | undefined, 5 | setPage: (page: IPage) => void 6 | } -------------------------------------------------------------------------------- /src/types/IPage.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from './IComponentConfig'; 2 | export default interface IPage { 3 | path: string, 4 | name: string, 5 | uid: string, 6 | component: IComponentConfig 7 | } -------------------------------------------------------------------------------- /tsconfig.dts.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "env.d.ts", 5 | "src/**/*.ts", 6 | "src/**/*.d.ts", 7 | "src/**/*.tsx", 8 | "src/**/*.vue" 9 | ] 10 | } -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/utils/makeDomId.ts: -------------------------------------------------------------------------------- 1 | import IPanelConfig from "@/types/IPanelConfig"; 2 | 3 | export default function makeDomId(config: IPanelConfig) { 4 | return `vue-cook-${config.uid}` 5 | } -------------------------------------------------------------------------------- /src/types/ICookState.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from "./ICookEditorState" 2 | import ICookPlayerState from "./ICookPlayerState" 3 | 4 | type ICookState = ICookEditorState | ICookPlayerState 5 | 6 | export default ICookState -------------------------------------------------------------------------------- /src/types/IPanelConfig.ts: -------------------------------------------------------------------------------- 1 | import IResourceConfigBase from "./IResourceConfigBase"; 2 | 3 | export default interface IPanelConfig extends IResourceConfigBase { 4 | makerType: "panel" 5 | alwaysOpen?: boolean 6 | } 7 | -------------------------------------------------------------------------------- /docs/.vuepress/hooks/useRoutes.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | import { RouteRecordRaw } from "vue-router"; 3 | 4 | const routes = ref([]) 5 | 6 | export default function useRoutes() { 7 | return routes 8 | } -------------------------------------------------------------------------------- /docs/thanks-list.md: -------------------------------------------------------------------------------- 1 | # 致谢 2 | 3 | 特别感谢: 4 | 5 | - [naive-ui](https://www.naiveui.com/zh-CN/os-theme)项目提供的UI支持 6 | - [vue-devtools](https://devtools.vuejs.org/)项目对组件拾取机制实现方面的启发 7 | 8 | 感谢: 9 | 10 | -------------------------------------------------------------------------------- /src/components/event-dragger/handleDragLeave.ts: -------------------------------------------------------------------------------- 1 | export default function handleDragLeave(e: DragEvent) { 2 | if (e.target && e.target instanceof HTMLElement) { 3 | e.target.classList.remove('dragenter') 4 | } 5 | } -------------------------------------------------------------------------------- /src/components/logic-dragger/handleDragLeave.ts: -------------------------------------------------------------------------------- 1 | export default function handleDragLeave(e: DragEvent) { 2 | if (e.target && e.target instanceof HTMLElement) { 3 | e.target.classList.remove('dragenter') 4 | } 5 | } -------------------------------------------------------------------------------- /src/components/component-dragger/handleDragLeave.ts: -------------------------------------------------------------------------------- 1 | 2 | export default function handleDragLeave(e: DragEvent) { 3 | if (e.target && e.target instanceof HTMLElement) { 4 | e.target.classList.remove('dragenter') 5 | } 6 | } -------------------------------------------------------------------------------- /src/components/cook-player/utils/isFragment.ts: -------------------------------------------------------------------------------- 1 | import { ComponentInternalInstance, Fragment } from "vue"; 2 | 3 | export default function isFragment(instance: ComponentInternalInstance) { 4 | return instance.subTree.type === Fragment 5 | } -------------------------------------------------------------------------------- /src/types/ICookPlayerState.ts: -------------------------------------------------------------------------------- 1 | import ICookStateBase from "./ICookStateBase"; 2 | import IPage from "./IPage"; 3 | 4 | export default interface ICookPlayerState extends ICookStateBase { 5 | type: "player", 6 | page: IPage 7 | } -------------------------------------------------------------------------------- /src/types/ICookStateBase.ts: -------------------------------------------------------------------------------- 1 | import ICookStateType from "./ICookStateType"; 2 | import IResourceMaker from "./IResourceMaker"; 3 | export default interface ICookStateBase { 4 | type: ICookStateType, 5 | makerList: IResourceMaker[] 6 | } -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-slots/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | 4 | 5 | 6 | 7 | 我是按钮 8 | 9 | -------------------------------------------------------------------------------- /src/types/ILogicConfig.ts: -------------------------------------------------------------------------------- 1 | import IResourceConfigBase from "./IResourceConfigBase"; 2 | 3 | export default interface ILogicConfig extends IResourceConfigBase { 4 | makerType: "logic", 5 | props?: Record 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/utils/getCookEditorExportDataUniqTag.ts: -------------------------------------------------------------------------------- 1 | import { VueCookEditorExportDataPreTag } from "./const-value"; 2 | 3 | export default function getCookEditorExportDataUniqTag(uid: string) { 4 | return `${VueCookEditorExportDataPreTag}-${uid}` 5 | } -------------------------------------------------------------------------------- /docs/advanced/component-picker-principle copy.md: -------------------------------------------------------------------------------- 1 | # 组件拾取机制 2 | 3 | ## 基础原理 4 | 5 | ## 暴露组件示例 6 | 7 | ## 同步编辑组件配置 8 | 9 | 10 | 11 | 编写中,可以先参考源码`src/built-in-resources/panels/page-editor/ComponentPicker.vue` 12 | 13 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/ruler-box/utils/getRulerUnit.ts: -------------------------------------------------------------------------------- 1 | export default function getRulerUnit(scale: number) { 2 | const s1 = Number((1 / scale * 50).toFixed()) 3 | const s2 = Number((s1 / 5).toFixed()) * 5 4 | return s2 5 | } -------------------------------------------------------------------------------- /src/components/cook-player/IPlayerConfig.ts: -------------------------------------------------------------------------------- 1 | import IPage from '@/types/IPage'; 2 | import IResourceMakerBase from '@/types/IResourceMakerBase'; 3 | export default interface IPlayerConfig { 4 | page: IPage, 5 | makerList: IResourceMakerBase[] 6 | } -------------------------------------------------------------------------------- /src/types/IComponentOverlay.ts: -------------------------------------------------------------------------------- 1 | import { IComponentRect } from "../components/cook-player/utils/getComponentRect"; 2 | 3 | export default interface IComponentOverlay { 4 | configUid: string, 5 | pageUid: string, 6 | rect: IComponentRect 7 | } -------------------------------------------------------------------------------- /src/types/ISplitLayout.ts: -------------------------------------------------------------------------------- 1 | import IPanelConfig from "./IPanelConfig"; 2 | 3 | export default interface ISplitLayout { 4 | "left": IPanelConfig[], 5 | "center": IPanelConfig[], 6 | "right": IPanelConfig[], 7 | "bottom": IPanelConfig[] 8 | } 9 | -------------------------------------------------------------------------------- /demos/test-pkg/components/button/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | make: () => Button 8 | }) -------------------------------------------------------------------------------- /src/types/ISplitPaneConfig.ts: -------------------------------------------------------------------------------- 1 | import IPanelConfig from "./IPanelConfig"; 2 | 3 | export default interface ISplitPaneConfig { 4 | list: IPanelConfig[], 5 | split?: { 6 | isH: Boolean, 7 | config: ISplitPaneConfig 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-props/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | {{ text ? text : '我是默认文字' }} 4 | 5 | -------------------------------------------------------------------------------- /src/types/IResourceMaker.ts: -------------------------------------------------------------------------------- 1 | import IComponentMaker from "./IComponentMaker" 2 | import ILogicMaker from "./ILogicMaker" 3 | import IPanelMaker from "./IPanelMaker" 4 | 5 | type IResourceMaker = IComponentMaker | ILogicMaker | IPanelMaker 6 | 7 | export default IResourceMaker -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/utils/useListGroup.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | import IPanelConfig from "@/types/IPanelConfig"; 3 | 4 | const group = ref>({}) 5 | 6 | export default function useListGroup() { 7 | return group 8 | } 9 | -------------------------------------------------------------------------------- /demos/test-pkg/components/button-fragment-with-dom-events/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | 点我不触发事件 4 | 点我触发事件 5 | 6 | -------------------------------------------------------------------------------- /demos/test-pkg/logic/fetch/index.ts: -------------------------------------------------------------------------------- 1 | // FetchMaker.ts 2 | import { defineLogicMaker } from "vue-cook"; 3 | import fetch from "./fetch"; 4 | export default defineLogicMaker({ 5 | name: "fetch", 6 | pkg: "test-pkg", 7 | make: () => { 8 | return fetch 9 | } 10 | }) -------------------------------------------------------------------------------- /src/types/IResourceConfigBase.ts: -------------------------------------------------------------------------------- 1 | import IResourceMakerType from "./IResourceMakerType"; 2 | 3 | export default interface IResourceConfigBase { 4 | uid: string, 5 | name: string, 6 | makerType: IResourceMakerType, 7 | makerName: string, 8 | makerPkg: string 9 | } 10 | 11 | -------------------------------------------------------------------------------- /demos/test-pkg/logic/fetch-with-params/index.ts: -------------------------------------------------------------------------------- 1 | // FetchMaker.ts 2 | import { defineLogicMaker } from "vue-cook"; 3 | import fetch from "./fetch"; 4 | export default defineLogicMaker({ 5 | name: "fetch", 6 | pkg: "test-pkg", 7 | make: () => { 8 | return fetch 9 | } 10 | }) -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/404.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-props/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | makePropOptions: () => ["text"], 8 | make: () => Button 9 | }) -------------------------------------------------------------------------------- /demos/test-pkg/logic/alert/index.ts: -------------------------------------------------------------------------------- 1 | // AlertMaker.ts 2 | import { defineLogicMaker } from "vue-cook"; 3 | export default defineLogicMaker({ 4 | name: "alert", 5 | pkg: "test-pkg", 6 | make: () => { 7 | return () => { 8 | alert("你好,按钮被点击了") 9 | } 10 | } 11 | }) -------------------------------------------------------------------------------- /src/types/ICookEditorState.ts: -------------------------------------------------------------------------------- 1 | import ICookStateBase from "./ICookStateBase"; 2 | import IPage from "./IPage"; 3 | import ISplitLayout from './ISplitLayout'; 4 | 5 | export default interface ICookEditorState extends ICookStateBase { 6 | type: "editor", 7 | pages: IPage[], 8 | layout: ISplitLayout, 9 | } -------------------------------------------------------------------------------- /src/types/IResourceConfig.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from "./IComponentConfig" 2 | import ILogicConfig from "./ILogicConfig" 3 | import IPanelConfig from "./IPanelConfig" 4 | 5 | type IResourceConfig = IComponentConfig | ILogicConfig | IPanelConfig 6 | 7 | export default IResourceConfig 8 | 9 | 10 | -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-slots/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | makeSlotOptions: () => ["default"], 8 | make: () => Button 9 | }) -------------------------------------------------------------------------------- /demos/test-pkg/panels/color-panel/index.ts: -------------------------------------------------------------------------------- 1 | import definePanelMaker from '@/utils/definePanelMaker'; 2 | import ColorPanel from './ColorPanel.vue' 3 | 4 | export default definePanelMaker({ 5 | name: "颜色选择器面板", 6 | pkg: "test-pkg", 7 | defaultSplitLayoutPaneName: "right", 8 | make: () => ColorPanel 9 | }) -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-color-props/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | makePropOptions: () => ["color"], 8 | make: () => Button 9 | }) -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-dom-events/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | makeEventOptions: () => ["click"], 8 | make: () => Button 9 | }) -------------------------------------------------------------------------------- /demos/test-pkg/components/button-fragment-with-dom-events/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | makeEventOptions: () => ["click"], 8 | make: () => Button 9 | }) -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-custom-events/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | makeEventOptions: () => ["delayClick"], 8 | make: () => Button 9 | }) -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-multi-slots/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | makeSlotOptions: () => ["preIcon", "postIcon"], 8 | make: () => Button 9 | }) -------------------------------------------------------------------------------- /demos/test-pkg/panels/resource-panel/index.ts: -------------------------------------------------------------------------------- 1 | import definePanelMaker from '@/utils/definePanelMaker'; 2 | import ResourcePanel from './ResourcePanel.vue' 3 | 4 | export default definePanelMaker({ 5 | name: "自定义资源面板", 6 | pkg: "test-pkg", 7 | defaultSplitLayoutPaneName: "bottom", 8 | make: () => ResourcePanel 9 | }) -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/UseFoo.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 点我 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | const { path } = require('@vuepress/utils') 2 | 3 | module.exports = { 4 | // 你的主题 5 | name: 'vuepress-theme-foo', 6 | // 要继承的父主题 7 | extends: '@vuepress/theme-default', 8 | // 覆盖父主题的布局 9 | layouts: { 10 | 404: path.resolve(__dirname, 'layouts/404.vue'), 11 | }, 12 | } -------------------------------------------------------------------------------- /src/built-in-resources/components/root-app/RootApp.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/utils/handleDragEnter.ts: -------------------------------------------------------------------------------- 1 | import isTabsTitleTarget from './isTabsTitleTarget'; 2 | import isTabTarget from './isTabTarget'; 3 | export default function handleDragEnter(e: DragEvent) { 4 | if (isTabsTitleTarget(e) || isTabTarget(e)) { 5 | e.target.classList.add('dragenter') 6 | } 7 | } -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-color-props/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | 我是可以配置颜色的按钮 4 | 5 | 10 | -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-send-extra-param-to-event/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Button from "./Button.vue"; 4 | export default defineComponentMaker({ 5 | name: "按钮", 6 | pkg: "test-pkg", 7 | makeEventOptions: () => ["overwriteClick"], 8 | make: () => Button 9 | }) -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/utils/handleDragLeave.ts: -------------------------------------------------------------------------------- 1 | import isTabsTitleTarget from './isTabsTitleTarget'; 2 | import isTabTarget from './isTabTarget'; 3 | export default function handleDragLeave(e: DragEvent) { 4 | if (isTabsTitleTarget(e) || isTabTarget(e)) { 5 | e.target.classList.remove('dragenter') 6 | } 7 | } -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/utils/handleDragStart.ts: -------------------------------------------------------------------------------- 1 | import isTabTarget from './isTabTarget'; 2 | export default function handleDragStart(e: DragEvent) { 3 | if (isTabTarget(e)) { 4 | e.dataTransfer?.setData('uid', e.target.dataset.uid) 5 | e.dataTransfer?.setData('luid', e.target.dataset.luid) 6 | } 7 | } -------------------------------------------------------------------------------- /demos/test-pkg/panels/logic-dragger-panel/index.ts: -------------------------------------------------------------------------------- 1 | import definePanelMaker from '@/utils/definePanelMaker'; 2 | import LogicDraggerPanel from './LogicDraggerPanel.vue' 3 | 4 | export default definePanelMaker({ 5 | name: "逻辑拖拽添加面板", 6 | pkg: "test-pkg", 7 | defaultSplitLayoutPaneName: "right", 8 | make: () => LogicDraggerPanel 9 | }) -------------------------------------------------------------------------------- /docs/.vuepress/components/DemoLink.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ path }} 4 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /src/utils/defineLogicMaker.ts: -------------------------------------------------------------------------------- 1 | import ILogicMaker from "@/types/ILogicMaker"; 2 | 3 | type ILogicMakerOptions = Omit 4 | 5 | export default function defineLogicMaker(maker: ILogicMakerOptions): ILogicMaker { 6 | const _maker: ILogicMaker = { 7 | type: "logic", 8 | ...maker 9 | } 10 | return _maker 11 | } -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-send-extra-param-to-event/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | 点我 4 | 5 | -------------------------------------------------------------------------------- /demos/test-pkg/panels/component-dragger-panel/index.ts: -------------------------------------------------------------------------------- 1 | import definePanelMaker from '@/utils/definePanelMaker'; 2 | import ComponentDraggerPanel from './ComponentDraggerPanel.vue' 3 | 4 | export default definePanelMaker({ 5 | name: "组件拖拽添加面板", 6 | pkg: "test-pkg", 7 | defaultSplitLayoutPaneName: "right", 8 | make: () => ComponentDraggerPanel 9 | }) -------------------------------------------------------------------------------- /src/utils/definePanelMaker.ts: -------------------------------------------------------------------------------- 1 | import IPanelMaker from "@/types/IPanelMaker"; 2 | 3 | type IPanelMakerOptions = Omit 4 | 5 | export default function definePanelMaker(maker: IPanelMakerOptions): IPanelMaker { 6 | const _maker: IPanelMaker = { 7 | type: "panel", 8 | ...maker 9 | } 10 | return _maker 11 | } 12 | 13 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/UseComponentRenderFoo.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 点我 4 | 5 | 6 | -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-multi-slots/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | 4 | 5 | 6 | 7 | 我是按钮 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demos/test-pkg/logic/alert-with-receive-params/index.ts: -------------------------------------------------------------------------------- 1 | // AlertMaker.ts 2 | import { defineLogicMaker } from "vue-cook"; 3 | export default defineLogicMaker({ 4 | name: "alert", 5 | pkg: "test-pkg", 6 | make: () => { 7 | return (event: MouseEvent) => { 8 | alert(`你好,按钮被点击了,位置在${event.x},${event.y}`) 9 | } 10 | } 11 | }) -------------------------------------------------------------------------------- /demos/test-pkg/components/button-with-custom-events/Button.vue: -------------------------------------------------------------------------------- 1 | // Button.vue 2 | 3 | 点我,2秒后触发事件 4 | 5 | -------------------------------------------------------------------------------- /src/types/ILogicMaker.ts: -------------------------------------------------------------------------------- 1 | import ICookState from "./ICookState"; 2 | import ILogicConfig from "./ILogicConfig"; 3 | import IResourceMakerBase from './IResourceMakerBase'; 4 | 5 | export default interface ILogicMaker extends IResourceMakerBase { 6 | readonly type: "logic" 7 | makePropOptions?: (cookState: ICookState, logicConfig: ILogicConfig) => string[] 8 | } -------------------------------------------------------------------------------- /demos/test-pkg/components/emoji/index.ts: -------------------------------------------------------------------------------- 1 | // EmojiMaker.ts 2 | import { defineComponent, h } from 'vue'; 3 | import { defineComponentMaker } from 'vue-cook' 4 | export default defineComponentMaker({ 5 | name: "emoji", 6 | pkg: "test-pkg", 7 | make: () => defineComponent({ 8 | render: () => h( 9 | 'span', 10 | '😀' 11 | ) 12 | }) 13 | }) -------------------------------------------------------------------------------- /src/utils/defineComponentMaker.ts: -------------------------------------------------------------------------------- 1 | import IComponentMaker from "@/types/IComponentMaker"; 2 | 3 | type IComponentMakerOptions = Omit 4 | 5 | export default function defineComponentMaker(maker: IComponentMakerOptions): IComponentMaker { 6 | const _maker: IComponentMaker = { 7 | type: "component", 8 | ...maker 9 | } 10 | return _maker 11 | } -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/Foo.vue: -------------------------------------------------------------------------------- 1 | // Foo.vue 2 | 3 | msg:{{ msg }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue Cook 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/component-editor/index.ts: -------------------------------------------------------------------------------- 1 | import EditorPanel from "./EditorPanel.vue"; 2 | import definePanelMaker from '@/utils/definePanelMaker'; 3 | import { pkgName } from '@/utils/const-value'; 4 | 5 | export default definePanelMaker({ 6 | name: "基础组件编辑器", 7 | pkg: pkgName, 8 | defaultSplitLayoutPaneName: "right", 9 | make: () => EditorPanel 10 | }) -------------------------------------------------------------------------------- /demos/test-pkg/components/table-with-fetch/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Table from "./Table.vue"; 4 | export interface IDataItem { 5 | name: string, 6 | age: number 7 | } 8 | export default defineComponentMaker({ 9 | name: "表格", 10 | pkg: "test-pkg", 11 | makePropOptions: () => ["fetch"], 12 | make: () => Table 13 | }) -------------------------------------------------------------------------------- /src/types/ICookPlayerExportData.ts: -------------------------------------------------------------------------------- 1 | import IComponentOverlay from './IComponentOverlay'; 2 | import IPage from './IPage'; 3 | export default interface ICookPlayerExportData { 4 | getComponetnOverlayFromElement: (element: Element) => IComponentOverlay | undefined 5 | getComponetnOverlayFromComponentConfigUid: (uid: string) => IComponentOverlay | undefined 6 | setPage: (page: IPage) => void 7 | } -------------------------------------------------------------------------------- /src/utils/createRenderLoop.ts: -------------------------------------------------------------------------------- 1 | export default function createRenderLoop(render: () => void) { 2 | let frameId: number; 3 | 4 | const f = () => { 5 | render(); 6 | frameId = requestAnimationFrame(f); 7 | } 8 | const stop = () => { 9 | cancelAnimationFrame(frameId); 10 | } 11 | frameId = requestAnimationFrame(f); 12 | return stop; 13 | } -------------------------------------------------------------------------------- /src/types/IComponentConfig.ts: -------------------------------------------------------------------------------- 1 | import ILogicConfig from "./ILogicConfig"; 2 | import IResourceConfigBase from "./IResourceConfigBase"; 3 | 4 | export default interface IComponentConfig extends IResourceConfigBase { 5 | makerType: "component", 6 | props?: Record, 7 | slots?: Record, 8 | events?: Record, 9 | } 10 | 11 | -------------------------------------------------------------------------------- /demos/test-pkg/logic/alert-with-props/index.ts: -------------------------------------------------------------------------------- 1 | // AlertMaker.ts 2 | import { defineLogicMaker } from "vue-cook"; 3 | export default defineLogicMaker({ 4 | name: "alert", 5 | pkg: "test-pkg", 6 | makePropOptions: () => ["msg"], 7 | make: (cookPlayerState, logicConfig) => { 8 | return () => { 9 | alert(`${logicConfig.props?.["msg"] || '你好,世界!'}`) 10 | } 11 | } 12 | }) -------------------------------------------------------------------------------- /docs/.vuepress/components/DemoTOC.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ l.path }} 5 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /src/components/event-dragger/handleDragOver.ts: -------------------------------------------------------------------------------- 1 | import { VueCookLogicMakerDraggerTag } from '@/utils/const-value'; 2 | const handleDragOver = (e: DragEvent) => { 3 | e.preventDefault(); 4 | if (e.dataTransfer) { 5 | if (!e.dataTransfer.types.includes(VueCookLogicMakerDraggerTag)) { 6 | e.dataTransfer.dropEffect = "none" 7 | } 8 | } 9 | } 10 | 11 | export default handleDragOver -------------------------------------------------------------------------------- /src/components/logic-dragger/handleDragOver.ts: -------------------------------------------------------------------------------- 1 | import { VueCookLogicMakerDraggerTag } from '@/utils/const-value'; 2 | const handleDragOver = (e: DragEvent) => { 3 | e.preventDefault(); 4 | if (e.dataTransfer) { 5 | if (!e.dataTransfer.types.includes(VueCookLogicMakerDraggerTag)) { 6 | e.dataTransfer.dropEffect = "none" 7 | } 8 | } 9 | } 10 | 11 | export default handleDragOver -------------------------------------------------------------------------------- /src/utils/addComponentConfig.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from '@/types/IComponentConfig'; 2 | export default function addComponentConfig(parentComponent: IComponentConfig, childComponent: IComponentConfig, slotName: string) { 3 | parentComponent.slots = parentComponent.slots || {} 4 | parentComponent.slots[slotName] = parentComponent.slots[slotName] || [] 5 | parentComponent.slots[slotName].push(childComponent) 6 | } -------------------------------------------------------------------------------- /src/utils/removeComponentConfig.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from '@/types/IComponentConfig'; 2 | export default function removeComponentConfig(parentComponent: IComponentConfig, childComponentUid: string, slotName: string) { 3 | const list = parentComponent.slots?.[slotName] || [] 4 | const index = list.findIndex(e => e.uid === childComponentUid) 5 | if (index > -1) { 6 | list.splice(index, 1) 7 | } 8 | } -------------------------------------------------------------------------------- /demos/test-pkg/components/table-with-fetch-params/index.ts: -------------------------------------------------------------------------------- 1 | // ButtonMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Table from "./Table.vue"; 4 | export interface IDataItem { 5 | name: string, 6 | age: number 7 | } 8 | export default defineComponentMaker({ 9 | name: "表格", 10 | pkg: "test-pkg", 11 | makePropOptions: () => ["fetch", "maxAge", "minAge"], 12 | make: () => Table 13 | }) -------------------------------------------------------------------------------- /src/hooks/useLogicMakerList.ts: -------------------------------------------------------------------------------- 1 | 2 | import { computed } from 'vue'; 3 | import ILogicMaker from '../types/ILogicMaker'; 4 | import ICookStateBase from '../types/ICookStateBase'; 5 | 6 | export default function useLogicMakerList(cookState: ICookStateBase) { 7 | return computed(() => { 8 | const allList = cookState.makerList 9 | return allList.filter(e => e.type === "logic") as ILogicMaker[] 10 | }) 11 | } -------------------------------------------------------------------------------- /src/hooks/usePanelMakerList.ts: -------------------------------------------------------------------------------- 1 | 2 | import { computed } from 'vue'; 3 | import IPanelMaker from '../types/IPanelMaker'; 4 | import ICookStateBase from '../types/ICookStateBase'; 5 | 6 | export default function usePanelMakerList(cookState: ICookStateBase) { 7 | return computed(() => { 8 | const allList = cookState.makerList 9 | return allList.filter(e => e.type === "panel") as IPanelMaker[] 10 | }) 11 | } -------------------------------------------------------------------------------- /src/built-in-resources/components/root-app/index.ts: -------------------------------------------------------------------------------- 1 | import defineComponentMaker from "@/utils/defineComponentMaker" 2 | import RootApp from "./RootApp.vue"; 3 | import { pkgName } from '@/utils/const-value'; 4 | import IResourceMakerType from "@/types/IResourceMakerType"; 5 | 6 | export default defineComponentMaker({ 7 | name: "主应用", 8 | pkg: pkgName, 9 | makeSlotOptions: () => ["default"], 10 | make: () => RootApp 11 | }) 12 | -------------------------------------------------------------------------------- /src/components/component-dragger/handleDragOver.ts: -------------------------------------------------------------------------------- 1 | import { VueCookComponentMakerDraggerTag } from '@/utils/const-value'; 2 | const handleDragOver = (e: DragEvent) => { 3 | e.preventDefault(); 4 | if (e.dataTransfer) { 5 | if (!e.dataTransfer.types.includes(VueCookComponentMakerDraggerTag)) { 6 | e.dataTransfer.dropEffect = "none" 7 | } 8 | } 9 | } 10 | 11 | export default handleDragOver 12 | -------------------------------------------------------------------------------- /demos/test-pkg/logic/alert-with-receive-extra-params/index.ts: -------------------------------------------------------------------------------- 1 | // AlertMaker.ts 2 | import { defineLogicMaker } from "vue-cook"; 3 | export default defineLogicMaker({ 4 | name: "alert", 5 | pkg: "test-pkg", 6 | make: () => { 7 | return (event: MouseEvent, a: string, b: string) => { 8 | alert(` 9 | 你好,按钮被点击了,位置在${event.x},${event.y} 10 | 额外信息:${a},${b} 11 | `) 12 | } 13 | } 14 | }) -------------------------------------------------------------------------------- /src/utils/parseLogicConfig.ts: -------------------------------------------------------------------------------- 1 | import isLogicConfig from "./isLogicConfig"; 2 | 3 | export default function parseLogicConfig(jsonString?: string) { 4 | if (jsonString) { 5 | let config; 6 | try { 7 | config = JSON.parse(jsonString) 8 | } catch (e) { 9 | console.log(e) 10 | } 11 | if (isLogicConfig(config)) { 12 | return config 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /demos/test-pkg/logic/fetch/fetch.ts: -------------------------------------------------------------------------------- 1 | // fetch.ts 2 | interface IDataItem { 3 | name: string, 4 | age: number 5 | } 6 | export default async function fetch() { 7 | return new Promise((resolve, reject) => { 8 | const data: IDataItem[] = [ 9 | { name: "张三", age: 20 }, 10 | { name: "李四", age: 21 }, 11 | ] 12 | setTimeout(() => { 13 | resolve(data) 14 | }, 1000) 15 | }) 16 | } -------------------------------------------------------------------------------- /src/hooks/useComponentMakerList.ts: -------------------------------------------------------------------------------- 1 | 2 | import { computed } from 'vue'; 3 | import ICookStateBase from '../types/ICookStateBase'; 4 | import IComponentMaker from './../types/IComponentMaker'; 5 | 6 | export default function useComponentMakerList(cookState: ICookStateBase) { 7 | return computed(() => { 8 | const allList = cookState.makerList 9 | return allList.filter(e => e.type === "component") as IComponentMaker[] 10 | }) 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "env.d.ts", 5 | "demos/**/*.ts", 6 | "demos/**/*.d.ts", 7 | "demos/**/*.tsx", 8 | "demos/**/*.vue", 9 | "docs/.vuepress/**/*.ts", 10 | "docs/.vuepress/**/*.d.ts", 11 | "docs/.vuepress/**/*.tsx", 12 | "docs/.vuepress/**/*.vue", 13 | "src/**/*.ts", 14 | "src/**/*.d.ts", 15 | "src/**/*.tsx", 16 | "src/**/*.vue" 17 | ] 18 | } -------------------------------------------------------------------------------- /demos/test-pkg/panels/simple-panel/index.ts: -------------------------------------------------------------------------------- 1 | // SimplePanelMaker.ts 2 | import definePanelMaker from '@/utils/definePanelMaker'; 3 | import { defineComponent, h } from "vue"; 4 | 5 | export default definePanelMaker({ 6 | name: "简单面板", 7 | pkg: "test-pkg", 8 | defaultSplitLayoutPaneName: "right", 9 | make: () => defineComponent({ 10 | render: () => h( 11 | 'div', 12 | '我是一个简单面板' 13 | ) 14 | }) 15 | }) -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare module '*.vue' { 6 | import { DefineComponent } from 'vue' 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 8 | const component: DefineComponent<{}, {}, any> 9 | export default component 10 | } 11 | 12 | const __VUEPRESS_SSR__: Boolean -------------------------------------------------------------------------------- /src/components/event-dragger/handleDragEnter.ts: -------------------------------------------------------------------------------- 1 | import { VueCookLogicMakerDraggerTag } from "@/utils/const-value"; 2 | 3 | export default function handleDragEnter(e: DragEvent) { 4 | if (e.dataTransfer) { 5 | if (e.dataTransfer.types.includes(VueCookLogicMakerDraggerTag)) { 6 | if (e.target && e.target instanceof HTMLElement) { 7 | e.target.classList.add('dragenter') 8 | } 9 | } 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/components/logic-dragger/handleDragEnter.ts: -------------------------------------------------------------------------------- 1 | import { VueCookLogicMakerDraggerTag } from "@/utils/const-value"; 2 | 3 | export default function handleDragEnter(e: DragEvent) { 4 | if (e.dataTransfer) { 5 | if (e.dataTransfer.types.includes(VueCookLogicMakerDraggerTag)) { 6 | if (e.target && e.target instanceof HTMLElement) { 7 | e.target.classList.add('dragenter') 8 | } 9 | } 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/built-in-resources/panels/resource-panel/index.ts: -------------------------------------------------------------------------------- 1 | import ResourcePanel from "./ResourcePanel.vue"; 2 | import { name as pkgName } from "@/../package.json" 3 | import definePanelMaker from '@/utils/definePanelMaker'; 4 | import IPanelMaker from '@/types/IPanelMaker'; 5 | 6 | const maker: IPanelMaker = definePanelMaker({ 7 | name: "资源面板", 8 | pkg: pkgName, 9 | defaultSplitLayoutPaneName: "bottom", 10 | make: () => ResourcePanel 11 | }) 12 | export default maker -------------------------------------------------------------------------------- /src/components/component-dragger/handleDragEnter.ts: -------------------------------------------------------------------------------- 1 | import { VueCookComponentMakerDraggerTag } from "@/utils/const-value"; 2 | 3 | export default function handleDragEnter(e: DragEvent) { 4 | if (e.dataTransfer) { 5 | if (e.dataTransfer.types.includes(VueCookComponentMakerDraggerTag)) { 6 | if (e.target && e.target instanceof HTMLElement) { 7 | e.target.classList.add('dragenter') 8 | } 9 | } 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/utils/const-value.ts: -------------------------------------------------------------------------------- 1 | import { name } from "@/../package.json" 2 | 3 | // export data tags 4 | export const VueCookPlayerExportDataTag = "__VUE_COOK_PLAYER_EXPORT_DATA__" 5 | export const VueCookEditorExportDataPreTag = "__VUE_COOK_EDITOR_EXPORT_PAGE_PRE__" 6 | 7 | // dragger tages 8 | export const VueCookLogicMakerDraggerTag = 'vue-cook-logic-maker-dragger-tag' 9 | export const VueCookComponentMakerDraggerTag = 'vue-cook-component-maker-dragger-tag' 10 | export const pkgName = name -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-component-tree/index.ts: -------------------------------------------------------------------------------- 1 | import PageComponentTree from "./PageComponentTree.vue"; 2 | import definePanelMaker from '@/utils/definePanelMaker'; 3 | import { name as pkgName } from "@/../package.json" 4 | import IPanelMaker from '@/types/IPanelMaker'; 5 | 6 | const maker: IPanelMaker = definePanelMaker({ 7 | name: "页面组件树", 8 | pkg: pkgName, 9 | defaultSplitLayoutPaneName: "left", 10 | make: () => PageComponentTree 11 | }) 12 | 13 | 14 | export default maker -------------------------------------------------------------------------------- /src/utils/getCookPlayerExportDataFromWindow.ts: -------------------------------------------------------------------------------- 1 | import { VueCookPlayerExportDataTag } from "./const-value" 2 | import ICookPlayerExportData from '@/types/ICookPlayerExportData'; 3 | 4 | export default function getCookPlayerExportDataFromWindow(window?: Window) { 5 | let exportData 6 | if (window) { 7 | // @ts-ignore 8 | const _exportData = window[VueCookPlayerExportDataTag] 9 | exportData = _exportData as ICookPlayerExportData 10 | } 11 | return exportData 12 | } -------------------------------------------------------------------------------- /src/utils/makeDefaultLogicConfig.ts: -------------------------------------------------------------------------------- 1 | import ILogicConfig from "@/types/ILogicConfig"; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | import ILogicMaker from "@/types/ILogicMaker"; 4 | 5 | export default function makeDefaultLogicConfig(maker: ILogicMaker): ILogicConfig { 6 | const uid = uuidv4(); 7 | let config: ILogicConfig = { 8 | uid, 9 | name: maker.name, 10 | makerType: "logic", 11 | makerName: maker.name, 12 | makerPkg: maker.pkg, 13 | }; 14 | return config 15 | } -------------------------------------------------------------------------------- /src/utils/makeDefaultPanelConfig.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | import IPanelConfig from "@/types/IPanelConfig"; 3 | import IPanelMaker from '@/types/IPanelMaker'; 4 | 5 | export default function makeDefaultPanelConfig(maker: IPanelMaker): IPanelConfig { 6 | const uid = uuidv4(); 7 | let config: IPanelConfig = { 8 | uid, 9 | name: maker.name, 10 | makerType: "panel", 11 | makerName: maker.name, 12 | makerPkg: maker.pkg, 13 | }; 14 | return config 15 | } -------------------------------------------------------------------------------- /src/types/IResourceMakerBase.ts: -------------------------------------------------------------------------------- 1 | 2 | import IResourceMakerType from './IResourceMakerType'; 3 | import IResourceConfig from './IResourceConfig'; 4 | import ICookState from './ICookState'; 5 | 6 | export default interface IResourceMakerBase { 7 | readonly name: string, 8 | readonly pkg: string, 9 | readonly type: IResourceMakerType, 10 | make: (cookState: ICookState, resourceConfig: T) => P, 11 | install?: (cookState: ICookState) => void 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/getCookEditorExportDataFromWindow.ts: -------------------------------------------------------------------------------- 1 | import getCookEditorExportDataUniqTag from "./getCookEditorExportDataUniqTag"; 2 | import ICookEditorExportData from "@/types/ICookEditorExportData"; 3 | 4 | export default function getCookEditorExportDataFromWindow(window: Window, pageUid: string) { 5 | const uniqTag = getCookEditorExportDataUniqTag(pageUid) 6 | // @ts-ignore 7 | const exportData = window[uniqTag] 8 | if (exportData) { 9 | return exportData as ICookEditorExportData 10 | } 11 | } -------------------------------------------------------------------------------- /src/utils/isLogicConfig.ts: -------------------------------------------------------------------------------- 1 | import ILogicConfig from '@/types/ILogicConfig'; 2 | export default function isLogicConfig(config: any): config is ILogicConfig { 3 | if (!config) { 4 | return false 5 | } 6 | if ( 7 | !config.uid || 8 | !config.name || 9 | !config.makerType || 10 | !config.makerName || 11 | !config.makerPkg 12 | ) { 13 | return false 14 | } 15 | if (config.makerType !== "logic") { 16 | return false 17 | } 18 | return true 19 | } -------------------------------------------------------------------------------- /demos/test-pkg/panels/toggle-panel/index.ts: -------------------------------------------------------------------------------- 1 | import definePanelMaker from '@/utils/definePanelMaker'; 2 | import { defineComponent, h } from 'vue'; 3 | import TogglePanel from './TogglePanel.vue' 4 | export default definePanelMaker({ 5 | name: "开关面板", 6 | pkg: "test-pkg", 7 | defaultSplitLayoutPaneName: "right", 8 | make: (cookEditorState, panelConfig) => defineComponent({ 9 | render: () => h( 10 | TogglePanel, 11 | { 12 | panelConfig 13 | } 14 | ) 15 | }) 16 | }) -------------------------------------------------------------------------------- /src/utils/makeDefaultComponentConfig.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from "@/types/IComponentConfig"; 2 | import IComponentMaker from "@/types/IComponentMaker"; 3 | import { v4 as uuidv4 } from 'uuid'; 4 | 5 | export default function makeDefaultComponentConfig(maker: IComponentMaker): IComponentConfig { 6 | const uid = uuidv4(); 7 | let config: IComponentConfig = { 8 | uid, 9 | name: maker.name, 10 | makerType: "component", 11 | makerName: maker.name, 12 | makerPkg: maker.pkg 13 | }; 14 | return config 15 | } -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/UseComponentRenderFooWithDynamicName.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ slot }} 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/ruler-box/utils/useRulerDivWidth.ts: -------------------------------------------------------------------------------- 1 | import createRenderLoop from "@/utils/createRenderLoop"; 2 | import { onUnmounted, ref, Ref } from "vue"; 3 | 4 | export default function useRulerDivWidth(rulerDiv: Ref) { 5 | const width = ref(0) 6 | const stopFunc = createRenderLoop(() => { 7 | if (rulerDiv.value) { 8 | width.value = rulerDiv.value.clientWidth 9 | } 10 | }) 11 | onUnmounted(() => { 12 | stopFunc() 13 | }) 14 | return width 15 | } -------------------------------------------------------------------------------- /demos/pages/get-started.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/ruler-box/utils/useRulerDivHeight.ts: -------------------------------------------------------------------------------- 1 | import createRenderLoop from "@/utils/createRenderLoop"; 2 | import { onUnmounted, ref, Ref } from "vue"; 3 | 4 | export default function useRulerDivHeight(rulerDiv: Ref) { 5 | const height = ref(0) 6 | const stopFunc = createRenderLoop(() => { 7 | if (rulerDiv.value) { 8 | height.value = rulerDiv.value.clientHeight 9 | } 10 | }) 11 | onUnmounted(() => { 12 | stopFunc() 13 | }) 14 | return height 15 | } -------------------------------------------------------------------------------- /src/utils/logic-run.ts: -------------------------------------------------------------------------------- 1 | import useLogicMaker from "@/hooks/useLogicMaker"; 2 | import ICookState from "@/types/ICookState"; 3 | import ILogicConfig from "@/types/ILogicConfig"; 4 | 5 | export default async function logicRun(cookState: ICookState, config: ILogicConfig, ...payload: any[]): Promise { 6 | let returns; 7 | const maker = useLogicMaker(cookState, config.makerName, config.makerPkg).value 8 | if (maker) { 9 | let func = maker.make(cookState, config) 10 | returns = await func(...payload) 11 | } 12 | return returns as T 13 | } -------------------------------------------------------------------------------- /demos/test-pkg/panels/resource-panel/ResourcePanel.vue: -------------------------------------------------------------------------------- 1 | // ResourcePanel.vue 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/hooks/useLogicMaker.ts: -------------------------------------------------------------------------------- 1 | import { computed, Ref } from "vue"; 2 | import ICookStateBase from "../types/ICookStateBase"; 3 | import ILogicMaker from "../types/ILogicMaker"; 4 | import useLogicMakerList from "./useLogicMakerList"; 5 | 6 | export default function useLogicMaker(cookState: ICookStateBase, name?: string, pkg?: string): Ref { 7 | return computed(() => { 8 | const makerList = useLogicMakerList(cookState); 9 | const maker = makerList.value.find(e => e.name === name && e.pkg === pkg) 10 | return maker; 11 | }) 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/hooks/usePanelMaker.ts: -------------------------------------------------------------------------------- 1 | import { computed, Ref } from "vue"; 2 | import ICookStateBase from "../types/ICookStateBase"; 3 | import IPanelMaker from "../types/IPanelMaker"; 4 | import usePanelMakerList from "./usePanelMakerList"; 5 | 6 | export default function usePanelMaker(cookState: ICookStateBase, name?: string, pkg?: string): Ref { 7 | return computed(() => { 8 | const makerList = usePanelMakerList(cookState); 9 | const maker = makerList.value.find(e => e.name === name && e.pkg === pkg) 10 | return maker; 11 | }) 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/UseComponentRenderRenderFoo.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /demos/test-pkg/logic/fetch-with-props/index.ts: -------------------------------------------------------------------------------- 1 | // FetchMaker.ts 2 | import { defineLogicMaker } from "vue-cook"; 3 | import fetch from "./fetch"; 4 | export default defineLogicMaker({ 5 | name: "fetch", 6 | pkg: "test-pkg", 7 | makePropOptions: () => ["maxAge", "minAge"], 8 | make: (cookPlayerState, logicConfig) => { 9 | const maxAgeNumber = Number(logicConfig?.props?.["maxAge"]) || undefined 10 | const minAgeNumber = Number(logicConfig?.props?.["minAge"]) || undefined 11 | return () => { 12 | return fetch(maxAgeNumber, minAgeNumber) 13 | } 14 | } 15 | }) -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/utils/isTabsTitleTarget.ts: -------------------------------------------------------------------------------- 1 | interface TabTitleDragEvent extends DragEvent { 2 | target: Target 3 | } 4 | 5 | interface Target extends HTMLDivElement { 6 | dataset: Dataset 7 | } 8 | 9 | interface Dataset extends DOMStringMap { 10 | readonly tabsTitle: string 11 | } 12 | 13 | export default function isTabsTitleTarget(e: DragEvent): e is TabTitleDragEvent { 14 | if (!(e.target instanceof HTMLDivElement)) { 15 | return false; 16 | } 17 | if (!e?.target?.dataset?.tabsTitle) { 18 | return false; 19 | } 20 | return true; 21 | } -------------------------------------------------------------------------------- /src/hooks/useComponentMaker.ts: -------------------------------------------------------------------------------- 1 | import { computed, Ref } from "vue"; 2 | import IComponentMaker from "../types/IComponentMaker"; 3 | import ICookStateBase from "../types/ICookStateBase"; 4 | import useComponentMakerList from "./useComponentMakerList"; 5 | 6 | export default function useComponentMaker(cookState: ICookStateBase,name?: string, pkg?: string): Ref { 7 | return computed(() => { 8 | const makerList = useComponentMakerList(cookState); 9 | const maker = makerList.value.find(e => e.name === name && e.pkg === pkg) 10 | return maker; 11 | }) 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": [ 6 | "src/*" 7 | ], 8 | "vue-cook": [ 9 | "src/index.ts" 10 | ] 11 | }, 12 | "target": "esnext", 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "strict": true, 16 | "jsx": "preserve", 17 | "sourceMap": true, 18 | "resolveJsonModule": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "types": [ 22 | "vite/client" 23 | ], 24 | "lib": [ 25 | "esnext", 26 | "dom" 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /src/utils/layoutAddTab.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from '@/types/ICookEditorState'; 2 | import IPanelConfig from '@/types/IPanelConfig'; 3 | import ISplitLayoutPaneName from '@/types/ISplitLayoutPaneName'; 4 | import usePanelMaker from '@/hooks/usePanelMaker'; 5 | 6 | export default function layoutAddTab(cookEditorState: ICookEditorState, panelConfig: IPanelConfig, splitLayoutPaneName: ISplitLayoutPaneName) { 7 | cookEditorState.layout[splitLayoutPaneName].push(panelConfig) 8 | const maker = usePanelMaker(cookEditorState, panelConfig.makerName, panelConfig.makerPkg) 9 | maker?.value?.onOpen?.(cookEditorState, panelConfig) 10 | } -------------------------------------------------------------------------------- /src/types/IComponentMaker.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "vue"; 2 | import IComponentConfig from "./IComponentConfig"; 3 | import IResourceMakerBase from './IResourceMakerBase'; 4 | import ICookState from "./ICookState"; 5 | 6 | export default interface IComponentMaker extends IResourceMakerBase { 7 | readonly type: "component", 8 | makePropOptions?: (cookState: ICookState, componentConfig: IComponentConfig) => string[] 9 | makeEventOptions?: (cookState: ICookState, componentConfig: IComponentConfig) => string[] 10 | makeSlotOptions?: (cookState: ICookState, componentConfig: IComponentConfig) => string[] 11 | } -------------------------------------------------------------------------------- /src/utils/findPanelConfig.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from "@/types/ICookEditorState"; 2 | 3 | export default function findPanelConfig(cookEditorState: ICookEditorState, panelUid: string) { 4 | const layout = cookEditorState.layout 5 | let found = undefined 6 | found = layout.left.find(e => e.uid === panelUid) 7 | if (!found) { 8 | found = layout.center.find(e => e.uid === panelUid) 9 | } 10 | if (!found) { 11 | found = layout.right.find(e => e.uid === panelUid) 12 | } 13 | if (!found) { 14 | found = layout.bottom.find(e => e.uid === panelUid) 15 | } 16 | return found 17 | } -------------------------------------------------------------------------------- /src/utils/getMakerDataFromDragEvent.ts: -------------------------------------------------------------------------------- 1 | import IResourceMakerType from "@/types/IResourceMakerType" 2 | 3 | 4 | export default function getMakerDataFromDragEvent(e: DragEvent) { 5 | if (!e.dataTransfer) { 6 | return 7 | } 8 | const makerName = e.dataTransfer.getData('name') 9 | const makerPkg = e.dataTransfer.getData('package') 10 | const makerType = e.dataTransfer.getData('type') as IResourceMakerType 11 | if (!makerName || !makerPkg || !makerType) { 12 | return 13 | } 14 | return { 15 | name: makerName, 16 | package: makerPkg, 17 | type: makerType 18 | }; 19 | } -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/utils/isTabTarget.ts: -------------------------------------------------------------------------------- 1 | interface TabDragEvent extends DragEvent { 2 | target: Target 3 | } 4 | 5 | interface Target extends HTMLDivElement { 6 | dataset: Dataset 7 | } 8 | 9 | interface Dataset extends DOMStringMap { 10 | readonly uid: string 11 | readonly luid: string 12 | } 13 | 14 | 15 | export default function isTabTarget(e: DragEvent): e is TabDragEvent { 16 | if (!(e.target instanceof HTMLDivElement)) { 17 | return false; 18 | } 19 | if (!e?.target?.dataset?.uid || !e?.target?.dataset?.luid) { 20 | return false; 21 | } 22 | return true; 23 | } -------------------------------------------------------------------------------- /src/types/IPanelMaker.ts: -------------------------------------------------------------------------------- 1 | import IPanelConfig from "./IPanelConfig"; 2 | import IResourceMakerBase from "./IResourceMakerBase"; 3 | import ISplitLayoutPaneName from "./ISplitLayoutPaneName"; 4 | import ICookState from "./ICookState"; 5 | import { Component } from "vue"; 6 | 7 | export default interface IPanelMaker extends IResourceMakerBase { 8 | readonly type: "panel", 9 | defaultSplitLayoutPaneName: ISplitLayoutPaneName, 10 | makeTitle?: (cookState: ICookState, panelConfig: IPanelConfig) => string, 11 | onClose?: (cookState: ICookState, panelConfig: IPanelConfig) => void 12 | onOpen?: (cookState: ICookState, panelConfig: IPanelConfig) => void 13 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import commonConfig from "./vite.common.config" 4 | import path from "path" 5 | 6 | const config = defineConfig({ 7 | plugins: [ 8 | vue(), 9 | ], 10 | publicDir: false, 11 | build: { 12 | lib: { 13 | entry: path.resolve(__dirname, 'src/index.ts'), 14 | name: 'VueCook', 15 | fileName: (format) => `vue-cook.${format}.js` 16 | }, 17 | rollupOptions: { 18 | external: ['vue'], 19 | output: { 20 | globals: { 21 | vue: 'Vue' 22 | } 23 | } 24 | } 25 | } 26 | }) 27 | 28 | export default mergeConfig(config, commonConfig) 29 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/UseComponentRenderRenderFooWithRecursion.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/ComponentRender.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ slot }} 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/cook-editor/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/utils/defaultMakerList.ts: -------------------------------------------------------------------------------- 1 | import PageComponentTreeMaker from "@/built-in-resources/panels/page-component-tree" 2 | import PageEditorMaker from "@/built-in-resources/panels/page-editor" 3 | import ComponentEditorMaker from "@/built-in-resources/panels/component-editor" 4 | import ResourcePanelMaker from "@/built-in-resources/panels/resource-panel" 5 | import RootAppMaker from "@/built-in-resources/components/root-app" 6 | import IResourceMaker from "@/types/IResourceMaker"; 7 | 8 | const defaultMakerList: IResourceMaker[] = [ 9 | PageComponentTreeMaker, 10 | PageEditorMaker, 11 | ComponentEditorMaker, 12 | ResourcePanelMaker, 13 | RootAppMaker 14 | ] 15 | 16 | export default defaultMakerList 17 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /logo.svg 4 | heroAlt: Logo image 5 | heroText: VueCook 6 | tagline: 基于vue的低代码平台辅助工具 7 | actions: 8 | - text: 快速上手 9 | link: /guide/ 10 | type: primary 11 | features: 12 | - title: 💎 组件渲染DOM一致 13 | details: 将组件渲染成对应的DOM结构时没有添加任何的包裹div层 14 | - title: 📝 多页面同时编辑 15 | details: 支持同时编辑多个页面 16 | - title: 🧱 自定义组件 17 | details: 支持自定义低代码组件,或者将现有的组件低代码化 18 | - title: 🔗 自定义逻辑 19 | details: 支持自定义低代码逻辑,或者将现有的业务逻辑低代码化 20 | - title: ⚙️ 自定义交互面板 21 | details: 支持自定义的交互面板,如自定义编辑器 22 | - title: 😊 内置多个交互面板 23 | details: 内置了多个开箱即用的交互面板,如基础组件编辑器、页面编辑器等等 24 | footer: MIT Licensed | Copyright © 2021-present LiuWenXing and @VueCook contributors 25 | --- 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/api/built-in.md: -------------------------------------------------------------------------------- 1 | # 内置资源 2 | 3 | ## 内置组件 4 | ### `RootAppMaker` 5 | 根应用组件,所有的页面的根组件,它有一个默认的插槽`default`。在创建一个新页面的时候,它会被默认使用,它的代码很简单: 6 | 7 | @[code vue](../../src/built-in-resources/components/root-app/RootApp.vue) 8 | 9 | ## 内置面板 10 | ### `ResourcePanelMaker` 11 | 资源面板,显示编辑器中所有已经安装的资源。支持功能: 12 | - 资源的搜索 13 | - 按类型过滤资源 14 | - 按包名过滤资源 15 | ### `PageComponentTreeMaker` 16 | 页面组件树,显示编辑器中已有的页面和其包含的组件,支持功能: 17 | - 搜索页面和组件 18 | - 新建页面 19 | - 点击页面打开页面编辑器 20 | - 点击组件选中组件 21 | ### `ComponentEditorMaker` 22 | 基础组件编辑器,支持一些基础的组件编辑功能,有: 23 | - 组件名称编辑 24 | - 组件属性编辑 25 | - 组件插槽编辑 26 | - 组件事件编辑 27 | ### `PageEditorMaker` 28 | 页面编辑器,有以下功能: 29 | - 组件选取 30 | - 页面实时效果预览 31 | - 编辑撤销与恢复 32 | - 页面删除以及相关信息编辑、查看 33 | - 页面预览窗口的大小调整 34 | -------------------------------------------------------------------------------- /demos/pages/custom-panel-with-toggle.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | -------------------------------------------------------------------------------- /src/components/cook-player/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /rollup.dts.config.js: -------------------------------------------------------------------------------- 1 | import dts from "rollup-plugin-dts"; 2 | import alias from '@rollup/plugin-alias'; 3 | import path from "path" 4 | 5 | const dtsTemp = path.resolve(__dirname, '.local/dts-temp'); 6 | 7 | /**@type {import("rollup").RollupOptions} */ 8 | const config = { 9 | input: path.resolve(__dirname, dtsTemp, "./src/index.d.ts"), 10 | output: { 11 | file: path.resolve(__dirname, "./dist/index.d.ts"), 12 | format: "es" 13 | }, 14 | plugins: [ 15 | alias({ 16 | entries: { 17 | "@": path.resolve(__dirname, dtsTemp, './src'), 18 | "vue-cook": path.resolve(__dirname, dtsTemp, './src/index.d.ts'), 19 | } 20 | }), 21 | dts(), 22 | ], 23 | } 24 | export default config; -------------------------------------------------------------------------------- /demos/pages/custom-component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | -------------------------------------------------------------------------------- /src/utils/createCookPlayerState.ts: -------------------------------------------------------------------------------- 1 | import ICookPlayerState from '@/types/ICookPlayerState'; 2 | import { reactive } from 'vue'; 3 | import defaultMakerList from './defaultMakerList'; 4 | 5 | type ICookPlayerStateOptions = Partial> & { 6 | page: ICookPlayerState["page"] 7 | } 8 | 9 | export default function createCookPlayerState(state: ICookPlayerStateOptions): ICookPlayerState { 10 | const _state: ICookPlayerState = reactive({ 11 | type: "player", 12 | makerList: defaultMakerList, 13 | ...state 14 | }) 15 | // 触发maker install 16 | const makerList = state.makerList || defaultMakerList 17 | makerList.map(maker => { 18 | maker.install?.(_state) 19 | }) 20 | return reactive(_state); 21 | } -------------------------------------------------------------------------------- /src/hooks/useComponentPickerEnable.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from "@/types/ICookEditorState"; 2 | import { computed, reactive } from "vue"; 3 | 4 | const stateMap = reactive(new Map()) 5 | 6 | export default function useComponentPickerEnable(cookEditorState: ICookEditorState) { 7 | const enable = computed(() => { 8 | const pickerEnable = stateMap.get(cookEditorState) 9 | return Boolean(pickerEnable) 10 | }) 11 | const get = () => { 12 | return enable 13 | } 14 | 15 | const set = (componentPickerEnable: boolean) => { 16 | stateMap.set(cookEditorState, componentPickerEnable) 17 | } 18 | 19 | const toggle = () => { 20 | set(!enable.value) 21 | } 22 | return { 23 | get, 24 | set, 25 | toggle 26 | } 27 | } -------------------------------------------------------------------------------- /demos/pages/custom-panel.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | -------------------------------------------------------------------------------- /demos/pages/custom-component-with-props.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | -------------------------------------------------------------------------------- /demos/test-pkg/components/layout/index.ts: -------------------------------------------------------------------------------- 1 | // LayoutMaker.ts 2 | import { defineComponentMaker } from 'vue-cook' 3 | import Layout from "./Layout.vue"; 4 | export default defineComponentMaker({ 5 | name: "布局", 6 | pkg: "test-pkg", 7 | makePropOptions: () => ["row", "col"], 8 | makeSlotOptions: (cookState, componentConfig) => { 9 | const getSlotName = (m: number, n: number) => `${m}-${n}` 10 | const rowNumber = Number(componentConfig?.props?.row) || 1 11 | const colNumber = Number(componentConfig?.props?.col) || 1 12 | const slots = [] 13 | for (let m = 1; m <= rowNumber; m++) { 14 | for (let n = 1; n <= colNumber; n++) { 15 | slots.push(getSlotName(m, n)) 16 | } 17 | 18 | } 19 | return slots 20 | }, 21 | make: () => Layout 22 | }) -------------------------------------------------------------------------------- /src/hooks/useSlotOptions.ts: -------------------------------------------------------------------------------- 1 | import { computed, Ref } from "vue"; 2 | import useComponentMaker from "./useComponentMaker"; 3 | import IComponentConfig from '@/types/IComponentConfig'; 4 | import ICookEditorState from '@/types/ICookEditorState'; 5 | 6 | export default function useSlotOptions(cookEditorState: ICookEditorState, componentConfig: Ref) { 7 | return computed(() => { 8 | const configValue = componentConfig.value; 9 | if (!configValue) { 10 | return [] 11 | }; 12 | const maker = useComponentMaker(cookEditorState, componentConfig.value?.makerName, componentConfig.value?.makerPkg).value 13 | if (!maker) { 14 | return []; 15 | } 16 | return maker?.makeSlotOptions?.(cookEditorState, configValue) || [] 17 | }) 18 | } -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/PanelRender.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | -------------------------------------------------------------------------------- /src/svgs/panel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /vite.common.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import svgReactiveLoader from "vite-plugin-vue-svg-reactive-loader" 3 | import path from "path" 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | svgReactiveLoader() 8 | ], 9 | resolve: { 10 | alias: { 11 | "@": path.resolve(__dirname, 'src'), 12 | "vue-cook": path.resolve(__dirname, 'src/index.ts'), 13 | } 14 | }, 15 | optimizeDeps: { 16 | include: [ 17 | "@daybrush/drag", 18 | "@scena/ruler", 19 | "@vicons/antd", 20 | "@vicons/fluent", 21 | "@vicons/ionicons5", 22 | "@vicons/material", 23 | "@vicons/tabler", 24 | "@vueuse/core", 25 | "lodash-es", 26 | "naive-ui", 27 | "uuid", 28 | ] 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /docs/.vuepress/components/PkgDepList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ pkg.pkgName }} 5 | 6 | 7 | 8 | 30 | -------------------------------------------------------------------------------- /src/utils/createCookEditorState.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from '@/types/ICookEditorState'; 2 | import { reactive } from 'vue'; 3 | import defaultMakerList from './defaultMakerList'; 4 | import defaultSplitLayout from './defaultSplitLayout'; 5 | 6 | type ICookEditorStateOptions = Partial> 7 | 8 | export default function createCookEditorState(state?: Partial) { 9 | state = state || {} 10 | const _state: ICookEditorState = reactive({ 11 | type: "editor", 12 | makerList: defaultMakerList, 13 | pages: [], 14 | layout: defaultSplitLayout, 15 | ...state 16 | }) 17 | // 触发maker install 18 | const makerList = state.makerList || defaultMakerList 19 | makerList.map(maker => { 20 | maker.install?.(_state) 21 | }) 22 | return _state 23 | } -------------------------------------------------------------------------------- /src/utils/defaultSplitLayout.ts: -------------------------------------------------------------------------------- 1 | import makeDefaultPanelConfig from './makeDefaultPanelConfig'; 2 | import ISplitLayout from '@/types/ISplitLayout'; 3 | import PageComponentTreeMaker from "@/built-in-resources/panels/page-component-tree" 4 | import ComponentEditorMaker from "@/built-in-resources/panels/component-editor" 5 | import ResourcePanelMaker from "@/built-in-resources/panels/resource-panel" 6 | 7 | const resourcePanelConfig = makeDefaultPanelConfig(ResourcePanelMaker) 8 | resourcePanelConfig.alwaysOpen = true 9 | const defaultSplitLayout: ISplitLayout = { 10 | "left": [ 11 | makeDefaultPanelConfig(PageComponentTreeMaker) 12 | ], 13 | "center": [], 14 | "bottom": [ 15 | resourcePanelConfig 16 | ], 17 | "right": [ 18 | makeDefaultPanelConfig(ComponentEditorMaker) 19 | ] 20 | } 21 | export default defaultSplitLayout 22 | -------------------------------------------------------------------------------- /demos/pages/custom-panel-with-color-input.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/ComponentRendeWithRecursion.vue: -------------------------------------------------------------------------------- 1 | // ComponentRendeWithRecursion.vue 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/svgs/logic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/cook-editor/insider-player/CookPlayerWrapper.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/component.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/component-overlay/handleClick.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from '@/types/ICookEditorState'; 2 | import IComponentOverlay from '@/types/IComponentOverlay'; 3 | import useComponentSelected from '@/hooks/useComponentSelected'; 4 | const handleClick = (cookEditorState: ICookEditorState, overlay: IComponentOverlay, event: MouseEvent) => { 5 | event.stopPropagation() 6 | if ( 7 | useComponentSelected(cookEditorState).get().value?.page.uid === overlay.pageUid && 8 | useComponentSelected(cookEditorState).get().value?.component.uid === overlay.configUid 9 | ) { 10 | useComponentSelected(cookEditorState).set() 11 | } else { 12 | useComponentSelected(cookEditorState).set({ 13 | pageUid: overlay.pageUid, 14 | componentUid: overlay.configUid 15 | }) 16 | } 17 | } 18 | 19 | export default handleClick 20 | -------------------------------------------------------------------------------- /demos/pages/custom-logic.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-layout-component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-component-with-slots.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-logic-with-props.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/test-pkg/panels/color-panel/ColorPanel.vue: -------------------------------------------------------------------------------- 1 | // ColorPanel.vue 2 | 3 | 4 | 5 | 6 | 请选择组件 7 | 8 | -------------------------------------------------------------------------------- /demos/pages/custom-logic-with-params.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-component-with-dom-events.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-component-with-multi-slots.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-logic-with-events-props.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-component-with-custom-events.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-panel-with-resource-panel.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /demos/pages/custom-fragment-component-with-dom-event.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/pages/custom-component-with-event-receive-param.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /demos/test-pkg/logic/fetch-with-params/fetch.ts: -------------------------------------------------------------------------------- 1 | // fetch.ts 2 | interface IDataItem { 3 | name: string, 4 | age: number 5 | } 6 | const data: IDataItem[] = [ 7 | { name: "刘一", age: 18 }, 8 | { name: "陈二", age: 19 }, 9 | { name: "张三", age: 20 }, 10 | { name: "李四", age: 21 }, 11 | { name: "王五", age: 22 }, 12 | { name: "赵六", age: 23 }, 13 | { name: "孙七", age: 24 }, 14 | { name: "周八", age: 25 }, 15 | { name: "吴九", age: 26 }, 16 | { name: "郑十", age: 27 }, 17 | ] 18 | export default async function fetch(maxAge: number = Infinity, minAge: number = 0) { 19 | return new Promise((resolve, reject) => { 20 | const _data = data.filter(e => { 21 | if (e.age > maxAge) { 22 | return false 23 | } 24 | if (e.age < minAge) { 25 | return false 26 | } 27 | return true 28 | }) 29 | setTimeout(() => { 30 | resolve(_data) 31 | }, 1000) 32 | }) 33 | } -------------------------------------------------------------------------------- /demos/test-pkg/logic/fetch-with-props/fetch.ts: -------------------------------------------------------------------------------- 1 | // fetch.ts 2 | interface IDataItem { 3 | name: string, 4 | age: number 5 | } 6 | const data: IDataItem[] = [ 7 | { name: "刘一", age: 18 }, 8 | { name: "陈二", age: 19 }, 9 | { name: "张三", age: 20 }, 10 | { name: "李四", age: 21 }, 11 | { name: "王五", age: 22 }, 12 | { name: "赵六", age: 23 }, 13 | { name: "孙七", age: 24 }, 14 | { name: "周八", age: 25 }, 15 | { name: "吴九", age: 26 }, 16 | { name: "郑十", age: 27 }, 17 | ] 18 | export default async function fetch(maxAge: number = Infinity, minAge: number = 0) { 19 | return new Promise((resolve, reject) => { 20 | const _data = data.filter(e => { 21 | if (e.age > maxAge) { 22 | return false 23 | } 24 | if (e.age < minAge) { 25 | return false 26 | } 27 | return true 28 | }) 29 | setTimeout(() => { 30 | resolve(_data) 31 | }, 1000) 32 | }) 33 | } -------------------------------------------------------------------------------- /docs/guide/why.md: -------------------------------------------------------------------------------- 1 | # 为什么选VueCook 2 | 3 | 4 | 一个低代码平台往往都会有一个用来可视化搭建的界面,通常这个界面会提供`界面编排`和`逻辑编排`两种能力。其中,`界面编排`表现为通过拖拽预制好的`组件`生成页面,通过`组件编辑器`修改组件的属性。`逻辑编排`则是将业务逻辑变成一个个函数,然后通过控制函数的`出参`和`入参`来达到编排逻辑的能力。除此之外,搭建界面往往还会提供一些额外的界面,例如`页面树`、`组件列表`等来辅助搭建。 5 | 6 | 7 | 当我们去开发一个低代码平台的时候,往往需要对上述能力进行重新的抽象和组织,而每个平台的开发者对这些能力的抽象和组织又不尽相同,这造成了每个低代码平台之间的资源往往都是不互通的。一个平台的组件没办法用到另一个平台,现有的一些组件库或者业务组件也没办法直接添加到平台上。组件的属性编辑只能是平台开发者提供的一种固定的编辑方式,想要自定义编辑器往往非常困难。 8 | 9 | 10 | VueCook 作为一个基于vue的低代码平台辅助工具,它试图对上述能力定义一个规范,用来保证一个低代码平台的`可维护性`和`可扩展性`。同时让开发者在开发一个可视化搭建平台的时候,减少一些时间在定义组件的开发规范上,让开发者更容易的将已有的Vue组件和业务逻辑低代码化,并且可以让开发者自己来控制组件的属性编辑方式。 11 | 12 | 13 | 为了上述目标,VueCook将上述能力抽象成了三个东西,`组件`、`逻辑`和`交互面板`,这些在VueCook当中统称为`资源`。其中: 14 | 15 | 16 | - `组件`:一种预制好的UI控件,支持一些属性的配置,如props、slots、events等。VueCook会将这些配置通过Vue中的动态组件进行渲染,并且渲染的时候不会为组件添加任何额外的包裹层,以保证组件DOM结构在渲染时与开发组件的时候是一致的。 17 | - `逻辑`:一个定义好入参的函数,它用来表达某种具体的业务逻辑。通过配置它的入参,将其配置放入到某一个组件的属性中,可以把某种业务逻辑和组件结合起来。 18 | - `交互面板`:一个自定义的交互面板,用来扩展搭建平台的编排能力,利用它可以扩展自定义的编辑器,自定义的信息展示面板等等,它的本质是对`组件`和`逻辑`的配置进行增删改查,以达到扩展编排能力的目的。 -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | # 每当创建tag时触发发布 3 | push: 4 | tags: 5 | - "v*" 6 | # 手动触发发布 7 | workflow_dispatch: 8 | 9 | name: publish 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: "14" 18 | registry-url: https://registry.npmjs.org/ 19 | - run: yarn 20 | - run: yarn build 21 | - run: npm publish 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 24 | 25 | github-release: 26 | needs: publish 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - uses: actions/setup-node@v1 31 | with: 32 | node-version: "14" 33 | registry-url: https://registry.npmjs.org/ 34 | - run: npm i -g github-release-from-changelog 35 | - run: github-release-from-changelog 36 | env: 37 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 38 | -------------------------------------------------------------------------------- /demos/pages/custom-component-with-send-extra-param-to-event.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /src/svgs/page-size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demos/test-pkg/panels/logic-dragger-panel/LogicDraggerPanel.vue: -------------------------------------------------------------------------------- 1 | // ComponentDraggerPanel.vue 2 | 3 | 4 | fetch:拖拽逻辑到此处添加 5 | 6 | 请选择组件 7 | 8 | -------------------------------------------------------------------------------- /demos/test-pkg/panels/toggle-panel/TogglePanel.vue: -------------------------------------------------------------------------------- 1 | 2 | 打开面板 3 | 关闭自己 4 | 5 | -------------------------------------------------------------------------------- /demos/pages/custom-panel-with-logic-dragger.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /demos/pages/custom-panel-with-component-dragger.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /src/utils/layoutRemoveTab.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from '@/types/ICookEditorState'; 2 | import IPanelConfig from '@/types/IPanelConfig'; 3 | import usePanelMaker from '@/hooks/usePanelMaker'; 4 | import ISplitLayoutPaneName from '@/types/ISplitLayoutPaneName'; 5 | 6 | function remove(cookEditorState: ICookEditorState, splitLayoutPaneName: ISplitLayoutPaneName, panelConfig: IPanelConfig,) { 7 | const list = cookEditorState.layout[splitLayoutPaneName] 8 | const index = list.findIndex(e => e.uid === panelConfig.uid) 9 | if (index > -1) { 10 | list.splice(index, 1) 11 | const maker = usePanelMaker(cookEditorState, panelConfig.makerName, panelConfig.makerPkg) 12 | maker?.value?.onClose?.(cookEditorState, panelConfig) 13 | } 14 | } 15 | 16 | export default function layoutRemoveTab(cookEditorState: ICookEditorState, panelConfig: IPanelConfig) { 17 | remove(cookEditorState, "left", panelConfig) 18 | remove(cookEditorState, "center", panelConfig) 19 | remove(cookEditorState, "bottom", panelConfig) 20 | remove(cookEditorState, "right", panelConfig) 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 柳问星 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 | -------------------------------------------------------------------------------- /docs/.vuepress/utils/addDemosRouteToRoutes.ts: -------------------------------------------------------------------------------- 1 | import fetchPages from '@/../demos/utils/fecthPages'; 2 | import TableMaker from "@/../demos/test-pkg/components/table-with-fetch" 3 | import FetchMaker from "@/../demos/test-pkg/logic/fetch-with-props" 4 | import { CookPlayer, createCookPlayerState, defaultMakerList, IPage } from "vue-cook" 5 | import useRoutes from '../hooks/useRoutes'; 6 | 7 | export default async function addDemosRouteToApp() { 8 | const pagesString = await fetchPages() 9 | const pages = JSON.parse(pagesString) as IPage[] 10 | pages.forEach(page => { 11 | const cookPlayerState = createCookPlayerState({ 12 | page, 13 | makerList: [ 14 | ...defaultMakerList, 15 | TableMaker, // import TableMaker from "./TableMaker.ts" 16 | FetchMaker, // import FetchMaker from "./FetchMaker.ts" 17 | ] 18 | }) 19 | useRoutes().value.push({ 20 | path: page.path, 21 | component: CookPlayer, 22 | props: { 23 | state: cookPlayerState 24 | } 25 | }) 26 | }) 27 | } -------------------------------------------------------------------------------- /src/hooks/usePageEditingUidList.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from "@/types/ICookEditorState"; 2 | import { computed, reactive } from 'vue'; 3 | 4 | const stateMap = reactive(new Map()) 5 | 6 | export default function usePageEditingUidList(cookEditorState: ICookEditorState) { 7 | const list = computed(() => { 8 | const _list = stateMap.get(cookEditorState) || [] 9 | return [..._list] 10 | }) 11 | const add = (pageUid: string) => { 12 | let _list = stateMap.get(cookEditorState) 13 | if (!_list) { 14 | _list = [] 15 | stateMap.set(cookEditorState, _list) 16 | } 17 | _list.push(pageUid) 18 | } 19 | const remove = (pageUid?: string) => { 20 | if (pageUid) { 21 | const list = stateMap.get(cookEditorState) 22 | if (list) { 23 | const _list = list.filter(d => d !== pageUid) 24 | stateMap.set(cookEditorState, _list) 25 | } 26 | } 27 | } 28 | return { 29 | list, 30 | add, 31 | remove 32 | } 33 | } -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/PageCookPanel.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /src/components/logic-dragger/handleDrop.ts: -------------------------------------------------------------------------------- 1 | import useLogicMaker from '@/hooks/useLogicMaker'; 2 | import makeDefaultLogicConfig from '@/utils/makeDefaultLogicConfig'; 3 | import getMakerDataFromDragEvent from '@/utils/getMakerDataFromDragEvent'; 4 | import ICookEditorState from '@/types/ICookEditorState'; 5 | import ILogicConfig from '@/types/ILogicConfig'; 6 | 7 | export type ICallback = (logicConfig: ILogicConfig) => void 8 | 9 | const handleDrop = (cookEditorState: ICookEditorState, e: DragEvent, callBack: ICallback) => { 10 | const data = getMakerDataFromDragEvent(e); 11 | if (!data) { 12 | return 13 | } 14 | e.preventDefault() 15 | e.stopPropagation() 16 | const makerName = data.name 17 | const makerPkg = data.package 18 | const maker = useLogicMaker(cookEditorState, makerName, makerPkg) 19 | if (!maker.value) { 20 | return; 21 | } 22 | const logicConfig = makeDefaultLogicConfig(maker.value); 23 | callBack(logicConfig) 24 | if (e.target && e.target instanceof HTMLElement) { 25 | e.target.classList.remove('dragenter') 26 | } 27 | } 28 | 29 | export default handleDrop 30 | -------------------------------------------------------------------------------- /src/utils/findComponentConfig.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from "@/types/IComponentConfig"; 2 | 3 | const findComponentConfig = (parent: IComponentConfig, componentUid: string): IComponentConfig | undefined => { 4 | let configFound: IComponentConfig | undefined; 5 | if (parent.uid === componentUid) { 6 | configFound = parent 7 | } else { 8 | const slots = parent?.slots; 9 | if (slots) { 10 | const keys = Object.keys(slots) 11 | for (let i = 0; i < keys.length; i++) { 12 | const key = keys[i]; 13 | const configList = slots[key]; 14 | for (let j = 0; j < configList.length; j++) { 15 | const config = configList[j]; 16 | configFound = findComponentConfig(config, componentUid) 17 | if (configFound) { 18 | return configFound; 19 | } 20 | } 21 | if (configFound) { 22 | break; 23 | } 24 | } 25 | } 26 | } 27 | return configFound; 28 | } 29 | 30 | export default findComponentConfig -------------------------------------------------------------------------------- /demos/pages/use-data-in-cook-player.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 26 | -------------------------------------------------------------------------------- /.vscode/vue.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | // Place your vue-cook 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 3 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 4 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 5 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 6 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 7 | // Placeholders with the same ids are connected. 8 | // Example: 9 | // "Print to console": { 10 | // "scope": "javascript,typescript", 11 | // "prefix": "log", 12 | // "body": [ 13 | // "console.log('$1');", 14 | // "$2" 15 | // ], 16 | // "description": "Log output to console" 17 | // } 18 | "vue setup ts less": { 19 | "prefix": "vstl", 20 | "body": [ 21 | "", 22 | " ", 23 | "", 24 | "", 27 | "", 29 | ], 30 | "description": "vue3 setup ts less" 31 | } 32 | } -------------------------------------------------------------------------------- /src/built-in-resources/panels/component-editor/logic-editor/LogicEditorItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/component-dragger/handleDrop.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from '@/types/IComponentConfig'; 2 | import useComponentMaker from '@/hooks/useComponentMaker'; 3 | import makeDefaultComponentConfig from '@/utils/makeDefaultComponentConfig'; 4 | import getMakerDataFromDragEvent from '@/utils/getMakerDataFromDragEvent'; 5 | import ICookEditorState from '@/types/ICookEditorState'; 6 | 7 | export type ICallback = (componentConfig: IComponentConfig) => void 8 | 9 | const handleDrop = (cookEditorState: ICookEditorState, e: DragEvent, callBack: ICallback) => { 10 | const data = getMakerDataFromDragEvent(e); 11 | if (!data) { 12 | return 13 | } 14 | e.preventDefault() 15 | e.stopPropagation() 16 | const makerName = data.name 17 | const makerPkg = data.package 18 | const maker = useComponentMaker(cookEditorState, makerName, makerPkg) 19 | if (!maker.value) { 20 | return; 21 | } 22 | const componentConfig = makeDefaultComponentConfig(maker.value); 23 | callBack(componentConfig) 24 | if (e.target && e.target instanceof HTMLElement) { 25 | e.target.classList.remove('dragenter') 26 | } 27 | } 28 | 29 | export default handleDrop 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VueCook(WIP) 2 | 一个基于vue的低代码平台辅助工具,让你的vue组件更容易低代码化。 3 | 4 | # 特性 5 | - 💎 组件渲染DOM一致: 将组件渲染成对应的DOM结构时没有添加任何的包裹div层 6 | - 📝 多页面同时编辑: 支持同时编辑多个页面 7 | - 🧱 自定义组件: 支持自定义低代码组件,或者将现有的组件低代码化 8 | - 🔗 自定义逻辑: 支持自定义低代码逻辑,或者将现有的业务逻辑低代码化 9 | - ⚙️ 自定义交互面板: 支持自定义的交互面板,如自定义编辑器 10 | - 😊 内置多个交互面板: 内置了多个开箱即用的交互面板,如基础组件编辑器、页面编辑器等等 11 | # 文档 12 | 13 | 官方文档在这里:https://liuwenxing1996.github.io/vue-cook/ 14 | 15 | # 安装 16 | 17 | ```bash 18 | npm install vue-cook 19 | ``` 20 | 或者 21 | 22 | ```bash 23 | yarn add vue-cook 24 | ``` 25 | 26 | # 使用 27 | 28 | ```js 29 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 30 | import { CookEditor, createCookEditorState} from "vue-cook" 31 | import "vue-cook/dist/style.css" 32 | 33 | const cookEditorState = createCookEditorState() // 创建编辑器全局状态 34 | const routes = [ 35 | { 36 | path: '/vue-cook', // 自定义的地址 37 | component: CookEditor, 38 | props: route => ({ 39 | state: cookEditorState, // 传入状态 40 | preview: route.query.preview // 是否预览 41 | }) 42 | } 43 | ] 44 | const router = createRouter({ 45 | history: createWebHistory(), // 也可以使用hash模式 46 | routes, 47 | }) 48 | 49 | export default router 50 | 51 | ``` -------------------------------------------------------------------------------- /src/hooks/useComponentSelected.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from "@/types/ICookEditorState"; 2 | import { computed, reactive } from "vue"; 3 | import findComponentConfig from "@/utils/findComponentConfig"; 4 | import IComponentSelected from "@/types/IComponentSelected"; 5 | 6 | const stateMap = reactive(new Map()) 7 | 8 | export default function useComponentSelected(cookEditorState: ICookEditorState) { 9 | const config = computed(() => { 10 | const { pageUid, componentUid } = stateMap.get(cookEditorState) || {} 11 | const page = cookEditorState.pages.find(e => e.uid === pageUid) 12 | if (page && componentUid) { 13 | const component = findComponentConfig(page.component, componentUid) 14 | if (component) { 15 | return { 16 | page, 17 | component 18 | } 19 | } 20 | } 21 | }) 22 | const get = () => { 23 | return config 24 | } 25 | const set = (componetSelected?: IComponentSelected) => { 26 | stateMap.set(cookEditorState, componetSelected) 27 | } 28 | return { 29 | get, 30 | set 31 | } 32 | } -------------------------------------------------------------------------------- /demos/test-pkg/components/table-with-fetch/Table.vue: -------------------------------------------------------------------------------- 1 | // Table.vue 2 | 3 | 4 | {{ fetch ? "点我获取数据" : "没有配置fetch属性" }} 5 | 6 | 7 | 姓名 8 | 年龄 9 | 10 | 11 | {{ n.name }} 12 | {{ n.age }} 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demos/utils/fecthPage.ts: -------------------------------------------------------------------------------- 1 | import { IPage } from "vue-cook" 2 | 3 | const pages: IPage = { 4 | "name": "新增页面", 5 | "uid": "8630b4fc-7e22-4bc8-993a-0c1d36e1c2a5", 6 | "path": "/8630b4fc-7e22-4bc8-993a-0c1d36e1c2a5", 7 | "component": { 8 | "uid": "807dd687-c54f-4223-837a-706f918c4205", 9 | "name": "主应用", 10 | "makerType": "component", 11 | "makerName": "主应用", 12 | "makerPkg": "vue-cook", 13 | "slots": { 14 | "default": [{ 15 | "uid": "54376c75-f1db-4ef5-9f21-ac673de70cf9", 16 | "name": "表格", 17 | "makerType": "component", 18 | "makerName": "表格", 19 | "makerPkg": "test-pkg", 20 | "props": { 21 | "fetch": "{\"uid\":\"571a0a02-0dbe-439b-8b5b-a448e3a183bd\",\"name\":\"fetch\",\"makerType\":\"logic\",\"makerName\":\"fetch\",\"makerPkg\":\"test-pkg\",\"props\":{\"maxAge\":\"24\",\"minAge\":\"20\"}}" 22 | } 23 | }] 24 | } 25 | } 26 | } 27 | 28 | export default async function fetchPage(): Promise { 29 | return new Promise((resolve, reject) => { 30 | setTimeout(() => { 31 | resolve(JSON.stringify(pages)) 32 | }, 10) 33 | }) 34 | } -------------------------------------------------------------------------------- /demos/utils/fecthPages.ts: -------------------------------------------------------------------------------- 1 | import { IPage } from "vue-cook" 2 | 3 | const pages: IPage[] = [{ 4 | "name": "新增页面", 5 | "uid": "8630b4fc-7e22-4bc8-993a-0c1d36e1c2a5", 6 | "path": "/8630b4fc-7e22-4bc8-993a-0c1d36e1c2a5", 7 | "component": { 8 | "uid": "807dd687-c54f-4223-837a-706f918c4205", 9 | "name": "主应用", 10 | "makerType": "component", 11 | "makerName": "主应用", 12 | "makerPkg": "vue-cook", 13 | "slots": { 14 | "default": [{ 15 | "uid": "54376c75-f1db-4ef5-9f21-ac673de70cf9", 16 | "name": "表格", 17 | "makerType": "component", 18 | "makerName": "表格", 19 | "makerPkg": "test-pkg", 20 | "props": { 21 | "fetch": "{\"uid\":\"571a0a02-0dbe-439b-8b5b-a448e3a183bd\",\"name\":\"fetch\",\"makerType\":\"logic\",\"makerName\":\"fetch\",\"makerPkg\":\"test-pkg\",\"props\":{\"maxAge\":\"24\",\"minAge\":\"20\"}}" 22 | } 23 | }] 24 | } 25 | } 26 | }] 27 | 28 | export default async function fetchPages(): Promise { 29 | return new Promise((resolve, reject) => { 30 | setTimeout(() => { 31 | resolve(JSON.stringify(pages)) 32 | }, 10) 33 | }) 34 | } -------------------------------------------------------------------------------- /src/hooks/useComponentFocused.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from "@/types/ICookEditorState"; 2 | import findComponentConfig from "@/utils/findComponentConfig"; 3 | import { computed, reactive } from "vue"; 4 | 5 | interface IComponentFoused { 6 | componentUid: string, 7 | pageUid: string 8 | } 9 | 10 | const stateMap = reactive(new Map()) 11 | 12 | export default function useComponentFocused(cookEditorState: ICookEditorState) { 13 | const config = computed(() => { 14 | const { pageUid, componentUid } = stateMap.get(cookEditorState) || {} 15 | const page = cookEditorState.pages.find(e => e.uid === pageUid) 16 | if (page && componentUid) { 17 | const component = findComponentConfig(page.component, componentUid) 18 | if (component) { 19 | return { 20 | page, 21 | component 22 | } 23 | } 24 | } 25 | }) 26 | const get = () => { 27 | return config 28 | } 29 | const set = (componetFoused?: IComponentFoused) => { 30 | stateMap.set(cookEditorState, componetFoused) 31 | } 32 | return { 33 | get, 34 | set 35 | } 36 | } -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { 4 | "type": "feat", 5 | "section": "Features" 6 | }, 7 | { 8 | "type": "feature", 9 | "section": "Features" 10 | }, 11 | { 12 | "type": "fix", 13 | "section": "Bug Fixes" 14 | }, 15 | { 16 | "type": "perf", 17 | "section": "Performance Improvements" 18 | }, 19 | { 20 | "type": "revert", 21 | "section": "Reverts" 22 | }, 23 | { 24 | "type": "docs", 25 | "section": "Documentation" 26 | }, 27 | { 28 | "type": "style", 29 | "section": "Styles" 30 | }, 31 | { 32 | "type": "chore", 33 | "section": "Miscellaneous Chores" 34 | }, 35 | { 36 | "type": "refactor", 37 | "section": "Code Refactoring" 38 | }, 39 | { 40 | "type": "test", 41 | "section": "Tests" 42 | }, 43 | { 44 | "type": "build", 45 | "section": "Build System" 46 | }, 47 | { 48 | "type": "ci", 49 | "section": "Continuous Integration" 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /src/components/cook-editor/insider-player/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 没有找到id为{{ preview }}的页面 6 | 7 | -------------------------------------------------------------------------------- /src/utils/exportCookEditorData.ts: -------------------------------------------------------------------------------- 1 | import IPage from '@/types/IPage'; 2 | import ICookEditorExportData from '@/types/ICookEditorExportData'; 3 | import ICookEditorState from '@/types/ICookEditorState'; 4 | import getCookEditorExportDataUniqTag from './getCookEditorExportDataUniqTag'; 5 | 6 | export default function exportCookEditorData(state: ICookEditorState, pageUid: string) { 7 | const data: ICookEditorExportData = { 8 | getPage: () => { 9 | const page = state.pages.find(e => e.uid === pageUid) 10 | if (page) { 11 | const jsonString = JSON.stringify(page); 12 | return JSON.parse(jsonString) as IPage; 13 | } 14 | }, 15 | setPage: (page) => { 16 | const index = state.pages.findIndex(e => e.uid === page.uid) 17 | if (index > -1) { 18 | const jsonString = JSON.stringify(page); 19 | const oldJsonString = JSON.stringify(state.pages[index]) 20 | if (jsonString !== oldJsonString) { 21 | const _page = JSON.parse(jsonString) as IPage; 22 | state.pages.splice(index, 1, _page) 23 | } 24 | } 25 | } 26 | } 27 | // @ts-ignore 28 | window[getCookEditorExportDataUniqTag(pageUid)] = data 29 | } -------------------------------------------------------------------------------- /demos/pages/use-data-in-cook-editor.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 29 | -------------------------------------------------------------------------------- /src/svgs/url.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/event-dragger/handleDrop.ts: -------------------------------------------------------------------------------- 1 | import IComponentConfig from '@/types/IComponentConfig'; 2 | import useLogicMaker from '@/hooks/useLogicMaker'; 3 | import makeDefaultLogicConfig from '@/utils/makeDefaultLogicConfig'; 4 | import getMakerDataFromDragEvent from '@/utils/getMakerDataFromDragEvent'; 5 | import ICookEditorState from '@/types/ICookEditorState'; 6 | 7 | const handleDrop = (cookEditorState: ICookEditorState, eventName: string, componentConfig: IComponentConfig | undefined, e: DragEvent,) => { 8 | 9 | if (!componentConfig) { 10 | return 11 | } 12 | const data = getMakerDataFromDragEvent(e); 13 | if (!data) { 14 | return 15 | } 16 | e.preventDefault() 17 | e.stopPropagation() 18 | const makerName = data.name 19 | const makerPkg = data.package 20 | const maker = useLogicMaker(cookEditorState, makerName, makerPkg) 21 | if (!maker.value) { 22 | return; 23 | } 24 | const logicConfig = makeDefaultLogicConfig(maker.value); 25 | componentConfig.events = componentConfig.events || {} 26 | componentConfig.events[eventName] = componentConfig.events[eventName] || [] 27 | componentConfig.events[eventName].push(logicConfig) 28 | if (e.target && e.target instanceof HTMLElement) { 29 | e.target.classList.remove('dragenter') 30 | } 31 | } 32 | 33 | export default handleDrop 34 | -------------------------------------------------------------------------------- /demos/test-pkg/components/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | // Layout.vue 2 | 3 | 4 | 5 | 6 | {{ m }}-{{ n }} 7 | 8 | 9 | 10 | 11 | 22 | -------------------------------------------------------------------------------- /demos/test-pkg/components/table-with-fetch-params/Table.vue: -------------------------------------------------------------------------------- 1 | // Table.vue 2 | 3 | 4 | {{ fetch ? "点我获取数据" : "没有配置fetch属性" }} 5 | 6 | 7 | 姓名 8 | 年龄 9 | 10 | 11 | {{ n.name }} 12 | {{ n.age }} 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/.vuepress/utils/addDemosRouteToApp.ts: -------------------------------------------------------------------------------- 1 | import fetchPages from '@/../demos/utils/fecthPages'; 2 | import TableMaker from "@/../demos/test-pkg/components/table-with-fetch" 3 | import FetchMaker from "@/../demos/test-pkg/logic/fetch-with-props" 4 | import { Router } from "vue-router"; 5 | import { CookPlayer, createCookPlayerState, defaultMakerList, IPage } from "vue-cook" 6 | 7 | export default function addDemosRouteToApp(router: Router) { 8 | let redirectTag = false; 9 | router.beforeEach(async (to, from) => { 10 | if (!redirectTag) { 11 | const pagesString = await fetchPages() 12 | const pages = JSON.parse(pagesString) as IPage[] 13 | pages.forEach(page => { 14 | const cookPlayerState = createCookPlayerState({ 15 | page, 16 | makerList: [ 17 | ...defaultMakerList, 18 | TableMaker, // import TableMaker from "./TableMaker.ts" 19 | FetchMaker, // import FetchMaker from "./FetchMaker.ts" 20 | ] 21 | }) 22 | router.addRoute({ 23 | path: page.path, 24 | component: CookPlayer, 25 | props: { 26 | state: cookPlayerState 27 | } 28 | }) 29 | }) 30 | redirectTag = true 31 | return to.fullPath 32 | } 33 | redirectTag = false 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/component-editor/props-editor/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 无 6 | 7 | -------------------------------------------------------------------------------- /src/components/event-dragger/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | {{ eventName }} 10 | 11 | 12 | 31 | -------------------------------------------------------------------------------- /src/components/logic-dragger/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 拖拽逻辑到此处添加 10 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /src/components/component-dragger/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 拖拽组件到此处添加 10 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/resource-panel/ResuorceMaker.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ maker.name }} 12 | {{ maker.pkg }} 13 | 14 | 15 | 16 | 30 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/component-editor/slots-editor/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 无 6 | 7 | 21 | -------------------------------------------------------------------------------- /docs/advanced/component-render-principle.md: -------------------------------------------------------------------------------- 1 | # 组件渲染机制 2 | 3 | Vue中有一个内置组件``,我们使用它就可以达到动态渲染组件的目的。假设已经有了这样一个组件,它可以接收一个名为`msg`的属性,一个名为`btnCilck`的事件,一个名为`btnText`的插槽 4 | 5 | @[code vue](../.vuepress/components/demos/Foo.vue) 6 | 7 | 正常的使用方式如下 8 | 9 | @[code vue](../.vuepress/components/demos/UseFoo.vue) 10 | 11 | 效果如下 12 | 13 | 14 | 15 | 使用``来选渲染它 16 | 17 | @[code vue](../.vuepress/components/demos/UseComponentRenderFoo.vue) 18 | 19 | 效果如下 20 | 21 | 22 | 23 | 虽然上述效果和正常使用组件得效果一致了,但是这样还不行,因为上述方式在传入属性、事件以及插槽的时候,直接指定了属性名称、事件名称和插槽名称。我们需要找到一个可以动态绑定这些名称的方式,恰巧的是,Vue天然支持了这种方式,我们改造一下之前代码,如下 24 | 25 | @[code vue](../.vuepress/components/demos/UseComponentRenderFooWithDynamicName.vue) 26 | 27 | 效果如下 28 | 29 | 30 | 31 | 现在我们已经可以动态绑定组件的属性、事件以及插槽了,这时候我们可以很容易的写出一个`ComponentRender`,如下 32 | 33 | @[code vue](../.vuepress/components/demos/ComponentRender.vue) 34 | 35 | 用它渲染一个使用`Foo`的示例 36 | 37 | @[code vue](../.vuepress/components/demos/UseComponentRenderRenderFoo.vue) 38 | 39 | 效果如下 40 | 41 | 42 | 43 | 此时还有一个问题,就是在绑定对应插槽的时候,传入的子组件怎么动态渲染的问题。这就需要`ComponentRender`可以递归的渲染自己,我们再次修改下代码 44 | 45 | @[code vue](../.vuepress/components/demos/ComponentRendeWithRecursion.vue) 46 | 47 | 用它渲染一个使用`Foo`的示例 48 | 49 | @[code vue](../.vuepress/components/demos/UseComponentRenderRenderFooWithRecursion.vue) 50 | 51 | 效果如下 52 | 53 | 54 | 55 | 组件的渲染机制就是这样,后面再通过一定的方式将`IRenderConfig`拆分为`IComponentMaker`和`IComponentConfig`,将无法转成字符串存储的部分放入`IComponentMaker`,如`component`这样类型的字段,将其他可以持久化存储的字段放入`IComponentConfig`,如`props`等。这样,就可以通过动态的配置,然后动态的渲染组件了。 -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | # 每当创建tag时触发部署 5 | push: 6 | tags: 7 | - "v*" 8 | # 手动触发部署 9 | workflow_dispatch: 10 | 11 | jobs: 12 | docs: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | # “最近更新时间” 等 git 日志相关信息,需要拉取全部提交记录 19 | fetch-depth: 0 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v1 23 | with: 24 | # 选择要使用的 node 版本 25 | node-version: "14" 26 | 27 | # 缓存 node_modules 28 | - name: Cache dependencies 29 | uses: actions/cache@v2 30 | id: yarn-cache 31 | with: 32 | path: | 33 | **/node_modules 34 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 35 | restore-keys: | 36 | ${{ runner.os }}-yarn- 37 | 38 | # 如果缓存没有命中,安装依赖 39 | - name: Install dependencies 40 | if: steps.yarn-cache.outputs.cache-hit != 'true' 41 | run: yarn --frozen-lockfile 42 | 43 | # 运行构建脚本 44 | - name: Build VuePress site 45 | run: yarn docs:build 46 | 47 | # 查看 workflow 的文档来获取更多信息 48 | # @see https://github.com/crazy-max/ghaction-github-pages 49 | - name: Deploy to GitHub Pages 50 | uses: crazy-max/ghaction-github-pages@v2 51 | with: 52 | # 部署到 gh-pages 分支 53 | target_branch: gh-pages 54 | # 部署目录为 VuePress 的默认输出目录 55 | build_dir: docs/.vuepress/dist 56 | env: 57 | # @see https://docs.github.com/cn/actions/reference/authentication-in-a-workflow#about-the-github_token-secret 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.0.3](https://github.com/LiuWenXing1996/vue-cook/compare/v1.0.2...v1.0.3) (2022-04-13) 6 | 7 | 8 | ### Documentation 9 | 10 | * 添加WIP警告 ([ae610f8](https://github.com/LiuWenXing1996/vue-cook/commit/ae610f8947639e0098c4527122c8e0731ad4cc57)) 11 | * 移除黑暗模式,它会对示例页面的展示造成影响 ([95cf23b](https://github.com/LiuWenXing1996/vue-cook/commit/95cf23b2fb9ce0f36585e87e7a950977f5708a4e)) 12 | 13 | ### [1.0.2](https://github.com/LiuWenXing1996/vue-cook/compare/v1.0.1...v1.0.2) (2021-12-05) 14 | 15 | 16 | ### Documentation 17 | 18 | * 入门页面添加动态图 ([5ec3c06](https://github.com/LiuWenXing1996/vue-cook/commit/5ec3c06ab17c09f565a39a1071c276bd7efc0e95)) 19 | * 添加致谢列表 ([1d14fe5](https://github.com/LiuWenXing1996/vue-cook/commit/1d14fe5a155d678cad5f0b1ee3e6f53cf26d5d20)) 20 | * 组件渲染机制 ([b6b0374](https://github.com/LiuWenXing1996/vue-cook/commit/b6b03741440d35d578029e8adc1a0571a6cecbe9)) 21 | 22 | ### [1.0.1](https://github.com/LiuWenXing1996/vue-cook/compare/v1.0.0...v1.0.1) (2021-11-27) 23 | 24 | 25 | ### Documentation 26 | 27 | * 手动修改更新日志 ([a379cbc](https://github.com/LiuWenXing1996/vue-cook/commit/a379cbcf1013dd8e50964794712c8d67ff98a56a)) 28 | 29 | ### [1.0.0](https://github.com/LiuWenXing1996/vue-cook/compare/v0.1.5...v1.0.0) (2021-11-27) 30 | 31 | ### Features: 32 | 33 | - 💎 组件渲染DOM一致: 将组件渲染成对应的DOM结构时没有添加任何的包裹div层 34 | - 📝 多页面同时编辑: 支持同时编辑多个页面 35 | - 🧱 自定义组件: 支持自定义低代码组件,或者将现有的组件低代码化 36 | - 🔗 自定义逻辑: 支持自定义低代码逻辑,或者将现有的业务逻辑低代码化 37 | - ⚙️ 自定义交互面板: 支持自定义的交互面板,如自定义编辑器 38 | - 😊 内置多个交互面板: 内置了多个开箱即用的交互面板,如基础组件编辑器、页面编辑器等等 -------------------------------------------------------------------------------- /docs/.vuepress/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/clientAppEnhance.ts: -------------------------------------------------------------------------------- 1 | import { defineClientAppEnhance } from '@vuepress/client' 2 | import DemoLink from "./components/DemoLink.vue" 3 | import DemoToc from "./components/DemoTOC.vue" 4 | import PkgDepList from "./components/PkgDepList.vue" 5 | import UseFoo from "./components/demos/UseFoo.vue" 6 | import UseComponentRenderFoo from "./components/demos/UseComponentRenderFoo.vue" 7 | import UseComponentRenderFooWithDynamicName from "./components/demos/UseComponentRenderFooWithDynamicName.vue" 8 | import UseComponentRenderRenderFooWithRecursion from "./components/demos/UseComponentRenderRenderFooWithRecursion.vue" 9 | import UseComponentRenderRenderFoo from "./components/demos/UseComponentRenderRenderFoo.vue" 10 | import routes from "virtual:generated-pages"; 11 | import useRoutes from './hooks/useRoutes'; 12 | import addDemosRouteToApp from './utils/addDemosRouteToApp'; 13 | import addDemosRouteToRoutes from './utils/addDemosRouteToRoutes'; 14 | 15 | export default defineClientAppEnhance(async ({ app, router, siteData }) => { 16 | app.component("DemoLink", DemoLink) 17 | app.component("DemoToc", DemoToc) 18 | app.component("PkgDepList", PkgDepList) 19 | app.component("DemosUseFoo", UseFoo) 20 | app.component("DemosUseComponentRenderFoo", UseComponentRenderFoo) 21 | app.component("DemosUseComponentRenderFooWithDynamicName", UseComponentRenderFooWithDynamicName) 22 | app.component("DemosUseComponentRenderRenderFoo", UseComponentRenderRenderFoo) 23 | app.component("DemosUseComponentRenderRenderFooWithRecursion", UseComponentRenderRenderFooWithRecursion) 24 | console.log(routes) 25 | useRoutes().value = routes 26 | routes.forEach(e => { 27 | router.addRoute(e) 28 | }) 29 | await addDemosRouteToRoutes() 30 | addDemosRouteToApp(router) 31 | }) -------------------------------------------------------------------------------- /demos/test-pkg/panels/component-dragger-panel/ComponentDraggerPanel.vue: -------------------------------------------------------------------------------- 1 | // ComponentDraggerPanel.vue 2 | 3 | 4 | 5 | {{ slotOption }} 6 | 拖拽组件到此处添加 7 | 8 | 9 | uid:{{ componentConfig.uid }} 10 | name:{{ componentConfig.name }} 11 | 删除 12 | 13 | 14 | 15 | 16 | 请选择组件 17 | 18 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # 开始 2 | 3 | ::: warning WIP 4 | vue-cook还在开发中,请不要将它用在生产环境 5 | ::: 6 | 7 | ## 总览 8 | 9 | VueCook 是一个基于vue的低代码平台辅助工具,让你的vue组件更容易低代码化。它主要有两个组件组成: 10 | 11 | - `CookEditor` 编辑器组件,它将多个面板融合在同一个页面中,我们可以通过自定义自己的交互面板来扩充这个编辑器的搭建能力。使用它,你可以很容易实现下面页面的效果 12 | 13 |  14 | 15 | - `CookPlayer` 渲染器组件,通过编辑器组件,我们会生成一套配置,将其放入到此组件中,它会根据提供的配置,使用`Vue`的`动态组件`将其渲染成对应的组件树,并且为了保持DOM结构的一致性,在渲染过程中没有添加任何的包裹div 16 | 17 | VueCook本身并不是一个低代码平台,它是开发低代码平台的辅助工具。VueCook将低代码平台的搭建能力抽象成了三个东西,`组件`、`逻辑`和`交互面板` ,并统称其为`资源`,通过对应的`define***Maker`函数可以添加自定义的资源。VueCook内置了一些开箱即用的资源: 18 | - 内置组件 19 | - `RootAppMaker`:根应用组件 20 | - 内置交互面板 21 | - `ResourcePanelMaker`:资源面板 22 | - `PageComponentTreeMaker`:页面组件树 23 | - `ComponentEditorMaker`:组件基础编辑器 24 | - `PageEditorMaker`:页面编辑器 25 | 26 | ## 安装 27 | 28 | ::: tip 前置依赖 29 | 需要先安装vue3和vue-router 30 | ::: 31 | 32 | 33 | 34 | 35 | ```bash 36 | yarn add vue-cook 37 | ``` 38 | 39 | 40 | 41 | 42 | 43 | ```bash 44 | npm install vue-cook 45 | ``` 46 | 47 | 48 | 49 | 50 | ## 使用 51 | 52 | 在路由配置文件中添加相关路由 53 | 54 | ```js 55 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 56 | import { CookEditor, createCookEditorState} from "vue-cook" 57 | import "vue-cook/dist/style.css" 58 | 59 | const cookEditorState = createCookEditorState() // 创建编辑器全局状态 60 | const routes = [ 61 | { 62 | path: '/vue-cook', // 自定义的地址 63 | component: CookEditor, 64 | props: route => ({ 65 | state: cookEditorState, // 传入状态 66 | preview: route.query.preview // 是否预览 67 | }) 68 | } 69 | ] 70 | const router = createRouter({ 71 | history: createWebHistory(), // 也可以使用hash模式 72 | routes, 73 | }) 74 | 75 | export default router 76 | 77 | ``` 78 | 此时开启开发服务器,进入`/vue-cook`,你会看到示例页面-起步页 -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/utils/PagePanelLinker.ts: -------------------------------------------------------------------------------- 1 | import ICookEditorState from "@/types/ICookEditorState"; 2 | 3 | interface IPagePanelLink { 4 | pageUid: string, 5 | panelUid: string 6 | } 7 | 8 | const stateMap = new Map() 9 | 10 | const getPanelLink = (cookEditorState: ICookEditorState, panelUid: string) => { 11 | const linkList = stateMap.get(cookEditorState) || [] 12 | return linkList.find(e => e.panelUid === panelUid) 13 | } 14 | const setLink = (cookEditorState: ICookEditorState, link: IPagePanelLink) => { 15 | let linkList = stateMap.get(cookEditorState) 16 | if (!linkList) { 17 | linkList = [] 18 | stateMap.set(cookEditorState, linkList) 19 | } 20 | linkList.push(link) 21 | } 22 | 23 | const getPageLink = (cookEditorState: ICookEditorState, pageUid: string) => { 24 | const linkList = stateMap.get(cookEditorState) || [] 25 | return linkList.find(e => e.pageUid === pageUid) 26 | } 27 | 28 | const PagePanelLinker = { 29 | getPageUid: (cookEditorState: ICookEditorState, panelUid: string) => { 30 | const link = getPanelLink(cookEditorState, panelUid) 31 | return link?.pageUid 32 | }, 33 | getPanelUid: (cookEditorState: ICookEditorState, pageUid: string) => { 34 | const link = getPageLink(cookEditorState, pageUid) 35 | return link?.panelUid 36 | }, 37 | link: (cookEditorState: ICookEditorState, pageUid: string, panelUid: string,) => { 38 | const pageLink = getPageLink(cookEditorState, pageUid) 39 | const panelLink = getPanelLink(cookEditorState, panelUid) 40 | if (pageLink) { 41 | pageLink.panelUid = panelUid 42 | return 43 | } 44 | if (panelLink) { 45 | panelLink.pageUid = pageUid 46 | return 47 | } 48 | setLink(cookEditorState, { 49 | pageUid, 50 | panelUid 51 | }) 52 | }, 53 | } 54 | 55 | export default PagePanelLinker -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/ruler-box/RulerVertical.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 64 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/ruler-box/RulerHorizontal.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 65 | -------------------------------------------------------------------------------- /docs/.vuepress/components/IframeDemo.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 在新窗口查看此页面 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 33 | 70 | -------------------------------------------------------------------------------- /src/components/cook-player/utils/getComponentElements.ts: -------------------------------------------------------------------------------- 1 | import { ComponentInternalInstance, VNode, isVNode, VNodeArrayChildren, Fragment } from "vue"; 2 | import isFragment from "./isFragment"; 3 | 4 | export default function getComponentElements(instance: ComponentInternalInstance) { 5 | let elements: Element[] = [] 6 | if (isFragment(instance)) { 7 | elements = getFragmentElements(instance.subTree) 8 | } else { 9 | const vNode = instance.subTree; 10 | elements = getVNodeElement(vNode) 11 | } 12 | return elements 13 | } 14 | 15 | function getVNodeElement(vnode: VNode) { 16 | let elements: Element[] = [] 17 | if (vnode.type === Fragment) { 18 | const _elements = getFragmentElements(vnode) 19 | elements.push(..._elements) 20 | } else if (vnode.component) { 21 | const _elements = getComponentElements(vnode.component) 22 | elements.push(..._elements) 23 | } else if (vnode.el) { 24 | const el = vnode.el 25 | if (el instanceof Element) { 26 | elements.push(el) 27 | } 28 | } 29 | return elements 30 | } 31 | 32 | function getFragmentElements(vNode: VNode) { 33 | const elements: Element[] = [] 34 | if (!vNode.children) return elements 35 | if (Array.isArray(vNode.children)) { 36 | for (let i = 0, l = vNode.children.length; i < l; i++) { 37 | const childVnode = vNode.children[i] 38 | if (isVNode(childVnode)) { 39 | const _elements = getVNodeElement(childVnode) 40 | elements.push(..._elements) 41 | } 42 | } 43 | } 44 | // @ts-ignore 45 | if (!vNode.dynamicChildren) return elements 46 | // @ts-ignore 47 | const dynamicChildren = vNode.dynamicChildren as VNode[] 48 | if (Array.isArray(dynamicChildren)) { 49 | for (let i = 0, l = dynamicChildren.length; i < l; i++) { 50 | const childVnode = dynamicChildren[i] 51 | if (isVNode(childVnode)) { 52 | const _elements = getVNodeElement(childVnode) 53 | elements.push(..._elements) 54 | } 55 | } 56 | } 57 | return elements 58 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Local files 107 | .history/ 108 | .local/ 109 | .DS_Store 110 | .temp 111 | .cache 112 | -------------------------------------------------------------------------------- /docs/guide/data-save-and-use.md: -------------------------------------------------------------------------------- 1 | # 编辑数据的保存和使用 2 | 3 | ## 编辑数据保存 4 | 5 | 使用``编辑的数据会实时存在它的`CookEditorState`的`pages`中,这个对象的类型描述如下: 6 | 7 | ```ts 8 | // pages =====> Array 9 | interface IPage { 10 | path: string, 11 | name: string, 12 | uid: string, 13 | component: IComponentConfig 14 | } 15 | interface IComponentConfig { 16 | uid: string, 17 | name: string, 18 | makerType: "component", 19 | makerName: string, 20 | makerPkg: string, 21 | props?: Record, 22 | slots?: Record, 23 | events?: Record 24 | } 25 | interface ILogicConfig { 26 | uid: string, 27 | name: string, 28 | makerType: "logic", 29 | makerName: string, 30 | makerPkg: string, 31 | props?: Record 32 | } 33 | ``` 34 | 我们可以通过`JSON.stringify`这个方法将`pages`转成字符串,存储到数据库中,等到使用的时候再通过接口取出来,使用`JSON.parse`转成对象。 35 | 36 | ## 编辑数据使用 37 | 38 | ### 在``中使用 39 | 40 | 在使用`createEditorState`创建编辑状态的时候,可以传入已经存在的一些编辑数据。 41 | 42 | 假设现在已经有了一些编辑数据 43 | 44 | @[code ts](../../demos/utils/fecthPages.ts) 45 | 46 | 创建`createEditorState` 47 | 48 | @[code{14-25} ts](../../demos/pages/use-data-in-cook-editor.vue) 49 | 50 | 在``中使用它 51 | 52 | @[code{1-5} vue](../../demos/pages/use-data-in-cook-editor.vue) 53 | 54 | 55 | 打开示例页面-在``使用编辑数据,可以看到左侧面板中已经有了一个新建页面,执行一下步骤 56 | 57 | - 点击已经存在的新建页面,可以看到页面编辑器中出现了一个带表格的页面 58 | - 取消选中模式,点击表格上面的按钮 59 | - 点击按钮,可以看到表格数据正常加载 60 | 61 | ### 在``中使用 62 | 63 | 在使用`createPlayerState`创建状态的时候,可以传入已经存在的页面数据。 64 | 65 | 假设存在有一个页面 66 | 67 | @[code ts](../../demos/utils/fecthPage.ts) 68 | 69 | 创建`createPlayerState` 70 | 71 | @[code{13-24} ts](../../demos/pages/use-data-in-cook-player.vue) 72 | 73 | 在``中使用它 74 | 75 | @[code{1-5} vue](../../demos/pages/use-data-in-cook-player.vue) 76 | 77 | 打开示例页面-在``使用编辑数据,可以看到一个带表格的页面显示了出来,点击按钮,可以看到表格数据正常加载。 78 | 79 | ### 结合动态路由使用`` 80 | 81 | 可以使用`vue-router`的动态路由,动态添加`` 82 | 83 | @[code{8-34} ts](../.vuepress/utils/addDemosRouteToApp.ts) 84 | 85 | 打开示例页面-结合动态路由使用``,可以看到一个带表格的页面显示了出来,点击按钮,可以看到表格数据正常加载。 -------------------------------------------------------------------------------- /src/built-in-resources/panels/component-editor/events-editor/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 无 6 | 7 | 40 | -------------------------------------------------------------------------------- /docs/guide/custom-logic.md: -------------------------------------------------------------------------------- 1 | # 自定义逻辑 2 | 3 | 可以通过`defineLogicMaker`这个工具函数自定义逻辑资源。 4 | 5 | ## 定义逻辑 6 | 7 | 可以将任何的函数变成自定义逻辑,假设,有一个获取数据的函数 8 | 9 | @[code ts](../../demos/test-pkg/logic/fetch/fetch.ts) 10 | 11 | 通过`defineLogicMaker`把它低代码化 12 | 13 | @[code ts](../../demos/test-pkg/logic/fetch/index.ts) 14 | 15 | 创建一个可以解析这个逻辑的组件 16 | 17 | @[code vue](../../demos/test-pkg/components/table-with-fetch/Table.vue) 18 | 19 | 打开示例页面-定义逻辑,可以在最下方的`资源面板`中找到新增加的获取数据逻辑,按照下面的步骤,使用它。 20 | 21 | - 创建一个带Table组件的页面,选中Table 22 | - 在组件编辑面板的`属性`一栏中,出现了一个名称为`fetch`的输入框,它的右边有一个图标,鼠标移上去,会提示`拖拽逻辑到此处添加` 23 | - 从资源面板中拖入`FetchMaker`到上面的位置,可以看到输入框中自动填入了一个逻辑配置。 24 | - 页面中的表格组件的按钮文字也变成`点我获取数据` 25 | - 关闭选中模式,点击按钮,稍等片刻,可以看到数据加载成功 26 | 27 | ## 逻辑传参 28 | 29 | 假设现在有一个获取数据的函数,它提供两个`maxAge`和`minAge`参数,来控制返回人员数据的年龄范围 30 | 31 | @[code ts](../../demos/test-pkg/logic/fetch-with-params/fetch.ts) 32 | 33 | 在`Table.vue`中对其传参 34 | 35 | @[code vue{23-24,36-38}](../../demos/test-pkg/components/table-with-fetch-params/Table.vue) 36 | 37 | 打开示例页面-逻辑传参,执行按照下面的步骤 38 | 39 | - 创建一个带Table组件的页面,选中Table 40 | - 在组件编辑面板的`属性`一栏中,可以看到它有三个属性配置项,`fetch`,`maxAge`,`minAge` 41 | - 配置`fetch`,`maxAge`,`minAge` 42 | - 关闭选中模式,点击按钮,稍等片刻,可以看到人员数据按照相应的年龄范围加载成功 43 | 44 | ## 添加属性配置 45 | 46 | 除了通过传参来控制自定义逻辑,还可以通过`defineComponentMaker`中的`makePropOptions`参数,为自定义的逻辑添加属性配置。 47 | 48 | 假设现在有一个获取数据的自定义逻辑,它提供两个`maxAge`和`minAge`属性,可以让用户配置返回人员数据的年龄范围 49 | 50 | @[code ts](../../demos/test-pkg/logic/fetch-with-props/index.ts) 51 | 52 | 打开示例页面-为逻辑添加属性配置,执行按照下面的步骤 53 | 54 | - 创建一个带Table组件的页面,选中Table 55 | - 配置`fetch`项,鼠标移动到`fetch`输入框最右边的图标上,可以看到一个编辑弹框 56 | - 滚动下,可以看到属性编辑栏有两个配置项`maxAge`和`minAge`,输入一些数字 57 | - 关闭选中模式,点击按钮,稍等片刻,可以看到人员数据按照相应的年龄范围加载成功 58 | 59 | ### 事件绑定的逻辑添加属性配置 60 | 61 | 当自定义逻辑绑定到某一事件上后,它也可以添加一些属性配置,以`AlertMaker`为例,添加`makePropOptions` 62 | 63 | @[code ts](../../demos/test-pkg/logic/alert-with-props/index.ts) 64 | 65 | 打开示例页面-事件绑定的逻辑添加属性配置,执行按照下面的步骤 66 | 67 | - 创建一个带按钮的页面,选中按钮 68 | - 为它的点击事件绑定`AlertMaker` 69 | - 下方表格新增的那条数据右侧操作按钮中有一个编辑按钮,鼠标移上去,可以看到一个编辑弹框 70 | - 滚动下,可以看到属性编辑栏有一个配置项`msg`,输入一些内容 71 | - 关闭选中模式,点击按钮,可以看到输入的内容被显示出来 72 | 73 | 74 | 更多`defineLogicMaker`的参数,参考[`defineLogicMaker`](../api/utils.md#definelogicmaker) -------------------------------------------------------------------------------- /src/built-in-resources/panels/component-editor/events-editor/EventInfoTips.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 名称 5 | {{ logicConfig.name }} 6 | 7 | 8 | 唯一ID 9 | {{ logicConfig.uid }} 10 | 11 | 12 | 资源类型 13 | 14 | {{ logicConfig.makerName }} 15 | - 16 | {{ logicConfig.makerPkg }} 17 | 18 | 19 | 20 | 21 | 31 | -------------------------------------------------------------------------------- /src/components/cook-editor/insider-editor/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 36 | -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const chalk = require('chalk') 3 | const execa = require('execa') 4 | const args = require('minimist')(process.argv.slice(2)) 5 | 6 | async function getGitBranch() { 7 | const { stdout } = await execa.command('git rev-parse --abbrev-ref HEAD'); 8 | return stdout; 9 | } 10 | 11 | async function lint() { 12 | let checked = true 13 | try { 14 | await execa.command('vue-tsc --noEmit'); 15 | } catch (err) { 16 | checked = false 17 | } 18 | return checked 19 | } 20 | 21 | async function version() { 22 | let cmdStr = `standard-version` 23 | if (args.r) { 24 | cmdStr = `standard-version -r ${args.r}` 25 | } 26 | const { stdout } = await execa.command(cmdStr); 27 | return stdout; 28 | } 29 | 30 | async function pushToGit() { 31 | let isSuccess = true 32 | try { 33 | await execa.command('git push --follow-tags origin main'); 34 | } catch (err) { 35 | isSuccess = false 36 | throw err 37 | } 38 | return isSuccess 39 | } 40 | 41 | async function branchStatus() { 42 | const { stdout } = await execa.command('git status -s'); 43 | return stdout; 44 | } 45 | 46 | async function main() { 47 | // console.log(args) 48 | // 检查分支名 49 | console.log(chalk.cyan(`正在检查分支名...`)) 50 | const branch = await getGitBranch() 51 | if (branch !== "main") { 52 | console.log(chalk.red(`你现在在分支${branch},请使用 git checkout main 切换到主分支main操作`)) 53 | return 54 | } 55 | console.log(chalk.blue(`你现在在主分支main`)) 56 | 57 | // 检查分支是否干净 58 | console.log(chalk.cyan(`正在检查当前分支是否干净...`)) 59 | const branchStatusInfo = await branchStatus() 60 | if (branchStatusInfo) { 61 | console.log(chalk.red(`检测到有未提交的更改:`)) 62 | console.log(branchStatusInfo) 63 | console.log(chalk.red(`请提交以上更改,保持git工作区的干净`)) 64 | return 65 | } 66 | console.log(chalk.blue(`当前分支是干净的`)) 67 | 68 | // 类型检查 69 | console.log(chalk.cyan(`正在进行类型检查...`)) 70 | const typeChecked = await lint() 71 | if (!typeChecked) { 72 | console.log(chalk.red(`类型检查出错,请使用 yarn lint 检查详细的类型错误`)) 73 | return 74 | } 75 | console.log(chalk.blue(`无类型错误`)) 76 | 77 | // 生成版本 78 | console.log(chalk.cyan(`正在生成版本信息...`)) 79 | const versionInfo = await version() 80 | console.log(versionInfo) 81 | 82 | // 推送至github 83 | console.log(chalk.cyan(`正在推送至github...`)) 84 | const pushRes = await pushToGit() 85 | if (pushRes) { 86 | console.log(chalk.blue(`推送至github完成,它会触发对应的工作流`)) 87 | console.log(chalk.blue(`打开 https://github.com/LiuWenXing1996/vue-cook/actions 查看`)) 88 | } 89 | } 90 | 91 | main().catch(err => { 92 | console.error(err) 93 | }) -------------------------------------------------------------------------------- /src/built-in-resources/panels/component-editor/events-editor/EventLogicAction.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 删除 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 上移 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 上移 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 61 | -------------------------------------------------------------------------------- /src/components/resource-maker/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 12 | {{ maker.name }}-{{ maker.pkg }} 13 | 14 | 15 | 57 | -------------------------------------------------------------------------------- /docs/api/hooks.md: -------------------------------------------------------------------------------- 1 | # 钩子 2 | 3 | ## useComponentFocused 4 | 页面编辑器中被聚焦的组件 5 | - **参数** 6 | - `cookEditorState` - `ICookEditorState` 7 | - **返回** 8 | - `get` - `() => IComponentFocused` 9 | ```ts 10 | type IComponentFocused = ComputedRef<{ 11 | page: IPage; 12 | component: IComponentConfig; 13 | } | undefined> 14 | ``` 15 | - `set` - `(componetFoused?:{pageUid: string,componentUid: string}) => void` 16 | - **用法** 17 | 18 | 通过控制它的值,可以将`页面编辑面板`中的某一个组件高亮,内置资源中的`页面组件树`和`组件编辑器`中的鼠标悬停高亮组件功能就是用此函数实现的 19 | ## useComponentMaker 20 | 获取当前状态中的一个自定义组件资源 21 | - **参数** 22 | - `cookState` - `ICookState` 23 | - `name` - `string | undefined` 24 | - `pkg` - `string | undefined` 25 | - **返回** `Ref` 26 | ## useComponentMakerList 27 | 当前状态中的自定义组件资源列表 28 | - **参数** 29 | - `cookState` - `ICookState` 30 | - **返回** `ComputedRef` 31 | ## useComponentPickerEnable 32 | 页面编辑器是否启用组件选取模式 33 | - **参数** 34 | - `cookEditorState` - `ICookEditorState` 35 | - **返回** 36 | - `get` - `() => ComputedRef` 37 | - `set` - `(componentPickerEnable: boolean) => void` 38 | - `toggle` - `() => void` 39 | ## useComponentSelected 40 | 页面编辑器中被选取的组件 41 | - **参数** 42 | - `cookEditorState` - `ICookEditorState` 43 | - **返回** 44 | - `get` - `() => ComputedRef` 45 | - `set` - `(componetSelected?: IComponentSelected) => void` 46 | ## useLogicMaker 47 | 获取当前状态中的一个自定义逻辑资源 48 | - **参数** 49 | - `cookState` - `ICookState` 50 | - `name` - `string | undefined` 51 | - `pkg` - `string | undefined` 52 | - **返回** `Ref` 53 | ## useLogicMakerList 54 | 当前状态中的自定义逻辑资源列表 55 | - **参数** 56 | - `cookState` - `ICookState` 57 | - **返回** `ComputedRef` 58 | ## usePageEditingUidList 59 | 当前在编辑状态的页面的uid列表 60 | - **参数** 61 | - `cookEditorState` - `ICookEditorState` 62 | - **返回** 63 | - `list` - `() => ComputedRef` 64 | - `add` - `(pageUid: string) => void` 65 | - `remove` - `(pageUid?: string | undefined) => void` 66 | - **用法** 67 | 68 | 可以通过它来动态控制`页面编辑面板`的开关 69 | 70 | ## usePanelMaker 71 | 获取当前状态中的一个自定义面板资源 72 | - **参数** 73 | - `cookState` - `ICookState` 74 | - `name` - `string | undefined` 75 | - `pkg` - `string | undefined` 76 | - **返回** `Ref` 77 | ## usePanelMakerList 78 | 当前状态中的自定义面板资源列表 79 | - **参数** 80 | - `cookState` - `ICookState` 81 | - **返回** `ComputedRef` 82 | ## useSlotOptions 83 | 获取组件资源的插槽可配置项 84 | - **参数** 85 | - `cookEditorState` - `ICookEditorState` 86 | - `componentConfig` - `Ref` 87 | - **返回** `ComputedRef` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-cook", 3 | "version": "1.0.3", 4 | "files": [ 5 | "dist" 6 | ], 7 | "description": "一个基于vue的低代码平台辅助工具,让你的vue组件更容易低代码化。", 8 | "license": "MIT", 9 | "author": "LiuWenXing1996 lwx_redstone@163.com", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/LiuWenXing1996/vue-cook" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/LiuWenXing1996/vue-cook/issues" 16 | }, 17 | "keywords": [ 18 | "low-code", 19 | "vue3" 20 | ], 21 | "homepage": "https://liuwenxing1996.github.io/vue-cook/", 22 | "main": "./dist/vue-cook.umd.js", 23 | "module": "./dist/vue-cook.es.js", 24 | "types": "./dist/index.d.ts", 25 | "scripts": { 26 | "dev": "vuepress dev docs --clean-cache", 27 | "build": "vue-tsc --noEmit && vite build", 28 | "build:dts": "vue-tsc --declaration --emitDeclarationOnly --outDir .local/dts-temp -p ./tsconfig.dts.json && rollup -c rollup.dts.config.js", 29 | "lint": "vue-tsc --noEmit", 30 | "docs:build": "vuepress build docs", 31 | "prepare": "husky install", 32 | "release": "node ./scripts/release.js", 33 | "cz": "git add . && cz" 34 | }, 35 | "dependencies": { 36 | "vue": "^3.2.6" 37 | }, 38 | "devDependencies": { 39 | "@commitlint/cli": "^15.0.0", 40 | "@commitlint/config-conventional": "^15.0.0", 41 | "@daybrush/drag": "^0.19.3", 42 | "@rollup/plugin-alias": "^3.1.8", 43 | "@scena/ruler": "^0.8.0", 44 | "@sicons/fluent": "^0.11.0", 45 | "@types/node": "^16.9.1", 46 | "@types/uuid": "^8.3.1", 47 | "@vicons/antd": "^0.11.0", 48 | "@vicons/fluent": "^0.11.0", 49 | "@vicons/ionicons5": "^0.11.0", 50 | "@vicons/material": "^0.11.0", 51 | "@vicons/tabler": "^0.11.0", 52 | "@vitejs/plugin-vue": "^1.6.1", 53 | "@vue/compiler-sfc": "^3.2.6", 54 | "@vuepress/bundler-vite": "^2.0.0-beta.27", 55 | "@vuepress/plugin-docsearch": "^2.0.0-beta.27", 56 | "@vueuse/core": "^6.5.3", 57 | "chalk": "^4.1.0", 58 | "commitizen": "^4.2.4", 59 | "cz-conventional-changelog": "3.3.0", 60 | "execa": "^4.0.2", 61 | "husky": "^7.0.4", 62 | "less": "^4.1.1", 63 | "lodash-es": "^4.17.21", 64 | "minimist": "^1.2.5", 65 | "naive-ui": "^2.19.7", 66 | "rollup-plugin-dts": "^4.0.1", 67 | "standard-version": "^9.3.2", 68 | "typescript": "^4.3.2", 69 | "uuid": "^8.3.2", 70 | "vite": "^2.6.13", 71 | "vite-plugin-pages": "^0.18.2", 72 | "vite-plugin-vue-svg-reactive-loader": "^0.0.3", 73 | "vue-router": "^4.0.12", 74 | "vue-tsc": "0.29.4", 75 | "vuepress": "^2.0.0-beta.27" 76 | }, 77 | "config": { 78 | "commitizen": { 79 | "path": "cz-conventional-changelog" 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/ruler-box/RulerBox.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 38 | -------------------------------------------------------------------------------- /src/built-in-resources/panels/page-editor/PageCookContent.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 57 | --------------------------------------------------------------------------------