feat: (web-ele)新增颜色输入框组件并优化图片上传组件

- 新增 ColorInput 组件用于颜色选择
- 重构 ImageUpload 组件,增加编辑和删除功能
- 更新 DIY 编辑器相关组件,优化用户体验
- 添加商城 H5 预览地址配置
- 优化导航栏单元格属性配置
This commit is contained in:
lrl
2025-08-05 15:32:12 +08:00
parent 1f155fa7c5
commit e7fc44715b
64 changed files with 2248 additions and 1165 deletions

View File

@@ -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' });

View File

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

View File

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

View File

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