From ed9f0429754083a0d3182fde0e35a93bc36128d2 Mon Sep 17 00:00:00 2001 From: dhb52 Date: Fri, 26 May 2023 18:19:39 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20mall=20=E4=BC=98=E6=83=A0?= =?UTF-8?q?=E5=88=B8=20=E5=88=9D=E7=A8=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mall/product/spu.ts | 46 ++ src/api/mall/promotion/coupon.ts | 16 + src/api/mall/promotion/couponTemplate.ts | 60 ++ src/utils/constants.ts | 78 +++ src/utils/dict.ts | 11 +- src/views/mall/promotion/coupon/index.vue | 200 ++++++ .../mall/promotion/couponTemplate/index.vue | 614 ++++++++++++++++++ 7 files changed, 1024 insertions(+), 1 deletion(-) create mode 100644 src/api/mall/product/spu.ts create mode 100755 src/api/mall/promotion/coupon.ts create mode 100755 src/api/mall/promotion/couponTemplate.ts create mode 100755 src/views/mall/promotion/coupon/index.vue create mode 100755 src/views/mall/promotion/couponTemplate/index.vue diff --git a/src/api/mall/product/spu.ts b/src/api/mall/product/spu.ts new file mode 100644 index 00000000..2cfe722c --- /dev/null +++ b/src/api/mall/product/spu.ts @@ -0,0 +1,46 @@ +import request from '@/config/axios' + +// 创建商品 SPU +export function createSpu(data) { + return request.post({ + url: '/product/spu/create', + data: data + }) +} + +// 更新商品 SPU +export function updateSpu(data) { + return request.put({ + url: '/product/spu/update', + data: data + }) +} + +// 删除商品 SPU +export function deleteSpu(id) { + return request.delete({ + url: `/product/spu/delete?id=${id}` + }) +} + +// 获得商品 SPU 详情 +export function getSpuDetail(id) { + return request.get({ + url: `/product/spu/get-detail?id=${id}` + }) +} + +// 获得商品 SPU 分页 +export function getSpuPage(query) { + return request.get({ + url: '/product/spu/page', + params: query + }) +} + +// 获得商品 SPU 精简列表 +export function getSpuSimpleList() { + return request.get({ + url: '/product/spu/get-simple-list' + }) +} diff --git a/src/api/mall/promotion/coupon.ts b/src/api/mall/promotion/coupon.ts new file mode 100755 index 00000000..99e8a027 --- /dev/null +++ b/src/api/mall/promotion/coupon.ts @@ -0,0 +1,16 @@ +import request from '@/config/axios' + +// 删除优惠劵 +export function deleteCoupon(id) { + return request.delete({ + url: `/promotion/coupon/delete?id=${id}` + }) +} + +// 获得优惠劵分页 +export function getCouponPage(query) { + return request.get({ + url: '/promotion/coupon/page', + params: query + }) +} diff --git a/src/api/mall/promotion/couponTemplate.ts b/src/api/mall/promotion/couponTemplate.ts new file mode 100755 index 00000000..a861b127 --- /dev/null +++ b/src/api/mall/promotion/couponTemplate.ts @@ -0,0 +1,60 @@ +import request from '@/config/axios' + +// 创建优惠劵模板 +export function createCouponTemplate(data) { + return request.post({ + url: '/promotion/coupon-template/create', + data: data + }) +} + +// 更新优惠劵模板 +export function updateCouponTemplate(data) { + return request.put({ + url: '/promotion/coupon-template/update', + data: data + }) +} + +// 更新优惠劵模板的状态 +export function updateCouponTemplateStatus(id, status) { + const data = { + id, + status + } + return request.put({ + url: '/promotion/coupon-template/update-status', + data: data + }) +} + +// 删除优惠劵模板 +export function deleteCouponTemplate(id) { + return request.delete({ + url: '/promotion/coupon-template/delete?id=' + id + }) +} + +// 获得优惠劵模板 +export function getCouponTemplate(id) { + return request.get({ + url: '/promotion/coupon-template/get?id=' + id + }) +} + +// 获得优惠劵模板分页 +export function getCouponTemplatePage(query) { + return request.get({ + url: '/promotion/coupon-template/page', + params: query + }) +} + +// 导出优惠劵模板 Excel +export function exportCouponTemplateExcel(query) { + return request.get({ + url: '/promotion/coupon-template/export-excel', + params: query, + responseType: 'blob' + }) +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 5cda391f..a9d41b90 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -220,3 +220,81 @@ export const PayRefundStatusEnum = { name: '退款关闭' } } + +/** + * 优惠劵模板的有限期类型的枚举 + */ +export const CouponTemplateValidityTypeEnum = { + DATE: { + type: 1, + name: '固定日期可用' + }, + TERM: { + type: 2, + name: '领取之后可用' + } +} + +/** + * 营销的商品范围枚举 + */ +export const PromotionProductScopeEnum = { + ALL: { + scope: 1, + name: '全部商品参与' + }, + SPU: { + scope: 2, + name: '指定商品参与' + } +} + +/** + * 营销的条件类型枚举 + */ +export const PromotionConditionTypeEnum = { + PRICE: { + type: 10, + name: '满 N 元' + }, + COUNT: { + type: 20, + name: '满 N 件' + } +} + +/** + * 促销活动的状态枚举 + */ +export const PromotionActivityStatusEnum = { + WAIT: { + type: 10, + name: '未开始' + }, + RUN: { + type: 20, + name: '进行中' + }, + END: { + type: 30, + name: '已结束' + }, + CLOSE: { + type: 40, + name: '已关闭' + } +} + +/** + * 优惠类型枚举 + */ +export const PromotionDiscountTypeEnum = { + PRICE: { + type: 1, + name: '满减' + }, + PERCENT: { + type: 2, + name: '折扣' + } +} diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 03e17e75..73913b91 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -144,5 +144,14 @@ export enum DICT_TYPE { // ========== MP 模块 ========== MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型 - MP_MESSAGE_TYPE = 'mp_message_type' // 消息类型 + MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型 + + // ========== MALL - PROMOTION 模块 ========== + PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型 + PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围 + PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型 + PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态 + PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式 + PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态 + PROMOTION_CONDITION_TYPE = 'promotion_condition_type' // 营销的条件类型枚举 } diff --git a/src/views/mall/promotion/coupon/index.vue b/src/views/mall/promotion/coupon/index.vue new file mode 100755 index 00000000..9fe8f0d4 --- /dev/null +++ b/src/views/mall/promotion/coupon/index.vue @@ -0,0 +1,200 @@ + + + diff --git a/src/views/mall/promotion/couponTemplate/index.vue b/src/views/mall/promotion/couponTemplate/index.vue new file mode 100755 index 00000000..a1424413 --- /dev/null +++ b/src/views/mall/promotion/couponTemplate/index.vue @@ -0,0 +1,614 @@ + + + From 820d8ab76a708fdc1cc1b14f8b23b275a51f7485 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 30 May 2023 10:00:04 +0800 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20pnpm=20=E4=BB=93=E5=BA=93=E4=B8=AD?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=20video.js=208.0.4=20=E8=BF=99=E4=B8=AA?= =?UTF-8?q?=E7=89=88=E6=9C=AC=EF=BC=8C=E6=9B=B4=E6=94=B9=E4=B8=BA=208.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7a00c122..3d9bbd75 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,8 @@ "qs": "^6.11.1", "steady-xml": "^0.1.0", "url": "^0.11.0", - "video.js": "^8.0.4", - "vue": "3.2.47", + "video.js": "^8.3.0", + "vue": "3.3.4", "vue-i18n": "9.2.2", "vue-router": "^4.1.6", "vue-types": "^5.0.2", From e555977757f76516638ce2c8fb90eb6a186bc25b Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 30 May 2023 18:14:40 +0800 Subject: [PATCH 03/12] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20review=20?= =?UTF-8?q?=E6=8F=90=E5=88=B0=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E5=88=86=E7=B1=BB=E9=80=89=E6=8B=A9=E5=B1=82=E7=BA=A7?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E3=80=81=E5=AE=8C=E6=95=B4=E5=B1=82=E7=BA=A7?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mall/product/spu.ts | 14 ++-- src/utils/tree.ts | 81 ++++++++++++++++++ src/views/mall/product/spu/addForm.vue | 15 ++-- .../product/spu/components/BasicInfoForm.vue | 59 ++++++++----- .../spu/components/DescriptionForm.vue | 6 +- .../spu/components/OtherSettingsForm.vue | 6 +- .../components/ProductAttributesAddForm.vue | 3 +- .../mall/product/spu/components/SkuList.vue | 58 ++++++++----- src/views/mall/product/spu/index.vue | 84 ++++++++++++++----- 9 files changed, 238 insertions(+), 88 deletions(-) diff --git a/src/api/mall/product/spu.ts b/src/api/mall/product/spu.ts index fd55e126..5555ce10 100644 --- a/src/api/mall/product/spu.ts +++ b/src/api/mall/product/spu.ts @@ -7,8 +7,7 @@ export interface Property { valueName?: string // 属性值名称 } -// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型? -export interface SkuType { +export interface Sku { id?: number // 商品 SKU 编号 spuId?: number // SPU 编号 properties?: Property[] // 属性数组 @@ -25,8 +24,7 @@ export interface SkuType { salesCount?: number // 商品销量 } -// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型? -export interface SpuType { +export interface Spu { id?: number name?: string // 商品名称 categoryId?: number | null // 商品分类 @@ -39,9 +37,9 @@ export interface SpuType { brandId?: number | null // 商品品牌编号 specType?: boolean // 商品规格 subCommissionType?: boolean // 分销类型 - skus: SkuType[] // sku数组 + skus: Sku[] // sku数组 description?: string // 商品详情 - sort?: string // 商品排序 + sort?: number // 商品排序 giveIntegral?: number // 赠送积分 virtualSalesCount?: number // 虚拟销量 recommendHot?: boolean // 是否热卖 @@ -62,12 +60,12 @@ export const getTabsCount = () => { } // 创建商品 Spu -export const createSpu = (data: SpuType) => { +export const createSpu = (data: Spu) => { return request.post({ url: '/product/spu/create', data }) } // 更新商品 Spu -export const updateSpu = (data: SpuType) => { +export const updateSpu = (data: Spu) => { return request.put({ url: '/product/spu/update', data }) } diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 445adf1b..82b5bc34 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -3,6 +3,7 @@ interface TreeHelperConfig { children: string pid: string } + const DEFAULT_CONFIG: TreeHelperConfig = { id: 'id', children: 'children', @@ -133,6 +134,7 @@ export const filter = ( ): T[] => { config = getConfig(config) const children = config.children as string + function listFilter(list: T[]) { return list .map((node: any) => ({ ...node })) @@ -141,6 +143,7 @@ export const filter = ( return func(node) || (node[children] && node[children].length) }) } + return listFilter(tree) } @@ -264,6 +267,7 @@ export const handleTree = (data: any[], id?: string, parentId?: string, children } } } + return tree } @@ -302,3 +306,80 @@ export const handleTree2 = (data, id, parentId, children, rootId) => { }) return treeData !== '' ? treeData : data } +/** + * + * @param tree 要操作的树结构数据 + * @param nodeId 需要判断在什么层级的数据 + * @param level 检查的级别, 默认检查到二级 + */ +export const checkSelectedNode = (tree: any[], nodeId, level = 2) => { + if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) { + console.warn('tree must be an array') + return false + } + // 校验是否是一级节点 + if (tree.some((item) => item.id === nodeId)) { + return false + } + // 递归计数 + let count = 1 + + // 深层次校验 + function performAThoroughValidation(arr) { + count += 1 + for (const item of arr) { + if (item.id === nodeId) { + return true + } else if (typeof item.children !== 'undefined' && item.children.length !== 0) { + performAThoroughValidation(item.children) + } + } + return false + } + + for (const item of tree) { + count = 1 + if (performAThoroughValidation(item.children)) { + // 找到后对比是否是期望的层级 + if (count >= level) return true + } + } + return false +} +/** + * 获取节点的完整结构 + * @param tree 树数据 + * @param nodeId 节点 id + */ +export const treeToString = (tree: any[], nodeId) => { + if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) { + console.warn('tree must be an array') + return '' + } + // 校验是否是一级节点 + const node = tree.find((item) => item.id === nodeId) + if (typeof node !== 'undefined') { + return node.name + } + let str = '' + + function performAThoroughValidation(arr) { + for (const item of arr) { + if (item.id === nodeId) { + str += `/${item.name}` + return true + } else if (typeof item.children !== 'undefined' && item.children.length !== 0) { + performAThoroughValidation(item.children) + } + } + return false + } + + for (const item of tree) { + str = `${item.name}` + if (performAThoroughValidation(item.children)) { + break + } + } + return str +} diff --git a/src/views/mall/product/spu/addForm.vue b/src/views/mall/product/spu/addForm.vue index 18f9d0b6..1ae1a4c2 100644 --- a/src/views/mall/product/spu/addForm.vue +++ b/src/views/mall/product/spu/addForm.vue @@ -51,15 +51,15 @@ const basicInfoRef = ref>() // 商品信息Re const descriptionRef = ref>() // 商品详情Ref const otherSettingsRef = ref>() // 其他设置Ref // spu 表单数据 -const formData = ref({ +const formData = ref({ name: '', // 商品名称 categoryId: null, // 商品分类 keyword: '', // 关键字 unit: null, // 单位 picUrl: '', // 商品封面图 - sliderPicUrls: [], // 商品轮播图 + sliderPicUrls: [''], // 商品轮播图 introduction: '', // 商品简介 - deliveryTemplateId: 1, // 运费模版 + deliveryTemplateId: null, // 运费模版 brandId: null, // 商品品牌 specType: false, // 商品规格 subCommissionType: false, // 分销类型 @@ -94,7 +94,7 @@ const getDetail = async () => { if (id) { formLoading.value = true try { - const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType + const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.Spu res.skus.forEach((item) => { // 回显价格分转元 item.price = formatToFraction(item.price) @@ -120,8 +120,9 @@ const submitForm = async () => { await unref(basicInfoRef)?.validate() await unref(descriptionRef)?.validate() await unref(otherSettingsRef)?.validate() - const deepCopyFormData = cloneDeep(unref(formData.value)) // 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复, - // TODO 兜底处理 sku 空数据 + // 深拷贝一份, 这样最终 server 端不满足,不需要恢复, + const deepCopyFormData = cloneDeep(unref(formData.value)) + // 兜底处理 sku 空数据 formData.value.skus.forEach((sku) => { // 因为是空数据这里判断一下商品条码是否为空就行 if (sku.barCode === '') { @@ -150,7 +151,7 @@ const submitForm = async () => { }) deepCopyFormData.sliderPicUrls = newSliderPicUrls // 校验都通过后提交表单 - const data = deepCopyFormData as ProductSpuApi.SpuType + const data = deepCopyFormData as ProductSpuApi.Spu const id = params.spuId as number if (!id) { await ProductSpuApi.createSpu(data) diff --git a/src/views/mall/product/spu/components/BasicInfoForm.vue b/src/views/mall/product/spu/components/BasicInfoForm.vue index d800e332..60fc7c69 100644 --- a/src/views/mall/product/spu/components/BasicInfoForm.vue +++ b/src/views/mall/product/spu/components/BasicInfoForm.vue @@ -7,7 +7,7 @@ - + @@ -119,9 +120,9 @@ import { PropType } from 'vue' import { copyValueToTarget } from '@/utils' import { propTypes } from '@/utils/propTypes' -import { defaultProps, handleTree } from '@/utils/tree' +import { checkSelectedNode, defaultProps, handleTree } from '@/utils/tree' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import type { SpuType } from '@/api/mall/product/spu' +import type { Spu } from '@/api/mall/product/spu' import { UploadImg, UploadImgs } from '@/components/UploadFile' import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index' import * as ProductCategoryApi from '@/api/mall/product/category' @@ -131,7 +132,7 @@ const message = useMessage() // 消息弹窗 const props = defineProps({ propFormData: { - type: Object as PropType, + type: Object as PropType, default: () => {} }, activeName: propTypes.string.def('') @@ -144,7 +145,7 @@ const skuListRef = ref() // 商品属性列表Ref const generateSkus = (propertyList) => { skuListRef.value.generateTableData(propertyList) } -const formData = reactive({ +const formData = reactive({ name: '', // 商品名称 categoryId: null, // 商品分类 keyword: '', // 关键字 @@ -185,26 +186,24 @@ watch( formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({ url: item })) - // TODO @puhui999:if return,减少嵌套层级 // 只有是多规格才处理 - if (formData.specType) { - // 直接拿返回的 skus 属性逆向生成出 propertyList - const properties = [] - formData.skus.forEach((sku) => { - sku.properties.forEach(({ propertyId, propertyName, valueId, valueName }) => { - // 添加属性 - if (!properties.some((item) => item.id === propertyId)) { - properties.push({ id: propertyId, name: propertyName, values: [] }) - } - // 添加属性值 - const index = properties.findIndex((item) => item.id === propertyId) - if (!properties[index].values.some((value) => value.id === valueId)) { - properties[index].values.push({ id: valueId, name: valueName }) - } - }) + if (!formData.specType) return + // 直接拿返回的 skus 属性逆向生成出 propertyList + const properties = [] + formData.skus.forEach((sku) => { + sku.properties.forEach(({ propertyId, propertyName, valueId, valueName }) => { + // 添加属性 + if (!properties.some((item) => item.id === propertyId)) { + properties.push({ id: propertyId, name: propertyName, values: [] }) + } + // 添加属性值 + const index = properties.findIndex((item) => item.id === propertyId) + if (!properties[index].values.some((value) => value.id === valueId)) { + properties[index].values.push({ id: valueId, name: valueName }) + } }) - propertyList.value = properties - } + }) + propertyList.value = properties }, { immediate: true @@ -216,6 +215,11 @@ watch( */ const emit = defineEmits(['update:activeName']) const validate = async () => { + // 校验 sku + if (!skuListRef.value.validateSku()) { + message.warning('商品相关价格不能低于0.01元!!') + throw new Error('商品相关价格不能低于0.01元!!') + } // 校验表单 if (!productSpuBasicInfoRef) return return await unref(productSpuBasicInfoRef).validate((valid) => { @@ -263,6 +267,15 @@ const onChangeSpec = () => { } const categoryList = ref([]) // 分类树 +/** + * 选择分类时触发校验 + */ +const nodeClick = () => { + if (!checkSelectedNode(categoryList.value, formData.categoryId)) { + formData.categoryId = null + message.warning('必须选择二级节点!!') + } +} const brandList = ref([]) // 精简商品品牌列表 onMounted(async () => { // 获得分类树 diff --git a/src/views/mall/product/spu/components/DescriptionForm.vue b/src/views/mall/product/spu/components/DescriptionForm.vue index fbae9a86..23a3e99a 100644 --- a/src/views/mall/product/spu/components/DescriptionForm.vue +++ b/src/views/mall/product/spu/components/DescriptionForm.vue @@ -7,7 +7,7 @@ diff --git a/src/views/mall/product/spu/index.vue b/src/views/mall/product/spu/index.vue index 539171b0..34719775 100644 --- a/src/views/mall/product/spu/index.vue +++ b/src/views/mall/product/spu/index.vue @@ -8,18 +8,16 @@ class="-mb-15px" label-width="68px" > - - + - - + @@ -80,31 +79,60 @@ /> - +虚拟销量:999 --> @@ -202,7 +230,7 @@ import { TabsPaneContext } from 'element-plus' import { cloneDeep } from 'lodash-es' import { createImageViewer } from '@/components/ImageViewer' import { dateFormatter } from '@/utils/formatTime' -import { defaultProps, handleTree } from '@/utils/tree' +import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree' import { ProductSpuStatusEnum } from '@/utils/constants' import { formatToFraction } from '@/utils' import download from '@/utils/download' @@ -391,7 +419,7 @@ const handleExport = async () => { } } -// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新 +// 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。 watch( () => currentRoute.value, () => { @@ -400,6 +428,22 @@ watch( ) const categoryList = ref() // 分类树 +/** + * 获取分类的节点的完整结构 + * @param categoryId 分类id + */ +const categoryString = (categoryId) => { + return treeToString(categoryList.value, categoryId) +} +/** + * 校验所选是否为二级节点 + */ +const nodeClick = () => { + if (!checkSelectedNode(categoryList.value, queryParams.value.categoryId)) { + queryParams.value.categoryId = null + message.warning('必须选择二级节点!!') + } +} /** 初始化 **/ onMounted(async () => { await getTabsCount() From 000aa950129d42aa141816f939c64639e89ff7c6 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 30 May 2023 21:21:04 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=97=A8=E5=BA=97?= =?UTF-8?q?=E8=87=AA=E6=8F=90=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mall/trade/delivery/pickUpStore/index.ts | 46 +++ .../delivery/pickUpStore/PickUpStoreForm.vue | 288 ++++++++++++++++++ .../mall/trade/delivery/pickUpStore/index.vue | 199 ++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 src/api/mall/trade/delivery/pickUpStore/index.ts create mode 100644 src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue create mode 100644 src/views/mall/trade/delivery/pickUpStore/index.vue diff --git a/src/api/mall/trade/delivery/pickUpStore/index.ts b/src/api/mall/trade/delivery/pickUpStore/index.ts new file mode 100644 index 00000000..90fb3d01 --- /dev/null +++ b/src/api/mall/trade/delivery/pickUpStore/index.ts @@ -0,0 +1,46 @@ +import request from '@/config/axios' + +export interface DeliveryPickUpStoreVO { + id: number + name: string + introduction: string + phone: string + areaId: number + detailAddress: string + logo: string + openingTime: string + closingTime: string + latitude: number + longitude: number + status: number +} + +// 查询自提门店列表 +export const getDeliveryPickUpStorePage = async (params: DeliveryPickUpStorePageReqVO) => { + return await request.get({ url: '/trade/delivery/pick-up-store/page', params }) +} + +// 查询自提门店详情 +export const getDeliveryPickUpStore = async (id: number) => { + return await request.get({ url: '/trade/delivery/pick-up-store/get?id=' + id }) +} + +// 新增自提门店 +export const createDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) => { + return await request.post({ url: '/trade/delivery/pick-up-store/create', data }) +} + +// 修改自提门店 +export const updateDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) => { + return await request.put({ url: '/trade/delivery/pick-up-store/update', data }) +} + +// 删除自提门店 +export const deleteDeliveryPickUpStore = async (id: number) => { + return await request.delete({ url: '/trade/delivery/pick-up-store/delete?id=' + id }) +} + +// 导出自提门店 Excel +export const exportDeliveryPickUpStoreApi = async (params) => { + return await request.download({ url: '/trade/delivery/pick-up-store/export-excel', params }) +} diff --git a/src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue b/src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue new file mode 100644 index 00000000..937c2a48 --- /dev/null +++ b/src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue @@ -0,0 +1,288 @@ + + + diff --git a/src/views/mall/trade/delivery/pickUpStore/index.vue b/src/views/mall/trade/delivery/pickUpStore/index.vue new file mode 100644 index 00000000..d163af10 --- /dev/null +++ b/src/views/mall/trade/delivery/pickUpStore/index.vue @@ -0,0 +1,199 @@ + + From 39c92cb944e2b41007d1870d411f5255c9da1d70 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Wed, 31 May 2023 14:42:45 +0800 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20=E5=AE=8C=E5=96=84=20SPU=20?= =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mall/product/spu.ts | 2 +- src/router/modules/remaining.ts | 13 +++ src/utils/tree.ts | 21 +++- src/views/mall/product/spu/addForm.vue | 12 +- .../product/spu/components/BasicInfoForm.vue | 80 ++++++++++++- .../spu/components/DescriptionForm.vue | 25 ++++- .../spu/components/OtherSettingsForm.vue | 37 +++++- .../mall/product/spu/components/SkuList.vue | 106 ++++++++++++++++-- .../mall/product/spu/components/spu.data.ts | 105 +++++++++++++++++ src/views/mall/product/spu/index.vue | 23 ++-- 10 files changed, 387 insertions(+), 37 deletions(-) create mode 100644 src/views/mall/product/spu/components/spu.data.ts diff --git a/src/api/mall/product/spu.ts b/src/api/mall/product/spu.ts index 5555ce10..e0d4ec83 100644 --- a/src/api/mall/product/spu.ts +++ b/src/api/mall/product/spu.ts @@ -37,7 +37,7 @@ export interface Spu { brandId?: number | null // 商品品牌编号 specType?: boolean // 商品规格 subCommissionType?: boolean // 分销类型 - skus: Sku[] // sku数组 + skus?: Sku[] // sku数组 description?: string // 商品详情 sort?: number // 商品排序 giveIntegral?: number // 赠送积分 diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index e530c410..f7e56fd0 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -379,6 +379,19 @@ const remainingRouter: AppRouteRecordRaw[] = [ title: '编辑商品', activeMenu: '/product/product-spu' } + }, + { + path: 'productSpuDetail/:spuId(\\d+)', + component: () => import('@/views/mall/product/spu/addForm.vue'), + name: 'productSpuDetail', + meta: { + noCache: true, + hidden: true, + canTo: true, + icon: 'ep:view', + title: '商品详情', + activeMenu: '/product/product-spu' + } } ] } diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 82b5bc34..65a9345c 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -312,26 +312,30 @@ export const handleTree2 = (data, id, parentId, children, rootId) => { * @param nodeId 需要判断在什么层级的数据 * @param level 检查的级别, 默认检查到二级 */ -export const checkSelectedNode = (tree: any[], nodeId, level = 2) => { +export const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => { if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) { console.warn('tree must be an array') return false } + // 校验是否是一级节点 if (tree.some((item) => item.id === nodeId)) { return false } + // 递归计数 let count = 1 // 深层次校验 - function performAThoroughValidation(arr) { + function performAThoroughValidation(arr: any[]): boolean { count += 1 for (const item of arr) { if (item.id === nodeId) { return true } else if (typeof item.children !== 'undefined' && item.children.length !== 0) { - performAThoroughValidation(item.children) + if (performAThoroughValidation(item.children)) { + return true + } } } return false @@ -341,11 +345,15 @@ export const checkSelectedNode = (tree: any[], nodeId, level = 2) => { count = 1 if (performAThoroughValidation(item.children)) { // 找到后对比是否是期望的层级 - if (count >= level) return true + if (count >= level) { + return true + } } } + return false } + /** * 获取节点的完整结构 * @param tree 树数据 @@ -369,7 +377,10 @@ export const treeToString = (tree: any[], nodeId) => { str += `/${item.name}` return true } else if (typeof item.children !== 'undefined' && item.children.length !== 0) { - performAThoroughValidation(item.children) + str += `/${item.name}` + if (performAThoroughValidation(item.children)) { + return true + } } } return false diff --git a/src/views/mall/product/spu/addForm.vue b/src/views/mall/product/spu/addForm.vue index 1ae1a4c2..737b5e17 100644 --- a/src/views/mall/product/spu/addForm.vue +++ b/src/views/mall/product/spu/addForm.vue @@ -5,6 +5,7 @@ @@ -12,6 +13,7 @@ @@ -19,6 +21,7 @@ @@ -42,11 +45,12 @@ import { convertToInteger, formatToFraction } from '@/utils' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 const { push, currentRoute } = useRouter() // 路由 -const { params } = useRoute() // 查询参数 +const { params, name } = useRoute() // 查询参数 const { delView } = useTagsViewStore() // 视图操作 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const activeName = ref('basicInfo') // Tag 激活的窗口 +const isDetail = ref(false) // 是否查看详情 const basicInfoRef = ref>() // 商品信息Ref const descriptionRef = ref>() // 商品详情Ref const otherSettingsRef = ref>() // 其他设置Ref @@ -90,12 +94,13 @@ const formData = ref({ /** 获得详情 */ const getDetail = async () => { + console.log(name) const id = params.spuId as number if (id) { formLoading.value = true try { const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.Spu - res.skus.forEach((item) => { + res.skus!.forEach((item) => { // 回显价格分转元 item.price = formatToFraction(item.price) item.marketPrice = formatToFraction(item.marketPrice) @@ -123,7 +128,7 @@ const submitForm = async () => { // 深拷贝一份, 这样最终 server 端不满足,不需要恢复, const deepCopyFormData = cloneDeep(unref(formData.value)) // 兜底处理 sku 空数据 - formData.value.skus.forEach((sku) => { + formData.value.skus!.forEach((sku) => { // 因为是空数据这里判断一下商品条码是否为空就行 if (sku.barCode === '') { const index = deepCopyFormData.skus.findIndex( @@ -171,7 +176,6 @@ const close = () => { delView(unref(currentRoute)) push('/product/product-spu') } - /** 初始化 */ onMounted(async () => { await getDetail() diff --git a/src/views/mall/product/spu/components/BasicInfoForm.vue b/src/views/mall/product/spu/components/BasicInfoForm.vue index 60fc7c69..2cc5a7c2 100644 --- a/src/views/mall/product/spu/components/BasicInfoForm.vue +++ b/src/views/mall/product/spu/components/BasicInfoForm.vue @@ -1,5 +1,11 @@ diff --git a/src/views/mall/product/spu/index.vue b/src/views/mall/product/spu/index.vue index f3e0476b..47a9c8d5 100644 --- a/src/views/mall/product/spu/index.vue +++ b/src/views/mall/product/spu/index.vue @@ -408,7 +408,7 @@ const openForm = (id?: number) => { * 查看商品详情 */ const openDetail = (id?: number) => { - push('/product/productSpuDetail' + id) + push('/product/productSpuDetail/' + id) } /** 导出按钮操作 */ From 6a10a81f58f055eede23e2126938f6ae37909b6e Mon Sep 17 00:00:00 2001 From: puhui999 Date: Wed, 31 May 2023 15:58:35 +0800 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20=E5=BC=95=E5=85=A5=20v-dompurify-h?= =?UTF-8?q?tml=20=E6=8C=87=E4=BB=A4=E8=A7=A3=E5=86=B3=20v-html=20=E7=9A=84?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E9=9A=90=E6=82=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.dev | 4 ++-- package.json | 1 + src/components/Form/src/Form.vue | 16 ++++++++-------- src/main.ts | 5 ++++- src/views/infra/build/index.vue | 8 ++++---- src/views/infra/codegen/PreviewCode.vue | 2 +- .../product/spu/components/DescriptionForm.vue | 2 +- src/views/system/mail/log/MailLogDetail.vue | 2 +- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.env.dev b/.env.dev index a52eec30..9249ced8 100644 --- a/.env.dev +++ b/.env.dev @@ -19,13 +19,13 @@ VITE_API_URL=/admin-api VITE_BASE_PATH=/ # 是否删除debugger -VITE_DROP_DEBUGGER=false +VITE_DROP_DEBUGGER=true # 是否删除console.log VITE_DROP_CONSOLE=false # 是否sourcemap -VITE_SOURCEMAP=true +VITE_SOURCEMAP=false # 输出路径 VITE_OUT_DIR=dist-dev diff --git a/package.json b/package.json index 3d9bbd75..8cdf9380 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "url": "^0.11.0", "video.js": "^8.3.0", "vue": "3.3.4", + "vue-dompurify-html": "^4.1.4", "vue-i18n": "9.2.2", "vue-router": "^4.1.6", "vue-types": "^5.0.2", diff --git a/src/components/Form/src/Form.vue b/src/components/Form/src/Form.vue index c1121641..3dca6c94 100644 --- a/src/components/Form/src/Form.vue +++ b/src/components/Form/src/Form.vue @@ -1,16 +1,16 @@