docs: update README

main
bqy_fe 3 years ago
parent 27fe5fc752
commit 9a05f858db
  1. 56
      README.md
  2. 52
      auto-imports.d.ts
  3. 7
      components.d.ts
  4. 46
      package.json
  5. 1056
      pnpm-lock.yaml
  6. 2
      src/packages/base-widgets/slider/index.tsx
  7. 120
      src/packages/base-widgets/tabbar/index.tsx
  8. 17
      src/packages/base-widgets/tabbar/tabbar-item.tsx
  9. 1
      src/visual-editor/components/header/index.vue
  10. 2
      src/visual-editor/components/left-aside/components/base-widgets/index.tsx
  11. 2
      src/visual-editor/components/left-aside/components/container-component/index.tsx
  12. 2
      src/visual-editor/components/left-aside/components/data-source/data-fetch.vue
  13. 2
      src/visual-editor/components/left-aside/components/data-source/data-model.vue
  14. 4
      src/visual-editor/components/right-attribute-panel/components/attr-editor/components/cross-sortable-options-editor/cross-sortable-options-editor.tsx
  15. 2
      src/visual-editor/components/right-attribute-panel/components/attr-editor/components/prop-config/index.tsx
  16. 98
      src/visual-editor/components/right-attribute-panel/components/attr-editor/components/table-prop-editor/table-prop-edit.service.tsx
  17. 2
      src/visual-editor/components/right-attribute-panel/components/event-action/index.tsx
  18. 477
      src/visual-editor/components/simulator-editor/simulator-editor.vue
  19. 2
      src/visual-editor/visual.command.tsx
  20. 4
      tsconfig.json
  21. 72
      types/utils.d.ts
  22. 14
      vite.config.ts

@ -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
```

52
auto-imports.d.ts vendored

@ -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 {};

7
components.d.ts vendored

@ -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'];
} }
} }

@ -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"
}, },

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,273 +54,276 @@
</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)
})
//
//@leafId id
//@nodes Json
//@path 使
const findPathByLeafId = (
leafId,
nodes: VisualEditorBlockData[] = [],
path: VisualEditorBlockData[] = []
) => {
for (let i = 0; i < nodes.length; i++) {
const tmpPath = path.concat()
tmpPath.push(nodes[i])
if (leafId == nodes[i]._vid) {
return tmpPath
} }
const slots = nodes[i].props?.slots || {} styleSheets.insertRule(bodyStyleStr);
const keys = Object.keys(slots) });
for (let j = 0; j < keys.length; j++) {
const children = slots[keys[j]]?.children //
if (children) { //@leafId id
const findResult = findPathByLeafId(leafId, children, tmpPath) //@nodes Json
if (findResult) { //@path 使
return findResult const findPathByLeafId = (
leafId,
nodes: VisualEditorBlockData[] = [],
path: VisualEditorBlockData[] = [],
) => {
for (let i = 0; i < nodes.length; i++) {
const tmpPath = path.concat();
tmpPath.push(nodes[i]);
if (leafId == nodes[i]._vid) {
return tmpPath;
}
const slots = nodes[i].props?.slots || {};
const keys = Object.keys(slots);
for (let j = 0; j < keys.length; j++) {
const children = slots[keys[j]]?.children;
if (children) {
const findResult = findPathByLeafId(leafId, children, tmpPath);
if (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,
if (index != -1) { ) => {
delete globalProperties.$$refs[parentBlocks[index]._vid] console.log(block, 'block');
const delTarget = parentBlocks.splice(index, 1)[0] const index = parentBlocks.findIndex((item) => item._vid == block._vid);
if (delTarget.focus) { if (index != -1) {
setCurrentBlock({} as VisualEditorBlockData) delete globalProperties.$$refs[parentBlocks[index]._vid];
const delTarget = parentBlocks.splice(index, 1)[0];
if (delTarget.focus) {
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,
content: () => ( content: () => (
<> <>
<DropdownOption <DropdownOption
label="复制节点" label="复制节点"
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
} label="查看节点"
}} icon="el-icon-view"
/> {...{
<DropdownOption onClick: () =>
label="查看节点" useModal({
icon="el-icon-view" title: '节点信息',
{...{ footer: null,
onClick: () => props: {
useModal({ width: 600,
title: '节点信息', },
footer: null, content: () => (
props: { <MonacoEditor
width: 600 code={JSON.stringify(block)}
}, layout={{ width: 530, height: 600 }}
content: () => ( vid={block._vid}
<MonacoEditor />
code={JSON.stringify(block)} ),
layout={{ width: 530, height: 600 }} }),
vid={block._vid} }}
/> />
) <DropdownOption
}) label="删除节点"
}} icon="el-icon-delete"
/> {...{
<DropdownOption onClick: () => deleteComp(block, parentBlocks),
label="删除节点" }}
icon="el-icon-delete" />
{...{ </>
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';
.simulator-container { .simulator-container {
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding-right: 380px; padding-right: 380px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@media (max-width: 1114px) { @media (max-width: 1114px) {
padding-right: 0; padding-right: 0;
}
} }
}
.simulator-editor { .simulator-editor {
width: 660px; width: 660px;
height: 740px; height: 740px;
min-width: 660px; min-width: 660px;
padding: 60px 150px 0; padding: 60px 150px 0;
overflow: hidden auto; overflow: hidden auto;
background: #fafafa; background: #fafafa;
border-radius: 5px; border-radius: 5px;
box-sizing: border-box; box-sizing: border-box;
background-clip: content-box; background-clip: content-box;
contain: layout; contain: layout;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 0; width: 0;
} }
&-content { &-content {
min-height: 100%; min-height: 100%;
transform: translate(0); transform: translate(0);
box-shadow: 0 8px 12px #ebedf0; box-shadow: 0 8px 12px #ebedf0;
}
} }
}
.list-group-item { .list-group-item {
position: relative;
padding: 3px;
cursor: move;
> div {
position: relative; position: relative;
} padding: 3px;
cursor: move;
&.focus { > div {
@include showComponentBorder; position: relative;
} }
&.drag::after { &.focus {
display: none; @include showComponentBorder;
} }
&:not(.has-slot) { &.drag::after {
content: ''; display: none;
} }
&.focusWithChild { &:not(.has-slot) {
@include showContainerBorder; content: '';
} }
i { &.focusWithChild {
cursor: pointer; @include showContainerBorder;
}
i {
cursor: pointer;
}
} }
}
</style> </style>

@ -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",

72
types/utils.d.ts vendored

@ -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…
Cancel
Save