Merge 4f73e2b82110a005c33d254cd522bc3c869ba324 into 6f2e8d8d74ed0d8a7ef1eefd0b39b08a90abc6e7

This commit is contained in:
chenPengXu 2026-03-29 22:38:46 +00:00 committed by GitHub
commit f0f420bd1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 491 additions and 5 deletions

View File

@ -154,6 +154,12 @@ const expandHandler = () => {
};
const nodeClickHandler = (event: MouseEvent) => {
const { data, parent } = props;
if (data.comInnerModule) {
data[data.comInnerModule] = parent?.[data.comInnerModule];
data.parentId = parent?.id;
data.type = parent?.type;
}
treeEmit?.('node-click', event, props.data);
};
</script>

View File

@ -100,7 +100,7 @@ const values = ref<FormValue>({});
const curFormConfig = ref<any>([]);
const node = computed(() => editorService.get('node'));
const nodes = computed(() => editorService.get('nodes'));
let innerModuleEditingInfo: { name: string; parentId: string; id: string } | null = null;
const styleFormConfig = [
{
tabPosition: 'right',
@ -116,7 +116,36 @@ const init = async () => {
const type = node.value.type || (node.value.items ? 'container' : 'text');
curFormConfig.value = await propsService.getPropsConfig(type);
values.value = node.value;
innerModuleEditingInfo = null;
if (node.value.comInnerModule) {
//
innerModuleEditingInfo = {
name: node.value.comInnerModule,
parentId: node.value.parentId,
id: String(node.value.id),
};
}
if (innerModuleEditingInfo) {
const { name } = innerModuleEditingInfo;
const curFormConfigItems = curFormConfig.value[0].items;
const { length } = curFormConfigItems;
for (let i = 0; i < length; i++) {
if (curFormConfigItems[i].title !== '属性') {
continue;
}
const len2 = curFormConfigItems[i].items.length;
for (let j = 0; j < len2; j++) {
if (curFormConfigItems[i].items[j].name === name) {
curFormConfigItems[i].items = curFormConfigItems[i].items[j].items;
break;
}
}
}
values.value = node.value[name] || {};
} else {
values.value = node.value;
}
};
watchEffect(init);
@ -128,6 +157,10 @@ onBeforeUnmount(() => {
const submit = async (v: MNode, eventData?: ContainerChangeEventData) => {
try {
if (innerModuleEditingInfo !== null) {
v._innerModuleEditingInfo = innerModuleEditingInfo;
v.id = innerModuleEditingInfo.id;
}
if (!v.id) {
v.id = values.value.id;
}

View File

@ -548,14 +548,31 @@ class Editor extends BaseService {
if (!config?.id) throw new Error('没有配置或者配置缺少id值');
const info = this.getNodeInfo(config.id, false);
let info = this.getNodeInfo(config.id, false);
if (!info.node) throw new Error(`获取不到id为${config.id}的节点`);
const node = toRaw(info.node);
let node = toRaw(info.node);
let tempConfig = config;
if (config._innerModuleEditingInfo) {
// 如果是组件内嵌模块的属性修改则找到组件node进行修改只能是组件内一层的结构 -- 只找了一个parent
const moduleKey = config._innerModuleEditingInfo.name;
const parentNode = toRaw(info.parent);
let newConfig = await this.toggleFixedPosition(toRaw(config), node, root);
const nodeSubModule = (node as any)[moduleKey];
if (nodeSubModule) {
const newObj: Record<string, any> = {};
Object.keys(nodeSubModule).forEach((key) => {
newObj[key] = (config as any)[key];
});
(parentNode as any)[moduleKey] = newObj;
tempConfig = parentNode!;
node = toRaw(info.parent)!;
info = this.getNodeInfo(parentNode!.id, true);
}
}
let newConfig = await this.toggleFixedPosition(toRaw(tempConfig), node, root);
newConfig = mergeWith(cloneDeep(node), newConfig, (objValue, srcValue, key, object: any, source: any) => {
if (typeof srcValue === 'undefined' && Object.hasOwn(source, key)) {
return '';
@ -571,6 +588,14 @@ class Editor extends BaseService {
});
if (!newConfig.type) throw new Error('配置缺少type值');
if (config._innerModuleEditingInfo) {
// 如果是组件内嵌模块的属性修改, 不做后面的更新
return {
oldNode: node,
newNode: newConfig,
changeRecords,
};
}
if (newConfig.type === NodeType.ROOT) {
this.set('root', newConfig as MApp);

View File

@ -0,0 +1,36 @@
{
"version": "0.2.0",
"name": "@tmagic/vue-game",
"type": "module",
"main": "src/index.ts",
"files": [
"src"
],
"engines": {
"node": ">=18"
},
"repository": {
"type": "git",
"url": "https://github.com/Tencent/tmagic-editor.git"
},
"dependencies": {
"@tmagic/form-schema": "workspace:^",
"vue-demi": "^0.14.10"
},
"peerDependencies": {
"@tmagic/core": "workspace:^",
"@tmagic/vue-runtime-help": "workspace:^",
"@vue/composition-api": ">=1.7.2",
"typescript": "catalog:",
"vue": ">=2.6.0 || >=3.5.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
}

View File

@ -0,0 +1,6 @@
import { COMMON_EVENT_PREFIX } from '@tmagic/core';
export default {
methods: [],
events: [{ label: '点击', value: `${COMMON_EVENT_PREFIX}click` }],
};

View File

@ -0,0 +1,43 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineFormConfig } from '@tmagic/form-schema';
export default defineFormConfig([
{
text: '游戏级别',
name: 'level',
type: 'select',
placeholder: '请选择级别',
options: [
{ text: '简单', value: 1 },
{ text: '困难', value: 2 },
],
},
{
name: 'prizeDialog',
type: 'hide',
items: [
{
text: '奖品名称',
name: 'prizeName',
type: 'data-source-input',
},
],
},
]);

View File

@ -0,0 +1,25 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Game from './index.vue';
export { default as config } from './formConfig';
export { default as value } from './initValue';
export { default as event } from './event';
export default Game;

View File

@ -0,0 +1,282 @@
<template>
<div class="demo-game">
<div class="game-box">
<span
class="dot"
:style="{ left: dotPosition.x + 'px', top: dotPosition.y + 'px' }"
@click="handleDotClick"
></span>
<div class="click-counter">
点击次数: {{ clickCount }}/{{ maxClicks }}/可获取{{ config.prizeDialog?.prizeName }}
</div>
</div>
<div class="game-prize-dialog" v-show="isShowPrize">
<div class="dialog-body">
<div class="game-prize-dialog-content">
<div class="game-prize-dialog-title">恭喜你获得 {{ config.prizeDialog?.prizeName }}</div>
</div>
<div class="game-prize-dialog-footer">
<button @click="handlePrizeConfirm">确定</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, type PropType, ref } from 'vue';
import { useApp } from '@tmagic/vue-runtime-help';
// 使
interface MComponent {
id?: string;
[key: string]: any;
}
const COMMON_EVENT_PREFIX = 'tmagic:';
interface GameSchema extends Omit<MComponent, 'id'> {
id?: string;
type?: 'game';
text: string;
level?: number;
prizeDialog?: {
prizeName?: string;
};
}
export default defineComponent<any, any>({
name: 'tmagic-game',
props: {
config: {
type: Object as PropType<GameSchema>,
required: true,
},
containerIndex: Number,
model: {
type: Object,
default: () => ({}),
},
},
setup(props) {
const { app, node } = useApp(props);
// useApp
const gameLevel = props.config?.level || 1;
//
const isShowPrize = ref(false); //
const dotPosition = ref({ x: 0, y: 0 }); //
const clickCount = ref(0); //
const maxClicks = 5; //
let moveInterval: number | null = null; // ID
//
const getMoveInterval = (level: number) => {
return Math.max(100, 1000 - (level - 1) * 200); // 11200ms0.1
};
// game-box
const getRandomPosition = () => {
// game-box
const gameBox = document.querySelector('.game-box');
if (!gameBox) return { x: 0, y: 0 };
const boxRect = gameBox.getBoundingClientRect();
const boxWidth = boxRect.width;
const boxHeight = boxRect.height;
// dotCSS
const dotSize = 10; // widthheight10px
// dotbox
const maxX = boxWidth - dotSize;
const maxY = boxHeight - dotSize;
//
return {
x: Math.floor(Math.random() * maxX),
y: Math.floor(Math.random() * maxY),
};
};
//
const moveDot = () => {
const newPosition = getRandomPosition();
dotPosition.value = newPosition;
};
//
const startMoving = () => {
if (moveInterval) {
clearInterval(moveInterval);
}
//
dotPosition.value = getRandomPosition();
//
moveInterval = window.setInterval(moveDot, getMoveInterval(gameLevel));
};
//
const stopMoving = () => {
if (moveInterval) {
clearInterval(moveInterval);
moveInterval = null;
}
};
//
const resetGame = () => {
//
clickCount.value = 0;
//
isShowPrize.value = false;
//
startMoving();
};
//
const handlePrizeConfirm = () => {
resetGame();
};
//
const handleDotClick = () => {
// 使++
clickCount.value = clickCount.value + 1;
//
if (app && node && typeof app.emit === 'function') {
app.emit(`${COMMON_EVENT_PREFIX}click`, node);
}
//
if (clickCount.value >= maxClicks) {
//
isShowPrize.value = true;
// 便
if (moveInterval) {
clearInterval(moveInterval);
moveInterval = null;
}
}
};
//
if (app && app.platform === 'editor' && app.page) {
console.log('-- props.config --', props, app, app.page.on);
// TreeNode
const onEditorSelectHandler = (info: any, _path: any[]) => {
if (info.node?.comInnerModule === 'prizeDialog' && info.node?.parentId === props.config?.id) {
isShowPrize.value = true;
} else {
isShowPrize.value = false;
}
console.log('-- onEditorSelectHandler --', info, props.config);
};
// editor model
setTimeout(() => {
app.page.on('editor:select', onEditorSelectHandler);
}, 1000); //
}
//
onMounted(() => {
startMoving();
});
onUnmounted(() => {
stopMoving();
});
return {
handleDotClick,
handlePrizeConfirm,
dotPosition,
isShowPrize,
clickCount,
maxClicks,
};
},
});
</script>
<style scoped>
.game-box {
position: relative;
width: 400px;
height: 300px;
border: 2px solid #333;
overflow: hidden;
background-color: #f0f0f0;
}
.game-box .dot {
position: absolute;
top: 0;
left: 0;
width: 30px;
height: 30px;
background-color: #007bff;
border-radius: 50%;
cursor: pointer;
transition: background-color 0.2s ease;
}
.game-box .dot:hover {
background-color: #0056b3;
transform: scale(1.2);
}
.game-box .click-counter {
position: absolute;
top: 10px;
left: 0px;
right: 0px;
text-align: center;
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 14px;
}
.game-prize-dialog {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.game-prize-dialog .dialog-body {
background-color: #fff;
width: 300px;
min-height: 200px;
margin: 0 auto;
padding: 20px;
border-radius: 10px;
}
.game-prize-dialog-title {
font-size: 18px;
font-weight: bold;
text-align: center;
margin-bottom: 20px;
}
.game-prize-dialog-footer {
text-align: center;
}
.game-prize-dialog-footer button {
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 5px;
}
</style>

View File

@ -0,0 +1,30 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default {
style: {
width: '100%',
height: '200',
border: 0,
backgroundColor: '#fb6f00',
},
level: 1,
prizeDialog: {
prizeName: '可乐糖',
},
};