docs: update README
This commit is contained in:
parent
27fe5fc752
commit
9a05f858db
56
README.md
56
README.md
|
@ -1,11 +1,41 @@
|
||||||
# 基于 Vite2.x + Vue3.x + TypeScript H5 低代码平台
|
# 基于 Vite2.x + Vue3.x + TypeScript H5 低代码平台
|
||||||
|
|
||||||
### 目前还只是一个简单的模板,后面可能会引入较为完善的机制系统,感兴趣的小伙伴可以根据自己的需要去调整, 通过这个项目或许你可以学到 vue3 很多有趣的新特性和玩法。
|
|
||||||
|
|
||||||
[![license](https://img.shields.io/github/license/buqiyuan/vite-vue3-lowcode.svg)](LICENSE)
|
[![license](https://img.shields.io/github/license/buqiyuan/vite-vue3-lowcode.svg)](LICENSE)
|
||||||
|
|
||||||
**中文** | [English](./README.EN.md)
|
**中文** | [English](./README.EN.md)
|
||||||
|
|
||||||
|
### 目前还只是一个简单的模板,后面可能会引入较为完善的机制系统,感兴趣的小伙伴可以根据自己的需要去调整, 通过这个项目或许你可以接触到 vue3 很多有趣的新特性和玩法。
|
||||||
|
|
||||||
|
` PS: 此项目为个人半年以前做的实验性小玩具,使用的都是最新的技术栈,后面由于个人时间问题,没有持续维护和完善,暂时计划于2022年下半年开始对项目进行整体的重构和重新设计,实现一个基本可用的简易低代码平台。感谢关注~`
|
||||||
|
|
||||||
|
## 计划实现:
|
||||||
|
|
||||||
|
- 操作历史快照
|
||||||
|
- 支持生成 vue 模板组件
|
||||||
|
- 生成组件大纲树
|
||||||
|
- 提供常见的表单和列表模板
|
||||||
|
- v-for 绑定数据源
|
||||||
|
- 在 sandbox 中执行自定义逻辑
|
||||||
|
- 基于 monaco-editor 自定义代码补全规则
|
||||||
|
- 基于 vue3 createRenderer 自定义渲染器
|
||||||
|
- 使用 Schema 描述数据结构(因为 schema 可以生成校验函数)
|
||||||
|
- (⊙o⊙)…想到再做了~
|
||||||
|
|
||||||
|
### 模型驱动的视图
|
||||||
|
|
||||||
|
从最简单的结构来看,一个模型驱动的视图体系包含以下要素:
|
||||||
|
|
||||||
|
- 模型
|
||||||
|
|
||||||
|
- 定义状态结构
|
||||||
|
- 定义动作
|
||||||
|
|
||||||
|
- 视图
|
||||||
|
- 订阅状态
|
||||||
|
- 触发动作
|
||||||
|
|
||||||
|
这是很简单的一种渲染模式,可以适用于大多数的场景。
|
||||||
|
|
||||||
## 克隆项目
|
## 克隆项目
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -138,25 +168,3 @@ JSON.stringify(
|
||||||
- `ci` 持续集成
|
- `ci` 持续集成
|
||||||
- `types` 类型定义文件更改
|
- `types` 类型定义文件更改
|
||||||
- `wip` 开发中
|
- `wip` 开发中
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
### 安装依赖
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
# or
|
|
||||||
yarn add
|
|
||||||
```
|
|
||||||
|
|
||||||
### 启动项目
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 项目打包
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,4 +1,54 @@
|
||||||
// Generated by 'unplugin-auto-import'
|
// Generated by 'unplugin-auto-import'
|
||||||
// We suggest you to commit this file into source control
|
// We suggest you to commit this file into source control
|
||||||
declare global {}
|
declare global {
|
||||||
|
const computed: typeof import('vue')['computed'];
|
||||||
|
const createApp: typeof import('vue')['createApp'];
|
||||||
|
const customRef: typeof import('vue')['customRef'];
|
||||||
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'];
|
||||||
|
const defineComponent: typeof import('vue')['defineComponent'];
|
||||||
|
const effectScope: typeof import('vue')['effectScope'];
|
||||||
|
const EffectScope: typeof import('vue')['EffectScope'];
|
||||||
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance'];
|
||||||
|
const getCurrentScope: typeof import('vue')['getCurrentScope'];
|
||||||
|
const h: typeof import('vue')['h'];
|
||||||
|
const inject: typeof import('vue')['inject'];
|
||||||
|
const isReadonly: typeof import('vue')['isReadonly'];
|
||||||
|
const isRef: typeof import('vue')['isRef'];
|
||||||
|
const markRaw: typeof import('vue')['markRaw'];
|
||||||
|
const nextTick: typeof import('vue')['nextTick'];
|
||||||
|
const onActivated: typeof import('vue')['onActivated'];
|
||||||
|
const onBeforeMount: typeof import('vue')['onBeforeMount'];
|
||||||
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'];
|
||||||
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'];
|
||||||
|
const onDeactivated: typeof import('vue')['onDeactivated'];
|
||||||
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured'];
|
||||||
|
const onMounted: typeof import('vue')['onMounted'];
|
||||||
|
const onRenderTracked: typeof import('vue')['onRenderTracked'];
|
||||||
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered'];
|
||||||
|
const onScopeDispose: typeof import('vue')['onScopeDispose'];
|
||||||
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch'];
|
||||||
|
const onUnmounted: typeof import('vue')['onUnmounted'];
|
||||||
|
const onUpdated: typeof import('vue')['onUpdated'];
|
||||||
|
const provide: typeof import('vue')['provide'];
|
||||||
|
const reactive: typeof import('vue')['reactive'];
|
||||||
|
const readonly: typeof import('vue')['readonly'];
|
||||||
|
const ref: typeof import('vue')['ref'];
|
||||||
|
const resolveComponent: typeof import('vue')['resolveComponent'];
|
||||||
|
const shallowReactive: typeof import('vue')['shallowReactive'];
|
||||||
|
const shallowReadonly: typeof import('vue')['shallowReadonly'];
|
||||||
|
const shallowRef: typeof import('vue')['shallowRef'];
|
||||||
|
const toRaw: typeof import('vue')['toRaw'];
|
||||||
|
const toRef: typeof import('vue')['toRef'];
|
||||||
|
const toRefs: typeof import('vue')['toRefs'];
|
||||||
|
const triggerRef: typeof import('vue')['triggerRef'];
|
||||||
|
const unref: typeof import('vue')['unref'];
|
||||||
|
const useAttrs: typeof import('vue')['useAttrs'];
|
||||||
|
const useCssModule: typeof import('vue')['useCssModule'];
|
||||||
|
const useCssVars: typeof import('vue')['useCssVars'];
|
||||||
|
const useRoute: typeof import('vue-router')['useRoute'];
|
||||||
|
const useRouter: typeof import('vue-router')['useRouter'];
|
||||||
|
const useSlots: typeof import('vue')['useSlots'];
|
||||||
|
const watch: typeof import('vue')['watch'];
|
||||||
|
const watchEffect: typeof import('vue')['watchEffect'];
|
||||||
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// generated by vite-plugin-components
|
// generated by unplugin-vue-components
|
||||||
// read more https://github.com/vuejs/vue-next/pull/3399
|
// We suggest you to commit this file into source control
|
||||||
|
// Read more: https://github.com/vuejs/vue-next/pull/3399
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
@ -15,6 +16,7 @@ declare module 'vue' {
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'];
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'];
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'];
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'];
|
||||||
ElHeader: typeof import('element-plus/es')['ElHeader'];
|
ElHeader: typeof import('element-plus/es')['ElHeader'];
|
||||||
|
ElIcon: typeof import('element-plus/es')['ElIcon'];
|
||||||
ElMain: typeof import('element-plus/es')['ElMain'];
|
ElMain: typeof import('element-plus/es')['ElMain'];
|
||||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm'];
|
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm'];
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover'];
|
ElPopover: typeof import('element-plus/es')['ElPopover'];
|
||||||
|
@ -24,6 +26,7 @@ declare module 'vue' {
|
||||||
ElTag: typeof import('element-plus/es')['ElTag'];
|
ElTag: typeof import('element-plus/es')['ElTag'];
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip'];
|
ElTooltip: typeof import('element-plus/es')['ElTooltip'];
|
||||||
ElTree: typeof import('element-plus/es')['ElTree'];
|
ElTree: typeof import('element-plus/es')['ElTree'];
|
||||||
|
InfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
46
package.json
46
package.json
|
@ -19,7 +19,7 @@
|
||||||
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
||||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||||
"lint:lint-staged": "lint-staged",
|
"lint:lint-staged": "lint-staged",
|
||||||
"deploy": "gh-pages -d dist",
|
"deploy": "npm run build && npx gh-pages -d dist",
|
||||||
"test:gzip": "npx http-server dist --cors --gzip -c-1",
|
"test:gzip": "npx http-server dist --cors --gzip -c-1",
|
||||||
"test:br": "npx http-server dist --cors --brotli -c-1",
|
"test:br": "npx http-server dist --cors --brotli -c-1",
|
||||||
"reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
|
"reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
|
||||||
|
@ -28,52 +28,50 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^0.2.4",
|
"@element-plus/icons-vue": "^0.2.4",
|
||||||
"@vant/touch-emulator": "^1.3.2",
|
"@vant/touch-emulator": "^1.3.2",
|
||||||
"@vueuse/core": "^7.5.1",
|
"@vueuse/core": "^7.5.3",
|
||||||
"@vueuse/integrations": "^7.5.1",
|
"@vueuse/integrations": "^7.5.3",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.10.7",
|
||||||
"dexie": "^3.2.0",
|
"dexie": "^3.2.0",
|
||||||
"element-plus": "1.3.0-beta.1",
|
"element-plus": "1.3.0-beta.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"monaco-editor": "^0.31.1",
|
"monaco-editor": "^0.31.1",
|
||||||
"nanoid": "^3.1.30",
|
"nanoid": "^3.1.32",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"nprogress": "^1.0.0-1",
|
"nprogress": "^1.0.0-1",
|
||||||
"pinia": "^2.0.9",
|
"pinia": "^2.0.9",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
"qs": "^6.10.2",
|
"qs": "^6.10.3",
|
||||||
"vant": "3.3.7",
|
"vant": "3.4.1",
|
||||||
"vue": "3.2.26",
|
"vue": "3.2.26",
|
||||||
"vue-router": "^4.0.12",
|
"vue-router": "^4.0.12",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^16.0.1",
|
"@commitlint/cli": "^16.0.2",
|
||||||
"@commitlint/config-conventional": "^16.0.0",
|
"@commitlint/config-conventional": "^16.0.0",
|
||||||
"@types/lodash": "^4.14.178",
|
"@types/lodash-es": "^4.17.5",
|
||||||
"@types/node": "^17.0.5",
|
"@types/node": "^17.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.8.1",
|
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||||
"@typescript-eslint/parser": "^5.8.1",
|
"@typescript-eslint/parser": "^5.9.1",
|
||||||
"@vitejs/plugin-legacy": "^1.6.4",
|
"@vitejs/plugin-legacy": "^1.6.4",
|
||||||
"@vitejs/plugin-vue": "^2.0.1",
|
"@vitejs/plugin-vue": "^2.0.1",
|
||||||
"@vitejs/plugin-vue-jsx": "^1.3.3",
|
"@vitejs/plugin-vue-jsx": "^1.3.3",
|
||||||
"@vue/compiler-sfc": "3.2.26",
|
"@vue/compiler-sfc": "3.2.26",
|
||||||
"commitizen": "^4.2.4",
|
"commitizen": "^4.2.4",
|
||||||
|
"conventional-changelog-cli": "^2.2.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cz-conventional-changelog": "^3.3.0",
|
"eslint": "^8.7.0",
|
||||||
"cz-customizable": "^6.3.0",
|
|
||||||
"eslint": "^8.5.0",
|
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-define-config": "^1.2.1",
|
"eslint-define-config": "^1.2.2",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-vue": "^8.2.0",
|
"eslint-plugin-vue": "^8.3.0",
|
||||||
"gh-pages": "^3.2.3",
|
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"lint-staged": "^12.1.4",
|
"lint-staged": "^12.1.7",
|
||||||
"postcss-html": "^1.3.0",
|
"postcss-html": "^1.3.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"sass": "1.45.2",
|
"sass": "1.48.0",
|
||||||
"stylelint": "^14.2.0",
|
"stylelint": "^14.2.0",
|
||||||
"stylelint-config-html": "^1.0.0",
|
"stylelint-config-html": "^1.0.0",
|
||||||
"stylelint-config-prettier": "^9.0.3",
|
"stylelint-config-prettier": "^9.0.3",
|
||||||
|
@ -82,10 +80,10 @@
|
||||||
"stylelint-order": "^5.0.0",
|
"stylelint-order": "^5.0.0",
|
||||||
"stylelint-scss": "^4.1.0",
|
"stylelint-scss": "^4.1.0",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"unplugin-auto-import": "^0.5.5",
|
"unplugin-auto-import": "^0.5.11",
|
||||||
"unplugin-vue-components": "^0.17.11",
|
"unplugin-vue-components": "^0.17.11",
|
||||||
"vite": "2.7.10",
|
"vite": "2.7.12",
|
||||||
"vite-plugin-windicss": "^1.6.1",
|
"vite-plugin-windicss": "^1.6.2",
|
||||||
"vue-eslint-parser": "^8.0.1",
|
"vue-eslint-parser": "^8.0.1",
|
||||||
"windicss": "^3.4.2"
|
"windicss": "^3.4.2"
|
||||||
},
|
},
|
||||||
|
|
1056
pnpm-lock.yaml
1056
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -16,7 +16,7 @@ import {
|
||||||
createEditorModelBindProp,
|
createEditorModelBindProp,
|
||||||
createEditorSwitchProp,
|
createEditorSwitchProp,
|
||||||
} from '@/visual-editor/visual-editor.props';
|
} from '@/visual-editor/visual-editor.props';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash-es';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
key: 'slider',
|
key: 'slider',
|
||||||
|
|
|
@ -6,19 +6,34 @@
|
||||||
* @Description: 导航栏
|
* @Description: 导航栏
|
||||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\tabbar\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\tabbar\index.tsx
|
||||||
*/
|
*/
|
||||||
import { Tabbar, TabbarItem } from 'vant'
|
import { Tabbar, TabbarItem } from 'vant';
|
||||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||||
import {
|
import {
|
||||||
createEditorCrossSortableProp,
|
createEditorCrossSortableProp,
|
||||||
createEditorInputProp,
|
createEditorInputProp,
|
||||||
createEditorSwitchProp,
|
createEditorSwitchProp,
|
||||||
createEditorColorProp
|
createEditorColorProp,
|
||||||
} from '@/visual-editor/visual-editor.props'
|
} from '@/visual-editor/visual-editor.props';
|
||||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
|
import { useGlobalProperties } from '@/hooks/useGlobalProperties';
|
||||||
import tabbarItem from './tabbar-item'
|
import { getTabbarItem } from './tabbar-item';
|
||||||
import { createNewBlock } from '@/visual-editor/visual-editor.utils'
|
import { createNewBlock } from '@/visual-editor/visual-editor.utils';
|
||||||
import { BASE_URL } from '@/visual-editor/utils'
|
import { BASE_URL } from '@/visual-editor/utils';
|
||||||
import { onMounted, onBeforeUnmount } from 'vue'
|
import { onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
|
const defaultTabbarItems = [
|
||||||
|
{
|
||||||
|
icon: 'home-o',
|
||||||
|
title: '首页',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'apps-o',
|
||||||
|
title: '导航',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'user-o',
|
||||||
|
title: '我的',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
key: 'tabbar',
|
key: 'tabbar',
|
||||||
|
@ -26,82 +41,77 @@ export default {
|
||||||
label: '底部标签栏',
|
label: '底部标签栏',
|
||||||
preview: () => (
|
preview: () => (
|
||||||
<Tabbar>
|
<Tabbar>
|
||||||
<TabbarItem icon="home-o">首页</TabbarItem>
|
{defaultTabbarItems.map((item) => (
|
||||||
<TabbarItem icon="apps-o">导航</TabbarItem>
|
<TabbarItem icon={item.icon}>{item.title}</TabbarItem>
|
||||||
<TabbarItem icon="user-o">我的</TabbarItem>
|
))}
|
||||||
</Tabbar>
|
</Tabbar>
|
||||||
),
|
),
|
||||||
render: ({ props, block }) => {
|
render: ({ props, block }) => {
|
||||||
const { registerRef } = useGlobalProperties()
|
const { registerRef } = useGlobalProperties();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const compEl = window.$$refs[block._vid]?.$el
|
const compEl = window.$$refs[block._vid]?.$el;
|
||||||
const draggableEl = compEl?.closest('div[data-draggable]')
|
const draggableEl = compEl?.closest('div[data-draggable]');
|
||||||
const dragArea: HTMLDivElement = document.querySelector(
|
const dragArea: HTMLDivElement = document.querySelector(
|
||||||
'.simulator-editor-content > .dragArea '
|
'.simulator-editor-content > .dragArea ',
|
||||||
)!
|
)!;
|
||||||
const tabbarEl = draggableEl?.querySelector('.van-tabbar') as HTMLDivElement
|
const tabbarEl = draggableEl?.querySelector('.van-tabbar') as HTMLDivElement;
|
||||||
if (draggableEl && tabbarEl && dragArea) {
|
if (draggableEl && tabbarEl && dragArea) {
|
||||||
tabbarEl.style.position = 'unset'
|
tabbarEl.style.position = 'unset';
|
||||||
draggableEl.style.position = 'fixed'
|
draggableEl.style.position = 'fixed';
|
||||||
draggableEl.style.bottom = '0'
|
draggableEl.style.bottom = '0';
|
||||||
draggableEl.style.left = '0'
|
draggableEl.style.left = '0';
|
||||||
draggableEl.style.width = '100%'
|
draggableEl.style.width = '100%';
|
||||||
draggableEl.style.zIndex = '1000'
|
draggableEl.style.zIndex = '1000';
|
||||||
dragArea.style.paddingBottom = '56px'
|
dragArea.style.paddingBottom = '56px';
|
||||||
} else {
|
} else {
|
||||||
document.body.style.paddingBottom = '50px'
|
document.body.style.paddingBottom = '50px';
|
||||||
const slotEl = compEl?.closest('__slot-item')
|
const slotEl = compEl?.closest('__slot-item');
|
||||||
if (slotEl) {
|
if (slotEl) {
|
||||||
slotEl.style.position = 'fixed'
|
slotEl.style.position = 'fixed';
|
||||||
slotEl.style.bottom = '0'
|
slotEl.style.bottom = '0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
const dragArea: HTMLDivElement = document.querySelector(
|
const dragArea: HTMLDivElement = document.querySelector(
|
||||||
'.simulator-editor-content > .dragArea '
|
'.simulator-editor-content > .dragArea ',
|
||||||
)!
|
)!;
|
||||||
if (dragArea) {
|
if (dragArea) {
|
||||||
dragArea.style.paddingBottom = ''
|
dragArea.style.paddingBottom = '';
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<Tabbar ref={(el) => registerRef(el, block._vid)} v-model={props.modelValue} {...props}>
|
<Tabbar ref={(el) => registerRef(el, block._vid)} v-model={props.modelValue} {...props}>
|
||||||
{props.tabs?.map((item) => {
|
{props.tabs?.map((item) => {
|
||||||
const itemProps = item.block?.props
|
const itemProps = item.block?.props;
|
||||||
const url = `${BASE_URL}${props.baseUrl}${itemProps.url}`.replace(/\/{2,}/g, '/')
|
const url = `${BASE_URL}${props.baseUrl}${itemProps.url}`.replace(/\/{2,}/g, '/');
|
||||||
return (
|
return (
|
||||||
<TabbarItem name={item.value} key={item.value} {...itemProps} url={url}>
|
<TabbarItem name={item.value} key={item.value} {...itemProps} url={url}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</TabbarItem>
|
</TabbarItem>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</Tabbar>
|
</Tabbar>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
modelValue: createEditorInputProp({
|
modelValue: createEditorInputProp({
|
||||||
label: '当前选中标签的名称或索引值',
|
label: '当前选中标签的名称或索引值',
|
||||||
defaultValue: ''
|
defaultValue: '',
|
||||||
}),
|
}),
|
||||||
tabs: createEditorCrossSortableProp({
|
tabs: createEditorCrossSortableProp({
|
||||||
label: '默认选项',
|
label: '默认选项',
|
||||||
labelPosition: 'top',
|
labelPosition: 'top',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
showItemPropsConfig: true,
|
showItemPropsConfig: true,
|
||||||
defaultValue: [
|
defaultValue: defaultTabbarItems.map((item) => {
|
||||||
{ label: '首页', value: 'index', component: tabbarItem, block: createNewBlock(tabbarItem) },
|
const block = createNewBlock(getTabbarItem());
|
||||||
{
|
block.props.icon = item.icon;
|
||||||
label: '导航',
|
return { label: item.title, value: item.icon, component: getTabbarItem(), block };
|
||||||
value: 'navigation',
|
}),
|
||||||
component: tabbarItem,
|
|
||||||
block: createNewBlock(tabbarItem)
|
|
||||||
},
|
|
||||||
{ label: '我的', value: 'user', component: tabbarItem, block: createNewBlock(tabbarItem) }
|
|
||||||
]
|
|
||||||
}),
|
}),
|
||||||
fixed: createEditorSwitchProp({ label: '是否固定在底部', defaultValue: true }),
|
fixed: createEditorSwitchProp({ label: '是否固定在底部', defaultValue: true }),
|
||||||
border: createEditorSwitchProp({ label: '是否显示外边框', defaultValue: true }),
|
border: createEditorSwitchProp({ label: '是否显示外边框', defaultValue: true }),
|
||||||
|
@ -116,15 +126,15 @@ export default {
|
||||||
// }),
|
// }),
|
||||||
safeAreaInsetBottom: createEditorSwitchProp({
|
safeAreaInsetBottom: createEditorSwitchProp({
|
||||||
label: '是否开启底部安全区适配,设置 fixed 时默认开启',
|
label: '是否开启底部安全区适配,设置 fixed 时默认开启',
|
||||||
defaultValue: false
|
defaultValue: false,
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
events: [
|
events: [
|
||||||
{ label: '点击左侧按钮时触发', value: 'click-left' },
|
{ label: '点击左侧按钮时触发', value: 'click-left' },
|
||||||
{ label: '点击右侧按钮时触发', value: 'click-right' }
|
{ label: '点击右侧按钮时触发', value: 'click-right' },
|
||||||
],
|
],
|
||||||
draggable: false,
|
draggable: false,
|
||||||
resize: {
|
resize: {
|
||||||
width: true
|
width: true,
|
||||||
}
|
},
|
||||||
} as VisualEditorComponent
|
} as VisualEditorComponent;
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
* @Description: 导航栏项
|
* @Description: 导航栏项
|
||||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\tabbar\tabbar-item.tsx
|
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\tabbar\tabbar-item.tsx
|
||||||
*/
|
*/
|
||||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||||
import { createEditorInputProp, createEditorSwitchProp } from '@/visual-editor/visual-editor.props'
|
import { createEditorInputProp, createEditorSwitchProp } from '@/visual-editor/visual-editor.props';
|
||||||
|
|
||||||
export default {
|
export const getTabbarItem = (): VisualEditorComponent => ({
|
||||||
key: 'tabbar-item',
|
key: 'tabbar-item',
|
||||||
moduleName: 'baseWidgets',
|
moduleName: 'baseWidgets',
|
||||||
label: '底部标签栏',
|
label: '底部标签栏',
|
||||||
|
@ -24,7 +24,7 @@ export default {
|
||||||
iconPrefix: createEditorInputProp({
|
iconPrefix: createEditorInputProp({
|
||||||
label: '图标类名前缀',
|
label: '图标类名前缀',
|
||||||
tips: '图标类名前缀,同 Icon 组件的 class-prefix 属性',
|
tips: '图标类名前缀,同 Icon 组件的 class-prefix 属性',
|
||||||
defaultValue: 'van-icon'
|
defaultValue: 'van-icon',
|
||||||
}),
|
}),
|
||||||
dot: createEditorSwitchProp({ label: '是否显示图标右上角小红点', defaultValue: false }),
|
dot: createEditorSwitchProp({ label: '是否显示图标右上角小红点', defaultValue: false }),
|
||||||
badge: createEditorInputProp({ label: '图标右上角徽标的内容', defaultValue: '' }),
|
badge: createEditorInputProp({ label: '图标右上角徽标的内容', defaultValue: '' }),
|
||||||
|
@ -34,14 +34,11 @@ export default {
|
||||||
// tips: '点击后跳转的目标路由对象,同 vue-router 的 to 属性',
|
// tips: '点击后跳转的目标路由对象,同 vue-router 的 to 属性',
|
||||||
// defaultValue: ''
|
// defaultValue: ''
|
||||||
// }),
|
// }),
|
||||||
replace: createEditorSwitchProp({ label: '是否在跳转时替换当前页面历史', defaultValue: false })
|
replace: createEditorSwitchProp({ label: '是否在跳转时替换当前页面历史', defaultValue: false }),
|
||||||
},
|
},
|
||||||
events: [
|
events: [
|
||||||
{ label: '点击左侧按钮时触发', value: 'click-left' },
|
{ label: '点击左侧按钮时触发', value: 'click-left' },
|
||||||
{ label: '点击右侧按钮时触发', value: 'click-right' }
|
{ label: '点击右侧按钮时触发', value: 'click-right' },
|
||||||
],
|
],
|
||||||
draggable: false,
|
draggable: false,
|
||||||
resize: {
|
});
|
||||||
width: true
|
|
||||||
}
|
|
||||||
} as VisualEditorComponent
|
|
||||||
|
|
|
@ -68,7 +68,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
|
||||||
import Preview from './preview.vue';
|
import Preview from './preview.vue';
|
||||||
import { useVisualData, localKey } from '@/visual-editor/hooks/useVisualData';
|
import { useVisualData, localKey } from '@/visual-editor/hooks/useVisualData';
|
||||||
import { BASE_URL } from '@/visual-editor/utils';
|
import { BASE_URL } from '@/visual-editor/utils';
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\base-widgets\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\base-widgets\index.tsx
|
||||||
*/
|
*/
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { visualConfig } from '@/visual.config';
|
import { visualConfig } from '@/visual.config';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
import { createNewBlock } from '@/visual-editor/visual-editor.utils';
|
import { createNewBlock } from '@/visual-editor/visual-editor.utils';
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\container-component\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\container-component\index.tsx
|
||||||
*/
|
*/
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { visualConfig } from '@/visual.config';
|
import { visualConfig } from '@/visual.config';
|
||||||
import Draggable from 'vuedraggable';
|
import Draggable from 'vuedraggable';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||||
import type { FetchApiItem, VisualEditorModel } from '@/visual-editor/visual-editor.utils';
|
import type { FetchApiItem, VisualEditorModel } from '@/visual-editor/visual-editor.utils';
|
||||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { generateNanoid } from '@/visual-editor/utils/';
|
import { generateNanoid } from '@/visual-editor/utils/';
|
||||||
import { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum';
|
import { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum';
|
||||||
import { useImportSwaggerJsonModal } from './utils';
|
import { useImportSwaggerJsonModal } from './utils';
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
import { useVisualData, fieldTypes } from '@/visual-editor/hooks/useVisualData';
|
import { useVisualData, fieldTypes } from '@/visual-editor/hooks/useVisualData';
|
||||||
import type { VisualEditorModel } from '@/visual-editor/visual-editor.utils';
|
import type { VisualEditorModel } from '@/visual-editor/visual-editor.utils';
|
||||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { generateNanoid } from '@/visual-editor/utils/';
|
import { generateNanoid } from '@/visual-editor/utils/';
|
||||||
import { useImportSwaggerJsonModal } from './utils';
|
import { useImportSwaggerJsonModal } from './utils';
|
||||||
import { Delete, Edit } from '@element-plus/icons-vue';
|
import { Delete, Edit } from '@element-plus/icons-vue';
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { isObject } from '@/visual-editor/utils/is';
|
||||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||||
import { PropConfig } from '../prop-config';
|
import { PropConfig } from '../prop-config';
|
||||||
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { Rank, CirclePlus, Remove } from '@element-plus/icons-vue';
|
import { Rank, CirclePlus, Remove } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
interface OptionItem extends LabelValue {
|
interface OptionItem extends LabelValue {
|
||||||
|
@ -175,7 +175,7 @@ export const CrossSortableOptionsEditor = defineComponent({
|
||||||
<ElTabs type={'border-card'}>
|
<ElTabs type={'border-card'}>
|
||||||
{state.list.map((item: OptionItem) => (
|
{state.list.map((item: OptionItem) => (
|
||||||
<ElTabPane label={item.label} key={item.label}>
|
<ElTabPane label={item.label} key={item.label}>
|
||||||
<ElForm labelPosition={'left'}>
|
<ElForm labelPosition={'left'} size="small">
|
||||||
<PropConfig component={item.component} block={item.block} />
|
<PropConfig component={item.component} block={item.block} />
|
||||||
</ElForm>
|
</ElForm>
|
||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
import { useDotProp } from '@/visual-editor/hooks/useDotProp';
|
import { useDotProp } from '@/visual-editor/hooks/useDotProp';
|
||||||
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props';
|
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props';
|
||||||
import { TablePropEditor, CrossSortableOptionsEditor } from '../../components';
|
import { TablePropEditor, CrossSortableOptionsEditor } from '../../components';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||||
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||||
import { Warning } from '@element-plus/icons-vue';
|
import { Warning } from '@element-plus/icons-vue';
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
import { VisualEditorProps } from '@/visual-editor/visual-editor.props'
|
import { VisualEditorProps } from '@/visual-editor/visual-editor.props';
|
||||||
import { defineComponent, getCurrentInstance, onMounted, PropType, reactive, createApp } from 'vue'
|
import { defineComponent, getCurrentInstance, onMounted, PropType, reactive, createApp } from 'vue';
|
||||||
import { defer } from '@/visual-editor/utils/defer'
|
import { defer } from '@/visual-editor/utils/defer';
|
||||||
import { ElButton, ElDialog, ElTable, ElTableColumn, ElInput } from 'element-plus'
|
import { ElButton, ElDialog, ElTable, ElTableColumn, ElInput } from 'element-plus';
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
export interface TablePropEditorServiceOption {
|
export interface TablePropEditorServiceOption {
|
||||||
data: any[]
|
data: any[];
|
||||||
config: VisualEditorProps
|
config: VisualEditorProps;
|
||||||
onConfirm: (val: any[]) => void
|
onConfirm: (val: any[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServiceComponent = defineComponent({
|
const ServiceComponent = defineComponent({
|
||||||
props: {
|
props: {
|
||||||
option: { type: Object as PropType<TablePropEditorServiceOption>, required: true }
|
option: { type: Object as PropType<TablePropEditorServiceOption>, required: true },
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const ctx = getCurrentInstance()!
|
const ctx = getCurrentInstance()!;
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
option: props.option,
|
option: props.option,
|
||||||
showFlag: false,
|
showFlag: false,
|
||||||
mounted: (() => {
|
mounted: (() => {
|
||||||
const dfd = defer()
|
const dfd = defer();
|
||||||
onMounted(() => setTimeout(() => dfd.resolve(), 0))
|
onMounted(() => setTimeout(() => dfd.resolve(), 0));
|
||||||
return dfd.promise
|
return dfd.promise;
|
||||||
})(),
|
})(),
|
||||||
editData: [] as any[]
|
editData: [] as any[],
|
||||||
})
|
});
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
service: (option: TablePropEditorServiceOption) => {
|
service: (option: TablePropEditorServiceOption) => {
|
||||||
state.option = option
|
state.option = option;
|
||||||
state.editData = cloneDeep(option.data || [])
|
state.editData = cloneDeep(option.data || []);
|
||||||
methods.show()
|
methods.show();
|
||||||
},
|
},
|
||||||
show: async () => {
|
show: async () => {
|
||||||
await state.mounted
|
await state.mounted;
|
||||||
state.showFlag = true
|
state.showFlag = true;
|
||||||
},
|
},
|
||||||
hide: () => {
|
hide: () => {
|
||||||
state.showFlag = false
|
state.showFlag = false;
|
||||||
},
|
},
|
||||||
add: () => {
|
add: () => {
|
||||||
state.editData.push({})
|
state.editData.push({});
|
||||||
},
|
},
|
||||||
reset: () => {
|
reset: () => {
|
||||||
state.editData = cloneDeep(state.option.data)
|
state.editData = cloneDeep(state.option.data);
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
const handler = {
|
const handler = {
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
state.option.onConfirm(state.editData)
|
state.option.onConfirm(state.editData);
|
||||||
methods.hide()
|
methods.hide();
|
||||||
},
|
},
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
methods.hide()
|
methods.hide();
|
||||||
},
|
},
|
||||||
onDelete: (index: number) => {
|
onDelete: (index: number) => {
|
||||||
state.editData.splice(index, 1)
|
state.editData.splice(index, 1);
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
Object.assign(ctx.proxy!, methods)
|
Object.assign(ctx.proxy!, methods);
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<>
|
<>
|
||||||
|
@ -79,7 +79,7 @@ const ServiceComponent = defineComponent({
|
||||||
{state.option.config.table!.options.map((item) => (
|
{state.option.config.table!.options.map((item) => (
|
||||||
<ElTableColumn {...({ label: item.label } as any)}>
|
<ElTableColumn {...({ label: item.label } as any)}>
|
||||||
{{
|
{{
|
||||||
default: ({ row }: { row: any }) => <ElInput v-model={row[item.field]} />
|
default: ({ row }: { row: any }) => <ElInput v-model={row[item.field]} />,
|
||||||
}}
|
}}
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
))}
|
))}
|
||||||
|
@ -92,7 +92,7 @@ const ServiceComponent = defineComponent({
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</ElButton>
|
</ElButton>
|
||||||
)
|
),
|
||||||
}}
|
}}
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
</ElTable>
|
</ElTable>
|
||||||
|
@ -105,28 +105,28 @@ const ServiceComponent = defineComponent({
|
||||||
确定
|
确定
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
}}
|
}}
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export const $$tablePropEditor = (() => {
|
export const $$tablePropEditor = (() => {
|
||||||
let ins: any
|
let ins: any;
|
||||||
return (option: Omit<TablePropEditorServiceOption, 'onConfirm'>) => {
|
return (option: Omit<TablePropEditorServiceOption, 'onConfirm'>) => {
|
||||||
if (!ins) {
|
if (!ins) {
|
||||||
const el = document.createElement('div')
|
const el = document.createElement('div');
|
||||||
document.body.appendChild(el)
|
document.body.appendChild(el);
|
||||||
const app = createApp(ServiceComponent, { option })
|
const app = createApp(ServiceComponent, { option });
|
||||||
ins = app.mount(el)
|
ins = app.mount(el);
|
||||||
}
|
}
|
||||||
const dfd = defer<any[]>()
|
const dfd = defer<any[]>();
|
||||||
ins.service({
|
ins.service({
|
||||||
...option,
|
...option,
|
||||||
onConfirm: dfd.resolve
|
onConfirm: dfd.resolve,
|
||||||
})
|
});
|
||||||
return dfd.promise
|
return dfd.promise;
|
||||||
}
|
};
|
||||||
})()
|
})();
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
import type { Action } from '@/visual-editor/visual-editor.utils';
|
import type { Action } from '@/visual-editor/visual-editor.utils';
|
||||||
import { generateNanoid } from '@/visual-editor/utils/';
|
import { generateNanoid } from '@/visual-editor/utils/';
|
||||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
activeNames: string[];
|
activeNames: string[];
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
focus: outElement.focus,
|
focus: outElement.focus,
|
||||||
focusWithChild: outElement.focusWithChild,
|
focusWithChild: outElement.focusWithChild,
|
||||||
drag,
|
drag,
|
||||||
['has-slot']: !!Object.keys(outElement.props.slots || {}).length
|
['has-slot']: !!Object.keys(outElement.props.slots || {}).length,
|
||||||
}"
|
}"
|
||||||
@contextmenu.stop.prevent="onContextmenuBlock($event, outElement)"
|
@contextmenu.stop.prevent="onContextmenuBlock($event, outElement)"
|
||||||
@mousedown="selectComp(outElement)"
|
@mousedown="selectComp(outElement)"
|
||||||
|
@ -25,7 +25,9 @@
|
||||||
:key="outElement._vid"
|
:key="outElement._vid"
|
||||||
:element="outElement"
|
:element="outElement"
|
||||||
:style="{
|
:style="{
|
||||||
pointerEvents: Object.keys(outElement.props?.slots || {}).length ? 'auto' : 'none'
|
pointerEvents: Object.keys(outElement.props?.slots || {}).length
|
||||||
|
? 'auto'
|
||||||
|
: 'none',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
|
@ -52,54 +54,54 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { defineComponent, reactive, watchEffect, toRefs } from 'vue'
|
import { defineComponent, reactive, watchEffect, toRefs } from 'vue';
|
||||||
import type { VisualEditorBlockData } from '@/visual-editor/visual-editor.utils'
|
import type { VisualEditorBlockData } from '@/visual-editor/visual-editor.utils';
|
||||||
import DraggableTransitionGroup from './draggable-transition-group.vue'
|
import DraggableTransitionGroup from './draggable-transition-group.vue';
|
||||||
import { $$dropdown, DropdownOption } from '@/visual-editor/utils/dropdown-service'
|
import { $$dropdown, DropdownOption } from '@/visual-editor/utils/dropdown-service';
|
||||||
import CompRender from './comp-render'
|
import CompRender from './comp-render';
|
||||||
import SlotItem from './slot-item.vue'
|
import SlotItem from './slot-item.vue';
|
||||||
import MonacoEditor from '@/visual-editor/components/common/monaco-editor/MonacoEditor'
|
import MonacoEditor from '@/visual-editor/components/common/monaco-editor/MonacoEditor';
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
|
import { useGlobalProperties } from '@/hooks/useGlobalProperties';
|
||||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||||
import { useModal } from '@/visual-editor/hooks/useModal'
|
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||||
import { generateNanoid } from '@/visual-editor/utils'
|
import { generateNanoid } from '@/visual-editor/utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SimulatorEditor',
|
name: 'SimulatorEditor',
|
||||||
components: {
|
components: {
|
||||||
DraggableTransitionGroup,
|
DraggableTransitionGroup,
|
||||||
CompRender,
|
CompRender,
|
||||||
SlotItem
|
SlotItem,
|
||||||
},
|
},
|
||||||
emits: ['on-selected'],
|
emits: ['on-selected'],
|
||||||
setup() {
|
setup() {
|
||||||
const { currentPage, setCurrentBlock } = useVisualData()
|
const { currentPage, setCurrentBlock } = useVisualData();
|
||||||
|
|
||||||
const { globalProperties } = useGlobalProperties()
|
const { globalProperties } = useGlobalProperties();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
drag: false
|
drag: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 操作当前页面样式表
|
* @description 操作当前页面样式表
|
||||||
*/
|
*/
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const { bgImage, bgColor } = currentPage.value.config
|
const { bgImage, bgColor } = currentPage.value.config;
|
||||||
const bodyStyleStr = `
|
const bodyStyleStr = `
|
||||||
.simulator-editor-content {
|
.simulator-editor-content {
|
||||||
background-color: ${bgColor};
|
background-color: ${bgColor};
|
||||||
background-image: url(${bgImage});
|
background-image: url(${bgImage});
|
||||||
}`
|
}`;
|
||||||
const styleSheets = document.styleSheets[0]
|
const styleSheets = document.styleSheets[0];
|
||||||
const firstCssRule = document.styleSheets[0].cssRules[0]
|
const firstCssRule = document.styleSheets[0].cssRules[0];
|
||||||
const isExistContent = firstCssRule.cssText.includes('.simulator-editor-content')
|
const isExistContent = firstCssRule.cssText.includes('.simulator-editor-content');
|
||||||
if (isExistContent) {
|
if (isExistContent) {
|
||||||
styleSheets.deleteRule(0)
|
styleSheets.deleteRule(0);
|
||||||
}
|
}
|
||||||
styleSheets.insertRule(bodyStyleStr)
|
styleSheets.insertRule(bodyStyleStr);
|
||||||
})
|
});
|
||||||
|
|
||||||
//递归实现
|
//递归实现
|
||||||
//@leafId 为你要查找的id,
|
//@leafId 为你要查找的id,
|
||||||
|
@ -108,78 +110,81 @@ export default defineComponent({
|
||||||
const findPathByLeafId = (
|
const findPathByLeafId = (
|
||||||
leafId,
|
leafId,
|
||||||
nodes: VisualEditorBlockData[] = [],
|
nodes: VisualEditorBlockData[] = [],
|
||||||
path: VisualEditorBlockData[] = []
|
path: VisualEditorBlockData[] = [],
|
||||||
) => {
|
) => {
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const tmpPath = path.concat()
|
const tmpPath = path.concat();
|
||||||
tmpPath.push(nodes[i])
|
tmpPath.push(nodes[i]);
|
||||||
if (leafId == nodes[i]._vid) {
|
if (leafId == nodes[i]._vid) {
|
||||||
return tmpPath
|
return tmpPath;
|
||||||
}
|
}
|
||||||
const slots = nodes[i].props?.slots || {}
|
const slots = nodes[i].props?.slots || {};
|
||||||
const keys = Object.keys(slots)
|
const keys = Object.keys(slots);
|
||||||
for (let j = 0; j < keys.length; j++) {
|
for (let j = 0; j < keys.length; j++) {
|
||||||
const children = slots[keys[j]]?.children
|
const children = slots[keys[j]]?.children;
|
||||||
if (children) {
|
if (children) {
|
||||||
const findResult = findPathByLeafId(leafId, children, tmpPath)
|
const findResult = findPathByLeafId(leafId, children, tmpPath);
|
||||||
if (findResult) {
|
if (findResult) {
|
||||||
return findResult
|
return findResult;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 给当前点击的组件设置聚焦
|
// 给当前点击的组件设置聚焦
|
||||||
const handleSlotsFocus = (block: VisualEditorBlockData, _vid: string) => {
|
const handleSlotsFocus = (block: VisualEditorBlockData, _vid: string) => {
|
||||||
const slots = block.props?.slots || {}
|
const slots = block.props?.slots || {};
|
||||||
if (Object.keys(slots).length > 0) {
|
if (Object.keys(slots).length > 0) {
|
||||||
Object.keys(slots).forEach((key) => {
|
Object.keys(slots).forEach((key) => {
|
||||||
slots[key]?.children?.forEach((item) => {
|
slots[key]?.children?.forEach((item) => {
|
||||||
item.focusWithChild = false
|
item.focusWithChild = false;
|
||||||
item.focus = item._vid == _vid
|
item.focus = item._vid == _vid;
|
||||||
if (item.focus) {
|
if (item.focus) {
|
||||||
const arr = findPathByLeafId(_vid, currentPage.value.blocks)
|
const arr = findPathByLeafId(_vid, currentPage.value.blocks);
|
||||||
arr.forEach((n) => (n.focusWithChild = true))
|
arr.forEach((n) => (n.focusWithChild = true));
|
||||||
}
|
}
|
||||||
if (Object.keys(item.props?.slots || {}).length) {
|
if (Object.keys(item.props?.slots || {}).length) {
|
||||||
handleSlotsFocus(item, _vid)
|
handleSlotsFocus(item, _vid);
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 选择要操作的组件
|
// 选择要操作的组件
|
||||||
const selectComp = (element: VisualEditorBlockData) => {
|
const selectComp = (element: VisualEditorBlockData) => {
|
||||||
setCurrentBlock(element)
|
setCurrentBlock(element);
|
||||||
currentPage.value.blocks.forEach((block) => {
|
currentPage.value.blocks.forEach((block) => {
|
||||||
block.focus = element._vid == block._vid
|
block.focus = element._vid == block._vid;
|
||||||
block.focusWithChild = false
|
block.focusWithChild = false;
|
||||||
handleSlotsFocus(block, element._vid)
|
handleSlotsFocus(block, element._vid);
|
||||||
element.focusWithChild = false
|
element.focusWithChild = false;
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除组件
|
* 删除组件
|
||||||
*/
|
*/
|
||||||
const deleteComp = (block: VisualEditorBlockData, parentBlocks = currentPage.value.blocks) => {
|
const deleteComp = (
|
||||||
console.log(block, 'block')
|
block: VisualEditorBlockData,
|
||||||
const index = parentBlocks.findIndex((item) => item._vid == block._vid)
|
parentBlocks = currentPage.value.blocks,
|
||||||
|
) => {
|
||||||
|
console.log(block, 'block');
|
||||||
|
const index = parentBlocks.findIndex((item) => item._vid == block._vid);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
delete globalProperties.$$refs[parentBlocks[index]._vid]
|
delete globalProperties.$$refs[parentBlocks[index]._vid];
|
||||||
const delTarget = parentBlocks.splice(index, 1)[0]
|
const delTarget = parentBlocks.splice(index, 1)[0];
|
||||||
if (delTarget.focus) {
|
if (delTarget.focus) {
|
||||||
setCurrentBlock({} as VisualEditorBlockData)
|
setCurrentBlock({} as VisualEditorBlockData);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onContextmenuBlock = (
|
const onContextmenuBlock = (
|
||||||
e: MouseEvent,
|
e: MouseEvent,
|
||||||
block: VisualEditorBlockData,
|
block: VisualEditorBlockData,
|
||||||
parentBlocks = currentPage.value.blocks
|
parentBlocks = currentPage.value.blocks,
|
||||||
) => {
|
) => {
|
||||||
$$dropdown({
|
$$dropdown({
|
||||||
reference: e,
|
reference: e,
|
||||||
|
@ -190,24 +195,24 @@ export default defineComponent({
|
||||||
icon="el-icon-document-copy"
|
icon="el-icon-document-copy"
|
||||||
{...{
|
{...{
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const index = parentBlocks.findIndex((item) => item._vid == block._vid)
|
const index = parentBlocks.findIndex((item) => item._vid == block._vid);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
const setBlockVid = (block: VisualEditorBlockData) => {
|
const setBlockVid = (block: VisualEditorBlockData) => {
|
||||||
block._vid = `vid_${generateNanoid()}`
|
block._vid = `vid_${generateNanoid()}`;
|
||||||
block.focus = false
|
block.focus = false;
|
||||||
const slots = block?.props?.slots || {}
|
const slots = block?.props?.slots || {};
|
||||||
const slotKeys = Object.keys(slots)
|
const slotKeys = Object.keys(slots);
|
||||||
if (slotKeys.length) {
|
if (slotKeys.length) {
|
||||||
slotKeys.forEach((slotKey) => {
|
slotKeys.forEach((slotKey) => {
|
||||||
slots[slotKey]?.children?.forEach((child) => setBlockVid(child))
|
slots[slotKey]?.children?.forEach((child) => setBlockVid(child));
|
||||||
})
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
const blockCopy = cloneDeep(parentBlocks[index])
|
|
||||||
setBlockVid(blockCopy)
|
|
||||||
parentBlocks.splice(index + 1, 0, blockCopy)
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
const blockCopy = cloneDeep(parentBlocks[index]);
|
||||||
|
setBlockVid(blockCopy);
|
||||||
|
parentBlocks.splice(index + 1, 0, blockCopy);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<DropdownOption
|
<DropdownOption
|
||||||
|
@ -219,7 +224,7 @@ export default defineComponent({
|
||||||
title: '节点信息',
|
title: '节点信息',
|
||||||
footer: null,
|
footer: null,
|
||||||
props: {
|
props: {
|
||||||
width: 600
|
width: 600,
|
||||||
},
|
},
|
||||||
content: () => (
|
content: () => (
|
||||||
<MonacoEditor
|
<MonacoEditor
|
||||||
|
@ -227,31 +232,31 @@ export default defineComponent({
|
||||||
layout={{ width: 530, height: 600 }}
|
layout={{ width: 530, height: 600 }}
|
||||||
vid={block._vid}
|
vid={block._vid}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
})
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<DropdownOption
|
<DropdownOption
|
||||||
label="删除节点"
|
label="删除节点"
|
||||||
icon="el-icon-delete"
|
icon="el-icon-delete"
|
||||||
{...{
|
{...{
|
||||||
onClick: () => deleteComp(block, parentBlocks)
|
onClick: () => deleteComp(block, parentBlocks),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
currentPage,
|
currentPage,
|
||||||
deleteComp,
|
deleteComp,
|
||||||
selectComp,
|
selectComp,
|
||||||
onContextmenuBlock
|
onContextmenuBlock,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import './func.scss';
|
@import './func.scss';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// import { useCommander } from './plugins/command.plugin'
|
// import { useCommander } from './plugins/command.plugin'
|
||||||
// import { VisualEditorBlockData, VisualEditorModelValue } from './visual-editor.utils'
|
// import { VisualEditorBlockData, VisualEditorModelValue } from './visual-editor.utils'
|
||||||
// import { cloneDeep } from 'lodash'
|
// import { cloneDeep } from 'lodash-es'
|
||||||
//
|
//
|
||||||
// export function useVisualCommand({
|
// export function useVisualCommand({
|
||||||
// focusData,
|
// focusData,
|
||||||
|
|
|
@ -55,7 +55,9 @@
|
||||||
"preview/**/*.ts",
|
"preview/**/*.ts",
|
||||||
"preview/**/*.d.ts",
|
"preview/**/*.d.ts",
|
||||||
"preview/**/*.tsx",
|
"preview/**/*.tsx",
|
||||||
"preview/**/*.vue"
|
"preview/**/*.vue",
|
||||||
|
"components.d.ts",
|
||||||
|
"auto-imports.d.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/** 提取Promise返回值 */
|
||||||
|
type UnboxPromise<T extends Promise<any>> = T extends Promise<infer U> ? U : never;
|
||||||
|
|
||||||
|
/** 将联合类型转为交叉类型 */
|
||||||
|
declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
||||||
|
k: infer I,
|
||||||
|
) => void
|
||||||
|
? I
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/** eg: type result = StringToUnion<'abc'> 结果:'a'|'b'|'c'*/
|
||||||
|
type StringToUnion<S extends string> = S extends `${infer S1}${infer S2}`
|
||||||
|
? S1 | StringToUnion<S2>
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/** 字符串替换,类似js的字符串replace方法 */
|
||||||
|
type Replace<
|
||||||
|
Str extends string,
|
||||||
|
From extends string,
|
||||||
|
To extends string,
|
||||||
|
> = Str extends `${infer Left}${From}${infer Right}` ? `${Left}${To}${Right}` : Str;
|
||||||
|
|
||||||
|
/** 字符串替换,类似js的字符串replaceAll方法 */
|
||||||
|
type ReplaceAll<
|
||||||
|
Str extends string,
|
||||||
|
From extends string,
|
||||||
|
To extends string,
|
||||||
|
> = Str extends `${infer Left}${From}${infer Right}`
|
||||||
|
? Replace<Replace<`${Left}${To}${Right}`, From, To>, From, To>
|
||||||
|
: Str;
|
||||||
|
|
||||||
|
/** eg: type result = CamelCase<'foo-bar-baz'>, 结果:fooBarBaz */
|
||||||
|
type CamelCase<S extends string> = S extends `${infer S1}-${infer S2}`
|
||||||
|
? S2 extends Capitalize<S2>
|
||||||
|
? `${S1}-${CamelCase<S2>}`
|
||||||
|
: `${S1}${CamelCase<Capitalize<S2>>}`
|
||||||
|
: S;
|
||||||
|
|
||||||
|
/** eg: type result = StringToArray<'abc'>, 结果:['a', 'b', 'c'] */
|
||||||
|
type StringToArray<S extends string, T extends any[] = []> = S extends `${infer S1}${infer S2}`
|
||||||
|
? StringToArray<S2, [...T, S1]>
|
||||||
|
: T;
|
||||||
|
|
||||||
|
/** `RequiredKeys`是用来获取所有必填字段,其中这些必填字段组合成一个联合类型 */
|
||||||
|
type RequiredKeys<T> = {
|
||||||
|
[P in keyof T]: T extends Record<P, T[P]> ? P : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
/** `OptionalKeys`是用来获取所有可选字段,其中这些可选字段组合成一个联合类型 */
|
||||||
|
type OptionalKeys<T> = {
|
||||||
|
[P in keyof T]: {} extends Pick<T, P> ? P : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
/** `GetRequired`是用来获取一个类型中,所有必填键及其类型所组成的一个新类型的 */
|
||||||
|
type GetRequired<T> = {
|
||||||
|
[P in RequiredKeys<T>]-?: T[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** `GetOptional`是用来获取一个类型中,所有可选键及其类型所组成的一个新类型的 */
|
||||||
|
type GetOptional<T> = {
|
||||||
|
[P in OptionalKeys<T>]?: T[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** type result1 = Includes<[1, 2, 3, 4], '4'> 结果: false; type result2 = Includes<[1, 2, 3, 4], 4> 结果: true */
|
||||||
|
type Includes<T extends any[], K> = K extends T[number] ? true : false;
|
||||||
|
|
||||||
|
/** eg:type result = MyConcat<[1, 2], [3, 4]> 结果:[1, 2, 3, 4]*/
|
||||||
|
type MyConcat<T extends any[], U extends any[]> = [...T, ...U];
|
||||||
|
/** eg: type result1 = MyPush<[1, 2, 3], 4> 结果:[1, 2, 3, 4] */
|
||||||
|
type MyPush<T extends any[], K> = [...T, K];
|
||||||
|
/** eg: type result3 = MyPop<[1, 2, 3]> 结果:[1, 2] */
|
||||||
|
type MyPop<T extends any[]> = T extends [...infer L, infer R] ? L : never; // eslint-disable-line
|
|
@ -55,9 +55,17 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
||||||
targets: ['defaults', 'not IE 11'],
|
targets: ['defaults', 'not IE 11'],
|
||||||
}),
|
}),
|
||||||
AutoImport({
|
AutoImport({
|
||||||
resolvers: [ElementPlusResolver(), VantResolver()],
|
include: [
|
||||||
|
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
|
||||||
|
/\.vue$/,
|
||||||
|
/\.vue\?vue/, // .vue
|
||||||
|
/\.md$/, // .md
|
||||||
|
],
|
||||||
|
dts: true,
|
||||||
|
imports: ['vue', 'vue-router'],
|
||||||
}),
|
}),
|
||||||
Components({
|
Components({
|
||||||
|
dts: true,
|
||||||
resolvers: [ElementPlusResolver(), VantResolver()],
|
resolvers: [ElementPlusResolver(), VantResolver()],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -99,7 +107,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['@vueuse/core', 'element-plus', 'vant', 'lodash', 'vuedraggable'],
|
include: ['@vueuse/core', 'element-plus', 'vant', 'lodash-es', 'vuedraggable'],
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
@ -110,7 +118,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
||||||
// 设置代理,根据项目实际情况配置
|
// 设置代理,根据项目实际情况配置
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://29135jo738.zicp.vip/api/v1',
|
target: 'https://nest-api.buqiyuan.site/api/admin/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
rewrite: (path) => path.replace('/api/', '/'),
|
rewrite: (path) => path.replace('/api/', '/'),
|
||||||
|
|
Loading…
Reference in New Issue