feat:【antd】【mall】diy-editor 的整体继续迁移

This commit is contained in:
YunaiV
2025-11-04 00:04:07 +08:00
parent fde4b7852c
commit f32bce424b
13 changed files with 1606 additions and 27 deletions

View File

@@ -1,4 +1,100 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { DividerProperty } from './config';
import { IconifyIcon } from '@vben/icons';
import { useVModel } from '@vueuse/core';
import {
Form,
FormItem,
RadioButton,
RadioGroup,
Slider,
Tooltip,
} from 'ant-design-vue';
import { ColorInput } from '#/views/mall/promotion/components';
/** 导航栏属性面板 */
defineOptions({ name: 'DividerProperty' });
const props = defineProps<{ modelValue: DividerProperty }>();
const emit = defineEmits(['update:modelValue']);
const BORDER_TYPES = [
{
icon: 'vaadin:line-h',
text: '实线',
type: 'solid',
},
{
icon: 'tabler:line-dashed',
text: '虚线',
type: 'dashed',
},
{
icon: 'tabler:line-dotted',
text: '点线',
type: 'dotted',
},
{
icon: 'entypo:progress-empty',
text: '无',
type: 'none',
},
]; // 线类型
const formData = useVModel(props, 'modelValue', emit);
</script>
<template><Page>待完成</Page></template>
<template>
<Form :model="formData">
<FormItem label="高度" name="height">
<Slider
v-model:value="formData.height"
:min="1"
:max="100"
/>
</FormItem>
<FormItem label="选择样式" name="borderType">
<RadioGroup v-model:value="formData!.borderType">
<Tooltip
placement="top"
v-for="(item, index) in BORDER_TYPES"
:key="index"
:title="item.text"
>
<RadioButton :value="item.type">
<IconifyIcon :icon="item.icon" />
</RadioButton>
</Tooltip>
</RadioGroup>
</FormItem>
<template v-if="formData.borderType !== 'none'">
<FormItem label="线宽" name="lineWidth">
<Slider
v-model:value="formData.lineWidth"
:min="1"
:max="30"
/>
</FormItem>
<FormItem label="左右边距" name="paddingType">
<RadioGroup v-model:value="formData!.paddingType">
<Tooltip title="无边距" placement="top">
<RadioButton value="none">
<IconifyIcon icon="tabler:box-padding" />
</RadioButton>
</Tooltip>
<Tooltip title="左右留边" placement="top">
<RadioButton value="horizontal">
<IconifyIcon icon="vaadin:padding" />
</RadioButton>
</Tooltip>
</RadioGroup>
</FormItem>
<FormItem label="颜色">
<ColorInput v-model="formData.lineColor" />
</FormItem>
</template>
</Form>
</template>

View File

