Compare commits

..

8 Commits

18 changed files with 410 additions and 1507 deletions

1
components.d.ts vendored
View File

@ -7,6 +7,7 @@ export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AdaptivePage: typeof import('./src/components/AdaptivePage.vue')['default']
DynamicComponent: typeof import('./src/components/DynamicComponent.vue')['default'] DynamicComponent: typeof import('./src/components/DynamicComponent.vue')['default']
ElTable: typeof import('./src/components/ElTable.vue')['default'] ElTable: typeof import('./src/components/ElTable.vue')['default']
MainView: typeof import('./src/components/MainView.vue')['default'] MainView: typeof import('./src/components/MainView.vue')['default']

56
package-lock.json generated
View File

@ -13,6 +13,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lsp-uuid": "^3.2.0", "lsp-uuid": "^3.2.0",
"pinia": "^2.2.4",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"vue": "^3.2.25", "vue": "^3.2.25",
"vue-draggable-plus": "^0.5.3" "vue-draggable-plus": "^0.5.3"
@ -1164,6 +1165,11 @@
"he": "^1.2.0" "he": "^1.2.0"
} }
}, },
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://repo.bingosoft.net/repository/npm/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
},
"node_modules/@vue/language-core": { "node_modules/@vue/language-core": {
"version": "2.0.29", "version": "2.0.29",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.0.29.tgz", "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.0.29.tgz",
@ -2646,6 +2652,56 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/pinia": {
"version": "2.2.4",
"resolved": "https://repo.bingosoft.net/repository/npm/pinia/-/pinia-2.2.4.tgz",
"integrity": "sha512-K7ZhpMY9iJ9ShTC0cR2+PnxdQRuwVIsXDO/WIEV/RnMC/vmSoKDTKW/exNQYPI+4ij10UjXqdNiEHwn47McANQ==",
"dependencies": {
"@vue/devtools-api": "^6.6.3",
"vue-demi": "^0.14.10"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://repo.bingosoft.net/repository/npm/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pkg-types": { "node_modules/pkg-types": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.1.3.tgz", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.1.3.tgz",

View File

@ -15,6 +15,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lsp-uuid": "^3.2.0", "lsp-uuid": "^3.2.0",
"pinia": "^2.2.4",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"vue": "^3.2.25", "vue": "^3.2.25",
"vue-draggable-plus": "^0.5.3" "vue-draggable-plus": "^0.5.3"

View File

@ -0,0 +1,27 @@
<template>
<div>
<div style="width: 100%;">
<slot name="header"></slot>
</div>
<div style="width: 100%;min-height: 480px;background-color: #f7f8fa;">
<slot>
MAIN
</slot>
</div>
<div style="width: 100%;">
<slot name="footer"></slot>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const dataSources = ref({})
onMounted(()=>
{
})
</script>

View File

@ -1,30 +1,30 @@
<!-- DynamicComponent.vue -->
<template> <template>
<component :is="componentType" v-bind="componentProps" :class="componentClass" :style="componentStyle"> <component @click.stop="handleClick" :id="componentId" :is="componentType" v-bind="componentProps"
:class="componentClass" :style="componentStyle">
{{ componentText }} {{ componentText }}
<template v-for="child in componentChildren" :key="child.id"> <template v-for="child in componentChildren" :key="child.id">
<DynamicComponent :component-data="child" /> <DynamicComponent :component-data="child" />
</template> </template>
<template v-for="(slot, key, index) in componentSlots" :key="index" v-slot:[key]> <template v-for="(slot, key, index) in componentSlots" :key="index" v-slot:[key]>
<DynamicComponent :component-data="slot" /> <DynamicComponent :component-data="slot" />
</template> </template>
</component> </component>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineProps, computed, onMounted } from 'vue'; import { defineProps, computed, onMounted } from 'vue';
import { componentMapping } from './componentMapping'; import { componentMapping } from './componentMapping';
import { useSchemeStore } from '../stores/useSchemeStore'
const store = useSchemeStore()
const props = defineProps({ const props = defineProps({
componentData: Object componentData: Object
}); });
onMounted(() => { onMounted(() => {
console.log(props.componentData) // console.log(props.componentData)
}) })
const componentId = computed(() => props.componentData?.id || '');
const componentType = computed(() => componentMapping[props.componentData?.type] || 'div'); const componentType = computed(() => componentMapping[props.componentData?.type] || 'div');
const componentProps = computed(() => props.componentData?.props || {}); const componentProps = computed(() => props.componentData?.props || {});
const componentChildren = computed(() => props.componentData?.children || []); const componentChildren = computed(() => props.componentData?.children || []);
@ -33,6 +33,16 @@ const componentClass = computed(() => props.componentData?.class || []);
const componentStyle = computed(() => props.componentData?.style || []); const componentStyle = computed(() => props.componentData?.style || []);
const componentSlots = computed(() => props.componentData?.slots || []); const componentSlots = computed(() => props.componentData?.slots || []);
// @ts-ignore
const updateScheme = () => {
}
const getCurrentSchemeObj = () => {
return store.getSchemeObj(componentId.value)
}
const handleClick = () => {
console.log(`Div with id ${JSON.stringify(getCurrentSchemeObj())} was clicked.`);
// 你可以在这里执行更多的逻辑,例如发出一个事件或调用一个方法
};
</script> </script>

View File

@ -1,63 +1,131 @@
<template> <template>
<div style="display: flex;"> <div class="flex-container">
<div> <div class="component-area">
compoent compoent
</div> </div>
<div style="overflow-y: auto; overflow-x: hidden; flex-grow: 0; flex-shrink: 0; flex-basis: 250px;"> <!-- 组件区域 -->
<div ref="el2" style="display: flex; flex-direction: column; height: 500px; width: 100%;"> <div class="component-list">
<div v-for="item in componentsList" :key="item.id" style="height: 30px; width: 250px; border: 1px solid red;"> <div ref="el2" class="component-list-inner">
<div v-for="item in componentsList" :key="item.id" class="component-item">
{{ item.name }}:{{ item.id }} {{ item.name }}:{{ item.id }}
</div> </div>
</div> </div>
</div> </div>
<div style="flex-grow: 1; flex-shrink: 0; overflow-y: auto;"> <div class="design-area">
<a-row>
<a-col flex="80%">
<div class="dragArea">
<div> <div>
<div
style="display: flex; flex-direction: row; width: 100%; height: 500px; overflow-y: auto; border: 1px solid black;">
<div class="flex justify-between">
<NestedFunction v-model="list"></NestedFunction> <NestedFunction v-model="list"></NestedFunction>
{{ list }} {{ list }}
</div> </div>
</div> </div>
<div style="width: 100%; height: 500px; overflow-y: auto; border: 1px solid black;"> </a-col>
<a-col flex="20%">
<div class="property-editor">属性编辑器</div>
<div>
{{ store.scheme }}
</div>
</a-col>
</a-row>
<!-- 渲染区 -->
<div class="render-area">
动态渲染 动态渲染
<!-- <component :is="componentMapping['Rate']" v-bind="{}"/> -->
<DynamicComponent v-for="component in list" :key="component.id" :componentData="component"> <DynamicComponent v-for="component in list" :key="component.id" :componentData="component">
{{ component.id }} {{ component.id }}
</DynamicComponent> </DynamicComponent>
</div> </div>
<div> <!-- 测试区域 -->
<div class="test-area">
{{ store.scheme }}
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import NestedFunction from './NestedFunction.vue'; import NestedFunction from './NestedFunction.vue';
import DynamicComponent from './DynamicComponent.vue'; import DynamicComponent from './DynamicComponent.vue';
import { useDraggable } from 'vue-draggable-plus'; import { useDraggable } from 'vue-draggable-plus';
import { uuid } from 'lsp-uuid'; import { uuid } from 'lsp-uuid';
import { IComponent } from '../type/IComponent.ts'; import { IPageComponent } from '../type/IPageComponent';
import { componentScheme } from '../schemes/scheme.ts'; import { componentScheme } from '../schemes/scheme';
import { useSchemeStore } from '../stores/useSchemeStore'
const baseScheme =
{
"type": "AdaptivePage",
"name": "AdaptivePage",
"id": uuid(),
"version": "2.0",
"props": {},
"class": "",
"style": "",
"variables": {},
"dataSources": {},
"functions": {},
"orchestrations": {},
"events": {},
"slots": {},
"header": {},
"footer": {},
"children": [],
"meta": {}
}
const componentsList = ref<any[]>([]); const componentsList = ref<any[]>([]);
const list = ref<IPageComponent[]>([]);
const list = ref<IComponent[]>([]);
const el2 = ref(); const el2 = ref();
const store = useSchemeStore()
//初始化scheme
const initScheme = () => {
store.initScheme([baseScheme])
}
const unsubscribe = store.$onAction(
({
name, // action 名称
store, // store 实例,类似 `someStore`
args, // 传递给 action 的参数数组
after, // 在 action 返回或解决后的钩子
onError, // action 抛出或拒绝的钩子
}) => {
// 为这个特定的 action 调用提供一个共享变量
const startTime = Date.now()
// 这将在执行 "store "的 action 之前触发。
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 这将在 action 成功并完全运行后触发。
// 它等待着任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${JSON.stringify(result) }.`
)
})
// 如果 action 抛出或返回一个拒绝的 promise这将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动删除监听器
// unsubscribe()
onMounted(() => { onMounted(() => {
const loadedComponents = Object.values(componentScheme) const loadedComponents = Object.values(componentScheme);
componentsList.value = loadedComponents componentsList.value = loadedComponents;
// console.log(loadedComponents) initScheme();
// @ts-ignore
list.value.push(baseScheme)
}); });
useDraggable(el2, componentsList, { useDraggable(el2, componentsList, {
@ -65,11 +133,11 @@ useDraggable(el2, componentsList, {
group: { name: 'designer', pull: 'clone', put: false }, group: { name: 'designer', pull: 'clone', put: false },
sort: false, sort: false,
onClone() { onClone() {
console.log('clone') console.log('clone');
}, },
clone(element: Record<'id' | 'name' | 'type' | 'children' | 'props' | 'text' | 'class' | 'style' | 'slots', any>) { clone(element) {
return { return {
id: `${element.id}-${uuid()}`, id: `${element.type}-${uuid()}`,
name: element.name, name: element.name,
type: element.type, type: element.type,
props: element.props, props: element.props,
@ -83,9 +151,65 @@ useDraggable(el2, componentsList, {
disable: "", disable: "",
events: {}, events: {},
loop: {}, loop: {},
};
} }
} });
})
</script> </script>
<style scoped></style> <style scoped>
.flex-container {
display: flex;
flex-wrap: nowrap;
align-items: stretch;
width: 100%;
/* Ensure the container takes full width */
}
.component-area {
flex-grow: 1;
max-width: 72px;
/* Fixed width or can be adjusted */
}
.component-list {
flex-grow: 1;
max-width: 250px;
/* Fixed width or can be adjusted */
}
.component-list-inner {
display: flex;
flex-direction: column;
height: 500px;
}
.component-item {
height: 30px;
width: 250px;
border: 1px solid red;
}
.design-area {
flex-grow: 1;
overflow-x: auto;
}
.dragArea {
overflow-x: auto;
display: flex;
flex-direction: row;
height: 500px;
border: 1px solid black;
}
.property-editor {
border: 1px red solid;
min-height: 500px;
}
.render-area {
height: 500px;
overflow-y: auto;
border: 1px solid black;
}
</style>

View File

@ -1,7 +1,7 @@
<template> <template>
<ul class="drag-area" ref="el" style="margin: 0;"> <ul class="drag-area" ref="el" style="margin: 0;">
<li v-for="el in modelValue" :key="el.name"> <li v-for="el in modelValue" :key="el.name">
<div style="width: 200px;border: 1px solid red;"> <div style="min-width: fit-content;border: 1px solid red;">
<p>{{ el.name }}:{{ el.id }}</p> <p>{{ el.name }}:{{ el.id }}</p>
</div> </div>
<nested-function v-model="el.children" /> <nested-function v-model="el.children" />
@ -11,15 +11,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { useDraggable} from 'vue-draggable-plus' import { useDraggable} from 'vue-draggable-plus'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { IComponent } from '../type/IComponent' import { IPageComponent } from '../type/IPageComponent'
interface Props { interface Props {
modelValue: IComponent[] modelValue: IPageComponent[]
} }
const props = defineProps<Props>() const props = defineProps<Props>()
interface Emits { interface Emits {
(e: 'update:modelValue', value: IComponent[]): void (e: 'update:modelValue', value: IPageComponent[]): void
} }
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const list = computed({ const list = computed({
@ -35,14 +35,14 @@ useDraggable(el, list, {
console.log('start') console.log('start')
}, },
onUpdate() { onUpdate() {
console.log('update list1') console.log('update list')
}, },
onAdd: (e) => { onAdd: () => {
// console.log(e) // console.log(e)
console.log('add list1') console.log('add list')
}, },
onRemove: () => { onRemove: () => {
console.log('remove list1') console.log('remove list')
} }
},) },)
</script> </script>

View File

@ -1,29 +1,33 @@
<!-- DynamicSlotsComponent.vue -->
<template>
<div>
<!-- 动态插槽 -->
<div>
<slot name="header"></slot>
</div>
<div>
<slot name="main"></slot>
</div>
<div>
<slot name="footer"></slot>
</div>
</div>
</template>
<script setup> <script setup>
import { defineProps } from 'vue'; import { useSchemeStore } from '@/stores/useSchemeStore'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useSchemeStore()
function add() {
store.increment()
}
function sub() {
store.decrement()
}
// 接收一个动态插槽名作为 prop function initScheme() {
const props = defineProps({ store.initScheme({
dynamicSlotName: String name: "test"
}); })
}
function updateScheme(value) {
store.$patch(state => {
state.scheme = { name: 'shoes', quantity: 1 }
state.hasChanged = true
})
}
</script> </script>
<template>
<div>{{ store.count }}</div>
<a-button @click=add>add</a-button>
<a-button @click=sub>sub</a-button>
<div>{{ store.scheme }}</div>
<a-button @click=initScheme>initScheme</a-button>
<a-button @click=updateScheme>updateScheme</a-button>
</template>

View File

@ -1,5 +1,6 @@
// componentMapping.ts // componentMapping.ts
import TestComponent from './TestComponent.vue'; import TestComponent from './TestComponent.vue';
import AdaptivePage from './AdaptivePage.vue';
import Icon from './Icon.tsx'; import Icon from './Icon.tsx';
import { import {
@ -30,7 +31,7 @@ import {
} from '@arco-design/web-vue'; } from '@arco-design/web-vue';
export const componentMapping: { [key: string]: any } = { export const componentMapping: { [key: string]: any } = {
TestComponent, TestComponent, AdaptivePage,
Affix, Alert, Anchor, AnchorLink, Affix, Alert, Anchor, AnchorLink,
AutoComplete, Avatar, AvatarGroup, BackTop, AutoComplete, Avatar, AvatarGroup, BackTop,
Badge, Breadcrumb, BreadcrumbItem, Button, Badge, Breadcrumb, BreadcrumbItem, Button,

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,12 @@ import ArcoVue from '@arco-design/web-vue';
// 额外引入图标库 // 额外引入图标库
import ArcoVueIcon from '@arco-design/web-vue/es/icon'; import ArcoVueIcon from '@arco-design/web-vue/es/icon';
import '@arco-design/web-vue/dist/arco.css'; import '@arco-design/web-vue/dist/arco.css';
import {createPinia} from "pinia"
const pinia = createPinia()
const app = createApp(App); const app = createApp(App);
app.use(pinia)
app.use(ArcoVue); app.use(ArcoVue);
app.use(ArcoVueIcon); app.use(ArcoVueIcon);
app.mount('#app'); app.mount('#app');

View File

@ -1,7 +1,11 @@
{ {
"type": "AdaptivePage", "type": "AdaptivePage",
"name":"AdaptivePage",
"id":"AdaptivePage",
"version": "2.0", "version": "2.0",
"props": {}, "props": {},
"class":"",
"style":"",
"variables": {}, "variables": {},
"dataSources":{}, "dataSources":{},
"functions" : {}, "functions" : {},
@ -10,6 +14,6 @@
"slots":{}, "slots":{},
"header":{}, "header":{},
"footer":{}, "footer":{},
"children":{}, "children":[],
"meta":{} "meta":{}
} }

View File

@ -12,7 +12,7 @@
"visible": "", "visible": "",
"slots": { "slots": {
"extra":{ "extra":{
"id": "avatar-9f8289a12910000", "id": "9f8289a12910000",
"name": "avatar", "name": "avatar",
"type": "Avatar", "type": "Avatar",
"props": {}, "props": {},

View File

@ -1,4 +1,23 @@
{ {
"AdaptivePage": {
"type": "AdaptivePage",
"name": "AdaptivePage",
"id": "AdaptivePage",
"version": "2.0",
"props": {},
"class": "",
"style": "",
"variables": {},
"dataSources": {},
"functions": {},
"orchestrations": {},
"events": {},
"slots": {},
"header": {},
"footer": {},
"children": [],
"meta": {}
},
"Affix": { "Affix": {
"type": "Affix", "type": "Affix",
"name": "affix", "name": "affix",
@ -240,7 +259,7 @@
"visible": "", "visible": "",
"slots": { "slots": {
"extra": { "extra": {
"id": "avatar-9f8289a12910000", "id": "9f8289a12910000",
"name": "avatar", "name": "avatar",
"type": "Avatar", "type": "Avatar",
"props": {}, "props": {},

View File

@ -1,4 +1,23 @@
export const componentScheme = { export const componentScheme = {
"AdaptivePage": {
"type": "AdaptivePage",
"name": "AdaptivePage",
"id": "AdaptivePage",
"version": "2.0",
"props": {},
"class": "",
"style": "",
"variables": {},
"dataSources": {},
"functions": {},
"orchestrations": {},
"events": {},
"slots": {},
"header": {},
"footer": {},
"children": [],
"meta": {}
},
"Affix": { "Affix": {
"type": "Affix", "type": "Affix",
"name": "affix", "name": "affix",
@ -240,7 +259,7 @@ export const componentScheme = {
"visible": "", "visible": "",
"slots": { "slots": {
"extra": { "extra": {
"id": "avatar-9f8289a12910000", "id": "9f8289a12910000",
"name": "avatar", "name": "avatar",
"type": "Avatar", "type": "Avatar",
"props": {}, "props": {},

View File

@ -0,0 +1,33 @@
import { defineStore } from 'pinia'
import { IPageComponent } from '../type/IPageComponent';
import { ref } from 'vue';
function findObjectById(obj, targetId) {
if (Array.isArray(obj)) {
for (let item of obj) {
let found = findObjectById(item, targetId);
if (found) return found;
}
} else if (typeof obj === 'object' && obj !== null) {
if (obj.id === targetId) {
return obj;
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let found = findObjectById(obj[key], targetId);
if (found) return found;
}
}
}
return null; // 如果没有找到则返回null
}
export const useSchemeStore = defineStore('scheme', () => {
const scheme = ref<IPageComponent[]>()
function initScheme(value) {
scheme.value = value
}
function getSchemeObj(id) {
return findObjectById(scheme.value, id)
}
return { scheme, initScheme, getSchemeObj }
})

View File

@ -7,5 +7,8 @@ export interface IComponent {
props: JSON; props: JSON;
style: string; style: string;
class: string; class: string;
children: IComponent[]; events:JSON;
orchestrations:JSON;
version:string;
loop:JSON
} }

View File

@ -0,0 +1,12 @@
import { IComponent } from "./IComponent";
export interface IPageComponent extends IComponent
{
variables:JSON;
dataSources:JSON;
functions:JSON;
header:JSON;
footer:JSON;
meta:JSON;
children: IPageComponent[];
}