feat: 优化 diy editor 样式
This commit is contained in:
@@ -5,7 +5,7 @@ import { computed } from 'vue';
|
|||||||
|
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, Tooltip } from 'ant-design-vue';
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
import { VerticalButtonGroup } from '#/views/mall/promotion/components';
|
import { VerticalButtonGroup } from '#/views/mall/promotion/components';
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ const handleDeleteComponent = () => {
|
|||||||
<div :style="style">
|
<div :style="style">
|
||||||
<component :is="component.id" :property="component.property" />
|
<component :is="component.id" :property="component.property" />
|
||||||
</div>
|
</div>
|
||||||
<div class="component-wrap absolute left-[-2px] top-0 block h-full w-full">
|
<div class="component-wrap absolute -left-0.5 top-1 block h-full w-full">
|
||||||
<!-- 左侧:组件名(悬浮的小贴条) -->
|
<!-- 左侧:组件名(悬浮的小贴条) -->
|
||||||
<div class="component-name" v-if="component.name">
|
<div class="component-name" v-if="component.name">
|
||||||
{{ component.name }}
|
{{ component.name }}
|
||||||
@@ -107,32 +107,52 @@ const handleDeleteComponent = () => {
|
|||||||
v-if="showToolbar && component.name && active"
|
v-if="showToolbar && component.name && active"
|
||||||
>
|
>
|
||||||
<VerticalButtonGroup type="primary">
|
<VerticalButtonGroup type="primary">
|
||||||
<Tooltip title="上移" placement="right">
|
|
||||||
<Button
|
<Button
|
||||||
:disabled="!canMoveUp"
|
:disabled="!canMoveUp"
|
||||||
@click.stop="handleMoveComponent(-1)"
|
@click.stop="handleMoveComponent(-1)"
|
||||||
|
v-tippy="{
|
||||||
|
content: '上移',
|
||||||
|
delay: 100,
|
||||||
|
placement: 'right',
|
||||||
|
arrow: true,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<IconifyIcon icon="ep:arrow-up" />
|
<IconifyIcon icon="ep:arrow-up" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="下移" placement="right">
|
|
||||||
<Button
|
<Button
|
||||||
:disabled="!canMoveDown"
|
:disabled="!canMoveDown"
|
||||||
@click.stop="handleMoveComponent(1)"
|
@click.stop="handleMoveComponent(1)"
|
||||||
|
v-tippy="{
|
||||||
|
content: '下移',
|
||||||
|
delay: 100,
|
||||||
|
placement: 'right',
|
||||||
|
arrow: true,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<IconifyIcon icon="ep:arrow-down" />
|
<IconifyIcon icon="ep:arrow-down" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
<Button
|
||||||
<Tooltip title="复制" placement="right">
|
@click.stop="handleCopyComponent()"
|
||||||
<Button @click.stop="handleCopyComponent()">
|
v-tippy="{
|
||||||
|
content: '复制',
|
||||||
|
delay: 100,
|
||||||
|
placement: 'right',
|
||||||
|
arrow: true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<IconifyIcon icon="ep:copy-document" />
|
<IconifyIcon icon="ep:copy-document" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
<Button
|
||||||
<Tooltip title="删除" placement="right">
|
@click.stop="handleDeleteComponent()"
|
||||||
<Button @click.stop="handleDeleteComponent()">
|
v-tippy="{
|
||||||
|
content: '删除',
|
||||||
|
delay: 100,
|
||||||
|
placement: 'right',
|
||||||
|
arrow: true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<IconifyIcon icon="ep:delete" />
|
<IconifyIcon icon="ep:delete" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
|
||||||
</VerticalButtonGroup>
|
</VerticalButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,7 +169,7 @@ $toolbar-position: -55px;
|
|||||||
.component-wrap {
|
.component-wrap {
|
||||||
/* 鼠标放到组件上时 */
|
/* 鼠标放到组件上时 */
|
||||||
&:hover {
|
&:hover {
|
||||||
border: $hover-border-width dashed var(--ant-color-primary);
|
border: $hover-border-width dashed var(--primary);
|
||||||
box-shadow: 0 0 5px 0 rgb(24 144 255 / 30%);
|
box-shadow: 0 0 5px 0 rgb(24 144 255 / 30%);
|
||||||
|
|
||||||
.component-name {
|
.component-name {
|
||||||
@@ -170,9 +190,9 @@ $toolbar-position: -55px;
|
|||||||
height: 25px;
|
height: 25px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
color: #6a6a6a;
|
color: hsl(var(--text-color));
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: #fff;
|
background: hsl(var(--background));
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 0 4px #00000014,
|
0 0 4px #00000014,
|
||||||
0 2px 6px #0000000f,
|
0 2px 6px #0000000f,
|
||||||
@@ -187,7 +207,7 @@ $toolbar-position: -55px;
|
|||||||
height: 0;
|
height: 0;
|
||||||
content: ' ';
|
content: ' ';
|
||||||
border: 5px solid transparent;
|
border: 5px solid transparent;
|
||||||
border-left-color: #fff;
|
border-left-color: hsl(var(--background));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +227,7 @@ $toolbar-position: -55px;
|
|||||||
height: 0;
|
height: 0;
|
||||||
content: ' ';
|
content: ' ';
|
||||||
border: 5px solid transparent;
|
border: 5px solid transparent;
|
||||||
border-right-color: #2d8cf0;
|
border-right-color: hsl(var(--primary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,7 +238,7 @@ $toolbar-position: -55px;
|
|||||||
|
|
||||||
.component-wrap {
|
.component-wrap {
|
||||||
margin-bottom: $active-border-width + $active-border-width;
|
margin-bottom: $active-border-width + $active-border-width;
|
||||||
border: $active-border-width solid var(--ant-color-primary) !important;
|
border: $active-border-width solid hsl(var(--primary)) !important;
|
||||||
box-shadow: 0 0 10px 0 rgb(24 144 255 / 30%);
|
box-shadow: 0 0 10px 0 rgb(24 144 255 / 30%);
|
||||||
|
|
||||||
.component-name {
|
.component-name {
|
||||||
@@ -227,10 +247,10 @@ $toolbar-position: -55px;
|
|||||||
/* 防止加了边框之后,位置移动 */
|
/* 防止加了边框之后,位置移动 */
|
||||||
left: $name-position - $active-border-width !important;
|
left: $name-position - $active-border-width !important;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: var(--ant-color-primary);
|
background: hsl(var(--primary));
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
border-left-color: var(--ant-color-primary);
|
border-left-color: hsl(var(--primary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DiyComponent, DiyComponentLibrary } from '../util';
|
import type { DiyComponent, DiyComponentLibrary } from '../util';
|
||||||
|
|
||||||
import { reactive, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { cloneDeep } from '@vben/utils';
|
import { cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
import { Collapse, CollapsePanel } from 'ant-design-vue';
|
import { Collapse } from 'ant-design-vue';
|
||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
|
|
||||||
import { componentConfigs } from './mobile/index';
|
import { componentConfigs } from './mobile/index';
|
||||||
@@ -19,28 +19,28 @@ const props = defineProps<{
|
|||||||
list: DiyComponentLibrary[];
|
list: DiyComponentLibrary[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const groups = reactive<any[]>([]); // 组件分组
|
const groups = ref<any[]>([]); // 组件分组
|
||||||
const extendGroups = reactive<string[]>([]); // 展开的折叠面板
|
const extendGroups = ref<string[]>([]); // 展开的折叠面板
|
||||||
|
|
||||||
/** 监听 list 属性,按照 DiyComponentLibrary 的 name 分组 */
|
/** 监听 list 属性,按照 DiyComponentLibrary 的 name 分组 */
|
||||||
watch(
|
watch(
|
||||||
() => props.list,
|
() => props.list,
|
||||||
() => {
|
() => {
|
||||||
// 清除旧数据
|
// 清除旧数据
|
||||||
extendGroups.length = 0;
|
extendGroups.value = [];
|
||||||
groups.length = 0;
|
groups.value = [];
|
||||||
// 重新生成数据
|
// 重新生成数据
|
||||||
props.list.forEach((group) => {
|
props.list.forEach((group) => {
|
||||||
// 是否展开分组
|
// 是否展开分组
|
||||||
if (group.extended) {
|
if (group.extended) {
|
||||||
extendGroups.push(group.name);
|
extendGroups.value.push(group.name);
|
||||||
}
|
}
|
||||||
// 查找组件
|
// 查找组件
|
||||||
const components = group.components
|
const components = group.components
|
||||||
.map((name) => componentConfigs[name] as DiyComponent<any>)
|
.map((name) => componentConfigs[name] as DiyComponent<any>)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
if (components.length > 0) {
|
if (components.length > 0) {
|
||||||
groups.push({
|
groups.value.push({
|
||||||
name: group.name,
|
name: group.name,
|
||||||
components,
|
components,
|
||||||
});
|
});
|
||||||
@@ -53,28 +53,32 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/** 克隆组件 */
|
/** 克隆组件 */
|
||||||
const handleCloneComponent = (component: DiyComponent<any>) => {
|
function handleCloneComponent(component: DiyComponent<any>) {
|
||||||
const instance = cloneDeep(component);
|
const instance = cloneDeep(component);
|
||||||
instance.uid = Date.now();
|
instance.uid = Date.now();
|
||||||
return instance;
|
return instance;
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<aside
|
<div
|
||||||
class="editor-left z-[1] w-[261px] shrink-0 select-none shadow-[8px_0_8px_-8px_rgb(0_0_0/0.12)]"
|
class="z-[1] max-h-[calc(80vh)] w-96 shrink-0 select-none overflow-y-auto"
|
||||||
>
|
>
|
||||||
<div class="h-full overflow-y-auto">
|
<Collapse
|
||||||
<Collapse v-model:active-key="extendGroups">
|
v-model:active-key="extendGroups"
|
||||||
<CollapsePanel
|
:bordered="false"
|
||||||
v-for="group in groups"
|
class="bg-card shadow-none"
|
||||||
|
>
|
||||||
|
<Collapse.Panel
|
||||||
|
v-for="(group, index) in groups"
|
||||||
:key="group.name"
|
:key="group.name"
|
||||||
:header="group.name"
|
:header="group.name"
|
||||||
|
:force-render="true"
|
||||||
>
|
>
|
||||||
<draggable
|
<draggable
|
||||||
class="flex flex-wrap items-center"
|
class="flex flex-wrap items-center"
|
||||||
ghost-class="draggable-ghost"
|
ghost-class="draggable-ghost"
|
||||||
item-key="index"
|
:item-key="index.toString()"
|
||||||
:list="group.components"
|
:list="group.components"
|
||||||
:sort="false"
|
:sort="false"
|
||||||
:group="{ name: 'component', pull: 'clone', put: false }"
|
:group="{ name: 'component', pull: 'clone', put: false }"
|
||||||
@@ -83,107 +87,18 @@ const handleCloneComponent = (component: DiyComponent<any>) => {
|
|||||||
:force-fallback="false"
|
:force-fallback="false"
|
||||||
>
|
>
|
||||||
<template #item="{ element }">
|
<template #item="{ element }">
|
||||||
<div>
|
|
||||||
<div class="hidden text-white">组件放置区域</div>
|
|
||||||
<div
|
<div
|
||||||
class="component flex h-[86px] w-[86px] cursor-move flex-col items-center justify-center border-b border-r [&:nth-of-type(3n)]:border-r-0"
|
class="component flex h-20 w-20 cursor-move flex-col items-center justify-center hover:border-2 hover:border-blue-500"
|
||||||
:style="{
|
|
||||||
borderColor: 'var(--ant-color-split)',
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
:icon="element.icon"
|
:icon="element.icon"
|
||||||
:size="32"
|
class="mb-1 size-8 text-gray-500"
|
||||||
class="mb-1 text-gray-500"
|
|
||||||
/>
|
/>
|
||||||
<span class="mt-1 text-xs">{{ element.name }}</span>
|
<span class="mt-1 text-xs">{{ element.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
</CollapsePanel>
|
</Collapse.Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.editor-left {
|
|
||||||
:deep(.ant-collapse) {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-collapse-item) {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-collapse-content-box) {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-collapse-header) {
|
|
||||||
height: 32px;
|
|
||||||
padding: 0 24px !important;
|
|
||||||
line-height: 32px;
|
|
||||||
background-color: var(--ant-color-bg-layout);
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 组件 hover 和 active 状态(需要 CSS 变量) */
|
|
||||||
.component.active,
|
|
||||||
.component:hover {
|
|
||||||
color: var(--ant-color-white);
|
|
||||||
background: var(--ant-color-primary);
|
|
||||||
|
|
||||||
:deep(.iconify) {
|
|
||||||
color: var(--ant-color-white);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 拖拽区域全局样式 */
|
|
||||||
.drag-area {
|
|
||||||
/* 拖拽到手机区域时的样式 */
|
|
||||||
.draggable-ghost {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
|
|
||||||
/* 条纹背景 */
|
|
||||||
background: linear-gradient(
|
|
||||||
45deg,
|
|
||||||
#91a8d5 0,
|
|
||||||
#91a8d5 10%,
|
|
||||||
#94b4eb 10%,
|
|
||||||
#94b4eb 50%,
|
|
||||||
#91a8d5 50%,
|
|
||||||
#91a8d5 60%,
|
|
||||||
#94b4eb 60%,
|
|
||||||
#94b4eb
|
|
||||||
);
|
|
||||||
background-size: 1rem 1rem;
|
|
||||||
transition: all 0.5s;
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: inline-block;
|
|
||||||
width: 140px;
|
|
||||||
height: 25px;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 25px;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
background: #5487df;
|
|
||||||
}
|
|
||||||
|
|
||||||
.component {
|
|
||||||
display: none; /* 拖拽时隐藏组件 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: block !important; /* 拖拽时显示占位提示 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { ref } from 'vue';
|
|||||||
|
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Carousel, Image } from 'ant-design-vue';
|
||||||
|
|
||||||
/** 轮播图 */
|
/** 轮播图 */
|
||||||
defineOptions({ name: 'Carousel' });
|
defineOptions({ name: 'Carousel' });
|
||||||
|
|
||||||
@@ -16,12 +18,13 @@ const handleIndexChange = (index: number) => {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<!-- 无图片 -->
|
<!-- 无图片 -->
|
||||||
<div
|
<div
|
||||||
class="flex h-[250px] items-center justify-center bg-gray-300"
|
class="bg-card flex h-64 items-center justify-center"
|
||||||
v-if="property.items.length === 0"
|
v-if="property.items.length === 0"
|
||||||
>
|
>
|
||||||
<IconifyIcon icon="tdesign:image" class="text-[120px] text-gray-800" />
|
<IconifyIcon icon="tdesign:image" class="text-3xl text-gray-800" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="relative">
|
<div v-else class="relative">
|
||||||
<Carousel
|
<Carousel
|
||||||
@@ -29,7 +32,7 @@ const handleIndexChange = (index: number) => {
|
|||||||
:autoplay-speed="property.interval * 1000"
|
:autoplay-speed="property.interval * 1000"
|
||||||
:dots="property.indicator !== 'number'"
|
:dots="property.indicator !== 'number'"
|
||||||
@change="handleIndexChange"
|
@change="handleIndexChange"
|
||||||
class="h-[174px]"
|
class="h-44"
|
||||||
>
|
>
|
||||||
<div v-for="(item, index) in property.items" :key="index">
|
<div v-for="(item, index) in property.items" :key="index">
|
||||||
<Image
|
<Image
|
||||||
@@ -41,11 +44,10 @@ const handleIndexChange = (index: number) => {
|
|||||||
</Carousel>
|
</Carousel>
|
||||||
<div
|
<div
|
||||||
v-if="property.indicator === 'number'"
|
v-if="property.indicator === 'number'"
|
||||||
class="absolute bottom-[10px] right-[10px] rounded-xl bg-black px-[8px] py-[2px] text-[10px] text-white opacity-40"
|
class="absolute bottom-2.5 right-2.5 rounded-xl bg-black px-2 py-1 text-xs text-white opacity-40"
|
||||||
>
|
>
|
||||||
{{ currentIndex }} / {{ property.items.length }}
|
{{ currentIndex }} / {{ property.items.length }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
<RadioGroup v-model="formData.type">
|
<RadioGroup v-model="formData.type">
|
||||||
<Tooltip class="item" content="默认" placement="bottom">
|
<Tooltip class="item" content="默认" placement="bottom">
|
||||||
<RadioButton value="default">
|
<RadioButton value="default">
|
||||||
<IconifyIcon icon="system-uicons:carousel" />
|
<IconifyIcon icon="system-uicons:carousel" class="size-6" />
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip class="item" content="卡片" placement="bottom">
|
<Tooltip class="item" content="卡片" placement="bottom">
|
||||||
<RadioButton value="card">
|
<RadioButton value="card">
|
||||||
<IconifyIcon icon="ic:round-view-carousel" />
|
<IconifyIcon icon="ic:round-view-carousel" class="size-6" />
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
@@ -90,7 +90,7 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
draggable="false"
|
draggable="false"
|
||||||
height="80px"
|
height="80px"
|
||||||
width="100%"
|
width="100%"
|
||||||
class="min-w-[80px]"
|
class="min-w-20"
|
||||||
:show-description="false"
|
:show-description="false"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -102,7 +102,7 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
:show-description="false"
|
:show-description="false"
|
||||||
height="80px"
|
height="80px"
|
||||||
width="100%"
|
width="100%"
|
||||||
class="min-w-[80px]"
|
class="min-w-20"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="视频" class="mb-2" label-width="40px">
|
<FormItem label="视频" class="mb-2" label-width="40px">
|
||||||
@@ -111,7 +111,7 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
:file-type="['mp4']"
|
:file-type="['mp4']"
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:file-size="100"
|
:file-size="100"
|
||||||
class="min-w-[80px]"
|
class="min-w-20"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</template>
|
</template>
|
||||||
@@ -124,5 +124,3 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
</Form>
|
</Form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -155,4 +155,3 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const handleActive = (index: number) => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-8 right-[calc(50%-375px/2+32px)] z-20 flex items-center gap-3"
|
class="absolute bottom-8 right-[calc(50%-384px/2+32px)] z-20 flex items-center gap-3"
|
||||||
:class="[
|
:class="[
|
||||||
{
|
{
|
||||||
'flex-row': property.direction === 'horizontal',
|
'flex-row': property.direction === 'horizontal',
|
||||||
@@ -74,9 +74,9 @@ const handleActive = (index: number) => {
|
|||||||
.modal-bg {
|
.modal-bg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: calc(50% - 375px / 2);
|
left: calc(50% - 384px / 2);
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
width: 375px;
|
width: 384px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgb(0 0 0 / 40%);
|
background-color: rgb(0 0 0 / 40%);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MenuGridProperty } from './config';
|
import type { MenuGridProperty } from './config';
|
||||||
|
|
||||||
|
import { Image } from 'ant-design-vue';
|
||||||
|
|
||||||
/** 宫格导航 */
|
/** 宫格导航 */
|
||||||
defineOptions({ name: 'MenuGrid' });
|
defineOptions({ name: 'MenuGrid' });
|
||||||
defineProps<{ property: MenuGridProperty }>();
|
defineProps<{ property: MenuGridProperty }>();
|
||||||
@@ -11,13 +13,13 @@ defineProps<{ property: MenuGridProperty }>();
|
|||||||
<div
|
<div
|
||||||
v-for="(item, index) in property.list"
|
v-for="(item, index) in property.list"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="relative flex flex-col items-center pb-3.5 pt-5"
|
class="relative flex flex-col items-center pb-4 pt-4"
|
||||||
:style="{ width: `${100 * (1 / property.column)}%` }"
|
:style="{ width: `${100 * (1 / property.column)}%` }"
|
||||||
>
|
>
|
||||||
<!-- 右上角角标 -->
|
<!-- 右上角角标 -->
|
||||||
<span
|
<span
|
||||||
v-if="item.badge?.show"
|
v-if="item.badge?.show"
|
||||||
class="absolute left-1/2 top-2.5 z-10 h-5 rounded-full px-1.5 text-center text-xs leading-5"
|
class="absolute left-1/2 top-2 z-10 h-4 rounded-full px-2 text-center text-xs leading-5"
|
||||||
:style="{
|
:style="{
|
||||||
color: item.badge.textColor,
|
color: item.badge.textColor,
|
||||||
backgroundColor: item.badge.bgColor,
|
backgroundColor: item.badge.bgColor,
|
||||||
@@ -27,7 +29,7 @@ defineProps<{ property: MenuGridProperty }>();
|
|||||||
</span>
|
</span>
|
||||||
<Image
|
<Image
|
||||||
v-if="item.iconUrl"
|
v-if="item.iconUrl"
|
||||||
class="h-7 w-7"
|
:width="32"
|
||||||
:src="item.iconUrl"
|
:src="item.iconUrl"
|
||||||
:preview="false"
|
:preview="false"
|
||||||
/>
|
/>
|
||||||
@@ -46,5 +48,3 @@ defineProps<{ property: MenuGridProperty }>();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ import {
|
|||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
import UploadImg from '#/components/upload/image-upload.vue';
|
import UploadImg from '#/components/upload/image-upload.vue';
|
||||||
import { AppLinkInput, Draggable } from '#/views/mall/promotion/components';
|
import {
|
||||||
|
AppLinkInput,
|
||||||
|
ColorInput,
|
||||||
|
Draggable,
|
||||||
|
} from '#/views/mall/promotion/components';
|
||||||
|
|
||||||
import ComponentContainerProperty from '../../component-container-property.vue';
|
import ComponentContainerProperty from '../../component-container-property.vue';
|
||||||
import { EMPTY_MENU_GRID_ITEM_PROPERTY } from './config';
|
import { EMPTY_MENU_GRID_ITEM_PROPERTY } from './config';
|
||||||
@@ -53,13 +57,13 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
</UploadImg>
|
</UploadImg>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="标题" prop="title">
|
<FormItem label="标题" prop="title">
|
||||||
<InputWithColor
|
<ColorInput
|
||||||
v-model="element.title"
|
v-model="element.title"
|
||||||
v-model:color="element.titleColor"
|
v-model:color="element.titleColor"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="副标题" prop="subtitle">
|
<FormItem label="副标题" prop="subtitle">
|
||||||
<InputWithColor
|
<ColorInput
|
||||||
v-model="element.subtitle"
|
v-model="element.subtitle"
|
||||||
v-model:color="element.subtitleColor"
|
v-model:color="element.subtitleColor"
|
||||||
/>
|
/>
|
||||||
@@ -72,7 +76,7 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
<template v-if="element.badge.show">
|
<template v-if="element.badge.show">
|
||||||
<FormItem label="角标内容" prop="badge.text">
|
<FormItem label="角标内容" prop="badge.text">
|
||||||
<InputWithColor
|
<ColorInput
|
||||||
v-model="element.badge.text"
|
v-model="element.badge.text"
|
||||||
v-model:color="element.badge.textColor"
|
v-model:color="element.badge.textColor"
|
||||||
/>
|
/>
|
||||||
@@ -87,5 +91,3 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
</Form>
|
</Form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { MenuSwiperItemProperty, MenuSwiperProperty } from './config';
|
|||||||
|
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Image } from 'ant-design-vue';
|
||||||
|
|
||||||
/** 菜单导航 */
|
/** 菜单导航 */
|
||||||
defineOptions({ name: 'MenuSwiper' });
|
defineOptions({ name: 'MenuSwiper' });
|
||||||
const props = defineProps<{ property: MenuSwiperProperty }>();
|
const props = defineProps<{ property: MenuSwiperProperty }>();
|
||||||
@@ -122,14 +124,14 @@ watch(
|
|||||||
button {
|
button {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
background: #ff6000;
|
background: hsl(var(--red));
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-carousel-dot-active button {
|
.ant-carousel-dot-active button {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
background: #ff6000;
|
background: hsl(var(--red));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import type { DiyComponent, DiyComponentLibrary, PageConfig } from './util';
|
|||||||
|
|
||||||
import { onMounted, ref, unref, watch } from 'vue';
|
import { onMounted, ref, unref, watch } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { cloneDeep, isEmpty, isString } from '@vben/utils';
|
import { cloneDeep, isEmpty, isString } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, Card, QRCode, Tag, Tooltip } from 'ant-design-vue';
|
import { Button, Card, Col, QRCode, Row, Tag, Tooltip } from 'ant-design-vue';
|
||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
|
|
||||||
import statusBarImg from '#/assets/imgs/diy/statusBar.png';
|
import statusBarImg from '#/assets/imgs/diy/statusBar.png';
|
||||||
@@ -287,54 +287,62 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<Page auto-content-height>
|
||||||
<div class="editor flex h-full flex-col">
|
|
||||||
<!-- 顶部:工具栏 -->
|
<!-- 顶部:工具栏 -->
|
||||||
<div class="editor-header flex items-center">
|
<Row class="bg-card flex max-h-12 rounded-lg">
|
||||||
<!-- 左侧操作区 -->
|
<!-- 左侧操作区 -->
|
||||||
|
<Col :span="8">
|
||||||
<slot name="toolBarLeft"></slot>
|
<slot name="toolBarLeft"></slot>
|
||||||
|
</Col>
|
||||||
<!-- 中心操作区 -->
|
<!-- 中心操作区 -->
|
||||||
<div class="header-center flex flex-1 items-center justify-center">
|
<Col :span="8">
|
||||||
<span>{{ title }}</span>
|
<span class="flex h-full items-center justify-center">{{ title }}</span>
|
||||||
</div>
|
</Col>
|
||||||
<!-- 右侧操作区 -->
|
<!-- 右侧操作区 -->
|
||||||
<div class="header-right flex">
|
<Col :span="8">
|
||||||
|
<Button.Group
|
||||||
|
direction="vertical"
|
||||||
|
size="large"
|
||||||
|
class="flex justify-end"
|
||||||
|
>
|
||||||
<Tooltip title="重置">
|
<Tooltip title="重置">
|
||||||
<Button @click="handleReset">
|
<Button @click="handleReset">
|
||||||
<IconifyIcon :size="24" icon="system-uicons:reset-alt" />
|
<IconifyIcon class="size-6" icon="system-uicons:reset-alt" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip v-if="previewUrl" title="预览">
|
<Tooltip v-if="previewUrl" title="预览">
|
||||||
<Button @click="handlePreview">
|
<Button @click="handlePreview">
|
||||||
<IconifyIcon :size="24" icon="ep:view" />
|
<IconifyIcon class="size-6" icon="ep:view" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="保存">
|
<Tooltip title="保存">
|
||||||
<Button @click="handleSave">
|
<Button @click="handleSave">
|
||||||
<IconifyIcon :size="24" icon="ep:check" />
|
<IconifyIcon class="size-6" icon="ep:check" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</Button.Group>
|
||||||
</div>
|
</Col>
|
||||||
|
</Row>
|
||||||
<!-- 中心区域 -->
|
<!-- 中心区域 -->
|
||||||
<div class="editor-container h-[calc(100vh-135px)]">
|
<Row class="mt-4 h-[calc(80vh)]">
|
||||||
<!-- 左侧:组件库(ComponentLibrary) -->
|
<!-- 左侧:组件库(ComponentLibrary) -->
|
||||||
|
<Col :span="6">
|
||||||
<ComponentLibrary
|
<ComponentLibrary
|
||||||
v-if="libs && libs.length > 0"
|
v-if="libs && libs.length > 0"
|
||||||
ref="componentLibrary"
|
ref="componentLibrary"
|
||||||
:list="libs"
|
:list="libs"
|
||||||
/>
|
/>
|
||||||
|
</Col>
|
||||||
<!-- 中心:设计区域(ComponentContainer) -->
|
<!-- 中心:设计区域(ComponentContainer) -->
|
||||||
|
<Col :span="12">
|
||||||
<div
|
<div
|
||||||
class="editor-center page-prop-area relative mt-4 flex w-full flex-1 flex-col justify-center overflow-hidden"
|
class="relative flex max-h-[calc(80vh)] w-full flex-1 flex-col justify-center overflow-y-auto"
|
||||||
:style="{ backgroundColor: 'var(--app-content-bg-color)' }"
|
|
||||||
@click="handlePageSelected"
|
@click="handlePageSelected"
|
||||||
>
|
>
|
||||||
<!-- 手机顶部 -->
|
<!-- 手机顶部 -->
|
||||||
<div class="editor-design-top mx-auto flex w-[375px] flex-col">
|
<div class="mx-auto flex w-96 flex-col">
|
||||||
<!-- 手机顶部状态栏 -->
|
<!-- 手机顶部状态栏 -->
|
||||||
<img alt="" class="h-5 w-[375px] bg-white" :src="statusBarImg" />
|
<img alt="" class="bg-card h-6" :src="statusBarImg" />
|
||||||
<!-- 手机顶部导航栏 -->
|
<!-- 手机顶部导航栏 -->
|
||||||
<ComponentContainer
|
<ComponentContainer
|
||||||
v-if="showNavigationBar"
|
v-if="showNavigationBar"
|
||||||
@@ -362,18 +370,20 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<!-- 手机页面编辑区域 -->
|
<!-- 手机页面编辑区域 -->
|
||||||
<div
|
<div
|
||||||
class="editor-design-center page-prop-area h-full w-full overflow-y-auto"
|
class="min-h-full w-full"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundColor: pageConfigComponent.property.backgroundColor,
|
// backgroundColor: pageConfigComponent.property.backgroundColor,
|
||||||
backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`,
|
backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="phone-container">
|
<div
|
||||||
|
class="bg-size-[auto_auto] relative mx-auto my-0 min-h-full w-96 items-center justify-center bg-no-repeat"
|
||||||
|
>
|
||||||
<draggable
|
<draggable
|
||||||
v-model="pageComponents"
|
v-model="pageComponents"
|
||||||
:animation="200"
|
:animation="200"
|
||||||
:force-fallback="false"
|
:force-fallback="false"
|
||||||
class="page-prop-area drag-area"
|
class="min-h-full w-full"
|
||||||
filter=".component-toolbar"
|
filter=".component-toolbar"
|
||||||
ghost-class="draggable-ghost"
|
ghost-class="draggable-ghost"
|
||||||
group="component"
|
group="component"
|
||||||
@@ -402,7 +412,7 @@ onMounted(() => {
|
|||||||
<!-- 手机底部导航 -->
|
<!-- 手机底部导航 -->
|
||||||
<div
|
<div
|
||||||
v-if="showTabBar"
|
v-if="showTabBar"
|
||||||
class="editor-design-bottom component mx-auto w-[375px] cursor-pointer"
|
class="bottom-2 mx-auto mb-2 w-96 cursor-pointer"
|
||||||
>
|
>
|
||||||
<ComponentContainer
|
<ComponentContainer
|
||||||
:active="selectedComponent?.id === tabBarComponent.id"
|
:active="selectedComponent?.id === tabBarComponent.id"
|
||||||
@@ -412,9 +422,7 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- 固定布局的组件 操作按钮区 -->
|
<!-- 固定布局的组件 操作按钮区 -->
|
||||||
<div
|
<div class="absolute right-4 top-0 flex flex-col gap-2">
|
||||||
class="fixed-component-action-group absolute right-4 top-0 flex flex-col gap-2"
|
|
||||||
>
|
|
||||||
<Tag
|
<Tag
|
||||||
v-if="showPageConfig"
|
v-if="showPageConfig"
|
||||||
:color="
|
:color="
|
||||||
@@ -426,7 +434,10 @@ onMounted(() => {
|
|||||||
size="large"
|
size="large"
|
||||||
@click="handleComponentSelected(pageConfigComponent)"
|
@click="handleComponentSelected(pageConfigComponent)"
|
||||||
>
|
>
|
||||||
<IconifyIcon :icon="pageConfigComponent.icon" :size="12" />
|
<IconifyIcon
|
||||||
|
:icon="pageConfigComponent.icon"
|
||||||
|
class="mr-2 size-4"
|
||||||
|
/>
|
||||||
<span>{{ pageConfigComponent.name }}</span>
|
<span>{{ pageConfigComponent.name }}</span>
|
||||||
</Tag>
|
</Tag>
|
||||||
<template v-for="(component, index) in pageComponents" :key="index">
|
<template v-for="(component, index) in pageComponents" :key="index">
|
||||||
@@ -441,29 +452,28 @@ onMounted(() => {
|
|||||||
@click="handleComponentSelected(component)"
|
@click="handleComponentSelected(component)"
|
||||||
@close="handleDeleteComponent(index)"
|
@close="handleDeleteComponent(index)"
|
||||||
>
|
>
|
||||||
<IconifyIcon :icon="component.icon" :size="12" />
|
<IconifyIcon :icon="component.icon" class="size-4" />
|
||||||
<span>{{ component.name }}</span>
|
<span>{{ component.name }}</span>
|
||||||
</Tag>
|
</Tag>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Col>
|
||||||
<!-- 右侧:属性面板(ComponentContainerProperty) -->
|
<!-- 右侧:属性面板(ComponentContainerProperty) -->
|
||||||
<aside
|
<Col :span="6" v-if="selectedComponent?.property">
|
||||||
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
|
<Card
|
||||||
class="h-full"
|
class="h-[calc(80vh)] px-2 py-4"
|
||||||
:body-style="{ padding: 0, height: 'calc(100% - 57px)' }"
|
:body-style="{ padding: 0 }"
|
||||||
|
:head-style="{ padding: 0, minHeight: '40px' }"
|
||||||
>
|
>
|
||||||
<!-- 组件名称 -->
|
<!-- 组件名称 -->
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex h-8 items-center gap-1">
|
||||||
<IconifyIcon :icon="selectedComponent?.icon" color="gray" />
|
<IconifyIcon :icon="selectedComponent?.icon" color="gray" />
|
||||||
<span>{{ selectedComponent?.name }}</span>
|
<span>{{ selectedComponent?.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="property h-full overflow-y-auto p-4">
|
<div class="property max-h-[calc(80vh-100px)] overflow-y-auto p-4">
|
||||||
<component
|
<component
|
||||||
:is="`${selectedComponent?.id}Property`"
|
:is="`${selectedComponent?.id}Property`"
|
||||||
:key="selectedComponent?.uid || selectedComponent?.id"
|
:key="selectedComponent?.uid || selectedComponent?.id"
|
||||||
@@ -471,16 +481,15 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</aside>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 预览弹框 -->
|
<!-- 预览弹框 -->
|
||||||
<PreviewModal title="预览" class="w-[700px]">
|
<PreviewModal title="预览" class="w-[700px]">
|
||||||
<div class="flex justify-around">
|
<div class="flex justify-around">
|
||||||
<iframe
|
<iframe
|
||||||
:src="previewUrl"
|
:src="previewUrl"
|
||||||
class="h-[667px] w-[375px] rounded-lg border-4 border-solid p-0.5"
|
class="h-[667px] w-96 rounded-lg border-4 border-solid p-0.5"
|
||||||
></iframe>
|
></iframe>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="text-base">手机扫码预览</div>
|
<div class="text-base">手机扫码预览</div>
|
||||||
@@ -488,112 +497,5 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PreviewModal>
|
</PreviewModal>
|
||||||
</div>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
|
||||||
/* 手机宽度 */
|
|
||||||
$phone-width: 375px;
|
|
||||||
|
|
||||||
/* 根节点样式 */
|
|
||||||
.editor {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
/* 顶部:工具栏 */
|
|
||||||
.editor-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 42px;
|
|
||||||
padding: 0;
|
|
||||||
background-color: var(--ant-color-bg-container);
|
|
||||||
border-bottom: solid 1px var(--ant-color-border);
|
|
||||||
|
|
||||||
/* 工具栏:右侧按钮 */
|
|
||||||
.header-right {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.ant-btn {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 隐藏工具栏按钮的边框 */
|
|
||||||
:deep(.ant-radio-button-wrapper),
|
|
||||||
:deep(.ant-btn) {
|
|
||||||
border-top: none !important;
|
|
||||||
border-bottom: none !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 中心操作区 */
|
|
||||||
.editor-container {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
/* 右侧属性面板 */
|
|
||||||
:deep(.editor-right) {
|
|
||||||
/* 属性面板顶部:减少内边距 */
|
|
||||||
:deep(.ant-card-head) {
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 属性面板分组 */
|
|
||||||
:deep(.property-group) {
|
|
||||||
margin: 0 -20px;
|
|
||||||
|
|
||||||
&.ant-card {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 属性分组名称 */
|
|
||||||
.ant-card-head {
|
|
||||||
padding: 8px 32px;
|
|
||||||
background: var(--ant-color-bg-layout);
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 中心区域 */
|
|
||||||
.editor-center {
|
|
||||||
/* 手机页面编辑区域 */
|
|
||||||
:deep(.editor-design-center) {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
/* 主体内容 */
|
|
||||||
.phone-container {
|
|
||||||
position: relative;
|
|
||||||
width: $phone-width;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
|
|
||||||
.drag-area {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 固定布局的组件 操作按钮区 */
|
|
||||||
.fixed-component-action-group {
|
|
||||||
:deep(.ant-tag) {
|
|
||||||
border: none;
|
|
||||||
box-shadow: 0 2px 8px 0 rgb(0 0 0 / 10%);
|
|
||||||
|
|
||||||
.ant-tag-icon {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { message, Radio, RadioGroup, Tooltip } from 'ant-design-vue';
|
import { message, Radio, RadioGroup } from 'ant-design-vue';
|
||||||
|
|
||||||
import { updateDiyPageProperty } from '#/api/mall/promotion/diy/page';
|
import { updateDiyPageProperty } from '#/api/mall/promotion/diy/page';
|
||||||
import {
|
import {
|
||||||
@@ -191,18 +191,18 @@ onMounted(async () => {
|
|||||||
<template #toolBarLeft>
|
<template #toolBarLeft>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
:value="selectedTemplateItem"
|
:value="selectedTemplateItem"
|
||||||
class="h-full!"
|
class="flex items-center"
|
||||||
|
size="large"
|
||||||
@change="handleTemplateItemChange"
|
@change="handleTemplateItemChange"
|
||||||
>
|
>
|
||||||
<Tooltip
|
<template v-for="(item, index) in templateItems" :key="index">
|
||||||
v-for="(item, index) in templateItems"
|
|
||||||
:key="index"
|
|
||||||
:title="item.name"
|
|
||||||
>
|
|
||||||
<Radio.Button :value="index">
|
<Radio.Button :value="index">
|
||||||
<IconifyIcon :icon="item.icon" :size="24" />
|
<IconifyIcon
|
||||||
|
:icon="item.icon"
|
||||||
|
class="mt-2 flex size-6 items-center"
|
||||||
|
/>
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
</Tooltip>
|
</template>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</template>
|
</template>
|
||||||
</DiyEditor>
|
</DiyEditor>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { MallPointActivityApi } from '#/api/mall/promotion/point';
|
import type { MallPointActivityApi } from '#/api/mall/promotion/point';
|
||||||
import type { RuleConfig } from '#/views/mall/product/spu/form';
|
import type { RuleConfig } from '#/views/mall/product/spu/form';
|
||||||
import type { SpuProperty } from '#/views/mall/promotion/components/types';
|
// TODO @puhui999:有问题
|
||||||
|
// import type { SpuProperty } from '#/views/mall/promotion/components/types';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
@@ -70,7 +71,9 @@ const ruleConfig: RuleConfig[] = [
|
|||||||
]; // SKU 规则配置
|
]; // SKU 规则配置
|
||||||
|
|
||||||
const spuList = ref<any[]>([]); // 选择的 SPU 列表
|
const spuList = ref<any[]>([]); // 选择的 SPU 列表
|
||||||
const spuPropertyList = ref<SpuProperty<any>[]>([]); // SPU 属性列表
|
// TODO @puhui999:有问题
|
||||||
|
// const spuPropertyList = ref<SpuProperty<any>[]>([]); // SPU 属性列表
|
||||||
|
const spuPropertyList = ref<any[]>([]); // SPU 属性列表
|
||||||
|
|
||||||
/** 打开商品选择器 */
|
/** 打开商品选择器 */
|
||||||
// TODO @puhui999:spuSkuSelectRef.value.open is not a function
|
// TODO @puhui999:spuSkuSelectRef.value.open is not a function
|
||||||
@@ -123,7 +126,9 @@ async function getSpuDetails(
|
|||||||
});
|
});
|
||||||
res.skus = selectSkus;
|
res.skus = selectSkus;
|
||||||
|
|
||||||
const spuProperties: SpuProperty[] = [];
|
// TODO @puhui999:有问题
|
||||||
|
// const spuProperties: SpuProperty[] = [];
|
||||||
|
const spuProperties: any[] = [];
|
||||||
spuProperties.push({
|
spuProperties.push({
|
||||||
spuId: res.id!,
|
spuId: res.id!,
|
||||||
spuDetail: res,
|
spuDetail: res,
|
||||||
|
|||||||
Reference in New Issue
Block a user