@@ -1,4 +1,57 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { PopoverProperty } from './config';
import { useVModel } from '@vueuse/core';
import {
Form,
FormItem,
Radio,
RadioGroup,
Tooltip,
} from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import { AppLinkInput, Draggable } from '#/views/mall/promotion/components';
/** 弹窗广告属性面板 */
defineOptions({ name: 'PopoverProperty' });
const props = defineProps<{ modelValue: PopoverProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>
<template><Page>待完成</Page></template>
<template>
<Form :model="formData">
<Draggable v-model="formData.list" :empty-item="{ showType: 'once' }">
<template #default="{ element, index }">
<FormItem label="图片" :name="`list[${index}].imgUrl`">
<UploadImg
v-model="element.imgUrl"
height="56px"
width="56px"
:show-description="false"
/>
</FormItem>
<FormItem label="跳转链接" :name="`list[${index}].url`">
<AppLinkInput v-model="element.url" />
</FormItem>
<FormItem label="显示次数" :name="`list[${index}].showType`">
<RadioGroup v-model:value="element.showType">
<Tooltip
title="只显示一次,下次打开时不显示"
placement="bottom"
>
<Radio value="once">一次</Radio>
</Tooltip>
<Tooltip title="每次打开时都会显示" placement="bottom">
<Radio value="always">不限</Radio>
</Tooltip>
</RadioGroup>
</FormItem>
</template>
</Draggable>
</Form>
</template>

View File

@@ -1,4 +1,176 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { CouponCardProperty } from './config';
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import {
CouponTemplateTakeTypeEnum,
PromotionDiscountTypeEnum,
} from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
import { floatToFixed2 } from '@vben/utils';
import { useVModel } from '@vueuse/core';
import {
Button,
Card,
Form,
FormItem,
RadioButton,
RadioGroup,
Slider,
Tooltip,
Typography,
} from 'ant-design-vue';
import * as CouponTemplateApi from '#/api/mall/promotion/coupon/couponTemplate';
import UploadImg from '#/components/upload/image-upload.vue';
import { ColorInput } from '#/views/mall/promotion/components';
import CouponSelect from '#/views/mall/promotion/coupon/components/select.vue';
import ComponentContainerProperty from '../../component-container-property.vue';
const { Text: ATypographyText } = Typography;
/** 优惠券卡片属性面板 */
defineOptions({ name: 'CouponCardProperty' });
const props = defineProps<{ modelValue: CouponCardProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
const couponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]); // 已选择的优惠券列表
const [CouponSelectModal, couponSelectModalApi] = useVbenModal({
connectedComponent: CouponSelect,
destroyOnClose: true,
});
/** 添加优惠劵 */
const handleAddCoupon = () => {
couponSelectModalApi.open();
};
/** 处理优惠劵选择 */
const handleCouponSelect = (
selectedCoupons: MallCouponTemplateApi.CouponTemplate[],
) => {
couponList.value = selectedCoupons;
formData.value.couponIds = selectedCoupons.map((coupon) => coupon.id);
};
/** 监听优惠券 ID 变化,加载优惠券列表 */
watch(
() => formData.value.couponIds,
async () => {
if (formData.value.couponIds?.length > 0) {
couponList.value = await CouponTemplateApi.getCouponTemplateList(
formData.value.couponIds,
);
}
},
{
immediate: true,
deep: true,
},
);
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<Form :model="formData">
<Card title="优惠券列表" class="property-group">
<div
v-for="(coupon, index) in couponList"
:key="index"
class="flex items-center justify-between"
>
<ATypographyText ellipsis class="text-base">{{ coupon.name }}</ATypographyText>
<ATypographyText type="secondary" ellipsis>
<span v-if="coupon.usePrice > 0">
{{ floatToFixed2(coupon.usePrice) }}
</span>
<span
v-if="
coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
"
>
减{{ floatToFixed2(coupon.discountPrice) }}元
</span>
<span v-else> 打{{ coupon.discountPercent }}折 </span>
</ATypographyText>
</div>
<FormItem>
<Button
@click="handleAddCoupon"
type="primary"
ghost
class="mt-2 w-full"
>
<template #icon>
<IconifyIcon icon="ep:plus" />
</template>
添加
</Button>
</FormItem>
</Card>
<Card title="优惠券样式" class="property-group">
<FormItem label="列数" name="type">
<RadioGroup v-model:value="formData.columns">
<Tooltip title="一列" placement="bottom">
<RadioButton :value="1">
<IconifyIcon icon="fluent:text-column-one-24-filled" />
</RadioButton>
</Tooltip>
<Tooltip title="二列" placement="bottom">
<RadioButton :value="2">
<IconifyIcon icon="fluent:text-column-two-24-filled" />
</RadioButton>
</Tooltip>
<Tooltip title="三列" placement="bottom">
<RadioButton :value="3">
<IconifyIcon icon="fluent:text-column-three-24-filled" />
</RadioButton>
</Tooltip>
</RadioGroup>
</FormItem>
<FormItem label="背景图片" name="bgImg">
<UploadImg
v-model="formData.bgImg"
height="80px"
width="100%"
class="min-w-[160px]"
:show-description="false"
/>
</FormItem>
<FormItem label="文字颜色" name="textColor">
<ColorInput v-model="formData.textColor" />
</FormItem>
<FormItem label="按钮背景" name="button.bgColor">
<ColorInput v-model="formData.button.bgColor" />
</FormItem>
<FormItem label="按钮文字" name="button.color">
<ColorInput v-model="formData.button.color" />
</FormItem>
<FormItem label="间隔" name="space">
<Slider
v-model:value="formData.space"
:max="100"
:min="0"
/>
</FormItem>
</Card>
</Form>
</ComponentContainerProperty>
<!-- 优惠券选择 -->
<CouponSelectModal
:take-type="CouponTemplateTakeTypeEnum.USER.type"
@success="handleCouponSelect"
/>
</template>

View File

@@ -1,4 +1,68 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { FloatingActionButtonProperty } from './config';
import { useVModel } from '@vueuse/core';
import {
Card,
Form,
FormItem,
Radio,
RadioGroup,
Switch,
} from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import {
AppLinkInput,
Draggable,
InputWithColor,
} from '#/views/mall/promotion/components';
/** 悬浮按钮属性面板 */
defineOptions({ name: 'FloatingActionButtonProperty' });
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>
<template><Page>待完成</Page></template>
<template>
<Form :model="formData">
<Card title="按钮配置" class="property-group">
<FormItem label="展开方向" name="direction">
<RadioGroup v-model:value="formData.direction">
<Radio value="vertical">垂直</Radio>
<Radio value="horizontal">水平</Radio>
</RadioGroup>
</FormItem>
<FormItem label="显示文字" name="showText">
<Switch v-model:checked="formData.showText" />
</FormItem>
</Card>
<Card title="按钮列表" class="property-group">
<Draggable v-model="formData.list" :empty-item="{ textColor: '#fff' }">
<template #default="{ element, index }">
<FormItem label="图标" :name="`list[${index}].imgUrl`">
<UploadImg
v-model="element.imgUrl"
height="56px"
width="56px"
:show-description="false"
/>
</FormItem>
<FormItem label="文字" :name="`list[${index}].text`">
<InputWithColor
v-model="element.text"
v-model:color="element.textColor"
/>
</FormItem>
<FormItem label="跳转链接" :name="`list[${index}].url`">
<AppLinkInput v-model="element.url" />
</FormItem>
</template>
</Draggable>
</Card>
</Form>
</template>

View File

