feat:【antd】【mall】diy-editor 代码风格统一 & 逐个测试 60%

This commit is contained in:
YunaiV
2025-11-11 22:20:52 +08:00
parent 4488425cbf
commit 7bd3a31a09
19 changed files with 174 additions and 215 deletions

View File

@@ -1,4 +1,87 @@
// 导出所有优惠券相关组件
export { CouponDiscount } from './coupon-discount';
export { CouponDiscountDesc } from './coupon-discount-desc';
export { CouponValidTerm } from './coupon-validTerm';
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { defineComponent } from 'vue';
import {
CouponTemplateValidityTypeEnum,
PromotionDiscountTypeEnum,
} from '@vben/constants';
import { floatToFixed2, formatDate } from '@vben/utils';
/** 有效期 */
export const CouponValidTerm = defineComponent({
name: 'CouponValidTerm',
props: {
coupon: {
type: Object as () => MallCouponTemplateApi.CouponTemplate,
required: true,
},
},
setup(props) {
const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
const text =
coupon.validityType === CouponTemplateValidityTypeEnum.DATE.type
? `有效期:${formatDate(coupon.validStartTime, 'YYYY-MM-DD')}${formatDate(
coupon.validEndTime,
'YYYY-MM-DD',
)}`
: `领取后第 ${coupon.fixedStartTerm} - ${coupon.fixedEndTerm} 天内可用`;
return () => <div>{text}</div>;
},
});
/** 优惠值 */
export const CouponDiscount = defineComponent({
name: 'CouponDiscount',
props: {
coupon: {
type: Object as () => MallCouponTemplateApi.CouponTemplate,
required: true,
},
},
setup(props) {
const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
// 折扣
let value = `${(coupon.discountPercent ?? 0) / 10}`;
let suffix = ' 折';
// 满减
if (coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
value = floatToFixed2(coupon.discountPrice);
suffix = ' 元';
}
return () => (
<div>
<span class={'text-20px font-bold'}>{value}</span>
<span>{suffix}</span>
</div>
);
},
});
/** 优惠描述 */
export const CouponDiscountDesc = defineComponent({
name: 'CouponDiscountDesc',
props: {
coupon: {
type: Object as () => MallCouponTemplateApi.CouponTemplate,
required: true,
},
},
setup(props) {
const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
// 使用条件
const useCondition =
coupon.usePrice > 0 ? `${floatToFixed2(coupon.usePrice)}元,` : '';
// 优惠描述
const discountDesc =
coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
? `${floatToFixed2(coupon.discountPrice)}`
: `${(coupon.discountPercent ?? 0) / 10}`;
return () => (
<div>
<span>{useCondition}</span>
<span>{discountDesc}</span>
</div>
);
},
});

View File

@@ -1,34 +0,0 @@
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { defineComponent } from 'vue';
import { PromotionDiscountTypeEnum } from '@vben/constants';
import { floatToFixed2 } from '@vben/utils';
// 优惠描述
export const CouponDiscountDesc = defineComponent({
name: 'CouponDiscountDesc',
props: {
coupon: {
type: Object as () => MallCouponTemplateApi.CouponTemplate,
required: true,
},
},
setup(props) {
const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
// 使用条件
const useCondition =
coupon.usePrice > 0 ? `${floatToFixed2(coupon.usePrice)}元,` : '';
// 优惠描述
const discountDesc =
coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
? `${floatToFixed2(coupon.discountPrice)}`
: `${(coupon.discountPercent ?? 0) / 10}`;
return () => (
<div>
<span>{useCondition}</span>
<span>{discountDesc}</span>
</div>
);
},
});

View File

@@ -1,34 +0,0 @@
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { defineComponent } from 'vue';
import { PromotionDiscountTypeEnum } from '@vben/constants';
import { floatToFixed2 } from '@vben/utils';
// 优惠值
export const CouponDiscount = defineComponent({
name: 'CouponDiscount',
props: {
coupon: {
type: Object as () => MallCouponTemplateApi.CouponTemplate,
required: true,
},
},
setup(props) {
const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
// 折扣
let value = `${(coupon.discountPercent ?? 0) / 10}`;
let suffix = ' 折';
// 满减
if (coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
value = floatToFixed2(coupon.discountPrice);
suffix = ' 元';
}
return () => (
<div>
<span class={'text-20px font-bold'}>{value}</span>
<span>{suffix}</span>
</div>
);
},
});

