fix: 冲突合并
This commit is contained in:
@@ -284,6 +284,17 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// @jason:看看能不能通过 tailwindcss 简化下
|
// @jason:看看能不能通过 tailwindcss 简化下
|
||||||
|
@keyframes bounce {
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.process-definition-container {
|
.process-definition-container {
|
||||||
.definition-item-card {
|
.definition-item-card {
|
||||||
.flow-icon-img {
|
.flow-icon-img {
|
||||||
@@ -310,15 +321,4 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes bounce {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ export function useDetailSchema(): DescriptionItemSchema[] {
|
|||||||
render: (val, data) => {
|
render: (val, data) => {
|
||||||
if (val === 0) {
|
if (val === 0) {
|
||||||
return '正常';
|
return '正常';
|
||||||
} else if (val > 0 && data?.resultCode > 0) {
|
} else if (val > 0 && data?.resultMsg) {
|
||||||
return `失败 | ${val} | ${data.resultMsg}`;
|
return `失败 | ${val} | ${data.resultMsg}`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import type { HotZoneProperty } from './config';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
import { Button, Form, FormItem, Typography } from 'ant-design-vue';
|
import { Button, Form, FormItem, Typography } from 'ant-design-vue';
|
||||||
|
|
||||||
import UploadImg from '#/components/upload/image-upload.vue';
|
import UploadImg from '#/components/upload/image-upload.vue';
|
||||||
@@ -30,7 +29,12 @@ const handleOpenEditDialog = () => {
|
|||||||
<template>
|
<template>
|
||||||
<ComponentContainerProperty v-model="formData.style">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<!-- 表单 -->
|
<!-- 表单 -->
|
||||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }" :model="formData" class="mt-2">
|
<Form
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 18 }"
|
||||||
|
:model="formData"
|
||||||
|
class="mt-2"
|
||||||
|
>
|
||||||
<FormItem label="上传图片" prop="imgUrl">
|
<FormItem label="上传图片" prop="imgUrl">
|
||||||
<UploadImg
|
<UploadImg
|
||||||
v-model="formData.imgUrl"
|
v-model="formData.imgUrl"
|
||||||
@@ -40,7 +44,9 @@ const handleOpenEditDialog = () => {
|
|||||||
:show-description="false"
|
:show-description="false"
|
||||||
>
|
>
|
||||||
<template #tip>
|
<template #tip>
|
||||||
<Typography.Text type="secondary" class="text-xs"> 推荐宽度 750</Typography.Text>
|
<Typography.Text type="secondary" class="text-xs">
|
||||||
|
推荐宽度 750
|
||||||
|
</Typography.Text>
|
||||||
</template>
|
</template>
|
||||||
</UploadImg>
|
</UploadImg>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import type { ImageBarProperty } from './config';
|
import type { ImageBarProperty } from './config';
|
||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
import { Form, FormItem } from 'ant-design-vue';
|
import { Form, FormItem } from 'ant-design-vue';
|
||||||
|
|
||||||
import UploadImg from '#/components/upload/image-upload.vue';
|
import UploadImg from '#/components/upload/image-upload.vue';
|
||||||
@@ -20,7 +19,11 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ComponentContainerProperty v-model="formData.style">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }" :model="formData">
|
<Form
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 18 }"
|
||||||
|
:model="formData"
|
||||||
|
>
|
||||||
<FormItem label="上传图片" prop="imgUrl">
|
<FormItem label="上传图片" prop="imgUrl">
|
||||||
<UploadImg
|
<UploadImg
|
||||||
v-model="formData.imgUrl"
|
v-model="formData.imgUrl"
|
||||||
|
|||||||
@@ -1,32 +1,25 @@
|
|||||||
import { defineAsyncComponent } from 'vue';
|
/**
|
||||||
|
|
||||||
/*
|
|
||||||
* 组件注册
|
* 组件注册
|
||||||
*
|
*
|
||||||
* 组件规范:
|
* 组件规范:每个子目录就是一个独立的组件,每个目录包括以下三个文件:
|
||||||
* 1. 每个子目录就是一个独立的组件,每个目录包括以下三个文件:
|
* 1. config.ts:组件配置,必选,用于定义组件、组件默认的属性、定义属性的类型
|
||||||
* 2. config.ts:组件配置,必选,用于定义组件、组件默认的属性、定义属性的类型
|
* 2. index.vue:组件展示,用于展示组件的渲染效果。可以不提供,如 Page(页面设置),只需要属性配置表单即可
|
||||||
* 3. index.vue:组件展示,用于展示组件的渲染效果。可以不提供,如 Page(页面设置),只需要属性配置表单即可
|
* 3. property.vue:组件属性表单,用于配置组件,必选,
|
||||||
* 4. property.vue:组件属性表单,用于配置组件,必选,
|
|
||||||
*
|
*
|
||||||
* 注:
|
* 注:
|
||||||
* 组件ID以config.ts中配置的id为准,与组件目录的名称无关,但还是建议组件目录的名称与组件ID保持一致
|
* 组件 ID 以 config.ts 中配置的 id 为准,与组件目录的名称无关,但还是建议组件目录的名称与组件 ID 保持一致
|
||||||
*/
|
*/
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
|
||||||
// 导入组件界面模块
|
const viewModules: Record<string, any> = import.meta.glob('./*/*.vue'); // 导入组件界面模块
|
||||||
const viewModules: Record<string, any> = import.meta.glob('./*/*.vue');
|
|
||||||
// 导入配置模块
|
|
||||||
const configModules: Record<string, any> = import.meta.glob('./*/config.ts', {
|
const configModules: Record<string, any> = import.meta.glob('./*/config.ts', {
|
||||||
eager: true,
|
eager: true,
|
||||||
});
|
}); // 导入配置模块
|
||||||
|
|
||||||
// 界面模块
|
const components: Record<string, any> = {}; // 界面模块
|
||||||
const components: Record<string, any> = {};
|
const componentConfigs: Record<string, any> = {}; // 组件配置模块
|
||||||
// 组件配置模块
|
|
||||||
const componentConfigs: Record<string, any> = {};
|
|
||||||
|
|
||||||
// 组件界面的类型
|
type ViewType = 'index' | 'property'; // 组件界面的类型
|
||||||
type ViewType = 'index' | 'property';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册组件的界面模块
|
* 注册组件的界面模块
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ setInterval(() => {
|
|||||||
>
|
>
|
||||||
<Image :src="property.iconUrl" class="h-[18px]" :preview="false" />
|
<Image :src="property.iconUrl" class="h-[18px]" :preview="false" />
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
<div class="flex-1 pr-2 h-6 truncate leading-6">
|
<div class="h-6 flex-1 truncate pr-2 leading-6">
|
||||||
{{ property.contents?.[activeIndex]?.text }}
|
{{ property.contents?.[activeIndex]?.text }}
|
||||||
</div>
|
</div>
|
||||||
<IconifyIcon icon="ep:arrow-right" />
|
<IconifyIcon icon="ep:arrow-right" />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable unicorn/no-nested-ternary -->
|
||||||
<!-- 积分活动橱窗组件 - 用于装修时展示和选择积分活动 -->
|
<!-- 积分活动橱窗组件 - 用于装修时展示和选择积分活动 -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// TODO @puhui999:看看是不是整体优化下代码风格,参考别的模块
|
// TODO @puhui999:看看是不是整体优化下代码风格,参考别的模块
|
||||||
@@ -95,9 +96,9 @@ watch(
|
|||||||
async () => {
|
async () => {
|
||||||
const ids = Array.isArray(props.modelValue)
|
const ids = Array.isArray(props.modelValue)
|
||||||
? props.modelValue
|
? props.modelValue
|
||||||
: (props.modelValue
|
: props.modelValue
|
||||||
? [props.modelValue]
|
? [props.modelValue]
|
||||||
: []);
|
: [];
|
||||||
|
|
||||||
// 不需要返显
|
// 不需要返显
|
||||||
if (ids.length === 0) {
|
if (ids.length === 0) {
|
||||||
@@ -158,7 +159,7 @@ watch(
|
|||||||
<!-- 积分活动选择对话框 -->
|
<!-- 积分活动选择对话框 -->
|
||||||
<PointTableSelect
|
<PointTableSelect
|
||||||
ref="pointActivityTableSelectRef"
|
ref="pointActivityTableSelectRef"
|
||||||
:multiple="limit != 1"
|
:multiple="limit !== 1"
|
||||||
@change="handleActivitySelected"
|
@change="handleActivitySelected"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -153,4 +153,3 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,9 @@ import { useDetailLogColumns, useDetailSchema } from '../data';
|
|||||||
const formData = ref<PayNotifyApi.NotifyTask>();
|
const formData = ref<PayNotifyApi.NotifyTask>();
|
||||||
|
|
||||||
const [Description] = useDescription({
|
const [Description] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 2,
|
column: 2,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<PayOrderApi.Order>();
|
const formData = ref<PayOrderApi.Order>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 2,
|
column: 2,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<PayRefundApi.Refund>();
|
const formData = ref<PayRefundApi.Refund>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 2,
|
column: 2,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<PayTransferApi.Transfer>();
|
const formData = ref<PayTransferApi.Transfer>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 2,
|
column: 2,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemLoginLogApi.LoginLog>();
|
const formData = ref<SystemLoginLogApi.LoginLog>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemMailLogApi.MailLog>();
|
const formData = ref<SystemMailLogApi.MailLog>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 2,
|
column: 2,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemOperateLogApi.OperateLog>();
|
const formData = ref<SystemOperateLogApi.OperateLog>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemSmsLogApi.SmsLog>();
|
const formData = ref<SystemSmsLogApi.SmsLog>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 2,
|
column: 2,
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemSocialUserApi.SocialUser>();
|
const formData = ref<SystemSocialUserApi.SocialUser>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
bordered: true,
|
bordered: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
size: 'middle',
|
size: 'middle',
|
||||||
class: 'mx-4',
|
class: 'mx-4',
|
||||||
labelStyle: { width: '185px' },
|
labelStyle: { width: '185px' },
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import { get, getNestedValue, isFunction } from '@vben/utils';
|
|||||||
import { ElDescriptions, ElDescriptionsItem } from 'element-plus';
|
import { ElDescriptions, ElDescriptionsItem } from 'element-plus';
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
// TODO @星语:bordered 不生效;之前好像是 border
|
border: { default: true, type: Boolean },
|
||||||
bordered: { default: true, type: Boolean },
|
|
||||||
column: {
|
column: {
|
||||||
default: () => {
|
default: () => {
|
||||||
return { lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4 };
|
return { lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4 };
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ export interface DescriptionProps extends ElDescriptionProps {
|
|||||||
schema: DescriptionItemSchema[];
|
schema: DescriptionItemSchema[];
|
||||||
// 数据
|
// 数据
|
||||||
data: Recordable<any>;
|
data: Recordable<any>;
|
||||||
// 是否包含边框
|
|
||||||
bordered?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DescInstance {
|
export interface DescInstance {
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ export function useDetailSchema(): DescriptionItemSchema[] {
|
|||||||
render: (val, data) => {
|
render: (val, data) => {
|
||||||
if (val === 0) {
|
if (val === 0) {
|
||||||
return '正常';
|
return '正常';
|
||||||
} else if (data && data.resultCode > 0) {
|
} else if (data && data.resultMsg) {
|
||||||
return `失败 | ${val} | ${data.resultMsg}`;
|
return `失败 | ${val} | ${data.resultMsg}`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { useDetailSchema } from '../data';
|
|||||||
|
|
||||||
const formData = ref<InfraApiAccessLogApi.ApiAccessLog>();
|
const formData = ref<InfraApiAccessLogApi.ApiAccessLog>();
|
||||||
|
|
||||||
// TODO @xingyu:antd 和 el 这 2 个组件在这个模块的 detail.vue 不一样,看看是不是统一?还是就是区分的哈?
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
|
border: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
labelWidth: 110,
|
labelWidth: 110,
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<InfraApiErrorLogApi.ApiErrorLog>();
|
const formData = ref<InfraApiErrorLogApi.ApiErrorLog>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
|
border: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
labelWidth: 110,
|
labelWidth: 110,
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const formData = ref<InfraJobApi.Job>(); // 任务详情
|
|||||||
const nextTimes = ref<Date[]>([]); // 下一次执行时间
|
const nextTimes = ref<Date[]>([]); // 下一次执行时间
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
|
border: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
labelWidth: 140,
|
labelWidth: 140,
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
bordered: false,
|
border: false,
|
||||||
column: 6,
|
column: 6,
|
||||||
schema: [
|
schema: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,32 +3,33 @@ import { computed, onMounted, ref } from 'vue';
|
|||||||
|
|
||||||
import { handleTree } from '@vben/utils';
|
import { handleTree } from '@vben/utils';
|
||||||
|
|
||||||
|
import { ElTreeSelect } from 'element-plus';
|
||||||
|
|
||||||
import { getCategoryList } from '#/api/mall/product/category';
|
import { getCategoryList } from '#/api/mall/product/category';
|
||||||
|
|
||||||
/** 商品分类选择组件 */
|
/** 商品分类选择组件 */
|
||||||
defineOptions({ name: 'ProductCategorySelect' });
|
defineOptions({ name: 'ProductCategorySelect' });
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 选中的ID
|
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [Number, Array<Number>],
|
type: [Number, Array<Number>],
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
}, // 选中的 ID
|
||||||
// 是否多选
|
|
||||||
multiple: {
|
multiple: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
}, // 是否多选
|
||||||
// 上级品类的编号
|
|
||||||
parentId: {
|
parentId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
}, // 上级品类的编号
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 分类选择 */
|
/** 分类选择 */
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const categoryList = ref<any[]>([]); // 分类树
|
||||||
|
|
||||||
/** 选中的分类 ID */
|
/** 选中的分类 ID */
|
||||||
const selectCategoryId = computed({
|
const selectCategoryId = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
@@ -40,7 +41,6 @@ const selectCategoryId = computed({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
const categoryList = ref<any[]>([]); // 分类树
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const data = await getCategoryList({
|
const data = await getCategoryList({
|
||||||
parentId: props.parentId,
|
parentId: props.parentId,
|
||||||
@@ -49,20 +49,19 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<el-tree-select
|
<ElTreeSelect
|
||||||
v-model="selectCategoryId"
|
v-model="selectCategoryId"
|
||||||
:data="categoryList"
|
:data="categoryList"
|
||||||
|
node-key="id"
|
||||||
:props="{
|
:props="{
|
||||||
children: 'children',
|
children: 'children',
|
||||||
label: 'name',
|
label: 'name',
|
||||||
value: 'id',
|
|
||||||
isLeaf: 'leaf',
|
|
||||||
emitPath: false,
|
|
||||||
}"
|
}"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
:show-checkbox="multiple"
|
:show-checkbox="multiple"
|
||||||
class="w-1/1"
|
check-strictly
|
||||||
node-key="id"
|
default-expand-all
|
||||||
|
class="w-full"
|
||||||
placeholder="请选择商品分类"
|
placeholder="请选择商品分类"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as ProductCategorySelect } from './category-select.vue';
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// TODO @AI:改成 El 标签风格,而不是 el-
|
|
||||||
// TODO @AI:一些 modal 是否使用 Modal 组件,而不是 el-modal?
|
// TODO @AI:一些 modal 是否使用 Modal 组件,而不是 el-modal?
|
||||||
import { ref, watch } from 'vue';
|
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 链接输入框 */
|
/** APP 链接输入框 */
|
||||||
defineOptions({ name: 'AppLinkInput' });
|
defineOptions({ name: 'AppLinkInput' });
|
||||||
|
|
||||||
// 定义属性
|
/** 定义属性 */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -23,8 +24,16 @@ const emit = defineEmits<{
|
|||||||
const dialogRef = ref(); // 选择对话框
|
const dialogRef = ref(); // 选择对话框
|
||||||
|
|
||||||
const appLink = 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(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
@@ -38,11 +47,11 @@ watch(
|
|||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<el-input v-model="appLink" placeholder="输入或选择链接">
|
<ElInput v-model="appLink" placeholder="输入或选择链接">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button @click="handleOpenDialog">选择</el-button>
|
<ElButton @click="handleOpenDialog">选择</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</ElInput>
|
||||||
|
|
||||||
<AppLinkSelectDialog ref="dialogRef" @change="handleLinkSelected" />
|
<AppLinkSelectDialog ref="dialogRef" @change="handleLinkSelected" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,11 +5,18 @@ import type { AppLink } from './data';
|
|||||||
|
|
||||||
import { nextTick, ref } from 'vue';
|
import { nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { getUrlNumberValue } from '@vben/utils';
|
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';
|
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM } from './data';
|
||||||
|
|
||||||
@@ -32,18 +39,31 @@ const groupBtnRefs = ref<ButtonInstance[]>([]); // 分组引用列表
|
|||||||
const detailSelectDialog = ref<{
|
const detailSelectDialog = ref<{
|
||||||
id?: number;
|
id?: number;
|
||||||
type?: APP_LINK_TYPE_ENUM;
|
type?: APP_LINK_TYPE_ENUM;
|
||||||
visible: boolean;
|
|
||||||
}>({
|
}>({
|
||||||
visible: false,
|
|
||||||
id: undefined,
|
id: undefined,
|
||||||
type: 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);
|
async function open(link: string) {
|
||||||
const open = (link: string) => {
|
|
||||||
activeAppLink.value.path = link;
|
activeAppLink.value.path = link;
|
||||||
dialogVisible.value = true;
|
modalApi.open();
|
||||||
// 滚动到当前的链接
|
// 滚动到当前的链接
|
||||||
const group = APP_LINK_GROUP_LIST.find((group) =>
|
const group = APP_LINK_GROUP_LIST.find((group) =>
|
||||||
group.links.some((linkItem) => {
|
group.links.some((linkItem) => {
|
||||||
@@ -56,19 +76,18 @@ const open = (link: string) => {
|
|||||||
);
|
);
|
||||||
if (group) {
|
if (group) {
|
||||||
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
|
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
|
||||||
nextTick(() => handleGroupSelected(group.name));
|
await nextTick();
|
||||||
|
handleGroupSelected(group.name);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
defineExpose({ open });
|
|
||||||
|
|
||||||
/** 处理 APP 链接选中 */
|
/** 处理 APP 链接选中 */
|
||||||
const handleAppLinkSelected = (appLink: AppLink) => {
|
function handleAppLinkSelected(appLink: AppLink) {
|
||||||
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
|
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
|
||||||
activeAppLink.value = appLink;
|
activeAppLink.value = appLink;
|
||||||
}
|
}
|
||||||
switch (appLink.type) {
|
switch (appLink.type) {
|
||||||
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST: {
|
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST: {
|
||||||
detailSelectDialog.value.visible = true;
|
|
||||||
detailSelectDialog.value.type = appLink.type;
|
detailSelectDialog.value.type = appLink.type;
|
||||||
// 返显
|
// 返显
|
||||||
detailSelectDialog.value.id =
|
detailSelectDialog.value.id =
|
||||||
@@ -76,22 +95,18 @@ const handleAppLinkSelected = (appLink: AppLink) => {
|
|||||||
'id',
|
'id',
|
||||||
`http://127.0.0.1${activeAppLink.value.path}`,
|
`http://127.0.0.1${activeAppLink.value.path}`,
|
||||||
) || undefined;
|
) || undefined;
|
||||||
|
detailSelectModalApi.open();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
dialogVisible.value = false;
|
|
||||||
emit('change', activeAppLink.value.path);
|
|
||||||
emit('appLinkChange', activeAppLink.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理右侧链接列表滚动
|
* 处理右侧链接列表滚动
|
||||||
|
*
|
||||||
* @param {object} param0 滚动事件参数
|
* @param {object} param0 滚动事件参数
|
||||||
* @param {number} param0.scrollTop 滚动条的位置
|
* @param {number} param0.scrollTop 滚动条的位置
|
||||||
*/
|
*/
|
||||||
@@ -134,36 +149,36 @@ function scrollToGroupBtn(group: string) {
|
|||||||
|
|
||||||
/** 是否为相同的链接(不比较参数,只比较链接) */
|
/** 是否为相同的链接(不比较参数,只比较链接) */
|
||||||
function isSameLink(link1: string, link2: 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) {
|
function handleProductCategorySelected(id: number) {
|
||||||
// TODO @AI:这里有点问题;activeAppLink 地址;
|
// 生成 activeAppLink
|
||||||
const url = new URL(activeAppLink.value.path, 'http://127.0.0.1');
|
const url = new URL(activeAppLink.value.path, 'http://127.0.0.1');
|
||||||
// 修改 id 参数
|
|
||||||
url.searchParams.set('id', `${id}`);
|
url.searchParams.set('id', `${id}`);
|
||||||
// 排除域名
|
|
||||||
activeAppLink.value.path = `${url.pathname}${url.search}`;
|
activeAppLink.value.path = `${url.pathname}${url.search}`;
|
||||||
// 关闭对话框
|
|
||||||
detailSelectDialog.value.visible = false;
|
// 关闭对话框,并重置 id
|
||||||
// 重置 id
|
detailSelectModalApi.close();
|
||||||
detailSelectDialog.value.id = undefined;
|
detailSelectDialog.value.id = undefined;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="dialogVisible" title="选择链接" width="65%">
|
<Modal title="选择链接" class="w-[65%]">
|
||||||
<div class="flex h-[500px] gap-2">
|
<div class="flex h-[500px] gap-2">
|
||||||
|
<div class="flex flex-col">
|
||||||
<!-- 左侧分组列表 -->
|
<!-- 左侧分组列表 -->
|
||||||
<ElScrollbar
|
<ElScrollbar
|
||||||
wrap-class="h-full"
|
wrap-class="h-full"
|
||||||
ref="groupScrollbar"
|
ref="groupScrollbar"
|
||||||
view-class="flex flex-col"
|
view-class="flex flex-col"
|
||||||
|
class="border-r border-gray-200 pr-2"
|
||||||
>
|
>
|
||||||
<el-button
|
<ElButton
|
||||||
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
|
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
|
||||||
:key="groupIndex"
|
:key="groupIndex"
|
||||||
class="ml-0 mr-4 w-[90px] justify-start"
|
class="!ml-0 mb-1 mr-4 !justify-start"
|
||||||
:class="[{ active: activeGroup === group.name }]"
|
:class="[{ active: activeGroup === group.name }]"
|
||||||
ref="groupBtnRefs"
|
ref="groupBtnRefs"
|
||||||
:text="activeGroup !== group.name"
|
:text="activeGroup !== group.name"
|
||||||
@@ -171,29 +186,33 @@ function handleProductCategorySelected(id: number) {
|
|||||||
@click="handleGroupSelected(group.name)"
|
@click="handleGroupSelected(group.name)"
|
||||||
>
|
>
|
||||||
{{ group.name }}
|
{{ group.name }}
|
||||||
</el-button>
|
</ElButton>
|
||||||
</ElScrollbar>
|
</ElScrollbar>
|
||||||
|
</div>
|
||||||
<!-- 右侧链接列表 -->
|
<!-- 右侧链接列表 -->
|
||||||
<ElScrollbar
|
<ElScrollbar
|
||||||
class="h-full flex-1"
|
class="h-full flex-1 pl-2"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
ref="linkScrollbar"
|
ref="linkScrollbar"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
|
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
|
||||||
:key="groupIndex"
|
: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"
|
v-for="(appLink, appLinkIndex) in group.links"
|
||||||
:key="appLinkIndex"
|
:key="appLinkIndex"
|
||||||
:content="appLink.path"
|
:content="appLink.path"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
:show-after="300"
|
:show-after="300"
|
||||||
>
|
>
|
||||||
<el-button
|
<ElButton
|
||||||
class="mb-2 ml-0 mr-2"
|
class="mb-2 ml-0 mr-2"
|
||||||
:type="
|
:type="
|
||||||
isSameLink(appLink.path, activeAppLink.path)
|
isSameLink(appLink.path, activeAppLink.path)
|
||||||
@@ -203,20 +222,16 @@ function handleProductCategorySelected(id: number) {
|
|||||||
@click="handleAppLinkSelected(appLink)"
|
@click="handleAppLinkSelected(appLink)"
|
||||||
>
|
>
|
||||||
{{ appLink.name }}
|
{{ appLink.name }}
|
||||||
</el-button>
|
</ElButton>
|
||||||
</el-tooltip>
|
</ElTooltip>
|
||||||
</div>
|
</div>
|
||||||
</ElScrollbar>
|
</ElScrollbar>
|
||||||
</div>
|
</div>
|
||||||
<!-- 底部对话框操作按钮 -->
|
</Modal>
|
||||||
<template #footer>
|
|
||||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
<DetailSelectModal title="选择分类" class="w-[65%]">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<ElForm class="min-h-[200px]">
|
||||||
</template>
|
<ElFormItem
|
||||||
</el-dialog>
|
|
||||||
<el-dialog v-model="detailSelectDialog.visible" title="" width="50%">
|
|
||||||
<el-form class="min-h-[200px]">
|
|
||||||
<el-form-item
|
|
||||||
label="选择分类"
|
label="选择分类"
|
||||||
v-if="
|
v-if="
|
||||||
detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
|
detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
|
||||||
@@ -227,12 +242,7 @@ function handleProductCategorySelected(id: number) {
|
|||||||
:parent-id="0"
|
:parent-id="0"
|
||||||
@update:model-value="handleProductCategorySelected"
|
@update:model-value="handleProductCategorySelected"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</ElFormItem>
|
||||||
</el-form>
|
</ElForm>
|
||||||
</el-dialog>
|
</DetailSelectModal>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
|
||||||
:deep(.el-button + .el-button) {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO @AI:改成 El 风格,而不是iel- 风格;
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ElColorPicker, ElInput } from 'element-plus';
|
||||||
|
|
||||||
import { PREDEFINE_COLORS } from '@vben/constants';
|
import { PREDEFINE_COLORS } from '@vben/constants';
|
||||||
|
|
||||||
/** 颜色输入框 */
|
/** 颜色输入框 */
|
||||||
@@ -27,11 +28,11 @@ const color = computed({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-input v-model="color">
|
<ElInput v-model="color">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<el-color-picker v-model="color" :predefine="PREDEFINE_COLORS" />
|
<ElColorPicker v-model="color" :predefine="PREDEFINE_COLORS" />
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</ElInput>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -2,32 +2,23 @@ import type { ComponentStyle, DiyComponent } from '../../../util';
|
|||||||
|
|
||||||
/** 轮播图属性 */
|
/** 轮播图属性 */
|
||||||
export interface CarouselProperty {
|
export interface CarouselProperty {
|
||||||
// 类型:默认 | 卡片
|
type: 'card' | 'default'; // 类型:默认 | 卡片
|
||||||
type: 'card' | 'default';
|
indicator: 'dot' | 'number'; // 指示器样式:点 | 数字
|
||||||
// 指示器样式:点 | 数字
|
autoplay: boolean; // 是否自动播放
|
||||||
indicator: 'dot' | 'number';
|
interval: number; // 播放间隔
|
||||||
// 是否自动播放
|
items: CarouselItemProperty[]; // 轮播内容
|
||||||
autoplay: boolean;
|
style: ComponentStyle; // 组件样式
|
||||||
// 播放间隔
|
|
||||||
interval: number;
|
|
||||||
// 轮播内容
|
|
||||||
items: CarouselItemProperty[];
|
|
||||||
// 组件样式
|
|
||||||
style: ComponentStyle;
|
|
||||||
}
|
|
||||||
// 轮播内容属性
|
|
||||||
export interface CarouselItemProperty {
|
|
||||||
// 类型:图片 | 视频
|
|
||||||
type: 'img' | 'video';
|
|
||||||
// 图片链接
|
|
||||||
imgUrl: string;
|
|
||||||
// 视频链接
|
|
||||||
videoUrl: string;
|
|
||||||
// 跳转链接
|
|
||||||
url: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件
|
/** 轮播内容属性 */
|
||||||
|
export interface CarouselItemProperty {
|
||||||
|
type: 'img' | 'video'; // 类型:图片 | 视频
|
||||||
|
imgUrl: string; // 图片链接
|
||||||
|
videoUrl: string; // 视频链接
|
||||||
|
url: string; // 跳转链接
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 定义组件 */
|
||||||
export const component = {
|
export const component = {
|
||||||
id: 'Carousel',
|
id: 'Carousel',
|
||||||
name: '轮播图',
|
name: '轮播图',
|
||||||
@@ -12,7 +12,9 @@ defineOptions({ name: 'Carousel' });
|
|||||||
|
|
||||||
defineProps<{ property: CarouselProperty }>();
|
defineProps<{ property: CarouselProperty }>();
|
||||||
|
|
||||||
const currentIndex = ref(0);
|
const currentIndex = ref(0); // 当前索引
|
||||||
|
|
||||||
|
/** 处理索引变化 */
|
||||||
const handleIndexChange = (index: number) => {
|
const handleIndexChange = (index: number) => {
|
||||||
currentIndex.value = index + 1;
|
currentIndex.value = index + 1;
|
||||||
};
|
};
|
||||||
@@ -46,5 +48,3 @@ const handleIndexChange = (index: number) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@@ -26,7 +26,9 @@ import ComponentContainerProperty from '../../component-container-property.vue';
|
|||||||
defineOptions({ name: 'CarouselProperty' });
|
defineOptions({ name: 'CarouselProperty' });
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: CarouselProperty }>();
|
const props = defineProps<{ modelValue: CarouselProperty }>();
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const formData = useVModel(props, 'modelValue', emit);
|
const formData = useVModel(props, 'modelValue', emit);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -1,4 +1,87 @@
|
|||||||
// 导出所有优惠券相关组件
|
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
|
||||||
export { CouponDiscount } from './coupon-discount';
|
|
||||||
export { CouponDiscountDesc } from './coupon-discount-desc';
|
import { defineComponent } from 'vue';
|
||||||
export { CouponValidTerm } from './coupon-validTerm';
|
|
||||||
|
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';
|
import type { ComponentStyle, DiyComponent } from '../../../util';
|
||||||
|
|
||||||
/** 商品卡片属性 */
|
/** 优惠劵卡片属性 */
|
||||||
export interface CouponCardProperty {
|
export interface CouponCardProperty {
|
||||||
// 列数
|
columns: number; // 列数
|
||||||
columns: number;
|
bgImg: string; // 背景图
|
||||||
// 背景图
|
textColor: string; // 文字颜色
|
||||||
bgImg: string;
|
|
||||||
// 文字颜色
|
|
||||||
textColor: string;
|
|
||||||
// 按钮样式
|
|
||||||
button: {
|
button: {
|
||||||
// 背景颜色
|
bgColor: string; // 背景颜色
|
||||||
bgColor: string;
|
color: string; // 文字颜色
|
||||||
// 颜色
|
}; // 按钮样式
|
||||||
color: string;
|
space: number; // 间距
|
||||||
};
|
couponIds: number[]; // 优惠券编号列表
|
||||||
// 间距
|
style: ComponentStyle; // 组件样式
|
||||||
space: number;
|
|
||||||
// 优惠券编号列表
|
|
||||||
couponIds: number[];
|
|
||||||
// 组件样式
|
|
||||||
style: ComponentStyle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件
|
/** 定义组件 */
|
||||||
export const component = {
|
export const component = {
|
||||||
id: 'CouponCard',
|
id: 'CouponCard',
|
||||||
name: '优惠券',
|
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,
|
CouponValidTerm,
|
||||||
} from './component';
|
} from './component';
|
||||||
|
|
||||||
/** 商品卡片 */
|
/** 优惠劵卡片 */
|
||||||
defineOptions({ name: 'CouponCard' });
|
defineOptions({ name: 'CouponCard' });
|
||||||
// 定义属性
|
|
||||||
|
/** 定义属性 */
|
||||||
const props = defineProps<{ property: CouponCardProperty }>();
|
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(
|
watch(
|
||||||
() => props.property.couponIds,
|
() => props.property.couponIds,
|
||||||
async () => {
|
async () => {
|
||||||
@@ -36,15 +43,7 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 手机宽度
|
/** 计算布局参数 */
|
||||||
const phoneWidth = ref(375);
|
|
||||||
// 容器
|
|
||||||
const containerRef = ref();
|
|
||||||
// 滚动条宽度
|
|
||||||
const scrollbarWidth = ref('100%');
|
|
||||||
// 优惠券的宽度
|
|
||||||
const couponWidth = ref(375);
|
|
||||||
// 计算布局参数
|
|
||||||
watch(
|
watch(
|
||||||
() => [props.property, phoneWidth, couponList.value.length],
|
() => [props.property, phoneWidth, couponList.value.length],
|
||||||
() => {
|
() => {
|
||||||
@@ -60,8 +59,9 @@ watch(
|
|||||||
},
|
},
|
||||||
{ immediate: true, deep: true },
|
{ immediate: true, deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 提取手机宽度
|
|
||||||
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375;
|
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -86,17 +86,14 @@ onMounted(() => {
|
|||||||
v-for="(coupon, index) in couponList"
|
v-for="(coupon, index) in couponList"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<!-- 布局1:1列-->
|
<!-- 布局 1:1 列-->
|
||||||
<div
|
<div
|
||||||
v-if="property.columns === 1"
|
v-if="property.columns === 1"
|
||||||
class="ml-4 flex flex-row justify-between p-2"
|
class="ml-4 flex flex-row justify-between p-2"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col justify-evenly gap-1">
|
<div class="flex flex-col justify-evenly gap-1">
|
||||||
<!-- 优惠值 -->
|
|
||||||
<CouponDiscount :coupon="coupon" />
|
<CouponDiscount :coupon="coupon" />
|
||||||
<!-- 优惠描述 -->
|
|
||||||
<CouponDiscountDesc :coupon="coupon" />
|
<CouponDiscountDesc :coupon="coupon" />
|
||||||
<!-- 有效期 -->
|
|
||||||
<CouponValidTerm :coupon="coupon" />
|
<CouponValidTerm :coupon="coupon" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-evenly">
|
<div class="flex flex-col justify-evenly">
|
||||||
@@ -111,17 +108,14 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 布局2:2列-->
|
<!-- 布局 2:2 列-->
|
||||||
<div
|
<div
|
||||||
v-else-if="property.columns === 2"
|
v-else-if="property.columns === 2"
|
||||||
class="ml-4 flex flex-row justify-between p-2"
|
class="ml-4 flex flex-row justify-between p-2"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col justify-evenly gap-1">
|
<div class="flex flex-col justify-evenly gap-1">
|
||||||
<!-- 优惠值 -->
|
|
||||||
<CouponDiscount :coupon="coupon" />
|
<CouponDiscount :coupon="coupon" />
|
||||||
<!-- 优惠描述 -->
|
|
||||||
<CouponDiscountDesc :coupon="coupon" />
|
<CouponDiscountDesc :coupon="coupon" />
|
||||||
<!-- 领取说明 -->
|
|
||||||
<div v-if="coupon.totalCount >= 0">
|
<div v-if="coupon.totalCount >= 0">
|
||||||
仅剩:{{ coupon.totalCount - coupon.takeCount }}张
|
仅剩:{{ coupon.totalCount - coupon.takeCount }}张
|
||||||
</div>
|
</div>
|
||||||
@@ -139,11 +133,9 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 布局3:3列-->
|
<!-- 布局 3:3 列-->
|
||||||
<div v-else class="flex flex-col items-center justify-around gap-1 p-1">
|
<div v-else class="flex flex-col items-center justify-around gap-1 p-1">
|
||||||
<!-- 优惠值 -->
|
|
||||||
<CouponDiscount :coupon="coupon" />
|
<CouponDiscount :coupon="coupon" />
|
||||||
<!-- 优惠描述 -->
|
|
||||||
<CouponDiscountDesc :coupon="coupon" />
|
<CouponDiscountDesc :coupon="coupon" />
|
||||||
<div
|
<div
|
||||||
class="rounded-full px-2 py-0.5"
|
class="rounded-full px-2 py-0.5"
|
||||||
@@ -159,4 +151,3 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</ElScrollbar>
|
</ElScrollbar>
|
||||||
</template>
|
</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 { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import {
|
import {
|
||||||
CouponTemplateTakeTypeEnum,
|
CouponTemplateTakeTypeEnum,
|
||||||
PromotionDiscountTypeEnum,
|
PromotionDiscountTypeEnum,
|
||||||
@@ -26,29 +27,40 @@ import {
|
|||||||
import * as CouponTemplateApi from '#/api/mall/promotion/coupon/couponTemplate';
|
import * as CouponTemplateApi from '#/api/mall/promotion/coupon/couponTemplate';
|
||||||
import UploadImg from '#/components/upload/image-upload.vue';
|
import UploadImg from '#/components/upload/image-upload.vue';
|
||||||
import { ColorInput } from '#/views/mall/promotion/components';
|
import { ColorInput } from '#/views/mall/promotion/components';
|
||||||
|
import CouponSelect from '#/views/mall/promotion/coupon/components/select.vue';
|
||||||
|
|
||||||
import ComponentContainerProperty from '../../component-container-property.vue';
|
import ComponentContainerProperty from '../../component-container-property.vue';
|
||||||
// TODO: 添加组件
|
|
||||||
// import CouponSelect from '#/views/mall/promotion/coupon/components/coupon-select.vue';
|
|
||||||
|
|
||||||
// 优惠券卡片属性面板
|
/** 优惠券卡片属性面板 */
|
||||||
defineOptions({ name: 'CouponCardProperty' });
|
defineOptions({ name: 'CouponCardProperty' });
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: CouponCardProperty }>();
|
const props = defineProps<{ modelValue: CouponCardProperty }>();
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const formData = useVModel(props, 'modelValue', emit);
|
const formData = useVModel(props, 'modelValue', emit);
|
||||||
|
|
||||||
// 优惠券列表
|
const couponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]); // 已选择的优惠券列表
|
||||||
const couponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]);
|
|
||||||
const couponSelectDialog = ref();
|
const [CouponSelectModal, couponSelectModalApi] = useVbenModal({
|
||||||
// 添加优惠券
|
connectedComponent: CouponSelect,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 添加优惠劵 */
|
||||||
const handleAddCoupon = () => {
|
const handleAddCoupon = () => {
|
||||||
couponSelectDialog.value.open();
|
couponSelectModalApi.open();
|
||||||
};
|
|
||||||
const handleCouponSelect = () => {
|
|
||||||
formData.value.couponIds = couponList.value.map((coupon) => coupon.id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 处理优惠劵选择 */
|
||||||
|
const handleCouponSelect = (
|
||||||
|
selectedCoupons: MallCouponTemplateApi.CouponTemplate[],
|
||||||
|
) => {
|
||||||
|
couponList.value = selectedCoupons;
|
||||||
|
formData.value.couponIds = selectedCoupons.map((coupon) => coupon.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 监听优惠券 ID 变化,加载优惠券列表 */
|
||||||
watch(
|
watch(
|
||||||
() => formData.value.couponIds,
|
() => formData.value.couponIds,
|
||||||
async () => {
|
async () => {
|
||||||
@@ -151,13 +163,10 @@ watch(
|
|||||||
</ElCard>
|
</ElCard>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
|
|
||||||
<!-- 优惠券选择 -->
|
<!-- 优惠券选择 -->
|
||||||
<CouponSelect
|
<CouponSelectModal
|
||||||
ref="couponSelectDialog"
|
|
||||||
v-model:multiple-selection="couponList"
|
|
||||||
:take-type="CouponTemplateTakeTypeEnum.USER.type"
|
:take-type="CouponTemplateTakeTypeEnum.USER.type"
|
||||||
@change="handleCouponSelect"
|
@success="handleCouponSelect"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -2,19 +2,14 @@ import type { DiyComponent } from '../../../util';
|
|||||||
|
|
||||||
/** 分割线属性 */
|
/** 分割线属性 */
|
||||||
export interface DividerProperty {
|
export interface DividerProperty {
|
||||||
// 高度
|
height: number; // 高度
|
||||||
height: number;
|
lineWidth: number; // 线宽
|
||||||
// 线宽
|
paddingType: 'horizontal' | 'none'; // 边距类型
|
||||||
lineWidth: number;
|
lineColor: string; // 颜色
|
||||||
// 边距类型
|
borderType: 'dashed' | 'dotted' | 'none' | 'solid'; // 类型
|
||||||
paddingType: 'horizontal' | 'none';
|
|
||||||
// 颜色
|
|
||||||
lineColor: string;
|
|
||||||
// 类型
|
|
||||||
borderType: 'dashed' | 'dotted' | 'none' | 'solid';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件
|
/** 定义组件 */
|
||||||
export const component = {
|
export const component = {
|
||||||
id: 'Divider',
|
id: 'Divider',
|
||||||
name: '分割线',
|
name: '分割线',
|
||||||
@@ -25,5 +25,3 @@ defineProps<{ property: DividerProperty }>();
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@@ -13,15 +13,15 @@ import {
|
|||||||
ElTooltip,
|
ElTooltip,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
import { InputWithColor as ColorInput } from '#/views/mall/promotion/components';
|
import { ColorInput } from '#/views/mall/promotion/components';
|
||||||
|
|
||||||
// 导航栏属性面板
|
/** 导航栏属性面板 */
|
||||||
defineOptions({ name: 'DividerProperty' });
|
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 = [
|
const BORDER_TYPES = [
|
||||||
{
|
{
|
||||||
icon: 'vaadin:line-h',
|
icon: 'vaadin:line-h',
|
||||||
@@ -43,7 +43,8 @@ const BORDER_TYPES = [
|
|||||||
text: '无',
|
text: '无',
|
||||||
type: 'none',
|
type: 'none',
|
||||||
},
|
},
|
||||||
];
|
]; // 线类型
|
||||||
|
const formData = useVModel(props, 'modelValue', emit);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -96,11 +97,8 @@ const BORDER_TYPES = [
|
|||||||
</ElRadioGroup>
|
</ElRadioGroup>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="颜色">
|
<ElFormItem label="颜色">
|
||||||
<!-- 分割线颜色 -->
|
|
||||||
<ColorInput v-model="formData.lineColor" />
|
<ColorInput v-model="formData.lineColor" />
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</template>
|
</template>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@@ -1,28 +1,21 @@
|
|||||||
import type { DiyComponent } from '../../../util';
|
import type { DiyComponent } from '../../../util';
|
||||||
|
|
||||||
// 悬浮按钮属性
|
/** 悬浮按钮属性 */
|
||||||
export interface FloatingActionButtonProperty {
|
export interface FloatingActionButtonProperty {
|
||||||
// 展开方向
|
direction: 'horizontal' | 'vertical'; // 展开方向
|
||||||
direction: 'horizontal' | 'vertical';
|
showText: boolean; // 是否显示文字
|
||||||
// 是否显示文字
|
list: FloatingActionButtonItemProperty[]; // 按钮列表
|
||||||
showText: boolean;
|
|
||||||
// 按钮列表
|
|
||||||
list: FloatingActionButtonItemProperty[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 悬浮按钮项属性
|
/** 悬浮按钮项属性 */
|
||||||
export interface FloatingActionButtonItemProperty {
|
export interface FloatingActionButtonItemProperty {
|
||||||
// 图片地址
|
imgUrl: string; // 图片地址
|
||||||
imgUrl: string;
|
url: string; // 跳转连接
|
||||||
// 跳转连接
|
text: string; // 文字
|
||||||
url: string;
|
textColor: string; // 文字颜色
|
||||||
// 文字
|
|
||||||
text: string;
|
|
||||||
// 文字颜色
|
|
||||||
textColor: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件
|
/** 定义组件 */
|
||||||
export const component = {
|
export const component = {
|
||||||
id: 'FloatingActionButton',
|
id: 'FloatingActionButton',
|
||||||
name: '悬浮按钮',
|
name: '悬浮按钮',
|
||||||
|
|||||||
@@ -5,23 +5,20 @@ import { ref } from 'vue';
|
|||||||
|
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { ElImage, ElMessage } from 'element-plus';
|
import { ElButton, ElImage } from 'element-plus';
|
||||||
|
|
||||||
/** 悬浮按钮 */
|
/** 悬浮按钮 */
|
||||||
defineOptions({ name: 'FloatingActionButton' });
|
defineOptions({ name: 'FloatingActionButton' });
|
||||||
// 定义属性
|
|
||||||
|
/** 定义属性 */
|
||||||
defineProps<{ property: FloatingActionButtonProperty }>();
|
defineProps<{ property: FloatingActionButtonProperty }>();
|
||||||
|
|
||||||
// 是否展开
|
const expanded = ref(false); // 是否展开
|
||||||
const expanded = ref(false);
|
|
||||||
// 处理展开/折叠
|
|
||||||
const handleToggleFab = () => {
|
|
||||||
expanded.value = !expanded.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleActive = (index: number) => {
|
/** 处理展开/折叠 */
|
||||||
ElMessage.success(`点击了${index}`);
|
function handleToggleFab() {
|
||||||
};
|
expanded.value = !expanded.value;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
@@ -38,7 +35,6 @@ const handleActive = (index: number) => {
|
|||||||
v-for="(item, index) in property.list"
|
v-for="(item, index) in property.list"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="flex flex-col items-center"
|
class="flex flex-col items-center"
|
||||||
@click="handleActive(index)"
|
|
||||||
>
|
>
|
||||||
<ElImage :src="item.imgUrl" fit="contain" class="h-7 w-7">
|
<ElImage :src="item.imgUrl" fit="contain" class="h-7 w-7">
|
||||||
<template #error>
|
<template #error>
|
||||||
@@ -57,13 +53,13 @@ const handleActive = (index: number) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- todo: @owen 使用APP主题色 -->
|
<!-- todo: @owen 使用APP主题色 -->
|
||||||
<el-button type="primary" size="large" circle @click="handleToggleFab">
|
<ElButton type="primary" size="large" circle @click="handleToggleFab">
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
icon="ep:plus"
|
icon="ep:plus"
|
||||||
class="fab-icon"
|
class="fab-icon"
|
||||||
:class="[{ active: expanded }]"
|
:class="[{ active: expanded }]"
|
||||||
/>
|
/>
|
||||||
</el-button>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<!-- 模态背景:展开时显示,点击后折叠 -->
|
<!-- 模态背景:展开时显示,点击后折叠 -->
|
||||||
<div v-if="expanded" class="modal-bg" @click="handleToggleFab"></div>
|
<div v-if="expanded" class="modal-bg" @click="handleToggleFab"></div>
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ import {
|
|||||||
InputWithColor,
|
InputWithColor,
|
||||||
} from '#/views/mall/promotion/components';
|
} from '#/views/mall/promotion/components';
|
||||||
|
|
||||||
// 悬浮按钮属性面板
|
/** 悬浮按钮属性面板 */
|
||||||
defineOptions({ name: 'FloatingActionButtonProperty' });
|
defineOptions({ name: 'FloatingActionButtonProperty' });
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>();
|
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>();
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const formData = useVModel(props, 'modelValue', emit);
|
const formData = useVModel(props, 'modelValue', emit);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -64,5 +66,3 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
</ElCard>
|
</ElCard>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import type { StyleValue } from 'vue';
|
|||||||
|
|
||||||
import type { HotZoneItemProperty } from '../../config';
|
import type { HotZoneItemProperty } from '../../config';
|
||||||
|
|
||||||
// 热区的最小宽高
|
export const HOT_ZONE_MIN_SIZE = 100; // 热区的最小宽高
|
||||||
export const HOT_ZONE_MIN_SIZE = 100;
|
|
||||||
|
|
||||||
// 控制的类型
|
/** 控制的类型 */
|
||||||
export enum CONTROL_TYPE_ENUM {
|
export enum CONTROL_TYPE_ENUM {
|
||||||
LEFT,
|
LEFT,
|
||||||
TOP,
|
TOP,
|
||||||
@@ -13,14 +12,14 @@ export enum CONTROL_TYPE_ENUM {
|
|||||||
HEIGHT,
|
HEIGHT,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义热区的控制点
|
/** 定义热区的控制点 */
|
||||||
export interface ControlDot {
|
export interface ControlDot {
|
||||||
position: string;
|
position: string;
|
||||||
types: CONTROL_TYPE_ENUM[];
|
types: CONTROL_TYPE_ENUM[];
|
||||||
style: StyleValue;
|
style: StyleValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 热区的8个控制点
|
/** 热区的 8 个控制点 */
|
||||||
export const CONTROL_DOT_LIST = [
|
export const CONTROL_DOT_LIST = [
|
||||||
{
|
{
|
||||||
position: '左上角',
|
position: '左上角',
|
||||||
@@ -98,10 +97,10 @@ export const CONTROL_DOT_LIST = [
|
|||||||
] as ControlDot[];
|
] as ControlDot[];
|
||||||
|
|
||||||
// region 热区的缩放
|
// region 热区的缩放
|
||||||
// 热区的缩放比例
|
export const HOT_ZONE_SCALE_RATE = 2; // 热区的缩放比例
|
||||||
export const HOT_ZONE_SCALE_RATE = 2;
|
|
||||||
// 缩小:缩回适合手机屏幕的大小
|
/** 缩小:缩回适合手机屏幕的大小 */
|
||||||
export const zoomOut = (list?: HotZoneItemProperty[]) => {
|
export function zoomOut(list?: HotZoneItemProperty[]) {
|
||||||
return (
|
return (
|
||||||
list?.map((hotZone) => ({
|
list?.map((hotZone) => ({
|
||||||
...hotZone,
|
...hotZone,
|
||||||
@@ -111,9 +110,10 @@ export const zoomOut = (list?: HotZoneItemProperty[]) => {
|
|||||||
height: (hotZone.height /= HOT_ZONE_SCALE_RATE),
|
height: (hotZone.height /= HOT_ZONE_SCALE_RATE),
|
||||||
})) || []
|
})) || []
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
// 放大:作用是为了方便在电脑屏幕上编辑
|
|
||||||
export const zoomIn = (list?: HotZoneItemProperty[]) => {
|
/** 放大:作用是为了方便在电脑屏幕上编辑 */
|
||||||
|
export function zoomIn(list?: HotZoneItemProperty[]) {
|
||||||
return (
|
return (
|
||||||
list?.map((hotZone) => ({
|
list?.map((hotZone) => ({
|
||||||
...hotZone,
|
...hotZone,
|
||||||
@@ -123,7 +123,8 @@ export const zoomIn = (list?: HotZoneItemProperty[]) => {
|
|||||||
height: (hotZone.height *= HOT_ZONE_SCALE_RATE),
|
height: (hotZone.height *= HOT_ZONE_SCALE_RATE),
|
||||||
})) || []
|
})) || []
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import type { AppLink } from '#/views/mall/promotion/components/app-link-input/d
|
|||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
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 {
|
import {
|
||||||
CONTROL_DOT_LIST,
|
CONTROL_DOT_LIST,
|
||||||
@@ -22,7 +25,7 @@ import {
|
|||||||
/** 热区编辑对话框 */
|
/** 热区编辑对话框 */
|
||||||
defineOptions({ name: 'HotZoneEditDialog' });
|
defineOptions({ name: 'HotZoneEditDialog' });
|
||||||
|
|
||||||
// 定义属性
|
/** 定义属性 */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array<HotZoneItemProperty>,
|
type: Array<HotZoneItemProperty>,
|
||||||
@@ -33,51 +36,60 @@ const props = defineProps({
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const formData = ref<HotZoneItemProperty[]>([]);
|
const formData = ref<HotZoneItemProperty[]>([]);
|
||||||
|
|
||||||
// 弹窗的是否显示
|
const [Modal, modalApi] = useVbenModal({
|
||||||
const dialogVisible = ref(false);
|
showCancelButton: false,
|
||||||
// 打开弹窗
|
onConfirm() {
|
||||||
const open = () => {
|
const list = zoomOut(formData.value);
|
||||||
|
emit('update:modelValue', list);
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
function open() {
|
||||||
// 放大
|
// 放大
|
||||||
formData.value = zoomIn(props.modelValue);
|
formData.value = zoomIn(props.modelValue);
|
||||||
dialogVisible.value = true;
|
modalApi.open();
|
||||||
};
|
}
|
||||||
// 提供 open 方法,用于打开弹窗
|
|
||||||
defineExpose({ open });
|
|
||||||
|
|
||||||
// 热区容器
|
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||||
const container = ref<HTMLDivElement>();
|
|
||||||
|
|
||||||
// 增加热区
|
const container = ref<HTMLDivElement>(); // 热区容器
|
||||||
const handleAdd = () => {
|
|
||||||
|
/** 增加热区 */
|
||||||
|
function handleAdd() {
|
||||||
formData.value.push({
|
formData.value.push({
|
||||||
width: HOT_ZONE_MIN_SIZE,
|
width: HOT_ZONE_MIN_SIZE,
|
||||||
height: HOT_ZONE_MIN_SIZE,
|
height: HOT_ZONE_MIN_SIZE,
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
} as HotZoneItemProperty);
|
} 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) => {
|
useDraggable(item, e, (left, top, _, __, moveWidth, moveHeight) => {
|
||||||
setLeft(item, left + moveWidth);
|
setLeft(item, left + moveWidth);
|
||||||
setTop(item, top + moveHeight);
|
setTop(item, top + moveHeight);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
// 调整热区大小、位置
|
/** 调整热区大小、位置 */
|
||||||
const handleResize = (
|
function handleResize(
|
||||||
item: HotZoneItemProperty,
|
item: HotZoneItemProperty,
|
||||||
ctrlDot: ControlDot,
|
ctrlDot: ControlDot,
|
||||||
e: MouseEvent,
|
e: MouseEvent,
|
||||||
) => {
|
) {
|
||||||
useDraggable(item, e, (left, top, width, height, moveWidth, moveHeight) => {
|
useDraggable(item, e, (left, top, width, height, moveWidth, moveHeight) => {
|
||||||
ctrlDot.types.forEach((type) => {
|
ctrlDot.types.forEach((type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -112,23 +124,25 @@ const handleResize = (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
// 设置X轴坐标
|
/** 设置 X 轴坐标 */
|
||||||
const setLeft = (item: HotZoneItemProperty, left: number) => {
|
function setLeft(item: HotZoneItemProperty, left: number) {
|
||||||
// 不能超出容器
|
// 不能超出容器
|
||||||
if (left >= 0 && left <= container.value!.offsetWidth - item.width) {
|
if (left >= 0 && left <= container.value!.offsetWidth - item.width) {
|
||||||
item.left = left;
|
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) {
|
if (top >= 0 && top <= container.value!.offsetHeight - item.height) {
|
||||||
item.top = top;
|
item.top = top;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
// 设置宽度
|
|
||||||
|
/** 设置宽度 */
|
||||||
const setWidth = (item: HotZoneItemProperty, width: number) => {
|
const setWidth = (item: HotZoneItemProperty, width: number) => {
|
||||||
// 不能小于最小宽度 && 不能超出容器右边
|
// 不能小于最小宽度 && 不能超出容器右边
|
||||||
if (
|
if (
|
||||||
@@ -138,7 +152,8 @@ const setWidth = (item: HotZoneItemProperty, width: number) => {
|
|||||||
item.width = width;
|
item.width = width;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 设置高度
|
|
||||||
|
/** 设置高度 */
|
||||||
const setHeight = (item: HotZoneItemProperty, height: number) => {
|
const setHeight = (item: HotZoneItemProperty, height: number) => {
|
||||||
// 不能小于最小高度 && 不能超出容器底部
|
// 不能小于最小高度 && 不能超出容器底部
|
||||||
if (
|
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 activeHotZone = ref<HotZoneItemProperty>();
|
||||||
const appLinkDialogRef = ref();
|
const appLinkDialogRef = ref();
|
||||||
|
|
||||||
|
/** 显示 App 链接选择对话框 */
|
||||||
const handleShowAppLinkDialog = (hotZone: HotZoneItemProperty) => {
|
const handleShowAppLinkDialog = (hotZone: HotZoneItemProperty) => {
|
||||||
activeHotZone.value = hotZone;
|
activeHotZone.value = hotZone;
|
||||||
appLinkDialogRef.value.open(hotZone.url);
|
appLinkDialogRef.value.open(hotZone.url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 处理 App 链接选择变更 */
|
||||||
const handleAppLinkChange = (appLink: AppLink) => {
|
const handleAppLinkChange = (appLink: AppLink) => {
|
||||||
if (!appLink || !activeHotZone.value) return;
|
if (!appLink || !activeHotZone.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
activeHotZone.value.name = appLink.name;
|
activeHotZone.value.name = appLink.name;
|
||||||
activeHotZone.value.url = appLink.path;
|
activeHotZone.value.url = appLink.path;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ElDialog
|
<Modal title="设置热区" class="w-[780px]">
|
||||||
v-model="dialogVisible"
|
|
||||||
title="设置热区"
|
|
||||||
width="780"
|
|
||||||
@close="handleClose"
|
|
||||||
>
|
|
||||||
<div ref="container" class="w-750px relative h-full">
|
<div ref="container" class="w-750px relative h-full">
|
||||||
<ElImage
|
<ElImage
|
||||||
:src="imgUrl"
|
:src="imgUrl"
|
||||||
@@ -200,9 +203,9 @@ const handleAppLinkChange = (appLink: AppLink) => {
|
|||||||
@mousedown="handleMove(item, $event)"
|
@mousedown="handleMove(item, $event)"
|
||||||
@dblclick="handleShowAppLinkDialog(item)"
|
@dblclick="handleShowAppLinkDialog(item)"
|
||||||
>
|
>
|
||||||
<span class="pointer-events-none select-none">{{
|
<span class="pointer-events-none select-none">
|
||||||
item.name || '双击选择链接'
|
{{ item.name || '双击选择链接' }}
|
||||||
}}</span>
|
</span>
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
icon="ep:close"
|
icon="ep:close"
|
||||||
class="delete"
|
class="delete"
|
||||||
@@ -210,7 +213,7 @@ const handleAppLinkChange = (appLink: AppLink) => {
|
|||||||
@click="handleRemove(item)"
|
@click="handleRemove(item)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 8个控制点 -->
|
<!-- 8 个控制点 -->
|
||||||
<span
|
<span
|
||||||
class="ctrl-dot"
|
class="ctrl-dot"
|
||||||
v-for="(dot, dotIndex) in CONTROL_DOT_LIST"
|
v-for="(dot, dotIndex) in CONTROL_DOT_LIST"
|
||||||
@@ -220,17 +223,14 @@ const handleAppLinkChange = (appLink: AppLink) => {
|
|||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #prepend-footer>
|
||||||
<ElButton @click="handleAdd" type="primary" plain>
|
<ElButton @click="handleAdd" type="primary" plain>
|
||||||
<IconifyIcon icon="ep:plus" class="mr-5px" />
|
<IconifyIcon icon="ep:plus" class="mr-5px" />
|
||||||
添加热区
|
添加热区
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton @click="handleSubmit" type="primary" plain>
|
|
||||||
<IconifyIcon icon="ep:check" class="mr-5px" />
|
|
||||||
确定
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</Modal>
|
||||||
|
|
||||||
<AppLinkSelectDialog
|
<AppLinkSelectDialog
|
||||||
ref="appLinkDialogRef"
|
ref="appLinkDialogRef"
|
||||||
@app-link-change="handleAppLinkChange"
|
@app-link-change="handleAppLinkChange"
|
||||||
|
|||||||
@@ -2,31 +2,22 @@ import type { ComponentStyle, DiyComponent } from '../../../util';
|
|||||||
|
|
||||||
/** 热区属性 */
|
/** 热区属性 */
|
||||||
export interface HotZoneProperty {
|
export interface HotZoneProperty {
|
||||||
// 图片地址
|
imgUrl: string; // 图片地址
|
||||||
imgUrl: string;
|
list: HotZoneItemProperty[]; // 导航菜单列表
|
||||||
// 导航菜单列表
|
style: ComponentStyle; // 组件样式
|
||||||
list: HotZoneItemProperty[];
|
|
||||||
// 组件样式
|
|
||||||
style: ComponentStyle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 热区项目属性 */
|
/** 热区项目属性 */
|
||||||
export interface HotZoneItemProperty {
|
export interface HotZoneItemProperty {
|
||||||
// 链接的名称
|
name: string; // 链接的名称
|
||||||
name: string;
|
url: string; // 链接
|
||||||
// 链接
|
width: number; // 宽
|
||||||
url: string;
|
height: number; // 高
|
||||||
// 宽
|
top: number; // 上
|
||||||
width: number;
|
left: number; // 左
|
||||||
// 高
|
|
||||||
height: number;
|
|
||||||
// 上
|
|
||||||
top: number;
|
|
||||||
// 左
|
|
||||||
left: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件
|
/** 定义组件 */
|
||||||
export const component = {
|
export const component = {
|
||||||
id: 'HotZone',
|
id: 'HotZone',
|
||||||
name: '热区',
|
name: '热区',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ElImage } from 'element-plus';
|
|||||||
|
|
||||||
/** 热区 */
|
/** 热区 */
|
||||||
defineOptions({ name: 'HotZone' });
|
defineOptions({ name: 'HotZone' });
|
||||||
|
|
||||||
const props = defineProps<{ property: HotZoneProperty }>();
|
const props = defineProps<{ property: HotZoneProperty }>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ import HotZoneEditDialog from './components/hot-zone-edit-dialog/index.vue';
|
|||||||
defineOptions({ name: 'HotZoneProperty' });
|
defineOptions({ name: 'HotZoneProperty' });
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: HotZoneProperty }>();
|
const props = defineProps<{ modelValue: HotZoneProperty }>();
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const formData = useVModel(props, 'modelValue', emit);
|
const formData = useVModel(props, 'modelValue', emit);
|
||||||
|
|
||||||
// 热区编辑对话框
|
const editDialogRef = ref(); // 热区编辑对话框
|
||||||
const editDialogRef = ref();
|
|
||||||
// 打开热区编辑对话框
|
/** 打开热区编辑对话框 */
|
||||||
const handleOpenEditDialog = () => {
|
const handleOpenEditDialog = () => {
|
||||||
editDialogRef.value.open();
|
editDialogRef.value.open();
|
||||||
};
|
};
|
||||||
@@ -49,6 +51,7 @@ const handleOpenEditDialog = () => {
|
|||||||
设置热区
|
设置热区
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
|
|
||||||
<!-- 热区编辑对话框 -->
|
<!-- 热区编辑对话框 -->
|
||||||
<HotZoneEditDialog
|
<HotZoneEditDialog
|
||||||
ref="editDialogRef"
|
ref="editDialogRef"
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ import type { ComponentStyle, DiyComponent } from '../../../util';
|
|||||||
|
|
||||||
/** 图片展示属性 */
|
/** 图片展示属性 */
|
||||||
export interface ImageBarProperty {
|
export interface ImageBarProperty {
|
||||||
// 图片链接
|
imgUrl: string; // 图片链接
|
||||||
imgUrl: string;
|
url: string; // 跳转链接
|
||||||
// 跳转链接
|
style: ComponentStyle; // 组件样式
|
||||||
url: string;
|
|
||||||
// 组件样式
|
|
||||||
style: ComponentStyle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件
|
/** 定义组件 */
|
||||||
export const component = {
|
export const component = {
|
||||||
id: 'ImageBar',
|
id: 'ImageBar',
|
||||||
name: '图片展示',
|
name: '图片展示',
|
||||||
|
|||||||
@@ -11,21 +11,11 @@ defineOptions({ name: 'ImageBar' });
|
|||||||
defineProps<{ property: ImageBarProperty }>();
|
defineProps<{ property: ImageBarProperty }>();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<!-- 无图片 -->
|
|
||||||
<div
|
<div
|
||||||
class="flex h-12 items-center justify-center bg-gray-300"
|
class="flex h-12 items-center justify-center bg-gray-300"
|
||||||
v-if="!property.imgUrl"
|
v-if="!property.imgUrl"
|
||||||
>
|
>
|
||||||
<IconifyIcon icon="ep:picture" class="text-3xl text-gray-600" />
|
<IconifyIcon icon="ep:picture" class="text-3xl text-gray-600" />
|
||||||
</div>
|
</div>
|
||||||
<ElImage class="min-h-8" v-else :src="property.imgUrl" />
|
<ElImage v-else class="block w-full h-full" :src="property.imgUrl" />
|
||||||
</template>
|
</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 { useVModel } from '@vueuse/core';
|
||||||
import { ElForm, ElFormItem } from 'element-plus';
|
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 { AppLinkInput } from '#/views/mall/promotion/components';
|
||||||
|
|
||||||
import ComponentContainerProperty from '../../component-container-property.vue';
|
import ComponentContainerProperty from '../../component-container-property.vue';
|
||||||
|
|
||||||
// 图片展示属性面板
|
/** 图片展示属性面板 */
|
||||||
defineOptions({ name: 'ImageBarProperty' });
|
defineOptions({ name: 'ImageBarProperty' });
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: ImageBarProperty }>();
|
const props = defineProps<{ modelValue: ImageBarProperty }>();
|
||||||
@@ -21,7 +21,7 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
<ComponentContainerProperty v-model="formData.style">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<ElForm label-width="80px" :model="formData">
|
<ElForm label-width="80px" :model="formData">
|
||||||
<ElFormItem label="上传图片" prop="imgUrl">
|
<ElFormItem label="上传图片" prop="imgUrl">
|
||||||
<UploadImg
|
<ImageUpload
|
||||||
v-model="formData.imgUrl"
|
v-model="formData.imgUrl"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
height="80px"
|
height="80px"
|
||||||
@@ -30,7 +30,7 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
:show-description="false"
|
:show-description="false"
|
||||||
>
|
>
|
||||||
<template #tip> 建议宽度750 </template>
|
<template #tip> 建议宽度750 </template>
|
||||||
</UploadImg>
|
</ImageUpload>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="链接" prop="url">
|
<ElFormItem label="链接" prop="url">
|
||||||
<AppLinkInput v-model="formData.url" />
|
<AppLinkInput v-model="formData.url" />
|
||||||
@@ -38,5 +38,3 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||||||
</ElForm>
|
</ElForm>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -2,39 +2,26 @@ import type { ComponentStyle, DiyComponent } from '../../../util';
|
|||||||
|
|
||||||
/** 广告魔方属性 */
|
/** 广告魔方属性 */
|
||||||
export interface MagicCubeProperty {
|
export interface MagicCubeProperty {
|
||||||
// 上圆角
|
borderRadiusTop: number; // 上圆角
|
||||||
borderRadiusTop: number;
|
borderRadiusBottom: number; // 下圆角
|
||||||
// 下圆角
|
space: number; // 间隔
|
||||||
borderRadiusBottom: number;
|
list: MagicCubeItemProperty[]; // 导航菜单列表
|
||||||
// 间隔
|
style: ComponentStyle; // 组件样式
|
||||||
space: number;
|
|
||||||
// 导航菜单列表
|
|
||||||
list: MagicCubeItemProperty[];
|
|
||||||
// 组件样式
|
|
||||||
style: ComponentStyle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 广告魔方项目属性 */
|
/** 广告魔方项目属性 */
|
||||||
export interface MagicCubeItemProperty {
|
export interface MagicCubeItemProperty {
|
||||||
// 图标链接
|
imgUrl: string; // 图标链接
|
||||||
imgUrl: string;
|
url: string; // 链接
|
||||||
// 链接
|
width: number; // 宽
|
||||||
url: string;
|
height: number; // 高
|
||||||
// 宽
|
top: number; // 上
|
||||||
width: number;
|
left: number; // 左
|
||||||
// 高
|
right: number; // 右
|
||||||
height: number;
|
bottom: number; // 下
|
||||||
// 上
|
|
||||||
top: number;
|
|
||||||
// 左
|
|
||||||
left: number;
|
|
||||||
// 右
|
|
||||||
right: number;
|
|
||||||
// 下
|
|
||||||
bottom: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件
|
/** 定义组件 */
|
||||||
export const component = {
|
export const component = {
|
||||||
id: 'MagicCube',
|
id: 'MagicCube',
|
||||||
name: '广告魔方',
|
name: '广告魔方',
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import { ElImage } from 'element-plus';
|
|||||||
|
|
||||||
/** 广告魔方 */
|
/** 广告魔方 */
|
||||||
defineOptions({ name: 'MagicCube' });
|
defineOptions({ name: 'MagicCube' });
|
||||||
|
|
||||||
const props = defineProps<{ property: MagicCubeProperty }>();
|
const props = defineProps<{ property: MagicCubeProperty }>();
|
||||||
// 一个方块的大小
|
|
||||||
const CUBE_SIZE = 93.75;
|
const CUBE_SIZE = 93.75; // 一个方块的大小
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算方块的行数
|
* 计算方块的行数
|
||||||
* 行数用于计算魔方的总体高度,存在以下情况:
|
* 行数用于计算魔方的总体高度,存在以下情况:
|
||||||
@@ -80,5 +82,3 @@ const rowCount = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ import ComponentContainerProperty from '../../component-container-property.vue';
|
|||||||
defineOptions({ name: 'MagicCubeProperty' });
|
defineOptions({ name: 'MagicCubeProperty' });
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: MagicCubeProperty }>();
|
const props = defineProps<{ modelValue: MagicCubeProperty }>();
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const formData = useVModel(props, 'modelValue', emit);
|
const formData = useVModel(props, 'modelValue', emit);
|
||||||
|
|
||||||
// 选中的热区
|
const selectedHotAreaIndex = ref(-1); // 选中的热区
|
||||||
const selectedHotAreaIndex = ref(-1);
|
|
||||||
|
/** 处理热区被选中事件 */
|
||||||
const handleHotAreaSelected = (_: any, index: number) => {
|
const handleHotAreaSelected = (_: any, index: number) => {
|
||||||
selectedHotAreaIndex.value = index;
|
selectedHotAreaIndex.value = index;
|
||||||
};
|
};
|
||||||
@@ -30,7 +33,6 @@ const handleHotAreaSelected = (_: any, index: number) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ComponentContainerProperty v-model="formData.style">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<!-- 表单 -->
|
|
||||||
<ElForm label-width="80px" :model="formData" class="mt-2">
|
<ElForm label-width="80px" :model="formData" class="mt-2">
|
||||||
<ElText tag="p"> 魔方设置 </ElText>
|
<ElText tag="p"> 魔方设置 </ElText>
|
||||||
<ElText type="info" size="small"> 每格尺寸187 * 187 </ElText>
|
<ElText type="info" size="small"> 每格尺寸187 * 187 </ElText>
|
||||||
@@ -89,5 +91,3 @@ const handleHotAreaSelected = (_: any, index: number) => {
|
|||||||
</ElForm>
|
</ElForm>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
|||||||
@@ -541,7 +541,7 @@ $phone-width: 375px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 属性面板分组 */
|
/* 属性面板分组 */
|
||||||
:deep(.property-group) {
|
.property-group {
|
||||||
margin: 0 -20px;
|
margin: 0 -20px;
|
||||||
|
|
||||||
&.el-card {
|
&.el-card {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { default as AppLinkInput } from './app-link-input/index.vue';
|
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 ColorInput } from './color-input/index.vue';
|
||||||
export { default as DiyEditor } from './diy-editor/index.vue';
|
export { default as DiyEditor } from './diy-editor/index.vue';
|
||||||
export { type DiyComponentLibrary, PAGE_LIBS } from './diy-editor/util';
|
export { type DiyComponentLibrary, PAGE_LIBS } from './diy-editor/util';
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
|
|
||||||
import { createRect, isContains, isOverlap } from './util';
|
import { createRect, isContains, isOverlap } from './util';
|
||||||
|
|
||||||
// TODO @AI: 改成标准注释
|
/**
|
||||||
// 魔方编辑器
|
* 魔方编辑器,有两部分组成:
|
||||||
// 有两部分组成:
|
* 1. 魔方矩阵:位于底层,由方块组件的二维表格,用于创建热区
|
||||||
// 1. 魔方矩阵:位于底层,由方块组件的二维表格,用于创建热区
|
* 操作方法:
|
||||||
// 操作方法:
|
* 1.1 点击其中一个方块就会进入热区选择模式
|
||||||
// 1.1 点击其中一个方块就会进入热区选择模式
|
* 1.2 再次点击另外一个方块时,结束热区选择模式
|
||||||
// 1.2 再次点击另外一个方块时,结束热区选择模式
|
* 1.3 在两个方块中间的区域创建热区
|
||||||
// 1.3 在两个方块中间的区域创建热区
|
* 如果两次点击的都是同一方块,就只创建一个格子的热区
|
||||||
// 如果两次点击的都是同一方块,就只创建一个格子的热区
|
* 2. 热区:位于顶层,采用绝对定位,覆盖在魔方矩阵上面。
|
||||||
// 2. 热区:位于顶层,采用绝对定位,覆盖在魔方矩阵上面。
|
*/
|
||||||
defineOptions({ name: 'MagicCubeEditor' });
|
defineOptions({ name: 'MagicCubeEditor' });
|
||||||
|
|
||||||
/** 定义属性 */
|
/** 定义属性 */
|
||||||
@@ -29,12 +29,10 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 4,
|
default: 4,
|
||||||
}, // 行数,默认 4 行
|
}, // 行数,默认 4 行
|
||||||
|
|
||||||
cols: {
|
cols: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 4,
|
default: 4,
|
||||||
}, // 列数,默认 4 列
|
}, // 列数,默认 4 列
|
||||||
|
|
||||||
cubeSize: {
|
cubeSize: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 75,
|
default: 75,
|
||||||
@@ -70,6 +68,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const hotAreas = ref<Rect[]>([]); // 热区列表
|
const hotAreas = ref<Rect[]>([]); // 热区列表
|
||||||
|
|
||||||
/** 初始化热区 */
|
/** 初始化热区 */
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
@@ -86,20 +85,20 @@ const isHotAreaSelectMode = () => !!hotAreaBeginCube.value; // 是否开启了
|
|||||||
* @param currentRow 当前行号
|
* @param currentRow 当前行号
|
||||||
* @param currentCol 当前列号
|
* @param currentCol 当前列号
|
||||||
*/
|
*/
|
||||||
const handleCubeClick = (currentRow: number, currentCol: number) => {
|
function handleCubeClick(currentRow: number, currentCol: number) {
|
||||||
const currentCube = cubes.value[currentRow]?.[currentCol];
|
const currentCube = cubes.value[currentRow]?.[currentCol];
|
||||||
if (!currentCube) {
|
if (!currentCube) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 情况1:进入热区选择模式
|
// 情况 1:进入热区选择模式
|
||||||
if (!isHotAreaSelectMode()) {
|
if (!isHotAreaSelectMode()) {
|
||||||
hotAreaBeginCube.value = currentCube;
|
hotAreaBeginCube.value = currentCube;
|
||||||
hotAreaBeginCube.value!.active = true;
|
hotAreaBeginCube.value!.active = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 情况2:结束热区选择模式
|
// 情况 2:结束热区选择模式
|
||||||
hotAreas.value.push(createRect(hotAreaBeginCube.value!, currentCube));
|
hotAreas.value.push(createRect(hotAreaBeginCube.value!, currentCube));
|
||||||
// 结束热区选择模式
|
// 结束热区选择模式
|
||||||
exitHotAreaSelectMode();
|
exitHotAreaSelectMode();
|
||||||
@@ -111,7 +110,7 @@ const handleCubeClick = (currentRow: number, currentCol: number) => {
|
|||||||
}
|
}
|
||||||
// 发送热区变动通知
|
// 发送热区变动通知
|
||||||
emitUpdateModelValue();
|
emitUpdateModelValue();
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理鼠标经过方块
|
* 处理鼠标经过方块
|
||||||
@@ -119,7 +118,7 @@ const handleCubeClick = (currentRow: number, currentCol: number) => {
|
|||||||
* @param currentRow 当前行号
|
* @param currentRow 当前行号
|
||||||
* @param currentCol 当前列号
|
* @param currentCol 当前列号
|
||||||
*/
|
*/
|
||||||
const handleCellHover = (currentRow: number, currentCol: number) => {
|
function handleCellHover(currentRow: number, currentCol: number) {
|
||||||
// 当前没有进入热区选择模式
|
// 当前没有进入热区选择模式
|
||||||
if (!isHotAreaSelectMode()) {
|
if (!isHotAreaSelectMode()) {
|
||||||
return;
|
return;
|
||||||
@@ -138,7 +137,6 @@ const handleCellHover = (currentRow: number, currentCol: number) => {
|
|||||||
if (isOverlap(hotArea, currentSelectedArea)) {
|
if (isOverlap(hotArea, currentSelectedArea)) {
|
||||||
// 结束热区选择模式
|
// 结束热区选择模式
|
||||||
exitHotAreaSelectMode();
|
exitHotAreaSelectMode();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,13 +145,9 @@ const handleCellHover = (currentRow: number, currentCol: number) => {
|
|||||||
eachCube((_, __, cube) => {
|
eachCube((_, __, cube) => {
|
||||||
cube.active = isContains(currentSelectedArea, cube);
|
cube.active = isContains(currentSelectedArea, cube);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/** 处理热区删除 */
|
||||||
* 处理热区删除
|
|
||||||
*
|
|
||||||
* @param index 热区索引
|
|
||||||
*/
|
|
||||||
function handleDeleteHotArea(index: number) {
|
function handleDeleteHotArea(index: number) {
|
||||||
hotAreas.value.splice(index, 1);
|
hotAreas.value.splice(index, 1);
|
||||||
// 结束热区选择模式
|
// 结束热区选择模式
|
||||||
@@ -165,14 +159,14 @@ function handleDeleteHotArea(index: number) {
|
|||||||
const emitUpdateModelValue = () => emit('update:modelValue', hotAreas.value); // 发送热区变动通知
|
const emitUpdateModelValue = () => emit('update:modelValue', hotAreas.value); // 发送热区变动通知
|
||||||
|
|
||||||
const selectedHotAreaIndex = ref(0); // 热区选中
|
const selectedHotAreaIndex = ref(0); // 热区选中
|
||||||
const handleHotAreaSelected = (hotArea: Rect, index: number) => {
|
|
||||||
|
/** 处理热区选中 */
|
||||||
|
function handleHotAreaSelected(hotArea: Rect, index: number) {
|
||||||
selectedHotAreaIndex.value = index;
|
selectedHotAreaIndex.value = index;
|
||||||
emit('hotAreaSelected', hotArea, index);
|
emit('hotAreaSelected', hotArea, index);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/** 结束热区选择模式 */
|
||||||
* 结束热区选择模式
|
|
||||||
*/
|
|
||||||
function exitHotAreaSelectMode() {
|
function exitHotAreaSelectMode() {
|
||||||
// 移除方块激活标记
|
// 移除方块激活标记
|
||||||
eachCube((_, __, cube) => {
|
eachCube((_, __, cube) => {
|
||||||
@@ -246,9 +240,9 @@ const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {
|
|||||||
>
|
>
|
||||||
<IconifyIcon icon="ep:circle-close-filled" />
|
<IconifyIcon icon="ep:circle-close-filled" />
|
||||||
</div>
|
</div>
|
||||||
<span v-if="hotArea.width">{{
|
<span v-if="hotArea.width">
|
||||||
`${hotArea.width}×${hotArea.height}`
|
{{ `${hotArea.width}×${hotArea.height}` }}
|
||||||
}}</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -265,6 +259,11 @@ const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid var(--el-border-color);
|
border: 1px solid var(--el-border-color);
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
:deep(.iconify) {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: var(--el-color-primary-light-9);
|
background: var(--el-color-primary-light-9);
|
||||||
|
|||||||
@@ -1,51 +1,47 @@
|
|||||||
// 坐标点
|
/** 坐标点 */
|
||||||
export interface Point {
|
export interface Point {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 矩形
|
/** 矩形 */
|
||||||
export interface Rect {
|
export interface Rect {
|
||||||
// 左上角 X 轴坐标
|
left: number; // 左上角 X 轴坐标
|
||||||
left: number;
|
top: number; // 左上角 Y 轴坐标
|
||||||
// 左上角 Y 轴坐标
|
right: number; // 右下角 X 轴坐标
|
||||||
top: number;
|
bottom: number; // 右下角 Y 轴坐标
|
||||||
// 右下角 X 轴坐标
|
width: number; // 矩形宽度
|
||||||
right: number;
|
height: number; // 矩形高度
|
||||||
// 右下角 Y 轴坐标
|
|
||||||
bottom: number;
|
|
||||||
// 矩形宽度
|
|
||||||
width: number;
|
|
||||||
// 矩形高度
|
|
||||||
height: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断两个矩形是否重叠
|
* 判断两个矩形是否重叠
|
||||||
|
*
|
||||||
* @param a 矩形 A
|
* @param a 矩形 A
|
||||||
* @param b 矩形 B
|
* @param b 矩形 B
|
||||||
*/
|
*/
|
||||||
export const isOverlap = (a: Rect, b: Rect): boolean => {
|
export function isOverlap(a: Rect, b: Rect): boolean {
|
||||||
return (
|
return (
|
||||||
a.left < b.left + b.width &&
|
a.left < b.left + b.width &&
|
||||||
a.left + a.width > b.left &&
|
a.left + a.width > b.left &&
|
||||||
a.top < b.top + b.height &&
|
a.top < b.top + b.height &&
|
||||||
a.height + a.top > b.top
|
a.height + a.top > b.top
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查坐标点是否在矩形内
|
* 检查坐标点是否在矩形内
|
||||||
* @param hotArea 矩形
|
* @param hotArea 矩形
|
||||||
* @param point 坐标
|
* @param point 坐标
|
||||||
*/
|
*/
|
||||||
export const isContains = (hotArea: Rect, point: Point): boolean => {
|
export function isContains(hotArea: Rect, point: Point): boolean {
|
||||||
return (
|
return (
|
||||||
point.x >= hotArea.left &&
|
point.x >= hotArea.left &&
|
||||||
point.x < hotArea.right &&
|
point.x < hotArea.right &&
|
||||||
point.y >= hotArea.top &&
|
point.y >= hotArea.top &&
|
||||||
point.y < hotArea.bottom
|
point.y < hotArea.bottom
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在两个坐标点中间,创建一个矩形
|
* 在两个坐标点中间,创建一个矩形
|
||||||
@@ -59,14 +55,17 @@ export const isContains = (hotArea: Rect, point: Point): boolean => {
|
|||||||
* @param a 坐标点一
|
* @param a 坐标点一
|
||||||
* @param b 坐标点二
|
* @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();
|
let [left, left2] = [a.x, b.x].sort();
|
||||||
const [top, top2] = [a.y, b.y].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 right = left2 + 1;
|
||||||
const bottom = top2 + 1;
|
const bottom = top2 + 1;
|
||||||
const height = bottom - top;
|
const height = bottom - top;
|
||||||
const width = right - left;
|
const width = right - left;
|
||||||
|
|
||||||
return { left, right, top, bottom, height, width };
|
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';
|
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 { sendCoupon } from '#/api/mall/promotion/coupon/coupon';
|
||||||
import { getCouponTemplatePage } from '#/api/mall/promotion/coupon/couponTemplate';
|
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) {
|
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 type { DescriptionItemSchema } from '#/components/description';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
@@ -20,19 +19,19 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'order.deliveryType',
|
field: 'order.deliveryType',
|
||||||
label: '配送方式',
|
label: '配送方式',
|
||||||
content: (data: MallAfterSaleApi.AfterSale) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TRADE_DELIVERY_TYPE,
|
type: DICT_TYPE.TRADE_DELIVERY_TYPE,
|
||||||
value: data?.order?.deliveryType,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'order.type',
|
field: 'order.type',
|
||||||
label: '订单类型',
|
label: '订单类型',
|
||||||
content: (data: MallAfterSaleApi.AfterSale) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TRADE_ORDER_TYPE,
|
type: DICT_TYPE.TRADE_ORDER_TYPE,
|
||||||
value: data?.order?.type,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -46,10 +45,10 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'order.terminal',
|
field: 'order.terminal',
|
||||||
label: '订单来源',
|
label: '订单来源',
|
||||||
content: (data: MallAfterSaleApi.AfterSale) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TERMINAL,
|
type: DICT_TYPE.TERMINAL,
|
||||||
value: data?.order?.terminal,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,10 +66,10 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'order.payChannelCode',
|
field: 'order.payChannelCode',
|
||||||
label: '付款方式',
|
label: '付款方式',
|
||||||
content: (data: MallAfterSaleApi.AfterSale) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.PAY_CHANNEL_CODE,
|
type: DICT_TYPE.PAY_CHANNEL_CODE,
|
||||||
value: data?.order?.payChannelCode,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -90,32 +89,30 @@ export function useAfterSaleInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'auditTime',
|
field: 'auditTime',
|
||||||
label: '申请时间',
|
label: '申请时间',
|
||||||
content: (data: MallAfterSaleApi.AfterSale) =>
|
render: (val) => formatDate(val) as string,
|
||||||
formatDate(data?.auditTime) as string,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'type',
|
field: 'type',
|
||||||
label: '售后类型',
|
label: '售后类型',
|
||||||
content: (data: MallAfterSaleApi.AfterSale) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TRADE_AFTER_SALE_TYPE,
|
type: DICT_TYPE.TRADE_AFTER_SALE_TYPE,
|
||||||
value: data?.type,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'way',
|
field: 'way',
|
||||||
label: '售后方式',
|
label: '售后方式',
|
||||||
content: (data: MallAfterSaleApi.AfterSale) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TRADE_AFTER_SALE_WAY,
|
type: DICT_TYPE.TRADE_AFTER_SALE_WAY,
|
||||||
value: data?.way,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'refundPrice',
|
field: 'refundPrice',
|
||||||
label: '退款金额',
|
label: '退款金额',
|
||||||
content: (data: MallAfterSaleApi.AfterSale) =>
|
render: (val) => fenToYuan(val ?? 0),
|
||||||
fenToYuan(data?.refundPrice ?? 0),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'applyReason',
|
field: 'applyReason',
|
||||||
@@ -128,8 +125,8 @@ export function useAfterSaleInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'applyPicUrls',
|
field: 'applyPicUrls',
|
||||||
label: '凭证图片',
|
label: '凭证图片',
|
||||||
content: (data) => {
|
render: (val) => {
|
||||||
const images = data?.applyPicUrls || [];
|
const images = val || [];
|
||||||
return h(
|
return h(
|
||||||
'div',
|
'div',
|
||||||
{ class: 'flex gap-10px' },
|
{ class: 'flex gap-10px' },
|
||||||
@@ -153,16 +150,16 @@ export function useRefundStatusSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
label: '退款状态',
|
label: '退款状态',
|
||||||
content: (data) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TRADE_AFTER_SALE_STATUS,
|
type: DICT_TYPE.TRADE_AFTER_SALE_STATUS,
|
||||||
value: data?.status,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'reminder',
|
field: 'reminder',
|
||||||
label: '提醒',
|
label: '提醒',
|
||||||
content: () =>
|
render: () =>
|
||||||
h('div', { class: 'text-red-500 mb-10px' }, [
|
h('div', { class: 'text-red-500 mb-10px' }, [
|
||||||
h('div', '如果未发货,请点击同意退款给买家。'),
|
h('div', '如果未发货,请点击同意退款给买家。'),
|
||||||
h('div', '如果实际已发货,请主动与买家联系。'),
|
h('div', '如果实际已发货,请主动与买家联系。'),
|
||||||
|
|||||||
@@ -49,38 +49,29 @@ const afterSale = ref<MallAfterSaleApi.AfterSale>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [OrderDescriptions] = useDescription({
|
const [OrderDescriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
title: '订单信息',
|
title: '订单信息',
|
||||||
border: false,
|
border: false,
|
||||||
column: 3,
|
column: 3,
|
||||||
direction: 'horizontal',
|
direction: 'horizontal',
|
||||||
labelWidth: 140,
|
labelWidth: 140,
|
||||||
extra: '',
|
|
||||||
},
|
|
||||||
schema: useOrderInfoSchema(),
|
schema: useOrderInfoSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [AfterSaleDescriptions] = useDescription({
|
const [AfterSaleDescriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
title: '售后信息',
|
title: '售后信息',
|
||||||
border: false,
|
border: false,
|
||||||
column: 3,
|
column: 3,
|
||||||
direction: 'horizontal',
|
direction: 'horizontal',
|
||||||
labelWidth: 140,
|
labelWidth: 140,
|
||||||
extra: '',
|
|
||||||
},
|
|
||||||
schema: useAfterSaleInfoSchema(),
|
schema: useAfterSaleInfoSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [RefundStatusDescriptions] = useDescription({
|
const [RefundStatusDescriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
title: '退款状态',
|
title: '退款状态',
|
||||||
border: false,
|
border: false,
|
||||||
column: 1,
|
column: 1,
|
||||||
direction: 'horizontal',
|
direction: 'horizontal',
|
||||||
labelWidth: 140,
|
labelWidth: 140,
|
||||||
extra: '',
|
|
||||||
},
|
|
||||||
schema: useRefundStatusSchema(),
|
schema: useRefundStatusSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { MallOrderApi } from '#/api/mall/trade/order';
|
|
||||||
import type { DescriptionItemSchema } from '#/components/description';
|
import type { DescriptionItemSchema } from '#/components/description';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
@@ -23,19 +22,19 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'type',
|
field: 'type',
|
||||||
label: '订单类型',
|
label: '订单类型',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TRADE_ORDER_TYPE,
|
type: DICT_TYPE.TRADE_ORDER_TYPE,
|
||||||
value: data?.type,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'terminal',
|
field: 'terminal',
|
||||||
label: '订单来源',
|
label: '订单来源',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TERMINAL,
|
type: DICT_TYPE.TERMINAL,
|
||||||
value: data?.terminal,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -53,10 +52,10 @@ export function useOrderInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'payChannelCode',
|
field: 'payChannelCode',
|
||||||
label: '付款方式',
|
label: '付款方式',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.PAY_CHANNEL_CODE,
|
type: DICT_TYPE.PAY_CHANNEL_CODE,
|
||||||
value: data?.payChannelCode,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -72,16 +71,16 @@ export function useOrderStatusSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
label: '订单状态',
|
label: '订单状态',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TRADE_ORDER_STATUS,
|
type: DICT_TYPE.TRADE_ORDER_STATUS,
|
||||||
value: data?.status,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'reminder',
|
field: 'reminder',
|
||||||
label: '提醒',
|
label: '提醒',
|
||||||
content: () =>
|
render: () =>
|
||||||
h('div', { class: 'space-y-1' }, [
|
h('div', { class: 'space-y-1' }, [
|
||||||
h('div', '买家付款成功后,货款将直接进入您的商户号(微信、支付宝)'),
|
h('div', '买家付款成功后,货款将直接进入您的商户号(微信、支付宝)'),
|
||||||
h('div', '请及时关注你发出的包裹状态,确保可以配送至买家手中'),
|
h('div', '请及时关注你发出的包裹状态,确保可以配送至买家手中'),
|
||||||
@@ -100,66 +99,46 @@ export function useOrderPriceSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'totalPrice',
|
field: 'totalPrice',
|
||||||
label: '商品总额',
|
label: '商品总额',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) => `${fenToYuan(val ?? 0)} 元`,
|
||||||
`${fenToYuan(data?.totalPrice ?? 0)} 元`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'deliveryPrice',
|
field: 'deliveryPrice',
|
||||||
label: '运费金额',
|
label: '运费金额',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) => `${fenToYuan(val ?? 0)} 元`,
|
||||||
`${fenToYuan(data?.deliveryPrice ?? 0)} 元`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'adjustPrice',
|
field: 'adjustPrice',
|
||||||
label: '订单调价',
|
label: '订单调价',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) => `${fenToYuan(val ?? 0)} 元`,
|
||||||
`${fenToYuan(data?.adjustPrice ?? 0)} 元`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'couponPrice',
|
field: 'couponPrice',
|
||||||
label: '优惠劵优惠',
|
label: '优惠劵优惠',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(
|
h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 元`),
|
||||||
'span',
|
|
||||||
{ class: 'text-red-500' },
|
|
||||||
`${fenToYuan(data?.couponPrice ?? 0)} 元`,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'vipPrice',
|
field: 'vipPrice',
|
||||||
label: 'VIP 优惠',
|
label: 'VIP 优惠',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(
|
h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 元`),
|
||||||
'span',
|
|
||||||
{ class: 'text-red-500' },
|
|
||||||
`${fenToYuan(data?.vipPrice ?? 0)} 元`,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'discountPrice',
|
field: 'discountPrice',
|
||||||
label: '活动优惠',
|
label: '活动优惠',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(
|
h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 元`),
|
||||||
'span',
|
|
||||||
{ class: 'text-red-500' },
|
|
||||||
`${fenToYuan(data?.discountPrice ?? 0)} 元`,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'pointPrice',
|
field: 'pointPrice',
|
||||||
label: '积分抵扣',
|
label: '积分抵扣',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(
|
h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 元`),
|
||||||
'span',
|
|
||||||
{ class: 'text-red-500' },
|
|
||||||
`${fenToYuan(data?.pointPrice ?? 0)} 元`,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'payPrice',
|
field: 'payPrice',
|
||||||
label: '应付金额',
|
label: '应付金额',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) => `${fenToYuan(val ?? 0)} 元`,
|
||||||
`${fenToYuan(data?.payPrice ?? 0)} 元`,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -170,10 +149,10 @@ export function useDeliveryInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'deliveryType',
|
field: 'deliveryType',
|
||||||
label: '配送方式',
|
label: '配送方式',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.TRADE_DELIVERY_TYPE,
|
type: DICT_TYPE.TRADE_DELIVERY_TYPE,
|
||||||
value: data?.deliveryType,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -187,14 +166,12 @@ export function useDeliveryInfoSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'receiverAddress',
|
field: 'receiverAddress',
|
||||||
label: '收货地址',
|
label: '收货地址',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val, data) => `${data?.receiverAreaName} ${val}`.trim(),
|
||||||
`${data?.receiverAreaName} ${data?.receiverDetailAddress}`.trim(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'deliveryTime',
|
field: 'deliveryTime',
|
||||||
label: '发货时间',
|
label: '发货时间',
|
||||||
content: (data: MallOrderApi.Order) =>
|
render: (val) => formatDateTime(val) as string,
|
||||||
formatDateTime(data?.deliveryTime) as string,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,38 +57,30 @@ const expressTrackList = ref<any[]>([]);
|
|||||||
const pickUpStore = ref<MallDeliveryPickUpStoreApi.PickUpStore | undefined>();
|
const pickUpStore = ref<MallDeliveryPickUpStoreApi.PickUpStore | undefined>();
|
||||||
|
|
||||||
const [OrderInfoDescriptions] = useDescription({
|
const [OrderInfoDescriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
title: '订单信息',
|
title: '订单信息',
|
||||||
border: false,
|
border: false,
|
||||||
column: 3,
|
column: 3,
|
||||||
},
|
|
||||||
schema: useOrderInfoSchema(),
|
schema: useOrderInfoSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [OrderStatusDescriptions] = useDescription({
|
const [OrderStatusDescriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
title: '订单状态',
|
title: '订单状态',
|
||||||
border: false,
|
border: false,
|
||||||
column: 1,
|
column: 1,
|
||||||
},
|
|
||||||
schema: useOrderStatusSchema(),
|
schema: useOrderStatusSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [OrderPriceDescriptions] = useDescription({
|
const [OrderPriceDescriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
title: '费用信息',
|
title: '费用信息',
|
||||||
border: false,
|
border: false,
|
||||||
column: 4,
|
column: 4,
|
||||||
},
|
|
||||||
schema: useOrderPriceSchema(),
|
schema: useOrderPriceSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [DeliveryInfoDescriptions] = useDescription({
|
const [DeliveryInfoDescriptions] = useDescription({
|
||||||
componentProps: {
|
|
||||||
title: '收货信息',
|
title: '收货信息',
|
||||||
border: false,
|
border: false,
|
||||||
column: 3,
|
column: 3,
|
||||||
},
|
|
||||||
schema: useDeliveryInfoSchema(),
|
schema: useDeliveryInfoSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { PayNotifyApi } from '#/api/pay/notify';
|
|
||||||
import type { DescriptionItemSchema } from '#/components/description';
|
import type { DescriptionItemSchema } from '#/components/description';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
@@ -182,10 +181,10 @@ export function useDetailSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'type',
|
field: 'type',
|
||||||
label: '通知类型',
|
label: '通知类型',
|
||||||
content: (data: PayNotifyApi.NotifyTask) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.PAY_NOTIFY_TYPE,
|
type: DICT_TYPE.PAY_NOTIFY_TYPE,
|
||||||
value: data?.type,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -195,10 +194,10 @@ export function useDetailSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
label: '通知状态',
|
label: '通知状态',
|
||||||
content: (data: PayNotifyApi.NotifyTask) =>
|
render: (val) =>
|
||||||
h(DictTag, {
|
h(DictTag, {
|
||||||
type: DICT_TYPE.PAY_NOTIFY_STATUS,
|
type: DICT_TYPE.PAY_NOTIFY_STATUS,
|
||||||
value: data?.status,
|
value: val,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -208,14 +207,12 @@ export function useDetailSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'lastExecuteTime',
|
field: 'lastExecuteTime',
|
||||||
label: '最后通知时间',
|
label: '最后通知时间',
|
||||||
content: (data: PayNotifyApi.NotifyTask) =>
|
render: (val) => formatDateTime(val) as string,
|
||||||
formatDateTime(data?.lastExecuteTime) as string,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'nextNotifyTime',
|
field: 'nextNotifyTime',
|
||||||
label: '下次通知时间',
|
label: '下次通知时间',
|
||||||
content: (data: PayNotifyApi.NotifyTask) =>
|
render: (val) => formatDateTime(val) as string,
|
||||||
formatDateTime(data?.nextNotifyTime) as string,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'notifyTimes',
|
field: 'notifyTimes',
|
||||||
@@ -228,14 +225,12 @@ export function useDetailSchema(): DescriptionItemSchema[] {
|
|||||||
{
|
{
|
||||||
field: 'createTime',
|
field: 'createTime',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
content: (data: PayNotifyApi.NotifyTask) =>
|
render: (val) => formatDateTime(val) as string,
|
||||||
formatDateTime(data?.createTime) as string,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'updateTime',
|
field: 'updateTime',
|
||||||
label: '更新时间',
|
label: '更新时间',
|
||||||
content: (data: PayNotifyApi.NotifyTask) =>
|
render: (val) => formatDateTime(val) as string,
|
||||||
formatDateTime(data?.updateTime) as string,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,10 @@ import { useDetailLogColumns, useDetailSchema } from '../data';
|
|||||||
const formData = ref<PayNotifyApi.NotifyTask>();
|
const formData = ref<PayNotifyApi.NotifyTask>();
|
||||||
|
|
||||||
const [Description] = useDescription({
|
const [Description] = useDescription({
|
||||||
componentProps: {
|
|
||||||
border: true,
|
border: true,
|
||||||
column: 2,
|
column: 2,
|
||||||
direction: 'horizontal',
|
direction: 'horizontal',
|
||||||
labelWidth: 140,
|
labelWidth: 140,
|
||||||
title: '',
|
|
||||||
extra: '',
|
|
||||||
},
|
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<PayOrderApi.Order>();
|
const formData = ref<PayOrderApi.Order>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
|
border: true,
|
||||||
column: 2,
|
column: 2,
|
||||||
labelWidth: 140,
|
labelWidth: 140,
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
|
border: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
labelWidth: 140,
|
labelWidth: 140,
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
const formData = ref<SystemNotifyMessageApi.NotifyMessage>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
|
border: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
labelWidth: 140,
|
labelWidth: 140,
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemOperateLogApi.OperateLog>();
|
const formData = ref<SystemOperateLogApi.OperateLog>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
|
border: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
direction: 'horizontal',
|
|
||||||
labelWidth: 110,
|
labelWidth: 110,
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import { useDetailSchema } from '../data';
|
|||||||
const formData = ref<SystemSocialUserApi.SocialUser>();
|
const formData = ref<SystemSocialUserApi.SocialUser>();
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
|
border: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
|
size: 'large',
|
||||||
labelWidth: 185,
|
labelWidth: 185,
|
||||||
schema: useDetailSchema(),
|
schema: useDetailSchema(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,8 +95,8 @@
|
|||||||
"vue-tsc": "catalog:"
|
"vue-tsc": "catalog:"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.10.0",
|
"node": ">=20.12.0",
|
||||||
"pnpm": ">=9.12.0"
|
"pnpm": ">=10.14.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.14.0",
|
"packageManager": "pnpm@10.14.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
607
pnpm-lock.yaml
generated
607
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user