@@ -1,4 +1,245 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { HotZoneItemProperty } from '../../config';
import type { ControlDot } from './controller';
import type { AppLink } from '#/views/mall/promotion/components/app-link-input/data';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Button, Image } from 'ant-design-vue';
import { AppLinkSelectDialog } from '#/views/mall/promotion/components';
import {
CONTROL_DOT_LIST,
CONTROL_TYPE_ENUM,
HOT_ZONE_MIN_SIZE,
useDraggable,
zoomIn,
zoomOut,
} from './controller';
/** 热区编辑对话框 */
defineOptions({ name: 'HotZoneEditDialog' });
/** 定义属性 */
const props = defineProps({
modelValue: {
type: Array<HotZoneItemProperty>,
default: () => [],
},
imgUrl: {
type: String,
default: '',
},
});
const emit = defineEmits(['update:modelValue']);
const formData = ref<HotZoneItemProperty[]>([]);
const [Modal, modalApi] = useVbenModal({
showCancelButton: false,
onConfirm() {
const list = zoomOut(formData.value);
emit('update:modelValue', list);
modalApi.close();
},
});
/** 打开弹窗 */
function open() {
// 放大
formData.value = zoomIn(props.modelValue);
modalApi.open();
}
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
const container = ref<HTMLDivElement>(); // 热区容器
/** 增加热区 */
function handleAdd() {
formData.value.push({
width: HOT_ZONE_MIN_SIZE,
height: HOT_ZONE_MIN_SIZE,
top: 0,
left: 0,
} as HotZoneItemProperty);
}
/** 删除热区 */
function handleRemove(hotZone: HotZoneItemProperty) {
formData.value = formData.value.filter((item) => item !== hotZone);
}
/** 移动热区 */
function handleMove(item: HotZoneItemProperty, e: MouseEvent) {
useDraggable(item, e, (left, top, _, __, moveWidth, moveHeight) => {
setLeft(item, left + moveWidth);
setTop(item, top + moveHeight);
});
}
/** 调整热区大小、位置 */
function handleResize(
item: HotZoneItemProperty,
ctrlDot: ControlDot,
e: MouseEvent,
) {
useDraggable(item, e, (left, top, width, height, moveWidth, moveHeight) => {
ctrlDot.types.forEach((type) => {
switch (type) {
case CONTROL_TYPE_ENUM.HEIGHT: {
{
// 左移时,宽度为减少
const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.TOP)
? -1
: 1;
setHeight(item, height + moveHeight * direction);
}
break;
}
case CONTROL_TYPE_ENUM.LEFT: {
setLeft(item, left + moveWidth);
break;
}
case CONTROL_TYPE_ENUM.TOP: {
setTop(item, top + moveHeight);
break;
}
case CONTROL_TYPE_ENUM.WIDTH: {
{
// 上移时,高度为减少
const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.LEFT)
? -1
: 1;
setWidth(item, width + moveWidth * direction);
}
break;
}
}
});
});
}
/** 设置 X 轴坐标 */
function setLeft(item: HotZoneItemProperty, left: number) {
// 不能超出容器
if (left >= 0 && left <= container.value!.offsetWidth - item.width) {
item.left = left;
}
}
/** 设置Y轴坐标 */
function setTop(item: HotZoneItemProperty, top: number) {
// 不能超出容器
if (top >= 0 && top <= container.value!.offsetHeight - item.height) {
item.top = top;
}
}
/** 设置宽度 */
const setWidth = (item: HotZoneItemProperty, width: number) => {
// 不能小于最小宽度 && 不能超出容器右边
if (
width >= HOT_ZONE_MIN_SIZE &&
item.left + width <= container.value!.offsetWidth
) {
item.width = width;
}
};
/** 设置高度 */
const setHeight = (item: HotZoneItemProperty, height: number) => {
// 不能小于最小高度 && 不能超出容器底部
if (
height >= HOT_ZONE_MIN_SIZE &&
item.top + height <= container.value!.offsetHeight
) {
item.height = height;
}
};
const activeHotZone = ref<HotZoneItemProperty>();
const appLinkDialogRef = ref();
/** 显示 App 链接选择对话框 */
const handleShowAppLinkDialog = (hotZone: HotZoneItemProperty) => {
activeHotZone.value = hotZone;
appLinkDialogRef.value.open(hotZone.url);
};
/** 处理 App 链接选择变更 */
const handleAppLinkChange = (appLink: AppLink) => {
if (!appLink || !activeHotZone.value) {
return;
}
activeHotZone.value.name = appLink.name;
activeHotZone.value.url = appLink.path;
};
</script>
<template><Page>待完成</Page></template>
<template>
<Modal title="设置热区" class="w-[780px]">
<div ref="container" class="w-750px relative h-full">
<Image
:src="imgUrl"
:preview="false"
class="w-750px pointer-events-none h-full select-none"
/>
<div
v-for="(item, hotZoneIndex) in formData"
:key="hotZoneIndex"
class="group absolute z-10 flex cursor-move items-center justify-center border text-base opacity-80"
:style="{
width: `${item.width}px`,
height: `${item.height}px`,
top: `${item.top}px`,
left: `${item.left}px`,
color: 'var(--ant-color-primary)',
background: 'color-mix(in srgb, var(--ant-color-primary) 30%, transparent)',
borderColor: 'var(--ant-color-primary)',
}"
@mousedown="handleMove(item, $event)"
@dblclick="handleShowAppLinkDialog(item)"
>
<span class="pointer-events-none select-none">
{{ item.name || '双击选择链接' }}
</span>
<IconifyIcon
icon="ep:close"
class="absolute right-0 top-0 hidden cursor-pointer rounded-bl-[80%] p-[2px_2px_6px_6px] text-right text-white group-hover:block"
:style="{ backgroundColor: 'var(--ant-color-primary)' }"
:size="14"
@click="handleRemove(item)"
/>
<!-- 8 个控制点 -->
<span
class="ctrl-dot absolute z-[11] h-2 w-2 rounded-full bg-white"
v-for="(dot, dotIndex) in CONTROL_DOT_LIST"
:key="dotIndex"
:style="{ ...dot.style, border: 'inherit' }"
@mousedown="handleResize(item, dot, $event)"
></span>
</div>
</div>
<template #prepend-footer>
<Button @click="handleAdd" type="primary" ghost>
<template #icon>
<IconifyIcon icon="ep:plus" />
</template>
添加热区
</Button>
</template>
</Modal>
<AppLinkSelectDialog
ref="appLinkDialogRef"
@app-link-change="handleAppLinkChange"
/>
</template>

