feat: (web-ele)新增颜色输入框组件并优化图片上传组件
- 新增 ColorInput 组件用于颜色选择 - 重构 ImageUpload 组件,增加编辑和删除功能 - 更新 DIY 编辑器相关组件,优化用户体验 - 添加商城 H5 预览地址配置 - 优化导航栏单元格属性配置pull/194/head
parent
1f155fa7c5
commit
e7fc44715b
|
@ -21,7 +21,8 @@
|
|||
// CSS 变量提示
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
// 在 package.json 中显示 PNPM catalog 的版本
|
||||
"antfu.pnpm-catalog-lens"
|
||||
"antfu.pnpm-catalog-lens",
|
||||
"augment.vscode-augment"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
// 和 volar 冲突
|
||||
|
|
|
@ -19,3 +19,5 @@ VITE_INJECT_APP_LOADING=true
|
|||
VITE_APP_DEFAULT_USERNAME=admin
|
||||
# 默认登录密码
|
||||
VITE_APP_DEFAULT_PASSWORD=admin123
|
||||
|
||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||
|
|
|
@ -21,3 +21,5 @@ VITE_INJECT_APP_LOADING=true
|
|||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
|
||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
|
@ -1,4 +1,5 @@
|
|||
import { createApp, watchEffect } from 'vue';
|
||||
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
||||
|
||||
import { registerAccessDirective } from '@vben/access';
|
||||
import { registerLoadingDirective } from '@vben/common-ui';
|
||||
|
@ -34,7 +35,7 @@ async function bootstrap(namespace: string) {
|
|||
// zIndex: 2000,
|
||||
// });
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(VueDOMPurifyHTML);
|
||||
// 注册Element Plus提供的v-loading指令
|
||||
app.directive('loading', ElLoading.directive);
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import { nextTick, ref } from 'vue';
|
|||
|
||||
import { getUrlNumberValue } from '@vben/utils';
|
||||
|
||||
import { ElScrollbar } from 'element-plus';
|
||||
|
||||
import ProductCategorySelect from '#/views/mall/product/category/components/product-category-select.vue';
|
||||
|
||||
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM } from './data';
|
||||
|
@ -28,7 +30,6 @@ const dialogVisible = ref(false);
|
|||
const open = (link: string) => {
|
||||
activeAppLink.value.path = link;
|
||||
dialogVisible.value = true;
|
||||
|
||||
// 滚动到当前的链接
|
||||
const group = APP_LINK_GROUP_LIST.find((group) =>
|
||||
group.links.some((linkItem) => {
|
||||
|
@ -127,7 +128,7 @@ const scrollToGroupBtn = (group: string) => {
|
|||
|
||||
// 是否为相同的链接(不比较参数,只比较链接)
|
||||
const isSameLink = (link1: string, link2: string) => {
|
||||
return link1.split('?')[0] === link2.split('?')[0];
|
||||
return link2 ? link1.split('?')[0] === link2.split('?')[0] : false;
|
||||
};
|
||||
|
||||
// 详情选择对话框
|
||||
|
@ -154,10 +155,10 @@ const handleProductCategorySelected = (id: number) => {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="选择链接" width="65%">
|
||||
<div class="h-500px gap-8px flex">
|
||||
<el-dialog v-model="dialogVisible" title="选择链接" width="65%">
|
||||
<div class="flex h-[500px] gap-2">
|
||||
<!-- 左侧分组列表 -->
|
||||
<el-scrollbar
|
||||
<ElScrollbar
|
||||
wrap-class="h-full"
|
||||
ref="groupScrollbar"
|
||||
view-class="flex flex-col"
|
||||
|
@ -165,7 +166,7 @@ const handleProductCategorySelected = (id: number) => {
|
|||
<el-button
|
||||
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
|
||||
:key="groupIndex"
|
||||
class="m-r-16px m-l-0px! justify-start! w-90px"
|
||||
class="ml-0 mr-4 w-[90px] justify-start"
|
||||
:class="[{ active: activeGroup === group.name }]"
|
||||
ref="groupBtnRefs"
|
||||
:text="activeGroup !== group.name"
|
||||
|
@ -174,9 +175,9 @@ const handleProductCategorySelected = (id: number) => {
|
|||
>
|
||||
{{ group.name }}
|
||||
</el-button>
|
||||
</el-scrollbar>
|
||||
</ElScrollbar>
|
||||
<!-- 右侧链接列表 -->
|
||||
<el-scrollbar
|
||||
<ElScrollbar
|
||||
class="h-full flex-1"
|
||||
@scroll="handleScroll"
|
||||
ref="linkScrollbar"
|
||||
|
@ -196,7 +197,7 @@ const handleProductCategorySelected = (id: number) => {
|
|||
:show-after="300"
|
||||
>
|
||||
<el-button
|
||||
class="m-b-8px m-r-8px m-l-0px!"
|
||||
class="mb-2 ml-0 mr-2"
|
||||
:type="
|
||||
isSameLink(appLink.path, activeAppLink.path)
|
||||
? 'primary'
|
||||
|
@ -208,16 +209,16 @@ const handleProductCategorySelected = (id: number) => {
|
|||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
<!-- 底部对话框操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<Dialog v-model="detailSelectDialog.visible" title="" width="50%">
|
||||
<el-form class="min-h-200px">
|
||||
</el-dialog>
|
||||
<el-dialog v-model="detailSelectDialog.visible" title="" width="50%">
|
||||
<el-form class="min-h-[200px]">
|
||||
<el-form-item
|
||||
label="选择分类"
|
||||
v-if="
|
||||
|
@ -231,6 +232,10 @@ const handleProductCategorySelected = (id: number) => {
|
|||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-button + .el-button) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import AppLinkSelectDialog from './app-link-select-dialog.vue';
|
||||
|
||||
// APP 链接输入框
|
||||
defineOptions({ name: 'AppLinkInput' });
|
||||
// 定义属性
|
||||
const props = defineProps({
|
||||
// 当前选中的链接
|
||||
modelValue: propTypes.string.def(''),
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
// setter
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { PREDEFINE_COLORS } from '#/utils/constants';
|
||||
|
||||
// 颜色输入框
|
||||
defineOptions({ name: 'ColorInput' });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const color = computed({
|
||||
get: () => {
|
||||
return props.modelValue;
|
||||
},
|
||||
set: (val: string) => {
|
||||
emit('update:modelValue', val);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-input v-model="color">
|
||||
<template #prepend>
|
||||
<el-color-picker v-model="color" :predefine="PREDEFINE_COLORS" />
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-input-group__prepend) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
|
@ -6,11 +6,16 @@ import type {
|
|||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { ElButton, ElTooltip } from 'element-plus';
|
||||
|
||||
import { components } from '#/components/diy-editor/components/mobile';
|
||||
import VerticalButtonGroup from '#/components/vertical-button-group/index.vue';
|
||||
|
||||
/**
|
||||
* 组件容器:目前在中间部分
|
||||
* 用于包裹组件,为组件提供 背景、外边距、内边距、边框等样式
|
||||
*/
|
||||
defineOptions({ name: 'ComponentContainer' });
|
||||
defineOptions({ name: 'ComponentContainer', components });
|
||||
|
||||
const props = defineProps({
|
||||
component: {
|
||||
|
@ -29,6 +34,10 @@ const props = defineProps({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showToolbar: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
|
@ -106,34 +115,37 @@ const handleDeleteComponent = () => {
|
|||
{{ component.name }}
|
||||
</div>
|
||||
<!-- 右侧:组件操作工具栏 -->
|
||||
<div class="component-toolbar" v-if="component.name && active">
|
||||
<div
|
||||
class="component-toolbar"
|
||||
v-if="showToolbar && component.name && active"
|
||||
>
|
||||
<VerticalButtonGroup type="primary">
|
||||
<el-tooltip content="上移" placement="right">
|
||||
<el-button
|
||||
<ElTooltip content="上移" placement="right">
|
||||
<ElButton
|
||||
:disabled="!canMoveUp"
|
||||
@click.stop="handleMoveComponent(-1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-up" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="下移" placement="right">
|
||||
<el-button
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="下移" placement="right">
|
||||
<ElButton
|
||||
:disabled="!canMoveDown"
|
||||
@click.stop="handleMoveComponent(1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-down" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="复制" placement="right">
|
||||
<el-button @click.stop="handleCopyComponent()">
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="复制" placement="right">
|
||||
<ElButton @click.stop="handleCopyComponent()">
|
||||
<Icon icon="ep:copy-document" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="right">
|
||||
<el-button @click.stop="handleDeleteComponent()">
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="删除" placement="right">
|
||||
<ElButton @click.stop="handleDeleteComponent()">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
</VerticalButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,20 @@
|
|||
import type { ComponentStyle } from '#/components/diy-editor/util';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRadio,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
ElTabPane,
|
||||
ElTabs,
|
||||
ElTree,
|
||||
} from 'element-plus';
|
||||
|
||||
import ColorInput from '#/components/color-input/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
|
||||
/**
|
||||
* 组件容器属性:目前右边部分
|
||||
|
@ -110,48 +124,54 @@ const handleSliderChange = (prop: string) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<el-tabs stretch>
|
||||
<ElTabs stretch>
|
||||
<!-- 每个组件的自定义内容 -->
|
||||
<el-tab-pane label="内容" v-if="$slots.default">
|
||||
<ElTabPane label="内容" v-if="$slots.default">
|
||||
<slot></slot>
|
||||
</el-tab-pane>
|
||||
</ElTabPane>
|
||||
|
||||
<!-- 每个组件的通用内容 -->
|
||||
<el-tab-pane label="样式" lazy>
|
||||
<el-card header="组件样式" class="property-group">
|
||||
<el-form :model="formData" label-width="80px">
|
||||
<el-form-item label="组件背景" prop="bgType">
|
||||
<el-radio-group v-model="formData.bgType">
|
||||
<el-radio value="color">纯色</el-radio>
|
||||
<el-radio value="img">图片</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
<ElTabPane label="样式" lazy>
|
||||
<ElCard header="组件样式" class="property-group">
|
||||
<ElForm :model="formData" label-width="80px">
|
||||
<ElFormItem label="组件背景" prop="bgType">
|
||||
<ElRadioGroup v-model="formData.bgType">
|
||||
<ElRadio value="color">纯色</ElRadio>
|
||||
<ElRadio value="img">图片</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
label="选择颜色"
|
||||
prop="bgColor"
|
||||
v-if="formData.bgType === 'color'"
|
||||
>
|
||||
<ColorInput v-model="formData.bgColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上传图片" prop="bgImg" v-else>
|
||||
<UploadImg v-model="formData.bgImg" :limit="1">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="上传图片" prop="bgImg" v-else>
|
||||
<UploadImg
|
||||
v-model="formData.bgImg"
|
||||
:limit="1"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip>建议宽度 750px</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
<el-tree
|
||||
</ElFormItem>
|
||||
<ElTree
|
||||
:data="treeData"
|
||||
:expand-on-click-node="false"
|
||||
default-expand-all
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<el-form-item
|
||||
<ElFormItem
|
||||
:label="data.label"
|
||||
:prop="data.prop"
|
||||
:label-width="node.level === 1 ? '80px' : '62px'"
|
||||
class="m-b-0! w-full"
|
||||
>
|
||||
<el-slider
|
||||
v-model="formData[data.prop as keyof ComponentStyle]"
|
||||
<ElSlider
|
||||
v-model="
|
||||
formData[data.prop as keyof ComponentStyle] as number
|
||||
"
|
||||
:max="100"
|
||||
:min="0"
|
||||
show-input
|
||||
|
@ -159,14 +179,14 @@ const handleSliderChange = (prop: string) => {
|
|||
:show-input-controls="false"
|
||||
@input="handleSliderChange(data.prop)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</el-tree>
|
||||
</ElTree>
|
||||
<slot name="style" :style="formData"></slot>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ElForm>
|
||||
</ElCard>
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -6,8 +6,10 @@ import type {
|
|||
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { ElAside, ElCollapse, ElCollapseItem, ElScrollbar } from 'element-plus';
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
import { componentConfigs } from '../components/mobile/index';
|
||||
|
@ -63,10 +65,10 @@ const handleCloneComponent = (component: DiyComponent<any>) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<el-aside class="editor-left" width="261px">
|
||||
<el-scrollbar>
|
||||
<el-collapse v-model="extendGroups">
|
||||
<el-collapse-item
|
||||
<ElAside class="editor-left" width="261px">
|
||||
<ElScrollbar>
|
||||
<ElCollapse v-model="extendGroups">
|
||||
<ElCollapseItem
|
||||
v-for="group in groups"
|
||||
:key="group.name"
|
||||
:name="group.name"
|
||||
|
@ -87,16 +89,16 @@ const handleCloneComponent = (component: DiyComponent<any>) => {
|
|||
<div>
|
||||
<div class="drag-placement">组件放置区域</div>
|
||||
<div class="component">
|
||||
<Icon :icon="element.icon" :size="32" />
|
||||
<IconifyIcon :icon="element.icon" :size="32" />
|
||||
<span class="mt-4px text-12px">{{ element.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</ElScrollbar>
|
||||
</ElAside>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { components } from '../components/mobile/index';
|
||||
|
||||
export default {
|
||||
components: { ...components },
|
||||
};
|
|
@ -3,6 +3,10 @@ import type { CarouselProperty } from './config';
|
|||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { ElCarousel, ElCarouselItem, ElImage } from 'element-plus';
|
||||
|
||||
/** 轮播图 */
|
||||
defineOptions({ name: 'Carousel' });
|
||||
|
||||
|
@ -16,13 +20,13 @@ const handleIndexChange = (index: number) => {
|
|||
<template>
|
||||
<!-- 无图片 -->
|
||||
<div
|
||||
class="h-250px bg-gray-3 flex items-center justify-center"
|
||||
class="flex h-[250px] items-center justify-center bg-gray-300"
|
||||
v-if="property.items.length === 0"
|
||||
>
|
||||
<Icon icon="tdesign:image" class="text-gray-8 text-120px!" />
|
||||
<IconifyIcon icon="tdesign:image" class="text-[120px] text-gray-800" />
|
||||
</div>
|
||||
<div v-else class="relative">
|
||||
<el-carousel
|
||||
<ElCarousel
|
||||
height="174px"
|
||||
:type="property.type === 'card' ? 'card' : ''"
|
||||
:autoplay="property.autoplay"
|
||||
|
@ -30,13 +34,13 @@ const handleIndexChange = (index: number) => {
|
|||
:indicator-position="property.indicator === 'number' ? 'none' : undefined"
|
||||
@change="handleIndexChange"
|
||||
>
|
||||
<el-carousel-item v-for="(item, index) in property.items" :key="index">
|
||||
<el-image class="h-full w-full" :src="item.imgUrl" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<ElCarouselItem v-for="(item, index) in property.items" :key="index">
|
||||
<ElImage class="h-full w-full" :src="item.imgUrl" />
|
||||
</ElCarouselItem>
|
||||
</ElCarousel>
|
||||
<div
|
||||
v-if="property.indicator === 'number'"
|
||||
class="bottom-10px right-10px p-x-8px p-y-2px text-10px absolute rounded-xl bg-black text-white opacity-40"
|
||||
class="absolute bottom-[10px] right-[10px] rounded-xl bg-black px-[8px] py-[2px] text-[10px] text-white opacity-40"
|
||||
>
|
||||
{{ currentIndex }} / {{ property.items.length }}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import type { CarouselProperty } from './config';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
ElSwitch,
|
||||
ElText,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import AppLinkInput from '#/components/app-link-input/index.vue';
|
||||
import ComponentContainerProperty from '#/components/diy-editor/components/ComponentContainerProperty.vue';
|
||||
import Draggable from '#/components/draggable/index.vue';
|
||||
import UploadFile from '#/components/upload/file-upload.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
|
||||
// 轮播图属性面板
|
||||
defineOptions({ name: 'CarouselProperty' });
|
||||
|
@ -13,33 +32,33 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="80px" :model="formData">
|
||||
<el-card header="样式设置" class="property-group" shadow="never">
|
||||
<el-form-item label="样式" prop="type">
|
||||
<el-radio-group v-model="formData.type">
|
||||
<el-tooltip class="item" content="默认" placement="bottom">
|
||||
<el-radio-button value="default">
|
||||
<Icon icon="system-uicons:carousel" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="卡片" placement="bottom">
|
||||
<el-radio-button value="card">
|
||||
<Icon icon="ic:round-view-carousel" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="指示器" prop="indicator">
|
||||
<el-radio-group v-model="formData.indicator">
|
||||
<el-radio value="dot">小圆点</el-radio>
|
||||
<el-radio value="number">数字</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否轮播" prop="autoplay">
|
||||
<el-switch v-model="formData.autoplay" />
|
||||
</el-form-item>
|
||||
<el-form-item label="播放间隔" prop="interval" v-if="formData.autoplay">
|
||||
<el-slider
|
||||
<ElForm label-width="80px" :model="formData">
|
||||
<ElCard header="样式设置" class="property-group" shadow="never">
|
||||
<ElFormItem label="样式" prop="type">
|
||||
<ElRadioGroup v-model="formData.type">
|
||||
<ElTooltip class="item" content="默认" placement="bottom">
|
||||
<ElRadioButton value="default">
|
||||
<IconifyIcon icon="system-uicons:carousel" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="卡片" placement="bottom">
|
||||
<ElRadioButton value="card">
|
||||
<IconifyIcon icon="ic:round-view-carousel" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="指示器" prop="indicator">
|
||||
<ElRadioGroup v-model="formData.indicator">
|
||||
<ElRadio value="dot">小圆点</ElRadio>
|
||||
<ElRadio value="number">数字</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="是否轮播" prop="autoplay">
|
||||
<ElSwitch v-model="formData.autoplay" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="播放间隔" prop="interval" v-if="formData.autoplay">
|
||||
<ElSlider
|
||||
v-model="formData.interval"
|
||||
:max="10"
|
||||
:min="0.5"
|
||||
|
@ -48,24 +67,24 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
<el-text type="info">单位:秒</el-text>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="内容设置" class="property-group" shadow="never">
|
||||
<ElText type="info">单位:秒</ElText>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="内容设置" class="property-group" shadow="never">
|
||||
<Draggable v-model="formData.items" :empty-item="{ type: 'img' }">
|
||||
<template #default="{ element }">
|
||||
<el-form-item
|
||||
<ElFormItem
|
||||
label="类型"
|
||||
prop="type"
|
||||
class="m-b-8px!"
|
||||
label-width="40px"
|
||||
>
|
||||
<el-radio-group v-model="element.type">
|
||||
<el-radio value="img">图片</el-radio>
|
||||
<el-radio value="video">视频</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
<ElRadioGroup v-model="element.type">
|
||||
<ElRadio value="img">图片</ElRadio>
|
||||
<ElRadio value="video">视频</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
label="图片"
|
||||
class="m-b-8px!"
|
||||
label-width="40px"
|
||||
|
@ -77,19 +96,21 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
height="80px"
|
||||
width="100%"
|
||||
class="min-w-80px"
|
||||
:show-description="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
<template v-else>
|
||||
<el-form-item label="封面" class="m-b-8px!" label-width="40px">
|
||||
<ElFormItem label="封面" class="m-b-8px!" label-width="40px">
|
||||
<UploadImg
|
||||
v-model="element.imgUrl"
|
||||
draggable="false"
|
||||
:show-description="false"
|
||||
height="80px"
|
||||
width="100%"
|
||||
class="min-w-80px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="视频" class="m-b-8px!" label-width="40px">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="视频" class="m-b-8px!" label-width="40px">
|
||||
<UploadFile
|
||||
v-model="element.videoUrl"
|
||||
:file-type="['mp4']"
|
||||
|
@ -97,15 +118,15 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
:file-size="100"
|
||||
class="min-w-80px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<el-form-item label="链接" class="m-b-8px!" label-width="40px">
|
||||
<ElFormItem label="链接" class="m-b-8px!" label-width="40px">
|
||||
<AppLinkInput v-model="element.url" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</Draggable>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTe
|
|||
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { ElScrollbar } from 'element-plus';
|
||||
|
||||
import * as CouponTemplateApi from '#/api/mall/promotion/coupon/couponTemplate';
|
||||
|
||||
import {
|
||||
|
@ -64,9 +66,9 @@ onMounted(() => {
|
|||
});
|
||||
</script>
|
||||
<template>
|
||||
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
|
||||
<ElScrollbar class="z-10 min-h-[30px]" wrap-class="w-full" ref="containerRef">
|
||||
<div
|
||||
class="text-12px flex flex-row"
|
||||
class="flex flex-row text-xs"
|
||||
:style="{
|
||||
gap: `${property.space}px`,
|
||||
width: scrollbarWidth,
|
||||
|
@ -87,9 +89,9 @@ onMounted(() => {
|
|||
<!-- 布局1:1列-->
|
||||
<div
|
||||
v-if="property.columns === 1"
|
||||
class="m-l-16px p-8px flex flex-row justify-between"
|
||||
class="ml-4 flex flex-row justify-between p-2"
|
||||
>
|
||||
<div class="gap-4px flex flex-col justify-evenly">
|
||||
<div class="flex flex-col justify-evenly gap-1">
|
||||
<!-- 优惠值 -->
|
||||
<CouponDiscount :coupon="coupon" />
|
||||
<!-- 优惠描述 -->
|
||||
|
@ -99,7 +101,7 @@ onMounted(() => {
|
|||
</div>
|
||||
<div class="flex flex-col justify-evenly">
|
||||
<div
|
||||
class="rounded-20px p-x-8px p-y-2px"
|
||||
class="rounded-full px-2 py-0.5"
|
||||
:style="{
|
||||
color: property.button.color,
|
||||
background: property.button.bgColor,
|
||||
|
@ -112,9 +114,9 @@ onMounted(() => {
|
|||
<!-- 布局2:2列-->
|
||||
<div
|
||||
v-else-if="property.columns === 2"
|
||||
class="m-l-16px p-8px flex flex-row justify-between"
|
||||
class="ml-4 flex flex-row justify-between p-2"
|
||||
>
|
||||
<div class="gap-4px flex flex-col justify-evenly">
|
||||
<div class="flex flex-col justify-evenly gap-1">
|
||||
<!-- 优惠值 -->
|
||||
<CouponDiscount :coupon="coupon" />
|
||||
<!-- 优惠描述 -->
|
||||
|
@ -127,7 +129,7 @@ onMounted(() => {
|
|||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="w-20px rounded-20px p-x-2px p-y-8px h-full text-center"
|
||||
class="h-full w-5 rounded-full px-0.5 py-2 text-center"
|
||||
:style="{
|
||||
color: property.button.color,
|
||||
background: property.button.bgColor,
|
||||
|
@ -138,16 +140,13 @@ onMounted(() => {
|
|||
</div>
|
||||
</div>
|
||||
<!-- 布局3:3列-->
|
||||
<div
|
||||
v-else
|
||||
class="gap-4px p-4px flex flex-col items-center justify-around"
|
||||
>
|
||||
<div v-else class="flex flex-col items-center justify-around gap-1 p-1">
|
||||
<!-- 优惠值 -->
|
||||
<CouponDiscount :coupon="coupon" />
|
||||
<!-- 优惠描述 -->
|
||||
<CouponDiscountDesc :coupon="coupon" />
|
||||
<div
|
||||
class="rounded-20px p-x-8px p-y-2px"
|
||||
class="rounded-full px-2 py-0.5"
|
||||
:style="{
|
||||
color: property.button.color,
|
||||
background: property.button.bgColor,
|
||||
|
@ -158,6 +157,6 @@ onMounted(() => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</ElScrollbar>
|
||||
</template>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
|
@ -5,11 +5,23 @@ import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTe
|
|||
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { floatToFixed2 } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import * as CouponTemplateApi from '#/api/mall/promotion/coupon/couponTemplate';
|
||||
import ColorInput from '#/components/color-input/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
import {
|
||||
CouponTemplateTakeTypeEnum,
|
||||
PromotionDiscountTypeEnum,
|
||||
|
@ -52,15 +64,15 @@ watch(
|
|||
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="80px" :model="formData">
|
||||
<el-card header="优惠券列表" class="property-group" shadow="never">
|
||||
<ElForm label-width="80px" :model="formData">
|
||||
<ElCard header="优惠券列表" class="property-group" shadow="never">
|
||||
<div
|
||||
v-for="(coupon, index) in couponList"
|
||||
:key="index"
|
||||
class="flex items-center justify-between"
|
||||
>
|
||||
<el-text size="large" truncated>{{ coupon.name }}</el-text>
|
||||
<el-text type="info" truncated>
|
||||
<ElText size="large" truncated>{{ coupon.name }}</ElText>
|
||||
<ElText type="info" truncated>
|
||||
<span v-if="coupon.usePrice > 0">
|
||||
满{{ floatToFixed2(coupon.usePrice) }}元,
|
||||
</span>
|
||||
|
@ -72,58 +84,59 @@ watch(
|
|||
减{{ floatToFixed2(coupon.discountPrice) }}元
|
||||
</span>
|
||||
<span v-else> 打{{ coupon.discountPercent }}折 </span>
|
||||
</el-text>
|
||||
</ElText>
|
||||
</div>
|
||||
<el-form-item label-width="0">
|
||||
<el-button
|
||||
<ElFormItem label-width="0">
|
||||
<ElButton
|
||||
@click="handleAddCoupon"
|
||||
type="primary"
|
||||
plain
|
||||
class="m-t-8px w-full"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 添加
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="优惠券样式" class="property-group" shadow="never">
|
||||
<el-form-item label="列数" prop="type">
|
||||
<el-radio-group v-model="formData.columns">
|
||||
<el-tooltip class="item" content="一列" placement="bottom">
|
||||
<el-radio-button :value="1">
|
||||
<Icon icon="fluent:text-column-one-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="二列" placement="bottom">
|
||||
<el-radio-button :value="2">
|
||||
<Icon icon="fluent:text-column-two-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="三列" placement="bottom">
|
||||
<el-radio-button :value="3">
|
||||
<Icon icon="fluent:text-column-three-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="背景图片" prop="bgImg">
|
||||
<IconifyIcon icon="ep:plus" class="mr-5px" /> 添加
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="优惠券样式" class="property-group" shadow="never">
|
||||
<ElFormItem label="列数" prop="type">
|
||||
<ElRadioGroup v-model="formData.columns">
|
||||
<ElTooltip class="item" content="一列" placement="bottom">
|
||||
<ElRadioButton :value="1">
|
||||
<IconifyIcon icon="fluent:text-column-one-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="二列" placement="bottom">
|
||||
<ElRadioButton :value="2">
|
||||
<IconifyIcon icon="fluent:text-column-two-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="三列" placement="bottom">
|
||||
<ElRadioButton :value="3">
|
||||
<IconifyIcon icon="fluent:text-column-three-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="背景图片" prop="bgImg">
|
||||
<UploadImg
|
||||
v-model="formData.bgImg"
|
||||
height="80px"
|
||||
width="100%"
|
||||
class="min-w-160px"
|
||||
:show-description="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="文字颜色" prop="textColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="文字颜色" prop="textColor">
|
||||
<ColorInput v-model="formData.textColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="按钮背景" prop="button.bgColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="按钮背景" prop="button.bgColor">
|
||||
<ColorInput v-model="formData.button.bgColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="按钮文字" prop="button.color">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="按钮文字" prop="button.color">
|
||||
<ColorInput v-model="formData.button.color" />
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" prop="space">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="间隔" prop="space">
|
||||
<ElSlider
|
||||
v-model="formData.space"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -131,9 +144,9 @@ watch(
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</ComponentContainerProperty>
|
||||
<!-- 优惠券选择 -->
|
||||
<CouponSelect
|
||||
|
|
|
@ -3,7 +3,9 @@ import type { FloatingActionButtonProperty } from './config';
|
|||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { ElImage, ElMessage } from 'element-plus';
|
||||
|
||||
/** 悬浮按钮 */
|
||||
defineOptions({ name: 'FloatingActionButton' });
|
||||
|
@ -23,7 +25,7 @@ const handleActive = (index: number) => {
|
|||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="bottom-32px z-12 gap-12px absolute right-[calc(50%-375px/2+32px)] flex items-center"
|
||||
class="absolute bottom-8 right-[calc(50%-375px/2+32px)] z-20 flex items-center gap-3"
|
||||
:class="[
|
||||
{
|
||||
'flex-row': property.direction === 'horizontal',
|
||||
|
@ -38,16 +40,16 @@ const handleActive = (index: number) => {
|
|||
class="flex flex-col items-center"
|
||||
@click="handleActive(index)"
|
||||
>
|
||||
<el-image :src="item.imgUrl" fit="contain" class="h-27px w-27px">
|
||||
<ElImage :src="item.imgUrl" fit="contain" class="h-7 w-7">
|
||||
<template #error>
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
<Icon icon="ep:picture" :color="item.textColor" />
|
||||
<IconifyIcon icon="ep:picture" :color="item.textColor" />
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</ElImage>
|
||||
<span
|
||||
v-if="property.showText"
|
||||
class="mt-4px text-12px"
|
||||
class="mt-1 text-xs"
|
||||
:style="{ color: item.textColor }"
|
||||
>
|
||||
{{ item.text }}
|
||||
|
@ -56,7 +58,11 @@ const handleActive = (index: number) => {
|
|||
</template>
|
||||
<!-- todo: @owen 使用APP主题色 -->
|
||||
<el-button type="primary" size="large" circle @click="handleToggleFab">
|
||||
<Icon icon="ep:plus" class="fab-icon" :class="[{ active: expanded }]" />
|
||||
<IconifyIcon
|
||||
icon="ep:plus"
|
||||
class="fab-icon"
|
||||
:class="[{ active: expanded }]"
|
||||
/>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 模态背景:展开时显示,点击后折叠 -->
|
||||
|
|
|
@ -2,6 +2,19 @@
|
|||
import type { FloatingActionButtonProperty } from './config';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRadio,
|
||||
ElRadioGroup,
|
||||
ElSwitch,
|
||||
} from 'element-plus';
|
||||
|
||||
import AppLinkInput from '#/components/app-link-input/index.vue';
|
||||
import Draggable from '#/components/draggable/index.vue';
|
||||
import InputWithColor from '#/components/input-with-color/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
|
||||
// 悬浮按钮属性面板
|
||||
defineOptions({ name: 'FloatingActionButtonProperty' });
|
||||
|
@ -12,37 +25,42 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<el-form label-width="80px" :model="formData">
|
||||
<el-card header="按钮配置" class="property-group" shadow="never">
|
||||
<el-form-item label="展开方向" prop="direction">
|
||||
<el-radio-group v-model="formData.direction">
|
||||
<el-radio value="vertical">垂直</el-radio>
|
||||
<el-radio value="horizontal">水平</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示文字" prop="showText">
|
||||
<el-switch v-model="formData.showText" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="按钮列表" class="property-group" shadow="never">
|
||||
<ElForm label-width="80px" :model="formData">
|
||||
<ElCard header="按钮配置" class="property-group" shadow="never">
|
||||
<ElFormItem label="展开方向" prop="direction">
|
||||
<ElRadioGroup v-model="formData.direction">
|
||||
<ElRadio value="vertical">垂直</ElRadio>
|
||||
<ElRadio value="horizontal">水平</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="显示文字" prop="showText">
|
||||
<ElSwitch v-model="formData.showText" />
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="按钮列表" class="property-group" shadow="never">
|
||||
<Draggable v-model="formData.list" :empty-item="{ textColor: '#fff' }">
|
||||
<template #default="{ element, index }">
|
||||
<el-form-item label="图标" :prop="`list[${index}].imgUrl`">
|
||||
<UploadImg v-model="element.imgUrl" height="56px" width="56px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文字" :prop="`list[${index}].text`">
|
||||
<ElFormItem label="图标" :prop="`list[${index}].imgUrl`">
|
||||
<UploadImg
|
||||
v-model="element.imgUrl"
|
||||
height="56px"
|
||||
width="56px"
|
||||
:show-description="false"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="文字" :prop="`list[${index}].text`">
|
||||
<InputWithColor
|
||||
v-model="element.text"
|
||||
v-model:color="element.textColor"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" :prop="`list[${index}].url`">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="跳转链接" :prop="`list[${index}].url`">
|
||||
<AppLinkInput v-model="element.url" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</Draggable>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
|
@ -6,6 +6,10 @@ import type { HotZoneItemProperty } from '#/components/diy-editor/components/mob
|
|||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { ElButton, ElDialog, ElImage } from 'element-plus';
|
||||
|
||||
import {
|
||||
CONTROL_DOT_LIST,
|
||||
CONTROL_TYPE_ENUM,
|
||||
|
@ -172,14 +176,14 @@ const handleAppLinkChange = (appLink: AppLink) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
title="设置热区"
|
||||
width="780"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div ref="container" class="w-750px relative h-full">
|
||||
<el-image
|
||||
<ElImage
|
||||
:src="imgUrl"
|
||||
class="w-750px pointer-events-none h-full select-none"
|
||||
/>
|
||||
|
@ -199,7 +203,7 @@ const handleAppLinkChange = (appLink: AppLink) => {
|
|||
<span class="pointer-events-none select-none">{{
|
||||
item.name || '双击选择链接'
|
||||
}}</span>
|
||||
<Icon
|
||||
<IconifyIcon
|
||||
icon="ep:close"
|
||||
class="delete"
|
||||
:size="14"
|
||||
|
@ -217,16 +221,16 @@ const handleAppLinkChange = (appLink: AppLink) => {
|
|||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleAdd" type="primary" plain>
|
||||
<Icon icon="ep:plus" class="mr-5px" />
|
||||
<ElButton @click="handleAdd" type="primary" plain>
|
||||
<IconifyIcon icon="ep:plus" class="mr-5px" />
|
||||
添加热区
|
||||
</el-button>
|
||||
<el-button @click="handleSubmit" type="primary" plain>
|
||||
<Icon icon="ep:check" class="mr-5px" />
|
||||
</ElButton>
|
||||
<ElButton @click="handleSubmit" type="primary" plain>
|
||||
<IconifyIcon icon="ep:check" class="mr-5px" />
|
||||
确定
|
||||
</el-button>
|
||||
</ElButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</ElDialog>
|
||||
<AppLinkSelectDialog
|
||||
ref="appLinkDialogRef"
|
||||
@app-link-change="handleAppLinkChange"
|
||||
|
|
|
@ -32,6 +32,7 @@ const handleOpenEditDialog = () => {
|
|||
height="50px"
|
||||
width="auto"
|
||||
class="min-w-80px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip>
|
||||
<el-text type="info" size="small"> 推荐宽度 750</el-text>
|
||||
|
|
|
@ -21,6 +21,7 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
height="80px"
|
||||
width="100%"
|
||||
class="min-w-80px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议宽度750 </template>
|
||||
</UploadImg>
|
||||
|
|
|
@ -35,7 +35,12 @@ const handleHotAreaSelected = (_: any, index: number) => {
|
|||
<template v-for="(hotArea, index) in formData.list" :key="index">
|
||||
<template v-if="selectedHotAreaIndex === index">
|
||||
<el-form-item label="上传图片" :prop="`list[${index}].imgUrl`">
|
||||
<UploadImg v-model="hotArea.imgUrl" height="80px" width="80px" />
|
||||
<UploadImg
|
||||
v-model="hotArea.imgUrl"
|
||||
height="80px"
|
||||
width="80px"
|
||||
:show-description="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="链接" :prop="`list[${index}].url`">
|
||||
<AppLinkInput v-model="hotArea.url" />
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { MenuGridProperty } from './config';
|
||||
|
||||
import { ElImage } from 'element-plus';
|
||||
|
||||
/** 宫格导航 */
|
||||
defineOptions({ name: 'MenuGrid' });
|
||||
defineProps<{ property: MenuGridProperty }>();
|
||||
|
@ -11,13 +13,13 @@ defineProps<{ property: MenuGridProperty }>();
|
|||
<div
|
||||
v-for="(item, index) in property.list"
|
||||
:key="index"
|
||||
class="p-b-14px p-t-20px relative flex flex-col items-center"
|
||||
class="relative flex flex-col items-center pb-3.5 pt-5"
|
||||
:style="{ width: `${100 * (1 / property.column)}%` }"
|
||||
>
|
||||
<!-- 右上角角标 -->
|
||||
<span
|
||||
v-if="item.badge?.show"
|
||||
class="left-50% top-10px z-1 h-20px rounded-50% p-x-6px text-12px leading-20px absolute text-center"
|
||||
class="absolute left-1/2 top-2.5 z-10 h-5 rounded-full px-1.5 text-center text-xs leading-5"
|
||||
:style="{
|
||||
color: item.badge.textColor,
|
||||
backgroundColor: item.badge.bgColor,
|
||||
|
@ -25,15 +27,15 @@ defineProps<{ property: MenuGridProperty }>();
|
|||
>
|
||||
{{ item.badge.text }}
|
||||
</span>
|
||||
<el-image v-if="item.iconUrl" class="h-28px w-28px" :src="item.iconUrl" />
|
||||
<ElImage v-if="item.iconUrl" class="h-7 w-7" :src="item.iconUrl" />
|
||||
<span
|
||||
class="m-t-8px h-16px text-12px leading-16px"
|
||||
class="mt-2 h-4 text-xs leading-4"
|
||||
:style="{ color: item.titleColor }"
|
||||
>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<span
|
||||
class="m-t-6px h-12px text-10px leading-12px"
|
||||
class="mt-1.5 h-3 text-xs leading-3"
|
||||
:style="{ color: item.subtitleColor }"
|
||||
>
|
||||
{{ item.subtitle }}
|
||||
|
|
|
@ -2,6 +2,19 @@
|
|||
import type { MenuGridProperty } from './config';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRadio,
|
||||
ElRadioGroup,
|
||||
ElSwitch,
|
||||
} from 'element-plus';
|
||||
|
||||
import AppLinkInput from '#/components/app-link-input/index.vue';
|
||||
import ComponentContainerProperty from '#/components/diy-editor/components/ComponentContainerProperty.vue';
|
||||
import Draggable from '#/components/draggable/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
|
||||
import { EMPTY_MENU_GRID_ITEM_PROPERTY } from './config';
|
||||
|
||||
|
@ -16,58 +29,63 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<!-- 表单 -->
|
||||
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
||||
<el-form-item label="每行数量" prop="column">
|
||||
<el-radio-group v-model="formData.column">
|
||||
<el-radio :value="3">3个</el-radio>
|
||||
<el-radio :value="4">4个</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<ElForm label-width="80px" :model="formData" class="m-t-8px">
|
||||
<ElFormItem label="每行数量" prop="column">
|
||||
<ElRadioGroup v-model="formData.column">
|
||||
<ElRadio :value="3">3个</ElRadio>
|
||||
<ElRadio :value="4">4个</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
|
||||
<el-card header="菜单设置" class="property-group" shadow="never">
|
||||
<ElCard header="菜单设置" class="property-group" shadow="never">
|
||||
<Draggable
|
||||
v-model="formData.list"
|
||||
:empty-item="EMPTY_MENU_GRID_ITEM_PROPERTY"
|
||||
>
|
||||
<template #default="{ element }">
|
||||
<el-form-item label="图标" prop="iconUrl">
|
||||
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
||||
<ElFormItem label="图标" prop="iconUrl">
|
||||
<UploadImg
|
||||
v-model="element.iconUrl"
|
||||
height="80px"
|
||||
width="80px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:44 * 44 </template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="title">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="标题" prop="title">
|
||||
<InputWithColor
|
||||
v-model="element.title"
|
||||
v-model:color="element.titleColor"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="副标题" prop="subtitle">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="副标题" prop="subtitle">
|
||||
<InputWithColor
|
||||
v-model="element.subtitle"
|
||||
v-model:color="element.subtitleColor"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="链接" prop="url">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="链接" prop="url">
|
||||
<AppLinkInput v-model="element.url" />
|
||||
</el-form-item>
|
||||
<el-form-item label="显示角标" prop="badge.show">
|
||||
<el-switch v-model="element.badge.show" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="显示角标" prop="badge.show">
|
||||
<ElSwitch v-model="element.badge.show" />
|
||||
</ElFormItem>
|
||||
<template v-if="element.badge.show">
|
||||
<el-form-item label="角标内容" prop="badge.text">
|
||||
<ElFormItem label="角标内容" prop="badge.text">
|
||||
<InputWithColor
|
||||
v-model="element.badge.text"
|
||||
v-model:color="element.badge.textColor"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="背景颜色" prop="badge.bgColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="背景颜色" prop="badge.bgColor">
|
||||
<ColorInput v-model="element.badge.bgColor" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</template>
|
||||
</Draggable>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -26,7 +26,12 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
>
|
||||
<template #default="{ element }">
|
||||
<el-form-item label="图标" prop="iconUrl">
|
||||
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
||||
<UploadImg
|
||||
v-model="element.iconUrl"
|
||||
height="80px"
|
||||
width="80px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:44 * 44 </template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
|
|
|
@ -46,7 +46,12 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
>
|
||||
<template #default="{ element }">
|
||||
<el-form-item label="图标" prop="iconUrl">
|
||||
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
||||
<UploadImg
|
||||
v-model="element.iconUrl"
|
||||
height="80px"
|
||||
width="80px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:98 * 98 </template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
<script lang="ts" setup>
|
||||
import type { NavigationBarCellProperty } from '../config';
|
||||
|
||||
import type { Rect } from '#/components/magic-cube-editor/util';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElRadio,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
} from 'element-plus';
|
||||
|
||||
import appNavBarMp from '#/assets/imgs/diy/app-nav-bar-mp.png';
|
||||
import AppLinkInput from '#/components/app-link-input/index.vue';
|
||||
import ColorInput from '#/components/color-input/index.vue';
|
||||
import MagicCubeEditor from '#/components/magic-cube-editor/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
|
||||
// 导航栏属性面板
|
||||
defineOptions({ name: 'NavigationBarCellProperty' });
|
||||
|
@ -24,6 +39,18 @@ const cellList = useVModel(props, 'modelValue', emit);
|
|||
// 单元格数量:小程序6个(右侧胶囊按钮占了2个),其它平台8个
|
||||
const cellCount = computed(() => (props.isMp ? 6 : 8));
|
||||
|
||||
// 转换为Rect格式的数据
|
||||
const rectList = computed<Rect[]>(() => {
|
||||
return cellList.value.map((cell) => ({
|
||||
left: cell.left,
|
||||
top: cell.top,
|
||||
width: cell.width,
|
||||
height: cell.height,
|
||||
right: cell.left + cell.width,
|
||||
bottom: cell.top + cell.height,
|
||||
}));
|
||||
});
|
||||
|
||||
// 选中的热区
|
||||
const selectedHotAreaIndex = ref(0);
|
||||
const handleHotAreaSelected = (
|
||||
|
@ -41,7 +68,7 @@ const handleHotAreaSelected = (
|
|||
<template>
|
||||
<div class="h-40px flex items-center justify-center">
|
||||
<MagicCubeEditor
|
||||
v-model="cellList"
|
||||
v-model="rectList"
|
||||
:cols="cellCount"
|
||||
:cube-size="38"
|
||||
:rows="1"
|
||||
|
@ -51,54 +78,55 @@ const handleHotAreaSelected = (
|
|||
<img
|
||||
v-if="isMp"
|
||||
alt=""
|
||||
class="h-30px w-76px"
|
||||
src="@/assets/imgs/diy/app-nav-bar-mp.png"
|
||||
style="width: 76px; height: 30px"
|
||||
:src="appNavBarMp"
|
||||
/>
|
||||
</div>
|
||||
<template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
|
||||
<template v-if="selectedHotAreaIndex === Number(cellIndex)">
|
||||
<el-form-item :prop="`cell[${cellIndex}].type`" label="类型">
|
||||
<el-radio-group v-model="cell.type">
|
||||
<el-radio value="text">文字</el-radio>
|
||||
<el-radio value="image">图片</el-radio>
|
||||
<el-radio value="search">搜索框</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<ElFormItem :prop="`cell[${cellIndex}].type`" label="类型">
|
||||
<ElRadioGroup v-model="cell.type">
|
||||
<ElRadio value="text">文字</ElRadio>
|
||||
<ElRadio value="image">图片</ElRadio>
|
||||
<ElRadio value="search">搜索框</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<!-- 1. 文字 -->
|
||||
<template v-if="cell.type === 'text'">
|
||||
<el-form-item :prop="`cell[${cellIndex}].text`" label="内容">
|
||||
<el-input v-model="cell!.text" maxlength="10" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :prop="`cell[${cellIndex}].text`" label="颜色">
|
||||
<ElFormItem :prop="`cell[${cellIndex}].text`" label="内容">
|
||||
<ElInput v-model="cell!.text" maxlength="10" show-word-limit />
|
||||
</ElFormItem>
|
||||
<ElFormItem :prop="`cell[${cellIndex}].text`" label="颜色">
|
||||
<ColorInput v-model="cell!.textColor" />
|
||||
</el-form-item>
|
||||
<el-form-item :prop="`cell[${cellIndex}].url`" label="链接">
|
||||
</ElFormItem>
|
||||
<ElFormItem :prop="`cell[${cellIndex}].url`" label="链接">
|
||||
<AppLinkInput v-model="cell.url" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<!-- 2. 图片 -->
|
||||
<template v-else-if="cell.type === 'image'">
|
||||
<el-form-item :prop="`cell[${cellIndex}].imgUrl`" label="图片">
|
||||
<ElFormItem :prop="`cell[${cellIndex}].imgUrl`" label="图片">
|
||||
<UploadImg
|
||||
v-model="cell.imgUrl"
|
||||
:limit="1"
|
||||
height="56px"
|
||||
width="56px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip>建议尺寸 56*56</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
<el-form-item :prop="`cell[${cellIndex}].url`" label="链接">
|
||||
</ElFormItem>
|
||||
<ElFormItem :prop="`cell[${cellIndex}].url`" label="链接">
|
||||
<AppLinkInput v-model="cell.url" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<!-- 3. 搜索框 -->
|
||||
<template v-else>
|
||||
<el-form-item :prop="`cell[${cellIndex}].placeholder`" label="提示文字">
|
||||
<el-input v-model="cell.placeholder" maxlength="10" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :prop="`cell[${cellIndex}].borderRadius`" label="圆角">
|
||||
<el-slider
|
||||
<ElFormItem :prop="`cell[${cellIndex}].placeholder`" label="提示文字">
|
||||
<ElInput v-model="cell.placeholder" maxlength="10" show-word-limit />
|
||||
</ElFormItem>
|
||||
<ElFormItem :prop="`cell[${cellIndex}].borderRadius`" label="圆角">
|
||||
<ElSlider
|
||||
v-model="cell.borderRadius"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -106,7 +134,7 @@ const handleHotAreaSelected = (
|
|||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { SearchProperty } from '#/components/diy-editor/components/mobile/S
|
|||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import appNavbarMp from '#/assets/imgs/diy/app-nav-bar-mp.png';
|
||||
import SearchBar from '#/components/diy-editor/components/mobile/SearchBar/index.vue';
|
||||
|
||||
/** 页面顶部导航栏 */
|
||||
|
@ -75,9 +76,9 @@ const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
|
|||
</div>
|
||||
<img
|
||||
v-if="property._local?.previewMp"
|
||||
src="@/assets/imgs/diy/app-nav-bar-mp.png"
|
||||
:src="appNavbarMp"
|
||||
alt=""
|
||||
class="h-30px w-86px"
|
||||
style="width: 86px; height: 30px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -70,6 +70,7 @@ if (!formData.value._local) {
|
|||
:limit="1"
|
||||
width="56px"
|
||||
height="56px"
|
||||
:show-description="false"
|
||||
/>
|
||||
<span class="mb-2 ml-2 text-xs text-gray-400">建议宽度:750</span>
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,11 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="80px" :model="formData" :rules="rules">
|
||||
<el-form-item label="公告图标" prop="iconUrl">
|
||||
<UploadImg v-model="formData.iconUrl" height="48px">
|
||||
<UploadImg
|
||||
v-model="formData.iconUrl"
|
||||
height="48px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip>建议尺寸:24 * 24</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
|
|
|
@ -28,7 +28,11 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
<ColorInput v-model="formData!.backgroundColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="背景图片" prop="backgroundImage">
|
||||
<UploadImg v-model="formData!.backgroundImage" :limit="1">
|
||||
<UploadImg
|
||||
v-model="formData!.backgroundImage"
|
||||
:limit="1"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip>建议宽度 750px</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
|
|
|
@ -16,7 +16,12 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
<Draggable v-model="formData.list" :empty-item="{ showType: 'once' }">
|
||||
<template #default="{ element, index }">
|
||||
<el-form-item label="图片" :prop="`list[${index}].imgUrl`">
|
||||
<UploadImg v-model="element.imgUrl" height="56px" width="56px" />
|
||||
<UploadImg
|
||||
v-model="element.imgUrl"
|
||||
height="56px"
|
||||
width="56px"
|
||||
:show-description="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" :prop="`list[${index}].url`">
|
||||
<AppLinkInput v-model="element.url" />
|
||||
|
|
|
@ -7,6 +7,8 @@ import { ref, watch } from 'vue';
|
|||
|
||||
import { fenToYuan } from '@vben/utils';
|
||||
|
||||
import { ElImage } from 'element-plus';
|
||||
|
||||
import * as ProductSpuApi from '#/api/mall/product/spu';
|
||||
|
||||
/** 商品卡片 */
|
||||
|
@ -55,7 +57,7 @@ const calculateWidth = () => {
|
|||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="min-h-30px box-content flex w-full flex-row flex-wrap"
|
||||
class="box-content flex min-h-[30px] w-full flex-row flex-wrap"
|
||||
ref="containerRef"
|
||||
>
|
||||
<div
|
||||
|
@ -74,28 +76,28 @@ const calculateWidth = () => {
|
|||
<!-- 角标 -->
|
||||
<div
|
||||
v-if="property.badge.show && property.badge.imgUrl"
|
||||
class="z-1 absolute left-0 top-0 items-center justify-center"
|
||||
class="absolute left-0 top-0 z-[1] items-center justify-center"
|
||||
>
|
||||
<el-image
|
||||
<ElImage
|
||||
fit="cover"
|
||||
:src="property.badge.imgUrl"
|
||||
class="h-26px w-38px"
|
||||
class="h-[26px] w-[38px]"
|
||||
/>
|
||||
</div>
|
||||
<!-- 商品封面图 -->
|
||||
<div
|
||||
class="h-140px"
|
||||
class="h-[140px]"
|
||||
:class="[
|
||||
{
|
||||
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||
'w-140px': property.layoutType === 'oneColSmallImg',
|
||||
'w-[140px]': property.layoutType === 'oneColSmallImg',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
|
||||
<ElImage fit="cover" class="h-full w-full" :src="spu.picUrl" />
|
||||
</div>
|
||||
<div
|
||||
class="gap-8px p-8px box-border flex flex-col"
|
||||
class="box-border flex flex-col gap-[8px] p-[8px]"
|
||||
:class="[
|
||||
{
|
||||
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||
|
@ -107,7 +109,7 @@ const calculateWidth = () => {
|
|||
<!-- 商品名称 -->
|
||||
<div
|
||||
v-if="property.fields.name.show"
|
||||
class="text-14px"
|
||||
class="text-[14px]"
|
||||
:class="[
|
||||
{
|
||||
truncate: property.layoutType !== 'oneColSmallImg',
|
||||
|
@ -122,7 +124,7 @@ const calculateWidth = () => {
|
|||
<!-- 商品简介 -->
|
||||
<div
|
||||
v-if="property.fields.introduction.show"
|
||||
class="text-12px truncate"
|
||||
class="truncate text-[12px]"
|
||||
:style="{ color: property.fields.introduction.color }"
|
||||
>
|
||||
{{ spu.introduction }}
|
||||
|
@ -131,7 +133,7 @@ const calculateWidth = () => {
|
|||
<!-- 价格 -->
|
||||
<span
|
||||
v-if="property.fields.price.show"
|
||||
class="text-16px"
|
||||
class="text-[16px]"
|
||||
:style="{ color: property.fields.price.color }"
|
||||
>
|
||||
¥{{ fenToYuan(spu.price as any) }}
|
||||
|
@ -139,12 +141,12 @@ const calculateWidth = () => {
|
|||
<!-- 市场价 -->
|
||||
<span
|
||||
v-if="property.fields.marketPrice.show && spu.marketPrice"
|
||||
class="ml-4px text-10px line-through"
|
||||
class="ml-[4px] text-[10px] line-through"
|
||||
:style="{ color: property.fields.marketPrice.color }"
|
||||
>¥{{ fenToYuan(spu.marketPrice) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-12px">
|
||||
<div class="text-[12px]">
|
||||
<!-- 销量 -->
|
||||
<span
|
||||
v-if="property.fields.salesCount.show"
|
||||
|
@ -162,11 +164,11 @@ const calculateWidth = () => {
|
|||
</div>
|
||||
</div>
|
||||
<!-- 购买按钮 -->
|
||||
<div class="bottom-8px right-8px absolute">
|
||||
<div class="absolute bottom-[8px] right-[8px]">
|
||||
<!-- 文字按钮 -->
|
||||
<span
|
||||
v-if="property.btnBuy.type === 'text'"
|
||||
class="p-x-12px p-y-4px text-12px rounded-full text-white"
|
||||
class="rounded-full px-[12px] py-[4px] text-[12px] text-white"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`,
|
||||
}"
|
||||
|
@ -174,9 +176,9 @@ const calculateWidth = () => {
|
|||
{{ property.btnBuy.text }}
|
||||
</span>
|
||||
<!-- 图片按钮 -->
|
||||
<el-image
|
||||
<ElImage
|
||||
v-else
|
||||
class="h-28px w-28px rounded-full"
|
||||
class="h-[28px] w-[28px] rounded-full"
|
||||
fit="cover"
|
||||
:src="property.btnBuy.imgUrl"
|
||||
/>
|
||||
|
|
|
@ -1,8 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import type { ProductCardProperty } from './config';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElCheckbox,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
ElSwitch,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import ColorInput from '#/components/color-input/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
import SpuShowcase from '#/views/mall/product/spu/components/spu-showcase.vue';
|
||||
|
||||
// 商品卡片属性面板
|
||||
|
@ -15,114 +31,116 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="80px" :model="formData">
|
||||
<el-card header="商品列表" class="property-group" shadow="never">
|
||||
<ElForm label-width="80px" :model="formData">
|
||||
<ElCard header="商品列表" class="property-group" shadow="never">
|
||||
<SpuShowcase v-model="formData.spuIds" />
|
||||
</el-card>
|
||||
<el-card header="商品样式" class="property-group" shadow="never">
|
||||
<el-form-item label="布局" prop="type">
|
||||
<el-radio-group v-model="formData.layoutType">
|
||||
<el-tooltip class="item" content="单列大图" placement="bottom">
|
||||
<el-radio-button value="oneColBigImg">
|
||||
<Icon icon="fluent:text-column-one-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="单列小图" placement="bottom">
|
||||
<el-radio-button value="oneColSmallImg">
|
||||
<Icon icon="fluent:text-column-two-left-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="双列" placement="bottom">
|
||||
<el-radio-button value="twoCol">
|
||||
<Icon icon="fluent:text-column-two-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="fields.name.show">
|
||||
</ElCard>
|
||||
<ElCard header="商品样式" class="property-group" shadow="never">
|
||||
<ElFormItem label="布局" prop="type">
|
||||
<ElRadioGroup v-model="formData.layoutType">
|
||||
<ElTooltip class="item" content="单列大图" placement="bottom">
|
||||
<ElRadioButton value="oneColBigImg">
|
||||
<IconifyIcon icon="fluent:text-column-one-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="单列小图" placement="bottom">
|
||||
<ElRadioButton value="oneColSmallImg">
|
||||
<IconifyIcon icon="fluent:text-column-two-left-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="双列" placement="bottom">
|
||||
<ElRadioButton value="twoCol">
|
||||
<IconifyIcon icon="fluent:text-column-two-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品名称" prop="fields.name.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.name.color" />
|
||||
<el-checkbox v-model="formData.fields.name.show" />
|
||||
<ElCheckbox v-model="formData.fields.name.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品简介" prop="fields.introduction.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品简介" prop="fields.introduction.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.introduction.color" />
|
||||
<el-checkbox v-model="formData.fields.introduction.show" />
|
||||
<ElCheckbox v-model="formData.fields.introduction.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品价格" prop="fields.price.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品价格" prop="fields.price.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.price.color" />
|
||||
<el-checkbox v-model="formData.fields.price.show" />
|
||||
<ElCheckbox v-model="formData.fields.price.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="市场价" prop="fields.marketPrice.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="市场价" prop="fields.marketPrice.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.marketPrice.color" />
|
||||
<el-checkbox v-model="formData.fields.marketPrice.show" />
|
||||
<ElCheckbox v-model="formData.fields.marketPrice.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品销量" prop="fields.salesCount.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品销量" prop="fields.salesCount.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.salesCount.color" />
|
||||
<el-checkbox v-model="formData.fields.salesCount.show" />
|
||||
<ElCheckbox v-model="formData.fields.salesCount.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品库存" prop="fields.stock.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品库存" prop="fields.stock.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.stock.color" />
|
||||
<el-checkbox v-model="formData.fields.stock.show" />
|
||||
<ElCheckbox v-model="formData.fields.stock.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="角标" class="property-group" shadow="never">
|
||||
<el-form-item label="角标" prop="badge.show">
|
||||
<el-switch v-model="formData.badge.show" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角标"
|
||||
prop="badge.imgUrl"
|
||||
v-if="formData.badge.show"
|
||||
>
|
||||
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="角标" class="property-group" shadow="never">
|
||||
<ElFormItem label="角标" prop="badge.show">
|
||||
<ElSwitch v-model="formData.badge.show" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
|
||||
<UploadImg
|
||||
v-model="formData.badge.imgUrl"
|
||||
height="44px"
|
||||
width="72px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:36 * 22 </template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="按钮" class="property-group" shadow="never">
|
||||
<el-form-item label="按钮类型" prop="btnBuy.type">
|
||||
<el-radio-group v-model="formData.btnBuy.type">
|
||||
<el-radio-button value="text">文字</el-radio-button>
|
||||
<el-radio-button value="img">图片</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="按钮" class="property-group" shadow="never">
|
||||
<ElFormItem label="按钮类型" prop="btnBuy.type">
|
||||
<ElRadioGroup v-model="formData.btnBuy.type">
|
||||
<ElRadioButton value="text">文字</ElRadioButton>
|
||||
<ElRadioButton value="img">图片</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<template v-if="formData.btnBuy.type === 'text'">
|
||||
<el-form-item label="按钮文字" prop="btnBuy.text">
|
||||
<el-input v-model="formData.btnBuy.text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||
<ElFormItem label="按钮文字" prop="btnBuy.text">
|
||||
<ElInput v-model="formData.btnBuy.text" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="右侧背景" prop="btnBuy.bgEndColor">
|
||||
<ColorInput v-model="formData.btnBuy.bgEndColor" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form-item label="图片" prop="btnBuy.imgUrl">
|
||||
<ElFormItem label="图片" prop="btnBuy.imgUrl">
|
||||
<UploadImg
|
||||
v-model="formData.btnBuy.imgUrl"
|
||||
height="56px"
|
||||
width="56px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:56 * 56 </template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</el-card>
|
||||
<el-card header="商品样式" class="property-group" shadow="never">
|
||||
<el-form-item label="上圆角" prop="borderRadiusTop">
|
||||
<el-slider
|
||||
</ElCard>
|
||||
<ElCard header="商品样式" class="property-group" shadow="never">
|
||||
<ElFormItem label="上圆角" prop="borderRadiusTop">
|
||||
<ElSlider
|
||||
v-model="formData.borderRadiusTop"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -130,9 +148,9 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="下圆角" prop="borderRadiusBottom">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="下圆角" prop="borderRadiusBottom">
|
||||
<ElSlider
|
||||
v-model="formData.borderRadiusBottom"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -140,9 +158,9 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" prop="space">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="间隔" prop="space">
|
||||
<ElSlider
|
||||
v-model="formData.space"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -150,9 +168,9 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -61,7 +61,12 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
prop="badge.imgUrl"
|
||||
v-if="formData.badge.show"
|
||||
>
|
||||
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||
<UploadImg
|
||||
v-model="formData.badge.imgUrl"
|
||||
height="44px"
|
||||
width="72px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:36 * 22 </template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
|
|
|
@ -8,6 +8,8 @@ import { ref, watch } from 'vue';
|
|||
|
||||
import { fenToYuan } from '@vben/utils';
|
||||
|
||||
import { ElImage } from 'element-plus';
|
||||
|
||||
import * as ProductSpuApi from '#/api/mall/product/spu';
|
||||
import * as CombinationActivityApi from '#/api/mall/promotion/combination/combinationActivity';
|
||||
|
||||
|
@ -97,7 +99,7 @@ const calculateWidth = () => {
|
|||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="min-h-30px box-content flex w-full flex-row flex-wrap"
|
||||
class="box-content flex min-h-[30px] w-full flex-row flex-wrap"
|
||||
ref="containerRef"
|
||||
>
|
||||
<div
|
||||
|
@ -116,28 +118,28 @@ const calculateWidth = () => {
|
|||
<!-- 角标 -->
|
||||
<div
|
||||
v-if="property.badge.show"
|
||||
class="z-1 absolute left-0 top-0 items-center justify-center"
|
||||
class="absolute left-0 top-0 z-[1] items-center justify-center"
|
||||
>
|
||||
<el-image
|
||||
<ElImage
|
||||
fit="cover"
|
||||
:src="property.badge.imgUrl"
|
||||
class="h-26px w-38px"
|
||||
class="h-[26px] w-[38px]"
|
||||
/>
|
||||
</div>
|
||||
<!-- 商品封面图 -->
|
||||
<div
|
||||
class="h-140px"
|
||||
class="h-[140px]"
|
||||
:class="[
|
||||
{
|
||||
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||
'w-140px': property.layoutType === 'oneColSmallImg',
|
||||
'w-[140px]': property.layoutType === 'oneColSmallImg',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
|
||||
<ElImage fit="cover" class="h-full w-full" :src="spu.picUrl" />
|
||||
</div>
|
||||
<div
|
||||
class="gap-8px p-8px box-border flex flex-col"
|
||||
class="box-border flex flex-col gap-[8px] p-[8px]"
|
||||
:class="[
|
||||
{
|
||||
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||
|
@ -149,7 +151,7 @@ const calculateWidth = () => {
|
|||
<!-- 商品名称 -->
|
||||
<div
|
||||
v-if="property.fields.name.show"
|
||||
class="text-14px"
|
||||
class="text-[14px]"
|
||||
:class="[
|
||||
{
|
||||
truncate: property.layoutType !== 'oneColSmallImg',
|
||||
|
@ -164,7 +166,7 @@ const calculateWidth = () => {
|
|||
<!-- 商品简介 -->
|
||||
<div
|
||||
v-if="property.fields.introduction.show"
|
||||
class="text-12px truncate"
|
||||
class="truncate text-[12px]"
|
||||
:style="{ color: property.fields.introduction.color }"
|
||||
>
|
||||
{{ spu.introduction }}
|
||||
|
@ -173,7 +175,7 @@ const calculateWidth = () => {
|
|||
<!-- 价格 -->
|
||||
<span
|
||||
v-if="property.fields.price.show"
|
||||
class="text-16px"
|
||||
class="text-[16px]"
|
||||
:style="{ color: property.fields.price.color }"
|
||||
>
|
||||
¥{{ fenToYuan(spu.price || Infinity) }}
|
||||
|
@ -181,13 +183,13 @@ const calculateWidth = () => {
|
|||
<!-- 市场价 -->
|
||||
<span
|
||||
v-if="property.fields.marketPrice.show && spu.marketPrice"
|
||||
class="ml-4px text-10px line-through"
|
||||
class="ml-[4px] text-[10px] line-through"
|
||||
:style="{ color: property.fields.marketPrice.color }"
|
||||
>
|
||||
¥{{ fenToYuan(spu.marketPrice) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-12px">
|
||||
<div class="text-[12px]">
|
||||
<!-- 销量 -->
|
||||
<span
|
||||
v-if="property.fields.salesCount.show"
|
||||
|
@ -205,11 +207,11 @@ const calculateWidth = () => {
|
|||
</div>
|
||||
</div>
|
||||
<!-- 购买按钮 -->
|
||||
<div class="bottom-8px right-8px absolute">
|
||||
<div class="absolute bottom-[8px] right-[8px]">
|
||||
<!-- 文字按钮 -->
|
||||
<span
|
||||
v-if="property.btnBuy.type === 'text'"
|
||||
class="p-x-12px p-y-4px text-12px rounded-full text-white"
|
||||
class="rounded-full px-[12px] py-[4px] text-[12px] text-white"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`,
|
||||
}"
|
||||
|
@ -217,9 +219,9 @@ const calculateWidth = () => {
|
|||
{{ property.btnBuy.text }}
|
||||
</span>
|
||||
<!-- 图片按钮 -->
|
||||
<el-image
|
||||
<ElImage
|
||||
v-else
|
||||
class="h-28px w-28px rounded-full"
|
||||
class="h-[28px] w-[28px] rounded-full"
|
||||
fit="cover"
|
||||
:src="property.btnBuy.imgUrl"
|
||||
/>
|
||||
|
|
|
@ -5,9 +5,22 @@ import type { MallCombinationActivityApi } from '#/api/mall/promotion/combinatio
|
|||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
ElSwitch,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import * as CombinationActivityApi from '#/api/mall/promotion/combination/combinationActivity';
|
||||
import ColorInput from '#/components/color-input/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
import { CommonStatusEnum } from '#/utils';
|
||||
import CombinationShowcase from '#/views/mall/promotion/combination/components/combination-showcase.vue';
|
||||
|
||||
|
@ -31,119 +44,116 @@ onMounted(async () => {
|
|||
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="80px" :model="formData">
|
||||
<el-card header="拼团活动" class="property-group" shadow="never">
|
||||
<ElForm label-width="80px" :model="formData">
|
||||
<ElCard header="拼团活动" class="property-group" shadow="never">
|
||||
<CombinationShowcase v-model="formData.activityIds" />
|
||||
</el-card>
|
||||
<el-card header="商品样式" class="property-group" shadow="never">
|
||||
<el-form-item label="布局" prop="type">
|
||||
<el-radio-group v-model="formData.layoutType">
|
||||
<el-tooltip class="item" content="单列大图" placement="bottom">
|
||||
<el-radio-button value="oneColBigImg">
|
||||
<Icon icon="fluent:text-column-one-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="单列小图" placement="bottom">
|
||||
<el-radio-button value="oneColSmallImg">
|
||||
<Icon icon="fluent:text-column-two-left-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="双列" placement="bottom">
|
||||
<el-radio-button value="twoCol">
|
||||
<Icon icon="fluent:text-column-two-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</ElCard>
|
||||
<ElCard header="商品样式" class="property-group" shadow="never">
|
||||
<ElFormItem label="布局" prop="type">
|
||||
<ElRadioGroup v-model="formData.layoutType">
|
||||
<ElTooltip class="item" content="单列大图" placement="bottom">
|
||||
<ElRadioButton value="oneColBigImg">
|
||||
<IconifyIcon icon="fluent:text-column-one-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="单列小图" placement="bottom">
|
||||
<ElRadioButton value="oneColSmallImg">
|
||||
<IconifyIcon icon="fluent:text-column-two-left-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="双列" placement="bottom">
|
||||
<ElRadioButton value="twoCol">
|
||||
<IconifyIcon icon="fluent:text-column-two-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<!--<el-tooltip class="item" content="三列" placement="bottom">
|
||||
<el-radio-button value="threeCol">
|
||||
<Icon icon="fluent:text-column-three-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>-->
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="fields.name.show">
|
||||
</ElRadioButton>
|
||||
</ElTooltip>-->
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品名称" prop="fields.name.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.name.color" />
|
||||
<el-checkbox v-model="formData.fields.name.show" />
|
||||
<ElCheckbox v-model="formData.fields.name.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品简介" prop="fields.introduction.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品简介" prop="fields.introduction.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.introduction.color" />
|
||||
<el-checkbox v-model="formData.fields.introduction.show" />
|
||||
<ElCheckbox v-model="formData.fields.introduction.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品价格" prop="fields.price.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品价格" prop="fields.price.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.price.color" />
|
||||
<el-checkbox v-model="formData.fields.price.show" />
|
||||
<ElCheckbox v-model="formData.fields.price.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="市场价" prop="fields.marketPrice.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="市场价" prop="fields.marketPrice.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.marketPrice.color" />
|
||||
<el-checkbox v-model="formData.fields.marketPrice.show" />
|
||||
<ElCheckbox v-model="formData.fields.marketPrice.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品销量" prop="fields.salesCount.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品销量" prop="fields.salesCount.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.salesCount.color" />
|
||||
<el-checkbox v-model="formData.fields.salesCount.show" />
|
||||
<ElCheckbox v-model="formData.fields.salesCount.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品库存" prop="fields.stock.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品库存" prop="fields.stock.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.stock.color" />
|
||||
<el-checkbox v-model="formData.fields.stock.show" />
|
||||
<ElCheckbox v-model="formData.fields.stock.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="角标" class="property-group" shadow="never">
|
||||
<el-form-item label="角标" prop="badge.show">
|
||||
<el-switch v-model="formData.badge.show" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角标"
|
||||
prop="badge.imgUrl"
|
||||
v-if="formData.badge.show"
|
||||
>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="角标" class="property-group" shadow="never">
|
||||
<ElFormItem label="角标" prop="badge.show">
|
||||
<ElSwitch v-model="formData.badge.show" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
|
||||
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||
<template #tip> 建议尺寸:36 * 22</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="按钮" class="property-group" shadow="never">
|
||||
<el-form-item label="按钮类型" prop="btnBuy.type">
|
||||
<el-radio-group v-model="formData.btnBuy.type">
|
||||
<el-radio-button value="text">文字</el-radio-button>
|
||||
<el-radio-button value="img">图片</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="按钮" class="property-group" shadow="never">
|
||||
<ElFormItem label="按钮类型" prop="btnBuy.type">
|
||||
<ElRadioGroup v-model="formData.btnBuy.type">
|
||||
<ElRadioButton value="text">文字</ElRadioButton>
|
||||
<ElRadioButton value="img">图片</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<template v-if="formData.btnBuy.type === 'text'">
|
||||
<el-form-item label="按钮文字" prop="btnBuy.text">
|
||||
<el-input v-model="formData.btnBuy.text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||
<ElFormItem label="按钮文字" prop="btnBuy.text">
|
||||
<ElInput v-model="formData.btnBuy.text" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="右侧背景" prop="btnBuy.bgEndColor">
|
||||
<ColorInput v-model="formData.btnBuy.bgEndColor" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form-item label="图片" prop="btnBuy.imgUrl">
|
||||
<ElFormItem label="图片" prop="btnBuy.imgUrl">
|
||||
<UploadImg
|
||||
v-model="formData.btnBuy.imgUrl"
|
||||
height="56px"
|
||||
width="56px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:56 * 56</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</el-card>
|
||||
<el-card header="商品样式" class="property-group" shadow="never">
|
||||
<el-form-item label="上圆角" prop="borderRadiusTop">
|
||||
<el-slider
|
||||
</ElCard>
|
||||
<ElCard header="商品样式" class="property-group" shadow="never">
|
||||
<ElFormItem label="上圆角" prop="borderRadiusTop">
|
||||
<ElSlider
|
||||
v-model="formData.borderRadiusTop"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -151,9 +161,9 @@ onMounted(async () => {
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="下圆角" prop="borderRadiusBottom">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="下圆角" prop="borderRadiusBottom">
|
||||
<ElSlider
|
||||
v-model="formData.borderRadiusBottom"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -161,9 +171,9 @@ onMounted(async () => {
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" prop="space">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="间隔" prop="space">
|
||||
<ElSlider
|
||||
v-model="formData.space"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -171,9 +181,9 @@ onMounted(async () => {
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import { ref, watch } from 'vue';
|
|||
|
||||
import { fenToYuan } from '@vben/utils';
|
||||
|
||||
import { ElImage } from 'element-plus';
|
||||
|
||||
import * as ProductSpuApi from '#/api/mall/product/spu';
|
||||
import * as PointActivityApi from '#/api/mall/promotion/point';
|
||||
|
||||
|
@ -94,7 +96,7 @@ const calculateWidth = () => {
|
|||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="min-h-30px box-content flex w-full flex-row flex-wrap"
|
||||
class="box-content flex min-h-[30px] w-full flex-row flex-wrap"
|
||||
>
|
||||
<div
|
||||
v-for="(spu, index) in spuList"
|
||||
|
@ -112,28 +114,28 @@ const calculateWidth = () => {
|
|||
<!-- 角标 -->
|
||||
<div
|
||||
v-if="property.badge.show"
|
||||
class="z-1 absolute left-0 top-0 items-center justify-center"
|
||||
class="absolute left-0 top-0 z-[1] items-center justify-center"
|
||||
>
|
||||
<el-image
|
||||
<ElImage
|
||||
:src="property.badge.imgUrl"
|
||||
class="h-26px w-38px"
|
||||
class="h-[26px] w-[38px]"
|
||||
fit="cover"
|
||||
/>
|
||||
</div>
|
||||
<!-- 商品封面图 -->
|
||||
<div
|
||||
class="h-140px"
|
||||
class="h-[140px]"
|
||||
:class="[
|
||||
{
|
||||
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||
'w-140px': property.layoutType === 'oneColSmallImg',
|
||||
'w-[140px]': property.layoutType === 'oneColSmallImg',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-image :src="spu.picUrl" class="h-full w-full" fit="cover" />
|
||||
<ElImage :src="spu.picUrl" class="h-full w-full" fit="cover" />
|
||||
</div>
|
||||
<div
|
||||
class="gap-8px p-8px box-border flex flex-col"
|
||||
class="box-border flex flex-col gap-[8px] p-[8px]"
|
||||
:class="[
|
||||
{
|
||||
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||
|
@ -145,7 +147,7 @@ const calculateWidth = () => {
|
|||
<!-- 商品名称 -->
|
||||
<div
|
||||
v-if="property.fields.name.show"
|
||||
class="text-14px"
|
||||
class="text-[14px]"
|
||||
:class="[
|
||||
{
|
||||
truncate: property.layoutType !== 'oneColSmallImg',
|
||||
|
@ -161,7 +163,7 @@ const calculateWidth = () => {
|
|||
<div
|
||||
v-if="property.fields.introduction.show"
|
||||
:style="{ color: property.fields.introduction.color }"
|
||||
class="text-12px truncate"
|
||||
class="truncate text-[12px]"
|
||||
>
|
||||
{{ spu.introduction }}
|
||||
</div>
|
||||
|
@ -170,7 +172,7 @@ const calculateWidth = () => {
|
|||
<span
|
||||
v-if="property.fields.price.show"
|
||||
:style="{ color: property.fields.price.color }"
|
||||
class="text-16px"
|
||||
class="text-[16px]"
|
||||
>
|
||||
{{ spu.point }}积分
|
||||
{{
|
||||
|
@ -183,12 +185,12 @@ const calculateWidth = () => {
|
|||
<span
|
||||
v-if="property.fields.marketPrice.show && spu.marketPrice"
|
||||
:style="{ color: property.fields.marketPrice.color }"
|
||||
class="ml-4px text-10px line-through"
|
||||
class="ml-[4px] text-[10px] line-through"
|
||||
>
|
||||
¥{{ fenToYuan(spu.marketPrice) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-12px">
|
||||
<div class="text-[12px]">
|
||||
<!-- 销量 -->
|
||||
<span
|
||||
v-if="property.fields.salesCount.show"
|
||||
|
@ -206,22 +208,22 @@ const calculateWidth = () => {
|
|||
</div>
|
||||
</div>
|
||||
<!-- 购买按钮 -->
|
||||
<div class="bottom-8px right-8px absolute">
|
||||
<div class="absolute bottom-[8px] right-[8px]">
|
||||
<!-- 文字按钮 -->
|
||||
<span
|
||||
v-if="property.btnBuy.type === 'text'"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`,
|
||||
}"
|
||||
class="p-x-12px p-y-4px text-12px rounded-full text-white"
|
||||
class="rounded-full px-[12px] py-[4px] text-[12px] text-white"
|
||||
>
|
||||
{{ property.btnBuy.text }}
|
||||
</span>
|
||||
<!-- 图片按钮 -->
|
||||
<el-image
|
||||
<ElImage
|
||||
v-else
|
||||
:src="property.btnBuy.imgUrl"
|
||||
class="h-28px w-28px rounded-full"
|
||||
class="h-[28px] w-[28px] rounded-full"
|
||||
fit="cover"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
<script lang="ts" setup>
|
||||
import type { PromotionPointProperty } from './config';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
ElSwitch,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import ColorInput from '#/components/color-input/index.vue';
|
||||
import PointShowcase from '#/views/mall/promotion/point/components/point-showcase.vue';
|
||||
|
||||
// 秒杀属性面板
|
||||
|
@ -15,119 +29,121 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form :model="formData" label-width="80px">
|
||||
<el-card class="property-group" header="积分商城活动" shadow="never">
|
||||
<ElForm :model="formData" label-width="80px">
|
||||
<ElCard class="property-group" header="积分商城活动" shadow="never">
|
||||
<PointShowcase v-model="formData.activityIds" />
|
||||
</el-card>
|
||||
<el-card class="property-group" header="商品样式" shadow="never">
|
||||
<el-form-item label="布局" prop="type">
|
||||
<el-radio-group v-model="formData.layoutType">
|
||||
<el-tooltip class="item" content="单列大图" placement="bottom">
|
||||
<el-radio-button value="oneColBigImg">
|
||||
<Icon icon="fluent:text-column-one-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="单列小图" placement="bottom">
|
||||
<el-radio-button value="oneColSmallImg">
|
||||
<Icon icon="fluent:text-column-two-left-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="双列" placement="bottom">
|
||||
<el-radio-button value="twoCol">
|
||||
<Icon icon="fluent:text-column-two-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</ElCard>
|
||||
<ElCard class="property-group" header="商品样式" shadow="never">
|
||||
<ElFormItem label="布局" prop="type">
|
||||
<ElRadioGroup v-model="formData.layoutType">
|
||||
<ElTooltip class="item" content="单列大图" placement="bottom">
|
||||
<ElRadioButton value="oneColBigImg">
|
||||
<IconifyIcon icon="fluent:text-column-one-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="单列小图" placement="bottom">
|
||||
<ElRadioButton value="oneColSmallImg">
|
||||
<IconifyIcon icon="fluent:text-column-two-left-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="双列" placement="bottom">
|
||||
<ElRadioButton value="twoCol">
|
||||
<IconifyIcon icon="fluent:text-column-two-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<!--<el-tooltip class="item" content="三列" placement="bottom">
|
||||
<el-radio-button value="threeCol">
|
||||
<Icon icon="fluent:text-column-three-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>-->
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="fields.name.show">
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品名称" prop="fields.name.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.name.color" />
|
||||
<el-checkbox v-model="formData.fields.name.show" />
|
||||
<ElCheckbox v-model="formData.fields.name.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品简介" prop="fields.introduction.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品简介" prop="fields.introduction.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.introduction.color" />
|
||||
<el-checkbox v-model="formData.fields.introduction.show" />
|
||||
<ElCheckbox v-model="formData.fields.introduction.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品价格" prop="fields.price.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品价格" prop="fields.price.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.price.color" />
|
||||
<el-checkbox v-model="formData.fields.price.show" />
|
||||
<ElCheckbox v-model="formData.fields.price.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="市场价" prop="fields.marketPrice.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="市场价" prop="fields.marketPrice.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.marketPrice.color" />
|
||||
<el-checkbox v-model="formData.fields.marketPrice.show" />
|
||||
<ElCheckbox v-model="formData.fields.marketPrice.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品销量" prop="fields.salesCount.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品销量" prop="fields.salesCount.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.salesCount.color" />
|
||||
<el-checkbox v-model="formData.fields.salesCount.show" />
|
||||
<ElCheckbox v-model="formData.fields.salesCount.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品库存" prop="fields.stock.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品库存" prop="fields.stock.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.stock.color" />
|
||||
<el-checkbox v-model="formData.fields.stock.show" />
|
||||
<ElCheckbox v-model="formData.fields.stock.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="property-group" header="角标" shadow="never">
|
||||
<el-form-item label="角标" prop="badge.show">
|
||||
<el-switch v-model="formData.badge.show" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.badge.show"
|
||||
label="角标"
|
||||
prop="badge.imgUrl"
|
||||
>
|
||||
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard class="property-group" header="角标" shadow="never">
|
||||
<ElFormItem label="角标" prop="badge.show">
|
||||
<ElSwitch v-model="formData.badge.show" />
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="formData.badge.show" label="角标" prop="badge.imgUrl">
|
||||
<UploadImg
|
||||
v-model="formData.badge.imgUrl"
|
||||
height="44px"
|
||||
width="72px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:36 * 22</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="property-group" header="按钮" shadow="never">
|
||||
<el-form-item label="按钮类型" prop="btnBuy.type">
|
||||
<el-radio-group v-model="formData.btnBuy.type">
|
||||
<el-radio-button value="text">文字</el-radio-button>
|
||||
<el-radio-button value="img">图片</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard class="property-group" header="按钮" shadow="never">
|
||||
<ElFormItem label="按钮类型" prop="btnBuy.type">
|
||||
<ElRadioGroup v-model="formData.btnBuy.type">
|
||||
<ElRadioButton value="text">文字</ElRadioButton>
|
||||
<ElRadioButton value="img">图片</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<template v-if="formData.btnBuy.type === 'text'">
|
||||
<el-form-item label="按钮文字" prop="btnBuy.text">
|
||||
<el-input v-model="formData.btnBuy.text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||
<ElFormItem label="按钮文字" prop="btnBuy.text">
|
||||
<ElInput v-model="formData.btnBuy.text" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="右侧背景" prop="btnBuy.bgEndColor">
|
||||
<ColorInput v-model="formData.btnBuy.bgEndColor" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form-item label="图片" prop="btnBuy.imgUrl">
|
||||
<ElFormItem label="图片" prop="btnBuy.imgUrl">
|
||||
<UploadImg
|
||||
v-model="formData.btnBuy.imgUrl"
|
||||
height="56px"
|
||||
width="56px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:56 * 56</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</el-card>
|
||||
<el-card class="property-group" header="商品样式" shadow="never">
|
||||
<el-form-item label="上圆角" prop="borderRadiusTop">
|
||||
<el-slider
|
||||
</ElCard>
|
||||
<ElCard class="property-group" header="商品样式" shadow="never">
|
||||
<ElFormItem label="上圆角" prop="borderRadiusTop">
|
||||
<ElSlider
|
||||
v-model="formData.borderRadiusTop"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -135,9 +151,9 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="下圆角" prop="borderRadiusBottom">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="下圆角" prop="borderRadiusBottom">
|
||||
<ElSlider
|
||||
v-model="formData.borderRadiusBottom"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -145,9 +161,9 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" prop="space">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="间隔" prop="space">
|
||||
<ElSlider
|
||||
v-model="formData.space"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -155,9 +171,9 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import { ref, watch } from 'vue';
|
|||
|
||||
import { fenToYuan } from '@vben/utils';
|
||||
|
||||
import { ElImage } from 'element-plus';
|
||||
|
||||
import * as ProductSpuApi from '#/api/mall/product/spu';
|
||||
import * as SeckillActivityApi from '#/api/mall/promotion/seckill/seckillActivity';
|
||||
|
||||
|
@ -93,7 +95,7 @@ const calculateWidth = () => {
|
|||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="min-h-30px box-content flex w-full flex-row flex-wrap"
|
||||
class="box-content flex min-h-[30px] w-full flex-row flex-wrap"
|
||||
ref="containerRef"
|
||||
>
|
||||
<div
|
||||
|
@ -112,28 +114,28 @@ const calculateWidth = () => {
|
|||
<!-- 角标 -->
|
||||
<div
|
||||
v-if="property.badge.show"
|
||||
class="z-1 absolute left-0 top-0 items-center justify-center"
|
||||
class="absolute left-0 top-0 z-[1] items-center justify-center"
|
||||
>
|
||||
<el-image
|
||||
<ElImage
|
||||
fit="cover"
|
||||
:src="property.badge.imgUrl"
|
||||
class="h-26px w-38px"
|
||||
class="h-[26px] w-[38px]"
|
||||
/>
|
||||
</div>
|
||||
<!-- 商品封面图 -->
|
||||
<div
|
||||
class="h-140px"
|
||||
class="h-[140px]"
|
||||
:class="[
|
||||
{
|
||||
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||
'w-140px': property.layoutType === 'oneColSmallImg',
|
||||
'w-[140px]': property.layoutType === 'oneColSmallImg',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
|
||||
<ElImage fit="cover" class="h-full w-full" :src="spu.picUrl" />
|
||||
</div>
|
||||
<div
|
||||
class="gap-8px p-8px box-border flex flex-col"
|
||||
class="box-border flex flex-col gap-2 p-2"
|
||||
:class="[
|
||||
{
|
||||
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||
|
@ -145,7 +147,7 @@ const calculateWidth = () => {
|
|||
<!-- 商品名称 -->
|
||||
<div
|
||||
v-if="property.fields.name.show"
|
||||
class="text-14px"
|
||||
class="text-sm"
|
||||
:class="[
|
||||
{
|
||||
truncate: property.layoutType !== 'oneColSmallImg',
|
||||
|
@ -160,7 +162,7 @@ const calculateWidth = () => {
|
|||
<!-- 商品简介 -->
|
||||
<div
|
||||
v-if="property.fields.introduction.show"
|
||||
class="text-12px truncate"
|
||||
class="truncate text-xs"
|
||||
:style="{ color: property.fields.introduction.color }"
|
||||
>
|
||||
{{ spu.introduction }}
|
||||
|
@ -169,7 +171,7 @@ const calculateWidth = () => {
|
|||
<!-- 价格 -->
|
||||
<span
|
||||
v-if="property.fields.price.show"
|
||||
class="text-16px"
|
||||
class="text-base"
|
||||
:style="{ color: property.fields.price.color }"
|
||||
>
|
||||
¥{{ fenToYuan(spu.price || Infinity) }}
|
||||
|
@ -177,13 +179,13 @@ const calculateWidth = () => {
|
|||
<!-- 市场价 -->
|
||||
<span
|
||||
v-if="property.fields.marketPrice.show && spu.marketPrice"
|
||||
class="ml-4px text-10px line-through"
|
||||
class="ml-1 text-[10px] line-through"
|
||||
:style="{ color: property.fields.marketPrice.color }"
|
||||
>
|
||||
¥{{ fenToYuan(spu.marketPrice) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-12px">
|
||||
<div class="text-xs">
|
||||
<!-- 销量 -->
|
||||
<span
|
||||
v-if="property.fields.salesCount.show"
|
||||
|
@ -201,11 +203,11 @@ const calculateWidth = () => {
|
|||
</div>
|
||||
</div>
|
||||
<!-- 购买按钮 -->
|
||||
<div class="bottom-8px right-8px absolute">
|
||||
<div class="absolute bottom-2 right-2">
|
||||
<!-- 文字按钮 -->
|
||||
<span
|
||||
v-if="property.btnBuy.type === 'text'"
|
||||
class="p-x-12px p-y-4px text-12px rounded-full text-white"
|
||||
class="rounded-full px-3 py-1 text-xs text-white"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`,
|
||||
}"
|
||||
|
@ -213,9 +215,9 @@ const calculateWidth = () => {
|
|||
{{ property.btnBuy.text }}
|
||||
</span>
|
||||
<!-- 图片按钮 -->
|
||||
<el-image
|
||||
<ElImage
|
||||
v-else
|
||||
class="h-28px w-28px rounded-full"
|
||||
class="h-7 w-7 rounded-full"
|
||||
fit="cover"
|
||||
:src="property.btnBuy.imgUrl"
|
||||
/>
|
||||
|
|
|
@ -5,9 +5,24 @@ import type { MallSeckillActivityApi } from '#/api/mall/promotion/seckill/seckil
|
|||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElCheckbox,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
ElSwitch,
|
||||
} from 'element-plus';
|
||||
|
||||
import * as SeckillActivityApi from '#/api/mall/promotion/seckill/seckillActivity';
|
||||
import ColorInput from '#/components/color-input/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
import { CommonStatusEnum } from '#/utils/constants';
|
||||
import SeckillShowcase from '#/views/mall/promotion/seckill/components/seckill-showcase.vue';
|
||||
|
||||
|
@ -31,119 +46,121 @@ onMounted(async () => {
|
|||
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="80px" :model="formData">
|
||||
<el-card header="秒杀活动" class="property-group" shadow="never">
|
||||
<ElForm label-width="80px" :model="formData">
|
||||
<ElCard header="秒杀活动" class="property-group" shadow="never">
|
||||
<SeckillShowcase v-model="formData.activityIds" />
|
||||
</el-card>
|
||||
<el-card header="商品样式" class="property-group" shadow="never">
|
||||
<el-form-item label="布局" prop="type">
|
||||
<el-radio-group v-model="formData.layoutType">
|
||||
<el-tooltip class="item" content="单列大图" placement="bottom">
|
||||
<el-radio-button value="oneColBigImg">
|
||||
<Icon icon="fluent:text-column-one-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="单列小图" placement="bottom">
|
||||
<el-radio-button value="oneColSmallImg">
|
||||
<Icon icon="fluent:text-column-two-left-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="双列" placement="bottom">
|
||||
<el-radio-button value="twoCol">
|
||||
<Icon icon="fluent:text-column-two-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</ElCard>
|
||||
<ElCard header="商品样式" class="property-group" shadow="never">
|
||||
<ElFormItem label="布局" prop="type">
|
||||
<ElRadioGroup v-model="formData.layoutType">
|
||||
<ElTooltip class="item" content="单列大图" placement="bottom">
|
||||
<ElRadioButton value="oneColBigImg">
|
||||
<IconifyIcon icon="fluent:text-column-one-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="单列小图" placement="bottom">
|
||||
<ElRadioButton value="oneColSmallImg">
|
||||
<IconifyIcon icon="fluent:text-column-two-left-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip class="item" content="双列" placement="bottom">
|
||||
<ElRadioButton value="twoCol">
|
||||
<IconifyIcon icon="fluent:text-column-two-24-filled" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<!--<el-tooltip class="item" content="三列" placement="bottom">
|
||||
<el-radio-button value="threeCol">
|
||||
<Icon icon="fluent:text-column-three-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>-->
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="fields.name.show">
|
||||
</ElTooltip>-->
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品名称" prop="fields.name.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.name.color" />
|
||||
<el-checkbox v-model="formData.fields.name.show" />
|
||||
<ElCheckbox v-model="formData.fields.name.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品简介" prop="fields.introduction.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品简介" prop="fields.introduction.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.introduction.color" />
|
||||
<el-checkbox v-model="formData.fields.introduction.show" />
|
||||
<ElCheckbox v-model="formData.fields.introduction.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品价格" prop="fields.price.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品价格" prop="fields.price.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.price.color" />
|
||||
<el-checkbox v-model="formData.fields.price.show" />
|
||||
<ElCheckbox v-model="formData.fields.price.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="市场价" prop="fields.marketPrice.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="市场价" prop="fields.marketPrice.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.marketPrice.color" />
|
||||
<el-checkbox v-model="formData.fields.marketPrice.show" />
|
||||
<ElCheckbox v-model="formData.fields.marketPrice.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品销量" prop="fields.salesCount.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品销量" prop="fields.salesCount.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.salesCount.color" />
|
||||
<el-checkbox v-model="formData.fields.salesCount.show" />
|
||||
<ElCheckbox v-model="formData.fields.salesCount.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品库存" prop="fields.stock.show">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品库存" prop="fields.stock.show">
|
||||
<div class="gap-8px flex">
|
||||
<ColorInput v-model="formData.fields.stock.color" />
|
||||
<el-checkbox v-model="formData.fields.stock.show" />
|
||||
<ElCheckbox v-model="formData.fields.stock.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="角标" class="property-group" shadow="never">
|
||||
<el-form-item label="角标" prop="badge.show">
|
||||
<el-switch v-model="formData.badge.show" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角标"
|
||||
prop="badge.imgUrl"
|
||||
v-if="formData.badge.show"
|
||||
>
|
||||
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="角标" class="property-group" shadow="never">
|
||||
<ElFormItem label="角标" prop="badge.show">
|
||||
<ElSwitch v-model="formData.badge.show" />
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="formData.badge.show" label="角标" prop="badge.imgUrl">
|
||||
<UploadImg
|
||||
v-model="formData.badge.imgUrl"
|
||||
height="44px"
|
||||
width="72px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:36 * 22</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="按钮" class="property-group" shadow="never">
|
||||
<el-form-item label="按钮类型" prop="btnBuy.type">
|
||||
<el-radio-group v-model="formData.btnBuy.type">
|
||||
<el-radio-button value="text">文字</el-radio-button>
|
||||
<el-radio-button value="img">图片</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
<ElCard header="按钮" class="property-group" shadow="never">
|
||||
<ElFormItem label="按钮类型" prop="btnBuy.type">
|
||||
<ElRadioGroup v-model="formData.btnBuy.type">
|
||||
<ElRadioButton value="text">文字</ElRadioButton>
|
||||
<ElRadioButton value="img">图片</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<template v-if="formData.btnBuy.type === 'text'">
|
||||
<el-form-item label="按钮文字" prop="btnBuy.text">
|
||||
<el-input v-model="formData.btnBuy.text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||
<ElFormItem label="按钮文字" prop="btnBuy.text">
|
||||
<ElInput v-model="formData.btnBuy.text" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="右侧背景" prop="btnBuy.bgEndColor">
|
||||
<ColorInput v-model="formData.btnBuy.bgEndColor" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form-item label="图片" prop="btnBuy.imgUrl">
|
||||
<ElFormItem label="图片" prop="btnBuy.imgUrl">
|
||||
<UploadImg
|
||||
v-model="formData.btnBuy.imgUrl"
|
||||
height="56px"
|
||||
width="56px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸:56 * 56</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</el-card>
|
||||
<el-card header="商品样式" class="property-group" shadow="never">
|
||||
<el-form-item label="上圆角" prop="borderRadiusTop">
|
||||
<el-slider
|
||||
</ElCard>
|
||||
<ElCard header="商品样式" class="property-group" shadow="never">
|
||||
<ElFormItem label="上圆角" prop="borderRadiusTop">
|
||||
<ElSlider
|
||||
v-model="formData.borderRadiusTop"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -151,9 +168,9 @@ onMounted(async () => {
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="下圆角" prop="borderRadiusBottom">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="下圆角" prop="borderRadiusBottom">
|
||||
<ElSlider
|
||||
v-model="formData.borderRadiusBottom"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -161,9 +178,9 @@ onMounted(async () => {
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" prop="space">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="间隔" prop="space">
|
||||
<ElSlider
|
||||
v-model="formData.space"
|
||||
:max="100"
|
||||
:min="0"
|
||||
|
@ -171,9 +188,9 @@ onMounted(async () => {
|
|||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { SearchProperty } from './config';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
/** 搜索框 */
|
||||
defineOptions({ name: 'SearchBar' });
|
||||
defineProps<{ property: SearchProperty }>();
|
||||
|
@ -28,7 +30,7 @@ defineProps<{ property: SearchProperty }>();
|
|||
justifyContent: property.placeholderPosition,
|
||||
}"
|
||||
>
|
||||
<Icon icon="ep:search" />
|
||||
<IconifyIcon icon="ep:search" />
|
||||
<span>{{ property.placeholder || '搜索商品' }}</span>
|
||||
</div>
|
||||
<div class="right">
|
||||
|
@ -37,7 +39,10 @@ defineProps<{ property: SearchProperty }>();
|
|||
keyword
|
||||
}}</span>
|
||||
<!-- 扫一扫 -->
|
||||
<Icon icon="ant-design:scan-outlined" v-show="property.showScan" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:scan-outlined"
|
||||
v-show="property.showScan"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,9 +3,23 @@ import type { SearchProperty } from './config';
|
|||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { isString } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElCard,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSlider,
|
||||
ElSwitch,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import ComponentContainerProperty from '#/components/diy-editor/components/ComponentContainerProperty.vue';
|
||||
import Draggable from '#/components/draggable/index.vue';
|
||||
|
||||
/** 搜索框属性面板 */
|
||||
defineOptions({ name: 'SearchProperty' });
|
||||
|
@ -31,9 +45,15 @@ watch(
|
|||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<!-- 表单 -->
|
||||
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
||||
<el-card header="搜索热词" class="property-group" shadow="never">
|
||||
<Draggable v-model="formData.hotKeywords" empty-item="" :min="0">
|
||||
<ElForm label-width="80px" :model="formData" class="m-t-8px">
|
||||
<ElCard header="搜索热词" class="property-group" shadow="never">
|
||||
<Draggable
|
||||
v-model="formData.hotKeywords"
|
||||
:empty-item="{
|
||||
type: 'input',
|
||||
placeholder: '请输入热词',
|
||||
}"
|
||||
>
|
||||
<template #default="{ index }">
|
||||
<el-input
|
||||
v-model="formData.hotKeywords[index]"
|
||||
|
@ -41,59 +61,59 @@ watch(
|
|||
/>
|
||||
</template>
|
||||
</Draggable>
|
||||
</el-card>
|
||||
<el-card header="搜索样式" class="property-group" shadow="never">
|
||||
<el-form-item label="框体样式">
|
||||
<el-radio-group v-model="formData!.borderRadius">
|
||||
<el-tooltip content="方形" placement="top">
|
||||
<el-radio-button :value="0">
|
||||
<Icon icon="tabler:input-search" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="圆形" placement="top">
|
||||
<el-radio-button :value="10">
|
||||
<Icon icon="iconoir:input-search" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="提示文字" prop="placeholder">
|
||||
</ElCard>
|
||||
<ElCard header="搜索样式" class="property-group" shadow="never">
|
||||
<ElFormItem label="框体样式">
|
||||
<ElRadioGroup v-model="formData!.borderRadius">
|
||||
<ElTooltip content="方形" placement="top">
|
||||
<ElRadioButton :value="0">
|
||||
<IconifyIcon icon="tabler:input-search" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="圆形" placement="top">
|
||||
<ElRadioButton :value="10">
|
||||
<IconifyIcon icon="iconoir:input-search" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="提示文字" prop="placeholder">
|
||||
<el-input v-model="formData.placeholder" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文本位置" prop="placeholderPosition">
|
||||
<el-radio-group v-model="formData!.placeholderPosition">
|
||||
<el-tooltip content="居左" placement="top">
|
||||
<el-radio-button value="left">
|
||||
<Icon icon="ant-design:align-left-outlined" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="居中" placement="top">
|
||||
<el-radio-button value="center">
|
||||
<Icon icon="ant-design:align-center-outlined" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="扫一扫" prop="showScan">
|
||||
<el-switch v-model="formData!.showScan" />
|
||||
</el-form-item>
|
||||
<el-form-item label="框体高度" prop="height">
|
||||
<el-slider
|
||||
</ElFormItem>
|
||||
<ElFormItem label="文本位置" prop="placeholderPosition">
|
||||
<ElRadioGroup v-model="formData!.placeholderPosition">
|
||||
<ElTooltip content="居左" placement="top">
|
||||
<ElRadioButton value="left">
|
||||
<IconifyIcon icon="ant-design:align-left-outlined" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="居中" placement="top">
|
||||
<ElRadioButton value="center">
|
||||
<IconifyIcon icon="ant-design:align-center-outlined" />
|
||||
</ElRadioButton>
|
||||
</ElTooltip>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="扫一扫" prop="showScan">
|
||||
<ElSwitch v-model="formData!.showScan" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="框体高度" prop="height">
|
||||
<ElSlider
|
||||
v-model="formData!.height"
|
||||
:max="50"
|
||||
:min="28"
|
||||
show-input
|
||||
input-size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="框体颜色" prop="backgroundColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="框体颜色" prop="backgroundColor">
|
||||
<ColorInput v-model="formData.backgroundColor" />
|
||||
</el-form-item>
|
||||
<el-form-item class="lef" label="文本颜色" prop="textColor">
|
||||
</ElFormItem>
|
||||
<ElFormItem class="lef" label="文本颜色" prop="textColor">
|
||||
<ColorInput v-model="formData.textColor" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
</ElForm>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import type { TabBarProperty } from './config';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { ElImage } from 'element-plus';
|
||||
|
||||
/** 页面底部导航栏 */
|
||||
defineOptions({ name: 'TabBar' });
|
||||
|
||||
|
@ -24,13 +28,13 @@ defineProps<{ property: TabBarProperty }>();
|
|||
:key="index"
|
||||
class="tab-bar-item"
|
||||
>
|
||||
<el-image :src="index === 0 ? item.activeIconUrl : item.iconUrl">
|
||||
<ElImage :src="index === 0 ? item.activeIconUrl : item.iconUrl">
|
||||
<template #error>
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
<Icon icon="ep:picture" />
|
||||
<IconifyIcon icon="ep:picture" />
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</ElImage>
|
||||
<span
|
||||
:style="{
|
||||
color:
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import type { TabBarProperty } from './config';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElOption,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSelect,
|
||||
ElText,
|
||||
} from 'element-plus';
|
||||
|
||||
import AppLinkInput from '#/components/app-link-input/index.vue';
|
||||
import ColorInput from '#/components/color-input/index.vue';
|
||||
import Draggable from '#/components/draggable/index.vue';
|
||||
import UploadImg from '#/components/upload/image-upload.vue';
|
||||
|
||||
import { component, THEME_LIST } from './config';
|
||||
// 底部导航栏
|
||||
|
@ -26,10 +43,10 @@ const handleThemeChange = () => {
|
|||
<template>
|
||||
<div class="tab-bar">
|
||||
<!-- 表单 -->
|
||||
<el-form :model="formData" label-width="80px">
|
||||
<el-form-item label="主题" prop="theme">
|
||||
<el-select v-model="formData!.theme" @change="handleThemeChange">
|
||||
<el-option
|
||||
<ElForm :model="formData" label-width="80px">
|
||||
<ElFormItem label="主题" prop="theme">
|
||||
<ElSelect v-model="formData!.theme" @change="handleThemeChange">
|
||||
<ElOption
|
||||
v-for="(theme, index) in THEME_LIST"
|
||||
:key="index"
|
||||
:label="theme.name"
|
||||
|
@ -37,55 +54,56 @@ const handleThemeChange = () => {
|
|||
>
|
||||
<template #default>
|
||||
<div class="flex items-center justify-between">
|
||||
<Icon :icon="theme.icon" :color="theme.color" />
|
||||
<IconifyIcon :icon="theme.icon" :color="theme.color" />
|
||||
<span>{{ theme.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="默认颜色">
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="默认颜色">
|
||||
<ColorInput v-model="formData!.style.color" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选中颜色">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选中颜色">
|
||||
<ColorInput v-model="formData!.style.activeColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="导航背景">
|
||||
<el-radio-group v-model="formData!.style.bgType">
|
||||
<el-radio-button value="color">纯色</el-radio-button>
|
||||
<el-radio-button value="img">图片</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择颜色" v-if="formData!.style.bgType === 'color'">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="导航背景">
|
||||
<ElRadioGroup v-model="formData!.style.bgType">
|
||||
<ElRadioButton value="color">纯色</ElRadioButton>
|
||||
<ElRadioButton value="img">图片</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选择颜色" v-if="formData!.style.bgType === 'color'">
|
||||
<ColorInput v-model="formData!.style.bgColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择图片" v-if="formData!.style.bgType === 'img'">
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选择图片" v-if="formData!.style.bgType === 'img'">
|
||||
<UploadImg
|
||||
v-model="formData!.style.bgImg"
|
||||
width="100%"
|
||||
height="50px"
|
||||
class="min-w-200px"
|
||||
class="min-w-[200px]"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议尺寸 375 * 50 </template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
|
||||
<el-text tag="p">图标设置</el-text>
|
||||
<el-text type="info" size="small">
|
||||
<ElText tag="p">图标设置</ElText>
|
||||
<ElText type="info" size="small">
|
||||
拖动左上角的小圆点可对其排序, 图标建议尺寸 44*44
|
||||
</el-text>
|
||||
</ElText>
|
||||
<Draggable v-model="formData.items" :limit="5">
|
||||
<template #default="{ element }">
|
||||
<div class="m-b-8px flex items-center justify-around">
|
||||
<div class="mb-2 flex items-center justify-around">
|
||||
<div class="flex flex-col items-center justify-between">
|
||||
<UploadImg
|
||||
v-model="element.iconUrl"
|
||||
width="40px"
|
||||
height="40px"
|
||||
:show-delete="false"
|
||||
:show-btn-text="false"
|
||||
:show-description="false"
|
||||
/>
|
||||
<el-text size="small">未选中</el-text>
|
||||
<ElText size="small">未选中</ElText>
|
||||
</div>
|
||||
<div>
|
||||
<UploadImg
|
||||
|
@ -93,30 +111,20 @@ const handleThemeChange = () => {
|
|||
width="40px"
|
||||
height="40px"
|
||||
:show-delete="false"
|
||||
:show-btn-text="false"
|
||||
:show-description="false"
|
||||
/>
|
||||
<el-text>已选中</el-text>
|
||||
<ElText>已选中</ElText>
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item
|
||||
prop="text"
|
||||
label="文字"
|
||||
label-width="48px"
|
||||
class="m-b-8px!"
|
||||
>
|
||||
<el-input v-model="element.text" placeholder="请输入文字" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
prop="url"
|
||||
label="链接"
|
||||
label-width="48px"
|
||||
class="m-b-0!"
|
||||
>
|
||||
<ElFormItem prop="text" label="文字" label-width="48px" class="mb-2">
|
||||
<ElInput v-model="element.text" placeholder="请输入文字" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="url" label="链接" label-width="48px" class="mb-0">
|
||||
<AppLinkInput v-model="element.url" />
|
||||
</el-form-item>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</Draggable>
|
||||
</el-form>
|
||||
</ElForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -18,7 +18,12 @@ const rules = {};
|
|||
<el-form label-width="85px" :model="formData" :rules="rules">
|
||||
<el-card header="风格" class="property-group" shadow="never">
|
||||
<el-form-item label="背景图片" prop="bgImgUrl">
|
||||
<UploadImg v-model="formData.bgImgUrl" width="100%" height="40px">
|
||||
<UploadImg
|
||||
v-model="formData.bgImgUrl"
|
||||
width="100%"
|
||||
height="40px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip>建议尺寸 750*80</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
|
|
|
@ -42,6 +42,7 @@ const formData = useVModel(props, 'modelValue', emit);
|
|||
height="80px"
|
||||
width="100%"
|
||||
class="min-w-80px"
|
||||
:show-description="false"
|
||||
>
|
||||
<template #tip> 建议宽度750 </template>
|
||||
</UploadImg>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { components } from './components/mobile/index';
|
||||
|
||||
export default {
|
||||
components: { ...components },
|
||||
};
|
|
@ -3,20 +3,40 @@ import type { DiyComponent, DiyComponentLibrary, PageConfig } from './util';
|
|||
|
||||
import { inject, onMounted, ref, unref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep, isEmpty, isString } from '@vben/utils';
|
||||
|
||||
import {
|
||||
ElAside,
|
||||
ElButtonGroup,
|
||||
ElCard,
|
||||
ElContainer,
|
||||
ElDialog,
|
||||
ElHeader,
|
||||
ElScrollbar,
|
||||
ElTag,
|
||||
ElText,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
import { componentConfigs } from '#/components/diy-editor/components/mobile';
|
||||
import statusBarImg from '#/assets/imgs/diy/statusBar.png';
|
||||
import {
|
||||
componentConfigs,
|
||||
components,
|
||||
} from '#/components/diy-editor/components/mobile';
|
||||
import { component as PAGE_CONFIG_COMPONENT } from '#/components/diy-editor/components/mobile/PageConfig/config';
|
||||
|
||||
import ComponentContainer from './components/ComponentContainer.vue';
|
||||
import ComponentLibrary from './components/ComponentLibrary.vue';
|
||||
import { component as NAVIGATION_BAR_COMPONENT } from './components/mobile/NavigationBar/config';
|
||||
import { component as TAB_BAR_COMPONENT } from './components/mobile/TabBar/config';
|
||||
|
||||
/** 页面装修详情页 */
|
||||
defineOptions({ name: 'DiyPageDetail' });
|
||||
|
||||
defineOptions({
|
||||
name: 'DiyPageDetail',
|
||||
components,
|
||||
});
|
||||
// 定义属性
|
||||
const props = defineProps({
|
||||
// 页面配置,支持Json字符串
|
||||
|
@ -34,7 +54,6 @@ const props = defineProps({
|
|||
// 预览地址:提供了预览地址,才会显示预览按钮
|
||||
previewUrl: { type: String, default: '' },
|
||||
});
|
||||
|
||||
// 工具栏操作
|
||||
const emits = defineEmits(['reset', 'preview', 'save', 'update:modelValue']);
|
||||
|
||||
|
@ -271,218 +290,217 @@ watch(
|
|||
() => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
|
||||
() => setDefaultSelectedComponent(),
|
||||
);
|
||||
|
||||
onMounted(() => setDefaultSelectedComponent());
|
||||
onMounted(() => {
|
||||
setDefaultSelectedComponent();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<el-container class="editor">
|
||||
<!-- 顶部:工具栏 -->
|
||||
<el-header class="editor-header">
|
||||
<!-- 左侧操作区 -->
|
||||
<slot name="toolBarLeft"></slot>
|
||||
<!-- 中心操作区 -->
|
||||
<div class="header-center flex flex-1 items-center justify-center">
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<!-- 右侧操作区 -->
|
||||
<el-button-group class="header-right">
|
||||
<el-tooltip content="重置">
|
||||
<el-button @click="handleReset">
|
||||
<Icon :size="24" icon="system-uicons:reset-alt" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="previewUrl" content="预览">
|
||||
<el-button @click="handlePreview">
|
||||
<Icon :size="24" icon="ep:view" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="保存">
|
||||
<el-button @click="handleSave">
|
||||
<Icon :size="24" icon="ep:check" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
</el-header>
|
||||
<div>
|
||||
<ElContainer class="editor">
|
||||
<!-- 顶部:工具栏 -->
|
||||
<ElHeader class="editor-header">
|
||||
<!-- 左侧操作区 -->
|
||||
<slot name="toolBarLeft"></slot>
|
||||
<!-- 中心操作区 -->
|
||||
<div class="header-center flex flex-1 items-center justify-center">
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<!-- 右侧操作区 -->
|
||||
<ElButtonGroup class="header-right">
|
||||
<ElTooltip content="重置">
|
||||
<ElButton @click="handleReset">
|
||||
<IconifyIcon :size="24" icon="system-uicons:reset-alt" />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip v-if="previewUrl" content="预览">
|
||||
<ElButton @click="handlePreview">
|
||||
<IconifyIcon :size="24" icon="ep:view" />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
<ElTooltip content="保存">
|
||||
<ElButton @click="handleSave">
|
||||
<IconifyIcon :size="24" icon="ep:check" />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
</ElButtonGroup>
|
||||
</ElHeader>
|
||||
|
||||
<!-- 中心区域 -->
|
||||
<el-container class="editor-container">
|
||||
<!-- 左侧:组件库(ComponentLibrary) -->
|
||||
<ComponentLibrary
|
||||
v-if="libs && libs.length > 0"
|
||||
ref="componentLibrary"
|
||||
:list="libs"
|
||||
/>
|
||||
<!-- 中心:设计区域(ComponentContainer) -->
|
||||
<div class="editor-center page-prop-area" @click="handlePageSelected">
|
||||
<!-- 手机顶部 -->
|
||||
<div class="editor-design-top">
|
||||
<!-- 手机顶部状态栏 -->
|
||||
<img
|
||||
alt=""
|
||||
class="status-bar"
|
||||
src="@/assets/imgs/diy/statusBar.png"
|
||||
/>
|
||||
<!-- 手机顶部导航栏 -->
|
||||
<ComponentContainer
|
||||
v-if="showNavigationBar"
|
||||
:active="selectedComponent?.id === navigationBarComponent.id"
|
||||
:component="navigationBarComponent"
|
||||
:show-toolbar="false"
|
||||
class="cursor-pointer!"
|
||||
@click="handleNavigationBarSelected"
|
||||
/>
|
||||
</div>
|
||||
<!-- 绝对定位的组件:例如 弹窗、浮动按钮等 -->
|
||||
<div
|
||||
v-for="(component, index) in pageComponents"
|
||||
:key="index"
|
||||
@click="handleComponentSelected(component, index)"
|
||||
>
|
||||
<component
|
||||
:is="component.id"
|
||||
v-if="
|
||||
component.position === 'fixed' &&
|
||||
selectedComponent?.uid === component.uid
|
||||
"
|
||||
:property="component.property"
|
||||
/>
|
||||
</div>
|
||||
<!-- 手机页面编辑区域 -->
|
||||
<el-scrollbar
|
||||
:view-style="{
|
||||
backgroundColor: pageConfigComponent.property.backgroundColor,
|
||||
backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`,
|
||||
}"
|
||||
height="100%"
|
||||
view-class="phone-container"
|
||||
wrap-class="editor-design-center page-prop-area"
|
||||
>
|
||||
<draggable
|
||||
v-model="pageComponents"
|
||||
:animation="200"
|
||||
:force-fallback="true"
|
||||
class="page-prop-area drag-area"
|
||||
filter=".component-toolbar"
|
||||
ghost-class="draggable-ghost"
|
||||
group="component"
|
||||
item-key="index"
|
||||
@change="handleComponentChange"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<ComponentContainer
|
||||
v-if="!element.position || element.position === 'center'"
|
||||
:active="selectedComponentIndex === index"
|
||||
:can-move-down="index < pageComponents.length - 1"
|
||||
:can-move-up="index > 0"
|
||||
:component="element"
|
||||
@click="handleComponentSelected(element, index)"
|
||||
@copy="handleCopyComponent(index)"
|
||||
@delete="handleDeleteComponent(index)"
|
||||
@move="
|
||||
(direction: number) => handleMoveComponent(index, direction)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</el-scrollbar>
|
||||
<!-- 手机底部导航 -->
|
||||
<div
|
||||
v-if="showTabBar"
|
||||
class="editor-design-bottom component cursor-pointer!"
|
||||
>
|
||||
<ComponentContainer
|
||||
:active="selectedComponent?.id === tabBarComponent.id"
|
||||
:component="tabBarComponent"
|
||||
:show-toolbar="false"
|
||||
@click="handleTabBarSelected"
|
||||
/>
|
||||
</div>
|
||||
<!-- 固定布局的组件 操作按钮区 -->
|
||||
<div class="fixed-component-action-group">
|
||||
<el-tag
|
||||
v-if="showPageConfig"
|
||||
:effect="
|
||||
selectedComponent?.uid === pageConfigComponent.uid
|
||||
? 'dark'
|
||||
: 'plain'
|
||||
"
|
||||
:type="
|
||||
selectedComponent?.uid === pageConfigComponent.uid
|
||||
? 'primary'
|
||||
: 'info'
|
||||
"
|
||||
size="large"
|
||||
@click="handleComponentSelected(pageConfigComponent)"
|
||||
>
|
||||
<Icon :icon="pageConfigComponent.icon" :size="12" />
|
||||
<span>{{ pageConfigComponent.name }}</span>
|
||||
</el-tag>
|
||||
<template v-for="(component, index) in pageComponents" :key="index">
|
||||
<el-tag
|
||||
v-if="component.position === 'fixed'"
|
||||
:effect="
|
||||
selectedComponent?.uid === component.uid ? 'dark' : 'plain'
|
||||
"
|
||||
:type="
|
||||
selectedComponent?.uid === component.uid ? 'primary' : 'info'
|
||||
"
|
||||
closable
|
||||
size="large"
|
||||
@click="handleComponentSelected(component)"
|
||||
@close="handleDeleteComponent(index)"
|
||||
>
|
||||
<Icon :icon="component.icon" :size="12" />
|
||||
<span>{{ component.name }}</span>
|
||||
</el-tag>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:属性面板(ComponentContainerProperty) -->
|
||||
<el-aside
|
||||
v-if="selectedComponent?.property"
|
||||
class="editor-right"
|
||||
width="350px"
|
||||
>
|
||||
<el-card
|
||||
body-class="h-[calc(100%-var(--el-card-padding)-var(--el-card-padding))]"
|
||||
class="h-full"
|
||||
shadow="never"
|
||||
>
|
||||
<!-- 组件名称 -->
|
||||
<template #header>
|
||||
<div class="gap-8px flex items-center">
|
||||
<Icon :icon="selectedComponent?.icon" color="gray" />
|
||||
<span>{{ selectedComponent?.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar
|
||||
class="m-[calc(0px-var(--el-card-padding))]"
|
||||
view-class="p-[var(--el-card-padding)] p-b-[calc(var(--el-card-padding)+var(--el-card-padding))] property"
|
||||
<!-- 中心区域 -->
|
||||
<ElContainer class="editor-container">
|
||||
<!-- 左侧:组件库(ComponentLibrary) -->
|
||||
<ComponentLibrary
|
||||
v-if="libs && libs.length > 0"
|
||||
ref="componentLibrary"
|
||||
:list="libs"
|
||||
/>
|
||||
<!-- 中心:设计区域(ComponentContainer) -->
|
||||
<div class="editor-center page-prop-area" @click="handlePageSelected">
|
||||
<!-- 手机顶部 -->
|
||||
<div class="editor-design-top">
|
||||
<!-- 手机顶部状态栏 -->
|
||||
<img alt="" class="status-bar" :src="statusBarImg" />
|
||||
<!-- 手机顶部导航栏 -->
|
||||
<ComponentContainer
|
||||
v-if="showNavigationBar"
|
||||
:active="selectedComponent?.id === navigationBarComponent.id"
|
||||
:component="navigationBarComponent"
|
||||
:show-toolbar="false"
|
||||
class="cursor-pointer"
|
||||
@click="handleNavigationBarSelected"
|
||||
/>
|
||||
</div>
|
||||
<!-- 绝对定位的组件:例如 弹窗、浮动按钮等 -->
|
||||
<div
|
||||
v-for="(component, index) in pageComponents"
|
||||
:key="index"
|
||||
@click="handleComponentSelected(component, index)"
|
||||
>
|
||||
<component
|
||||
:is="`${selectedComponent?.id}Property`"
|
||||
:key="selectedComponent?.uid || selectedComponent?.id"
|
||||
v-model="selectedComponent.property"
|
||||
:is="component.id"
|
||||
v-if="
|
||||
component.position === 'fixed' &&
|
||||
selectedComponent?.uid === component.uid
|
||||
"
|
||||
:property="component.property"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
</el-aside>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
<!-- 手机页面编辑区域 -->
|
||||
<ElScrollbar
|
||||
:view-style="{
|
||||
backgroundColor: pageConfigComponent.property.backgroundColor,
|
||||
backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`,
|
||||
}"
|
||||
view-class="phone-container"
|
||||
wrap-class="editor-design-center page-prop-area"
|
||||
style="height: calc(100vh - 135px - 120px)"
|
||||
>
|
||||
<draggable
|
||||
v-model="pageComponents"
|
||||
:animation="200"
|
||||
:force-fallback="true"
|
||||
class="page-prop-area drag-area"
|
||||
filter=".component-toolbar"
|
||||
ghost-class="draggable-ghost"
|
||||
group="component"
|
||||
item-key="index"
|
||||
@change="handleComponentChange"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<ComponentContainer
|
||||
v-if="!element.position || element.position === 'center'"
|
||||
:active="selectedComponentIndex === index"
|
||||
:can-move-down="index < pageComponents.length - 1"
|
||||
:can-move-up="index > 0"
|
||||
:component="element"
|
||||
@click="handleComponentSelected(element, index)"
|
||||
@copy="handleCopyComponent(index)"
|
||||
@delete="handleDeleteComponent(index)"
|
||||
@move="
|
||||
(direction: number) => handleMoveComponent(index, direction)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</ElScrollbar>
|
||||
<!-- 手机底部导航 -->
|
||||
<div
|
||||
v-if="showTabBar"
|
||||
class="editor-design-bottom component cursor-pointer"
|
||||
>
|
||||
<ComponentContainer
|
||||
:active="selectedComponent?.id === tabBarComponent.id"
|
||||
:component="tabBarComponent"
|
||||
:show-toolbar="false"
|
||||
@click="handleTabBarSelected"
|
||||
/>
|
||||
</div>
|
||||
<!-- 固定布局的组件 操作按钮区 -->
|
||||
<div class="fixed-component-action-group gap-2">
|
||||
<ElTag
|
||||
v-if="showPageConfig"
|
||||
:effect="
|
||||
selectedComponent?.uid === pageConfigComponent.uid
|
||||
? 'dark'
|
||||
: 'plain'
|
||||
"
|
||||
:type="
|
||||
selectedComponent?.uid === pageConfigComponent.uid
|
||||
? 'primary'
|
||||
: 'info'
|
||||
"
|
||||
size="large"
|
||||
@click="handleComponentSelected(pageConfigComponent)"
|
||||
>
|
||||
<IconifyIcon :icon="pageConfigComponent.icon" :size="12" />
|
||||
<span>{{ pageConfigComponent.name }}</span>
|
||||
</ElTag>
|
||||
<template v-for="(component, index) in pageComponents" :key="index">
|
||||
<ElTag
|
||||
v-if="component.position === 'fixed'"
|
||||
:effect="
|
||||
selectedComponent?.uid === component.uid ? 'dark' : 'plain'
|
||||
"
|
||||
:type="
|
||||
selectedComponent?.uid === component.uid ? 'primary' : 'info'
|
||||
"
|
||||
closable
|
||||
size="large"
|
||||
@click="handleComponentSelected(component)"
|
||||
@close="handleDeleteComponent(index)"
|
||||
>
|
||||
<IconifyIcon :icon="component.icon" :size="12" />
|
||||
<span>{{ component.name }}</span>
|
||||
</ElTag>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:属性面板(ComponentContainerProperty) -->
|
||||
<ElAside
|
||||
v-if="selectedComponent?.property"
|
||||
class="editor-right"
|
||||
width="350px"
|
||||
>
|
||||
<ElCard
|
||||
body-class="h-[calc(100%-var(--el-card-padding)-var(--el-card-padding))]"
|
||||
class="h-full"
|
||||
shadow="never"
|
||||
>
|
||||
<!-- 组件名称 -->
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconifyIcon :icon="selectedComponent?.icon" color="gray" />
|
||||
<span>{{ selectedComponent?.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ElScrollbar
|
||||
class="m-[calc(0px-var(--el-card-padding))]"
|
||||
view-class="p-[var(--el-card-padding)] pb-[calc(var(--el-card-padding)+var(--el-card-padding))] property"
|
||||
>
|
||||
<component
|
||||
:is="`${selectedComponent?.id}Property`"
|
||||
:key="selectedComponent?.uid || selectedComponent?.id"
|
||||
v-model="selectedComponent.property"
|
||||
/>
|
||||
</ElScrollbar>
|
||||
</ElCard>
|
||||
</ElAside>
|
||||
</ElContainer>
|
||||
</ElContainer>
|
||||
|
||||
<!-- 预览弹框 -->
|
||||
<Dialog v-model="previewDialogVisible" title="预览" width="700">
|
||||
<div class="flex justify-around">
|
||||
<IFrame
|
||||
:src="previewUrl"
|
||||
class="w-375px border-4px border-rounded-8px p-2px h-667px! border-solid"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<el-text>手机扫码预览</el-text>
|
||||
<Qrcode :text="previewUrl" logo="/logo.gif" />
|
||||
<!-- 预览弹框 -->
|
||||
<ElDialog v-model="previewDialogVisible" title="预览" width="700">
|
||||
<div class="flex justify-around">
|
||||
<IFrame
|
||||
:src="previewUrl"
|
||||
class="h-[667px] w-[375px] rounded-lg border-4 border-solid p-0.5"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<ElText>手机扫码预览</ElText>
|
||||
<Qrcode :text="previewUrl" logo="/logo.gif" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
/* 手机宽度 */
|
||||
|
@ -493,7 +511,6 @@ $phone-width: 375px;
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin: calc(0px - var(--app-content-padding));
|
||||
|
||||
/* 顶部:工具栏 */
|
||||
.editor-header {
|
||||
|
@ -525,15 +542,10 @@ $phone-width: 375px;
|
|||
|
||||
/* 中心操作区 */
|
||||
.editor-container {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(
|
||||
--app-footer-height
|
||||
) -
|
||||
42px
|
||||
);
|
||||
height: calc(100vh - 135px);
|
||||
|
||||
/* 右侧属性面板 */
|
||||
.editor-right {
|
||||
:deep(.editor-right) {
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
box-shadow: -8px 0 8px -8px rgb(0 0 0 / 12%);
|
||||
|
@ -624,7 +636,6 @@ $phone-width: 375px;
|
|||
right: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
:deep(.el-tag) {
|
||||
border: none;
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<script setup lang="ts">
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
// 拖拽组件
|
||||
import VueDraggable from 'vuedraggable';
|
||||
|
||||
// 拖拽组件封装
|
||||
defineOptions({ name: 'Draggable' });
|
||||
|
||||
// 定义属性
|
||||
const props = defineProps({
|
||||
// 绑定值
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 空的元素:点击添加按钮时,创建元素并添加到列表;默认为空对象
|
||||
emptyItem: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
// 数量限制:默认为0,表示不限制
|
||||
limit: {
|
||||
type: Number,
|
||||
default: Number.MAX_VALUE,
|
||||
},
|
||||
// 最小数量:默认为1
|
||||
min: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
});
|
||||
// 定义事件
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const formData = useVModel(props, 'modelValue', emit);
|
||||
|
||||
// 处理添加
|
||||
const handleAdd = () => formData.value.push(cloneDeep(props.emptyItem || {}));
|
||||
// 处理删除
|
||||
const handleDelete = (index: number) => formData.value.splice(index, 1);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-text type="info" size="small"> 拖动左上角的小圆点可对其排序 </el-text>
|
||||
<VueDraggable
|
||||
:list="formData"
|
||||
:force-fallback="true"
|
||||
:animation="200"
|
||||
handle=".drag-icon"
|
||||
class="mt-2"
|
||||
item-key="index"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="mb-1 flex flex-col gap-1 rounded border border-gray-200 p-2">
|
||||
<!-- 操作按钮区 -->
|
||||
<div
|
||||
class="-m-2 mb-1 flex flex-row items-center justify-between p-2"
|
||||
style="background-color: var(--app-content-bg-color)"
|
||||
>
|
||||
<el-tooltip content="拖动排序">
|
||||
<IconifyIcon
|
||||
icon="ic:round-drag-indicator"
|
||||
class="drag-icon cursor-move"
|
||||
style="color: #8a909c"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除">
|
||||
<IconifyIcon
|
||||
icon="ep:delete"
|
||||
class="cursor-pointer text-red-500"
|
||||
v-if="formData.length > min"
|
||||
@click="handleDelete(index)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<!-- 内容区 -->
|
||||
<slot :element="element" :index="index"></slot>
|
||||
</div>
|
||||
</template>
|
||||
</VueDraggable>
|
||||
<el-tooltip :disabled="limit < 1" :content="`最多添加${limit}个`">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
class="mt-1 w-full"
|
||||
:disabled="limit > 0 && formData.length >= limit"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<IconifyIcon icon="ep:plus" /><span>添加</span>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
|
@ -0,0 +1,44 @@
|
|||
<script lang="ts" setup>
|
||||
import { useVModels } from '@vueuse/core';
|
||||
import { ElColorPicker, ElInput } from 'element-plus';
|
||||
|
||||
import { PREDEFINE_COLORS } from '#/utils/constants';
|
||||
|
||||
/**
|
||||
* 带颜色选择器输入框
|
||||
*/
|
||||
defineOptions({ name: 'InputWithColor' });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue', 'update:color']);
|
||||
const { modelValue, color } = useVModels(props, emit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElInput v-model="modelValue" v-bind="$attrs">
|
||||
<template #append>
|
||||
<ElColorPicker v-model="color" :predefine="PREDEFINE_COLORS" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-input-group__append) {
|
||||
padding: 0;
|
||||
|
||||
.el-color-picker__trigger {
|
||||
padding: 0;
|
||||
border-left: none;
|
||||
border-radius: 0 var(--el-input-border-radius) var(--el-input-border-radius)
|
||||
0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,300 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Point, Rect } from './util';
|
||||
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { createRect, isContains, isOverlap } from './util';
|
||||
|
||||
// 魔方编辑器
|
||||
// 有两部分组成:
|
||||
// 1. 魔方矩阵:位于底层,由方块组件的二维表格,用于创建热区
|
||||
// 操作方法:
|
||||
// 1.1 点击其中一个方块就会进入热区选择模式
|
||||
// 1.2 再次点击另外一个方块时,结束热区选择模式
|
||||
// 1.3 在两个方块中间的区域创建热区
|
||||
// 如果两次点击的都是同一方块,就只创建一个格子的热区
|
||||
// 2. 热区:位于顶层,采用绝对定位,覆盖在魔方矩阵上面。
|
||||
defineOptions({ name: 'MagicCubeEditor' });
|
||||
|
||||
// 定义属性
|
||||
const props = defineProps({
|
||||
// 热区列表
|
||||
modelValue: {
|
||||
type: Array as () => Rect[],
|
||||
default: () => [],
|
||||
},
|
||||
// 行数,默认 4 行
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
// 列数,默认 4 列
|
||||
cols: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
// 方块大小,单位px,默认75px
|
||||
cubeSize: {
|
||||
type: Number,
|
||||
default: 75,
|
||||
},
|
||||
});
|
||||
|
||||
// 发送模型更新
|
||||
const emit = defineEmits(['update:modelValue', 'hotAreaSelected']);
|
||||
|
||||
/**
|
||||
* 方块
|
||||
* @property active 是否激活
|
||||
*/
|
||||
type Cube = Point & { active: boolean };
|
||||
|
||||
// 魔方矩阵:所有的方块
|
||||
const cubes = ref<Cube[][]>([]);
|
||||
// 监听行数、列数变化
|
||||
watch(
|
||||
() => [props.rows, props.cols],
|
||||
() => {
|
||||
// 清空魔方
|
||||
cubes.value = [];
|
||||
if (!props.rows || !props.cols) return;
|
||||
|
||||
// 初始化魔方
|
||||
for (let row = 0; row < props.rows; row++) {
|
||||
cubes.value[row] = [];
|
||||
for (let col = 0; col < props.cols; col++) {
|
||||
cubes.value[row]!.push({ x: col, y: row, active: false });
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// 热区列表
|
||||
const hotAreas = ref<Rect[]>([]);
|
||||
// 初始化热区
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => (hotAreas.value = props.modelValue || []),
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// 热区起始方块
|
||||
const hotAreaBeginCube = ref<Cube>();
|
||||
// 是否开启了热区选择模式
|
||||
const isHotAreaSelectMode = () => !!hotAreaBeginCube.value;
|
||||
/**
|
||||
* 处理鼠标点击方块
|
||||
*
|
||||
* @param currentRow 当前行号
|
||||
* @param currentCol 当前列号
|
||||
*/
|
||||
const handleCubeClick = (currentRow: number, currentCol: number) => {
|
||||
const currentCube = cubes.value[currentRow]?.[currentCol];
|
||||
if (!currentCube) return;
|
||||
|
||||
// 情况1:进入热区选择模式
|
||||
if (!isHotAreaSelectMode()) {
|
||||
hotAreaBeginCube.value = currentCube;
|
||||
hotAreaBeginCube.value!.active = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况2:结束热区选择模式
|
||||
hotAreas.value.push(createRect(hotAreaBeginCube.value!, currentCube));
|
||||
// 结束热区选择模式
|
||||
exitHotAreaSelectMode();
|
||||
// 创建后就选中热区
|
||||
const hotAreaIndex = hotAreas.value.length - 1;
|
||||
const hotArea = hotAreas.value[hotAreaIndex];
|
||||
if (hotArea) {
|
||||
handleHotAreaSelected(hotArea, hotAreaIndex);
|
||||
}
|
||||
// 发送热区变动通知
|
||||
emitUpdateModelValue();
|
||||
};
|
||||
/**
|
||||
* 处理鼠标经过方块
|
||||
*
|
||||
* @param currentRow 当前行号
|
||||
* @param currentCol 当前列号
|
||||
*/
|
||||
const handleCellHover = (currentRow: number, currentCol: number) => {
|
||||
// 当前没有进入热区选择模式
|
||||
if (!isHotAreaSelectMode()) return;
|
||||
|
||||
// 当前已选的区域
|
||||
const currentCube = cubes.value[currentRow]?.[currentCol];
|
||||
if (!currentCube) return;
|
||||
|
||||
const currentSelectedArea = createRect(hotAreaBeginCube.value!, currentCube);
|
||||
// 热区不允许重叠
|
||||
for (const hotArea of hotAreas.value) {
|
||||
// 检查是否重叠
|
||||
if (isOverlap(hotArea, currentSelectedArea)) {
|
||||
// 结束热区选择模式
|
||||
exitHotAreaSelectMode();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 激活选中区域内部的方块
|
||||
eachCube((_, __, cube) => {
|
||||
cube.active = isContains(currentSelectedArea, cube);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 处理热区删除
|
||||
*
|
||||
* @param index 热区索引
|
||||
*/
|
||||
const handleDeleteHotArea = (index: number) => {
|
||||
hotAreas.value.splice(index, 1);
|
||||
// 结束热区选择模式
|
||||
exitHotAreaSelectMode();
|
||||
// 发送热区变动通知
|
||||
emitUpdateModelValue();
|
||||
};
|
||||
|
||||
// 发送热区变动通知
|
||||
const emitUpdateModelValue = () => emit('update:modelValue', hotAreas.value);
|
||||
|
||||
// 热区选中
|
||||
const selectedHotAreaIndex = ref(0);
|
||||
const handleHotAreaSelected = (hotArea: Rect, index: number) => {
|
||||
selectedHotAreaIndex.value = index;
|
||||
emit('hotAreaSelected', hotArea, index);
|
||||
};
|
||||
|
||||
/**
|
||||
* 结束热区选择模式
|
||||
*/
|
||||
function exitHotAreaSelectMode() {
|
||||
// 移除方块激活标记
|
||||
eachCube((_, __, cube) => {
|
||||
if (cube.active) {
|
||||
cube.active = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 清除起点
|
||||
hotAreaBeginCube.value = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 迭代魔方矩阵
|
||||
* @param callback 回调
|
||||
*/
|
||||
const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {
|
||||
for (const [x, row] of cubes.value.entries()) {
|
||||
if (!row) continue;
|
||||
for (const [y, cube] of row.entries()) {
|
||||
if (cube) {
|
||||
callback(x, y, cube);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="relative">
|
||||
<table class="cube-table">
|
||||
<!-- 底层:魔方矩阵 -->
|
||||
<tbody>
|
||||
<tr v-for="(rowCubes, row) in cubes" :key="row">
|
||||
<td
|
||||
v-for="(cube, col) in rowCubes"
|
||||
:key="col"
|
||||
class="cube"
|
||||
:class="[{ active: cube.active }]"
|
||||
:style="{
|
||||
width: `${cubeSize}px`,
|
||||
height: `${cubeSize}px`,
|
||||
}"
|
||||
@click="handleCubeClick(row, col)"
|
||||
@mouseenter="handleCellHover(row, col)"
|
||||
>
|
||||
<Icon icon="ep-plus" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<!-- 顶层:热区 -->
|
||||
<div
|
||||
v-for="(hotArea, index) in hotAreas"
|
||||
:key="index"
|
||||
class="hot-area"
|
||||
:style="{
|
||||
top: `${cubeSize * hotArea.top}px`,
|
||||
left: `${cubeSize * hotArea.left}px`,
|
||||
height: `${cubeSize * hotArea.height}px`,
|
||||
width: `${cubeSize * hotArea.width}px`,
|
||||
}"
|
||||
@click="handleHotAreaSelected(hotArea, index)"
|
||||
@mouseover="exitHotAreaSelectMode"
|
||||
>
|
||||
<!-- 右上角热区删除按钮 -->
|
||||
<div
|
||||
v-if="
|
||||
selectedHotAreaIndex === index && hotArea.width && hotArea.height
|
||||
"
|
||||
class="btn-delete"
|
||||
@click="handleDeleteHotArea(index)"
|
||||
>
|
||||
<IconifyIcon icon="ep:circle-close-filled" />
|
||||
</div>
|
||||
<span v-if="hotArea.width">{{
|
||||
`${hotArea.width}×${hotArea.height}`
|
||||
}}</span>
|
||||
</div>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.cube-table {
|
||||
position: relative;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
|
||||
.cube {
|
||||
border: 1px solid var(--el-border-color);
|
||||
text-align: center;
|
||||
color: var(--el-text-color-secondary);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
&.active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
|
||||
.hot-area {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-8);
|
||||
color: var(--el-color-primary);
|
||||
box-sizing: border-box;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
cursor: pointer;
|
||||
|
||||
.btn-delete {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,72 @@
|
|||
// 坐标点
|
||||
export interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
// 矩形
|
||||
export interface Rect {
|
||||
// 左上角 X 轴坐标
|
||||
left: number;
|
||||
// 左上角 Y 轴坐标
|
||||
top: number;
|
||||
// 右下角 X 轴坐标
|
||||
right: number;
|
||||
// 右下角 Y 轴坐标
|
||||
bottom: number;
|
||||
// 矩形宽度
|
||||
width: number;
|
||||
// 矩形高度
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个矩形是否重叠
|
||||
* @param a 矩形 A
|
||||
* @param b 矩形 B
|
||||
*/
|
||||
export const isOverlap = (a: Rect, b: Rect): boolean => {
|
||||
return (
|
||||
a.left < b.left + b.width &&
|
||||
a.left + a.width > b.left &&
|
||||
a.top < b.top + b.height &&
|
||||
a.height + a.top > b.top
|
||||
);
|
||||
};
|
||||
/**
|
||||
* 检查坐标点是否在矩形内
|
||||
* @param hotArea 矩形
|
||||
* @param point 坐标
|
||||
*/
|
||||
export const isContains = (hotArea: Rect, point: Point): boolean => {
|
||||
return (
|
||||
point.x >= hotArea.left &&
|
||||
point.x < hotArea.right &&
|
||||
point.y >= hotArea.top &&
|
||||
point.y < hotArea.bottom
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 在两个坐标点中间,创建一个矩形
|
||||
*
|
||||
* 存在以下情况:
|
||||
* 1. 两个坐标点是同一个位置,只占一个位置的正方形,宽高都为 1
|
||||
* 2. X 轴坐标相同,只占一行的矩形,高度为 1
|
||||
* 3. Y 轴坐标相同,只占一列的矩形,宽度为 1
|
||||
* 4. 多行多列的矩形
|
||||
*
|
||||
* @param a 坐标点一
|
||||
* @param b 坐标点二
|
||||
*/
|
||||
export const createRect = (a: Point, b: Point): Rect => {
|
||||
// 计算矩形的范围
|
||||
const [left, left2] = [a.x, b.x].sort();
|
||||
const [top, top2] = [a.y, b.y].sort();
|
||||
const right = left2 + 1;
|
||||
const bottom = top2 + 1;
|
||||
const height = bottom - top;
|
||||
const width = right - left;
|
||||
|
||||
return { left, right, top, bottom, height, width };
|
||||
};
|
|
@ -11,7 +11,7 @@ import type { UploadListType } from './typing';
|
|||
|
||||
import type { AxiosProgressEvent } from '#/api/infra/file';
|
||||
|
||||
import { ref, toRefs, watch } from 'vue';
|
||||
import { nextTick, ref, toRefs, watch } from 'vue';
|
||||
|
||||
import { CloudUpload } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
@ -33,26 +33,28 @@ const props = withDefaults(
|
|||
file: File,
|
||||
onUploadProgress?: AxiosProgressEvent,
|
||||
) => Promise<AxiosResponse<any>>;
|
||||
// 组件边框圆角
|
||||
borderradius?: string;
|
||||
// 上传的目录
|
||||
directory?: string;
|
||||
disabled?: boolean;
|
||||
// 上传框高度
|
||||
height?: number | string;
|
||||
helpText?: string;
|
||||
listType?: UploadListType;
|
||||
// 最大数量的文件,Infinity不限制
|
||||
maxNumber?: number;
|
||||
// 文件最大多少MB
|
||||
maxSize?: number;
|
||||
modelValue?: string | string[];
|
||||
// 是否支持多选
|
||||
multiple?: boolean;
|
||||
// support xxx.xxx.xx
|
||||
resultField?: string;
|
||||
// 是否显示下面的描述
|
||||
showDescription?: boolean;
|
||||
modelValue?: string | string[];
|
||||
// 上传框宽度
|
||||
width?: string | number;
|
||||
// 上传框高度
|
||||
height?: string | number;
|
||||
width?: number | string;
|
||||
}>(),
|
||||
{
|
||||
modelValue: () => [],
|
||||
|
@ -69,11 +71,13 @@ const props = withDefaults(
|
|||
showDescription: true,
|
||||
width: '',
|
||||
height: '',
|
||||
borderradius: '8px',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits(['change', 'update:modelValue', 'delete']);
|
||||
const { accept, helpText, maxNumber, maxSize, width, height } = toRefs(props);
|
||||
const { accept, helpText, maxNumber, maxSize, width, height, borderradius } =
|
||||
toRefs(props);
|
||||
const isInnerOperate = ref<boolean>(false);
|
||||
const { getStringAccept } = useUploadType({
|
||||
acceptRef: accept,
|
||||
|
@ -219,7 +223,6 @@ async function customRequest(options: UploadRequestOptions) {
|
|||
}
|
||||
|
||||
function getValue() {
|
||||
console.log(fileList.value);
|
||||
const list = (fileList.value || [])
|
||||
.filter((item) => item?.status === UploadResultStatus.SUCCESS)
|
||||
.map((item: any) => {
|
||||
|
@ -234,58 +237,178 @@ function getValue() {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// 编辑按钮:触发文件选择
|
||||
const triggerEdit = () => {
|
||||
if (props.disabled) return;
|
||||
// 只查找当前 upload-box 下的 input
|
||||
nextTick(() => {
|
||||
const uploadBox = document.querySelector('.upload-box');
|
||||
if (uploadBox) {
|
||||
const input = uploadBox.querySelector(
|
||||
'input[type="file"]',
|
||||
) as HTMLInputElement | null;
|
||||
if (input) input.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ElUpload
|
||||
v-bind="$attrs"
|
||||
v-model:file-list="fileList"
|
||||
:accept="getStringAccept"
|
||||
:before-upload="beforeUpload"
|
||||
:http-request="customRequest"
|
||||
:disabled="disabled"
|
||||
:list-type="listType"
|
||||
:limit="maxNumber"
|
||||
:multiple="multiple"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
:class="width || height ? 'custom-upload' : ''"
|
||||
<div
|
||||
class="upload-box"
|
||||
:style="{
|
||||
width: width || '150px',
|
||||
height: height || '150px',
|
||||
borderRadius: borderradius,
|
||||
}"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
fileList.length > 0 &&
|
||||
fileList[0] &&
|
||||
fileList[0].status === UploadResultStatus.SUCCESS
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="upload-content flex flex-col items-center justify-center"
|
||||
:style="{ width: width || '', height: height || '' }"
|
||||
>
|
||||
<CloudUpload />
|
||||
<div class="mt-2">{{ $t('ui.upload.imgUpload') }}</div>
|
||||
<div class="upload-image-wrapper">
|
||||
<img :src="fileList[0].url" class="upload-image" />
|
||||
<div class="upload-handle">
|
||||
<div class="handle-icon" @click="handlePreview(fileList[0]!)">
|
||||
<i class="el-icon el-icon-zoom-in"></i>
|
||||
<span>详情</span>
|
||||
</div>
|
||||
<div v-if="!disabled" class="handle-icon" @click="triggerEdit">
|
||||
<i class="el-icon el-icon-edit"></i>
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!disabled"
|
||||
class="handle-icon"
|
||||
@click="handleRemove(fileList[0]!)"
|
||||
>
|
||||
<i class="el-icon el-icon-delete"></i>
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElUpload>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ElUpload
|
||||
v-bind="$attrs"
|
||||
v-model:file-list="fileList"
|
||||
:accept="getStringAccept"
|
||||
:before-upload="beforeUpload"
|
||||
:http-request="customRequest"
|
||||
:disabled="disabled"
|
||||
:list-type="listType"
|
||||
:limit="maxNumber"
|
||||
:multiple="multiple"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
class="upload"
|
||||
:style="{
|
||||
width: width || '150px',
|
||||
height: height || '150px',
|
||||
borderRadius: borderradius,
|
||||
}"
|
||||
>
|
||||
<div class="upload-content flex flex-col items-center justify-center">
|
||||
<CloudUpload />
|
||||
<div class="mt-2">{{ $t('ui.upload.imgUpload') }}</div>
|
||||
</div>
|
||||
</ElUpload>
|
||||
</template>
|
||||
<div v-if="showDescription" class="mt-2 text-xs text-gray-500">
|
||||
{{ getStringAccept }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.ant-upload-select-picture-card {
|
||||
@apply flex items-center justify-center;
|
||||
}
|
||||
|
||||
.custom-upload .el-upload {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.custom-upload .el-upload--picture-card {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
}
|
||||
|
||||
.custom-upload .upload-content {
|
||||
<style lang="scss" scoped>
|
||||
.upload-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed var(--el-border-color-darker);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #fafafa;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
.upload {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
border: none !important;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-image-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: inherit;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.upload-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.upload-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.handle-icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
margin: 0 8px;
|
||||
font-size: 18px;
|
||||
span {
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.upload-image-wrapper:hover .upload-handle {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<script setup lang="ts">
|
||||
/**
|
||||
* 垂直按钮组
|
||||
* Element官方的按钮组只支持水平显示,通过重写样式实现垂直布局
|
||||
*/
|
||||
defineOptions({ name: 'VerticalButtonGroup' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-button-group v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</el-button-group>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.el-button-group {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.el-button-group > :deep(.el-button:first-child) {
|
||||
border-bottom-color: var(--el-button-divide-border-color);
|
||||
border-top-right-radius: var(--el-border-radius-base);
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.el-button-group > :deep(.el-button:last-child) {
|
||||
border-top-color: var(--el-button-divide-border-color);
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-left-radius: var(--el-border-radius-base);
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
.el-button-group :deep(.el-button--primary:not(:first-child, :last-child)) {
|
||||
border-top-color: var(--el-button-divide-border-color);
|
||||
border-bottom-color: var(--el-button-divide-border-color);
|
||||
}
|
||||
|
||||
.el-button-group > :deep(.el-button:not(:last-child)) {
|
||||
margin-right: 0;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
</style>
|
|
@ -4,16 +4,18 @@ const routes: RouteRecordRaw[] = [
|
|||
{
|
||||
path: '/diy',
|
||||
name: 'DiyCenter',
|
||||
meta: { hidden: true },
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '营销中心',
|
||||
icon: 'lucide:shopping-bag',
|
||||
keepAlive: true,
|
||||
hideInMenu: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'template/decorate/:id',
|
||||
path: String.raw`template/decorate/:id(\d+)`,
|
||||
name: 'DiyTemplateDecorate',
|
||||
meta: {
|
||||
title: '模板装修',
|
||||
noCache: false,
|
||||
hidden: true,
|
||||
activeMenu: '/mall/promotion/diy-template/diy-template',
|
||||
},
|
||||
component: () =>
|
||||
|
|
|
@ -644,3 +644,24 @@ export enum TaskStatusEnum {
|
|||
*/
|
||||
WAIT = 0,
|
||||
}
|
||||
|
||||
// 预设颜色
|
||||
export const PREDEFINE_COLORS = [
|
||||
'#ff4500',
|
||||
'#ff8c00',
|
||||
'#ffd700',
|
||||
'#90ee90',
|
||||
'#00ced1',
|
||||
'#1e90ff',
|
||||
'#c71585',
|
||||
'#409EFF',
|
||||
'#909399',
|
||||
'#C0C4CC',
|
||||
'#b7390b',
|
||||
'#ff7800',
|
||||
'#fad400',
|
||||
'#5b8c5f',
|
||||
'#00babd',
|
||||
'#1f73c3',
|
||||
'#711f57',
|
||||
];
|
||||
|
|
|
@ -3,6 +3,10 @@ import type { MallSpuApi } from '#/api/mall/product/spu';
|
|||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { ElImage, ElTooltip } from 'element-plus';
|
||||
|
||||
import * as ProductSpuApi from '#/api/mall/product/spu';
|
||||
import SpuTableSelect from '#/views/mall/product/spu/components/spu-table-select.vue';
|
||||
|
||||
|
@ -110,23 +114,23 @@ const emitSpuChange = () => {
|
|||
:key="spu.id"
|
||||
class="select-box spu-pic"
|
||||
>
|
||||
<el-tooltip :content="spu.name">
|
||||
<ElTooltip :content="spu.name">
|
||||
<div class="relative h-full w-full">
|
||||
<el-image :src="spu.picUrl" class="h-full w-full" />
|
||||
<Icon
|
||||
<ElImage :src="spu.picUrl" class="h-full w-full" />
|
||||
<IconifyIcon
|
||||
v-show="!disabled"
|
||||
class="del-icon"
|
||||
icon="ep:circle-close-filled"
|
||||
@click="handleRemoveSpu(index)"
|
||||
/>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<el-tooltip content="选择商品" v-if="canAdd">
|
||||
<ElTooltip content="选择商品" v-if="canAdd">
|
||||
<div class="select-box" @click="openSpuTableSelect">
|
||||
<Icon icon="ep:plus" />
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<!-- 商品选择对话框(表格形式) -->
|
||||
<SpuTableSelect
|
||||
|
|
|
@ -6,7 +6,21 @@ import { onMounted, ref } from 'vue';
|
|||
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import { CHANGE_EVENT } from 'element-plus';
|
||||
import {
|
||||
CHANGE_EVENT,
|
||||
ElButton,
|
||||
ElCheckbox,
|
||||
ElDatePicker,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElImage,
|
||||
ElInput,
|
||||
ElRadio,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElTreeSelect,
|
||||
} from 'element-plus';
|
||||
|
||||
import * as ProductCategoryApi from '#/api/mall/product/category';
|
||||
import * as ProductSpuApi from '#/api/mall/product/spu';
|
||||
|
@ -210,30 +224,30 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:append-to-body="true"
|
||||
title="选择商品"
|
||||
width="70%"
|
||||
>
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
<ElForm
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
<el-input
|
||||
<ElFormItem label="商品名称" prop="name">
|
||||
<ElInput
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品分类" prop="categoryId">
|
||||
<el-tree-select
|
||||
</ElFormItem>
|
||||
<ElFormItem label="商品分类" prop="categoryId">
|
||||
<ElTreeSelect
|
||||
v-model="queryParams.categoryId"
|
||||
:data="categoryTreeList"
|
||||
:props="{
|
||||
|
@ -248,9 +262,9 @@ onMounted(async () => {
|
|||
node-key="id"
|
||||
placeholder="请选择商品分类"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
</ElFormItem>
|
||||
<ElFormItem label="创建时间" prop="createTime">
|
||||
<ElDatePicker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
|
@ -259,67 +273,67 @@ onMounted(async () => {
|
|||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
</ElFormItem>
|
||||
<ElFormItem>
|
||||
<ElButton @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
</ElButton>
|
||||
<ElButton @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="loading" :data="list" show-overflow-tooltip>
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<ElTable v-loading="loading" :data="list" show-overflow-tooltip>
|
||||
<!-- 1. 多选模式(不能使用type="selection",Element会忽略Header插槽) -->
|
||||
<el-table-column width="55" v-if="multiple">
|
||||
<ElTableColumn width="55" v-if="multiple">
|
||||
<template #header>
|
||||
<el-checkbox
|
||||
<ElCheckbox
|
||||
v-model="isCheckAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAll"
|
||||
/>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox
|
||||
<ElCheckbox
|
||||
v-model="checkedStatus[row.id]"
|
||||
@change="(checked: boolean) => handleCheckOne(checked, row, true)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ElTableColumn>
|
||||
<!-- 2. 单选模式 -->
|
||||
<el-table-column label="#" width="55" v-else>
|
||||
<ElTableColumn label="#" width="55" v-else>
|
||||
<template #default="{ row }">
|
||||
<el-radio
|
||||
<ElRadio
|
||||
:value="row.id"
|
||||
v-model="selectedSpuId"
|
||||
@change="handleSingleSelected(row)"
|
||||
>
|
||||
<!-- 空格不能省略,是为了让单选框不显示label,如果不指定label不会有选中的效果 -->
|
||||
|
||||
</el-radio>
|
||||
</ElRadio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
</ElTableColumn>
|
||||
<ElTableColumn
|
||||
key="id"
|
||||
align="center"
|
||||
label="商品编号"
|
||||
prop="id"
|
||||
min-width="60"
|
||||
/>
|
||||
<el-table-column label="商品图" min-width="80">
|
||||
<ElTableColumn label="商品图" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-image
|
||||
<ElImage
|
||||
:src="row.picUrl"
|
||||
class="h-30px w-30px"
|
||||
:preview-src-list="[row.picUrl]"
|
||||
preview-teleported
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品名称" min-width="200" prop="name" />
|
||||
<el-table-column label="商品分类" min-width="100" prop="categoryId">
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="商品名称" min-width="200" prop="name" />
|
||||
<ElTableColumn label="商品分类" min-width="100" prop="categoryId">
|
||||
<template #default="{ row }">
|
||||
<span>{{
|
||||
categoryList?.find(
|
||||
|
@ -327,8 +341,8 @@ onMounted(async () => {
|
|||
)?.name
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
|
@ -338,8 +352,8 @@ onMounted(async () => {
|
|||
/>
|
||||
</ContentWrap>
|
||||
<template #footer v-if="multiple">
|
||||
<el-button type="primary" @click="handleEmitChange">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<ElButton type="primary" @click="handleEmitChange">确 定</ElButton>
|
||||
<ElButton @click="dialogVisible = false">取 消</ElButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useRoute } from 'vue-router';
|
|||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import * as DiyPageApi from '#/api/mall/promotion/diy/page';
|
||||
import { PAGE_LIBS } from '#/components/DiyEditor/util';
|
||||
import { PAGE_LIBS } from '#/components/diy-editor/util';
|
||||
|
||||
/** 装修页面表单 */
|
||||
defineOptions({ name: 'DiyPageDecorate' });
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
<script lang="ts" setup>
|
||||
import * as DiyPageApi from '@/api/mall/promotion/diy/page';
|
||||
import type { MallDiyPageApi } from '#/api/mall/promotion/diy/page';
|
||||
import type { MallDiyTemplateApi } from '#/api/mall/promotion/diy/template';
|
||||
import type { DiyComponentLibrary } from '#/components/diy-editor/util'; // 商城的 DIY 组件,在 DiyEditor 目录下
|
||||
|
||||
import { onMounted, reactive, ref, unref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import * as DiyPageApi from '#/api/mall/promotion/diy/page';
|
||||
// TODO @疯狂:要不要建个 decorate 目录,然后挪进去,改成 index.vue,这样可以更明确看到是个独立界面哈,更好找
|
||||
import * as DiyTemplateApi from '@/api/mall/promotion/diy/template';
|
||||
import { DiyComponentLibrary, PAGE_LIBS } from '@/components/DiyEditor/util'; // 商城的 DIY 组件,在 DiyEditor 目录下
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
||||
import { isEmpty } from '@/utils/is';
|
||||
import { toNumber } from 'lodash-es';
|
||||
import * as DiyTemplateApi from '#/api/mall/promotion/diy/template';
|
||||
import DiyEditor from '#/components/diy-editor/index.vue';
|
||||
import { PAGE_LIBS } from '#/components/diy-editor/util';
|
||||
|
||||
/** 装修模板表单 */
|
||||
defineOptions({ name: 'DiyTemplateDecorate' });
|
||||
|
@ -18,20 +28,18 @@ const templateItems = reactive([
|
|||
{ name: '我的', icon: 'ep:user-filled' },
|
||||
]);
|
||||
|
||||
const message = useMessage(); // 消息弹窗
|
||||
|
||||
const formLoading = ref(false); // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref<DiyTemplateApi.DiyTemplatePropertyVO>();
|
||||
const formData = ref<MallDiyTemplateApi.DiyTemplateProperty>();
|
||||
const formRef = ref(); // 表单 Ref
|
||||
// 当前编辑的属性
|
||||
const currentFormData = ref<
|
||||
DiyPageApi.DiyPageVO | DiyTemplateApi.DiyTemplatePropertyVO
|
||||
MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty
|
||||
>({
|
||||
property: '',
|
||||
} as DiyPageApi.DiyPageVO);
|
||||
} as MallDiyPageApi.DiyPage);
|
||||
// templateItem 对应的缓存
|
||||
const currentFormDataMap = ref<
|
||||
Map<string, DiyPageApi.DiyPageVO | DiyTemplateApi.DiyTemplatePropertyVO>
|
||||
Map<string, MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty>
|
||||
>(new Map());
|
||||
// 商城 H5 预览地址
|
||||
const previewUrl = ref('');
|
||||
|
@ -57,11 +65,11 @@ const libs = ref<DiyComponentLibrary[]>(templateLibs);
|
|||
const handleTemplateItemChange = (val: number) => {
|
||||
// 缓存模版编辑数据
|
||||
currentFormDataMap.value.set(
|
||||
templateItems[selectedTemplateItem.value].name,
|
||||
templateItems[selectedTemplateItem.value]?.name || '',
|
||||
currentFormData.value!,
|
||||
);
|
||||
// 读取模版缓存
|
||||
const data = currentFormDataMap.value.get(templateItems[val].name);
|
||||
const data = currentFormDataMap.value.get(templateItems[val]?.name || '');
|
||||
|
||||
// 切换模版
|
||||
selectedTemplateItem.value = val;
|
||||
|
@ -69,8 +77,8 @@ const handleTemplateItemChange = (val: number) => {
|
|||
if (val === 0) {
|
||||
libs.value = templateLibs;
|
||||
currentFormData.value = (isEmpty(data) ? formData.value : data) as
|
||||
| DiyPageApi.DiyPageVO
|
||||
| DiyTemplateApi.DiyTemplatePropertyVO;
|
||||
| MallDiyPageApi.DiyPage
|
||||
| MallDiyTemplateApi.DiyTemplateProperty;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -79,16 +87,17 @@ const handleTemplateItemChange = (val: number) => {
|
|||
currentFormData.value = (
|
||||
isEmpty(data)
|
||||
? formData.value!.pages.find(
|
||||
(page: DiyPageApi.DiyPageVO) => page.name === templateItems[val].name,
|
||||
(page: MallDiyPageApi.DiyPage) =>
|
||||
page.name === templateItems[val]?.name,
|
||||
)
|
||||
: data
|
||||
) as DiyPageApi.DiyPageVO | DiyTemplateApi.DiyTemplatePropertyVO;
|
||||
) as MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty;
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return;
|
||||
if (!formRef.value) return;
|
||||
// 提交请求
|
||||
formLoading.value = true;
|
||||
try {
|
||||
|
@ -114,7 +123,7 @@ const submitForm = async () => {
|
|||
await DiyPageApi.updateDiyPageProperty(data!);
|
||||
}
|
||||
}
|
||||
message.success('保存成功');
|
||||
ElMessage.success('保存成功');
|
||||
} finally {
|
||||
formLoading.value = false;
|
||||
}
|
||||
|
@ -131,7 +140,7 @@ const resetForm = () => {
|
|||
previewPicUrls: [],
|
||||
property: '',
|
||||
pages: [],
|
||||
} as DiyTemplateApi.DiyTemplatePropertyVO;
|
||||
} as MallDiyTemplateApi.DiyTemplateProperty;
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
|
@ -147,17 +156,17 @@ const storePageIndex = () =>
|
|||
// 2. 恢复
|
||||
const recoverPageIndex = () => {
|
||||
// 恢复重置前的页面,默认是第一个页面
|
||||
const pageIndex = toNumber(sessionStorage.getItem(DIY_PAGE_INDEX_KEY)) || 0;
|
||||
const pageIndex = Number(sessionStorage.getItem(DIY_PAGE_INDEX_KEY)) || 0;
|
||||
// 移除标记
|
||||
sessionStorage.removeItem(DIY_PAGE_INDEX_KEY);
|
||||
|
||||
// 重新初始化数据
|
||||
currentFormData.value = formData.value as
|
||||
| DiyPageApi.DiyPageVO
|
||||
| DiyTemplateApi.DiyTemplatePropertyVO;
|
||||
| MallDiyPageApi.DiyPage
|
||||
| MallDiyTemplateApi.DiyTemplateProperty;
|
||||
currentFormDataMap.value = new Map<
|
||||
string,
|
||||
DiyPageApi.DiyPageVO | DiyTemplateApi.DiyTemplatePropertyVO
|
||||
MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty
|
||||
>();
|
||||
// 切换页面
|
||||
if (pageIndex !== selectedTemplateItem.value) {
|
||||
|
@ -168,15 +177,12 @@ const recoverPageIndex = () => {
|
|||
|
||||
/** 初始化 */
|
||||
const { currentRoute } = useRouter(); // 路由
|
||||
const { delView } = useTagsViewStore(); // 视图操作
|
||||
onMounted(async () => {
|
||||
resetForm();
|
||||
if (!currentRoute.value.params.id) {
|
||||
message.warning('参数错误,页面编号不能为空!');
|
||||
delView(unref(currentRoute));
|
||||
ElMessage.warning('参数错误,页面编号不能为空!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询详情
|
||||
await getPageDetail(currentRoute.value.params.id);
|
||||
// 恢复重置前的页面
|
||||
|
@ -192,7 +198,7 @@ onMounted(async () => {
|
|||
:show-navigation-bar="selectedTemplateItem !== 0"
|
||||
:show-page-config="selectedTemplateItem !== 0"
|
||||
:show-tab-bar="selectedTemplateItem === 0"
|
||||
:title="templateItems[selectedTemplateItem].name"
|
||||
:title="templateItems[selectedTemplateItem]?.name || ''"
|
||||
@reset="handleEditorReset"
|
||||
@save="submitForm"
|
||||
>
|
||||
|
@ -208,7 +214,7 @@ onMounted(async () => {
|
|||
:content="item.name"
|
||||
>
|
||||
<el-radio-button :value="index">
|
||||
<Icon :icon="item.icon" :size="24" />
|
||||
<IconifyIcon :icon="item.icon" :size="24" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
|
|
|
@ -3,6 +3,8 @@ import type { MallPointActivityApi } from '#/api/mall/promotion/point';
|
|||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { ElImage, ElTooltip } from 'element-plus';
|
||||
|
||||
import * as PointActivityApi from '#/api/mall/promotion/point';
|
||||
|
||||
import PointTableSelect from './point-table-select.vue';
|
||||
|
@ -123,23 +125,23 @@ const emitActivityChange = () => {
|
|||
:key="pointActivity.id"
|
||||
class="select-box spu-pic"
|
||||
>
|
||||
<el-tooltip :content="pointActivity.spuName">
|
||||
<ElTooltip :content="pointActivity.spuName">
|
||||
<div class="relative h-full w-full">
|
||||
<el-image :src="pointActivity.picUrl" class="h-full w-full" />
|
||||
<Icon
|
||||
<ElImage :src="pointActivity.picUrl" class="h-full w-full" />
|
||||
<IconifyIcon
|
||||
v-show="!disabled"
|
||||
class="del-icon"
|
||||
icon="ep:circle-close-filled"
|
||||
@click="handleRemoveActivity(index)"
|
||||
/>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<el-tooltip v-if="canAdd" content="选择活动">
|
||||
<ElTooltip v-if="canAdd" content="选择活动">
|
||||
<div class="select-box" @click="openSeckillActivityTableSelect">
|
||||
<Icon icon="ep:plus" />
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<!-- 拼团活动选择对话框(表格形式) -->
|
||||
<PointTableSelect
|
||||
|
|
|
@ -3,6 +3,10 @@ import type { MallSeckillActivityApi } from '#/api/mall/promotion/seckill/seckil
|
|||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { ElImage, ElTooltip } from 'element-plus';
|
||||
|
||||
import * as SeckillActivityApi from '#/api/mall/promotion/seckill/seckillActivity';
|
||||
import SeckillTableSelect from '#/views/mall/promotion/seckill/components/seckill-table-select.vue';
|
||||
|
||||
|
@ -120,23 +124,23 @@ const emitActivityChange = () => {
|
|||
:key="seckillActivity.id"
|
||||
class="select-box spu-pic"
|
||||
>
|
||||
<el-tooltip :content="seckillActivity.name">
|
||||
<ElTooltip :content="seckillActivity.name">
|
||||
<div class="relative h-full w-full">
|
||||
<el-image :src="seckillActivity.picUrl" class="h-full w-full" />
|
||||
<Icon
|
||||
<ElImage :src="seckillActivity.picUrl" class="h-full w-full" />
|
||||
<IconifyIcon
|
||||
v-show="!disabled"
|
||||
class="del-icon"
|
||||
icon="ep:circle-close-filled"
|
||||
@click="handleRemoveActivity(index)"
|
||||
/>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<el-tooltip content="选择活动" v-if="canAdd">
|
||||
<ElTooltip content="选择活动" v-if="canAdd">
|
||||
<div class="select-box" @click="openSeckillActivityTableSelect">
|
||||
<Icon icon="ep:plus" />
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<!-- 拼团活动选择对话框(表格形式) -->
|
||||
<SeckillTableSelect
|
||||
|
|
Loading…
Reference in New Issue