fix: 冲突合并

This commit is contained in:
hw
2025-11-03 11:22:11 +08:00
80 changed files with 1168 additions and 1095 deletions

View File

@@ -10,8 +10,7 @@ import { get, getNestedValue, isFunction } from '@vben/utils';
import { ElDescriptions, ElDescriptionsItem } from 'element-plus';
const props = {
// TODO @星语bordered 不生效;之前好像是 border
bordered: { default: true, type: Boolean },
border: { default: true, type: Boolean },
column: {
default: () => {
return { lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4 };

View File

@@ -32,8 +32,6 @@ export interface DescriptionProps extends ElDescriptionProps {
schema: DescriptionItemSchema[];
// 数据
data: Recordable<any>;
// 是否包含边框
bordered?: boolean;
}
export interface DescInstance {

View File

@@ -245,7 +245,7 @@ export function useDetailSchema(): DescriptionItemSchema[] {
render: (val, data) => {
if (val === 0) {
return '正常';
} else if (data && data.resultCode > 0) {
} else if (data && data.resultMsg) {
return `失败 | ${val} | ${data.resultMsg}`;
}
return '';

View File

@@ -11,8 +11,8 @@ import { useDetailSchema } from '../data';
const formData = ref<InfraApiAccessLogApi.ApiAccessLog>();
// TODO @xingyuantd 和 el 这 2 个组件在这个模块的 detail.vue 不一样,看看是不是统一?还是就是区分的哈?
const [Descriptions] = useDescription({
border: true,
column: 1,
labelWidth: 110,
schema: useDetailSchema(),

View File

@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
const formData = ref<InfraApiErrorLogApi.ApiErrorLog>();
const [Descriptions] = useDescription({
border: true,
column: 1,
labelWidth: 110,
schema: useDetailSchema(),

View File

@@ -14,6 +14,7 @@ const formData = ref<InfraJobApi.Job>(); // 任务详情
const nextTimes = ref<Date[]>([]); // 下一次执行时间
const [Descriptions] = useDescription({
border: true,
column: 1,
labelWidth: 140,
schema: useDetailSchema(),

View File

@@ -8,7 +8,7 @@ defineProps<{
}>();
const [Descriptions] = useDescription({
bordered: false,
border: false,
column: 6,
schema: [
{

View File

@@ -3,32 +3,33 @@ import { computed, onMounted, ref } from 'vue';
import { handleTree } from '@vben/utils';
import { ElTreeSelect } from 'element-plus';
import { getCategoryList } from '#/api/mall/product/category';
/** 商品分类选择组件 */
defineOptions({ name: 'ProductCategorySelect' });
const props = defineProps({
// ID
modelValue: {
type: [Number, Array<Number>],
default: undefined,
},
//
}, // ID
multiple: {
type: Boolean,
default: false,
},
//
}, //
parentId: {
type: Number,
default: undefined,
},
}, //
});
/** 分类选择 */
const emit = defineEmits(['update:modelValue']);
const categoryList = ref<any[]>([]); //
/** 选中的分类 ID */
const selectCategoryId = computed({
get: () => {
@@ -40,7 +41,6 @@ const selectCategoryId = computed({
});
/** 初始化 */
const categoryList = ref<any[]>([]); //
onMounted(async () => {
const data = await getCategoryList({
parentId: props.parentId,
@@ -49,20 +49,19 @@ onMounted(async () => {
});
</script>
<template>
<el-tree-select
<ElTreeSelect
v-model="selectCategoryId"
:data="categoryList"
node-key="id"
:props="{
children: 'children',
label: 'name',
value: 'id',
isLeaf: 'leaf',
emitPath: false,
}"
:multiple="multiple"
:show-checkbox="multiple"
class="w-1/1"
node-key="id"
check-strictly
default-expand-all
class="w-full"
placeholder="请选择商品分类"
/>
</template>

View File

@@ -0,0 +1 @@
export { default as ProductCategorySelect } from './category-select.vue';

View File

@@ -1,14 +1,15 @@
<script lang="ts" setup>
// TODO @AI改成 El 标签风格,而不是 el-
// TODO @AI一些 modal 是否使用 Modal 组件,而不是 el-modal
import { ref, watch } from 'vue';
import AppLinkSelectDialog from './app-link-select-dialog.vue';
import { ElButton, ElInput } from 'element-plus';
import AppLinkSelectDialog from './select-dialog.vue';
/** APP 链接输入框 */
defineOptions({ name: 'AppLinkInput' });
// 定义属性
/** 定义属性 */
const props = defineProps({
modelValue: {
type: String,
@@ -23,8 +24,16 @@ const emit = defineEmits<{
const dialogRef = ref(); // 选择对话框
const appLink = ref(''); // 当前的链接
const handleOpenDialog = () => dialogRef.value?.open(appLink.value); // 处理打开对话框
const handleLinkSelected = (link: string) => (appLink.value = link); // 处理 APP 链接选中
/** 处理打开对话框 */
function handleOpenDialog() {
return dialogRef.value?.open(appLink.value);
}
/** 处理 APP 链接选中 */
function handleLinkSelected(link: string) {
appLink.value = link;
}
watch(
() => props.modelValue,
@@ -38,11 +47,11 @@ watch(
);
</script>
<template>
<el-input v-model="appLink" placeholder="输入或选择链接">
<ElInput v-model="appLink" placeholder="输入或选择链接">
<template #append>
<el-button @click="handleOpenDialog">选择</el-button>
<ElButton @click="handleOpenDialog">选择</ElButton>
</template>
</el-input>
</ElInput>
<AppLinkSelectDialog ref="dialogRef" @change="handleLinkSelected" />
</template>

View File

@@ -5,11 +5,18 @@ import type { AppLink } from './data';
import { nextTick, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { getUrlNumberValue } from '@vben/utils';
import { ElScrollbar } from 'element-plus';
import {
ElButton,
ElForm,
ElFormItem,
ElScrollbar,
ElTooltip,
} from 'element-plus';
import ProductCategorySelect from '#/views/mall/product/category/components/product-category-select.vue';
import { ProductCategorySelect } from '#/views/mall/product/category/components/';
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM } from './data';
@@ -32,18 +39,31 @@ const groupBtnRefs = ref<ButtonInstance[]>([]); // 分组引用列表
const detailSelectDialog = ref<{
id?: number;
type?: APP_LINK_TYPE_ENUM;
visible: boolean;
}>({
visible: false,
id: undefined,
type: undefined,
}); //
const [Modal, modalApi] = useVbenModal({
onConfirm() {
emit('change', activeAppLink.value.path);
emit('appLinkChange', activeAppLink.value);
modalApi.close();
},
});
const [DetailSelectModal, detailSelectModalApi] = useVbenModal({
onConfirm() {
detailSelectModalApi.close();
},
});
defineExpose({ open });
/** 打开弹窗 */
const dialogVisible = ref(false);
const open = (link: string) => {
async function open(link: string) {
activeAppLink.value.path = link;
dialogVisible.value = true;
modalApi.open();
//
const group = APP_LINK_GROUP_LIST.find((group) =>
group.links.some((linkItem) => {
@@ -56,19 +76,18 @@ const open = (link: string) => {
);
if (group) {
// 使 nextTick Dom
nextTick(() => handleGroupSelected(group.name));
await nextTick();
handleGroupSelected(group.name);
}
};
defineExpose({ open });
}
/** 处理 APP 链接选中 */
const handleAppLinkSelected = (appLink: AppLink) => {
function handleAppLinkSelected(appLink: AppLink) {
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
activeAppLink.value = appLink;
}
switch (appLink.type) {
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST: {
detailSelectDialog.value.visible = true;
detailSelectDialog.value.type = appLink.type;
//
detailSelectDialog.value.id =
@@ -76,22 +95,18 @@ const handleAppLinkSelected = (appLink: AppLink) => {
'id',
`http://127.0.0.1${activeAppLink.value.path}`,
) || undefined;
detailSelectModalApi.open();
break;
}
default: {
break;
}
}
};
function handleSubmit() {
dialogVisible.value = false;
emit('change', activeAppLink.value.path);
emit('appLinkChange', activeAppLink.value);
}
/**
* 处理右侧链接列表滚动
*
* @param {object} param0 滚动事件参数
* @param {number} param0.scrollTop 滚动条的位置
*/
@@ -134,66 +149,70 @@ function scrollToGroupBtn(group: string) {
/** 是否为相同的链接(不比较参数,只比较链接) */
function isSameLink(link1: string, link2: string) {
return link2 ? link1.split('?')[0] === link2.split('?')[0] : false;
return link2 ? link1?.split('?')[0] === link2.split('?')[0] : false;
}
/** 处理详情选择 */
function handleProductCategorySelected(id: number) {
// TODO @AIactiveAppLink
// activeAppLink
const url = new URL(activeAppLink.value.path, 'http://127.0.0.1');
// id
url.searchParams.set('id', `${id}`);
//
activeAppLink.value.path = `${url.pathname}${url.search}`;
//
detailSelectDialog.value.visible = false;
// id
// id
detailSelectModalApi.close();
detailSelectDialog.value.id = undefined;
}
</script>
<template>
<el-dialog v-model="dialogVisible" title="选择链接" width="65%">
<Modal title="选择链接" class="w-[65%]">
<div class="flex h-[500px] gap-2">
<!-- 左侧分组列表 -->
<ElScrollbar
wrap-class="h-full"
ref="groupScrollbar"
view-class="flex flex-col"
>
<el-button
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
:key="groupIndex"
class="ml-0 mr-4 w-[90px] justify-start"
:class="[{ active: activeGroup === group.name }]"
ref="groupBtnRefs"
:text="activeGroup !== group.name"
:type="activeGroup === group.name ? 'primary' : 'default'"
@click="handleGroupSelected(group.name)"
<div class="flex flex-col">
<!-- 左侧分组列表 -->
<ElScrollbar
wrap-class="h-full"
ref="groupScrollbar"
view-class="flex flex-col"
class="border-r border-gray-200 pr-2"
>
{{ group.name }}
</el-button>
</ElScrollbar>
<ElButton
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
:key="groupIndex"
class="!ml-0 mb-1 mr-4 !justify-start"
:class="[{ active: activeGroup === group.name }]"
ref="groupBtnRefs"
:text="activeGroup !== group.name"
:type="activeGroup === group.name ? 'primary' : 'default'"
@click="handleGroupSelected(group.name)"
>
{{ group.name }}
</ElButton>
</ElScrollbar>
</div>
<!-- 右侧链接列表 -->
<ElScrollbar
class="h-full flex-1"
class="h-full flex-1 pl-2"
@scroll="handleScroll"
ref="linkScrollbar"
>
<div
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
:key="groupIndex"
class="mb-4 border-b border-gray-100 pb-4 last:mb-0 last:border-b-0"
>
<!-- 分组标题 -->
<div class="font-bold" ref="groupTitleRefs">{{ group.name }}</div>
<div class="mb-2 font-bold" ref="groupTitleRefs">
{{ group.name }}
</div>
<!-- 链接列表 -->
<el-tooltip
<ElTooltip
v-for="(appLink, appLinkIndex) in group.links"
:key="appLinkIndex"
:content="appLink.path"
placement="bottom"
:show-after="300"
>
<el-button
<ElButton
class="mb-2 ml-0 mr-2"
:type="
isSameLink(appLink.path, activeAppLink.path)
@@ -203,20 +222,16 @@ function handleProductCategorySelected(id: number) {
@click="handleAppLinkSelected(appLink)"
>
{{ appLink.name }}
</el-button>
</el-tooltip>
</ElButton>
</ElTooltip>
</div>
</ElScrollbar>
</div>
<!-- 底部对话框操作按钮 -->
<template #footer>
<el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-dialog>
<el-dialog v-model="detailSelectDialog.visible" title="" width="50%">
<el-form class="min-h-[200px]">
<el-form-item
</Modal>
<DetailSelectModal title="选择分类" class="w-[65%]">
<ElForm class="min-h-[200px]">
<ElFormItem
label="选择分类"
v-if="
detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
@@ -227,12 +242,7 @@ function handleProductCategorySelected(id: number) {
:parent-id="0"
@update:model-value="handleProductCategorySelected"
/>
</el-form-item>
</el-form>
</el-dialog>
</ElFormItem>
</ElForm>
</DetailSelectModal>
</template>
<style lang="scss" scoped>
:deep(.el-button + .el-button) {
margin-left: 0 !important;
}
</style>

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
// TODO @AI改成 El 风格而不是iel- 风格;
import { computed } from 'vue';
import { ElColorPicker, ElInput } from 'element-plus';
import { PREDEFINE_COLORS } from '@vben/constants';
/** 颜色输入框 */
@@ -27,11 +28,11 @@ const color = computed({
</script>
<template>
<el-input v-model="color">
<ElInput v-model="color">
<template #prepend>
<el-color-picker v-model="color" :predefine="PREDEFINE_COLORS" />
<ElColorPicker v-model="color" :predefine="PREDEFINE_COLORS" />
</template>
</el-input>
</ElInput>
</template>
<style scoped lang="scss">

View File

@@ -2,32 +2,23 @@ import type { ComponentStyle, DiyComponent } from '../../../util';
/** 轮播图属性 */
export interface CarouselProperty {
// 类型:默认 | 卡片
type: 'card' | 'default';
// 指示器样式:点 | 数字
indicator: 'dot' | 'number';
// 是否自动播放
autoplay: boolean;
// 播放间隔
interval: number;
// 轮播内容
items: CarouselItemProperty[];
// 组件样式
style: ComponentStyle;
}
// 轮播内容属性
export interface CarouselItemProperty {
// 类型:图片 | 视频
type: 'img' | 'video';
// 图片链接
imgUrl: string;
// 视频链接
videoUrl: string;
// 跳转链接
url: string;
type: 'card' | 'default'; // 类型:默认 | 卡片
indicator: 'dot' | 'number'; // 指示器样式:点 | 数字
autoplay: boolean; // 是否自动播放
interval: number; // 播放间隔
items: CarouselItemProperty[]; // 轮播内容
style: ComponentStyle; // 组件样式
}
// 定义组件
/** 轮播内容属性 */
export interface CarouselItemProperty {
type: 'img' | 'video'; // 类型:图片 | 视频
imgUrl: string; // 图片链接
videoUrl: string; // 视频链接
url: string; // 跳转链接
}
/** 定义组件 */
export const component = {
id: 'Carousel',
name: '轮播图',

View File

@@ -12,7 +12,9 @@ defineOptions({ name: 'Carousel' });
defineProps<{ property: CarouselProperty }>();
const currentIndex = ref(0);
const currentIndex = ref(0); //
/** 处理索引变化 */
const handleIndexChange = (index: number) => {
currentIndex.value = index + 1;
};
@@ -46,5 +48,3 @@ const handleIndexChange = (index: number) => {
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -26,7 +26,9 @@ import ComponentContainerProperty from '../../component-container-property.vue';
defineOptions({ name: 'CarouselProperty' });
const props = defineProps<{ modelValue: CarouselProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
</script>

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,29 +1,20 @@
import type { ComponentStyle, DiyComponent } from '../../../util';
/** 商品卡片属性 */
/** 优惠劵卡片属性 */
export interface CouponCardProperty {
// 列数
columns: number;
// 背景图
bgImg: string;
// 文字颜色
textColor: string;
// 按钮样式
columns: number; // 列数
bgImg: string; // 背景图
textColor: string; // 文字颜色
button: {
// 背景颜色
bgColor: string;
// 颜色
color: string;
};
// 间距
space: number;
// 优惠券编号列表
couponIds: number[];
// 组件样式
style: ComponentStyle;
bgColor: string; // 背景颜色
color: string; // 文字颜色
}; // 按钮样式
space: number; // 间距
couponIds: number[]; // 优惠券编号列表
style: ComponentStyle; // 组件样式
}
// 定义组件
/** 定义组件 */
export const component = {
id: 'CouponCard',
name: '优惠券',

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 / 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 / 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

@@ -15,12 +15,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 () => {
@@ -36,15 +43,7 @@ watch(
},
);
// 手机宽度
const phoneWidth = ref(375);
// 容器
const containerRef = ref();
// 滚动条宽度
const scrollbarWidth = ref('100%');
// 优惠券的宽度
const couponWidth = ref(375);
// 计算布局参数
/** 计算布局参数 */
watch(
() => [props.property, phoneWidth, couponList.value.length],
() => {
@@ -60,8 +59,9 @@ watch(
},
{ immediate: true, deep: true },
);
/** 初始化 */
onMounted(() => {
// 提取手机宽度
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375;
});
</script>
@@ -86,17 +86,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">
@@ -111,17 +108,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>
@@ -139,11 +133,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"
@@ -159,4 +151,3 @@ onMounted(() => {
</div>
</ElScrollbar>
</template>
<style scoped lang="scss"></style>

View File

@@ -5,6 +5,7 @@ import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTe
import { ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import {
CouponTemplateTakeTypeEnum,
PromotionDiscountTypeEnum,
@@ -26,29 +27,40 @@ import {
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';
// TODO: 添加组件
// import CouponSelect from '#/views/mall/promotion/coupon/components/coupon-select.vue';
// 优惠券卡片属性面板
/** 优惠券卡片属性面板 */
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 couponSelectDialog = ref();
// 添加优惠券
const couponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]); // 已选择的优惠券列表
const [CouponSelectModal, couponSelectModalApi] = useVbenModal({
connectedComponent: CouponSelect,
destroyOnClose: true,
});
/** 添加优惠劵 */
const handleAddCoupon = () => {
couponSelectDialog.value.open();
};
const handleCouponSelect = () => {
formData.value.couponIds = couponList.value.map((coupon) => coupon.id);
couponSelectModalApi.open();
};
/** 处理优惠劵选择 */
const handleCouponSelect = (
selectedCoupons: MallCouponTemplateApi.CouponTemplate[],
) => {
couponList.value = selectedCoupons;
formData.value.couponIds = selectedCoupons.map((coupon) => coupon.id);
};
/** 监听优惠券 ID 变化,加载优惠券列表 */
watch(
() => formData.value.couponIds,
async () => {
@@ -151,13 +163,10 @@ watch(
</ElCard>
</ElForm>
</ComponentContainerProperty>
<!-- 优惠券选择 -->
<CouponSelect
ref="couponSelectDialog"
v-model:multiple-selection="couponList"
<CouponSelectModal
:take-type="CouponTemplateTakeTypeEnum.USER.type"
@change="handleCouponSelect"
@success="handleCouponSelect"
/>
</template>
<style scoped lang="scss"></style>

View File

@@ -2,19 +2,14 @@ import type { DiyComponent } from '../../../util';
/** 分割线属性 */
export interface DividerProperty {
// 高度
height: number;
// 线宽
lineWidth: number;
// 边距类型
paddingType: 'horizontal' | 'none';
// 颜色
lineColor: string;
// 类型
borderType: 'dashed' | 'dotted' | 'none' | 'solid';
height: number; // 高度
lineWidth: number; // 线宽
paddingType: 'horizontal' | 'none'; // 边距类型
lineColor: string; // 颜色
borderType: 'dashed' | 'dotted' | 'none' | 'solid'; // 类型
}
// 定义组件
/** 定义组件 */
export const component = {
id: 'Divider',
name: '分割线',

View File

@@ -25,5 +25,3 @@ defineProps<{ property: DividerProperty }>();
></div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -13,15 +13,15 @@ import {
ElTooltip,
} from 'element-plus';
import { InputWithColor as ColorInput } from '#/views/mall/promotion/components';
import { ColorInput } from '#/views/mall/promotion/components';
//
/** 导航栏属性面板 */
defineOptions({ name: 'DividerProperty' });
const props = defineProps<{ modelValue: DividerProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
// 线
const props = defineProps<{ modelValue: DividerProperty }>();
const emit = defineEmits(['update:modelValue']);
const BORDER_TYPES = [
{
icon: 'vaadin:line-h',
@@ -43,7 +43,8 @@ const BORDER_TYPES = [
text: '无',
type: 'none',
},
];
]; // 线
const formData = useVModel(props, 'modelValue', emit);
</script>
<template>
@@ -96,11 +97,8 @@ const BORDER_TYPES = [
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="颜色">
<!-- 分割线颜色 -->
<ColorInput v-model="formData.lineColor" />
</ElFormItem>
</template>
</ElForm>
</template>
<style scoped lang="scss"></style>

View File

@@ -1,28 +1,21 @@
import type { DiyComponent } from '../../../util';
// 悬浮按钮属性
/** 悬浮按钮属性 */
export interface FloatingActionButtonProperty {
// 展开方向
direction: 'horizontal' | 'vertical';
// 是否显示文字
showText: boolean;
// 按钮列表
list: FloatingActionButtonItemProperty[];
direction: 'horizontal' | 'vertical'; // 展开方向
showText: boolean; // 是否显示文字
list: FloatingActionButtonItemProperty[]; // 按钮列表
}
// 悬浮按钮项属性
/** 悬浮按钮项属性 */
export interface FloatingActionButtonItemProperty {
// 图片地址
imgUrl: string;
// 跳转连接
url: string;
// 文字
text: string;
// 文字颜色
textColor: string;
imgUrl: string; // 图片地址
url: string; // 跳转连接
text: string; // 文字
textColor: string; // 文字颜色
}
// 定义组件
/** 定义组件 */
export const component = {
id: 'FloatingActionButton',
name: '悬浮按钮',

View File

@@ -5,23 +5,20 @@ import { ref } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { ElImage, ElMessage } from 'element-plus';
import { ElButton, ElImage } from 'element-plus';
/** 悬浮按钮 */
defineOptions({ name: 'FloatingActionButton' });
// 定义属性
/** 定义属性 */
defineProps<{ property: FloatingActionButtonProperty }>();
// 是否展开
const expanded = ref(false);
// 处理展开/折叠
const handleToggleFab = () => {
expanded.value = !expanded.value;
};
const expanded = ref(false); // 是否展开
const handleActive = (index: number) => {
ElMessage.success(`点击了${index}`);
};
/** 处理展开/折叠 */
function handleToggleFab() {
expanded.value = !expanded.value;
}
</script>
<template>
<div
@@ -38,7 +35,6 @@ const handleActive = (index: number) => {
v-for="(item, index) in property.list"
:key="index"
class="flex flex-col items-center"
@click="handleActive(index)"
>
<ElImage :src="item.imgUrl" fit="contain" class="h-7 w-7">
<template #error>
@@ -57,13 +53,13 @@ const handleActive = (index: number) => {
</div>
</template>
<!-- todo: @owen 使用APP主题色 -->
<el-button type="primary" size="large" circle @click="handleToggleFab">
<ElButton type="primary" size="large" circle @click="handleToggleFab">
<IconifyIcon
icon="ep:plus"
class="fab-icon"
:class="[{ active: expanded }]"
/>
</el-button>
</ElButton>
</div>
<!-- 模态背景展开时显示点击后折叠 -->
<div v-if="expanded" class="modal-bg" @click="handleToggleFab"></div>

View File

@@ -18,11 +18,13 @@ import {
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>
@@ -64,5 +66,3 @@ const formData = useVModel(props, 'modelValue', emit);
</ElCard>
</ElForm>
</template>
<style scoped lang="scss"></style>

View File

@@ -2,10 +2,9 @@ import type { StyleValue } from 'vue';
import type { HotZoneItemProperty } from '../../config';
// 热区的最小宽高
export const HOT_ZONE_MIN_SIZE = 100;
export const HOT_ZONE_MIN_SIZE = 100; // 热区的最小宽高
// 控制的类型
/** 控制的类型 */
export enum CONTROL_TYPE_ENUM {
LEFT,
TOP,
@@ -13,14 +12,14 @@ export enum CONTROL_TYPE_ENUM {
HEIGHT,
}
// 定义热区的控制点
/** 定义热区的控制点 */
export interface ControlDot {
position: string;
types: CONTROL_TYPE_ENUM[];
style: StyleValue;
}
// 热区的8个控制点
/** 热区的 8 个控制点 */
export const CONTROL_DOT_LIST = [
{
position: '左上角',
@@ -98,10 +97,10 @@ export const CONTROL_DOT_LIST = [
] as ControlDot[];
// region 热区的缩放
// 热区的缩放比例
export const HOT_ZONE_SCALE_RATE = 2;
// 缩小:缩回适合手机屏幕的大小
export const zoomOut = (list?: HotZoneItemProperty[]) => {
export const HOT_ZONE_SCALE_RATE = 2; // 热区的缩放比例
/** 缩小:缩回适合手机屏幕的大小 */
export function zoomOut(list?: HotZoneItemProperty[]) {
return (
list?.map((hotZone) => ({
...hotZone,
@@ -111,9 +110,10 @@ export const zoomOut = (list?: HotZoneItemProperty[]) => {
height: (hotZone.height /= HOT_ZONE_SCALE_RATE),
})) || []
);
};
// 放大:作用是为了方便在电脑屏幕上编辑
export const zoomIn = (list?: HotZoneItemProperty[]) => {
}
/** 放大:作用是为了方便在电脑屏幕上编辑 */
export function zoomIn(list?: HotZoneItemProperty[]) {
return (
list?.map((hotZone) => ({
...hotZone,
@@ -123,7 +123,8 @@ export const zoomIn = (list?: HotZoneItemProperty[]) => {
height: (hotZone.height *= HOT_ZONE_SCALE_RATE),
})) || []
);
};
}
// endregion
/**

View File

@@ -6,9 +6,12 @@ import type { AppLink } from '#/views/mall/promotion/components/app-link-input/d
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { ElButton, ElDialog, ElImage } from 'element-plus';
import { ElButton, ElImage } from 'element-plus';
import { AppLinkSelectDialog } from '#/views/mall/promotion/components';
import {
CONTROL_DOT_LIST,
@@ -22,7 +25,7 @@ import {
/** 热区编辑对话框 */
defineOptions({ name: 'HotZoneEditDialog' });
// 定义属性
/** 定义属性 */
const props = defineProps({
modelValue: {
type: Array<HotZoneItemProperty>,
@@ -33,51 +36,60 @@ const props = defineProps({
default: '',
},
});
const emit = defineEmits(['update:modelValue']);
const formData = ref<HotZoneItemProperty[]>([]);
// 弹窗的是否显示
const dialogVisible = ref(false);
// 打开弹窗
const open = () => {
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);
dialogVisible.value = true;
};
// 提供 open 方法,用于打开弹窗
defineExpose({ open });
modalApi.open();
}
// 热区容器
const container = ref<HTMLDivElement>();
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
// 增加热区
const handleAdd = () => {
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);
};
// 删除热区
const handleRemove = (hotZone: HotZoneItemProperty) => {
formData.value = formData.value.filter((item) => item !== hotZone);
};
}
// 移动热区
const handleMove = (item: HotZoneItemProperty, e: MouseEvent) => {
/** 删除热区 */
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);
});
};
}
// 调整热区大小、位置
const handleResize = (
/** 调整热区大小、位置 */
function handleResize(
item: HotZoneItemProperty,
ctrlDot: ControlDot,
e: MouseEvent,
) => {
) {
useDraggable(item, e, (left, top, width, height, moveWidth, moveHeight) => {
ctrlDot.types.forEach((type) => {
switch (type) {
@@ -112,23 +124,25 @@ const handleResize = (
}
});
});
};
}
// 设置X轴坐标
const setLeft = (item: HotZoneItemProperty, left: number) => {
/** 设置 X 轴坐标 */
function setLeft(item: HotZoneItemProperty, left: number) {
// 不能超出容器
if (left >= 0 && left <= container.value!.offsetWidth - item.width) {
item.left = left;
}
};
// 设置Y轴坐标
const setTop = (item: HotZoneItemProperty, top: number) => {
}
/** 设置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 (
@@ -138,7 +152,8 @@ const setWidth = (item: HotZoneItemProperty, width: number) => {
item.width = width;
}
};
// 设置高度
/** 设置高度 */
const setHeight = (item: HotZoneItemProperty, height: number) => {
// 不能小于最小高度 && 不能超出容器底部
if (
@@ -149,39 +164,27 @@ const setHeight = (item: HotZoneItemProperty, height: number) => {
}
};
// 处理对话框关闭
const handleSubmit = () => {
// 会自动触发handleClose
dialogVisible.value = false;
};
// 处理对话框关闭
const handleClose = () => {
// 缩小
const list = zoomOut(formData.value);
emit('update:modelValue', list);
};
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;
if (!appLink || !activeHotZone.value) {
return;
}
activeHotZone.value.name = appLink.name;
activeHotZone.value.url = appLink.path;
};
</script>
<template>
<ElDialog
v-model="dialogVisible"
title="设置热区"
width="780"
@close="handleClose"
>
<Modal title="设置热区" class="w-[780px]">
<div ref="container" class="w-750px relative h-full">
<ElImage
:src="imgUrl"
@@ -200,9 +203,9 @@ const handleAppLinkChange = (appLink: AppLink) => {
@mousedown="handleMove(item, $event)"
@dblclick="handleShowAppLinkDialog(item)"
>
<span class="pointer-events-none select-none">{{
item.name || '双击选择链接'
}}</span>
<span class="pointer-events-none select-none">
{{ item.name || '双击选择链接' }}
</span>
<IconifyIcon
icon="ep:close"
class="delete"
@@ -210,7 +213,7 @@ const handleAppLinkChange = (appLink: AppLink) => {
@click="handleRemove(item)"
/>
<!-- 8个控制点 -->
<!-- 8 个控制点 -->
<span
class="ctrl-dot"
v-for="(dot, dotIndex) in CONTROL_DOT_LIST"
@@ -220,17 +223,14 @@ const handleAppLinkChange = (appLink: AppLink) => {
></span>
</div>
</div>
<template #footer>
<template #prepend-footer>
<ElButton @click="handleAdd" type="primary" plain>
<IconifyIcon icon="ep:plus" class="mr-5px" />
添加热区
</ElButton>
<ElButton @click="handleSubmit" type="primary" plain>
<IconifyIcon icon="ep:check" class="mr-5px" />
确定
</ElButton>
</template>
</ElDialog>
</Modal>
<AppLinkSelectDialog
ref="appLinkDialogRef"
@app-link-change="handleAppLinkChange"

View File

@@ -2,31 +2,22 @@ import type { ComponentStyle, DiyComponent } from '../../../util';
/** 热区属性 */
export interface HotZoneProperty {
// 图片地址
imgUrl: string;
// 导航菜单列表
list: HotZoneItemProperty[];
// 组件样式
style: ComponentStyle;
imgUrl: string; // 图片地址
list: HotZoneItemProperty[]; // 导航菜单列表
style: ComponentStyle; // 组件样式
}
/** 热区项目属性 */
export interface HotZoneItemProperty {
// 链接的名称
name: string;
// 链接
url: string;
//
width: number;
// 高
height: number;
// 上
top: number;
// 左
left: number;
name: string; // 链接的名称
url: string; // 链接
width: number; //
height: number; // 高
top: number; //
left: number; // 左
}
// 定义组件
/** 定义组件 */
export const component = {
id: 'HotZone',
name: '热区',

View File

@@ -5,6 +5,7 @@ import { ElImage } from 'element-plus';
/** 热区 */
defineOptions({ name: 'HotZone' });
const props = defineProps<{ property: HotZoneProperty }>();
</script>

View File

@@ -15,12 +15,14 @@ import HotZoneEditDialog from './components/hot-zone-edit-dialog/index.vue';
defineOptions({ name: 'HotZoneProperty' });
const props = defineProps<{ modelValue: HotZoneProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
// 热区编辑对话框
const editDialogRef = ref();
// 打开热区编辑对话框
const editDialogRef = ref(); // 热区编辑对话框
/** 打开热区编辑对话框 */
const handleOpenEditDialog = () => {
editDialogRef.value.open();
};
@@ -49,6 +51,7 @@ const handleOpenEditDialog = () => {
设置热区
</ElButton>
</ComponentContainerProperty>
<!-- 热区编辑对话框 -->
<HotZoneEditDialog
ref="editDialogRef"

View File

@@ -2,15 +2,12 @@ import type { ComponentStyle, DiyComponent } from '../../../util';
/** 图片展示属性 */
export interface ImageBarProperty {
// 图片链接
imgUrl: string;
// 跳转链接
url: string;
// 组件样式
style: ComponentStyle;
imgUrl: string; // 图片链接
url: string; // 跳转链接
style: ComponentStyle; // 组件样式
}
// 定义组件
/** 定义组件 */
export const component = {
id: 'ImageBar',
name: '图片展示',

View File

@@ -11,21 +11,11 @@ defineOptions({ name: 'ImageBar' });
defineProps<{ property: ImageBarProperty }>();
</script>
<template>
<!-- 无图片 -->
<div
class="flex h-12 items-center justify-center bg-gray-300"
v-if="!property.imgUrl"
>
<IconifyIcon icon="ep:picture" class="text-3xl text-gray-600" />
</div>
<ElImage class="min-h-8" v-else :src="property.imgUrl" />
<ElImage v-else class="block w-full h-full" :src="property.imgUrl" />
</template>
<style scoped lang="scss">
/* 图片 */
img {
display: block;
width: 100%;
height: 100%;
}
</style>

View File

@@ -4,12 +4,12 @@ import type { ImageBarProperty } from './config';
import { useVModel } from '@vueuse/core';
import { ElForm, ElFormItem } from 'element-plus';
import UploadImg from '#/components/upload/image-upload.vue';
import { ImageUpload } from '#/components/upload/';
import { AppLinkInput } from '#/views/mall/promotion/components';
import ComponentContainerProperty from '../../component-container-property.vue';
// 图片展示属性面板
/** 图片展示属性面板 */
defineOptions({ name: 'ImageBarProperty' });
const props = defineProps<{ modelValue: ImageBarProperty }>();
@@ -21,7 +21,7 @@ const formData = useVModel(props, 'modelValue', emit);
<ComponentContainerProperty v-model="formData.style">
<ElForm label-width="80px" :model="formData">
<ElFormItem label="上传图片" prop="imgUrl">
<UploadImg
<ImageUpload
v-model="formData.imgUrl"
draggable="false"
height="80px"
@@ -30,7 +30,7 @@ const formData = useVModel(props, 'modelValue', emit);
:show-description="false"
>
<template #tip> 建议宽度750 </template>
</UploadImg>
</ImageUpload>
</ElFormItem>
<ElFormItem label="链接" prop="url">
<AppLinkInput v-model="formData.url" />
@@ -38,5 +38,3 @@ const formData = useVModel(props, 'modelValue', emit);
</ElForm>
</ComponentContainerProperty>
</template>
<style scoped lang="scss"></style>

View File

@@ -2,39 +2,26 @@ import type { ComponentStyle, DiyComponent } from '../../../util';
/** 广告魔方属性 */
export interface MagicCubeProperty {
// 上圆角
borderRadiusTop: number;
// 下圆角
borderRadiusBottom: number;
// 间隔
space: number;
// 导航菜单列表
list: MagicCubeItemProperty[];
// 组件样式
style: ComponentStyle;
borderRadiusTop: number; // 上圆角
borderRadiusBottom: number; // 下圆角
space: number; // 间隔
list: MagicCubeItemProperty[]; // 导航菜单列表
style: ComponentStyle; // 组件样式
}
/** 广告魔方项目属性 */
export interface MagicCubeItemProperty {
// 图标链接
imgUrl: string;
// 链接
url: string;
//
width: number;
//
height: number;
// 上
top: number;
// 左
left: number;
// 右
right: number;
// 下
bottom: number;
imgUrl: string; // 图标链接
url: string; // 链接
width: number; //
height: number; // 高
top: number; //
left: number; // 左
right: number; //
bottom: number; // 下
}
// 定义组件
/** 定义组件 */
export const component = {
id: 'MagicCube',
name: '广告魔方',

View File

@@ -9,9 +9,11 @@ import { ElImage } from 'element-plus';
/** 广告魔方 */
defineOptions({ name: 'MagicCube' });
const props = defineProps<{ property: MagicCubeProperty }>();
// 一个方块的大小
const CUBE_SIZE = 93.75;
const CUBE_SIZE = 93.75; // 一个方块的大小
/**
* 计算方块的行数
* 行数用于计算魔方的总体高度,存在以下情况:
@@ -80,5 +82,3 @@ const rowCount = computed(() => {
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -18,11 +18,14 @@ import ComponentContainerProperty from '../../component-container-property.vue';
defineOptions({ name: 'MagicCubeProperty' });
const props = defineProps<{ modelValue: MagicCubeProperty }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
// 选中的热区
const selectedHotAreaIndex = ref(-1);
const selectedHotAreaIndex = ref(-1); // 选中的热区
/** 处理热区被选中事件 */
const handleHotAreaSelected = (_: any, index: number) => {
selectedHotAreaIndex.value = index;
};
@@ -30,7 +33,6 @@ const handleHotAreaSelected = (_: any, index: number) => {
<template>
<ComponentContainerProperty v-model="formData.style">
<!-- 表单 -->
<ElForm label-width="80px" :model="formData" class="mt-2">
<ElText tag="p"> 魔方设置 </ElText>
<ElText type="info" size="small"> 每格尺寸187 * 187 </ElText>
@@ -89,5 +91,3 @@ const handleHotAreaSelected = (_: any, index: number) => {
</ElForm>
</ComponentContainerProperty>
</template>
<style scoped lang="scss"></style>

View File

@@ -541,7 +541,7 @@ $phone-width: 375px;
}
/* 属性面板分组 */
:deep(.property-group) {
.property-group {
margin: 0 -20px;
&.el-card {

View File

@@ -1,4 +1,5 @@
export { default as AppLinkInput } from './app-link-input/index.vue';
export { default as AppLinkSelectDialog } from './app-link-input/select-dialog.vue';
export { default as ColorInput } from './color-input/index.vue';
export { default as DiyEditor } from './diy-editor/index.vue';
export { type DiyComponentLibrary, PAGE_LIBS } from './diy-editor/util';

View File

@@ -7,16 +7,16 @@ import { IconifyIcon } from '@vben/icons';
import { createRect, isContains, isOverlap } from './util';
// TODO @AI: 改成标准注释
// 魔方编辑器
// 有两部分组成:
// 1. 魔方矩阵:位于底层,由方块组件的二维表格,用于创建热区
// 操作方法:
// 1.1 点击其中一个方块就会进入热区选择模式
// 1.2 再次点击另外一个方块时,结束热区选择模式
// 1.3 在两个方块中间的区域创建热区
// 如果两次点击的都是同一方块,就只创建一个格子的热区
// 2. 热区:位于顶层,采用绝对定位,覆盖在魔方矩阵上面。
/**
* 魔方编辑器,有两部分组成:
* 1. 魔方矩阵:位于底层,由方块组件的二维表格,用于创建热区
* 操作方法:
* 1.1 点击其中一个方块就会进入热区选择模式
* 1.2 再次点击另外一个方块时,结束热区选择模式
* 1.3 在两个方块中间的区域创建热区
* 如果两次点击的都是同一方块,就只创建一个格子的热区
* 2. 热区:位于顶层,采用绝对定位,覆盖在魔方矩阵上面。
*/
defineOptions({ name: 'MagicCubeEditor' });
/** 定义属性 */
@@ -29,12 +29,10 @@ const props = defineProps({
type: Number,
default: 4,
}, // 行数,默认 4 行
cols: {
type: Number,
default: 4,
}, // 列数,默认 4 列
cubeSize: {
type: Number,
default: 75,
@@ -70,6 +68,7 @@ watch(
);
const hotAreas = ref<Rect[]>([]); // 热区列表
/** 初始化热区 */
watch(
() => props.modelValue,
@@ -86,20 +85,20 @@ const isHotAreaSelectMode = () => !!hotAreaBeginCube.value; // 是否开启了
* @param currentRow 当前行号
* @param currentCol 当前列号
*/
const handleCubeClick = (currentRow: number, currentCol: number) => {
function handleCubeClick(currentRow: number, currentCol: number) {
const currentCube = cubes.value[currentRow]?.[currentCol];
if (!currentCube) {
return;
}
// 情况1进入热区选择模式
// 情况 1进入热区选择模式
if (!isHotAreaSelectMode()) {
hotAreaBeginCube.value = currentCube;
hotAreaBeginCube.value!.active = true;
return;
}
// 情况2结束热区选择模式
// 情况 2结束热区选择模式
hotAreas.value.push(createRect(hotAreaBeginCube.value!, currentCube));
// 结束热区选择模式
exitHotAreaSelectMode();
@@ -111,7 +110,7 @@ const handleCubeClick = (currentRow: number, currentCol: number) => {
}
// 发送热区变动通知
emitUpdateModelValue();
};
}
/**
* 处理鼠标经过方块
@@ -119,7 +118,7 @@ const handleCubeClick = (currentRow: number, currentCol: number) => {
* @param currentRow 当前行号
* @param currentCol 当前列号
*/
const handleCellHover = (currentRow: number, currentCol: number) => {
function handleCellHover(currentRow: number, currentCol: number) {
// 当前没有进入热区选择模式
if (!isHotAreaSelectMode()) {
return;
@@ -138,7 +137,6 @@ const handleCellHover = (currentRow: number, currentCol: number) => {
if (isOverlap(hotArea, currentSelectedArea)) {
// 结束热区选择模式
exitHotAreaSelectMode();
return;
}
}
@@ -147,13 +145,9 @@ const handleCellHover = (currentRow: number, currentCol: number) => {
eachCube((_, __, cube) => {
cube.active = isContains(currentSelectedArea, cube);
});
};
}
/**
* 处理热区删除
*
* @param index 热区索引
*/
/** 处理热区删除 */
function handleDeleteHotArea(index: number) {
hotAreas.value.splice(index, 1);
// 结束热区选择模式
@@ -165,14 +159,14 @@ function handleDeleteHotArea(index: number) {
const emitUpdateModelValue = () => emit('update:modelValue', hotAreas.value); // 发送热区变动通知
const selectedHotAreaIndex = ref(0); // 热区选中
const handleHotAreaSelected = (hotArea: Rect, index: number) => {
/** 处理热区选中 */
function handleHotAreaSelected(hotArea: Rect, index: number) {
selectedHotAreaIndex.value = index;
emit('hotAreaSelected', hotArea, index);
};
}
/**
* 结束热区选择模式
*/
/** 结束热区选择模式 */
function exitHotAreaSelectMode() {
// 移除方块激活标记
eachCube((_, __, cube) => {
@@ -246,9 +240,9 @@ const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {
>
<IconifyIcon icon="ep:circle-close-filled" />
</div>
<span v-if="hotArea.width">{{
`${hotArea.width}×${hotArea.height}`
}}</span>
<span v-if="hotArea.width">
{{ `${hotArea.width}×${hotArea.height}` }}
</span>
</div>
</table>
</div>
@@ -265,6 +259,11 @@ const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {
text-align: center;
cursor: pointer;
border: 1px solid var(--el-border-color);
line-height: 1;
:deep(.iconify) {
display: inline-block;
}
&.active {
background: var(--el-color-primary-light-9);

View File

@@ -1,51 +1,47 @@
// 坐标点
/** 坐标点 */
export interface Point {
x: number;
y: number;
}
// 矩形
/** 矩形 */
export interface Rect {
// 左上角 X 轴坐标
left: number;
// 左上Y 轴坐标
top: number;
// 右下角 X 轴坐标
right: number;
// 右下角 Y 轴坐标
bottom: number;
// 矩形宽度
width: number;
// 矩形高度
height: number;
left: number; // 左上角 X 轴坐标
top: number; // 左上角 Y 轴坐标
right: number; // 右下X 轴坐标
bottom: number; // 右下角 Y 轴坐标
width: number; // 矩形宽度
height: number; // 矩形高度
}
/**
* 判断两个矩形是否重叠
*
* @param a 矩形 A
* @param b 矩形 B
*/
export const isOverlap = (a: Rect, b: Rect): boolean => {
export function isOverlap(a: Rect, b: Rect): boolean {
return (
a.left < b.left + b.width &&
a.left + a.width > b.left &&
a.top < b.top + b.height &&
a.height + a.top > b.top
);
};
}
/**
* 检查坐标点是否在矩形内
* @param hotArea 矩形
* @param point 坐标
*/
export const isContains = (hotArea: Rect, point: Point): boolean => {
export function isContains(hotArea: Rect, point: Point): boolean {
return (
point.x >= hotArea.left &&
point.x < hotArea.right &&
point.y >= hotArea.top &&
point.y < hotArea.bottom
);
};
}
/**
* 在两个坐标点中间,创建一个矩形
@@ -59,14 +55,17 @@ export const isContains = (hotArea: Rect, point: Point): boolean => {
* @param a 坐标点一
* @param b 坐标点二
*/
export const createRect = (a: Point, b: Point): Rect => {
export function createRect(a: Point, b: Point): Rect {
// 计算矩形的范围
const [left, left2] = [a.x, b.x].sort();
const [top, top2] = [a.y, b.y].sort();
let [left, left2] = [a.x, b.x].sort();
left = left ?? 0;
left2 = left2 ?? 0;
let [top, top2] = [a.y, b.y].sort();
top = top ?? 0;
top2 = top2 ?? 0;
const right = left2 + 1;
const bottom = top2 + 1;
const height = bottom - top;
const width = right - left;
return { left, right, top, bottom, height, width };
};
}

View File

@@ -1,2 +1,2 @@
export * from './data';
export { default as CouponSelect } from './select.vue';
export { default as CouponSendForm } from './send-form.vue';

View File

@@ -0,0 +1,119 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import {
discountFormat,
remainedCountFormat,
takeLimitCountFormat,
validityTypeFormat,
} from '../formatter';
/** 优惠券选择的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'name',
label: '优惠券名称',
component: 'Input',
componentProps: {
placeholder: '请输入优惠劵名',
clearable: true,
},
},
{
fieldName: 'discountType',
label: '优惠类型',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'),
placeholder: '请选择优惠券类型',
clearable: true,
},
},
];
}
/** 优惠券选择的表格列 */
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{ type: 'checkbox', width: 55 },
{
field: 'name',
title: '优惠券名称',
minWidth: 140,
},
{
field: 'productScope',
title: '类型',
minWidth: 80,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE },
},
},
{
field: 'discountType',
title: '优惠类型',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.PROMOTION_DISCOUNT_TYPE },
},
},
{
field: 'discountPrice',
title: '优惠力度',
minWidth: 100,
formatter: ({ row }) => discountFormat(row),
},
{
field: 'takeType',
title: '领取方式',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE },
},
},
{
field: 'validityType',
title: '使用时间',
minWidth: 185,
align: 'center',
formatter: ({ row }) => validityTypeFormat(row),
},
{
field: 'totalCount',
title: '发放数量',
minWidth: 100,
align: 'center',
},
{
field: 'remainedCount',
title: '剩余数量',
minWidth: 100,
align: 'center',
formatter: ({ row }) => remainedCountFormat(row),
},
{
field: 'takeLimitCount',
title: '领取上限',
minWidth: 100,
align: 'center',
formatter: ({ row }) => takeLimitCountFormat(row),
},
{
field: 'status',
title: '状态',
minWidth: 80,
align: 'center',
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
];
}

View File

@@ -0,0 +1,69 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import * as CouponTemplateApi from '#/api/mall/promotion/coupon/couponTemplate';
import {
useGridColumns,
useGridFormSchema,
} from './select-data';
defineOptions({ name: 'CouponSelect' });
const props = defineProps<{
takeType: number; // 领取方式
}>();
const emit = defineEmits(['success']);
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 500,
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await CouponTemplateApi.getCouponTemplatePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
canTakeTypes: props.takeType ? [props.takeType] : [],
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MallCouponTemplateApi.CouponTemplate>,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
// 从 gridApi 获取选中的记录
const selectedRecords = (gridApi.grid?.getCheckboxRecords() || []) as MallCouponTemplateApi.CouponTemplate[];
await modalApi.close();
emit('success', selectedRecords);
},
});
</script>
<template>
<Modal title="选择优惠劵" class="w-3/5">
<Grid />
</Modal>
</template>

View File

@@ -11,7 +11,7 @@ import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { sendCoupon } from '#/api/mall/promotion/coupon/coupon';
import { getCouponTemplatePage } from '#/api/mall/promotion/coupon/couponTemplate';
import { useFormSchema, useGridColumns } from './data';
import { useFormSchema, useGridColumns } from './send-form-data.ts';
/** 发送优惠券 */
async function handleSendCoupon(row: MallCouponTemplateApi.CouponTemplate) {

View File

@@ -1,4 +1,3 @@
import type { MallAfterSaleApi } from '#/api/mall/trade/afterSale';
import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
@@ -20,19 +19,19 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
{
field: 'order.deliveryType',
label: '配送方式',
content: (data: MallAfterSaleApi.AfterSale) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TRADE_DELIVERY_TYPE,
value: data?.order?.deliveryType,
value: val,
}),
},
{
field: 'order.type',
label: '订单类型',
content: (data: MallAfterSaleApi.AfterSale) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TRADE_ORDER_TYPE,
value: data?.order?.type,
value: val,
}),
},
{
@@ -46,10 +45,10 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
{
field: 'order.terminal',
label: '订单来源',
content: (data: MallAfterSaleApi.AfterSale) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TERMINAL,
value: data?.order?.terminal,
value: val,
}),
},
{
@@ -67,10 +66,10 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
{
field: 'order.payChannelCode',
label: '付款方式',
content: (data: MallAfterSaleApi.AfterSale) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.PAY_CHANNEL_CODE,
value: data?.order?.payChannelCode,
value: val,
}),
},
{
@@ -90,32 +89,30 @@ export function useAfterSaleInfoSchema(): DescriptionItemSchema[] {
{
field: 'auditTime',
label: '申请时间',
content: (data: MallAfterSaleApi.AfterSale) =>
formatDate(data?.auditTime) as string,
render: (val) => formatDate(val) as string,
},
{
field: 'type',
label: '售后类型',
content: (data: MallAfterSaleApi.AfterSale) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TRADE_AFTER_SALE_TYPE,
value: data?.type,
value: val,
}),
},
{
field: 'way',
label: '售后方式',
content: (data: MallAfterSaleApi.AfterSale) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TRADE_AFTER_SALE_WAY,
value: data?.way,
value: val,
}),
},
{
field: 'refundPrice',
label: '退款金额',
content: (data: MallAfterSaleApi.AfterSale) =>
fenToYuan(data?.refundPrice ?? 0),
render: (val) => fenToYuan(val ?? 0),
},
{
field: 'applyReason',
@@ -128,8 +125,8 @@ export function useAfterSaleInfoSchema(): DescriptionItemSchema[] {
{
field: 'applyPicUrls',
label: '凭证图片',
content: (data) => {
const images = data?.applyPicUrls || [];
render: (val) => {
const images = val || [];
return h(
'div',
{ class: 'flex gap-10px' },
@@ -153,16 +150,16 @@ export function useRefundStatusSchema(): DescriptionItemSchema[] {
{
field: 'status',
label: '退款状态',
content: (data) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TRADE_AFTER_SALE_STATUS,
value: data?.status,
value: val,
}),
},
{
field: 'reminder',
label: '提醒',
content: () =>
render: () =>
h('div', { class: 'text-red-500 mb-10px' }, [
h('div', '如果未发货,请点击同意退款给买家。'),
h('div', '如果实际已发货,请主动与买家联系。'),

View File

@@ -49,38 +49,29 @@ const afterSale = ref<MallAfterSaleApi.AfterSale>({
});
const [OrderDescriptions] = useDescription({
componentProps: {
title: '订单信息',
border: false,
column: 3,
direction: 'horizontal',
labelWidth: 140,
extra: '',
},
title: '订单信息',
border: false,
column: 3,
direction: 'horizontal',
labelWidth: 140,
schema: useOrderInfoSchema(),
});
const [AfterSaleDescriptions] = useDescription({
componentProps: {
title: '售后信息',
border: false,
column: 3,
direction: 'horizontal',
labelWidth: 140,
extra: '',
},
title: '售后信息',
border: false,
column: 3,
direction: 'horizontal',
labelWidth: 140,
schema: useAfterSaleInfoSchema(),
});
const [RefundStatusDescriptions] = useDescription({
componentProps: {
title: '退款状态',
border: false,
column: 1,
direction: 'horizontal',
labelWidth: 140,
extra: '',
},
title: '退款状态',
border: false,
column: 1,
direction: 'horizontal',
labelWidth: 140,
schema: useRefundStatusSchema(),
});

View File

@@ -1,5 +1,4 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallOrderApi } from '#/api/mall/trade/order';
import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
@@ -23,19 +22,19 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
{
field: 'type',
label: '订单类型',
content: (data: MallOrderApi.Order) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TRADE_ORDER_TYPE,
value: data?.type,
value: val,
}),
},
{
field: 'terminal',
label: '订单来源',
content: (data: MallOrderApi.Order) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TERMINAL,
value: data?.terminal,
value: val,
}),
},
{
@@ -53,10 +52,10 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
{
field: 'payChannelCode',
label: '付款方式',
content: (data: MallOrderApi.Order) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.PAY_CHANNEL_CODE,
value: data?.payChannelCode,
value: val,
}),
},
{
@@ -72,16 +71,16 @@ export function useOrderStatusSchema(): DescriptionItemSchema[] {
{
field: 'status',
label: '订单状态',
content: (data: MallOrderApi.Order) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TRADE_ORDER_STATUS,
value: data?.status,
value: val,
}),
},
{
field: 'reminder',
label: '提醒',
content: () =>
render: () =>
h('div', { class: 'space-y-1' }, [
h('div', '买家付款成功后,货款将直接进入您的商户号(微信、支付宝)'),
h('div', '请及时关注你发出的包裹状态,确保可以配送至买家手中'),
@@ -100,66 +99,46 @@ export function useOrderPriceSchema(): DescriptionItemSchema[] {
{
field: 'totalPrice',
label: '商品总额',
content: (data: MallOrderApi.Order) =>
`${fenToYuan(data?.totalPrice ?? 0)}`,
render: (val) => `${fenToYuan(val ?? 0)}`,
},
{
field: 'deliveryPrice',
label: '运费金额',
content: (data: MallOrderApi.Order) =>
`${fenToYuan(data?.deliveryPrice ?? 0)}`,
render: (val) => `${fenToYuan(val ?? 0)}`,
},
{
field: 'adjustPrice',
label: '订单调价',
content: (data: MallOrderApi.Order) =>
`${fenToYuan(data?.adjustPrice ?? 0)}`,
render: (val) => `${fenToYuan(val ?? 0)}`,
},
{
field: 'couponPrice',
label: '优惠劵优惠',
content: (data: MallOrderApi.Order) =>
h(
'span',
{ class: 'text-red-500' },
`${fenToYuan(data?.couponPrice ?? 0)}`,
),
render: (val) =>
h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)}`),
},
{
field: 'vipPrice',
label: 'VIP 优惠',
content: (data: MallOrderApi.Order) =>
h(
'span',
{ class: 'text-red-500' },
`${fenToYuan(data?.vipPrice ?? 0)}`,
),
render: (val) =>
h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)}`),
},
{
field: 'discountPrice',
label: '活动优惠',
content: (data: MallOrderApi.Order) =>
h(
'span',
{ class: 'text-red-500' },
`${fenToYuan(data?.discountPrice ?? 0)}`,
),
render: (val) =>
h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)}`),
},
{
field: 'pointPrice',
label: '积分抵扣',
content: (data: MallOrderApi.Order) =>
h(
'span',
{ class: 'text-red-500' },
`${fenToYuan(data?.pointPrice ?? 0)}`,
),
render: (val) =>
h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)}`),
},
{
field: 'payPrice',
label: '应付金额',
content: (data: MallOrderApi.Order) =>
`${fenToYuan(data?.payPrice ?? 0)}`,
render: (val) => `${fenToYuan(val ?? 0)}`,
},
];
}
@@ -170,10 +149,10 @@ export function useDeliveryInfoSchema(): DescriptionItemSchema[] {
{
field: 'deliveryType',
label: '配送方式',
content: (data: MallOrderApi.Order) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.TRADE_DELIVERY_TYPE,
value: data?.deliveryType,
value: val,
}),
},
{
@@ -187,14 +166,12 @@ export function useDeliveryInfoSchema(): DescriptionItemSchema[] {
{
field: 'receiverAddress',
label: '收货地址',
content: (data: MallOrderApi.Order) =>
`${data?.receiverAreaName} ${data?.receiverDetailAddress}`.trim(),
render: (val, data) => `${data?.receiverAreaName} ${val}`.trim(),
},
{
field: 'deliveryTime',
label: '发货时间',
content: (data: MallOrderApi.Order) =>
formatDateTime(data?.deliveryTime) as string,
render: (val) => formatDateTime(val) as string,
},
];
}

View File

@@ -57,38 +57,30 @@ const expressTrackList = ref<any[]>([]);
const pickUpStore = ref<MallDeliveryPickUpStoreApi.PickUpStore | undefined>();
const [OrderInfoDescriptions] = useDescription({
componentProps: {
title: '订单信息',
border: false,
column: 3,
},
title: '订单信息',
border: false,
column: 3,
schema: useOrderInfoSchema(),
});
const [OrderStatusDescriptions] = useDescription({
componentProps: {
title: '订单状态',
border: false,
column: 1,
},
title: '订单状态',
border: false,
column: 1,
schema: useOrderStatusSchema(),
});
const [OrderPriceDescriptions] = useDescription({
componentProps: {
title: '费用信息',
border: false,
column: 4,
},
title: '费用信息',
border: false,
column: 4,
schema: useOrderPriceSchema(),
});
const [DeliveryInfoDescriptions] = useDescription({
componentProps: {
title: '收货信息',
border: false,
column: 3,
},
title: '收货信息',
border: false,
column: 3,
schema: useDeliveryInfoSchema(),
});

View File

@@ -1,6 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { PayNotifyApi } from '#/api/pay/notify';
import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
@@ -182,10 +181,10 @@ export function useDetailSchema(): DescriptionItemSchema[] {
{
field: 'type',
label: '通知类型',
content: (data: PayNotifyApi.NotifyTask) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.PAY_NOTIFY_TYPE,
value: data?.type,
value: val,
}),
},
{
@@ -195,10 +194,10 @@ export function useDetailSchema(): DescriptionItemSchema[] {
{
field: 'status',
label: '通知状态',
content: (data: PayNotifyApi.NotifyTask) =>
render: (val) =>
h(DictTag, {
type: DICT_TYPE.PAY_NOTIFY_STATUS,
value: data?.status,
value: val,
}),
},
{
@@ -208,14 +207,12 @@ export function useDetailSchema(): DescriptionItemSchema[] {
{
field: 'lastExecuteTime',
label: '最后通知时间',
content: (data: PayNotifyApi.NotifyTask) =>
formatDateTime(data?.lastExecuteTime) as string,
render: (val) => formatDateTime(val) as string,
},
{
field: 'nextNotifyTime',
label: '下次通知时间',
content: (data: PayNotifyApi.NotifyTask) =>
formatDateTime(data?.nextNotifyTime) as string,
render: (val) => formatDateTime(val) as string,
},
{
field: 'notifyTimes',
@@ -228,14 +225,12 @@ export function useDetailSchema(): DescriptionItemSchema[] {
{
field: 'createTime',
label: '创建时间',
content: (data: PayNotifyApi.NotifyTask) =>
formatDateTime(data?.createTime) as string,
render: (val) => formatDateTime(val) as string,
},
{
field: 'updateTime',
label: '更新时间',
content: (data: PayNotifyApi.NotifyTask) =>
formatDateTime(data?.updateTime) as string,
render: (val) => formatDateTime(val) as string,
},
];
}

View File

@@ -17,14 +17,10 @@ import { useDetailLogColumns, useDetailSchema } from '../data';
const formData = ref<PayNotifyApi.NotifyTask>();
const [Description] = useDescription({
componentProps: {
border: true,
column: 2,
direction: 'horizontal',
labelWidth: 140,
title: '',
extra: '',
},
border: true,
column: 2,
direction: 'horizontal',
labelWidth: 140,
schema: useDetailSchema(),
});

View File

@@ -13,6 +13,7 @@ import { useDetailSchema } from '../data';
const formData = ref<PayOrderApi.Order>();
const [Descriptions] = useDescription({
border: true,
column: 2,
labelWidth: 140,
schema: useDetailSchema(),

View File

@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
const [Descriptions] = useDescription({
border: true,
column: 1,
labelWidth: 140,
schema: useDetailSchema(),

View File

@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
const [Descriptions] = useDescription({
border: true,
column: 1,
labelWidth: 140,
schema: useDetailSchema(),

View File

@@ -12,8 +12,8 @@ import { useDetailSchema } from '../data';
const formData = ref<SystemOperateLogApi.OperateLog>();
const [Descriptions] = useDescription({
border: true,
column: 1,
direction: 'horizontal',
labelWidth: 110,
schema: useDetailSchema(),
});

View File

@@ -14,7 +14,9 @@ import { useDetailSchema } from '../data';
const formData = ref<SystemSocialUserApi.SocialUser>();
const [Descriptions] = useDescription({
border: true,
column: 1,
size: 'large',
labelWidth: 185,
schema: useDetailSchema(),
});