diff --git a/apps/web-antd/src/api/iot/ota/task/record/index.ts b/apps/web-antd/src/api/iot/ota/task/record/index.ts index 8d41d1b74..2d66a422f 100644 --- a/apps/web-antd/src/api/iot/ota/task/record/index.ts +++ b/apps/web-antd/src/api/iot/ota/task/record/index.ts @@ -22,6 +22,7 @@ export namespace IoTOtaTaskRecordApi { } } +// TODO @AI:这里应该拿到 IoTOtaTaskRecordApi 里 /** IoT OTA 升级任务记录 */ export interface OtaTaskRecord { id?: number; diff --git a/apps/web-antd/src/api/mall/promotion/reward/rewardActivity.ts b/apps/web-antd/src/api/mall/promotion/reward/rewardActivity.ts index 2faf443b8..e59cad300 100644 --- a/apps/web-antd/src/api/mall/promotion/reward/rewardActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/reward/rewardActivity.ts @@ -18,6 +18,7 @@ export namespace MallRewardActivityApi { export interface RewardActivity { id?: number; // 活动编号 name?: string; // 活动名称 + status?: number; // 活动状态 startTime?: Date; // 开始时间 endTime?: Date; // 结束时间 startAndEndTime?: Date[]; // 开始和结束时间(仅前端使用) diff --git a/apps/web-antd/src/components/form-create/rules/use-dict-select.ts b/apps/web-antd/src/components/form-create/rules/use-dict-select.ts index 0054cd41e..c486b6716 100644 --- a/apps/web-antd/src/components/form-create/rules/use-dict-select.ts +++ b/apps/web-antd/src/components/form-create/rules/use-dict-select.ts @@ -1,8 +1,10 @@ +import type { SystemDictTypeApi } from '#/api/system/dict/type'; + import { onMounted, ref } from 'vue'; import { buildUUID, cloneDeep } from '@vben/utils'; -import * as DictDataApi from '#/api/system/dict/type'; +import { getSimpleDictTypeList } from '#/api/system/dict/type'; import { localeProps, makeRequiredRule, @@ -18,12 +20,12 @@ export function useDictSelectRule() { const rules = cloneDeep(selectRule); const dictOptions = ref<{ label: string; value: string }[]>([]); // 字典类型下拉数据 onMounted(async () => { - const data = await DictDataApi.getSimpleDictTypeList(); + const data = await getSimpleDictTypeList(); if (!data || data.length === 0) { return; } dictOptions.value = - data?.map((item: DictDataApi.SystemDictTypeApi.DictType) => ({ + data?.map((item: SystemDictTypeApi.DictType) => ({ label: item.name, value: item.type, })) ?? []; diff --git a/apps/web-antd/src/views/bpm/model/form/modules/extra-setting.vue b/apps/web-antd/src/views/bpm/model/form/modules/extra-setting.vue index c07d73ff0..2a4361cba 100644 --- a/apps/web-antd/src/views/bpm/model/form/modules/extra-setting.vue +++ b/apps/web-antd/src/views/bpm/model/form/modules/extra-setting.vue @@ -26,7 +26,7 @@ import { } from 'ant-design-vue'; import dayjs from 'dayjs'; -import * as FormApi from '#/api/bpm/form'; +import { getForm } from '#/api/bpm/form'; import { HttpRequestSetting, parseFormFields, @@ -229,7 +229,7 @@ watch( () => modelData.value.formId, async (newFormId) => { if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) { - const data = await FormApi.getForm(newFormId); + const data = await getForm(newFormId); const result: Array<{ field: string; title: string }> = []; if (data.fields) { unParsedFormFields.value = data.fields; diff --git a/apps/web-antd/src/views/bpm/processInstance/create/index.vue b/apps/web-antd/src/views/bpm/processInstance/create/index.vue index c4cefbbfc..691cf0bbb 100644 --- a/apps/web-antd/src/views/bpm/processInstance/create/index.vue +++ b/apps/web-antd/src/views/bpm/processInstance/create/index.vue @@ -284,6 +284,17 @@ onMounted(() => { diff --git a/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue b/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue index c7536dc15..e07a58dfc 100644 --- a/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue +++ b/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue @@ -4,6 +4,7 @@ import type { FormInstance } from 'ant-design-vue'; import type { Rule } from 'ant-design-vue/es/form'; import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; +import type { SystemUserApi } from '#/api/system/user'; import { computed, nextTick, reactive, ref, watch } from 'vue'; import { useRouter } from 'vue-router'; @@ -42,8 +43,17 @@ import { cancelProcessInstanceByStartUser, getNextApprovalNodes, } from '#/api/bpm/processInstance'; -import * as TaskApi from '#/api/bpm/task'; -import * as UserApi from '#/api/system/user'; +import { + approveTask, + copyTask, + delegateTask, + getTaskListByReturn, + rejectTask, + returnTask, + signCreateTask, + signDeleteTask, + transferTask, +} from '#/api/bpm/task'; import { setConfAndFields2 } from '#/components/form-create'; import { $t } from '#/locales'; @@ -57,7 +67,7 @@ const props = defineProps<{ normalFormApi: any; // 流程表单 formCreate Api processDefinition: any; // 流程定义信息 processInstance: any; // 流程实例信息 - userOptions: UserApi.SystemUserApi.User[]; + userOptions: SystemUserApi.User[]; writableFields: string[]; // 流程表单可以编辑的字段 }>(); // 当前登录的编号 const emit = defineEmits(['success']); @@ -249,7 +259,7 @@ async function openPopover(type: string) { } if (type === 'return') { // 获取退回节点 - returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id); + returnList.value = await getTaskListByReturn(runningTask.value.id); if (returnList.value.length === 0) { message.warning('当前没有可退回的节点'); return; @@ -375,7 +385,7 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) { await formCreateApi.validate(); data.variables = approveForm.value.value; } - await TaskApi.approveTask(data); + await approveTask(data); popOverVisible.value.approve = false; nextAssigneesActivityNode.value = []; // 清理 Timeline 组件中的自定义审批人数据 @@ -389,7 +399,7 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) { id: runningTask.value.id, reason: rejectReasonForm.reason, }; - await TaskApi.rejectTask(data); + await rejectTask(data); popOverVisible.value.reject = false; message.success('审批不通过成功'); } @@ -415,7 +425,7 @@ async function handleCopy() { reason: copyForm.copyReason, copyUserIds: copyForm.copyUserIds, }; - await TaskApi.copyTask(data); + await copyTask(data); copyFormRef.value.resetFields(); popOverVisible.value.copy = false; message.success($t('ui.actionMessage.operationSuccess')); @@ -439,7 +449,7 @@ async function handleTransfer() { reason: transferForm.reason, assigneeUserId: transferForm.assigneeUserId, }; - await TaskApi.transferTask(data); + await transferTask(data); transferFormRef.value.resetFields(); popOverVisible.value.transfer = false; message.success($t('ui.actionMessage.operationSuccess')); @@ -463,7 +473,7 @@ async function handleDelegate() { reason: delegateForm.reason, delegateUserId: delegateForm.delegateUserId, }; - await TaskApi.delegateTask(data); + await delegateTask(data); popOverVisible.value.delegate = false; delegateFormRef.value.resetFields(); message.success($t('ui.actionMessage.operationSuccess')); @@ -488,7 +498,7 @@ async function handlerAddSign(type: string) { reason: addSignForm.reason, userIds: addSignForm.addSignUserIds, }; - await TaskApi.signCreateTask(data); + await signCreateTask(data); message.success($t('ui.actionMessage.operationSuccess')); addSignFormRef.value.resetFields(); popOverVisible.value.addSign = false; @@ -512,7 +522,7 @@ async function handleReturn() { reason: returnForm.returnReason, targetTaskDefinitionKey: returnForm.targetTaskDefinitionKey, }; - await TaskApi.returnTask(data); + await returnTask(data); popOverVisible.value.return = false; returnFormRef.value.resetFields(); message.success($t('ui.actionMessage.operationSuccess')); @@ -573,7 +583,7 @@ async function handlerDeleteSign() { id: deleteSignForm.deleteSignTaskId, reason: deleteSignForm.reason, }; - await TaskApi.signDeleteTask(data); + await signDeleteTask(data); message.success('减签成功'); deleteSignFormRef.value.resetFields(); popOverVisible.value.deleteSign = false; diff --git a/apps/web-antd/src/views/crm/backlog/index.vue b/apps/web-antd/src/views/crm/backlog/index.vue index 7c51c3890..b461f18a7 100644 --- a/apps/web-antd/src/views/crm/backlog/index.vue +++ b/apps/web-antd/src/views/crm/backlog/index.vue @@ -5,11 +5,18 @@ import { Page } from '@vben/common-ui'; import { Badge, Card, List } from 'ant-design-vue'; -import * as ClueApi from '#/api/crm/clue'; -import * as ContractApi from '#/api/crm/contract'; -import * as CustomerApi from '#/api/crm/customer'; -import * as ReceivableApi from '#/api/crm/receivable'; -import * as ReceivablePlanApi from '#/api/crm/receivable/plan'; +import { getFollowClueCount } from '#/api/crm/clue'; +import { + getAuditContractCount, + getRemindContractCount, +} from '#/api/crm/contract'; +import { + getFollowCustomerCount, + getPutPoolRemindCustomerCount, + getTodayContactCustomerCount, +} from '#/api/crm/customer'; +import { getAuditReceivableCount } from '#/api/crm/receivable'; +import { getReceivablePlanRemindCount } from '#/api/crm/receivable/plan'; import { useLeftSides } from './data'; import ClueFollowList from './modules/clue-follow-list.vue'; @@ -64,17 +71,14 @@ function sideClick(item: { menu: string }) { /** 获取数量 */ async function getCount() { - customerTodayContactCount.value = - await CustomerApi.getTodayContactCustomerCount(); - customerPutPoolRemindCount.value = - await CustomerApi.getPutPoolRemindCustomerCount(); - customerFollowCount.value = await CustomerApi.getFollowCustomerCount(); - clueFollowCount.value = await ClueApi.getFollowClueCount(); - contractAuditCount.value = await ContractApi.getAuditContractCount(); - contractRemindCount.value = await ContractApi.getRemindContractCount(); - receivableAuditCount.value = await ReceivableApi.getAuditReceivableCount(); - receivablePlanRemindCount.value = - await ReceivablePlanApi.getReceivablePlanRemindCount(); + customerTodayContactCount.value = await getTodayContactCustomerCount(); + customerPutPoolRemindCount.value = await getPutPoolRemindCustomerCount(); + customerFollowCount.value = await getFollowCustomerCount(); + clueFollowCount.value = await getFollowClueCount(); + contractAuditCount.value = await getAuditContractCount(); + contractRemindCount.value = await getRemindContractCount(); + receivableAuditCount.value = await getAuditReceivableCount(); + receivablePlanRemindCount.value = await getReceivablePlanRemindCount(); } /** 激活时 */ diff --git a/apps/web-antd/src/views/infra/apiAccessLog/data.ts b/apps/web-antd/src/views/infra/apiAccessLog/data.ts index 73662e3a3..83b2977c0 100644 --- a/apps/web-antd/src/views/infra/apiAccessLog/data.ts +++ b/apps/web-antd/src/views/infra/apiAccessLog/data.ts @@ -245,7 +245,7 @@ export function useDetailSchema(): DescriptionItemSchema[] { render: (val, data) => { if (val === 0) { return '正常'; - } else if (val > 0 && data?.resultCode > 0) { + } else if (val > 0 && data?.resultMsg) { return `失败 | ${val} | ${data.resultMsg}`; } return ''; diff --git a/apps/web-antd/src/views/infra/apiAccessLog/modules/detail.vue b/apps/web-antd/src/views/infra/apiAccessLog/modules/detail.vue index b3de99cc7..badc9376c 100644 --- a/apps/web-antd/src/views/infra/apiAccessLog/modules/detail.vue +++ b/apps/web-antd/src/views/infra/apiAccessLog/modules/detail.vue @@ -14,7 +14,6 @@ const formData = ref(); const [Descriptions] = useDescription({ bordered: true, column: 1, - class: 'mx-4', schema: useDetailSchema(), }); diff --git a/apps/web-antd/src/views/infra/apiErrorLog/modules/detail.vue b/apps/web-antd/src/views/infra/apiErrorLog/modules/detail.vue index 9c4e44c9f..da52c17fa 100644 --- a/apps/web-antd/src/views/infra/apiErrorLog/modules/detail.vue +++ b/apps/web-antd/src/views/infra/apiErrorLog/modules/detail.vue @@ -14,7 +14,6 @@ const formData = ref(); const [Descriptions] = useDescription({ bordered: true, column: 1, - class: 'mx-4', schema: useDetailSchema(), }); diff --git a/apps/web-antd/src/views/infra/job/logger/modules/detail.vue b/apps/web-antd/src/views/infra/job/logger/modules/detail.vue index 18c868de4..ceb1b5bf1 100644 --- a/apps/web-antd/src/views/infra/job/logger/modules/detail.vue +++ b/apps/web-antd/src/views/infra/job/logger/modules/detail.vue @@ -15,7 +15,6 @@ const formData = ref(); const [Descriptions] = useDescription({ bordered: true, column: 1, - class: 'mx-4', schema: useDetailSchema(), }); diff --git a/apps/web-antd/src/views/infra/job/modules/detail.vue b/apps/web-antd/src/views/infra/job/modules/detail.vue index 145a19dab..f23149e2d 100644 --- a/apps/web-antd/src/views/infra/job/modules/detail.vue +++ b/apps/web-antd/src/views/infra/job/modules/detail.vue @@ -16,7 +16,6 @@ const nextTimes = ref([]); // 下一次执行时间 const [Descriptions] = useDescription({ bordered: true, column: 1, - class: 'mx-4', schema: useDetailSchema(), }); diff --git a/apps/web-antd/src/views/iot/device/group/data.ts b/apps/web-antd/src/views/iot/device/group/data.ts index 0bc313e52..01b4cc3f9 100644 --- a/apps/web-antd/src/views/iot/device/group/data.ts +++ b/apps/web-antd/src/views/iot/device/group/data.ts @@ -1,10 +1,11 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import { DICT_TYPE } from '@vben/constants'; +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; import { z } from '#/adapter/form'; -import { getSimpleDeviceGroupList } from '#/api/iot/device/group'; +import { getRangePickerDefaultProps } from '#/utils'; /** 新增/修改设备分组的表单 */ export function useFormSchema(): VbenFormSchema[] { @@ -30,16 +31,15 @@ export function useFormSchema(): VbenFormSchema[] { .max(64, '分组名称长度不能超过 64 个字符'), }, { - fieldName: 'parentId', - label: '父级分组', - component: 'ApiTreeSelect', + fieldName: 'status', + label: '分组状态', + component: 'RadioGroup', componentProps: { - api: getSimpleDeviceGroupList, - labelField: 'name', - valueField: 'id', - placeholder: '请选择父级分组', - allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', }, + rules: z.number().default(CommonStatusEnum.ENABLE), }, { fieldName: 'description', @@ -65,6 +65,15 @@ export function useGridFormSchema(): VbenFormSchema[] { allowClear: true, }, }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, ]; } @@ -72,14 +81,13 @@ export function useGridFormSchema(): VbenFormSchema[] { export function useGridColumns(): VxeTableGridOptions['columns'] { return [ { - field: 'name', - title: '分组名称', - minWidth: 200, - treeNode: true, + field: 'id', + title: 'ID', + minWidth: 100, }, { - field: 'description', - title: '分组描述', + field: 'name', + title: '分组名称', minWidth: 200, }, { @@ -92,9 +100,9 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, }, { - field: 'deviceCount', - title: '设备数量', - minWidth: 100, + field: 'description', + title: '分组描述', + minWidth: 200, }, { field: 'createTime', @@ -102,6 +110,11 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { minWidth: 180, formatter: 'formatDateTime', }, + { + field: 'deviceCount', + title: '设备数量', + minWidth: 100, + }, { title: '操作', width: 200, diff --git a/apps/web-antd/src/views/iot/device/group/index.vue b/apps/web-antd/src/views/iot/device/group/index.vue index f6a0992f8..ebf33e9be 100644 --- a/apps/web-antd/src/views/iot/device/group/index.vue +++ b/apps/web-antd/src/views/iot/device/group/index.vue @@ -3,7 +3,6 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { IotDeviceGroupApi } from '#/api/iot/device/group'; import { Page, useVbenModal } from '@vben/common-ui'; -import { handleTree } from '@vben/utils'; import { message } from 'ant-design-vue'; @@ -62,24 +61,14 @@ const [Grid, gridApi] = useVbenVxeGrid({ columns: useGridColumns(), height: 'auto', keepSource: true, - treeConfig: { - transform: true, - rowField: 'id', - parentField: 'parentId', - }, proxyConfig: { ajax: { query: async ({ page }, formValues) => { - const data = await getDeviceGroupPage({ + return await getDeviceGroupPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues, }); - // 转换为树形结构 - return { - ...data, - list: handleTree(data.list, 'id', 'parentId'), - }; }, }, }, diff --git a/apps/web-antd/src/views/iot/device/group/modules/device-group-form.vue b/apps/web-antd/src/views/iot/device/group/modules/device-group-form.vue index fe010aba1..28168c0b4 100644 --- a/apps/web-antd/src/views/iot/device/group/modules/device-group-form.vue +++ b/apps/web-antd/src/views/iot/device/group/modules/device-group-form.vue @@ -39,6 +39,7 @@ const [Form, formApi] = useVbenForm({ }, schema: useFormSchema(), showCollapseButton: false, + showDefaultActions: false, }); const [Modal, modalApi] = useVbenModal({ @@ -70,9 +71,13 @@ const [Modal, modalApi] = useVbenModal({ async onOpenChange(isOpen: boolean) { if (!isOpen) { formData.value = undefined; + formApi.resetForm(); return; } + // 重置表单 + await formApi.resetForm(); + const data = modalApi.getData(); // 如果没有数据或没有 id,表示是新增 if (!data || !data.id) { diff --git a/apps/web-antd/src/views/iot/ota/modules/detail/index.vue b/apps/web-antd/src/views/iot/ota/modules/detail/index.vue index a19a03117..ca6613146 100644 --- a/apps/web-antd/src/views/iot/ota/modules/detail/index.vue +++ b/apps/web-antd/src/views/iot/ota/modules/detail/index.vue @@ -8,8 +8,8 @@ import { formatDate } from '@vben/utils'; import { Card, Col, Descriptions, Row } from 'ant-design-vue'; -import * as IoTOtaFirmwareApi from '#/api/iot/ota/firmware'; -import * as IoTOtaTaskRecordApi from '#/api/iot/ota/task/record'; +import { getOtaFirmware } from '#/api/iot/ota/firmware'; +import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record'; import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants'; import OtaTaskList from '../task/OtaTaskList.vue'; @@ -30,7 +30,7 @@ const firmwareStatistics = ref>({}); async function getFirmwareInfo() { firmwareLoading.value = true; try { - firmware.value = await IoTOtaFirmwareApi.getOtaFirmware(firmwareId.value); + firmware.value = await getOtaFirmware(firmwareId.value); } finally { firmwareLoading.value = false; } @@ -40,10 +40,9 @@ async function getFirmwareInfo() { async function getStatistics() { firmwareStatisticsLoading.value = true; try { - firmwareStatistics.value = - await IoTOtaTaskRecordApi.getOtaTaskRecordStatusStatistics( - firmwareId.value, - ); + firmwareStatistics.value = await getOtaTaskRecordStatusStatistics( + firmwareId.value, + ); } finally { firmwareStatisticsLoading.value = false; } diff --git a/apps/web-antd/src/views/iot/ota/modules/firmware-detail/index.vue b/apps/web-antd/src/views/iot/ota/modules/firmware-detail/index.vue index 8bd43420c..04533924d 100644 --- a/apps/web-antd/src/views/iot/ota/modules/firmware-detail/index.vue +++ b/apps/web-antd/src/views/iot/ota/modules/firmware-detail/index.vue @@ -8,8 +8,8 @@ import { formatDate } from '@vben/utils'; import { Card, Col, Descriptions, Row } from 'ant-design-vue'; -import * as IoTOtaFirmwareApi from '#/api/iot/ota/firmware'; -import * as IoTOtaTaskRecordApi from '#/api/iot/ota/task/record'; +import { getOtaFirmware } from '#/api/iot/ota/firmware'; +import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record'; import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants'; import OtaTaskList from '../task/OtaTaskList.vue'; @@ -30,7 +30,7 @@ const firmwareStatistics = ref>({}); async function getFirmwareInfo() { firmwareLoading.value = true; try { - firmware.value = await IoTOtaFirmwareApi.getOtaFirmware(firmwareId.value); + firmware.value = await getOtaFirmware(firmwareId.value); } finally { firmwareLoading.value = false; } @@ -40,10 +40,9 @@ async function getFirmwareInfo() { async function getStatistics() { firmwareStatisticsLoading.value = true; try { - firmwareStatistics.value = - await IoTOtaTaskRecordApi.getOtaTaskRecordStatusStatistics( - firmwareId.value, - ); + firmwareStatistics.value = await getOtaTaskRecordStatusStatistics( + firmwareId.value, + ); } finally { firmwareStatisticsLoading.value = false; } diff --git a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskDetail.vue b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskDetail.vue index 8a19d63d7..8768c2bf6 100644 --- a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskDetail.vue +++ b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskDetail.vue @@ -21,8 +21,12 @@ import { Tag, } from 'ant-design-vue'; -import * as IoTOtaTaskApi from '#/api/iot/ota/task'; -import * as IoTOtaTaskRecordApi from '#/api/iot/ota/task/record'; +import { getOtaTask } from '#/api/iot/ota/task'; +import { + cancelOtaTaskRecord, + getOtaTaskRecordPage, + getOtaTaskRecordStatusStatistics, +} from '#/api/iot/ota/task/record'; import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants'; /** OTA 任务详情组件 */ @@ -119,7 +123,7 @@ async function getTaskInfo() { } taskLoading.value = true; try { - task.value = await IoTOtaTaskApi.getOtaTask(taskId.value); + task.value = await getOtaTask(taskId.value); } finally { taskLoading.value = false; } @@ -132,11 +136,10 @@ async function getStatistics() { } taskStatisticsLoading.value = true; try { - taskStatistics.value = - await IoTOtaTaskRecordApi.getOtaTaskRecordStatusStatistics( - undefined, - taskId.value, - ); + taskStatistics.value = await getOtaTaskRecordStatusStatistics( + undefined, + taskId.value, + ); } finally { taskStatisticsLoading.value = false; } @@ -150,7 +153,7 @@ async function getRecordList() { recordLoading.value = true; try { queryParams.taskId = taskId.value; - const data = await IoTOtaTaskRecordApi.getOtaTaskRecordPage(queryParams); + const data = await getOtaTaskRecordPage(queryParams); recordList.value = data.list || []; recordTotal.value = data.total || 0; } finally { @@ -181,7 +184,7 @@ async function handleCancelUpgrade(record: OtaTaskRecord) { content: '确认要取消该设备的升级任务吗?', async onOk() { try { - await IoTOtaTaskRecordApi.cancelOtaTaskRecord(record.id!); + await cancelOtaTaskRecord(record.id!); message.success('取消成功'); await getRecordList(); await getStatistics(); diff --git a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskForm.vue b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskForm.vue index 0971ac2ef..bb3818cc5 100644 --- a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskForm.vue +++ b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskForm.vue @@ -8,8 +8,8 @@ import { useVbenModal } from '@vben/common-ui'; import { Form, Input, message, Select, Spin } from 'ant-design-vue'; -import * as DeviceApi from '#/api/iot/device/device'; -import * as IoTOtaTaskApi from '#/api/iot/ota/task'; +import { getDeviceListByProductId } from '#/api/iot/device/device'; +import { createOtaTask } from '#/api/iot/ota/task'; import { IoTOtaTaskDeviceScopeEnum } from '#/views/iot/utils/constants'; /** IoT OTA 升级任务表单 */ @@ -82,7 +82,7 @@ const [Modal, modalApi] = useVbenModal({ try { await formRef.value.validate(); modalApi.lock(); - await IoTOtaTaskApi.createOtaTask(formData.value); + await createOtaTask(formData.value); message.success('创建成功'); await modalApi.close(); emit('success'); @@ -98,8 +98,7 @@ const [Modal, modalApi] = useVbenModal({ // 加载设备列表 formLoading.value = true; try { - devices.value = - (await DeviceApi.getDeviceListByProductId(props.productId)) || []; + devices.value = (await getDeviceListByProductId(props.productId)) || []; } finally { formLoading.value = false; } diff --git a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskList.vue b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskList.vue index 130f344fc..14f8cfa98 100644 --- a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskList.vue +++ b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskList.vue @@ -19,7 +19,7 @@ import { Tag, } from 'ant-design-vue'; -import * as IoTOtaTaskApi from '#/api/iot/ota/task'; +import { getOtaTaskPage } from '#/api/iot/ota/task'; import { IoTOtaTaskStatusEnum } from '#/views/iot/utils/constants'; import OtaTaskDetail from './OtaTaskDetail.vue'; @@ -52,7 +52,7 @@ const taskDetailRef = ref(); // 任务详情引用 async function getTaskList() { taskLoading.value = true; try { - const data = await IoTOtaTaskApi.getOtaTaskPage(queryParams); + const data = await getOtaTaskPage(queryParams); taskList.value = data.list; taskTotal.value = data.total; } finally { diff --git a/apps/web-antd/src/views/iot/product/category/data.ts b/apps/web-antd/src/views/iot/product/category/data.ts index 2604b0406..4857bc7d2 100644 --- a/apps/web-antd/src/views/iot/product/category/data.ts +++ b/apps/web-antd/src/views/iot/product/category/data.ts @@ -1,10 +1,11 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import { DICT_TYPE } from '@vben/constants'; +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; import { z } from '#/adapter/form'; -import { getSimpleProductCategoryList } from '#/api/iot/product/category'; +import { getRangePickerDefaultProps } from '#/utils'; /** 新增/修改产品分类的表单 */ export function useFormSchema(): VbenFormSchema[] { @@ -19,51 +20,37 @@ export function useFormSchema(): VbenFormSchema[] { }, { fieldName: 'name', - label: '分类名称', + label: '分类名字', component: 'Input', componentProps: { - placeholder: '请输入分类名称', + placeholder: '请输入分类名字', }, rules: z .string() - .min(1, '分类名称不能为空') - .max(64, '分类名称长度不能超过 64 个字符'), - }, - { - fieldName: 'parentId', - label: '父级分类', - component: 'ApiTreeSelect', - componentProps: { - api: getSimpleProductCategoryList, - labelField: 'name', - valueField: 'id', - placeholder: '请选择父级分类', - allowClear: true, - }, + .min(1, '分类名字不能为空') + .max(64, '分类名字长度不能超过 64 个字符'), }, { fieldName: 'sort', - label: '排序', + label: '分类排序', component: 'InputNumber', componentProps: { - placeholder: '请输入排序', + placeholder: '请输入分类排序', class: 'w-full', min: 0, }, - rules: 'required', + rules: z.number().min(0, '分类排序不能为空'), }, { fieldName: 'status', - label: '状态', + label: '分类状态', component: 'RadioGroup', - defaultValue: 1, componentProps: { - options: [ - { label: '开启', value: 1 }, - { label: '关闭', value: 0 }, - ], + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', }, - rules: 'required', + rules: z.number().default(CommonStatusEnum.ENABLE), }, { fieldName: 'description', @@ -82,10 +69,10 @@ export function useGridFormSchema(): VbenFormSchema[] { return [ { fieldName: 'name', - label: '分类名称', + label: '分类名字', component: 'Input', componentProps: { - placeholder: '请输入分类名称', + placeholder: '请输入分类名字', allowClear: true, }, }, @@ -94,9 +81,8 @@ export function useGridFormSchema(): VbenFormSchema[] { label: '创建时间', component: 'RangePicker', componentProps: { - placeholder: ['开始日期', '结束日期'], + ...getRangePickerDefaultProps(), allowClear: true, - class: 'w-full', }, }, ]; @@ -114,7 +100,6 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { field: 'name', title: '名字', minWidth: 200, - treeNode: true, }, { field: 'sort', diff --git a/apps/web-antd/src/views/iot/product/category/index.vue b/apps/web-antd/src/views/iot/product/category/index.vue index 7344bd5de..476c5991e 100644 --- a/apps/web-antd/src/views/iot/product/category/index.vue +++ b/apps/web-antd/src/views/iot/product/category/index.vue @@ -3,7 +3,6 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { IotProductCategoryApi } from '#/api/iot/product/category'; import { Page, useVbenModal } from '@vben/common-ui'; -import { handleTree } from '@vben/utils'; import { message } from 'ant-design-vue'; @@ -70,16 +69,11 @@ const [Grid, gridApi] = useVbenVxeGrid({ proxyConfig: { ajax: { query: async ({ page }, formValues) => { - const data = await getProductCategoryPage({ + return await getProductCategoryPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues, }); - // 转换为树形结构 - return { - ...data, - list: handleTree(data.list, 'id', 'parentId'), - }; }, }, }, @@ -91,16 +85,6 @@ const [Grid, gridApi] = useVbenVxeGrid({ refresh: true, search: true, }, - treeConfig: { - parentField: 'parentId', - rowField: 'id', - transform: true, - expandAll: true, - reserve: true, - trigger: 'default', - iconOpen: '', - iconClose: '', - }, } as VxeTableGridOptions, }); diff --git a/apps/web-antd/src/views/iot/product/category/modules/ProductCategoryForm.vue b/apps/web-antd/src/views/iot/product/category/modules/ProductCategoryForm.vue index b508b481e..d80ee2602 100644 --- a/apps/web-antd/src/views/iot/product/category/modules/ProductCategoryForm.vue +++ b/apps/web-antd/src/views/iot/product/category/modules/ProductCategoryForm.vue @@ -63,13 +63,17 @@ const [Modal, modalApi] = useVbenModal({ async onOpenChange(isOpen: boolean) { if (!isOpen) { formData.value = undefined; + formApi.resetForm(); return; } - // 加载数据 - let data = modalApi.getData< - IotProductCategoryApi.ProductCategory & { parentId?: number } - >(); - if (!data) { + + // 重置表单 + await formApi.resetForm(); + + const data = modalApi.getData(); + // 如果没有数据或没有 id,表示是新增 + if (!data || !data.id) { + formData.value = undefined; // 新增模式:设置默认值 await formApi.setValues({ sort: 0, @@ -77,23 +81,12 @@ const [Modal, modalApi] = useVbenModal({ }); return; } + + // 编辑模式:加载数据 modalApi.lock(); try { - if (data.id) { - // 编辑模式:加载完整数据 - data = await getProductCategory(data.id); - } else if (data.parentId) { - // 新增下级分类:设置父级ID - await formApi.setValues({ - parentId: data.parentId, - sort: 0, - status: 1, - }); - return; - } - // 设置到 values - formData.value = data; - await formApi.setValues(data); + formData.value = await getProductCategory(data.id); + await formApi.setValues(formData.value); } finally { modalApi.unlock(); } diff --git a/apps/web-antd/src/views/mall/home/modules/member-statistics-card.vue b/apps/web-antd/src/views/mall/home/modules/member-statistics-card.vue index 3b7e0db48..770249117 100644 --- a/apps/web-antd/src/views/mall/home/modules/member-statistics-card.vue +++ b/apps/web-antd/src/views/mall/home/modules/member-statistics-card.vue @@ -10,7 +10,7 @@ import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import { Card, Radio, RadioGroup, Spin } from 'ant-design-vue'; import dayjs from 'dayjs'; -import * as MemberStatisticsApi from '#/api/mall/statistics/member'; +import { getMemberRegisterCountList } from '#/api/mall/statistics/member'; import { getMemberStatisticsChartOptions, @@ -71,13 +71,13 @@ async function handleTimeRangeTypeChange() { } } // 发送时间范围选中事件 - await getMemberRegisterCountList(beginTime, endTime); + await loadMemberRegisterCountList(beginTime, endTime); } -async function getMemberRegisterCountList(beginTime: Dayjs, endTime: Dayjs) { +async function loadMemberRegisterCountList(beginTime: Dayjs, endTime: Dayjs) { loading.value = true; try { - const list = await MemberStatisticsApi.getMemberRegisterCountList( + const list = await getMemberRegisterCountList( beginTime.toDate(), endTime.toDate(), ); diff --git a/apps/web-antd/src/views/mall/home/modules/operation-data-card.vue b/apps/web-antd/src/views/mall/home/modules/operation-data-card.vue index 22d9c855e..a0f46c2aa 100644 --- a/apps/web-antd/src/views/mall/home/modules/operation-data-card.vue +++ b/apps/web-antd/src/views/mall/home/modules/operation-data-card.vue @@ -6,9 +6,9 @@ import { CountTo } from '@vben/common-ui'; import { Card } from 'ant-design-vue'; -import * as ProductSpuApi from '#/api/mall/product/spu'; -import * as PayStatisticsApi from '#/api/mall/statistics/pay'; -import * as TradeStatisticsApi from '#/api/mall/statistics/trade'; +import { getTabsCount } from '#/api/mall/product/spu'; +import { getWalletRechargePrice } from '#/api/mall/statistics/pay'; +import { getOrderCount } from '#/api/mall/statistics/trade'; /** 运营数据卡片 */ defineOptions({ name: 'OperationDataCard' }); @@ -51,8 +51,8 @@ const data = reactive({ }); /** 查询订单数据 */ -async function getOrderData() { - const orderCount = await TradeStatisticsApi.getOrderCount(); +async function loadOrderData() { + const orderCount = await getOrderCount(); if (orderCount.undelivered) { data.orderUndelivered.value = orderCount.undelivered; } @@ -68,16 +68,16 @@ async function getOrderData() { } /** 查询商品数据 */ -async function getProductData() { - const productCount = await ProductSpuApi.getTabsCount(); +async function loadProductData() { + const productCount = await getTabsCount(); data.productForSale.value = productCount['0'] || 0; data.productInWarehouse.value = productCount['1'] || 0; data.productAlertStock.value = productCount['3'] || 0; } /** 查询钱包充值数据 */ -async function getWalletRechargeData() { - const paySummary = await PayStatisticsApi.getWalletRechargePrice(); +async function loadWalletRechargeData() { + const paySummary = await getWalletRechargePrice(); data.rechargePrice.value = paySummary.rechargePrice; } @@ -88,16 +88,16 @@ function handleClick(routerName: string) { /** 激活时 */ onActivated(() => { - getOrderData(); - getProductData(); - getWalletRechargeData(); + loadOrderData(); + loadProductData(); + loadWalletRechargeData(); }); /** 初始化 */ onMounted(() => { - getOrderData(); - getProductData(); - getWalletRechargeData(); + loadOrderData(); + loadProductData(); + loadWalletRechargeData(); }); diff --git a/apps/web-antd/src/views/mall/home/modules/trade-trend-card.vue b/apps/web-antd/src/views/mall/home/modules/trade-trend-card.vue index 1d5a1cf6a..2c9d72583 100644 --- a/apps/web-antd/src/views/mall/home/modules/trade-trend-card.vue +++ b/apps/web-antd/src/views/mall/home/modules/trade-trend-card.vue @@ -11,7 +11,7 @@ import { fenToYuan } from '@vben/utils'; import { Card, Radio, RadioGroup, Spin } from 'ant-design-vue'; import dayjs from 'dayjs'; -import * as TradeStatisticsApi from '#/api/mall/statistics/trade'; +import { getOrderCountTrendComparison } from '#/api/mall/statistics/trade'; import { getTradeTrendChartOptions, @@ -76,15 +76,15 @@ async function handleTimeRangeTypeChange() { } } // 发送时间范围选中事件 - await getOrderCountTrendComparison(beginTime, endTime); + await loadOrderCountTrendComparison(beginTime, endTime); } /** 查询订单数量趋势对照数据 */ -async function getOrderCountTrendComparison(beginTime: Dayjs, endTime: Dayjs) { +async function loadOrderCountTrendComparison(beginTime: Dayjs, endTime: Dayjs) { loading.value = true; try { // 1. 查询数据 - const list = await TradeStatisticsApi.getOrderCountTrendComparison( + const list = await getOrderCountTrendComparison( timeRangeType.value, beginTime.toDate(), endTime.toDate(), diff --git a/apps/web-antd/src/views/mall/product/category/components/product-category-select.vue b/apps/web-antd/src/views/mall/product/category/components/product-category-select.vue index b54c6f0d2..dedb2d2fa 100644 --- a/apps/web-antd/src/views/mall/product/category/components/product-category-select.vue +++ b/apps/web-antd/src/views/mall/product/category/components/product-category-select.vue @@ -3,7 +3,7 @@ import { computed, onMounted, ref } from 'vue'; import { handleTree } from '@vben/utils'; -import * as ProductCategoryApi from '#/api/mall/product/category'; +import { getCategoryList } from '#/api/mall/product/category'; /** 商品分类选择组件 */ defineOptions({ name: 'ProductCategorySelect' }); @@ -42,8 +42,7 @@ const selectCategoryId = computed({ /** 初始化 */ const categoryList = ref([]); // 分类树 onMounted(async () => { - // 获得分类树 - const data = await ProductCategoryApi.getCategoryList({ + const data = await getCategoryList({ parentId: props.parentId, }); categoryList.value = handleTree(data, 'id', 'parentId'); diff --git a/apps/web-antd/src/views/mall/product/spu/components/index.ts b/apps/web-antd/src/views/mall/product/spu/components/index.ts new file mode 100644 index 000000000..122cbcea0 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/index.ts @@ -0,0 +1,3 @@ +export { default as SkuTableSelect } from './sku-table-select.vue'; +export { default as SpuShowcase } from './spu-showcase.vue'; +export { default as SpuTableSelect } from './spu-table-select.vue'; diff --git a/apps/web-antd/src/views/mall/product/spu/form/modules/sku-table-select.vue b/apps/web-antd/src/views/mall/product/spu/components/sku-table-select.vue similarity index 60% rename from apps/web-antd/src/views/mall/product/spu/form/modules/sku-table-select.vue rename to apps/web-antd/src/views/mall/product/spu/components/sku-table-select.vue index 0900c360e..2679a9d4f 100644 --- a/apps/web-antd/src/views/mall/product/spu/form/modules/sku-table-select.vue +++ b/apps/web-antd/src/views/mall/product/spu/components/sku-table-select.vue @@ -8,8 +8,6 @@ import { computed, ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { fenToYuan } from '@vben/utils'; -import { Input, message } from 'ant-design-vue'; - import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { getSpu } from '#/api/mall/product/spu'; @@ -21,18 +19,13 @@ const emit = defineEmits<{ change: [sku: MallSpuApi.Sku]; }>(); -const selectedSkuId = ref(); const spuId = ref(); -/** 配置列 */ -// TODO @puhui999:貌似列太宽了? +/** 表格列配置 */ const gridColumns = computed(() => [ { - field: 'id', - title: '#', - width: 60, - align: 'center', - slots: { default: 'radio-column' }, + type: 'radio', + width: 55, }, { field: 'picUrl', @@ -66,73 +59,65 @@ const gridColumns = computed(() => [ }, ]); +// TODO @芋艿:要不要直接非 pager? const [Grid, gridApi] = useVbenVxeGrid({ gridOptions: { columns: gridColumns.value, height: 400, border: true, showOverflow: true, + radioConfig: { + reserve: true, + }, proxyConfig: { ajax: { query: async () => { if (!spuId.value) { return { items: [], total: 0 }; } - try { - const spu = await getSpu(spuId.value); - return { - items: spu.skus || [], - total: spu.skus?.length || 0, - }; - } catch (error) { - message.error('加载 SKU 数据失败'); - console.error(error); - return { items: [], total: 0 }; - } + const spu = await getSpu(spuId.value); + return { + items: spu.skus || [], + total: spu.skus?.length || 0, + }; }, }, }, }, + gridEvents: { + radioChange: handleRadioChange, + }, }); /** 处理选中 */ -function handleSelected(row: MallSpuApi.Sku) { - emit('change', row); - modalApi.close(); - selectedSkuId.value = undefined; +function handleRadioChange() { + const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Sku; + if (selectedRow) { + emit('change', selectedRow); + modalApi.close(); + } } const [Modal, modalApi] = useVbenModal({ destroyOnClose: true, onOpenChange: async (isOpen: boolean) => { if (!isOpen) { - selectedSkuId.value = undefined; + gridApi.grid.clearRadioRow(); spuId.value = undefined; return; } const data = modalApi.getData(); - // TODO @puhui999:这里要不 if return,让括号的层级简单点。 - if (data?.spuId) { - spuId.value = data.spuId; - // 触发数据查询 - await gridApi.query(); + if (!data?.spuId) { + return; } + spuId.value = data.spuId; + await gridApi.query(); }, }); diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue new file mode 100644 index 000000000..e1abcb255 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue @@ -0,0 +1,141 @@ + + + + diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue new file mode 100644 index 000000000..236d4c264 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue @@ -0,0 +1,225 @@ + + + + diff --git a/apps/web-antd/src/views/mall/product/spu/form/data.ts b/apps/web-antd/src/views/mall/product/spu/form/data.ts index d2ff7eeb5..3ca6e5c79 100644 --- a/apps/web-antd/src/views/mall/product/spu/form/data.ts +++ b/apps/web-antd/src/views/mall/product/spu/form/data.ts @@ -102,6 +102,7 @@ export function useInfoFormSchema(): VbenFormSchema[] { } /** 价格库存的表单 */ +// TODO @puhui999:貌似太宽了。。。屏幕小的,整个 table 展示补全哈~~ export function useSkuFormSchema( propertyList: any[] = [], isDetail: boolean = false, diff --git a/apps/web-antd/src/views/mall/product/spu/form/modules/sku-list.vue b/apps/web-antd/src/views/mall/product/spu/form/modules/sku-list.vue index b3df262c1..348f5f22a 100644 --- a/apps/web-antd/src/views/mall/product/spu/form/modules/sku-list.vue +++ b/apps/web-antd/src/views/mall/product/spu/form/modules/sku-list.vue @@ -7,7 +7,12 @@ import type { MallSpuApi } from '#/api/mall/product/spu'; import { ref, watch } from 'vue'; -import { copyValueToTarget, formatToFraction, isEmpty } from '@vben/utils'; +import { + copyValueToTarget, + formatToFraction, + getNestedValue, + isEmpty, +} from '@vben/utils'; import { Button, Image, Input, InputNumber, message } from 'ant-design-vue'; @@ -43,9 +48,12 @@ const emit = defineEmits<{ const { isBatch, isDetail, isComponent, isActivityComponent } = props; -const formData: Ref = ref(); // 表单数据 -const skuList = ref([ - { +const formData: Ref = ref(); +const tableHeaders = ref<{ label: string; prop: string }[]>([]); + +/** 创建空 SKU 数据 */ +function createEmptySku(): MallSpuApi.Sku { + return { price: 0, marketPrice: 0, costPrice: 0, @@ -56,8 +64,10 @@ const skuList = ref([ volume: 0, firstBrokeragePrice: 0, secondBrokeragePrice: 0, - }, -]); // 批量添加时的临时数据 + }; +} + +const skuList = ref([createEmptySku()]); /** 批量添加 */ function batchAdd() { @@ -79,34 +89,33 @@ function validateProperty() { } } -/** 删除 sku */ +/** 删除 SKU */ function deleteSku(row: MallSpuApi.Sku) { const index = formData.value!.skus!.findIndex( - // 直接把列表转成字符串比较 (sku: MallSpuApi.Sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties), ); - formData.value!.skus!.splice(index, 1); + if (index !== -1) { + formData.value!.skus!.splice(index, 1); + } } -const tableHeaders = ref<{ label: string; prop: string }[]>([]); // 多属性表头 - -/** 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种 */ +/** 校验 SKU 数据:保存时,每个商品规格的表单要校验。例如:销售金额最低是 0.01 */ function validateSku() { validateProperty(); let warningInfo = '请检查商品各行相关属性配置,'; - let validate = true; // 默认通过 + let validate = true; + for (const sku of formData.value!.skus!) { - // 作为活动组件的校验 for (const rule of props?.ruleConfig as RuleConfig[]) { - const arg = getValue(sku, rule.name); - if (!rule.rule(arg)) { - validate = false; // 只要有一个不通过则直接不通过 + const value = getNestedValue(sku, rule.name); + if (!rule.rule(value)) { + validate = false; warningInfo += rule.message; break; } } - // 只要有一个不通过则结束后续的校验 + if (!validate) { message.warning(warningInfo); throw new Error(warningInfo); @@ -114,21 +123,6 @@ function validateSku() { } } -// TODO @puhui999:是不是可以通过 getNestedValue 简化? -function getValue(obj: any, arg: string): unknown { - const keys = arg.split('.'); - let value: any = obj; - for (const key of keys) { - if (value && typeof value === 'object' && key in value) { - value = value[key]; - } else { - value = undefined; - break; - } - } - return value; -} - /** * 选择时触发 * @@ -155,7 +149,6 @@ watch( /** 生成表数据 */ function generateTableData(propertyList: PropertyAndValues[]) { - // 构建数据结构 const propertyValues = propertyList.map((item: PropertyAndValues) => (item.values || []).map((v: { id: number; name: string }) => ({ propertyId: item.id, @@ -164,35 +157,30 @@ function generateTableData(propertyList: PropertyAndValues[]) { valueName: v.name, })), ); + const buildSkuList = build(propertyValues); + // 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表 if (!validateData(propertyList)) { - // 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表 formData.value!.skus = []; } + for (const item of buildSkuList) { + const properties = Array.isArray(item) ? item : [item]; const row = { - properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象 - price: 0, - marketPrice: 0, - costPrice: 0, - barCode: '', - picUrl: '', - stock: 0, - weight: 0, - volume: 0, - firstBrokeragePrice: 0, - secondBrokeragePrice: 0, + ...createEmptySku(), + properties, }; + // 如果存在属性相同的 sku 则不做处理 - const index = formData.value!.skus!.findIndex( + const exists = formData.value!.skus!.some( (sku: MallSpuApi.Sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties), ); - if (index !== -1) { - continue; + + if (!exists) { + formData.value!.skus!.push(row); } - formData.value!.skus!.push(row); } } @@ -224,7 +212,9 @@ function build( const result: MallSpuApi.Property[][] = []; const rest = build(propertyValuesList.slice(1)); const firstList = propertyValuesList[0]; - if (!firstList) return []; + if (!firstList) { + return []; + } for (const element of firstList) { for (const element_ of rest) { @@ -248,43 +238,33 @@ watch( if (!formData.value!.specType) { return; } + // 如果当前组件作为批量添加数据使用,则重置表数据 if (props.isBatch) { - skuList.value = [ - { - price: 0, - marketPrice: 0, - costPrice: 0, - barCode: '', - picUrl: '', - stock: 0, - weight: 0, - volume: 0, - firstBrokeragePrice: 0, - secondBrokeragePrice: 0, - }, - ]; + skuList.value = [createEmptySku()]; } // 判断代理对象是否为空 if (JSON.stringify(propertyList) === '[]') { return; } - // 重置表头 - tableHeaders.value = []; - // 生成表头 - propertyList.forEach((item, index) => { - // name加属性项index区分属性值 - tableHeaders.value.push({ prop: `name${index}`, label: item.name }); - }); + + // 重置并生成表头 + tableHeaders.value = propertyList.map((item, index) => ({ + prop: `name${index}`, + label: item.name, + })); + // 如果回显的 sku 属性和添加的属性一致则不处理 if (validateData(propertyList)) { return; } + // 添加新属性没有属性值也不做处理 if (propertyList.some((item) => !item.values || isEmpty(item.values))) { return; } + // 生成 table 数据,即 sku 列表 generateTableData(propertyList); }, @@ -296,17 +276,21 @@ watch( const activitySkuListRef = ref(); +/** 获取 SKU 表格引用 */ function getSkuTableRef() { return activitySkuListRef.value; } -defineExpose({ generateTableData, validateSku, getSkuTableRef }); +defineExpose({ + generateTableData, + validateSku, + getSkuTableRef, +}); - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/property.vue index 9db7ac2ad..19b4fc814 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/property.vue @@ -12,7 +12,12 @@ import { } from 'element-plus'; import UploadImg from '#/components/upload/image-upload.vue'; -import { AppLinkInput, Draggable } from '#/views/mall/promotion/components'; +import { + AppLinkInput, + ColorInput, + Draggable, + InputWithColor, +} from '#/views/mall/promotion/components'; import ComponentContainerProperty from '../../component-container-property.vue'; import { EMPTY_MENU_GRID_ITEM_PROPERTY } from './config'; @@ -21,7 +26,9 @@ import { EMPTY_MENU_GRID_ITEM_PROPERTY } from './config'; defineOptions({ name: 'MenuGridProperty' }); const props = defineProps<{ modelValue: MenuGridProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); @@ -35,7 +42,6 @@ const formData = useVModel(props, 'modelValue', emit); 4个 - - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/config.ts index 6567c553d..375546ae3 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/config.ts @@ -4,28 +4,21 @@ import { cloneDeep } from '@vben/utils'; /** 列表导航属性 */ export interface MenuListProperty { - // 导航菜单列表 - list: MenuListItemProperty[]; - // 组件样式 - style: ComponentStyle; + list: MenuListItemProperty[]; // 导航菜单列表 + style: ComponentStyle; // 组件样式 } /** 列表导航项目属性 */ export interface MenuListItemProperty { - // 图标链接 - iconUrl: string; - // 标题 - title: string; - // 标题颜色 - titleColor: string; - // 副标题 - subtitle: string; - // 副标题颜色 - subtitleColor: string; - // 链接 - url: string; + iconUrl: string; // 图标链接 + title: string; // 标题 + titleColor: string; // 标题颜色 + subtitle: string; // 副标题 + subtitleColor: string; // 副标题颜色 + url: string; // 链接 } +/** 空的列表导航项目属性 */ export const EMPTY_MENU_LIST_ITEM_PROPERTY = { title: '标题', titleColor: '#333', @@ -33,7 +26,7 @@ export const EMPTY_MENU_LIST_ITEM_PROPERTY = { subtitleColor: '#bbb', }; -// 定义组件 +/** 定义组件 */ export const component = { id: 'MenuList', name: '列表导航', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/index.vue index 12670dcb0..0a05a6c7b 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/index.vue @@ -7,6 +7,7 @@ import { ElImage } from 'element-plus'; /** 列表导航 */ defineOptions({ name: 'MenuList' }); + defineProps<{ property: MenuListProperty }>(); @@ -19,14 +20,14 @@ defineProps<{ property: MenuListProperty }>(); >
- {{ - item.title - }} + + {{ item.title }} +
- {{ - item.subtitle - }} + + {{ item.subtitle }} +
diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/property.vue index c5b076936..2c5991501 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/property.vue @@ -18,7 +18,9 @@ import { EMPTY_MENU_LIST_ITEM_PROPERTY } from './config'; defineOptions({ name: 'MenuListProperty' }); const props = defineProps<{ modelValue: MenuListProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); @@ -26,8 +28,6 @@ const formData = useVModel(props, 'modelValue', emit); 菜单设置 拖动左侧的小圆点可以调整顺序 - - - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/config.ts index 27138ebee..fa83498c8 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/config.ts @@ -4,40 +4,28 @@ import { cloneDeep } from '@vben/utils'; /** 菜单导航属性 */ export interface MenuSwiperProperty { - // 布局: 图标+文字 | 图标 - layout: 'icon' | 'iconText'; - // 行数 - row: number; - // 列数 - column: number; - // 导航菜单列表 - list: MenuSwiperItemProperty[]; - // 组件样式 - style: ComponentStyle; -} -/** 菜单导航项目属性 */ -export interface MenuSwiperItemProperty { - // 图标链接 - iconUrl: string; - // 标题 - title: string; - // 标题颜色 - titleColor: string; - // 链接 - url: string; - // 角标 - badge: { - // 角标背景颜色 - bgColor: string; - // 是否显示 - show: boolean; - // 角标文字 - text: string; - // 角标文字颜色 - textColor: string; - }; + layout: 'icon' | 'iconText'; // 布局:图标+文字 | 图标 + row: number; // 行数 + column: number; // 列数 + list: MenuSwiperItemProperty[]; // 导航菜单列表 + style: ComponentStyle; // 组件样式 } +/** 菜单导航项目属性 */ +export interface MenuSwiperItemProperty { + iconUrl: string; // 图标链接 + title: string; // 标题 + titleColor: string; // 标题颜色 + url: string; // 链接 + badge: { + bgColor: string; // 角标背景颜色 + show: boolean; // 是否显示 + text: string; // 角标文字 + textColor: string; // 角标文字颜色 + }; // 角标 +} + +/** 空菜单导航项目属性 */ export const EMPTY_MENU_SWIPER_ITEM_PROPERTY = { title: '标题', titleColor: '#333', @@ -48,7 +36,7 @@ export const EMPTY_MENU_SWIPER_ITEM_PROPERTY = { }, } as MenuSwiperItemProperty; -// 定义组件 +/** 定义组件 */ export const component = { id: 'MenuSwiper', name: '菜单导航', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/index.vue index 0d69c7c86..fb4304522 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/index.vue @@ -7,22 +7,19 @@ import { ElCarousel, ElCarouselItem, ElImage } from 'element-plus'; /** 菜单导航 */ defineOptions({ name: 'MenuSwiper' }); -const props = defineProps<{ property: MenuSwiperProperty }>(); -// 标题的高度 -const TITLE_HEIGHT = 20; -// 图标的高度 -const ICON_SIZE = 32; -// 垂直间距:一行上下的间距 -const SPACE_Y = 16; -// 分页 -const pages = ref([]); -// 轮播图高度 -const carouselHeight = ref(0); -// 行高 -const rowHeight = ref(0); -// 列宽 -const columnWidth = ref(''); +const props = defineProps<{ property: MenuSwiperProperty }>(); + +const TITLE_HEIGHT = 20; // 标题的高度 +const ICON_SIZE = 32; // 图标的高度 +const SPACE_Y = 16; // 垂直间距:一行上下的间距 + +const pages = ref([]); // 分页 +const carouselHeight = ref(0); // 轮播图高度 + +const rowHeight = ref(0); // 行高 +const columnWidth = ref(''); // 列宽 + watch( () => props.property, () => { @@ -75,9 +72,7 @@ watch( class="relative flex flex-col items-center justify-center" :style="{ width: columnWidth, height: `${rowHeight}px` }" > -
-
- (); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/components/cell-property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/components/cell-property.vue index 585a0f0a7..366723d88 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/components/cell-property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/components/cell-property.vue @@ -22,7 +22,7 @@ import { MagicCubeEditor, } from '#/views/mall/promotion/components'; -// 导航栏属性面板 +/** 导航栏单元格属性面板 */ defineOptions({ name: 'NavigationBarCellProperty' }); const props = defineProps({ @@ -35,13 +35,19 @@ const props = defineProps({ default: () => [], }, }); + const emit = defineEmits(['update:modelValue']); + const cellList = useVModel(props, 'modelValue', emit); -// 单元格数量:小程序6个(右侧胶囊按钮占了2个),其它平台8个 +/** + * 计算单元格数量 + * 1. 小程序:6 个(因为右侧有胶囊按钮占据 2 个格子的空间) + * 2. 其它平台:8 个(全部空间可用) + */ const cellCount = computed(() => (props.isMp ? 6 : 8)); -// 转换为Rect格式的数据 +/** 转换为 Rect 格式的数据:MagicCubeEditor 组件需要 Rect 格式的数据来渲染热区 */ const rectList = computed(() => { return cellList.value.map((cell) => ({ left: cell.left, @@ -53,18 +59,19 @@ const rectList = computed(() => { })); }); -// 选中的热区 -const selectedHotAreaIndex = ref(0); -const handleHotAreaSelected = ( +const selectedHotAreaIndex = ref(0); // 选中的热区 + +/** 处理热区被选中事件 */ +function handleHotAreaSelected( cellValue: NavigationBarCellProperty, index: number, -) => { +) { selectedHotAreaIndex.value = index; if (!cellValue.type) { cellValue.type = 'text'; cellValue.textColor = '#111111'; } -}; +} - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/config.ts index fd675b16e..f898ce9e8 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/config.ts @@ -2,56 +2,35 @@ import type { DiyComponent } from '../../../util'; /** 顶部导航栏属性 */ export interface NavigationBarProperty { - // 背景类型 - bgType: 'color' | 'img'; - // 背景颜色 - bgColor: string; - // 图片链接 - bgImg: string; - // 样式类型:默认 | 沉浸式 - styleType: 'inner' | 'normal'; - // 常驻显示 - alwaysShow: boolean; - // 小程序单元格列表 - mpCells: NavigationBarCellProperty[]; - // 其它平台单元格列表 - otherCells: NavigationBarCellProperty[]; - // 本地变量 + bgType: 'color' | 'img'; // 背景类型 + bgColor: string; // 背景颜色 + bgImg: string; // 图片链接 + styleType: 'inner' | 'normal'; // 样式类型:默认 | 沉浸式 + alwaysShow: boolean; // 常驻显示 + mpCells: NavigationBarCellProperty[]; // 小程序单元格列表 + otherCells: NavigationBarCellProperty[]; // 其它平台单元格列表 _local: { - // 预览顶部导航(小程序) - previewMp: boolean; - // 预览顶部导航(非小程序) - previewOther: boolean; - }; + previewMp: boolean; // 预览顶部导航(小程序) + previewOther: boolean; // 预览顶部导航(非小程序) + }; // 本地变量 } /** 顶部导航栏 - 单元格 属性 */ export interface NavigationBarCellProperty { - // 类型:文字 | 图片 | 搜索框 - type: 'image' | 'search' | 'text'; - // 宽度 - width: number; - // 高度 - height: number; - // 顶部位置 - top: number; - // 左侧位置 - left: number; - // 文字内容 - text: string; - // 文字颜色 - textColor: string; - // 图片地址 - imgUrl: string; - // 图片链接 - url: string; - // 搜索框:提示文字 - placeholder: string; - // 搜索框:边框圆角半径 - borderRadius: number; + type: 'image' | 'search' | 'text'; // 类型:文字 | 图片 | 搜索框 + width: number; // 宽度 + height: number; // 高度 + top: number; // 顶部位置 + left: number; // 左侧位置 + text: string; // 文字内容 + textColor: string; // 文字颜色 + imgUrl: string; // 图片地址 + url: string; // 图片链接 + placeholder: string; // 搜索框:提示文字 + borderRadius: number; // 搜索框:边框圆角半径 } -// 定义组件 +/** 定义组件 */ export const component = { id: 'NavigationBar', name: '顶部导航栏', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/index.vue index 9e3b17a07..3460ee53e 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/index.vue @@ -18,7 +18,7 @@ defineOptions({ name: 'NavigationBar' }); const props = defineProps<{ property: NavigationBarProperty }>(); -// 背景 +/** 计算背景样式 */ const bgStyle = computed(() => { const background = props.property.bgType === 'img' && props.property.bgImg @@ -26,27 +26,31 @@ const bgStyle = computed(() => { : props.property.bgColor; return { background }; }); -// 单元格列表 + +/** 获取当前预览的单元格列表 */ const cellList = computed(() => props.property._local?.previewMp ? props.property.mpCells : props.property.otherCells, ); -// 单元格宽度 + +/** 计算单元格宽度 */ const cellWidth = computed(() => { return props.property._local?.previewMp ? (375 - 80 - 86) / 6 : (375 - 90) / 8; }); -// 获得单元格样式 -const getCellStyle = (cell: NavigationBarCellProperty) => { + +/** 获取单元格样式 */ +function getCellStyle(cell: NavigationBarCellProperty) { return { width: `${cell.width * cellWidth.value + (cell.width - 1) * 10}px`, left: `${cell.left * cellWidth.value + (cell.left + 1) * 10}px`, position: 'absolute', } as StyleValue; -}; -// 获得搜索框属性 +} + +/** 获取搜索框属性配置 */ const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => { return { height: 30, diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/property.vue index ce3e3f698..9703be641 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/property.vue @@ -2,19 +2,31 @@ import type { NavigationBarProperty } from './config'; import { useVModel } from '@vueuse/core'; +import { + ElCard, + ElCheckbox, + ElForm, + ElFormItem, + ElRadio, + ElRadioGroup, + ElTooltip, +} from 'element-plus'; + +import UploadImg from '#/components/upload/image-upload.vue'; +import { ColorInput } from '#/views/mall/promotion/components'; import NavigationBarCellProperty from './components/cell-property.vue'; -// 导航栏属性面板 +/** 导航栏属性面板 */ defineOptions({ name: 'NavigationBarProperty' }); + const props = defineProps<{ modelValue: NavigationBarProperty }>(); const emit = defineEmits(['update:modelValue']); -// 表单校验 const rules = { name: [{ required: true, message: '请输入页面名称', trigger: 'blur' }], -}; +}; // 表单校验 const formData = useVModel(props, 'modelValue', emit); if (!formData.value._local) { @@ -23,47 +35,47 @@ if (!formData.value._local) { - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/config.ts index 2d4acffb2..d9b68333e 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/config.ts @@ -2,27 +2,20 @@ import type { ComponentStyle, DiyComponent } from '../../../util'; /** 公告栏属性 */ export interface NoticeBarProperty { - // 图标地址 - iconUrl: string; - // 公告内容列表 - contents: NoticeContentProperty[]; - // 背景颜色 - backgroundColor: string; - // 文字颜色 - textColor: string; - // 组件样式 - style: ComponentStyle; + iconUrl: string; // 图标地址 + contents: NoticeContentProperty[]; // 公告内容列表 + backgroundColor: string; // 背景颜色 + textColor: string; // 文字颜色 + style: ComponentStyle; // 组件样式 } /** 内容属性 */ export interface NoticeContentProperty { - // 内容文字 - text: string; - // 链接地址 - url: string; + text: string; // 内容文字 + url: string; // 链接地址 } -// 定义组件 +/** 定义组件 */ export const component = { id: 'NoticeBar', name: '公告栏', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/property.vue index 055aa2e10..5fdb1f06b 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/property.vue @@ -13,18 +13,17 @@ import { import ComponentContainerProperty from '../../component-container-property.vue'; -// 通知栏属性面板 +/** 公告栏属性面板 */ defineOptions({ name: 'NoticeBarProperty' }); + const props = defineProps<{ modelValue: NoticeBarProperty }>(); const emit = defineEmits(['update:modelValue']); -// 表单校验 +const formData = useVModel(props, 'modelValue', emit); const rules = { content: [{ required: true, message: '请输入公告', trigger: 'blur' }], -}; - -const formData = useVModel(props, 'modelValue', emit); +}; // 表单校验 - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/config.ts index 73032a84a..a00e6458b 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/config.ts @@ -2,15 +2,12 @@ import type { DiyComponent } from '../../../util'; /** 页面设置属性 */ export interface PageConfigProperty { - // 页面描述 - description: string; - // 页面背景颜色 - backgroundColor: string; - // 页面背景图片 - backgroundImage: string; + description: string; // 页面描述 + backgroundColor: string; // 页面背景颜色 + backgroundImage: string; // 页面背景图片 } -// 定义页面组件 +/** 定义页面组件 */ export const component = { id: 'PageConfig', name: '页面设置', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/property.vue index 9d904c0e8..27aa37bec 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/property.vue @@ -7,24 +7,22 @@ import { ElForm, ElFormItem, ElInput } from 'element-plus'; import UploadImg from '#/components/upload/image-upload.vue'; import { ColorInput } from '#/views/mall/promotion/components'; -// 导航栏属性面板 +/** 导航栏属性面板 */ defineOptions({ name: 'PageConfigProperty' }); + const props = defineProps<{ modelValue: PageConfigProperty }>(); const emit = defineEmits(['update:modelValue']); -// 表单校验 -const rules = {}; - const formData = useVModel(props, 'modelValue', emit); - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/config.ts similarity index 58% rename from apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/config.ts rename to apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/config.ts index 6f365b735..52bbeacdb 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/config.ts @@ -2,19 +2,17 @@ import type { DiyComponent } from '../../../util'; /** 弹窗广告属性 */ export interface PopoverProperty { - list: PopoverItemProperty[]; + list: PopoverItemProperty[]; // 弹窗列表 } +/** 弹窗广告项目属性 */ export interface PopoverItemProperty { - // 图片地址 - imgUrl: string; - // 跳转连接 - url: string; - // 显示类型:仅显示一次、每次启动都会显示 - showType: 'always' | 'once'; + imgUrl: string; // 图片地址 + url: string; // 跳转连接 + showType: 'always' | 'once'; // 显示类型:仅显示一次、每次启动都会显示 } -// 定义组件 +/** 定义组件 */ export const component = { id: 'Popover', name: '弹窗广告', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/index.vue similarity index 81% rename from apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/index.vue rename to apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/index.vue index bc724eca9..694f77ed2 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/index.vue @@ -9,18 +9,20 @@ import { ElImage } from 'element-plus'; /** 弹窗广告 */ defineOptions({ name: 'Popover' }); -// 定义属性 -defineProps<{ property: PopoverProperty }>(); -// 处理选中 -const activeIndex = ref(0); -const handleActive = (index: number) => { +const props = defineProps<{ property: PopoverProperty }>(); + +const activeIndex = ref(0); // 选中 index + +/** 处理选中 */ +function handleActive(index: number) { activeIndex.value = index; -}; +} + - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/property.vue similarity index 96% rename from apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/property.vue rename to apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/property.vue index 7f41d413c..189b6b068 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/Popover/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/popover/property.vue @@ -13,11 +13,13 @@ import { import UploadImg from '#/components/upload/image-upload.vue'; import { AppLinkInput, Draggable } from '#/views/mall/promotion/components'; -// 弹窗广告属性面板 +/** 弹窗广告属性面板 */ defineOptions({ name: 'PopoverProperty' }); const props = defineProps<{ modelValue: PopoverProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); @@ -53,5 +55,3 @@ const formData = useVModel(props, 'modelValue', emit);
- - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/config.ts index d165b01f6..a9e84977b 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/config.ts @@ -2,63 +2,40 @@ import type { ComponentStyle, DiyComponent } from '../../../util'; /** 商品卡片属性 */ export interface ProductCardProperty { - // 布局类型:单列大图 | 单列小图 | 双列 - layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'; - // 商品字段 + layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'; // 布局类型:单列大图 | 单列小图 | 双列 fields: { - // 商品简介 - introduction: ProductCardFieldProperty; - // 商品市场价 - marketPrice: ProductCardFieldProperty; - // 商品名称 - name: ProductCardFieldProperty; - // 商品价格 - price: ProductCardFieldProperty; - // 商品销量 - salesCount: ProductCardFieldProperty; - // 商品库存 - stock: ProductCardFieldProperty; - }; - // 角标 + introduction: ProductCardFieldProperty; // 商品简介 + marketPrice: ProductCardFieldProperty; // 商品市场价 + name: ProductCardFieldProperty; // 商品名称 + price: ProductCardFieldProperty; // 商品价格 + salesCount: ProductCardFieldProperty; // 商品销量 + stock: ProductCardFieldProperty; // 商品库存 + }; // 商品字段 badge: { - // 角标图片 - imgUrl: string; - // 是否显示 - show: boolean; - }; - // 按钮 + imgUrl: string; // 角标图片 + show: boolean; // 是否显示 + }; // 角标 btnBuy: { - // 文字按钮:背景渐变起始颜色 - bgBeginColor: string; - // 文字按钮:背景渐变结束颜色 - bgEndColor: string; - // 图片按钮:图片地址 - imgUrl: string; - // 文字 - text: string; - // 类型:文字 | 图片 - type: 'img' | 'text'; - }; - // 上圆角 - borderRadiusTop: number; - // 下圆角 - borderRadiusBottom: number; - // 间距 - space: number; - // 商品编号列表 - spuIds: number[]; - // 组件样式 - style: ComponentStyle; -} -// 商品字段 -export interface ProductCardFieldProperty { - // 是否显示 - show: boolean; - // 颜色 - color: string; + bgBeginColor: string; // 文字按钮:背景渐变起始颜色 + bgEndColor: string; // 文字按钮:背景渐变结束颜色 + imgUrl: string; // 图片按钮:图片地址 + text: string; // 文字 + type: 'img' | 'text'; // 类型:文字 | 图片 + }; // 按钮 + borderRadiusTop: number; // 上圆角 + borderRadiusBottom: number; // 下圆角 + space: number; // 间距 + spuIds: number[]; // 商品编号列表 + style: ComponentStyle; // 组件样式 } -// 定义组件 +/** 商品字段属性 */ +export interface ProductCardFieldProperty { + show: boolean; // 是否显示 + color: string; // 颜色 +} + +/** 定义组件 */ export const component = { id: 'ProductCard', name: '商品卡片', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/index.vue index b1cf736e8..b66c27c6a 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/index.vue @@ -13,10 +13,11 @@ import * as ProductSpuApi from '#/api/mall/product/spu'; /** 商品卡片 */ defineOptions({ name: 'ProductCard' }); -// 定义属性 + const props = defineProps<{ property: ProductCardProperty }>(); -// 商品列表 + const spuList = ref([]); + watch( () => props.property.spuIds, async () => { @@ -28,28 +29,21 @@ watch( }, ); -/** - * 计算商品的间距 - * @param index 商品索引 - */ -const calculateSpace = (index: number) => { - // 商品的列数 - const columns = props.property.layoutType === 'twoCol' ? 2 : 1; - // 第一列没有左边距 - const marginLeft = index % columns === 0 ? '0' : `${props.property.space}px`; - // 第一行没有上边距 - const marginTop = index < columns ? '0' : `${props.property.space}px`; - +/** 计算商品的间距 */ +function calculateSpace(index: number) { + const columns = props.property.layoutType === 'twoCol' ? 2 : 1; // 商品的列数 + const marginLeft = index % columns === 0 ? '0' : `${props.property.space}px`; // 第一列没有左边距 + const marginTop = index < columns ? '0' : `${props.property.space}px`; // 第一行没有上边距 return { marginLeft, marginTop }; -}; +} -// 容器 const containerRef = ref(); -// 计算商品的宽度 + +/** 计算商品的宽度 */ const calculateWidth = () => { let width = '100%'; - // 双列时每列的宽度为:(总宽度 - 间距)/ 2 if (props.property.layoutType === 'twoCol') { + // 双列时每列的宽度为:(总宽度 - 间距)/ 2 width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`; } return { width }; @@ -136,14 +130,14 @@ const calculateWidth = () => { class="text-[16px]" :style="{ color: property.fields.price.color }" > - ¥{{ fenToYuan(spu.price as any) }} + ¥{{ fenToYuan(spu.price!) }} ¥{{ fenToYuan(spu.marketPrice) }} + >¥{{ fenToYuan(spu.marketPrice!) }}
@@ -186,5 +180,3 @@ const calculateWidth = () => {
- - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/property.vue index 728ae7827..f7680e7a4 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/property.vue @@ -18,15 +18,18 @@ import { } from 'element-plus'; import UploadImg from '#/components/upload/image-upload.vue'; +import { SpuShowcase } from '#/views/mall/product/spu/components'; import { ColorInput } from '#/views/mall/promotion/components'; -// TODO: 添加组件 -// import SpuShowcase from '#/views/mall/product/spu/components/spu-showcase.vue'; -// 商品卡片属性面板 +import ComponentContainerProperty from '../../component-container-property.vue'; + +/** 商品卡片属性面板 */ defineOptions({ name: 'ProductCardProperty' }); const props = defineProps<{ modelValue: ProductCardProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); @@ -34,7 +37,7 @@ const formData = useVModel(props, 'modelValue', emit); - + @@ -174,5 +177,3 @@ const formData = useVModel(props, 'modelValue', emit); - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/config.ts index 20b4d37af..f5d3e4e5f 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/config.ts @@ -2,42 +2,29 @@ import type { ComponentStyle, DiyComponent } from '../../../util'; /** 商品栏属性 */ export interface ProductListProperty { - // 布局类型:双列 | 三列 | 水平滑动 - layoutType: 'horizSwiper' | 'threeCol' | 'twoCol'; - // 商品字段 + layoutType: 'horizSwiper' | 'threeCol' | 'twoCol'; // 布局类型:双列 | 三列 | 水平滑动 fields: { - // 商品名称 - name: ProductListFieldProperty; - // 商品价格 - price: ProductListFieldProperty; - }; - // 角标 + name: ProductListFieldProperty; // 商品名称 + price: ProductListFieldProperty; // 商品价格 + }; // 商品字段 badge: { - // 角标图片 - imgUrl: string; - // 是否显示 - show: boolean; - }; - // 上圆角 - borderRadiusTop: number; - // 下圆角 - borderRadiusBottom: number; - // 间距 - space: number; - // 商品编号列表 - spuIds: number[]; - // 组件样式 - style: ComponentStyle; -} -// 商品字段 -export interface ProductListFieldProperty { - // 是否显示 - show: boolean; - // 颜色 - color: string; + imgUrl: string; // 角标图片 + show: boolean; // 是否显示 + }; // 角标 + borderRadiusTop: number; // 上圆角 + borderRadiusBottom: number; // 下圆角 + space: number; // 间距 + spuIds: number[]; // 商品编号列表 + style: ComponentStyle; // 组件样式 } -// 定义组件 +/** 商品字段属性 */ +export interface ProductListFieldProperty { + show: boolean; // 是否显示 + color: string; // 颜色 +} + +/** 定义组件 */ export const component = { id: 'ProductList', name: '商品栏', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/index.vue index 0e7ec6e1b..54d795af0 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/index.vue @@ -13,10 +13,11 @@ import * as ProductSpuApi from '#/api/mall/product/spu'; /** 商品栏 */ defineOptions({ name: 'ProductList' }); -// 定义属性 + const props = defineProps<{ property: ProductListProperty }>(); -// 商品列表 + const spuList = ref([]); + watch( () => props.property.spuIds, async () => { @@ -27,19 +28,15 @@ watch( deep: true, }, ); -// 手机宽度 -const phoneWidth = ref(375); -// 容器 -const containerRef = ref(); -// 商品的列数 -const columns = ref(2); -// 滚动条宽度 -const scrollbarWidth = ref('100%'); -// 商品图大小 -const imageSize = ref('0'); -// 商品网络列数 -const gridTemplateColumns = ref(''); -// 计算布局参数 + +const phoneWidth = ref(375); // 手机宽度 +const containerRef = ref(); // 容器 +const columns = ref(2); // 商品的列数 +const scrollbarWidth = ref('100%'); // 滚动条宽度 +const imageSize = ref('0'); // 商品图大小 +const gridTemplateColumns = ref(''); // 商品网络列数 + +/** 计算布局参数 */ watch( () => [props.property, phoneWidth, spuList.value.length], () => { @@ -69,8 +66,9 @@ watch( }, { immediate: true, deep: true }, ); + +/** 初始化 */ onMounted(() => { - // 提取手机宽度 phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375; }); @@ -146,5 +144,3 @@ onMounted(() => { - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/property.vue index 8e941b599..550dbfeae 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/property.vue @@ -6,6 +6,7 @@ import { IconifyIcon } from '@vben/icons'; import { useVModel } from '@vueuse/core'; import { ElCard, + ElCheckbox, ElForm, ElFormItem, ElRadioButton, @@ -16,17 +17,18 @@ import { } from 'element-plus'; import UploadImg from '#/components/upload/image-upload.vue'; -import { InputWithColor as ColorInput } from '#/views/mall/promotion/components'; +import { SpuShowcase } from '#/views/mall/product/spu/components'; +import { ColorInput } from '#/views/mall/promotion/components'; import ComponentContainerProperty from '../../component-container-property.vue'; -// TODO: 添加组件 -// import SpuShowcase from '#/views/mall/product/spu/components/spu-showcase.vue'; -// 商品栏属性面板 +/** 商品栏属性面板 */ defineOptions({ name: 'ProductListProperty' }); const props = defineProps<{ modelValue: ProductListProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); @@ -34,7 +36,7 @@ const formData = useVModel(props, 'modelValue', emit); - + @@ -119,5 +121,3 @@ const formData = useVModel(props, 'modelValue', emit); - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/config.ts index 9a1fc4194..ac47bf4b5 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/config.ts @@ -2,13 +2,11 @@ import type { ComponentStyle, DiyComponent } from '../../../util'; /** 营销文章属性 */ export interface PromotionArticleProperty { - // 文章编号 - id: number; - // 组件样式 - style: ComponentStyle; + id: number; // 文章编号 + style: ComponentStyle; // 组件样式 } -// 定义组件 +/** 定义组件 */ export const component = { id: 'PromotionArticle', name: '营销文章', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/index.vue index 10e9f5aa3..09f1ff673 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/index.vue @@ -9,10 +9,10 @@ import * as ArticleApi from '#/api/mall/promotion/article/index'; /** 营销文章 */ defineOptions({ name: 'PromotionArticle' }); -// 定义属性 -const props = defineProps<{ property: PromotionArticleProperty }>(); -// 商品列表 -const article = ref(); + +const props = defineProps<{ property: PromotionArticleProperty }>(); // 定义属性 + +const article = ref(); // 商品列表 watch( () => props.property.id, @@ -29,5 +29,3 @@ watch( - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/property.vue index 7a701c54f..d70503b17 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/property.vue @@ -12,18 +12,19 @@ import * as ArticleApi from '#/api/mall/promotion/article/index'; import ComponentContainerProperty from '../../component-container-property.vue'; -// 营销文章属性面板 +/** 营销文章属性面板 */ defineOptions({ name: 'PromotionArticleProperty' }); const props = defineProps<{ modelValue: PromotionArticleProperty }>(); -const emit = defineEmits(['update:modelValue']); -const formData = useVModel(props, 'modelValue', emit); -// 文章列表 -const articles = ref([]); -// 加载中 -const loading = ref(false); -// 查询文章列表 +const emit = defineEmits(['update:modelValue']); + +const formData = useVModel(props, 'modelValue', emit); + +const articles = ref([]); // 文章列表 +const loading = ref(false); // 加载中 + +/** 查询文章列表 */ const queryArticleList = async (title?: string) => { loading.value = true; const { list } = await ArticleApi.getArticlePage({ @@ -35,7 +36,7 @@ const queryArticleList = async (title?: string) => { loading.value = false; }; -// 初始化 +/** 初始化 */ onMounted(() => { queryArticleList(); }); @@ -65,5 +66,3 @@ onMounted(() => { - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/config.ts index f0497dc19..f9db86b73 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/config.ts @@ -2,64 +2,40 @@ import type { ComponentStyle, DiyComponent } from '../../../util'; /** 拼团属性 */ export interface PromotionCombinationProperty { - // 布局类型:单列 | 三列 - layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'; - // 商品字段 + layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'; // 布局类型:单列 | 三列 fields: { - // 商品简介 - introduction: PromotionCombinationFieldProperty; - // 市场价 - marketPrice: PromotionCombinationFieldProperty; - // 商品名称 - name: PromotionCombinationFieldProperty; - // 商品价格 - price: PromotionCombinationFieldProperty; - // 商品销量 - salesCount: PromotionCombinationFieldProperty; - // 商品库存 - stock: PromotionCombinationFieldProperty; - }; - // 角标 + introduction: PromotionCombinationFieldProperty; // 商品简介 + marketPrice: PromotionCombinationFieldProperty; // 市场价 + name: PromotionCombinationFieldProperty; // 商品名称 + price: PromotionCombinationFieldProperty; // 商品价格 + salesCount: PromotionCombinationFieldProperty; // 商品销量 + stock: PromotionCombinationFieldProperty; // 商品库存 + }; // 商品字段 badge: { - // 角标图片 - imgUrl: string; - // 是否显示 - show: boolean; - }; - // 按钮 + imgUrl: string; // 角标图片 + show: boolean; // 是否显示 + }; // 角标 btnBuy: { - // 文字按钮:背景渐变起始颜色 - bgBeginColor: string; - // 文字按钮:背景渐变结束颜色 - bgEndColor: string; - // 图片按钮:图片地址 - imgUrl: string; - // 文字 - text: string; - // 类型:文字 | 图片 - type: 'img' | 'text'; - }; - // 上圆角 - borderRadiusTop: number; - // 下圆角 - borderRadiusBottom: number; - // 间距 - space: number; - // 拼团活动编号 - activityIds: number[]; - // 组件样式 - style: ComponentStyle; + bgBeginColor: string; // 文字按钮:背景渐变起始颜色 + bgEndColor: string; // 文字按钮:背景渐变结束颜色 + imgUrl: string; // 图片按钮:图片地址 + text: string; // 文字 + type: 'img' | 'text'; // 类型:文字 | 图片 + }; // 按钮 + borderRadiusTop: number; // 上圆角 + borderRadiusBottom: number; // 下圆角 + space: number; // 间距 + activityIds: number[]; // 拼团活动编号 + style: ComponentStyle; // 组件样式 } -// 商品字段 +/** 商品字段属性 */ export interface PromotionCombinationFieldProperty { - // 是否显示 - show: boolean; - // 颜色 - color: string; + show: boolean; // 是否显示 + color: string; // 颜色 } -// 定义组件 +/** 定义组件 */ export const component = { id: 'PromotionCombination', name: '拼团', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/index.vue index 82f1f5bd7..4be26abf4 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/index.vue @@ -15,9 +15,9 @@ import * as CombinationActivityApi from '#/api/mall/promotion/combination/combin /** 拼团卡片 */ defineOptions({ name: 'PromotionCombination' }); -// 定义属性 + const props = defineProps<{ property: PromotionCombinationProperty }>(); -// 商品列表 + const spuList = ref([]); const spuIdList = ref([]); const combinationActivityList = ref< @@ -30,7 +30,7 @@ watch( try { // 新添加的拼团组件,是没有活动ID的 const activityIds = props.property.activityIds; - // 检查活动ID的有效性 + // 检查活动 ID 的有效性 if (Array.isArray(activityIds) && activityIds.length > 0) { // 获取拼团活动详情列表 combinationActivityList.value = @@ -70,28 +70,21 @@ watch( }, ); -/** - * 计算商品的间距 - * @param index 商品索引 - */ -const calculateSpace = (index: number) => { - // 商品的列数 - const columns = props.property.layoutType === 'twoCol' ? 2 : 1; - // 第一列没有左边距 - const marginLeft = index % columns === 0 ? '0' : `${props.property.space}px`; - // 第一行没有上边距 - const marginTop = index < columns ? '0' : `${props.property.space}px`; - +/** 计算商品的间距 */ +function calculateSpace(index: number) { + const columns = props.property.layoutType === 'twoCol' ? 2 : 1; // 商品的列数 + const marginLeft = index % columns === 0 ? '0' : `${props.property.space}px`; // 第一列没有左边距 + const marginTop = index < columns ? '0' : `${props.property.space}px`; // 第一行没有上边距 return { marginLeft, marginTop }; -}; +} -// 容器 const containerRef = ref(); -// 计算商品的宽度 + +/** 计算商品的宽度 */ const calculateWidth = () => { let width = '100%'; - // 双列时每列的宽度为:(总宽度 - 间距)/ 2 if (props.property.layoutType === 'twoCol') { + // 双列时每列的宽度为:(总宽度 - 间距)/ 2 width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`; } return { width }; @@ -117,7 +110,7 @@ const calculateWidth = () => { >
{
- ¥{{ fenToYuan(spu.marketPrice) }} + ¥{{ fenToYuan(spu.marketPrice!) }}
@@ -229,5 +222,3 @@ const calculateWidth = () => {
- - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/property.vue index 273580f44..fe222be0b 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/property.vue @@ -1,17 +1,15 @@ @@ -186,5 +182,3 @@ onMounted(async () => { - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/config.ts index 01d5894d1..c471780e1 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/config.ts @@ -13,10 +13,10 @@ export interface SearchProperty { style: ComponentStyle; } -// 文字位置 +/** 文字位置 */ export type PlaceholderPosition = 'center' | 'left'; -// 定义组件 +/** 定义组件 */ export const component = { id: 'SearchBar', name: '搜索框', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/index.vue index e7c770304..7463d7db6 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/index.vue @@ -5,19 +5,19 @@ import { IconifyIcon } from '@vben/icons'; /** 搜索框 */ defineOptions({ name: 'SearchBar' }); + defineProps<{ property: SearchProperty }>(); - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/property.vue index 5e1822078..4dca02da7 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/property.vue @@ -11,6 +11,7 @@ import { ElCard, ElForm, ElFormItem, + ElInput, ElRadioButton, ElRadioGroup, ElSlider, @@ -18,7 +19,7 @@ import { ElTooltip, } from 'element-plus'; -import { Draggable } from '#/views/mall/promotion/components'; +import { ColorInput, Draggable } from '#/views/mall/promotion/components'; import ComponentContainerProperty from '../../component-container-property.vue'; @@ -26,10 +27,12 @@ import ComponentContainerProperty from '../../component-container-property.vue'; defineOptions({ name: 'SearchProperty' }); const props = defineProps<{ modelValue: SearchProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); -// 监听热词数组变化 +/** 监听热词数组变化 */ watch( () => formData.value.hotKeywords, (newVal) => { @@ -45,8 +48,7 @@ watch( \ No newline at end of file diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/property.vue index 79a7eb080..46a0e16c5 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/property.vue @@ -5,16 +5,16 @@ import { useVModel } from '@vueuse/core'; import ComponentContainerProperty from '../../component-container-property.vue'; -// 用户卡片属性面板 +/** 用户卡片属性面板 */ defineOptions({ name: 'UserCardProperty' }); const props = defineProps<{ modelValue: UserCardProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/config.ts index 25ca85065..e149b22f1 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/config.ts @@ -2,11 +2,10 @@ import type { ComponentStyle, DiyComponent } from '../../../util'; /** 用户订单属性 */ export interface UserOrderProperty { - // 组件样式 - style: ComponentStyle; + style: ComponentStyle; // 组件样式 } -// 定义组件 +/** 定义组件 */ export const component = { id: 'UserOrder', name: '用户订单', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/index.vue index ab527acee..1c171b3eb 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/index.vue @@ -5,13 +5,12 @@ import { ElImage } from 'element-plus'; /** 用户订单 */ defineOptions({ name: 'UserOrder' }); -// 定义属性 + +/** 定义属性 */ defineProps<{ property: UserOrderProperty }>(); - - + \ No newline at end of file diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/property.vue index 48f0c0a41..2f9abe556 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/property.vue @@ -5,16 +5,16 @@ import { useVModel } from '@vueuse/core'; import ComponentContainerProperty from '../../component-container-property.vue'; -// 用户订单属性面板 +/** 用户订单属性面板 */ defineOptions({ name: 'UserOrderProperty' }); const props = defineProps<{ modelValue: UserOrderProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); - - + \ No newline at end of file diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/config.ts index 2b10d32fb..71d8d0245 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/config.ts @@ -2,11 +2,10 @@ import type { ComponentStyle, DiyComponent } from '../../../util'; /** 用户资产属性 */ export interface UserWalletProperty { - // 组件样式 - style: ComponentStyle; + style: ComponentStyle; // 组件样式 } -// 定义组件 +/** 定义组件 */ export const component = { id: 'UserWallet', name: '用户资产', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/index.vue index 7581e54c9..4eabad045 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/index.vue @@ -5,13 +5,12 @@ import { ElImage } from 'element-plus'; /** 用户资产 */ defineOptions({ name: 'UserWallet' }); -// 定义属性 + +/** 定义属性 */ defineProps<{ property: UserWalletProperty }>(); - - + \ No newline at end of file diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/property.vue index 5c01b828e..74b374535 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/property.vue @@ -5,11 +5,13 @@ import { useVModel } from '@vueuse/core'; import ComponentContainerProperty from '../../component-container-property.vue'; -// 用户资产属性面板 +/** 用户资产属性面板 */ defineOptions({ name: 'UserWalletProperty' }); const props = defineProps<{ modelValue: UserWalletProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/config.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/config.ts index bc9195575..ab7ccd311 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/config.ts +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/config.ts @@ -2,23 +2,18 @@ import type { ComponentStyle, DiyComponent } from '../../../util'; /** 视频播放属性 */ export interface VideoPlayerProperty { - // 视频链接 - videoUrl: string; - // 封面链接 - posterUrl: string; - // 是否自动播放 - autoplay: boolean; - // 组件样式 - style: VideoPlayerStyle; + videoUrl: string; // 视频链接 + posterUrl: string; // 封面链接 + autoplay: boolean; // 是否自动播放 + style: VideoPlayerStyle; // 组件样式 } -// 视频播放样式 +/** 视频播放样式 */ export interface VideoPlayerStyle extends ComponentStyle { - // 视频高度 - height: number; + height: number; // 视频高度 } -// 定义组件 +/** 定义组件 */ export const component = { id: 'VideoPlayer', name: '视频播放', diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/index.vue index ddd448d96..b58577d5b 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/index.vue @@ -11,13 +11,13 @@ defineProps<{ property: VideoPlayerProperty }>(); - - diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/property.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/property.vue index 67c355905..c576d8f76 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/property.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/property.vue @@ -9,11 +9,13 @@ import UploadImg from '#/components/upload/image-upload.vue'; import ComponentContainerProperty from '../../component-container-property.vue'; -// 视频播放属性面板 +/** 视频播放属性面板 */ defineOptions({ name: 'VideoPlayerProperty' }); const props = defineProps<{ modelValue: VideoPlayerProperty }>(); + const emit = defineEmits(['update:modelValue']); + const formData = useVModel(props, 'modelValue', emit); @@ -58,6 +60,4 @@ const formData = useVModel(props, 'modelValue', emit); - - - + \ No newline at end of file diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/index.vue b/apps/web-ele/src/views/mall/promotion/components/diy-editor/index.vue index 5afc427a9..8d3fda28b 100644 --- a/apps/web-ele/src/views/mall/promotion/components/diy-editor/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/index.vue @@ -1,9 +1,9 @@ - - + 添加 - - + + - - diff --git a/apps/web-ele/src/views/mall/promotion/components/index.ts b/apps/web-ele/src/views/mall/promotion/components/index.ts index a088806e2..6dbe001fe 100644 --- a/apps/web-ele/src/views/mall/promotion/components/index.ts +++ b/apps/web-ele/src/views/mall/promotion/components/index.ts @@ -1,4 +1,5 @@ export { default as AppLinkInput } from './app-link-input/index.vue'; +export { default as AppLinkSelectDialog } from './app-link-input/select-dialog.vue'; export { default as ColorInput } from './color-input/index.vue'; export { default as DiyEditor } from './diy-editor/index.vue'; export { type DiyComponentLibrary, PAGE_LIBS } from './diy-editor/util'; diff --git a/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/index.vue b/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/index.vue index d260ded9c..b985a8142 100644 --- a/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/index.vue @@ -7,16 +7,16 @@ import { IconifyIcon } from '@vben/icons'; import { createRect, isContains, isOverlap } from './util'; -// TODO @AI: 改成标准注释 -// 魔方编辑器 -// 有两部分组成: -// 1. 魔方矩阵:位于底层,由方块组件的二维表格,用于创建热区 -// 操作方法: -// 1.1 点击其中一个方块就会进入热区选择模式 -// 1.2 再次点击另外一个方块时,结束热区选择模式 -// 1.3 在两个方块中间的区域创建热区 -// 如果两次点击的都是同一方块,就只创建一个格子的热区 -// 2. 热区:位于顶层,采用绝对定位,覆盖在魔方矩阵上面。 +/** + * 魔方编辑器,有两部分组成: + * 1. 魔方矩阵:位于底层,由方块组件的二维表格,用于创建热区 + * 操作方法: + * 1.1 点击其中一个方块就会进入热区选择模式 + * 1.2 再次点击另外一个方块时,结束热区选择模式 + * 1.3 在两个方块中间的区域创建热区 + * 如果两次点击的都是同一方块,就只创建一个格子的热区 + * 2. 热区:位于顶层,采用绝对定位,覆盖在魔方矩阵上面。 + */ defineOptions({ name: 'MagicCubeEditor' }); /** 定义属性 */ @@ -29,12 +29,10 @@ const props = defineProps({ type: Number, default: 4, }, // 行数,默认 4 行 - cols: { type: Number, default: 4, }, // 列数,默认 4 列 - cubeSize: { type: Number, default: 75, @@ -70,6 +68,7 @@ watch( ); const hotAreas = ref([]); // 热区列表 + /** 初始化热区 */ watch( () => props.modelValue, @@ -86,20 +85,20 @@ const isHotAreaSelectMode = () => !!hotAreaBeginCube.value; // 是否开启了 * @param currentRow 当前行号 * @param currentCol 当前列号 */ -const handleCubeClick = (currentRow: number, currentCol: number) => { +function handleCubeClick(currentRow: number, currentCol: number) { const currentCube = cubes.value[currentRow]?.[currentCol]; if (!currentCube) { return; } - // 情况1:进入热区选择模式 + // 情况 1:进入热区选择模式 if (!isHotAreaSelectMode()) { hotAreaBeginCube.value = currentCube; hotAreaBeginCube.value!.active = true; return; } - // 情况2:结束热区选择模式 + // 情况 2:结束热区选择模式 hotAreas.value.push(createRect(hotAreaBeginCube.value!, currentCube)); // 结束热区选择模式 exitHotAreaSelectMode(); @@ -111,7 +110,7 @@ const handleCubeClick = (currentRow: number, currentCol: number) => { } // 发送热区变动通知 emitUpdateModelValue(); -}; +} /** * 处理鼠标经过方块 @@ -119,7 +118,7 @@ const handleCubeClick = (currentRow: number, currentCol: number) => { * @param currentRow 当前行号 * @param currentCol 当前列号 */ -const handleCellHover = (currentRow: number, currentCol: number) => { +function handleCellHover(currentRow: number, currentCol: number) { // 当前没有进入热区选择模式 if (!isHotAreaSelectMode()) { return; @@ -138,7 +137,6 @@ const handleCellHover = (currentRow: number, currentCol: number) => { if (isOverlap(hotArea, currentSelectedArea)) { // 结束热区选择模式 exitHotAreaSelectMode(); - return; } } @@ -147,13 +145,9 @@ const handleCellHover = (currentRow: number, currentCol: number) => { eachCube((_, __, cube) => { cube.active = isContains(currentSelectedArea, cube); }); -}; +} -/** - * 处理热区删除 - * - * @param index 热区索引 - */ +/** 处理热区删除 */ function handleDeleteHotArea(index: number) { hotAreas.value.splice(index, 1); // 结束热区选择模式 @@ -165,14 +159,14 @@ function handleDeleteHotArea(index: number) { const emitUpdateModelValue = () => emit('update:modelValue', hotAreas.value); // 发送热区变动通知 const selectedHotAreaIndex = ref(0); // 热区选中 -const handleHotAreaSelected = (hotArea: Rect, index: number) => { + +/** 处理热区选中 */ +function handleHotAreaSelected(hotArea: Rect, index: number) { selectedHotAreaIndex.value = index; emit('hotAreaSelected', hotArea, index); -}; +} -/** - * 结束热区选择模式 - */ +/** 结束热区选择模式 */ function exitHotAreaSelectMode() { // 移除方块激活标记 eachCube((_, __, cube) => { @@ -246,9 +240,9 @@ const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => { > - {{ - `${hotArea.width}×${hotArea.height}` - }} + + {{ `${hotArea.width}×${hotArea.height}` }} + @@ -265,6 +259,11 @@ const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => { text-align: center; cursor: pointer; border: 1px solid var(--el-border-color); + line-height: 1; + + :deep(.iconify) { + display: inline-block; + } &.active { background: var(--el-color-primary-light-9); diff --git a/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/util.ts b/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/util.ts index 1ee8fb5a4..6e3eb7cd2 100644 --- a/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/util.ts +++ b/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/util.ts @@ -1,51 +1,47 @@ -// 坐标点 +/** 坐标点 */ export interface Point { x: number; y: number; } -// 矩形 +/** 矩形 */ export interface Rect { - // 左上角 X 轴坐标 - left: number; - // 左上角 Y 轴坐标 - top: number; - // 右下角 X 轴坐标 - right: number; - // 右下角 Y 轴坐标 - bottom: number; - // 矩形宽度 - width: number; - // 矩形高度 - height: number; + left: number; // 左上角 X 轴坐标 + top: number; // 左上角 Y 轴坐标 + right: number; // 右下角 X 轴坐标 + bottom: number; // 右下角 Y 轴坐标 + width: number; // 矩形宽度 + height: number; // 矩形高度 } /** * 判断两个矩形是否重叠 + * * @param a 矩形 A * @param b 矩形 B */ -export const isOverlap = (a: Rect, b: Rect): boolean => { +export function isOverlap(a: Rect, b: Rect): boolean { return ( a.left < b.left + b.width && a.left + a.width > b.left && a.top < b.top + b.height && a.height + a.top > b.top ); -}; +} + /** * 检查坐标点是否在矩形内 * @param hotArea 矩形 * @param point 坐标 */ -export const isContains = (hotArea: Rect, point: Point): boolean => { +export function isContains(hotArea: Rect, point: Point): boolean { return ( point.x >= hotArea.left && point.x < hotArea.right && point.y >= hotArea.top && point.y < hotArea.bottom ); -}; +} /** * 在两个坐标点中间,创建一个矩形 @@ -59,14 +55,17 @@ export const isContains = (hotArea: Rect, point: Point): boolean => { * @param a 坐标点一 * @param b 坐标点二 */ -export const createRect = (a: Point, b: Point): Rect => { +export function createRect(a: Point, b: Point): Rect { // 计算矩形的范围 - const [left, left2] = [a.x, b.x].sort(); - const [top, top2] = [a.y, b.y].sort(); + let [left, left2] = [a.x, b.x].sort(); + left = left ?? 0; + left2 = left2 ?? 0; + let [top, top2] = [a.y, b.y].sort(); + top = top ?? 0; + top2 = top2 ?? 0; const right = left2 + 1; const bottom = top2 + 1; const height = bottom - top; const width = right - left; - return { left, right, top, bottom, height, width }; -}; +} diff --git a/apps/web-ele/src/views/mall/promotion/components/vertical-button-group/index.vue b/apps/web-ele/src/views/mall/promotion/components/vertical-button-group/index.vue index 2c076afa5..d900eab41 100644 --- a/apps/web-ele/src/views/mall/promotion/components/vertical-button-group/index.vue +++ b/apps/web-ele/src/views/mall/promotion/components/vertical-button-group/index.vue @@ -1,4 +1,6 @@