View File

@@ -1,28 +0,0 @@
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { defineComponent } from 'vue';
import { CouponTemplateValidityTypeEnum } from '@vben/constants';
import { formatDate } from '@vben/utils';
// 有效期
export const CouponValidTerm = defineComponent({
name: 'CouponValidTerm',
props: {
coupon: {
type: Object as () => MallCouponTemplateApi.CouponTemplate,
required: true,
},
},
setup(props) {
const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
const text =
coupon.validityType === CouponTemplateValidityTypeEnum.DATE.type
? `有效期:${formatDate(coupon.validStartTime, 'YYYY-MM-DD')}${formatDate(
coupon.validEndTime,
'YYYY-MM-DD',
)}`
: `领取后第 ${coupon.fixedStartTerm} - ${coupon.fixedEndTerm} 天内可用`;
return () => <div>{text}</div>;
},
});

View File

@@ -13,12 +13,19 @@ import {
CouponValidTerm,
} from './component';
/** 商品卡片 */
/** 优惠劵卡片 */
defineOptions({ name: 'CouponCard' });
// 定义属性
/** 定义属性 */
const props = defineProps<{ property: CouponCardProperty }>();
// 商品列表
const couponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]);
const couponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]); // 优惠劵列表
const phoneWidth = ref(375); // 手机宽度
const containerRef = ref(); // 容器引用
const scrollbarWidth = ref('100%'); // 滚动条宽度
const couponWidth = ref(375); // 优惠券宽度
/** 监听优惠券 ID 变化,加载优惠券列表 */
watch(
() => props.property.couponIds,
async () => {
@@ -32,15 +39,7 @@ watch(
},
);
// 手机宽度
const phoneWidth = ref(384);
// 容器
const containerRef = ref();
// 滚动条宽度
const scrollbarWidth = ref('100%');
// 优惠券的宽度
const couponWidth = ref(384);
// 计算布局参数
/** 计算布局参数 */
watch(
() => [props.property, phoneWidth, couponList.value.length],
() => {
@@ -56,9 +55,10 @@ watch(
},
{ immediate: true, deep: true },
);
/** 初始化 */
onMounted(() => {
// 提取手机宽度
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 384;
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375;
});
</script>
<template>
@@ -82,17 +82,14 @@ onMounted(() => {
v-for="(coupon, index) in couponList"
:key="index"
>
<!-- 布局11-->
<!-- 布局 11 -->
<div
v-if="property.columns === 1"
class="ml-4 flex flex-row justify-between p-2"
>
<div class="flex flex-col justify-evenly gap-1">
<!-- 优惠值 -->
<CouponDiscount :coupon="coupon" />
<!-- 优惠描述 -->
<CouponDiscountDesc :coupon="coupon" />
<!-- 有效期 -->
<CouponValidTerm :coupon="coupon" />
</div>
<div class="flex flex-col justify-evenly">
@@ -107,17 +104,14 @@ onMounted(() => {
</div>
</div>
</div>
<!-- 布局22-->
<!-- 布局 22 -->
<div
v-else-if="property.columns === 2"
class="ml-4 flex flex-row justify-between p-2"
>
<div class="flex flex-col justify-evenly gap-1">
<!-- 优惠值 -->
<CouponDiscount :coupon="coupon" />
<!-- 优惠描述 -->
<CouponDiscountDesc :coupon="coupon" />
<!-- 领取说明 -->
<div v-if="coupon.totalCount >= 0">
仅剩{{ coupon.totalCount - coupon.takeCount }}
</div>
@@ -135,11 +129,9 @@ onMounted(() => {
</div>
</div>
</div>
<!-- 布局33-->
<!-- 布局 33 -->
<div v-else class="flex flex-col items-center justify-around gap-1 p-1">
<!-- 优惠值 -->
<CouponDiscount :coupon="coupon" />
<!-- 优惠描述 -->
<CouponDiscountDesc :coupon="coupon" />
<div
class="rounded-full px-2 py-0.5"

View File

@@ -5,23 +5,20 @@ import { ref } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { Button, Image, message } from 'ant-design-vue';
import { Button, Image } from 'ant-design-vue';
/** 悬浮按钮 */
defineOptions({ name: 'FloatingActionButton' });
// 定义属性
/** 定义属性 */
defineProps<{ property: FloatingActionButtonProperty }>();
// 是否展开
const expanded = ref(false);
// 处理展开/折叠
const handleToggleFab = () => {
expanded.value = !expanded.value;
};
const expanded = ref(false); // 是否展开
const handleActive = (index: number) => {
message.success(`点击了${index}`);
};
/** 处理展开/折叠 */
function handleToggleFab() {
expanded.value = !expanded.value;
}
</script>
<template>
<div
@@ -38,9 +35,8 @@ const handleActive = (index: number) => {
v-for="(item, index) in property.list"
:key="index"
class="flex flex-col items-center"
@click="handleActive(index)"
>
<Image :src="item.imgUrl" fit="contain" class="h-7 w-7">
<Image :src="item.imgUrl" fit="contain" class="!h-7 !w-7">
<template #error>
<div class="flex h-full w-full items-center justify-center">
<IconifyIcon
@@ -64,33 +60,15 @@ const handleActive = (index: number) => {
<Button type="primary" size="large" circle @click="handleToggleFab">
<IconifyIcon
icon="lucide:plus"
class="fab-icon"
:class="[{ active: expanded }]"
class="transition-transform duration-300"
:class="expanded ? 'rotate-[135deg]' : 'rotate-0'"
/>
</Button>
</div>
<!-- 模态背景展开时显示点击后折叠 -->
<div v-if="expanded" class="modal-bg" @click="handleToggleFab"></div>
<div
v-if="expanded"
class="absolute left-[calc(50%-384px/2)] top-0 z-[11] h-full w-[384px] bg-black/40"
@click="handleToggleFab"
></div>
</template>
<style scoped lang="scss">
/* 模态背景 */
.modal-bg {
position: absolute;
top: 0;
left: calc(50% - 384px / 2);
z-index: 11;
width: 384px;
height: 100%;
background-color: rgb(0 0 0 / 40%);
}
.fab-icon {
transform: rotate(0deg);
transition: transform 0.3s;
&.active {
transform: rotate(135deg);
}
}
</style>

View File

@@ -11,7 +11,7 @@ import { IconifyIcon } from '@vben/icons';
import { Button, Image } from 'ant-design-vue';
import AppLinkSelectDialog from '#/views/mall/promotion/components/app-link-input/app-link-select-dialog.vue';
import { AppLinkSelectDialog } from '#/views/mall/promotion/components';
import {
CONTROL_DOT_LIST,

View File

@@ -9,9 +9,11 @@ import { Image } from 'ant-design-vue';
/** 广告魔方 */
defineOptions({ name: 'MagicCube' });
const props = defineProps<{ property: MagicCubeProperty }>();
// 一个方块的大小
const CUBE_SIZE = 93.75;
const CUBE_SIZE = 93.75; // 一个方块的大小
/**
* 计算方块的行数
* 行数用于计算魔方的总体高度,存在以下情况:

View File

@@ -57,6 +57,7 @@ const handleHotAreaSelected = (_: any, index: number) => {
</FormItem>
</template>
</template>
<!-- TODO @芋艿距离不一致需要看看怎么统一 -->
<FormItem label="上圆角" name="borderRadiusTop">
<Slider v-model:value="formData.borderRadiusTop" :max="100" :min="0" />
</FormItem>

View File

@@ -5,6 +5,7 @@ import { Image } from 'ant-design-vue';
/** 宫格导航 */
defineOptions({ name: 'MenuGrid' });
defineProps<{ property: MenuGridProperty }>();
</script>

View File

@@ -9,6 +9,7 @@ import {
AppLinkInput,
ColorInput,
Draggable,
InputWithColor,
} from '#/views/mall/promotion/components';
import ComponentContainerProperty from '../../component-container-property.vue';
@@ -18,7 +19,9 @@ import { EMPTY_MENU_GRID_ITEM_PROPERTY } from './config';
defineOptions({ name: 'MenuGridProperty' });
const props = defineProps<{ modelValue: MenuGridProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>
@@ -32,7 +35,6 @@ const formData = useVModel(props, 'modelValue', emit);
<Radio :value="4">4</Radio>
</RadioGroup>
</FormItem>
<p class="text-base font-bold">菜单设置</p>
<div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
<Draggable
@@ -47,17 +49,17 @@ const formData = useVModel(props, 'modelValue', emit);
width="80px"
:show-description="false"
>
<template #tip> 建议尺寸44 * 44 </template>
<template #tip> 建议尺寸44 * 44</template>
</UploadImg>
</FormItem>
<FormItem label="标题" prop="title">
<ColorInput
<InputWithColor
v-model="element.title"
v-model:color="element.titleColor"
/>
</FormItem>
<FormItem label="副标题" prop="subtitle">
<ColorInput
<InputWithColor
v-model="element.subtitle"
v-model:color="element.subtitleColor"
/>
@@ -70,7 +72,7 @@ const formData = useVModel(props, 'modelValue', emit);
</FormItem>
<template v-if="element.badge.show">
<FormItem label="角标内容" prop="badge.text">
<ColorInput
<InputWithColor
v-model="element.badge.text"
v-model:color="element.badge.textColor"
/>

View File

@@ -7,6 +7,7 @@ import { Image } from 'ant-design-vue';
/** 列表导航 */
defineOptions({ name: 'MenuList' });
defineProps<{ property: MenuListProperty }>();
</script>

View File

@@ -23,7 +23,7 @@ import {
MagicCubeEditor,
} from '#/views/mall/promotion/components';
// 导航栏属性面板
/** 导航栏单元格属性面板 */
defineOptions({ name: 'NavigationBarCellProperty' });
const props = defineProps({
@@ -36,26 +36,21 @@ const props = defineProps({
default: () => [],
},
});
const emit = defineEmits(['update:modelValue']);
const cellList = useVModel(props, 'modelValue', emit);
// 单元格数量小程序6个右侧胶囊按钮占了2个其它平台8个
/**
* 计算单元格数量
* 1. 小程序6 个(因为右侧有胶囊按钮占据 2 个格子的空间)
* 2. 其它平台8 个(全部空间可用)
*/
const cellCount = computed(() => (props.isMp ? 6 : 8));
// 转换为Rect格式的数据
const rectList = computed<Rect[]>(() => {
return cellList.value.map((cell) => ({
left: cell.left,
top: cell.top,
width: cell.width,
height: cell.height,
right: cell.left + cell.width,
bottom: cell.top + cell.height,
}));
});
const selectedHotAreaIndex = ref(0); // 选中的热区
// 选中的热区
const selectedHotAreaIndex = ref(0);
/** 处理热区被选中事件 */
const handleHotAreaSelected = (
cellValue: NavigationBarCellProperty,
index: number,
@@ -71,7 +66,7 @@ const handleHotAreaSelected = (
<template>
<div class="h-40px flex items-center justify-center">
<MagicCubeEditor
v-model="rectList"
v-model="cellList as any"
:cols="cellCount"
:cube-size="38"
:rows="1"

View File

@@ -18,7 +18,7 @@ defineOptions({ name: 'NavigationBar' });
const props = defineProps<{ property: NavigationBarProperty }>();
// 背景
/** 计算背景样式 */
const bgStyle = computed(() => {
const background =
props.property.bgType === 'img' && props.property.bgImg
@@ -26,19 +26,22 @@ const bgStyle = computed(() => {
: props.property.bgColor;
return { background };
});
// 单元格列表
/** 获取当前预览的单元格列表 */
const cellList = computed(() =>
props.property._local?.previewMp
? props.property.mpCells
: props.property.otherCells,
);
// 单元格宽度
/** 计算单元格宽度 */
const cellWidth = computed(() => {
return props.property._local?.previewMp
? (384 - 80 - 86) / 6
: (384 - 90) / 8;
? (375 - 80 - 86) / 6
: (375 - 90) / 8;
});
// 获得单元格样式
/** 获取单元格样式 */
const getCellStyle = (cell: NavigationBarCellProperty) => {
return {
width: `${cell.width * cellWidth.value + (cell.width - 1) * 10}px`,
@@ -46,7 +49,8 @@ const getCellStyle = (cell: NavigationBarCellProperty) => {
position: 'absolute',
} as StyleValue;
};
// 获得搜索框属性
/** 获取搜索框属性配置 */
const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
return {
height: 30,

View File

@@ -17,16 +17,16 @@ import { ColorInput } from '#/views/mall/promotion/components';
import NavigationBarCellProperty from './components/cell-property.vue';
// 导航栏属性面板
/** 导航栏属性面板 */
defineOptions({ name: 'NavigationBarProperty' });
const props = defineProps<{ modelValue: NavigationBarProperty }>();
const emit = defineEmits(['update:modelValue']);
// 表单校验
const rules: Record<string, any> = {
name: [{ required: true, message: '请输入页面名称', trigger: 'blur' }],
};
}; // 表单校验
const formData = useVModel(props, 'modelValue', emit);
if (!formData.value._local) {

View File

@@ -9,18 +9,19 @@ import { Image } from 'ant-design-vue';
/** 弹窗广告 */
defineOptions({ name: 'Popover' });
// 定义属性
defineProps<{ property: PopoverProperty }>();
// 处理选中
const activeIndex = ref(0);
const handleActive = (index: number) => {
const props = defineProps<{ property: PopoverProperty }>();
const activeIndex = ref(0); // 选中 index
/** 处理选中 */
function handleActive(index: number) {
activeIndex.value = index;
};
}
</script>
<template>
<div
v-for="(item, index) in property.list"
v-for="(item, index) in props.property.list"
:key="index"
class="absolute bottom-1/2 right-1/2 h-[454px] w-[292px] rounded border border-gray-300 bg-white p-0.5"
:style="{
@@ -40,5 +41,3 @@ const handleActive = (index: number) => {
<div class="absolute right-1 top-1 text-xs">{{ index + 1 }}</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -7,7 +7,7 @@ import { onMounted, ref, watch } from 'vue';
import { ElScrollbar } from 'element-plus';
import * as CouponTemplateApi from '#/api/mall/promotion/coupon/couponTemplate';
import { getCouponTemplateList } from '#/api/mall/promotion/coupon/couponTemplate';
import {
CouponDiscount,
@@ -32,9 +32,7 @@ watch(
() => props.property.couponIds,
async () => {
if (props.property.couponIds?.length > 0) {
couponList.value = await CouponTemplateApi.getCouponTemplateList(
props.property.couponIds,
);
couponList.value = await getCouponTemplateList(props.property.couponIds);
}
},
{

View File

@@ -39,7 +39,7 @@ function handleToggleFab() {
<ElImage :src="item.imgUrl" fit="contain" class="h-7 w-7">
<template #error>
<div class="flex h-full w-full items-center justify-center">
<IconifyIcon icon="ep:picture" :color="item.textColor" />
<IconifyIcon icon="lucide:image" :color="item.textColor" />
</div>
</template>
</ElImage>
@@ -55,7 +55,7 @@ function handleToggleFab() {
<!-- todo: @owen 使用APP主题色 -->
<ElButton type="primary" size="large" circle @click="handleToggleFab">
<IconifyIcon
icon="ep:plus"
icon="lucide:plus"
class="transition-transform duration-300"
:class="expanded ? 'rotate-[135deg]' : 'rotate-0'"
/>

View File

@@ -19,7 +19,6 @@ function handleActive(index: number) {
activeIndex.value = index;
}
</script>
<template>
<div
v-for="(item, index) in props.property.list"
@@ -35,7 +34,7 @@ function handleActive(index: number) {
<ElImage :src="item.imgUrl" fit="contain" class="h-full w-full">
<template #error>
<div class="flex h-full w-full items-center justify-center">
<IconifyIcon icon="ep:picture" />
<IconifyIcon icon="lucide:image" />
</div>
</template>
</ElImage>