feat:【antd】【mall】diy-editor 的整体继续迁移
This commit is contained in:
@@ -131,13 +131,9 @@ const handleSliderChange = (prop: string) => {
|
||||
</TabPane>
|
||||
|
||||
<!-- 每个组件的通用内容 -->
|
||||
<TabPane tab="样式" key="style">
|
||||
<TabPane tab="样式" key="style" force-render>
|
||||
<Card title="组件样式" class="property-group">
|
||||
<Form
|
||||
:model="formData"
|
||||
label-col="{ span: 6 }"
|
||||
wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form :model="formData">
|
||||
<FormItem label="组件背景" name="bgType">
|
||||
<RadioGroup v-model:value="formData.bgType">
|
||||
<Radio value="color">纯色</Radio>
|
||||
@@ -160,24 +156,22 @@ const handleSliderChange = (prop: string) => {
|
||||
<template #tip>建议宽度 750px</template>
|
||||
</UploadImg>
|
||||
</FormItem>
|
||||
<Tree
|
||||
:tree-data="treeData"
|
||||
:expand-on-click-node="false"
|
||||
default-expand-all
|
||||
>
|
||||
<template #title="{ data, node }">
|
||||
<Tree :tree-data="treeData" default-expand-all>
|
||||
<template #title="{ dataRef }">
|
||||
<FormItem
|
||||
:label="data.label"
|
||||
:name="data.prop"
|
||||
:label="dataRef.label"
|
||||
:name="dataRef.prop"
|
||||
:label-col="dataRef.children ? { span: 6 } : { span: 5, offset: 1 }"
|
||||
:wrapper-col="dataRef.children ? { span: 18 } : { span: 18 }"
|
||||
class="mb-0 w-full"
|
||||
>
|
||||
<Slider
|
||||
v-model:value="
|
||||
formData[data.prop as keyof ComponentStyle] as number
|
||||
formData[dataRef.prop as keyof ComponentStyle] as number
|
||||
"
|
||||
:max="100"
|
||||
:min="0"
|
||||
@change="handleSliderChange(data.prop)"
|
||||
@change="handleSliderChange(dataRef.prop)"
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
@@ -197,4 +191,14 @@ const handleSliderChange = (prop: string) => {
|
||||
:deep(.ant-input-number) {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
:deep(.ant-tree) {
|
||||
.ant-tree-node-content-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,9 +49,7 @@ const emits = defineEmits<{
|
||||
type DiyComponentWithStyle = DiyComponent<any> & {
|
||||
property: { style?: ComponentStyle };
|
||||
};
|
||||
/**
|
||||
* 组件样式
|
||||
*/
|
||||
/** 组件样式 */
|
||||
const style = computed(() => {
|
||||
const componentStyle = props.component.property.style;
|
||||
if (!componentStyle) {
|
||||
@@ -78,38 +76,27 @@ const style = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* 移动组件
|
||||
* @param direction 移动方向
|
||||
*/
|
||||
/** 移动组件 */
|
||||
const handleMoveComponent = (direction: number) => {
|
||||
emits('move', direction);
|
||||
};
|
||||
|
||||
/**
|
||||
* 复制组件
|
||||
*/
|
||||
/** 复制组件 */
|
||||
const handleCopyComponent = () => {
|
||||
emits('copy');
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除组件
|
||||
*/
|
||||
/** 删除组件 */
|
||||
const handleDeleteComponent = () => {
|
||||
emits('delete');
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="component" :class="[{ active }]">
|
||||
<div
|
||||
:style="{
|
||||
...style,
|
||||
}"
|
||||
>
|
||||
<div class="component relative cursor-move" :class="[{ active }]">
|
||||
<div :style="style">
|
||||
<component :is="component.id" :property="component.property" />
|
||||
</div>
|
||||
<div class="component-wrap">
|
||||
<div class="component-wrap absolute left-[-2px] top-0 block h-full w-full">
|
||||
<!-- 左侧:组件名(悬浮的小贴条) -->
|
||||
<div class="component-name" v-if="component.name">
|
||||
{{ component.name }}
|
||||
@@ -158,19 +145,8 @@ $hover-border-width: 1px;
|
||||
$name-position: -85px;
|
||||
$toolbar-position: -55px;
|
||||
|
||||
/* 组件 */
|
||||
.component {
|
||||
position: relative;
|
||||
cursor: move;
|
||||
|
||||
.component-wrap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -$active-border-width;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
/* 鼠标放到组件上时 */
|
||||
&:hover {
|
||||
border: $hover-border-width dashed var(--ant-color-primary);
|
||||
@@ -236,7 +212,7 @@ $toolbar-position: -55px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 组件选中时 */
|
||||
/* 选中状态 */
|
||||
&.active {
|
||||
margin-bottom: 4px;
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ const handleCloneComponent = (component: DiyComponent<any>) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<aside
|
||||
class="editor-left z-[1] w-[261px] shrink-0 select-none shadow-[8px_0_8px_-8px_rgb(0_0_0/0.12)]"
|
||||
>
|
||||
<div class="h-full overflow-y-auto">
|
||||
@@ -104,7 +104,7 @@ const handleCloneComponent = (component: DiyComponent<any>) => {
|
||||
</CollapsePanel>
|
||||
</Collapse>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,4 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { Page } from '@vben/common-ui';
|
||||
import type { SearchProperty } from './config';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { isString } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
Card,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
RadioButton,
|
||||
RadioGroup,
|
||||
Slider,
|
||||
Switch,
|
||||
Tooltip,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { ColorInput, Draggable } from '#/views/mall/promotion/components';
|
||||
|
||||
import ComponentContainerProperty from '../../component-container-property.vue';
|
||||
|
||||
/** 搜索框属性面板 */
|
||||
defineOptions({ name: 'SearchProperty' });
|
||||
|
||||
const props = defineProps<{ modelValue: SearchProperty }>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const formData = useVModel(props, 'modelValue', emit);
|
||||
|
||||
/** 监听热词数组变化 */
|
||||
watch(
|
||||
() => formData.value.hotKeywords,
|
||||
(newVal) => {
|
||||
// 找到非字符串项的索引
|
||||
const nonStringIndex = newVal.findIndex((item) => !isString(item));
|
||||
if (nonStringIndex !== -1) {
|
||||
formData.value.hotKeywords[nonStringIndex] = '';
|
||||
}
|
||||
},
|
||||
{ deep: true, flush: 'post' },
|
||||
);
|
||||
</script>
|
||||
<template><Page>待完成</Page></template>
|
||||
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<Form :model="formData">
|
||||
<Card title="搜索热词" class="property-group">
|
||||
<Draggable
|
||||
v-model="formData.hotKeywords"
|
||||
:empty-item="{
|
||||
type: 'input',
|
||||
placeholder: '请输入热词',
|
||||
}"
|
||||
>
|
||||
<template #default="{ index }">
|
||||
<Input
|
||||
v-model:value="formData.hotKeywords[index]"
|
||||
placeholder="请输入热词"
|
||||
/>
|
||||
</template>
|
||||
</Draggable>
|
||||
</Card>
|
||||
<Card title="搜索样式" class="property-group">
|
||||
<FormItem label="框体样式">
|
||||
<RadioGroup v-model:value="formData!.borderRadius">
|
||||
<Tooltip title="方形" placement="top">
|
||||
<RadioButton :value="0">
|
||||
<IconifyIcon icon="tabler:input-search" />
|
||||
</RadioButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="圆形" placement="top">
|
||||
<RadioButton :value="10">
|
||||
<IconifyIcon icon="iconoir:input-search" />
|
||||
</RadioButton>
|
||||
</Tooltip>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="提示文字" name="placeholder">
|
||||
<Input v-model:value="formData.placeholder" />
|
||||
</FormItem>
|
||||
<FormItem label="文本位置" name="placeholderPosition">
|
||||
<RadioGroup v-model:value="formData!.placeholderPosition">
|
||||
<Tooltip title="居左" placement="top">
|
||||
<RadioButton value="left">
|
||||
<IconifyIcon icon="ant-design:align-left-outlined" />
|
||||
</RadioButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="居中" placement="top">
|
||||
<RadioButton value="center">
|
||||
<IconifyIcon icon="ant-design:align-center-outlined" />
|
||||
</RadioButton>
|
||||
</Tooltip>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="扫一扫" name="showScan">
|
||||
<Switch v-model:checked="formData!.showScan" />
|
||||
</FormItem>
|
||||
<FormItem label="框体高度" name="height">
|
||||
<Slider
|
||||
v-model:value="formData!.height"
|
||||
:max="50"
|
||||
:min="28"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="框体颜色" name="backgroundColor">
|
||||
<ColorInput v-model="formData.backgroundColor" />
|
||||
</FormItem>
|
||||
<FormItem label="文本颜色" name="textColor">
|
||||
<ColorInput v-model="formData.textColor" />
|
||||
</FormItem>
|
||||
</Card>
|
||||
</Form>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
@@ -232,7 +232,6 @@ function handleCopyComponent(index: number) {
|
||||
|
||||
/** 删除组件 */
|
||||
function handleDeleteComponent(index: number) {
|
||||
// 删除组件
|
||||
pageComponents.value.splice(index, 1);
|
||||
if (index < pageComponents.value.length) {
|
||||
// 1. 不是最后一个组件时,删除后选中下面的组件
|
||||
@@ -325,7 +324,7 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- 中心区域 -->
|
||||
<div class="editor-container flex flex-1">
|
||||
<div class="editor-container h-[calc(100vh-135px)]">
|
||||
<!-- 左侧:组件库(ComponentLibrary) -->
|
||||
<ComponentLibrary
|
||||
v-if="libs && libs.length > 0"
|
||||
@@ -333,11 +332,15 @@ onMounted(() => {
|
||||
:list="libs"
|
||||
/>
|
||||
<!-- 中心:设计区域(ComponentContainer) -->
|
||||
<div class="editor-center page-prop-area" @click="handlePageSelected">
|
||||
<div
|
||||
class="editor-center page-prop-area relative mt-4 flex w-full flex-1 flex-col justify-center overflow-hidden"
|
||||
:style="{ backgroundColor: 'var(--app-content-bg-color)' }"
|
||||
@click="handlePageSelected"
|
||||
>
|
||||
<!-- 手机顶部 -->
|
||||
<div class="editor-design-top">
|
||||
<div class="editor-design-top mx-auto flex w-[375px] flex-col">
|
||||
<!-- 手机顶部状态栏 -->
|
||||
<img alt="" class="status-bar" :src="statusBarImg" />
|
||||
<img alt="" class="h-5 w-[375px] bg-white" :src="statusBarImg" />
|
||||
<!-- 手机顶部导航栏 -->
|
||||
<ComponentContainer
|
||||
v-if="showNavigationBar"
|
||||
@@ -365,45 +368,46 @@ onMounted(() => {
|
||||
</div>
|
||||
<!-- 手机页面编辑区域 -->
|
||||
<div
|
||||
class="editor-design-center page-prop-area phone-container overflow-y-auto"
|
||||
class="editor-design-center page-prop-area h-full w-full overflow-y-auto"
|
||||
:style="{
|
||||
backgroundColor: pageConfigComponent.property.backgroundColor,
|
||||
backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`,
|
||||
height: 'calc(100vh - 135px - 120px)',
|
||||
}"
|
||||
>
|
||||
<draggable
|
||||
v-model="pageComponents"
|
||||
:animation="200"
|
||||
:force-fallback="true"
|
||||
class="page-prop-area drag-area"
|
||||
filter=".component-toolbar"
|
||||
ghost-class="draggable-ghost"
|
||||
group="component"
|
||||
item-key="index"
|
||||
@change="handleComponentChange"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<ComponentContainer
|
||||
v-if="!element.position || element.position === 'center'"
|
||||
:active="selectedComponentIndex === index"
|
||||
:can-move-down="index < pageComponents.length - 1"
|
||||
:can-move-up="index > 0"
|
||||
:component="element"
|
||||
@click="handleComponentSelected(element, index)"
|
||||
@copy="handleCopyComponent(index)"
|
||||
@delete="handleDeleteComponent(index)"
|
||||
@move="
|
||||
(direction: number) => handleMoveComponent(index, direction)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
<div class="phone-container">
|
||||
<draggable
|
||||
v-model="pageComponents"
|
||||
:animation="200"
|
||||
:force-fallback="false"
|
||||
class="page-prop-area drag-area"
|
||||
filter=".component-toolbar"
|
||||
ghost-class="draggable-ghost"
|
||||
group="component"
|
||||
item-key="index"
|
||||
@change="handleComponentChange"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<ComponentContainer
|
||||
v-if="!element.position || element.position === 'center'"
|
||||
:active="selectedComponentIndex === index"
|
||||
:can-move-down="index < pageComponents.length - 1"
|
||||
:can-move-up="index > 0"
|
||||
:component="element"
|
||||
@click="handleComponentSelected(element, index)"
|
||||
@copy="handleCopyComponent(index)"
|
||||
@delete="handleDeleteComponent(index)"
|
||||
@move="
|
||||
(direction: number) => handleMoveComponent(index, direction)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 手机底部导航 -->
|
||||
<div
|
||||
v-if="showTabBar"
|
||||
class="editor-design-bottom component cursor-pointer"
|
||||
class="editor-design-bottom component mx-auto w-[375px] cursor-pointer"
|
||||
>
|
||||
<ComponentContainer
|
||||
:active="selectedComponent?.id === tabBarComponent.id"
|
||||
@@ -413,7 +417,9 @@ onMounted(() => {
|
||||
/>
|
||||
</div>
|
||||
<!-- 固定布局的组件 操作按钮区 -->
|
||||
<div class="fixed-component-action-group gap-2">
|
||||
<div
|
||||
class="fixed-component-action-group absolute right-4 top-0 flex flex-col gap-2"
|
||||
>
|
||||
<Tag
|
||||
v-if="showPageConfig"
|
||||
:color="
|
||||
@@ -422,6 +428,7 @@ onMounted(() => {
|
||||
: 'default'
|
||||
"
|
||||
class="cursor-pointer"
|
||||
size="large"
|
||||
@click="handleComponentSelected(pageConfigComponent)"
|
||||
>
|
||||
<IconifyIcon :icon="pageConfigComponent.icon" :size="12" />
|
||||
@@ -435,6 +442,7 @@ onMounted(() => {
|
||||
"
|
||||
closable
|
||||
class="cursor-pointer"
|
||||
size="large"
|
||||
@click="handleComponentSelected(component)"
|
||||
@close="handleDeleteComponent(index)"
|
||||
>
|
||||
@@ -445,11 +453,11 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:属性面板(ComponentContainerProperty) -->
|
||||
<div v-if="selectedComponent?.property" class="editor-right w-[350px]">
|
||||
<Card
|
||||
class="h-full"
|
||||
:body-style="{ height: 'calc(100% - 57px)', padding: 0 }"
|
||||
>
|
||||
<aside
|
||||
v-if="selectedComponent?.property"
|
||||
class="editor-right w-[350px] shrink-0 overflow-hidden shadow-[-8px_0_8px_-8px_rgb(0_0_0/0.12)]"
|
||||
>
|
||||
<Card class="h-full" :body-style="{ padding: 0, height: 'calc(100% - 57px)' }">
|
||||
<!-- 组件名称 -->
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -465,7 +473,7 @@ onMounted(() => {
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -524,14 +532,10 @@ $phone-width: 375px;
|
||||
|
||||
/* 中心操作区 */
|
||||
.editor-container {
|
||||
height: calc(100vh - 135px);
|
||||
display: flex;
|
||||
|
||||
/* 右侧属性面板 */
|
||||
:deep(.editor-right) {
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
box-shadow: -8px 0 8px -8px rgb(0 0 0 / 12%);
|
||||
|
||||
/* 属性面板顶部:减少内边距 */
|
||||
:deep(.ant-card-head) {
|
||||
padding: 8px 16px;
|
||||
@@ -560,37 +564,6 @@ $phone-width: 375px;
|
||||
|
||||
/* 中心区域 */
|
||||
.editor-center {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin: 16px 0 0;
|
||||
overflow: hidden;
|
||||
background-color: var(--app-content-bg-color);
|
||||
|
||||
/* 手机顶部 */
|
||||
.editor-design-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: $phone-width;
|
||||
margin: 0 auto;
|
||||
|
||||
/* 手机顶部状态栏 */
|
||||
.status-bar {
|
||||
width: $phone-width;
|
||||
height: 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机底部导航 */
|
||||
.editor-design-bottom {
|
||||
width: $phone-width;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 手机页面编辑区域 */
|
||||
:deep(.editor-design-center) {
|
||||
width: 100%;
|
||||
@@ -613,21 +586,11 @@ $phone-width: 375px;
|
||||
|
||||
/* 固定布局的组件 操作按钮区 */
|
||||
.fixed-component-action-group {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:deep(.ant-tag) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px 0 rgb(0 0 0 / 10%);
|
||||
|
||||
.anticon {
|
||||
.ant-tag-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user