feat:【mall】diy 优化 index.vue 的注释
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { DiyComponent, DiyComponentLibrary, PageConfig } from './util';
|
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 { IFrame } from '@vben/common-ui';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
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 NAVIGATION_BAR_COMPONENT } from './components/mobile/navigation-bar/config';
|
||||||
import { component as PAGE_CONFIG_COMPONENT } from './components/mobile/page-config/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';
|
import { component as TAB_BAR_COMPONENT } from './components/mobile/tab-bar/config';
|
||||||
|
|
||||||
/** 页面装修详情页 */
|
/** 页面装修详情页 */
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'DiyPageDetail',
|
name: 'DiyPageDetail',
|
||||||
components,
|
components,
|
||||||
});
|
});
|
||||||
// 定义属性
|
|
||||||
|
/** 定义属性 */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 页面配置,支持Json字符串
|
modelValue: { type: [String, Object], required: true }, // 页面配置,支持 Json 字符串
|
||||||
modelValue: { type: [String, Object], required: true },
|
title: { type: String, default: '' }, // 标题
|
||||||
// 标题
|
libs: { type: Array<DiyComponentLibrary>, default: () => [] }, // 组件库
|
||||||
title: { type: String, default: '' },
|
showNavigationBar: { type: Boolean, default: true }, // 是否显示顶部导航栏
|
||||||
// 组件库
|
showTabBar: { type: Boolean, default: false }, // 是否显示底部导航菜单
|
||||||
libs: { type: Array<DiyComponentLibrary>, default: () => [] },
|
showPageConfig: { type: Boolean, default: true }, // 是否显示页面配置
|
||||||
// 是否显示顶部导航栏
|
previewUrl: { type: String, 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, {
|
const qrcode = useQRCode(props.previewUrl, {
|
||||||
errorCorrectionLevel: 'H',
|
errorCorrectionLevel: 'H',
|
||||||
margin: 4,
|
margin: 4,
|
||||||
});
|
}); // 预览二维码
|
||||||
|
|
||||||
// 左侧组件库
|
const componentLibrary = ref(); // 左侧组件库
|
||||||
const componentLibrary = ref();
|
|
||||||
// 页面设置组件
|
|
||||||
const pageConfigComponent = ref<DiyComponent<any>>(
|
const pageConfigComponent = ref<DiyComponent<any>>(
|
||||||
cloneDeep(PAGE_CONFIG_COMPONENT),
|
cloneDeep(PAGE_CONFIG_COMPONENT),
|
||||||
);
|
); // 页面设置组件
|
||||||
// 顶部导航栏
|
|
||||||
const navigationBarComponent = ref<DiyComponent<any>>(
|
const navigationBarComponent = ref<DiyComponent<any>>(
|
||||||
cloneDeep(NAVIGATION_BAR_COMPONENT),
|
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 selectedComponent = ref<DiyComponent<any>>();
|
const selectedComponentIndex = ref<number>(-1); // 选中的组件索引
|
||||||
// 选中的组件索引
|
const pageComponents = ref<DiyComponent<any>[]>([]); // 组件列表
|
||||||
const selectedComponentIndex = ref<number>(-1);
|
|
||||||
// 组件列表
|
/**
|
||||||
const pageComponents = ref<DiyComponent<any>[]>([]);
|
* 监听传入的页面配置
|
||||||
// 监听传入的页面配置
|
* 解析出 pageConfigComponent 页面整体的配置,navigationBarComponent、pageComponents、tabBarComponent 页面上、中、下的配置
|
||||||
// 解析出 pageConfigComponent 页面整体的配置,navigationBarComponent、pageComponents、tabBarComponent 页面上、中、下的配置
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
() => {
|
() => {
|
||||||
@@ -77,6 +68,7 @@ watch(
|
|||||||
isString(props.modelValue) && !isEmpty(props.modelValue)
|
isString(props.modelValue) && !isEmpty(props.modelValue)
|
||||||
? (JSON.parse(props.modelValue) as PageConfig)
|
? (JSON.parse(props.modelValue) as PageConfig)
|
||||||
: props.modelValue;
|
: props.modelValue;
|
||||||
|
// TODO @AI:这里可以简化么?idea 提示 Invalid 'typeof' check: 'modelValue' cannot have type 'string'
|
||||||
pageConfigComponent.value.property =
|
pageConfigComponent.value.property =
|
||||||
(typeof modelValue !== 'string' && modelValue?.page) ||
|
(typeof modelValue !== 'string' && modelValue?.page) ||
|
||||||
PAGE_CONFIG_COMPONENT.property;
|
PAGE_CONFIG_COMPONENT.property;
|
||||||
@@ -113,19 +105,20 @@ watch(
|
|||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// 保存
|
/** 保存 */
|
||||||
const handleSave = () => {
|
function handleSave() {
|
||||||
// 发送保存通知
|
// 发送保存通知,由外部保存
|
||||||
emits('save');
|
emits('save');
|
||||||
};
|
}
|
||||||
// 监听配置修改
|
|
||||||
const pageConfigChange = () => {
|
/** 监听配置修改 */
|
||||||
|
function pageConfigChange() {
|
||||||
const pageConfig = {
|
const pageConfig = {
|
||||||
page: pageConfigComponent.value.property,
|
page: pageConfigComponent.value.property,
|
||||||
navigationBar: navigationBarComponent.value.property,
|
navigationBar: navigationBarComponent.value.property,
|
||||||
tabBar: tabBarComponent.value.property,
|
tabBar: tabBarComponent.value.property,
|
||||||
components: pageComponents.value.map((component) => {
|
components: pageComponents.value.map((component) => {
|
||||||
// 只保留APP有用的字段
|
// 只保留 APP 有用的字段
|
||||||
return { id: component.id, property: component.property };
|
return { id: component.id, property: component.property };
|
||||||
}),
|
}),
|
||||||
} as PageConfig;
|
} as PageConfig;
|
||||||
@@ -137,7 +130,8 @@ const pageConfigChange = () => {
|
|||||||
? JSON.stringify(pageConfig)
|
? JSON.stringify(pageConfig)
|
||||||
: pageConfig;
|
: pageConfig;
|
||||||
emits('update:modelValue', modelValue);
|
emits('update:modelValue', modelValue);
|
||||||
};
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [
|
() => [
|
||||||
pageConfigComponent.value.property,
|
pageConfigComponent.value.property,
|
||||||
@@ -150,15 +144,17 @@ watch(
|
|||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
// 处理页面选中:显示属性表单
|
|
||||||
const handlePageSelected = (event: any) => {
|
|
||||||
if (!props.showPageConfig) return;
|
|
||||||
|
|
||||||
|
/** 处理页面选中:显示属性表单 */
|
||||||
|
function handlePageSelected(event: any) {
|
||||||
|
if (!props.showPageConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 配置了样式 page-prop-area 的元素,才显示页面设置
|
// 配置了样式 page-prop-area 的元素,才显示页面设置
|
||||||
if (event?.target?.classList?.contains('page-prop-area')) {
|
if (event?.target?.classList?.contains('page-prop-area')) {
|
||||||
handleComponentSelected(unref(pageConfigComponent));
|
handleComponentSelected(unref(pageConfigComponent));
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选中组件
|
* 选中组件
|
||||||
@@ -166,26 +162,26 @@ const handlePageSelected = (event: any) => {
|
|||||||
* @param component 组件
|
* @param component 组件
|
||||||
* @param index 组件的索引
|
* @param index 组件的索引
|
||||||
*/
|
*/
|
||||||
const handleComponentSelected = (
|
function handleComponentSelected(
|
||||||
component: DiyComponent<any>,
|
component: DiyComponent<any>,
|
||||||
index: number = -1,
|
index: number = -1,
|
||||||
) => {
|
) {
|
||||||
selectedComponent.value = component;
|
selectedComponent.value = component;
|
||||||
selectedComponentIndex.value = index;
|
selectedComponentIndex.value = index;
|
||||||
};
|
}
|
||||||
|
|
||||||
// 选中顶部导航栏
|
/** 选中顶部导航栏 */
|
||||||
const handleNavigationBarSelected = () => {
|
function handleNavigationBarSelected() {
|
||||||
handleComponentSelected(unref(navigationBarComponent));
|
handleComponentSelected(unref(navigationBarComponent));
|
||||||
};
|
}
|
||||||
|
|
||||||
// 选中底部导航菜单
|
/** 选中底部导航菜单 */
|
||||||
const handleTabBarSelected = () => {
|
function handleTabBarSelected() {
|
||||||
handleComponentSelected(unref(tabBarComponent));
|
handleComponentSelected(unref(tabBarComponent));
|
||||||
};
|
}
|
||||||
|
|
||||||
// 组件变动(拖拽)
|
/** 组件变动(拖拽) */
|
||||||
const handleComponentChange = (dragEvent: any) => {
|
function handleComponentChange(dragEvent: any) {
|
||||||
// 新增,即从组件库拖拽添加组件
|
// 新增,即从组件库拖拽添加组件
|
||||||
if (dragEvent.added) {
|
if (dragEvent.added) {
|
||||||
const { element, newIndex } = dragEvent.added;
|
const { element, newIndex } = dragEvent.added;
|
||||||
@@ -196,40 +192,38 @@ const handleComponentChange = (dragEvent: any) => {
|
|||||||
// 保持选中
|
// 保持选中
|
||||||
selectedComponentIndex.value = newIndex;
|
selectedComponentIndex.value = newIndex;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 交换组件
|
/** 交换组件 */
|
||||||
const swapComponent = (oldIndex: number, newIndex: number) => {
|
function swapComponent(oldIndex: number, newIndex: number) {
|
||||||
const temp = pageComponents.value[oldIndex]!;
|
const temp = pageComponents.value[oldIndex]!;
|
||||||
pageComponents.value[oldIndex] = pageComponents.value[newIndex]!;
|
pageComponents.value[oldIndex] = pageComponents.value[newIndex]!;
|
||||||
pageComponents.value[newIndex] = temp;
|
pageComponents.value[newIndex] = temp;
|
||||||
// 保持选中
|
// 保持选中
|
||||||
selectedComponentIndex.value = newIndex;
|
selectedComponentIndex.value = newIndex;
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 移动组件(上移、下移) */
|
/** 移动组件(上移、下移) */
|
||||||
const handleMoveComponent = (index: number, direction: number) => {
|
function handleMoveComponent(index: number, direction: number) {
|
||||||
const newIndex = index + direction;
|
const newIndex = index + direction;
|
||||||
if (newIndex < 0 || newIndex >= pageComponents.value.length) return;
|
if (newIndex < 0 || newIndex >= pageComponents.value.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
swapComponent(index, newIndex);
|
swapComponent(index, newIndex);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 复制组件 */
|
/** 复制组件 */
|
||||||
const handleCopyComponent = (index: number) => {
|
function handleCopyComponent(index: number) {
|
||||||
const component = pageComponents.value[index];
|
const component = pageComponents.value[index];
|
||||||
if (component) {
|
if (component) {
|
||||||
const clonedComponent = cloneDeep(component);
|
const clonedComponent = cloneDeep(component);
|
||||||
clonedComponent.uid = Date.now();
|
clonedComponent.uid = Date.now();
|
||||||
pageComponents.value.splice(index + 1, 0, clonedComponent);
|
pageComponents.value.splice(index + 1, 0, clonedComponent);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/** 删除组件 */
|
||||||
* 删除组件
|
function handleDeleteComponent(index: number) {
|
||||||
* @param index 当前组件index
|
|
||||||
*/
|
|
||||||
const handleDeleteComponent = (index: number) => {
|
|
||||||
// 删除组件
|
// 删除组件
|
||||||
pageComponents.value.splice(index, 1);
|
pageComponents.value.splice(index, 1);
|
||||||
if (index < pageComponents.value.length) {
|
if (index < pageComponents.value.length) {
|
||||||
@@ -250,25 +244,23 @@ const handleDeleteComponent = (index: number) => {
|
|||||||
// 3. 组件全部删除之后,显示页面设置
|
// 3. 组件全部删除之后,显示页面设置
|
||||||
handleComponentSelected(unref(pageConfigComponent));
|
handleComponentSelected(unref(pageConfigComponent));
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// // 注入无感刷新页面函数
|
/** 重置 */
|
||||||
// const reload = inject<() => void>('reload'); // TODO @芋艿:是 vue3 + element-plus 独有的,可以清理掉。
|
function handleReset() {
|
||||||
// // 重置
|
emits('reset');
|
||||||
// const handleReset = () => {
|
}
|
||||||
// if (reload) reload();
|
|
||||||
// emits('reset');
|
|
||||||
// };
|
|
||||||
|
|
||||||
// 预览
|
// TODO @AI:搞成 modal 来?
|
||||||
|
/** 预览 */
|
||||||
const previewDialogVisible = ref(false);
|
const previewDialogVisible = ref(false);
|
||||||
const handlePreview = () => {
|
function handlePreview() {
|
||||||
previewDialogVisible.value = true;
|
previewDialogVisible.value = true;
|
||||||
emits('preview');
|
emits('preview');
|
||||||
};
|
}
|
||||||
|
|
||||||
// 设置默认选中的组件
|
/** 设置默认选中的组件 */
|
||||||
const setDefaultSelectedComponent = () => {
|
function setDefaultSelectedComponent() {
|
||||||
if (props.showPageConfig) {
|
if (props.showPageConfig) {
|
||||||
selectedComponent.value = unref(pageConfigComponent);
|
selectedComponent.value = unref(pageConfigComponent);
|
||||||
} else if (props.showNavigationBar) {
|
} else if (props.showNavigationBar) {
|
||||||
@@ -276,12 +268,14 @@ const setDefaultSelectedComponent = () => {
|
|||||||
} else if (props.showTabBar) {
|
} else if (props.showTabBar) {
|
||||||
selectedComponent.value = unref(tabBarComponent);
|
selectedComponent.value = unref(tabBarComponent);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
|
() => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
|
||||||
() => setDefaultSelectedComponent(),
|
() => setDefaultSelectedComponent(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setDefaultSelectedComponent();
|
setDefaultSelectedComponent();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,37 +2,29 @@ import type { NavigationBarProperty } from './components/mobile/navigation-bar/c
|
|||||||
import type { PageConfigProperty } from './components/mobile/page-config/config';
|
import type { PageConfigProperty } from './components/mobile/page-config/config';
|
||||||
import type { TabBarProperty } from './components/mobile/tab-bar/config';
|
import type { TabBarProperty } from './components/mobile/tab-bar/config';
|
||||||
|
|
||||||
// 页面装修组件
|
/** 页面装修组件 */
|
||||||
export interface DiyComponent<T> {
|
export interface DiyComponent<T> {
|
||||||
// 用于区分同一种组件的不同实例
|
uid?: number; // 用于区分同一种组件的不同实例
|
||||||
uid?: number;
|
id: string; // 组件唯一标识
|
||||||
// 组件唯一标识
|
name: string; // 组件名称
|
||||||
id: string;
|
icon: string; // 组件图标
|
||||||
// 组件名称
|
|
||||||
name: string;
|
|
||||||
// 组件图标
|
|
||||||
icon: string;
|
|
||||||
/*
|
/*
|
||||||
组件位置:
|
组件位置:
|
||||||
top: 固定于手机顶部,例如 顶部的导航栏
|
top: 固定于手机顶部,例如 顶部的导航栏
|
||||||
bottom: 固定于手机底部,例如 底部的菜单导航栏
|
bottom: 固定于手机底部,例如 底部的菜单导航栏
|
||||||
center: 位于手机中心,每个组件占一行,顺序向下排列
|
center: 位于手机中心,每个组件占一行,顺序向下排列
|
||||||
空:同center
|
空:同 center
|
||||||
fixed: 由组件自己决定位置,如弹窗位于手机中心、浮动按钮一般位于手机右下角
|
fixed: 由组件自己决定位置,如弹窗位于手机中心、浮动按钮一般位于手机右下角
|
||||||
*/
|
*/
|
||||||
position?: '' | 'bottom' | 'center' | 'fixed' | 'top';
|
position?: '' | 'bottom' | 'center' | 'fixed' | 'top';
|
||||||
// 组件属性
|
property: T; // 组件属性
|
||||||
property: T;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面装修组件库
|
/** 页面装修组件库 */
|
||||||
export interface DiyComponentLibrary {
|
export interface DiyComponentLibrary {
|
||||||
// 组件库名称
|
name: string; // 组件库名称
|
||||||
name: string;
|
extended: boolean; // 是否展开
|
||||||
// 是否展开
|
components: string[]; // 组件列表
|
||||||
extended: boolean;
|
|
||||||
// 组件列表
|
|
||||||
components: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件样式
|
// 组件样式
|
||||||
@@ -63,21 +55,18 @@ export interface ComponentStyle {
|
|||||||
borderBottomLeftRadius: number;
|
borderBottomLeftRadius: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面配置
|
/** 页面配置 */
|
||||||
export interface PageConfig {
|
export interface PageConfig {
|
||||||
// 页面属性
|
page: PageConfigProperty; // 页面属性
|
||||||
page: PageConfigProperty;
|
navigationBar: NavigationBarProperty; // 顶部导航栏属性
|
||||||
// 顶部导航栏属性
|
tabBar?: TabBarProperty; // 底部导航菜单属性
|
||||||
navigationBar: NavigationBarProperty;
|
|
||||||
// 底部导航菜单属性
|
|
||||||
tabBar?: TabBarProperty;
|
|
||||||
// 页面组件列表
|
|
||||||
components: PageComponent[];
|
|
||||||
}
|
|
||||||
// 页面组件,只保留组件ID,组件属性
|
|
||||||
export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>;
|
|
||||||
|
|
||||||
// 页面组件库
|
components: PageComponent[]; // 页面组件列表
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>; // 页面组件,只保留组件 ID,组件属性
|
||||||
|
|
||||||
|
/** 页面组件库 */
|
||||||
export const PAGE_LIBS = [
|
export const PAGE_LIBS = [
|
||||||
{
|
{
|
||||||
name: '基础组件',
|
name: '基础组件',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { DiyComponent, DiyComponentLibrary, PageConfig } from './util';
|
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 { IFrame } from '@vben/common-ui';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
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 NAVIGATION_BAR_COMPONENT } from './components/mobile/navigation-bar/config';
|
||||||
import { component as PAGE_CONFIG_COMPONENT } from './components/mobile/page-config/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';
|
import { component as TAB_BAR_COMPONENT } from './components/mobile/tab-bar/config';
|
||||||
|
|
||||||
/** 页面装修详情页 */
|
/** 页面装修详情页 */
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'DiyPageDetail',
|
name: 'DiyPageDetail',
|
||||||
components,
|
components,
|
||||||
});
|
});
|
||||||
// 定义属性
|
|
||||||
|
/** 定义属性 */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 页面配置,支持Json字符串
|
modelValue: { type: [String, Object], required: true }, // 页面配置,支持 Json 字符串
|
||||||
modelValue: { type: [String, Object], required: true },
|
title: { type: String, default: '' }, // 标题
|
||||||
// 标题
|
libs: { type: Array<DiyComponentLibrary>, default: () => [] }, // 组件库
|
||||||
title: { type: String, default: '' },
|
showNavigationBar: { type: Boolean, default: true }, // 是否显示顶部导航栏
|
||||||
// 组件库
|
showTabBar: { type: Boolean, default: false }, // 是否显示底部导航菜单
|
||||||
libs: { type: Array<DiyComponentLibrary>, default: () => [] },
|
showPageConfig: { type: Boolean, default: true }, // 是否显示页面配置
|
||||||
// 是否显示顶部导航栏
|
previewUrl: { type: String, 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, {
|
const qrcode = useQRCode(props.previewUrl, {
|
||||||
errorCorrectionLevel: 'H',
|
errorCorrectionLevel: 'H',
|
||||||
margin: 4,
|
margin: 4,
|
||||||
});
|
}); // 预览二维码
|
||||||
|
|
||||||
// 左侧组件库
|
const componentLibrary = ref(); // 左侧组件库
|
||||||
const componentLibrary = ref();
|
|
||||||
// 页面设置组件
|
|
||||||
const pageConfigComponent = ref<DiyComponent<any>>(
|
const pageConfigComponent = ref<DiyComponent<any>>(
|
||||||
cloneDeep(PAGE_CONFIG_COMPONENT),
|
cloneDeep(PAGE_CONFIG_COMPONENT),
|
||||||
);
|
); // 页面设置组件
|
||||||
// 顶部导航栏
|
|
||||||
const navigationBarComponent = ref<DiyComponent<any>>(
|
const navigationBarComponent = ref<DiyComponent<any>>(
|
||||||
cloneDeep(NAVIGATION_BAR_COMPONENT),
|
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 selectedComponent = ref<DiyComponent<any>>();
|
const selectedComponentIndex = ref<number>(-1); // 选中的组件索引
|
||||||
// 选中的组件索引
|
const pageComponents = ref<DiyComponent<any>[]>([]); // 组件列表
|
||||||
const selectedComponentIndex = ref<number>(-1);
|
|
||||||
// 组件列表
|
/**
|
||||||
const pageComponents = ref<DiyComponent<any>[]>([]);
|
* 监听传入的页面配置
|
||||||
// 监听传入的页面配置
|
* 解析出 pageConfigComponent 页面整体的配置,navigationBarComponent、pageComponents、tabBarComponent 页面上、中、下的配置
|
||||||
// 解析出 pageConfigComponent 页面整体的配置,navigationBarComponent、pageComponents、tabBarComponent 页面上、中、下的配置
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
() => {
|
() => {
|
||||||
@@ -86,6 +77,7 @@ watch(
|
|||||||
isString(props.modelValue) && !isEmpty(props.modelValue)
|
isString(props.modelValue) && !isEmpty(props.modelValue)
|
||||||
? (JSON.parse(props.modelValue) as PageConfig)
|
? (JSON.parse(props.modelValue) as PageConfig)
|
||||||
: props.modelValue;
|
: props.modelValue;
|
||||||
|
// TODO @AI:这里可以简化么?idea 提示 Invalid 'typeof' check: 'modelValue' cannot have type 'string'
|
||||||
pageConfigComponent.value.property =
|
pageConfigComponent.value.property =
|
||||||
(typeof modelValue !== 'string' && modelValue?.page) ||
|
(typeof modelValue !== 'string' && modelValue?.page) ||
|
||||||
PAGE_CONFIG_COMPONENT.property;
|
PAGE_CONFIG_COMPONENT.property;
|
||||||
@@ -122,19 +114,20 @@ watch(
|
|||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// 保存
|
/** 保存 */
|
||||||
const handleSave = () => {
|
function handleSave() {
|
||||||
// 发送保存通知
|
// 发送保存通知,由外部保存
|
||||||
emits('save');
|
emits('save');
|
||||||
};
|
}
|
||||||
// 监听配置修改
|
|
||||||
const pageConfigChange = () => {
|
/** 监听配置修改 */
|
||||||
|
function pageConfigChange() {
|
||||||
const pageConfig = {
|
const pageConfig = {
|
||||||
page: pageConfigComponent.value.property,
|
page: pageConfigComponent.value.property,
|
||||||
navigationBar: navigationBarComponent.value.property,
|
navigationBar: navigationBarComponent.value.property,
|
||||||
tabBar: tabBarComponent.value.property,
|
tabBar: tabBarComponent.value.property,
|
||||||
components: pageComponents.value.map((component) => {
|
components: pageComponents.value.map((component) => {
|
||||||
// 只保留APP有用的字段
|
// 只保留 APP 有用的字段
|
||||||
return { id: component.id, property: component.property };
|
return { id: component.id, property: component.property };
|
||||||
}),
|
}),
|
||||||
} as PageConfig;
|
} as PageConfig;
|
||||||
@@ -146,7 +139,8 @@ const pageConfigChange = () => {
|
|||||||
? JSON.stringify(pageConfig)
|
? JSON.stringify(pageConfig)
|
||||||
: pageConfig;
|
: pageConfig;
|
||||||
emits('update:modelValue', modelValue);
|
emits('update:modelValue', modelValue);
|
||||||
};
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [
|
() => [
|
||||||
pageConfigComponent.value.property,
|
pageConfigComponent.value.property,
|
||||||
@@ -159,15 +153,17 @@ watch(
|
|||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
// 处理页面选中:显示属性表单
|
|
||||||
const handlePageSelected = (event: any) => {
|
|
||||||
if (!props.showPageConfig) return;
|
|
||||||
|
|
||||||
|
/** 处理页面选中:显示属性表单 */
|
||||||
|
function handlePageSelected(event: any) {
|
||||||
|
if (!props.showPageConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 配置了样式 page-prop-area 的元素,才显示页面设置
|
// 配置了样式 page-prop-area 的元素,才显示页面设置
|
||||||
if (event?.target?.classList?.contains('page-prop-area')) {
|
if (event?.target?.classList?.contains('page-prop-area')) {
|
||||||
handleComponentSelected(unref(pageConfigComponent));
|
handleComponentSelected(unref(pageConfigComponent));
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选中组件
|
* 选中组件
|
||||||
@@ -175,26 +171,26 @@ const handlePageSelected = (event: any) => {
|
|||||||
* @param component 组件
|
* @param component 组件
|
||||||
* @param index 组件的索引
|
* @param index 组件的索引
|
||||||
*/
|
*/
|
||||||
const handleComponentSelected = (
|
function handleComponentSelected(
|
||||||
component: DiyComponent<any>,
|
component: DiyComponent<any>,
|
||||||
index: number = -1,
|
index: number = -1,
|
||||||
) => {
|
) {
|
||||||
selectedComponent.value = component;
|
selectedComponent.value = component;
|
||||||
selectedComponentIndex.value = index;
|
selectedComponentIndex.value = index;
|
||||||
};
|
}
|
||||||
|
|
||||||
// 选中顶部导航栏
|
/** 选中顶部导航栏 */
|
||||||
const handleNavigationBarSelected = () => {
|
function handleNavigationBarSelected() {
|
||||||
handleComponentSelected(unref(navigationBarComponent));
|
handleComponentSelected(unref(navigationBarComponent));
|
||||||
};
|
}
|
||||||
|
|
||||||
// 选中底部导航菜单
|
/** 选中底部导航菜单 */
|
||||||
const handleTabBarSelected = () => {
|
function handleTabBarSelected() {
|
||||||
handleComponentSelected(unref(tabBarComponent));
|
handleComponentSelected(unref(tabBarComponent));
|
||||||
};
|
}
|
||||||
|
|
||||||
// 组件变动(拖拽)
|
/** 组件变动(拖拽) */
|
||||||
const handleComponentChange = (dragEvent: any) => {
|
function handleComponentChange(dragEvent: any) {
|
||||||
// 新增,即从组件库拖拽添加组件
|
// 新增,即从组件库拖拽添加组件
|
||||||
if (dragEvent.added) {
|
if (dragEvent.added) {
|
||||||
const { element, newIndex } = dragEvent.added;
|
const { element, newIndex } = dragEvent.added;
|
||||||
@@ -205,40 +201,38 @@ const handleComponentChange = (dragEvent: any) => {
|
|||||||
// 保持选中
|
// 保持选中
|
||||||
selectedComponentIndex.value = newIndex;
|
selectedComponentIndex.value = newIndex;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 交换组件
|
/** 交换组件 */
|
||||||
const swapComponent = (oldIndex: number, newIndex: number) => {
|
function swapComponent(oldIndex: number, newIndex: number) {
|
||||||
const temp = pageComponents.value[oldIndex]!;
|
const temp = pageComponents.value[oldIndex]!;
|
||||||
pageComponents.value[oldIndex] = pageComponents.value[newIndex]!;
|
pageComponents.value[oldIndex] = pageComponents.value[newIndex]!;
|
||||||
pageComponents.value[newIndex] = temp;
|
pageComponents.value[newIndex] = temp;
|
||||||
// 保持选中
|
// 保持选中
|
||||||
selectedComponentIndex.value = newIndex;
|
selectedComponentIndex.value = newIndex;
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 移动组件(上移、下移) */
|
/** 移动组件(上移、下移) */
|
||||||
const handleMoveComponent = (index: number, direction: number) => {
|
function handleMoveComponent(index: number, direction: number) {
|
||||||
const newIndex = index + direction;
|
const newIndex = index + direction;
|
||||||
if (newIndex < 0 || newIndex >= pageComponents.value.length) return;
|
if (newIndex < 0 || newIndex >= pageComponents.value.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
swapComponent(index, newIndex);
|
swapComponent(index, newIndex);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 复制组件 */
|
/** 复制组件 */
|
||||||
const handleCopyComponent = (index: number) => {
|
function handleCopyComponent(index: number) {
|
||||||
const component = pageComponents.value[index];
|
const component = pageComponents.value[index];
|
||||||
if (component) {
|
if (component) {
|
||||||
const clonedComponent = cloneDeep(component);
|
const clonedComponent = cloneDeep(component);
|
||||||
clonedComponent.uid = Date.now();
|
clonedComponent.uid = Date.now();
|
||||||
pageComponents.value.splice(index + 1, 0, clonedComponent);
|
pageComponents.value.splice(index + 1, 0, clonedComponent);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/** 删除组件 */
|
||||||
* 删除组件
|
function handleDeleteComponent(index: number) {
|
||||||
* @param index 当前组件index
|
|
||||||
*/
|
|
||||||
const handleDeleteComponent = (index: number) => {
|
|
||||||
// 删除组件
|
// 删除组件
|
||||||
pageComponents.value.splice(index, 1);
|
pageComponents.value.splice(index, 1);
|
||||||
if (index < pageComponents.value.length) {
|
if (index < pageComponents.value.length) {
|
||||||
@@ -259,25 +253,23 @@ const handleDeleteComponent = (index: number) => {
|
|||||||
// 3. 组件全部删除之后,显示页面设置
|
// 3. 组件全部删除之后,显示页面设置
|
||||||
handleComponentSelected(unref(pageConfigComponent));
|
handleComponentSelected(unref(pageConfigComponent));
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// // 注入无感刷新页面函数
|
/** 重置 */
|
||||||
// const reload = inject<() => void>('reload'); // TODO @芋艿:是 vue3 + element-plus 独有的,可以清理掉。
|
function handleReset() {
|
||||||
// // 重置
|
emits('reset');
|
||||||
// const handleReset = () => {
|
}
|
||||||
// if (reload) reload();
|
|
||||||
// emits('reset');
|
|
||||||
// };
|
|
||||||
|
|
||||||
// 预览
|
// TODO @AI:搞成 modal 来?
|
||||||
|
/** 预览 */
|
||||||
const previewDialogVisible = ref(false);
|
const previewDialogVisible = ref(false);
|
||||||
const handlePreview = () => {
|
function handlePreview() {
|
||||||
previewDialogVisible.value = true;
|
previewDialogVisible.value = true;
|
||||||
emits('preview');
|
emits('preview');
|
||||||
};
|
}
|
||||||
|
|
||||||
// 设置默认选中的组件
|
/** 设置默认选中的组件 */
|
||||||
const setDefaultSelectedComponent = () => {
|
function setDefaultSelectedComponent() {
|
||||||
if (props.showPageConfig) {
|
if (props.showPageConfig) {
|
||||||
selectedComponent.value = unref(pageConfigComponent);
|
selectedComponent.value = unref(pageConfigComponent);
|
||||||
} else if (props.showNavigationBar) {
|
} else if (props.showNavigationBar) {
|
||||||
@@ -285,12 +277,14 @@ const setDefaultSelectedComponent = () => {
|
|||||||
} else if (props.showTabBar) {
|
} else if (props.showTabBar) {
|
||||||
selectedComponent.value = unref(tabBarComponent);
|
selectedComponent.value = unref(tabBarComponent);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
|
() => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
|
||||||
() => setDefaultSelectedComponent(),
|
() => setDefaultSelectedComponent(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setDefaultSelectedComponent();
|
setDefaultSelectedComponent();
|
||||||
});
|
});
|
||||||
@@ -489,6 +483,7 @@ onMounted(() => {
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<ElText>手机扫码预览</ElText>
|
<ElText>手机扫码预览</ElText>
|
||||||
<img :src="qrcode" alt="qrcode" class="w-1/2" />
|
<img :src="qrcode" alt="qrcode" class="w-1/2" />
|
||||||
|
<!-- TODO @AI:要不要用 element-plus 组件? -->
|
||||||
<!-- <Qrcode :text="previewUrl" logo="/logo.gif" /> -->
|
<!-- <Qrcode :text="previewUrl" logo="/logo.gif" /> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,37 +2,29 @@ import type { NavigationBarProperty } from './components/mobile/navigation-bar/c
|
|||||||
import type { PageConfigProperty } from './components/mobile/page-config/config';
|
import type { PageConfigProperty } from './components/mobile/page-config/config';
|
||||||
import type { TabBarProperty } from './components/mobile/tab-bar/config';
|
import type { TabBarProperty } from './components/mobile/tab-bar/config';
|
||||||
|
|
||||||
// 页面装修组件
|
/** 页面装修组件 */
|
||||||
export interface DiyComponent<T> {
|
export interface DiyComponent<T> {
|
||||||
// 用于区分同一种组件的不同实例
|
uid?: number; // 用于区分同一种组件的不同实例
|
||||||
uid?: number;
|
id: string; // 组件唯一标识
|
||||||
// 组件唯一标识
|
name: string; // 组件名称
|
||||||
id: string;
|
icon: string; // 组件图标
|
||||||
// 组件名称
|
|
||||||
name: string;
|
|
||||||
// 组件图标
|
|
||||||
icon: string;
|
|
||||||
/*
|
/*
|
||||||
组件位置:
|
组件位置:
|
||||||
top: 固定于手机顶部,例如 顶部的导航栏
|
top: 固定于手机顶部,例如 顶部的导航栏
|
||||||
bottom: 固定于手机底部,例如 底部的菜单导航栏
|
bottom: 固定于手机底部,例如 底部的菜单导航栏
|
||||||
center: 位于手机中心,每个组件占一行,顺序向下排列
|
center: 位于手机中心,每个组件占一行,顺序向下排列
|
||||||
空:同center
|
空:同 center
|
||||||
fixed: 由组件自己决定位置,如弹窗位于手机中心、浮动按钮一般位于手机右下角
|
fixed: 由组件自己决定位置,如弹窗位于手机中心、浮动按钮一般位于手机右下角
|
||||||
*/
|
*/
|
||||||
position?: '' | 'bottom' | 'center' | 'fixed' | 'top';
|
position?: '' | 'bottom' | 'center' | 'fixed' | 'top';
|
||||||
// 组件属性
|
property: T; // 组件属性
|
||||||
property: T;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面装修组件库
|
/** 页面装修组件库 */
|
||||||
export interface DiyComponentLibrary {
|
export interface DiyComponentLibrary {
|
||||||
// 组件库名称
|
name: string; // 组件库名称
|
||||||
name: string;
|
extended: boolean; // 是否展开
|
||||||
// 是否展开
|
components: string[]; // 组件列表
|
||||||
extended: boolean;
|
|
||||||
// 组件列表
|
|
||||||
components: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件样式
|
// 组件样式
|
||||||
@@ -63,21 +55,18 @@ export interface ComponentStyle {
|
|||||||
borderBottomLeftRadius: number;
|
borderBottomLeftRadius: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面配置
|
/** 页面配置 */
|
||||||
export interface PageConfig {
|
export interface PageConfig {
|
||||||
// 页面属性
|
page: PageConfigProperty; // 页面属性
|
||||||
page: PageConfigProperty;
|
navigationBar: NavigationBarProperty; // 顶部导航栏属性
|
||||||
// 顶部导航栏属性
|
tabBar?: TabBarProperty; // 底部导航菜单属性
|
||||||
navigationBar: NavigationBarProperty;
|
|
||||||
// 底部导航菜单属性
|
|
||||||
tabBar?: TabBarProperty;
|
|
||||||
// 页面组件列表
|
|
||||||
components: PageComponent[];
|
|
||||||
}
|
|
||||||
// 页面组件,只保留组件ID,组件属性
|
|
||||||
export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>;
|
|
||||||
|
|
||||||
// 页面组件库
|
components: PageComponent[]; // 页面组件列表
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>; // 页面组件,只保留组件 ID,组件属性
|
||||||
|
|
||||||
|
/** 页面组件库 */
|
||||||
export const PAGE_LIBS = [
|
export const PAGE_LIBS = [
|
||||||
{
|
{
|
||||||
name: '基础组件',
|
name: '基础组件',
|
||||||
|
|||||||
Reference in New Issue
Block a user