feat:【mall】diy 优化 index.vue 的注释

pull/242/head
YunaiV 2025-10-27 12:59:53 +08:00
parent a00c5caf6c
commit b262dc7303
4 changed files with 213 additions and 246 deletions

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { DiyComponent, DiyComponentLibrary, PageConfig } from './util';
import { inject, onMounted, ref, unref, watch } from 'vue';
import { onMounted, ref, unref, watch } from 'vue';
import { IFrame } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
@ -19,57 +19,48 @@ import { componentConfigs, components } from './components/mobile';
import { component as NAVIGATION_BAR_COMPONENT } from './components/mobile/navigation-bar/config';
import { component as PAGE_CONFIG_COMPONENT } from './components/mobile/page-config/config';
import { component as TAB_BAR_COMPONENT } from './components/mobile/tab-bar/config';
/** 页面装修详情页 */
defineOptions({
name: 'DiyPageDetail',
components,
});
//
/** 定义属性 */
const props = defineProps({
// Json
modelValue: { type: [String, Object], required: true },
//
title: { type: String, default: '' },
//
libs: { type: Array<DiyComponentLibrary>, default: () => [] },
//
showNavigationBar: { type: Boolean, default: true },
//
showTabBar: { type: Boolean, default: false },
//
showPageConfig: { type: Boolean, default: true },
//
previewUrl: { type: String, default: '' },
modelValue: { type: [String, Object], required: true }, // Json
title: { type: String, default: '' }, //
libs: { type: Array<DiyComponentLibrary>, default: () => [] }, //
showNavigationBar: { type: Boolean, default: true }, //
showTabBar: { type: Boolean, default: false }, //
showPageConfig: { type: Boolean, default: true }, //
previewUrl: { type: String, default: '' }, //
});
//
const emits = defineEmits(['reset', 'preview', 'save', 'update:modelValue']);
const emits = defineEmits(['reset', 'preview', 'save', 'update:modelValue']); //
const qrcode = useQRCode(props.previewUrl, {
errorCorrectionLevel: 'H',
margin: 4,
});
}); //
//
const componentLibrary = ref();
//
const componentLibrary = ref(); //
const pageConfigComponent = ref<DiyComponent<any>>(
cloneDeep(PAGE_CONFIG_COMPONENT),
);
//
); //
const navigationBarComponent = ref<DiyComponent<any>>(
cloneDeep(NAVIGATION_BAR_COMPONENT),
);
//
const tabBarComponent = ref<DiyComponent<any>>(cloneDeep(TAB_BAR_COMPONENT));
); //
const tabBarComponent = ref<DiyComponent<any>>(cloneDeep(TAB_BAR_COMPONENT)); //
//
const selectedComponent = ref<DiyComponent<any>>();
//
const selectedComponentIndex = ref<number>(-1);
//
const pageComponents = ref<DiyComponent<any>[]>([]);
//
// pageConfigComponent navigationBarComponentpageComponentstabBarComponent
const selectedComponent = ref<DiyComponent<any>>(); //
const selectedComponentIndex = ref<number>(-1); //
const pageComponents = ref<DiyComponent<any>[]>([]); //
/**
* 监听传入的页面配置
* 解析出 pageConfigComponent 页面整体的配置navigationBarComponentpageComponentstabBarComponent 页面上下的配
*/
watch(
() => props.modelValue,
() => {
@ -77,6 +68,7 @@ watch(
isString(props.modelValue) && !isEmpty(props.modelValue)
? (JSON.parse(props.modelValue) as PageConfig)
: props.modelValue;
// TODO @AIidea Invalid 'typeof' check: 'modelValue' cannot have type 'string'
pageConfigComponent.value.property =
(typeof modelValue !== 'string' && modelValue?.page) ||
PAGE_CONFIG_COMPONENT.property;
@ -113,19 +105,20 @@ watch(
{ deep: true },
);
//
const handleSave = () => {
//
/** 保存 */
function handleSave() {
//
emits('save');
};
//
const pageConfigChange = () => {
}
/** 监听配置修改 */
function pageConfigChange() {
const pageConfig = {
page: pageConfigComponent.value.property,
navigationBar: navigationBarComponent.value.property,
tabBar: tabBarComponent.value.property,
components: pageComponents.value.map((component) => {
// APP
// APP
return { id: component.id, property: component.property };
}),
} as PageConfig;
@ -137,7 +130,8 @@ const pageConfigChange = () => {
? JSON.stringify(pageConfig)
: pageConfig;
emits('update:modelValue', modelValue);
};
}
watch(
() => [
pageConfigComponent.value.property,
@ -150,15 +144,17 @@ watch(
},
{ deep: true },
);
//
const handlePageSelected = (event: any) => {
if (!props.showPageConfig) return;
/** 处理页面选中:显示属性表单 */
function handlePageSelected(event: any) {
if (!props.showPageConfig) {
return;
}
// page-prop-area
if (event?.target?.classList?.contains('page-prop-area')) {
handleComponentSelected(unref(pageConfigComponent));
}
};
}
/**
* 选中组件
@ -166,26 +162,26 @@ const handlePageSelected = (event: any) => {
* @param component 组件
* @param index 组件的索引
*/
const handleComponentSelected = (
function handleComponentSelected(
component: DiyComponent<any>,
index: number = -1,
) => {
) {
selectedComponent.value = component;
selectedComponentIndex.value = index;
};
}
//
const handleNavigationBarSelected = () => {
/** 选中顶部导航栏 */
function handleNavigationBarSelected() {
handleComponentSelected(unref(navigationBarComponent));
};
}
//
const handleTabBarSelected = () => {
/** 选中底部导航菜单 */
function handleTabBarSelected() {
handleComponentSelected(unref(tabBarComponent));
};
}
//
const handleComponentChange = (dragEvent: any) => {
/** 组件变动(拖拽) */
function handleComponentChange(dragEvent: any) {
//
if (dragEvent.added) {
const { element, newIndex } = dragEvent.added;
@ -196,40 +192,38 @@ const handleComponentChange = (dragEvent: any) => {
//
selectedComponentIndex.value = newIndex;
}
};
}
//
const swapComponent = (oldIndex: number, newIndex: number) => {
/** 交换组件 */
function swapComponent(oldIndex: number, newIndex: number) {
const temp = pageComponents.value[oldIndex]!;
pageComponents.value[oldIndex] = pageComponents.value[newIndex]!;
pageComponents.value[newIndex] = temp;
//
selectedComponentIndex.value = newIndex;
};
}
/** 移动组件(上移、下移) */
const handleMoveComponent = (index: number, direction: number) => {
function handleMoveComponent(index: number, direction: number) {
const newIndex = index + direction;
if (newIndex < 0 || newIndex >= pageComponents.value.length) return;
if (newIndex < 0 || newIndex >= pageComponents.value.length) {
return;
}
swapComponent(index, newIndex);
};
}
/** 复制组件 */
const handleCopyComponent = (index: number) => {
function handleCopyComponent(index: number) {
const component = pageComponents.value[index];
if (component) {
const clonedComponent = cloneDeep(component);
clonedComponent.uid = Date.now();
pageComponents.value.splice(index + 1, 0, clonedComponent);
}
};
}
/**
* 删除组件
* @param index 当前组件index
*/
const handleDeleteComponent = (index: number) => {
/** 删除组件 */
function handleDeleteComponent(index: number) {
//
pageComponents.value.splice(index, 1);
if (index < pageComponents.value.length) {
@ -250,25 +244,23 @@ const handleDeleteComponent = (index: number) => {
// 3.
handleComponentSelected(unref(pageConfigComponent));
}
};
}
// //
// const reload = inject<() => void>('reload'); // TODO @ vue3 + element-plus
// //
// const handleReset = () => {
// if (reload) reload();
// emits('reset');
// };
/** 重置 */
function handleReset() {
emits('reset');
}
//
// TODO @AI modal
/** 预览 */
const previewDialogVisible = ref(false);
const handlePreview = () => {
function handlePreview() {
previewDialogVisible.value = true;
emits('preview');
};
}
//
const setDefaultSelectedComponent = () => {
/** 设置默认选中的组件 */
function setDefaultSelectedComponent() {
if (props.showPageConfig) {
selectedComponent.value = unref(pageConfigComponent);
} else if (props.showNavigationBar) {
@ -276,12 +268,14 @@ const setDefaultSelectedComponent = () => {
} else if (props.showTabBar) {
selectedComponent.value = unref(tabBarComponent);
}
};
}
watch(
() => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
() => setDefaultSelectedComponent(),
);
/** 初始化 */
onMounted(() => {
setDefaultSelectedComponent();
});

View File

@ -2,37 +2,29 @@ import type { NavigationBarProperty } from './components/mobile/navigation-bar/c
import type { PageConfigProperty } from './components/mobile/page-config/config';
import type { TabBarProperty } from './components/mobile/tab-bar/config';
// 页面装修组件
/** 页面装修组件 */
export interface DiyComponent<T> {
// 用于区分同一种组件的不同实例
uid?: number;
// 组件唯一标识
id: string;
// 组件名称
name: string;
// 组件图标
icon: string;
uid?: number; // 用于区分同一种组件的不同实例
id: string; // 组件唯一标识
name: string; // 组件名称
icon: string; // 组件图标
/*
top:
bottom:
center:
center
fixed:
top:
bottom:
center:
center
fixed:
*/
position?: '' | 'bottom' | 'center' | 'fixed' | 'top';
// 组件属性
property: T;
property: T; // 组件属性
}
// 页面装修组件库
/** 页面装修组件库 */
export interface DiyComponentLibrary {
// 组件库名称
name: string;
// 是否展开
extended: boolean;
// 组件列表
components: string[];
name: string; // 组件库名称
extended: boolean; // 是否展开
components: string[]; // 组件列表
}
// 组件样式
@ -63,21 +55,18 @@ export interface ComponentStyle {
borderBottomLeftRadius: number;
}
// 页面配置
/** 页面配置 */
export interface PageConfig {
// 页面属性
page: PageConfigProperty;
// 顶部导航栏属性
navigationBar: NavigationBarProperty;
// 底部导航菜单属性
tabBar?: TabBarProperty;
// 页面组件列表
components: PageComponent[];
}
// 页面组件只保留组件ID组件属性
export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>;
page: PageConfigProperty; // 页面属性
navigationBar: NavigationBarProperty; // 顶部导航栏属性
tabBar?: TabBarProperty; // 底部导航菜单属性
// 页面组件库
components: PageComponent[]; // 页面组件列表
}
export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>; // 页面组件,只保留组件 ID组件属性
/** 页面组件库 */
export const PAGE_LIBS = [
{
name: '基础组件',

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { DiyComponent, DiyComponentLibrary, PageConfig } from './util';
import { inject, onMounted, ref, unref, watch } from 'vue';
import { onMounted, ref, unref, watch } from 'vue';
import { IFrame } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
@ -28,57 +28,48 @@ import { componentConfigs, components } from './components/mobile';
import { component as NAVIGATION_BAR_COMPONENT } from './components/mobile/navigation-bar/config';
import { component as PAGE_CONFIG_COMPONENT } from './components/mobile/page-config/config';
import { component as TAB_BAR_COMPONENT } from './components/mobile/tab-bar/config';
/** 页面装修详情页 */
defineOptions({
name: 'DiyPageDetail',
components,
});
//
/** 定义属性 */
const props = defineProps({
// Json
modelValue: { type: [String, Object], required: true },
//
title: { type: String, default: '' },
//
libs: { type: Array<DiyComponentLibrary>, default: () => [] },
//
showNavigationBar: { type: Boolean, default: true },
//
showTabBar: { type: Boolean, default: false },
//
showPageConfig: { type: Boolean, default: true },
//
previewUrl: { type: String, default: '' },
modelValue: { type: [String, Object], required: true }, // Json
title: { type: String, default: '' }, //
libs: { type: Array<DiyComponentLibrary>, default: () => [] }, //
showNavigationBar: { type: Boolean, default: true }, //
showTabBar: { type: Boolean, default: false }, //
showPageConfig: { type: Boolean, default: true }, //
previewUrl: { type: String, default: '' }, //
});
//
const emits = defineEmits(['reset', 'preview', 'save', 'update:modelValue']);
const emits = defineEmits(['reset', 'preview', 'save', 'update:modelValue']); //
const qrcode = useQRCode(props.previewUrl, {
errorCorrectionLevel: 'H',
margin: 4,
});
}); //
//
const componentLibrary = ref();
//
const componentLibrary = ref(); //
const pageConfigComponent = ref<DiyComponent<any>>(
cloneDeep(PAGE_CONFIG_COMPONENT),
);
//
); //
const navigationBarComponent = ref<DiyComponent<any>>(
cloneDeep(NAVIGATION_BAR_COMPONENT),
);
//
const tabBarComponent = ref<DiyComponent<any>>(cloneDeep(TAB_BAR_COMPONENT));
); //
const tabBarComponent = ref<DiyComponent<any>>(cloneDeep(TAB_BAR_COMPONENT)); //
//
const selectedComponent = ref<DiyComponent<any>>();
//
const selectedComponentIndex = ref<number>(-1);
//
const pageComponents = ref<DiyComponent<any>[]>([]);
//
// pageConfigComponent navigationBarComponentpageComponentstabBarComponent
const selectedComponent = ref<DiyComponent<any>>(); //
const selectedComponentIndex = ref<number>(-1); //
const pageComponents = ref<DiyComponent<any>[]>([]); //
/**
* 监听传入的页面配置
* 解析出 pageConfigComponent 页面整体的配置navigationBarComponentpageComponentstabBarComponent 页面上下的配
*/
watch(
() => props.modelValue,
() => {
@ -86,6 +77,7 @@ watch(
isString(props.modelValue) && !isEmpty(props.modelValue)
? (JSON.parse(props.modelValue) as PageConfig)
: props.modelValue;
// TODO @AIidea Invalid 'typeof' check: 'modelValue' cannot have type 'string'
pageConfigComponent.value.property =
(typeof modelValue !== 'string' && modelValue?.page) ||
PAGE_CONFIG_COMPONENT.property;
@ -122,19 +114,20 @@ watch(
{ deep: true },
);
//
const handleSave = () => {
//
/** 保存 */
function handleSave() {
//
emits('save');
};
//
const pageConfigChange = () => {
}
/** 监听配置修改 */
function pageConfigChange() {
const pageConfig = {
page: pageConfigComponent.value.property,
navigationBar: navigationBarComponent.value.property,
tabBar: tabBarComponent.value.property,
components: pageComponents.value.map((component) => {
// APP
// APP
return { id: component.id, property: component.property };
}),
} as PageConfig;
@ -146,7 +139,8 @@ const pageConfigChange = () => {
? JSON.stringify(pageConfig)
: pageConfig;
emits('update:modelValue', modelValue);
};
}
watch(
() => [
pageConfigComponent.value.property,
@ -159,15 +153,17 @@ watch(
},
{ deep: true },
);
//
const handlePageSelected = (event: any) => {
if (!props.showPageConfig) return;
/** 处理页面选中:显示属性表单 */
function handlePageSelected(event: any) {
if (!props.showPageConfig) {
return;
}
// page-prop-area
if (event?.target?.classList?.contains('page-prop-area')) {
handleComponentSelected(unref(pageConfigComponent));
}
};
}
/**
* 选中组件
@ -175,26 +171,26 @@ const handlePageSelected = (event: any) => {
* @param component 组件
* @param index 组件的索引
*/
const handleComponentSelected = (
function handleComponentSelected(
component: DiyComponent<any>,
index: number = -1,
) => {
) {
selectedComponent.value = component;
selectedComponentIndex.value = index;
};
}
//
const handleNavigationBarSelected = () => {
/** 选中顶部导航栏 */
function handleNavigationBarSelected() {
handleComponentSelected(unref(navigationBarComponent));
};
}
//
const handleTabBarSelected = () => {
/** 选中底部导航菜单 */
function handleTabBarSelected() {
handleComponentSelected(unref(tabBarComponent));
};
}
//
const handleComponentChange = (dragEvent: any) => {
/** 组件变动(拖拽) */
function handleComponentChange(dragEvent: any) {
//
if (dragEvent.added) {
const { element, newIndex } = dragEvent.added;
@ -205,40 +201,38 @@ const handleComponentChange = (dragEvent: any) => {
//
selectedComponentIndex.value = newIndex;
}
};
}
//
const swapComponent = (oldIndex: number, newIndex: number) => {
/** 交换组件 */
function swapComponent(oldIndex: number, newIndex: number) {
const temp = pageComponents.value[oldIndex]!;
pageComponents.value[oldIndex] = pageComponents.value[newIndex]!;
pageComponents.value[newIndex] = temp;
//
selectedComponentIndex.value = newIndex;
};
}
/** 移动组件(上移、下移) */
const handleMoveComponent = (index: number, direction: number) => {
function handleMoveComponent(index: number, direction: number) {
const newIndex = index + direction;
if (newIndex < 0 || newIndex >= pageComponents.value.length) return;
if (newIndex < 0 || newIndex >= pageComponents.value.length) {
return;
}
swapComponent(index, newIndex);
};
}
/** 复制组件 */
const handleCopyComponent = (index: number) => {
function handleCopyComponent(index: number) {
const component = pageComponents.value[index];
if (component) {
const clonedComponent = cloneDeep(component);
clonedComponent.uid = Date.now();
pageComponents.value.splice(index + 1, 0, clonedComponent);
}
};
}
/**
* 删除组件
* @param index 当前组件index
*/
const handleDeleteComponent = (index: number) => {
/** 删除组件 */
function handleDeleteComponent(index: number) {
//
pageComponents.value.splice(index, 1);
if (index < pageComponents.value.length) {
@ -259,25 +253,23 @@ const handleDeleteComponent = (index: number) => {
// 3.
handleComponentSelected(unref(pageConfigComponent));
}
};
}
// //
// const reload = inject<() => void>('reload'); // TODO @ vue3 + element-plus
// //
// const handleReset = () => {
// if (reload) reload();
// emits('reset');
// };
/** 重置 */
function handleReset() {
emits('reset');
}
//
// TODO @AI modal
/** 预览 */
const previewDialogVisible = ref(false);
const handlePreview = () => {
function handlePreview() {
previewDialogVisible.value = true;
emits('preview');
};
}
//
const setDefaultSelectedComponent = () => {
/** 设置默认选中的组件 */
function setDefaultSelectedComponent() {
if (props.showPageConfig) {
selectedComponent.value = unref(pageConfigComponent);
} else if (props.showNavigationBar) {
@ -285,12 +277,14 @@ const setDefaultSelectedComponent = () => {
} else if (props.showTabBar) {
selectedComponent.value = unref(tabBarComponent);
}
};
}
watch(
() => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
() => setDefaultSelectedComponent(),
);
/** 初始化 */
onMounted(() => {
setDefaultSelectedComponent();
});
@ -489,6 +483,7 @@ onMounted(() => {
<div class="flex flex-col">
<ElText>手机扫码预览</ElText>
<img :src="qrcode" alt="qrcode" class="w-1/2" />
<!-- TODO @AI要不要用 element-plus 组件 -->
<!-- <Qrcode :text="previewUrl" logo="/logo.gif" /> -->
</div>
</div>

View File

@ -2,37 +2,29 @@ import type { NavigationBarProperty } from './components/mobile/navigation-bar/c
import type { PageConfigProperty } from './components/mobile/page-config/config';
import type { TabBarProperty } from './components/mobile/tab-bar/config';
// 页面装修组件
/** 页面装修组件 */
export interface DiyComponent<T> {
// 用于区分同一种组件的不同实例
uid?: number;
// 组件唯一标识
id: string;
// 组件名称
name: string;
// 组件图标
icon: string;
uid?: number; // 用于区分同一种组件的不同实例
id: string; // 组件唯一标识
name: string; // 组件名称
icon: string; // 组件图标
/*
top:
bottom:
center:
center
fixed:
top:
bottom:
center:
center
fixed:
*/
position?: '' | 'bottom' | 'center' | 'fixed' | 'top';
// 组件属性
property: T;
property: T; // 组件属性
}
// 页面装修组件库
/** 页面装修组件库 */
export interface DiyComponentLibrary {
// 组件库名称
name: string;
// 是否展开
extended: boolean;
// 组件列表
components: string[];
name: string; // 组件库名称
extended: boolean; // 是否展开
components: string[]; // 组件列表
}
// 组件样式
@ -63,21 +55,18 @@ export interface ComponentStyle {
borderBottomLeftRadius: number;
}
// 页面配置
/** 页面配置 */
export interface PageConfig {
// 页面属性
page: PageConfigProperty;
// 顶部导航栏属性
navigationBar: NavigationBarProperty;
// 底部导航菜单属性
tabBar?: TabBarProperty;
// 页面组件列表
components: PageComponent[];
}
// 页面组件只保留组件ID组件属性
export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>;
page: PageConfigProperty; // 页面属性
navigationBar: NavigationBarProperty; // 顶部导航栏属性
tabBar?: TabBarProperty; // 底部导航菜单属性
// 页面组件库
components: PageComponent[]; // 页面组件列表
}
export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>; // 页面组件,只保留组件 ID组件属性
/** 页面组件库 */
export const PAGE_LIBS = [
{
name: '基础组件',