refactor: simplified code
This commit is contained in:
parent
74df8be4f2
commit
035663eb3b
|
@ -32,7 +32,6 @@ declare module '@vue/runtime-core' {
|
|||
RouterLink: typeof import('vue-router')['RouterLink'];
|
||||
RouterView: typeof import('vue-router')['RouterView'];
|
||||
}
|
||||
|
||||
export interface ComponentCustomProperties {
|
||||
vInfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll'];
|
||||
}
|
||||
|
|
32
package.json
32
package.json
|
@ -33,13 +33,13 @@
|
|||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.6",
|
||||
"@vant/touch-emulator": "^1.3.2",
|
||||
"@vueuse/core": "^8.7.5",
|
||||
"@vueuse/integrations": "^8.7.5",
|
||||
"@vueuse/core": "^8.9.1",
|
||||
"@vueuse/integrations": "^8.9.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^0.27.2",
|
||||
"dayjs": "^1.11.3",
|
||||
"dexie": "^3.2.2",
|
||||
"element-plus": "2.2.8",
|
||||
"element-plus": "2.2.9",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"nanoid": "^4.0.0",
|
||||
|
@ -57,9 +57,9 @@
|
|||
"@commitlint/cli": "^17.0.3",
|
||||
"@commitlint/config-conventional": "^17.0.3",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/node": "^18.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.3",
|
||||
"@typescript-eslint/parser": "^5.30.3",
|
||||
"@types/node": "^18.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||
"@typescript-eslint/parser": "^5.30.5",
|
||||
"@vitejs/plugin-legacy": "^1.8.2",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
||||
|
@ -71,32 +71,32 @@
|
|||
"eslint-define-config": "^1.5.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.1.1",
|
||||
"eslint-plugin-vue": "^9.2.0",
|
||||
"husky": "^8.0.1",
|
||||
"lint-staged": "^13.0.3",
|
||||
"lint-staged": "^12.5.0",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-html": "^1.4.1",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-scss": "^4.0.4",
|
||||
"prettier": "^2.7.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "1.53.0",
|
||||
"stylelint": "^14.9.1",
|
||||
"stylelint-config-html": "^1.0.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-recommended": "^8.0.0",
|
||||
"stylelint-config-standard": "^26.0.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"stylelint-scss": "^4.2.0",
|
||||
"stylelint-scss": "^4.3.0",
|
||||
"typescript": "^4.7.4",
|
||||
"unplugin-auto-import": "^0.9.2",
|
||||
"unplugin-vue-components": "^0.21.0",
|
||||
"unplugin-vue-define-options": "^0.6.1",
|
||||
"vite": "2.9.13",
|
||||
"vite-plugin-checker": "^0.4.7",
|
||||
"unplugin-vue-components": "^0.21.1",
|
||||
"unplugin-vue-define-options": "^0.6.2",
|
||||
"vite": "2.9.14",
|
||||
"vite-plugin-checker": "^0.4.8",
|
||||
"vite-plugin-windicss": "^1.8.6",
|
||||
"vue-eslint-parser": "^9.0.3",
|
||||
"vue-tsc": "^0.38.2",
|
||||
"vue-tsc": "^0.38.3",
|
||||
"windicss": "^3.5.6"
|
||||
},
|
||||
"repository": {
|
||||
|
|
511
pnpm-lock.yaml
511
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,12 @@
|
|||
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||
|
||||
const modules = import.meta.globEager('./*/index.tsx');
|
||||
|
||||
const components = {};
|
||||
const components: Record<string, VisualEditorComponent> = {};
|
||||
|
||||
Object.keys(modules).forEach((key: string) => {
|
||||
Object.entries(modules).forEach(([key, module]) => {
|
||||
const name = key.replace(/\.\/(.*)\/index\.(tsx|vue)/, '$1');
|
||||
components[name] = modules[key]?.default || modules[key];
|
||||
components[name] = module?.default || module;
|
||||
});
|
||||
|
||||
console.log(components, 'base-widgets');
|
||||
|
|
|
@ -51,4 +51,4 @@ export const fontArr = [
|
|||
{ label: '思源黑体', value: 'Source Han Sans CN' },
|
||||
{ label: '思源宋体', value: 'Source Han Serif SC' },
|
||||
{ label: '文泉驿微米黑', value: 'WenQuanYi Micro Hei' },
|
||||
];
|
||||
] as const;
|
||||
|
|
|
@ -40,7 +40,7 @@ export default {
|
|||
},
|
||||
props: {
|
||||
text: createEditorInputProp({ label: '显示文本' }),
|
||||
font: createEditorSelectProp({ label: '字体设置', options: fontArr }),
|
||||
font: createEditorSelectProp({ label: '字体设置', options: [...fontArr] }),
|
||||
color: createEditorColorProp({ label: '字体颜色' }),
|
||||
size: createEditorInputNumberProp({
|
||||
label: '字体大小',
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||
|
||||
const modules = import.meta.globEager('./*/index.tsx');
|
||||
|
||||
const components = {};
|
||||
const components: Record<string, VisualEditorComponent> = {};
|
||||
|
||||
Object.keys(modules).forEach((key: string) => {
|
||||
const name = key.replace(/\.\/(.*)\/index\.(tsx|vue)/, '$1');
|
||||
|
|
|
@ -44,9 +44,9 @@ export default {
|
|||
|
||||
watchEffect(() => {
|
||||
if (Object.keys(props.slots || {}).length) {
|
||||
Object.keys(props.slots).forEach((key) => {
|
||||
Object.entries<SlotItem>(props.slots).forEach(([key, slot]) => {
|
||||
if (slotsTemp[block._vid][key]?.children) {
|
||||
props.slots[key].children = slotsTemp[block._vid][key].children;
|
||||
slot.children = slotsTemp[block._vid][key].children;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -74,6 +74,10 @@
|
|||
import { useVisualData, localKey } from '@/visual-editor/hooks/useVisualData';
|
||||
import { BASE_URL } from '@/visual-editor/utils';
|
||||
|
||||
defineOptions({
|
||||
name: 'PageHeader',
|
||||
});
|
||||
|
||||
const isShowH5Preview = ref(false);
|
||||
|
||||
const tools = useTools();
|
||||
|
|
|
@ -10,37 +10,24 @@
|
|||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { BASE_URL } from '@/visual-editor/utils';
|
||||
/**
|
||||
* @name: preview
|
||||
* @author: 卜启缘
|
||||
* @date: 2021/4/29 23:09
|
||||
* @description:preview
|
||||
* @update: 2021/4/29 23:09
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'Preview',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:visible'],
|
||||
setup(props, { emit }) {
|
||||
const state = reactive({
|
||||
dialogVisible: useVModel(props, 'visible', emit),
|
||||
previewUrl: `${BASE_URL}preview/${location.hash}`,
|
||||
});
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
};
|
||||
defineOptions({
|
||||
name: 'Preview',
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['update:visible']);
|
||||
|
||||
const dialogVisible = useVModel(props, 'visible', emits);
|
||||
const previewUrl = `${BASE_URL}preview/${location.hash}`;
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
ElMessage,
|
||||
ElCascader,
|
||||
ElIcon,
|
||||
ExpandTrigger,
|
||||
} from 'element-plus';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { Delete, Edit } from '@element-plus/icons-vue';
|
||||
|
@ -163,14 +164,14 @@
|
|||
<ElFormItem label="请求数据" prop={'data.bind'}>
|
||||
<ElCascader
|
||||
v-model={state.ruleForm.data.bind}
|
||||
options={models.value}
|
||||
options={[...models.value]}
|
||||
clearable={true}
|
||||
props={{
|
||||
checkStrictly: true,
|
||||
children: 'entitys',
|
||||
label: 'name',
|
||||
value: 'key',
|
||||
expandTrigger: 'hover',
|
||||
expandTrigger: ExpandTrigger.HOVER,
|
||||
}}
|
||||
placeholder="请选择绑定的请求数据"
|
||||
onChange={handleBindChange}
|
||||
|
@ -184,12 +185,12 @@
|
|||
children: 'entitys',
|
||||
label: 'name',
|
||||
value: 'key',
|
||||
expandTrigger: 'hover',
|
||||
expandTrigger: ExpandTrigger.HOVER,
|
||||
}}
|
||||
placeholder="请选择绑定的响应数据"
|
||||
onChange={handleBindChange}
|
||||
v-model={state.ruleForm.data.recv}
|
||||
options={models.value}
|
||||
options={[...models.value]}
|
||||
></ElCascader>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
|
|
@ -22,25 +22,25 @@ import { useModal } from '@/visual-editor/hooks/useModal';
|
|||
export const importSwaggerJson = (swagger: any) => {
|
||||
swagger = typeof swagger == 'string' ? JSON.parse(swagger) : swagger;
|
||||
const models: VisualEditorModel[] = [];
|
||||
Object.keys(swagger.definitions).forEach((model) => {
|
||||
const properties = swagger.definitions[model].properties;
|
||||
Object.entries<any>(swagger.definitions).forEach(([name, model]) => {
|
||||
const properties = model.properties;
|
||||
const modelItem: VisualEditorModel = {
|
||||
name: model,
|
||||
name,
|
||||
key: generateNanoid(),
|
||||
entitys: [],
|
||||
};
|
||||
Object.keys(properties).forEach((field) => {
|
||||
Object.entries<any>(properties).forEach(([field, property]) => {
|
||||
modelItem.entitys.push({
|
||||
key: field,
|
||||
name: properties[field].description || field,
|
||||
type: properties[field].type,
|
||||
name: property.description || field,
|
||||
type: property.type,
|
||||
value: '',
|
||||
});
|
||||
});
|
||||
models.push(modelItem);
|
||||
});
|
||||
const apis: FetchApiItem[] = [];
|
||||
Object.keys(swagger.paths).forEach((url) => {
|
||||
Object.entries(swagger.paths).forEach(([url]) => {
|
||||
Object.keys(swagger.paths[url]).forEach((method) => {
|
||||
const apiUrlObj = swagger.paths[url][method];
|
||||
const model = apiUrlObj.parameters?.[0]?.schema?.$ref?.split('/').pop();
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { DefineComponent } from 'vue';
|
||||
|
||||
const modules = import.meta.globEager('./*/index.(tsx|vue)');
|
||||
|
||||
const components = {};
|
||||
const components: Record<string, DefineComponent> = {};
|
||||
|
||||
console.log(modules, '起航');
|
||||
|
||||
|
|
|
@ -33,10 +33,10 @@
|
|||
name: 'LeftAside',
|
||||
});
|
||||
|
||||
const tabs = Object.keys(components)
|
||||
.map((name) => {
|
||||
const { label, icon, order } = components[name];
|
||||
return { label, icon, name, order, comp: components[name] };
|
||||
const tabs = Object.entries(components)
|
||||
.map(([name, component]) => {
|
||||
const { label, icon, order } = component;
|
||||
return { label, icon, name, order, comp: component };
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
|
||||
|
|
|
@ -142,10 +142,10 @@ export const Animate = defineComponent({
|
|||
// 可添加的动画列表组件
|
||||
const AnimateList = () => (
|
||||
<ElTabs v-model={state.activeName} stretch>
|
||||
{Object.keys(animationTabs).map((tabKey) => (
|
||||
{Object.entries(animationTabs).map(([tabKey, animationBox]) => (
|
||||
<ElTabPane label={animationTabs[tabKey].label} name={tabKey} key={tabKey}>
|
||||
<ElRow gutter={10}>
|
||||
{animationTabs[tabKey].value.map((animateItem: Animation) => (
|
||||
{animationBox.value.map((animateItem: Animation) => (
|
||||
<ElCol span={8} key={animateItem.value}>
|
||||
<div
|
||||
class={'animate-item'}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import type { Animation } from '@/visual-editor/visual-editor.utils';
|
||||
|
||||
export interface animationBoxTs {
|
||||
export type AnimationBox = {
|
||||
label: string;
|
||||
value: Animation[];
|
||||
}
|
||||
};
|
||||
// 动画类型
|
||||
export interface animationTabsTs {
|
||||
in: animationBoxTs;
|
||||
out: animationBoxTs;
|
||||
other: animationBoxTs;
|
||||
}
|
||||
export type AnimationTabs = {
|
||||
in: AnimationBox;
|
||||
out: AnimationBox;
|
||||
other: AnimationBox;
|
||||
};
|
||||
|
||||
export const animationTabs: animationTabsTs = {
|
||||
export const animationTabs: AnimationTabs = {
|
||||
in: {
|
||||
label: '进入',
|
||||
value: [],
|
||||
|
@ -431,7 +431,7 @@ const opt = [
|
|||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* @return {Object} { animationValue: animatonLabel }
|
||||
|
@ -446,7 +446,7 @@ const defaultOption = {
|
|||
};
|
||||
for (let index = 0; index < opt.length; index++) {
|
||||
const items = opt[index].children;
|
||||
items.forEach((item) => {
|
||||
items.forEach((item: LabelValue) => {
|
||||
if (inReg.test(item.label)) {
|
||||
animationTabs.in.value.push({
|
||||
...item,
|
||||
|
|
|
@ -66,7 +66,7 @@ const ServiceComponent = defineComponent({
|
|||
|
||||
return () => (
|
||||
<>
|
||||
<ElDialog modelValue={state.showFlag}>
|
||||
<ElDialog v-model={state.showFlag}>
|
||||
{{
|
||||
default: () => (
|
||||
<div>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
</draggable>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* @name: draggable-transition-group
|
||||
* @author:卜启缘
|
||||
|
@ -30,52 +30,44 @@
|
|||
* @description:draggable-transition-group
|
||||
* @update: 2021/5/1 23:15
|
||||
*/
|
||||
import { computed, defineComponent, reactive, toRefs, SetupContext } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import draggable from 'vuedraggable';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
export default defineComponent({
|
||||
defineOptions({
|
||||
name: 'DraggableTransitionGroup',
|
||||
components: { draggable },
|
||||
props: {
|
||||
moduleValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
drag: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
default: '_vid',
|
||||
},
|
||||
group: {
|
||||
type: Object,
|
||||
default: () => ({ name: 'components' }),
|
||||
},
|
||||
fallbackClass: String,
|
||||
},
|
||||
emits: ['update:moduleValue', 'update:drag'],
|
||||
setup(props, { emit }: SetupContext) {
|
||||
const state = reactive({
|
||||
list: useVModel(props, 'moduleValue', emit),
|
||||
isDrag: useVModel(props, 'drag', emit),
|
||||
});
|
||||
|
||||
const dragOptions = computed(() => ({
|
||||
animation: 200,
|
||||
disabled: false,
|
||||
scroll: true,
|
||||
ghostClass: 'ghost',
|
||||
}));
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
dragOptions,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
moduleValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
drag: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
default: '_vid',
|
||||
},
|
||||
group: {
|
||||
type: Object,
|
||||
default: () => ({ name: 'components' }),
|
||||
},
|
||||
fallbackClass: String,
|
||||
});
|
||||
const emit = defineEmits(['update:moduleValue', 'update:drag']);
|
||||
|
||||
const list = useVModel(props, 'moduleValue', emit);
|
||||
const isDrag = useVModel(props, 'drag', emit);
|
||||
|
||||
const dragOptions = computed(() => ({
|
||||
animation: 200,
|
||||
disabled: false,
|
||||
scroll: true,
|
||||
ghostClass: 'ghost',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -53,8 +53,8 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="tsx">
|
||||
import { defineComponent, reactive, watchEffect, toRefs } from 'vue';
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import DraggableTransitionGroup from './draggable-transition-group.vue';
|
||||
import CompRender from './comp-render';
|
||||
|
@ -67,196 +67,176 @@
|
|||
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||
import { generateNanoid } from '@/visual-editor/utils';
|
||||
|
||||
export default defineComponent({
|
||||
defineOptions({
|
||||
name: 'SimulatorEditor',
|
||||
components: {
|
||||
DraggableTransitionGroup,
|
||||
CompRender,
|
||||
SlotItem,
|
||||
},
|
||||
emits: ['on-selected'],
|
||||
setup() {
|
||||
const { currentPage, setCurrentBlock } = useVisualData();
|
||||
});
|
||||
|
||||
const { globalProperties } = useGlobalProperties();
|
||||
const { currentPage, setCurrentBlock } = useVisualData();
|
||||
|
||||
const state = reactive({
|
||||
drag: false,
|
||||
});
|
||||
const { globalProperties } = useGlobalProperties();
|
||||
|
||||
/**
|
||||
* @description 操作当前页面样式表
|
||||
*/
|
||||
watchEffect(() => {
|
||||
const { bgImage, bgColor } = currentPage.value.config;
|
||||
const bodyStyleStr = `
|
||||
const drag = ref(false);
|
||||
|
||||
/**
|
||||
* @description 操作当前页面样式表
|
||||
*/
|
||||
watchEffect(() => {
|
||||
const { bgImage, bgColor } = currentPage.value.config;
|
||||
const bodyStyleStr = `
|
||||
.simulator-editor-content {
|
||||
background-color: ${bgColor};
|
||||
background-image: url(${bgImage});
|
||||
}`;
|
||||
const styleSheets = document.styleSheets[0];
|
||||
const firstCssRule = document.styleSheets[0].cssRules[0];
|
||||
const isExistContent = firstCssRule.cssText.includes('.simulator-editor-content');
|
||||
if (isExistContent) {
|
||||
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 || {};
|
||||
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 slots = block.props?.slots || {};
|
||||
if (Object.keys(slots).length > 0) {
|
||||
Object.keys(slots).forEach((key) => {
|
||||
slots[key]?.children?.forEach((item) => {
|
||||
item.focusWithChild = false;
|
||||
item.focus = item._vid == _vid;
|
||||
if (item.focus) {
|
||||
const arr = findPathByLeafId(_vid, currentPage.value.blocks);
|
||||
arr.forEach((n) => (n.focusWithChild = true));
|
||||
}
|
||||
if (Object.keys(item.props?.slots || {}).length) {
|
||||
handleSlotsFocus(item, _vid);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 选择要操作的组件
|
||||
const selectComp = (element: VisualEditorBlockData) => {
|
||||
setCurrentBlock(element);
|
||||
currentPage.value.blocks.forEach((block) => {
|
||||
block.focus = element._vid == block._vid;
|
||||
block.focusWithChild = false;
|
||||
handleSlotsFocus(block, element._vid);
|
||||
element.focusWithChild = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除组件
|
||||
*/
|
||||
const deleteComp = (
|
||||
block: VisualEditorBlockData,
|
||||
parentBlocks = currentPage.value.blocks,
|
||||
) => {
|
||||
console.log(block, 'block');
|
||||
const index = parentBlocks.findIndex((item) => item._vid == block._vid);
|
||||
if (index != -1) {
|
||||
delete globalProperties.$$refs[parentBlocks[index]._vid];
|
||||
const delTarget = parentBlocks.splice(index, 1)[0];
|
||||
if (delTarget.focus) {
|
||||
setCurrentBlock({} as VisualEditorBlockData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onContextmenuBlock = (
|
||||
e: MouseEvent,
|
||||
block: VisualEditorBlockData,
|
||||
parentBlocks = currentPage.value.blocks,
|
||||
) => {
|
||||
$$dropdown({
|
||||
reference: e,
|
||||
content: () => (
|
||||
<>
|
||||
<DropdownOption
|
||||
label="复制节点"
|
||||
icon="el-icon-document-copy"
|
||||
{...{
|
||||
onClick: () => {
|
||||
const index = parentBlocks.findIndex((item) => item._vid == block._vid);
|
||||
if (index != -1) {
|
||||
const setBlockVid = (block: VisualEditorBlockData) => {
|
||||
block._vid = `vid_${generateNanoid()}`;
|
||||
block.focus = false;
|
||||
const slots = block?.props?.slots || {};
|
||||
const slotKeys = Object.keys(slots);
|
||||
if (slotKeys.length) {
|
||||
slotKeys.forEach((slotKey) => {
|
||||
slots[slotKey]?.children?.forEach((child) => setBlockVid(child));
|
||||
});
|
||||
}
|
||||
};
|
||||
const blockCopy = cloneDeep(parentBlocks[index]);
|
||||
setBlockVid(blockCopy);
|
||||
parentBlocks.splice(index + 1, 0, blockCopy);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<DropdownOption
|
||||
label="查看节点"
|
||||
icon="el-icon-view"
|
||||
{...{
|
||||
onClick: () =>
|
||||
useModal({
|
||||
title: '节点信息',
|
||||
footer: null,
|
||||
props: {
|
||||
width: 600,
|
||||
},
|
||||
content: () => (
|
||||
<MonacoEditor
|
||||
code={JSON.stringify(block)}
|
||||
layout={{ width: 530, height: 600 }}
|
||||
vid={block._vid}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<DropdownOption
|
||||
label="删除节点"
|
||||
icon="el-icon-delete"
|
||||
{...{
|
||||
onClick: () => deleteComp(block, parentBlocks),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
currentPage,
|
||||
deleteComp,
|
||||
selectComp,
|
||||
onContextmenuBlock,
|
||||
};
|
||||
},
|
||||
const styleSheets = document.styleSheets[0];
|
||||
const firstCssRule = document.styleSheets[0].cssRules[0];
|
||||
const isExistContent = firstCssRule.cssText.includes('.simulator-editor-content');
|
||||
if (isExistContent) {
|
||||
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 || {};
|
||||
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 slots = block.props?.slots || {};
|
||||
if (Object.keys(slots).length > 0) {
|
||||
Object.keys(slots).forEach((key) => {
|
||||
slots[key]?.children?.forEach((item) => {
|
||||
item.focusWithChild = false;
|
||||
item.focus = item._vid == _vid;
|
||||
if (item.focus) {
|
||||
const arr = findPathByLeafId(_vid, currentPage.value.blocks);
|
||||
arr.forEach((n) => (n.focusWithChild = true));
|
||||
}
|
||||
if (Object.keys(item.props?.slots || {}).length) {
|
||||
handleSlotsFocus(item, _vid);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 选择要操作的组件
|
||||
const selectComp = (element: VisualEditorBlockData) => {
|
||||
setCurrentBlock(element);
|
||||
currentPage.value.blocks.forEach((block) => {
|
||||
block.focus = element._vid == block._vid;
|
||||
block.focusWithChild = false;
|
||||
handleSlotsFocus(block, element._vid);
|
||||
element.focusWithChild = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除组件
|
||||
*/
|
||||
const deleteComp = (block: VisualEditorBlockData, parentBlocks = currentPage.value.blocks) => {
|
||||
console.log(block, 'block');
|
||||
const index = parentBlocks.findIndex((item) => item._vid == block._vid);
|
||||
if (index != -1) {
|
||||
delete globalProperties.$$refs[parentBlocks[index]._vid];
|
||||
const delTarget = parentBlocks.splice(index, 1)[0];
|
||||
if (delTarget.focus) {
|
||||
setCurrentBlock({} as VisualEditorBlockData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onContextmenuBlock = (
|
||||
e: MouseEvent,
|
||||
block: VisualEditorBlockData,
|
||||
parentBlocks = currentPage.value.blocks,
|
||||
) => {
|
||||
$$dropdown({
|
||||
reference: e,
|
||||
content: () => (
|
||||
<>
|
||||
<DropdownOption
|
||||
label="复制节点"
|
||||
icon="el-icon-document-copy"
|
||||
{...{
|
||||
onClick: () => {
|
||||
const index = parentBlocks.findIndex((item) => item._vid == block._vid);
|
||||
if (index != -1) {
|
||||
const setBlockVid = (block: VisualEditorBlockData) => {
|
||||
block._vid = `vid_${generateNanoid()}`;
|
||||
block.focus = false;
|
||||
const slots = block?.props?.slots || {};
|
||||
const slotKeys = Object.keys(slots);
|
||||
if (slotKeys.length) {
|
||||
slotKeys.forEach((slotKey) => {
|
||||
slots[slotKey]?.children?.forEach((child) => setBlockVid(child));
|
||||
});
|
||||
}
|
||||
};
|
||||
const blockCopy = cloneDeep(parentBlocks[index]);
|
||||
setBlockVid(blockCopy);
|
||||
parentBlocks.splice(index + 1, 0, blockCopy);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<DropdownOption
|
||||
label="查看节点"
|
||||
icon="el-icon-view"
|
||||
{...{
|
||||
onClick: () =>
|
||||
useModal({
|
||||
title: '节点信息',
|
||||
footer: null,
|
||||
props: {
|
||||
width: 600,
|
||||
},
|
||||
content: () => (
|
||||
<MonacoEditor
|
||||
code={JSON.stringify(block)}
|
||||
layout={{ width: 530, height: 600 }}
|
||||
vid={block._vid}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<DropdownOption
|
||||
label="删除节点"
|
||||
icon="el-icon-delete"
|
||||
{...{
|
||||
onClick: () => deleteComp(block, parentBlocks),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import './func.scss';
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</draggable-transition-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* @name: slot-item
|
||||
* @author:卜启缘
|
||||
|
@ -48,54 +48,51 @@
|
|||
* @update: 2021/5/2 22:36
|
||||
*/
|
||||
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { PropType } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import DraggableTransitionGroup from './draggable-transition-group.vue';
|
||||
import CompRender from './comp-render';
|
||||
import type { VisualEditorBlockData } from '@/visual-editor/visual-editor.utils';
|
||||
|
||||
export default defineComponent({
|
||||
defineOptions({
|
||||
name: 'SlotItem',
|
||||
components: { CompRender, DraggableTransitionGroup },
|
||||
props: {
|
||||
slotKey: {
|
||||
type: String as PropType<string | number>,
|
||||
default: '',
|
||||
},
|
||||
drag: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
children: {
|
||||
type: Array as PropType<VisualEditorBlockData[]>,
|
||||
default: () => [],
|
||||
},
|
||||
selectComp: {
|
||||
type: Function as PropType<(comp: VisualEditorBlockData) => void>,
|
||||
required: true,
|
||||
},
|
||||
onContextmenuBlock: {
|
||||
type: Function as PropType<
|
||||
(
|
||||
e: MouseEvent,
|
||||
block: VisualEditorBlockData,
|
||||
parentBlocks?: VisualEditorBlockData[],
|
||||
) => void
|
||||
>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:children', 'on-selected', 'update:drag'],
|
||||
setup(props, { emit }) {
|
||||
// 初始化时设置上次选中的组件
|
||||
props.children.some((item) => item.focus && props.selectComp(item));
|
||||
});
|
||||
|
||||
return {
|
||||
isDrag: useVModel(props, 'drag', emit),
|
||||
slotChildren: useVModel(props, 'children', emit),
|
||||
};
|
||||
const props = defineProps({
|
||||
slotKey: {
|
||||
type: String as PropType<string | number>,
|
||||
default: '',
|
||||
},
|
||||
drag: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
children: {
|
||||
type: Array as PropType<VisualEditorBlockData[]>,
|
||||
default: () => [],
|
||||
},
|
||||
selectComp: {
|
||||
type: Function as PropType<(comp: VisualEditorBlockData) => void>,
|
||||
required: true,
|
||||
},
|
||||
onContextmenuBlock: {
|
||||
type: Function as PropType<
|
||||
(
|
||||
e: MouseEvent,
|
||||
block: VisualEditorBlockData,
|
||||
parentBlocks?: VisualEditorBlockData[],
|
||||
) => void
|
||||
>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['update:children', 'on-selected', 'update:drag']);
|
||||
|
||||
const isDrag = useVModel(props, 'drag', emit);
|
||||
const slotChildren = useVModel(props, 'children', emit);
|
||||
|
||||
// 初始化时设置上次选中的组件
|
||||
props.children.some((item) => item.focus && props.selectComp(item));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -67,7 +67,7 @@ const Modal = defineComponent({
|
|||
|
||||
return () => (
|
||||
<ElDialog
|
||||
modelValue={state.visible}
|
||||
v-model={state.visible}
|
||||
title={state.options.title}
|
||||
destroyOnClose={true}
|
||||
{...state.options.props}
|
||||
|
|
|
@ -64,7 +64,7 @@ const ServiceComponent = defineComponent({
|
|||
|
||||
return () => (
|
||||
<>
|
||||
<ElDialog modelValue={state.showFlag} title={state.option.title} key={state.key}>
|
||||
<ElDialog v-model={state.showFlag} title={state.option.title} key={state.key}>
|
||||
{{
|
||||
default: () => (
|
||||
<div>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { generateNanoid } from '@/visual-editor/utils';
|
|||
/**
|
||||
* @description 组件属性
|
||||
*/
|
||||
export interface VisualEditorBlockData {
|
||||
export type VisualEditorBlockData = {
|
||||
/** 组件id 时间戳, 组件唯一标识 */
|
||||
_vid: string;
|
||||
/** 组件所属的模块(基础组件、容器组件) */
|
||||
|
@ -42,11 +42,11 @@ export interface VisualEditorBlockData {
|
|||
/** 组件事件集合 */
|
||||
events: { label: string; value: string }[];
|
||||
[prop: string]: any;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 组件动作事件处理
|
||||
*/
|
||||
export interface ActionHandle {
|
||||
export type ActionHandle = {
|
||||
key: string;
|
||||
name: string;
|
||||
link: string[];
|
||||
|
@ -54,32 +54,32 @@ export interface ActionHandle {
|
|||
bind?: string;
|
||||
recv?: string;
|
||||
};
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 组件动作
|
||||
*/
|
||||
export interface Action {
|
||||
export type Action = {
|
||||
key: string;
|
||||
name: string;
|
||||
event: string;
|
||||
handle: ActionHandle[];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 页面配置
|
||||
*/
|
||||
export interface PageConfig {
|
||||
export type PageConfig = {
|
||||
/** 背景图片 */
|
||||
bgImage: string;
|
||||
/** 背景颜色 */
|
||||
bgColor: string;
|
||||
/** 是否缓存当前页面 */
|
||||
keepAlive: boolean;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 页面对象
|
||||
*/
|
||||
export interface VisualEditorPage {
|
||||
export type VisualEditorPage = {
|
||||
/** 页面标题 */
|
||||
title: string;
|
||||
/** 页面路径 */
|
||||
|
@ -90,13 +90,13 @@ export interface VisualEditorPage {
|
|||
config: PageConfig;
|
||||
/** 当前页面的所有组件 */
|
||||
blocks: VisualEditorBlockData[];
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 可以认为是 路由=>页面
|
||||
*/
|
||||
export interface VisualEditorPages {
|
||||
export type VisualEditorPages = {
|
||||
[path: string]: VisualEditorPage;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 实体类型
|
||||
*/
|
||||
|
@ -114,18 +114,18 @@ export type EntityType = {
|
|||
/**
|
||||
* @description 数据模型
|
||||
*/
|
||||
export interface VisualEditorModel {
|
||||
export type VisualEditorModel = {
|
||||
/** 数据源名称 */
|
||||
name: string;
|
||||
/** 绑定的字段 该字段创建的时候生成 */
|
||||
key: string;
|
||||
/** 实体集合 */
|
||||
entitys: EntityType[];
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 接口请求对象
|
||||
*/
|
||||
export interface FetchApiItem {
|
||||
export type FetchApiItem = {
|
||||
/** 随机生成的key */
|
||||
key: string;
|
||||
/** 随机生成的key */
|
||||
|
@ -144,12 +144,12 @@ export interface FetchApiItem {
|
|||
/** 响应的结果绑定到某个实体上 */
|
||||
recv: string;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 动作集合
|
||||
*/
|
||||
export interface VisualEditorActions {
|
||||
export type VisualEditorActions = {
|
||||
fetch: {
|
||||
name: '接口请求';
|
||||
apis: FetchApiItem[];
|
||||
|
@ -158,22 +158,22 @@ export interface VisualEditorActions {
|
|||
name: '对话框';
|
||||
handlers: [];
|
||||
};
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 总的数据集
|
||||
*/
|
||||
export interface VisualEditorModelValue {
|
||||
export type VisualEditorModelValue = {
|
||||
/** 页面 */
|
||||
pages: VisualEditorPages;
|
||||
/** 实体 */
|
||||
models: VisualEditorModel[];
|
||||
/** 动作 */
|
||||
actions: VisualEditorActions;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 动画项
|
||||
*/
|
||||
export interface Animation {
|
||||
export type Animation = {
|
||||
/** 动画名称 */
|
||||
label: string;
|
||||
/** 动画类名 */
|
||||
|
@ -186,11 +186,11 @@ export interface Animation {
|
|||
count: number;
|
||||
/** 是否无限循环动画 */
|
||||
infinite: boolean;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 单个组件注册规则
|
||||
*/
|
||||
export interface VisualEditorComponent {
|
||||
export type VisualEditorComponent = {
|
||||
/** 组件name */
|
||||
key: string;
|
||||
/** 组件所属模块名称 */
|
||||
|
@ -221,12 +221,12 @@ export interface VisualEditorComponent {
|
|||
events?: { label: string; value: string }[];
|
||||
/** 组件样式 */
|
||||
styles?: CSSProperties;
|
||||
}
|
||||
};
|
||||
|
||||
export interface VisualEditorMarkLines {
|
||||
export type VisualEditorMarkLines = {
|
||||
x: { left: number; showLeft: number }[];
|
||||
y: { top: number; showTop: number }[];
|
||||
}
|
||||
};
|
||||
|
||||
export function createNewBlock(component: VisualEditorComponent): VisualEditorBlockData {
|
||||
return {
|
||||
|
@ -246,10 +246,10 @@ export function createNewBlock(component: VisualEditorComponent): VisualEditorBl
|
|||
tempPadding: '0',
|
||||
},
|
||||
hasResize: false,
|
||||
props: Object.keys(component.props || {}).reduce((prev, curr) => {
|
||||
const { propObj, prop } = useDotProp(prev, curr);
|
||||
if (component.props![curr]?.defaultValue) {
|
||||
propObj[prop] = prev[curr] = component.props![curr]?.defaultValue;
|
||||
props: Object.entries(component.props || {}).reduce((prev, [propName, propSchema]) => {
|
||||
const { propObj, prop } = useDotProp(prev, propName);
|
||||
if (propSchema?.defaultValue) {
|
||||
propObj[prop] = prev[propName] = propSchema?.defaultValue;
|
||||
}
|
||||
return prev;
|
||||
}, {}),
|
||||
|
@ -262,7 +262,7 @@ export function createNewBlock(component: VisualEditorComponent): VisualEditorBl
|
|||
};
|
||||
}
|
||||
|
||||
export interface VisualDragEvent {
|
||||
export type VisualDragEvent = {
|
||||
dragstart: {
|
||||
on: (cb: () => void) => void;
|
||||
off: (cb: () => void) => void;
|
||||
|
@ -273,7 +273,7 @@ export interface VisualDragEvent {
|
|||
off: (cb: () => void) => void;
|
||||
emit: () => void;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const VisualDragProvider = (() => {
|
||||
const VISUAL_DRAG_PROVIDER = '@@VISUAL_DRAG_PROVIDER';
|
||||
|
@ -288,10 +288,10 @@ export const VisualDragProvider = (() => {
|
|||
})();
|
||||
|
||||
// 组件模块
|
||||
export interface ComponentModules {
|
||||
export type ComponentModules = {
|
||||
baseWidgets: VisualEditorComponent[]; // 基础组件
|
||||
containerComponents: VisualEditorComponent[]; // 容器组件
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @description 创建编辑器配置
|
||||
* @returns {} 返回编辑器注册组件的方法等
|
||||
|
|
|
@ -4,12 +4,12 @@ import containerComponent from '@/packages/container-component';
|
|||
|
||||
export const visualConfig = createVisualEditorConfig();
|
||||
// 注册基础控件
|
||||
Object.keys(baseWidgets).forEach((name: string) =>
|
||||
visualConfig.registry('baseWidgets', name, baseWidgets[name]),
|
||||
Object.entries(baseWidgets).forEach(([name, widget]) =>
|
||||
visualConfig.registry('baseWidgets', name, widget),
|
||||
);
|
||||
// 注册容器组件
|
||||
Object.keys(containerComponent).forEach((name: string) =>
|
||||
visualConfig.registry('containerComponents', name, containerComponent[name]),
|
||||
Object.entries(containerComponent).forEach(([name, widget]) =>
|
||||
visualConfig.registry('containerComponents', name, widget),
|
||||
);
|
||||
|
||||
console.log(
|
||||
|
|
Loading…
Reference in New Issue