fix: 冲突合并
This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -32,8 +32,6 @@ export interface DescriptionProps extends ElDescriptionProps {
|
||||
schema: DescriptionItemSchema[];
|
||||
// 数据
|
||||
data: Recordable<any>;
|
||||
// 是否包含边框
|
||||
bordered?: boolean;
|
||||
}
|
||||
|
||||
export interface DescInstance {
|
||||
|
||||
@@ -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 '';
|
||||
|
||||
@@ -11,8 +11,8 @@ import { useDetailSchema } from '../data';
|
||||
|
||||
const formData = ref<InfraApiAccessLogApi.ApiAccessLog>();
|
||||
|
||||
// TODO @xingyu:antd 和 el 这 2 个组件在这个模块的 detail.vue 不一样,看看是不是统一?还是就是区分的哈?
|
||||
const [Descriptions] = useDescription({
|
||||
border: true,
|
||||
column: 1,
|
||||
labelWidth: 110,
|
||||
schema: useDetailSchema(),
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
|
||||
const formData = ref<InfraApiErrorLogApi.ApiErrorLog>();
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
border: true,
|
||||
column: 1,
|
||||
labelWidth: 110,
|
||||
schema: useDetailSchema(),
|
||||
|
||||
@@ -14,6 +14,7 @@ const formData = ref<InfraJobApi.Job>(); // 任务详情
|
||||
const nextTimes = ref<Date[]>([]); // 下一次执行时间
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
border: true,
|
||||
column: 1,
|
||||
labelWidth: 140,
|
||||
schema: useDetailSchema(),
|
||||
|
||||
@@ -8,7 +8,7 @@ defineProps<{
|
||||
}>();
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
bordered: false,
|
||||
border: false,
|
||||
column: 6,
|
||||
schema: [
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ProductCategorySelect } from './category-select.vue';
|
||||
@@ -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>
|
||||
|
||||
@@ -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 @AI:这里有点问题;activeAppLink 地址;
|
||||
// 生成 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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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: '轮播图',
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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: '优惠券',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -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>;
|
||||
},
|
||||
});
|
||||
@@ -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"
|
||||
>
|
||||
<!-- 布局1:1列-->
|
||||
<!-- 布局 1:1 列-->
|
||||
<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>
|
||||
<!-- 布局2:2列-->
|
||||
<!-- 布局 2:2 列-->
|
||||
<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>
|
||||
<!-- 布局3:3列-->
|
||||
<!-- 布局 3:3 列-->
|
||||
<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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: '分割线',
|
||||
@@ -25,5 +25,3 @@ defineProps<{ property: DividerProperty }>();
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -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>
|
||||
@@ -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: '悬浮按钮',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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: '热区',
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ElImage } from 'element-plus';
|
||||
|
||||
/** 热区 */
|
||||
defineOptions({ name: 'HotZone' });
|
||||
|
||||
const props = defineProps<{ property: HotZoneProperty }>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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: '图片展示',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: '广告魔方',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -541,7 +541,7 @@ $phone-width: 375px;
|
||||
}
|
||||
|
||||
/* 属性面板分组 */
|
||||
:deep(.property-group) {
|
||||
.property-group {
|
||||
margin: 0 -20px;
|
||||
|
||||
&.el-card {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './data';
|
||||
export { default as CouponSelect } from './select.vue';
|
||||
export { default as CouponSendForm } from './send-form.vue';
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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', '如果实际已发货,请主动与买家联系。'),
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useDetailSchema } from '../data';
|
||||
const formData = ref<PayOrderApi.Order>();
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
border: true,
|
||||
column: 2,
|
||||
labelWidth: 140,
|
||||
schema: useDetailSchema(),
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
|
||||
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
border: true,
|
||||
column: 1,
|
||||
labelWidth: 140,
|
||||
schema: useDetailSchema(),
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
|
||||
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
border: true,
|
||||
column: 1,
|
||||
labelWidth: 140,
|
||||
schema: useDetailSchema(),
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user