View File

@@ -1,4 +1,86 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { MagicCubeProperty } from './config';
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import { Form, FormItem, Slider, Typography } from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import {
AppLinkInput,
MagicCubeEditor,
} from '#/views/mall/promotion/components';
import ComponentContainerProperty from '../../component-container-property.vue';
const { Text: ATypographyText } = Typography;
/** 广告魔方属性面板 */
defineOptions({ name: 'MagicCubeProperty' });
const props = defineProps<{ modelValue: MagicCubeProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
const selectedHotAreaIndex = ref(-1); // 选中的热区
/** 处理热区被选中事件 */
const handleHotAreaSelected = (_: any, index: number) => {
selectedHotAreaIndex.value = index;
};
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<Form :model="formData" class="mt-2">
<ATypographyText tag="p"> 魔方设置 </ATypographyText>
<ATypographyText type="secondary" class="text-sm"> 每格尺寸187 * 187 </ATypographyText>
<MagicCubeEditor
class="my-4"
v-model="formData.list"
:rows="4"
:cols="4"
@hot-area-selected="handleHotAreaSelected"
/>
<template v-for="(hotArea, index) in formData.list" :key="index">
<template v-if="selectedHotAreaIndex === index">
<FormItem label="上传图片" :name="`list[${index}].imgUrl`">
<UploadImg
v-model="hotArea.imgUrl"
height="80px"
width="80px"
:show-description="false"
/>
</FormItem>
<FormItem label="链接" :name="`list[${index}].url`">
<AppLinkInput v-model="hotArea.url" />
</FormItem>
</template>
</template>
<FormItem label="上圆角" name="borderRadiusTop">
<Slider
v-model:value="formData.borderRadiusTop"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="下圆角" name="borderRadiusBottom">
<Slider
v-model:value="formData.borderRadiusBottom"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="间隔" name="space">
<Slider
v-model:value="formData.space"
:max="100"
:min="0"
/>
</FormItem>
</Form>
</ComponentContainerProperty>
</template>

View File

@@ -1,4 +1,68 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { MenuListProperty } from './config';
import { useVModel } from '@vueuse/core';
import { Form, Typography } from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import {
AppLinkInput,
Draggable,
InputWithColor,
} from '#/views/mall/promotion/components';
import ComponentContainerProperty from '../../component-container-property.vue';
import { EMPTY_MENU_LIST_ITEM_PROPERTY } from './config';
const { Text: ATypographyText } = Typography;
/** 列表导航属性面板 */
defineOptions({ name: 'MenuListProperty' });
const props = defineProps<{ modelValue: MenuListProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<ATypographyText tag="p"> 菜单设置 </ATypographyText>
<ATypographyText type="secondary" class="text-sm"> 拖动左侧的小圆点可以调整顺序 </ATypographyText>
<Form :model="formData" class="mt-2">
<Draggable
v-model="formData.list"
:empty-item="EMPTY_MENU_LIST_ITEM_PROPERTY"
>
<template #default="{ element }">
<FormItem label="图标" name="iconUrl">
<UploadImg
v-model="element.iconUrl"
height="80px"
width="80px"
:show-description="false"
>
<template #tip> 建议尺寸44 * 44 </template>
</UploadImg>
</FormItem>
<FormItem label="标题" name="title">
<InputWithColor
v-model="element.title"
v-model:color="element.titleColor"
/>
</FormItem>
<FormItem label="副标题" name="subtitle">
<InputWithColor
v-model="element.subtitle"
v-model:color="element.subtitleColor"
/>
</FormItem>
<FormItem label="链接" name="url">
<AppLinkInput v-model="element.url" />
</FormItem>
</template>
</Draggable>
</Form>
</ComponentContainerProperty>
</template>

View File

@@ -1,4 +1,103 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { MenuSwiperProperty } from './config';
import { cloneDeep } from '@vben/utils';
import { useVModel } from '@vueuse/core';
import {
Card,
Form,
FormItem,
Radio,
RadioGroup,
Switch,
} from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import {
AppLinkInput,
ColorInput,
Draggable,
InputWithColor,
} from '#/views/mall/promotion/components';
import ComponentContainerProperty from '../../component-container-property.vue';
import { EMPTY_MENU_SWIPER_ITEM_PROPERTY } from './config';
/** 菜单导航属性面板 */
defineOptions({ name: 'MenuSwiperProperty' });
const props = defineProps<{ modelValue: MenuSwiperProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<Form :model="formData" class="mt-2">
<FormItem label="布局" name="layout">
<RadioGroup v-model:value="formData.layout">
<Radio value="iconText">图标+文字</Radio>
<Radio value="icon">仅图标</Radio>
</RadioGroup>
</FormItem>
<FormItem label="行数" name="row">
<RadioGroup v-model:value="formData.row">
<Radio :value="1">1</Radio>
<Radio :value="2">2</Radio>
</RadioGroup>
</FormItem>
<FormItem label="列数" name="column">
<RadioGroup v-model:value="formData.column">
<Radio :value="3">3</Radio>
<Radio :value="4">4</Radio>
<Radio :value="5">5</Radio>
</RadioGroup>
</FormItem>
<Card title="菜单设置" class="property-group">
<Draggable
v-model="formData.list"
:empty-item="cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY)"
>
<template #default="{ element }">
<FormItem label="图标" name="iconUrl">
<UploadImg
v-model="element.iconUrl"
height="80px"
width="80px"
:show-description="false"
>
<template #tip> 建议尺寸98 * 98 </template>
</UploadImg>
</FormItem>
<FormItem label="标题" name="title">
<InputWithColor
v-model="element.title"
v-model:color="element.titleColor"
/>
</FormItem>
<FormItem label="链接" name="url">
<AppLinkInput v-model="element.url" />
</FormItem>
<FormItem label="显示角标" name="badge.show">
<Switch v-model:checked="element.badge.show" />
</FormItem>
<template v-if="element.badge.show">
<FormItem label="角标内容" name="badge.text">
<InputWithColor
v-model="element.badge.text"
v-model:color="element.badge.textColor"
/>
</FormItem>
<FormItem label="背景颜色" name="badge.bgColor">
<ColorInput v-model="element.badge.bgColor" />
</FormItem>
</template>
</template>
</Draggable>
</Card>
</Form>
</ComponentContainerProperty>
</template>

View File

@@ -1,4 +1,61 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { NoticeBarProperty } from './config';
import { useVModel } from '@vueuse/core';
import { Card, Form, FormItem, Input } from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import {
AppLinkInput,
ColorInput,
Draggable,
} from '#/views/mall/promotion/components';
import ComponentContainerProperty from '../../component-container-property.vue';
/** 公告栏属性面板 */
defineOptions({ name: 'NoticeBarProperty' });
const props = defineProps<{ modelValue: NoticeBarProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
const rules = {
content: [{ required: true, message: '请输入公告', trigger: 'blur' }],
}; // 表单校验
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<Form :model="formData" :rules="rules">
<FormItem label="公告图标" name="iconUrl">
<UploadImg
v-model="formData.iconUrl"
height="48px"
:show-description="false"
>
<template #tip>建议尺寸24 * 24</template>
</UploadImg>
</FormItem>
<FormItem label="背景颜色" name="backgroundColor">
<ColorInput v-model="formData.backgroundColor" />
</FormItem>
<FormItem label="文字颜色" name="textColor">
<ColorInput v-model="formData.textColor" />
</FormItem>
<Card title="公告内容" class="property-group">
<Draggable v-model="formData.contents">
<template #default="{ element }">
<FormItem label="公告" name="text">
<Input v-model:value="element.text" placeholder="请输入公告" />
</FormItem>
<FormItem label="链接" name="url">
<AppLinkInput v-model="element.url" />
</FormItem>
</template>
</Draggable>
</Card>
</Form>
</ComponentContainerProperty>
</template>

View File

@@ -1,4 +1,170 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { ProductCardProperty } from './config';
import { IconifyIcon } from '@vben/icons';
import { useVModel } from '@vueuse/core';
import {
Card,
Checkbox,
Form,
FormItem,
Input,
RadioButton,
RadioGroup,
Slider,
Switch,
Tooltip,
} from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import { SpuShowcase } from '#/views/mall/product/spu/components';
import { ColorInput } from '#/views/mall/promotion/components';
import ComponentContainerProperty from '../../component-container-property.vue';
/** 商品卡片属性面板 */
defineOptions({ name: 'ProductCardProperty' });
const props = defineProps<{ modelValue: ProductCardProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<Form :model="formData">
<Card title="商品列表" class="property-group">
<SpuShowcase v-model="formData.spuIds" />
</Card>
<Card title="商品样式" class="property-group">
<FormItem label="布局" name="type">
<RadioGroup v-model:value="formData.layoutType">
<Tooltip title="单列大图" placement="bottom">
<RadioButton value="oneColBigImg">
<IconifyIcon icon="fluent:text-column-one-24-filled" />
</RadioButton>
</Tooltip>
<Tooltip title="单列小图" placement="bottom">
<RadioButton value="oneColSmallImg">
<IconifyIcon icon="fluent:text-column-two-left-24-filled" />
</RadioButton>
</Tooltip>
<Tooltip title="双列" placement="bottom">
<RadioButton value="twoCol">
<IconifyIcon icon="fluent:text-column-two-24-filled" />
</RadioButton>
</Tooltip>
</RadioGroup>
</FormItem>
<FormItem label="商品名称" name="fields.name.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.name.color" />
<Checkbox v-model:checked="formData.fields.name.show" />
</div>
</FormItem>
<FormItem label="商品简介" name="fields.introduction.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.introduction.color" />
<Checkbox v-model:checked="formData.fields.introduction.show" />
</div>
</FormItem>
<FormItem label="商品价格" name="fields.price.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.price.color" />
<Checkbox v-model:checked="formData.fields.price.show" />
</div>
</FormItem>
<FormItem label="市场价" name="fields.marketPrice.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.marketPrice.color" />
<Checkbox v-model:checked="formData.fields.marketPrice.show" />
</div>
</FormItem>
<FormItem label="商品销量" name="fields.salesCount.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.salesCount.color" />
<Checkbox v-model:checked="formData.fields.salesCount.show" />
</div>
</FormItem>
<FormItem label="商品库存" name="fields.stock.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.stock.color" />
<Checkbox v-model:checked="formData.fields.stock.show" />
</div>
</FormItem>
</Card>
<Card title="角标" class="property-group">
<FormItem label="角标" name="badge.show">
<Switch v-model:checked="formData.badge.show" />
</FormItem>
<FormItem label="角标" name="badge.imgUrl" v-if="formData.badge.show">
<UploadImg
v-model="formData.badge.imgUrl"
height="44px"
width="72px"
:show-description="false"
>
<template #tip> 建议尺寸36 * 22 </template>
</UploadImg>
</FormItem>
</Card>
<Card title="按钮" class="property-group">
<FormItem label="按钮类型" name="btnBuy.type">
<RadioGroup v-model:value="formData.btnBuy.type">
<RadioButton value="text">文字</RadioButton>
<RadioButton value="img">图片</RadioButton>
</RadioGroup>
</FormItem>
<template v-if="formData.btnBuy.type === 'text'">
<FormItem label="按钮文字" name="btnBuy.text">
<Input v-model:value="formData.btnBuy.text" />
</FormItem>
<FormItem label="左侧背景" name="btnBuy.bgBeginColor">
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
</FormItem>
<FormItem label="右侧背景" name="btnBuy.bgEndColor">
<ColorInput v-model="formData.btnBuy.bgEndColor" />
</FormItem>
</template>
<template v-else>
<FormItem label="图片" name="btnBuy.imgUrl">
<UploadImg
v-model="formData.btnBuy.imgUrl"
height="56px"
width="56px"
:show-description="false"
>
<template #tip> 建议尺寸56 * 56 </template>
</UploadImg>
</FormItem>
</template>
</Card>
<Card title="商品样式" class="property-group">
<FormItem label="上圆角" name="borderRadiusTop">
<Slider
v-model:value="formData.borderRadiusTop"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="下圆角" name="borderRadiusBottom">
<Slider
v-model:value="formData.borderRadiusBottom"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="间隔" name="space">
<Slider
v-model:value="formData.space"
:max="100"
:min="0"
/>
</FormItem>
</Card>
</Form>
</ComponentContainerProperty>
</template>

View File

@@ -1,4 +1,168 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
<script lang="ts" setup>
import type { PromotionPointProperty } from './config';
import { IconifyIcon } from '@vben/icons';
import { useVModel } from '@vueuse/core';
import {
Card,
Checkbox,
Form,
FormItem,
Input,
RadioButton,
RadioGroup,
Slider,
Switch,
Tooltip,
} from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import { ColorInput } from '#/views/mall/promotion/components';
import { PointShowcase } from '#/views/mall/promotion/point/components';
import ComponentContainerProperty from '../../component-container-property.vue';
/** 积分属性面板 */
defineOptions({ name: 'PromotionPointProperty' });
const props = defineProps<{ modelValue: PromotionPointProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<Form :model="formData">
<Card title="积分商城活动" class="property-group">
<PointShowcase v-model="formData.activityIds" />
</Card>
<Card title="商品样式" class="property-group">
<FormItem label="布局" name="type">
<RadioGroup v-model:value="formData.layoutType">
<Tooltip title="单列大图" placement="bottom">
<RadioButton value="oneColBigImg">
<IconifyIcon icon="fluent:text-column-one-24-filled" />
</RadioButton>
</Tooltip>
<Tooltip title="单列小图" placement="bottom">
<RadioButton value="oneColSmallImg">
<IconifyIcon icon="fluent:text-column-two-left-24-filled" />
</RadioButton>
</Tooltip>
<Tooltip title="双列" placement="bottom">
<RadioButton value="twoCol">
<IconifyIcon icon="fluent:text-column-two-24-filled" />
</RadioButton>
</Tooltip>
</RadioGroup>
</FormItem>
<FormItem label="商品名称" name="fields.name.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.name.color" />
<Checkbox v-model:checked="formData.fields.name.show" />
</div>
</FormItem>
<FormItem label="商品简介" name="fields.introduction.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.introduction.color" />
<Checkbox v-model:checked="formData.fields.introduction.show" />
</div>
</FormItem>
<FormItem label="商品价格" name="fields.price.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.price.color" />
<Checkbox v-model:checked="formData.fields.price.show" />
</div>
</FormItem>
<FormItem label="市场价" name="fields.marketPrice.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.marketPrice.color" />
<Checkbox v-model:checked="formData.fields.marketPrice.show" />
</div>
</FormItem>
<FormItem label="商品销量" name="fields.salesCount.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.salesCount.color" />
<Checkbox v-model:checked="formData.fields.salesCount.show" />
</div>
</FormItem>
<FormItem label="商品库存" name="fields.stock.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.stock.color" />
<Checkbox v-model:checked="formData.fields.stock.show" />
</div>
</FormItem>
</Card>
<Card title="角标" class="property-group">
<FormItem label="角标" name="badge.show">
<Switch v-model:checked="formData.badge.show" />
</FormItem>
<FormItem label="角标" name="badge.imgUrl" v-if="formData.badge.show">
<UploadImg
v-model="formData.badge.imgUrl"
height="44px"
width="72px"
:show-description="false"
>
<template #tip> 建议尺寸36 * 22 </template>
</UploadImg>
</FormItem>
</Card>
<Card title="按钮" class="property-group">
<FormItem label="按钮类型" name="btnBuy.type">
<RadioGroup v-model:value="formData.btnBuy.type">
<RadioButton value="text">文字</RadioButton>
<RadioButton value="img">图片</RadioButton>
</RadioGroup>
</FormItem>
<template v-if="formData.btnBuy.type === 'text'">
<FormItem label="按钮文字" name="btnBuy.text">
<Input v-model:value="formData.btnBuy.text" />
</FormItem>
<FormItem label="左侧背景" name="btnBuy.bgBeginColor">
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
</FormItem>
<FormItem label="右侧背景" name="btnBuy.bgEndColor">
<ColorInput v-model="formData.btnBuy.bgEndColor" />
</FormItem>
</template>
<template v-else>
<FormItem label="图片" name="btnBuy.imgUrl">
<UploadImg
v-model="formData.btnBuy.imgUrl"
height="56px"
width="56px"
:show-description="false"
>
<template #tip> 建议尺寸56 * 56 </template>
</UploadImg>
</FormItem>
</template>
</Card>
<Card title="商品样式" class="property-group">
<FormItem label="上圆角" name="borderRadiusTop">
<Slider
v-model:value="formData.borderRadiusTop"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="下圆角" name="borderRadiusBottom">
<Slider
v-model:value="formData.borderRadiusBottom"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="间隔" name="space">
<Slider
v-model:value="formData.space"
:max="100"
:min="0"
/>
</FormItem>
</Card>
</Form>
</ComponentContainerProperty>
</template>

View File

@@ -1,4 +1,170 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { PromotionSeckillProperty } from './config';
import { IconifyIcon } from '@vben/icons';
import { useVModel } from '@vueuse/core';
import {
Card,
Checkbox,
Form,
FormItem,
Input,
RadioButton,
RadioGroup,
Slider,
Switch,
Tooltip,
} from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import { ColorInput } from '#/views/mall/promotion/components';
import { SeckillShowcase } from '#/views/mall/promotion/seckill/components';
import ComponentContainerProperty from '../../component-container-property.vue';
/** 秒杀属性面板 */
defineOptions({ name: 'PromotionSeckillProperty' });
const props = defineProps<{ modelValue: PromotionSeckillProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<Form :model="formData">
<Card title="秒杀活动" class="property-group">
<SeckillShowcase v-model="formData.activityIds" />
</Card>
<Card title="商品样式" class="property-group">
<FormItem label="布局" name="type">
<RadioGroup v-model:value="formData.layoutType">
<Tooltip title="单列大图" placement="bottom">
<RadioButton value="oneColBigImg">
<IconifyIcon icon="fluent:text-column-one-24-filled" />
</RadioButton>
</Tooltip>
<Tooltip title="单列小图" placement="bottom">
<RadioButton value="oneColSmallImg">
<IconifyIcon icon="fluent:text-column-two-left-24-filled" />
</RadioButton>
</Tooltip>
<Tooltip title="双列" placement="bottom">
<RadioButton value="twoCol">
<IconifyIcon icon="fluent:text-column-two-24-filled" />
</RadioButton>
</Tooltip>
</RadioGroup>
</FormItem>
<FormItem label="商品名称" name="fields.name.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.name.color" />
<Checkbox v-model:checked="formData.fields.name.show" />
</div>
</FormItem>
<FormItem label="商品简介" name="fields.introduction.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.introduction.color" />
<Checkbox v-model:checked="formData.fields.introduction.show" />
</div>
</FormItem>
<FormItem label="商品价格" name="fields.price.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.price.color" />
<Checkbox v-model:checked="formData.fields.price.show" />
</div>
</FormItem>
<FormItem label="市场价" name="fields.marketPrice.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.marketPrice.color" />
<Checkbox v-model:checked="formData.fields.marketPrice.show" />
</div>
</FormItem>
<FormItem label="商品销量" name="fields.salesCount.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.salesCount.color" />
<Checkbox v-model:checked="formData.fields.salesCount.show" />
</div>
</FormItem>
<FormItem label="商品库存" name="fields.stock.show">
<div class="flex gap-2">
<ColorInput v-model="formData.fields.stock.color" />
<Checkbox v-model:checked="formData.fields.stock.show" />
</div>
</FormItem>
</Card>
<Card title="角标" class="property-group">
<FormItem label="角标" name="badge.show">
<Switch v-model:checked="formData.badge.show" />
</FormItem>
<FormItem label="角标" name="badge.imgUrl" v-if="formData.badge.show">
<UploadImg
v-model="formData.badge.imgUrl"
height="44px"
width="72px"
:show-description="false"
>
<template #tip> 建议尺寸36 * 22 </template>
</UploadImg>
</FormItem>
</Card>
<Card title="按钮" class="property-group">
<FormItem label="按钮类型" name="btnBuy.type">
<RadioGroup v-model:value="formData.btnBuy.type">
<RadioButton value="text">文字</RadioButton>
<RadioButton value="img">图片</RadioButton>
</RadioGroup>
</FormItem>
<template v-if="formData.btnBuy.type === 'text'">
<FormItem label="按钮文字" name="btnBuy.text">
<Input v-model:value="formData.btnBuy.text" />
</FormItem>
<FormItem label="左侧背景" name="btnBuy.bgBeginColor">
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
</FormItem>
<FormItem label="右侧背景" name="btnBuy.bgEndColor">
<ColorInput v-model="formData.btnBuy.bgEndColor" />
</FormItem>
</template>
<template v-else>
<FormItem label="图片" name="btnBuy.imgUrl">
<UploadImg
v-model="formData.btnBuy.imgUrl"
height="56px"
width="56px"
:show-description="false"
>
<template #tip> 建议尺寸56 * 56</template>
</UploadImg>
</FormItem>
</template>
</Card>
<Card title="商品样式" class="property-group">
<FormItem label="上圆角" name="borderRadiusTop">
<Slider
v-model:value="formData.borderRadiusTop"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="下圆角" name="borderRadiusBottom">
<Slider
v-model:value="formData.borderRadiusBottom"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="间隔" name="space">
<Slider
v-model:value="formData.space"
:max="100"
:min="0"
/>
</FormItem>
</Card>
</Form>
</ComponentContainerProperty>
</template>

View File

@@ -1,4 +1,159 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import type { TitleBarProperty } from './config';
import { IconifyIcon } from '@vben/icons';
import { useVModel } from '@vueuse/core';
import {
Card,
Checkbox,
Form,
FormItem,
Input,
Radio,
RadioButton,
RadioGroup,
Slider,
Tooltip,
} from 'ant-design-vue';
import UploadImg from '#/components/upload/image-upload.vue';
import {
AppLinkInput,
InputWithColor,
} from '#/views/mall/promotion/components';
import ComponentContainerProperty from '../../component-container-property.vue';
/** 导航栏属性面板 */
defineOptions({ name: 'TitleBarProperty' });
const props = defineProps<{ modelValue: TitleBarProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
const rules = {}; // 表单校验
</script>
<template><Page>待完成</Page></template>
<template>
<ComponentContainerProperty v-model="formData.style">
<Form :model="formData" :rules="rules">
<Card title="风格" class="property-group">
<FormItem label="背景图片" name="bgImgUrl">
<UploadImg
v-model="formData.bgImgUrl"
width="100%"
height="40px"
:show-description="false"
>
<template #tip>建议尺寸 750*80</template>
</UploadImg>
</FormItem>
<FormItem label="标题位置" name="textAlign">
<RadioGroup v-model:value="formData!.textAlign">
<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="marginLeft">
<Slider
v-model:value="formData.marginLeft"
:max="100"
:min="0"
/>
</FormItem>
<FormItem label="高度" name="height">
<Slider
v-model:value="formData.height"
:max="200"
:min="20"
/>
</FormItem>
</Card>
<Card title="主标题" class="property-group">
<FormItem label="文字" name="title">
<InputWithColor
v-model="formData.title"
v-model:color="formData.titleColor"
show-count
:maxlength="20"
/>
</FormItem>
<FormItem label="大小" name="titleSize">
<Slider
v-model:value="formData.titleSize"
:max="60"
:min="10"
/>
</FormItem>
<FormItem label="粗细" name="titleWeight">
<Slider
v-model:value="formData.titleWeight"
:min="100"
:max="900"
:step="100"
/>
</FormItem>
</Card>
<Card title="副标题" class="property-group">
<FormItem label="文字" name="description">
<InputWithColor
v-model="formData.description"
v-model:color="formData.descriptionColor"
show-count
:maxlength="50"
/>
</FormItem>
<FormItem label="大小" name="descriptionSize">
<Slider
v-model:value="formData.descriptionSize"
:max="60"
:min="10"
/>
</FormItem>
<FormItem label="粗细" name="descriptionWeight">
<Slider
v-model:value="formData.descriptionWeight"
:min="100"
:max="900"
:step="100"
/>
</FormItem>
</Card>
<Card title="查看更多" class="property-group">
<FormItem label="是否显示" name="more.show">
<Checkbox v-model:checked="formData.more.show" />
</FormItem>
<!-- 更多按钮的 样式选择 -->
<template v-if="formData.more.show">
<FormItem label="样式" name="more.type">
<RadioGroup v-model:value="formData.more.type">
<Radio value="text">文字</Radio>
<Radio value="icon">图标</Radio>
<Radio value="all">文字+图标</Radio>
</RadioGroup>
</FormItem>
<FormItem
label="更多文字"
name="more.text"
v-show="formData.more.type !== 'icon'"
>
<Input v-model:value="formData.more.text" />
</FormItem>
<FormItem label="跳转链接" name="more.url">
<AppLinkInput v-model="formData.more.url" />
</FormItem>
</template>
</Card>
</Form>
</ComponentContainerProperty>
</template>