Files
qiming/qiming-vite-plugin-design-mode/packages/client-vue/src/DesignModeApp.vue
2026-06-01 13:43:09 +08:00

171 lines
5.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { onMounted, onUnmounted, watch, unref } from 'vue';
import { createDesignMode } from './composables/useDesignMode';
import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
// Create and provide design mode context
const designMode = createDesignMode();
// Global event handlers
const handleClick = (e: MouseEvent) => {
if ((e.target as HTMLElement).closest('#__vite_plugin_design_mode__')) return;
if ((e.target as HTMLElement).closest(`[${AttributeNames.contextMenu}="true"]`)) return;
if (
(e.target as HTMLElement).closest(`[${AttributeNames.staticContent}="true"]`) ||
(e.target as HTMLElement).closest(`[${AttributeNames.staticClass}="true"]`)
) {
e.preventDefault();
e.stopPropagation();
const target = e.target as HTMLElement;
designMode.selectElement(target);
}
};
const handleMouseOver = (e: MouseEvent) => {
if ((e.target as HTMLElement).closest('#__vite_plugin_design_mode__')) return;
if ((e.target as HTMLElement).closest(`[${AttributeNames.contextMenu}="true"]`)) return;
if (
(e.target as HTMLElement).hasAttribute(AttributeNames.staticContent) ||
(e.target as HTMLElement).hasAttribute(AttributeNames.staticClass)
) {
const target = e.target as HTMLElement;
target.setAttribute('data-design-hover', 'true');
designMode.setHoveredElement(target);
}
};
const handleMouseOut = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (!target.hasAttribute(AttributeNames.contextMenuHover)) {
target.removeAttribute('data-design-hover');
}
designMode.setHoveredElement(null);
};
// Watch design mode stateAPI 上为 ComputedRef需 unref 才能拿到布尔值)
watch(
() => unref(designMode.isDesignMode),
isDesignMode => {
if (!isDesignMode) {
document.querySelectorAll('[data-design-selected]').forEach(el => {
el.removeAttribute('data-design-selected');
});
}
}
);
// Watch selected element
watch(
() => unref(designMode.selectedElement),
(selectedElement, oldElement) => {
if (oldElement) {
oldElement.removeAttribute('data-design-selected');
}
if (selectedElement) {
selectedElement.setAttribute('data-design-selected', 'true');
}
}
);
// Lifecycle hooks
onMounted(() => {
if (!unref(designMode.isDesignMode)) return;
document.addEventListener('click', handleClick, true);
document.addEventListener('mouseover', handleMouseOver);
document.addEventListener('mouseout', handleMouseOut);
// Inject global styles
const styleId = 'appdev-design-mode-styles';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
[data-design-hover="true"] {
outline: 2px dashed #60a5fa !important;
outline-offset: 2px;
cursor: pointer;
}
[data-design-selected="true"] {
outline: 2px solid #2563eb !important;
outline-offset: 2px;
}
[contenteditable="true"] {
outline: 2px solid #22c55e !important;
cursor: text;
background-color: rgba(34, 197, 94, 0.1);
}
`;
document.head.appendChild(style);
}
});
onUnmounted(() => {
document.removeEventListener('click', handleClick, true);
document.removeEventListener('mouseover', handleMouseOver);
document.removeEventListener('mouseout', handleMouseOut);
const style = document.getElementById('appdev-design-mode-styles');
if (style) {
style.remove();
}
document.querySelectorAll('[data-design-hover]').forEach(el => {
el.removeAttribute('data-design-hover');
});
});
// Re-setup listeners when design mode changes
watch(
() => unref(designMode.isDesignMode),
isDesignMode => {
if (isDesignMode) {
document.addEventListener('click', handleClick, true);
document.addEventListener('mouseover', handleMouseOver);
document.addEventListener('mouseout', handleMouseOut);
const styleId = 'appdev-design-mode-styles';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
[data-design-hover="true"] {
outline: 2px dashed #60a5fa !important;
outline-offset: 2px;
cursor: pointer;
}
[data-design-selected="true"] {
outline: 2px solid #2563eb !important;
outline-offset: 2px;
}
[contenteditable="true"] {
outline: 2px solid #22c55e !important;
cursor: text;
background-color: rgba(34, 197, 94, 0.1);
}
`;
document.head.appendChild(style);
}
} else {
document.removeEventListener('click', handleClick, true);
document.removeEventListener('mouseover', handleMouseOver);
document.removeEventListener('mouseout', handleMouseOut);
document.querySelectorAll('[data-design-hover]').forEach(el => {
el.removeAttribute('data-design-hover');
});
}
}
);
</script>
<!--
@xagi/design-mode-client-react 注入入口对齐React 端仅挂载 DesignModeProvider + DesignModeManager
DesignModeManager 返回 null不在 iframe 内渲染Design Mode开关/编辑面板
设计模式开关与样式编辑由父应用XAGI shell 通过 TOGGLE_DESIGN_MODE / 后续消息驱动
若宿主需要本地面板可从包内导出自行挂载 DesignModeUI.vue / ToastContainer.vue
-->
<template></template>