fix:解决商品上一版遗留的各种小bug关键部分已添加fix注释。完成的TODO也已添加fix标记

(cherry picked from commit 0c42b76a52)
This commit is contained in:
puhui999 2023-05-19 17:04:13 +08:00 committed by shizhong
parent f26c72ced9
commit 4410bdd93c
7 changed files with 205 additions and 75 deletions

View File

@ -54,3 +54,8 @@ export const getBrand = (id: number) => {
export const getBrandParam = (params: PageParam) => { export const getBrandParam = (params: PageParam) => {
return request.get({ url: '/product/brand/page', params }) return request.get({ url: '/product/brand/page', params })
} }
// 获得商品品牌精简信息列表
export const getSimpleBrandList = () => {
return request.get({ url: '/product/brand/list-all-simple' })
}

View File

@ -34,6 +34,7 @@ export interface SpuType {
sliderPicUrls?: string[] // 商品轮播图 sliderPicUrls?: string[] // 商品轮播图
introduction?: string // 商品简介 introduction?: string // 商品简介
deliveryTemplateId?: number | null // 运费模版 deliveryTemplateId?: number | null // 运费模版
brandId?: number | null // 商品品牌编号
specType?: boolean // 商品规格 specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型 subCommissionType?: boolean // 分销类型
skus: SkuType[] // sku数组 skus: SkuType[] // sku数组

View File

@ -173,3 +173,24 @@ export const copyValueToTarget = (target, source) => {
// 更新目标对象值 // 更新目标对象值
Object.assign(target, newObj) Object.assign(target, newObj)
} }
/**
*
* @param num
*/
export const formatToFraction = (num: number | string | undefined): number => {
if (typeof num === 'undefined') return 0
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
return parseFloat((parsedNumber / 100).toFixed(2))
}
/**
*
* @param num
*/
export const convertToInteger = (num: number | string | undefined): number => {
if (typeof num === 'undefined') return 0
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
// TODO 分转元后还有小数则四舍五入
return Math.round(parsedNumber * 100)
}

View File

@ -38,6 +38,7 @@ import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
// api // api
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
import * as PropertyApi from '@/api/mall/product/property' import * as PropertyApi from '@/api/mall/product/property'
import { convertToInteger, formatToFraction } from '@/utils'
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
@ -60,6 +61,7 @@ const formData = ref<ProductSpuApi.SpuType>({
sliderPicUrls: [], // sliderPicUrls: [], //
introduction: '', // introduction: '', //
deliveryTemplateId: 1, // deliveryTemplateId: 1, //
brandId: null, //
specType: false, // specType: false, //
subCommissionType: false, // subCommissionType: false, //
skus: [ skus: [
@ -94,14 +96,34 @@ const getDetail = async () => {
formLoading.value = true formLoading.value = true
try { try {
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType
res.skus.forEach((item) => {
//
item.price = formatToFraction(item.price)
item.marketPrice = formatToFraction(item.marketPrice)
item.costPrice = formatToFraction(item.costPrice)
item.subCommissionFirstPrice = formatToFraction(item.subCommissionFirstPrice)
item.subCommissionSecondPrice = formatToFraction(item.subCommissionSecondPrice)
})
formData.value = res formData.value = res
// id //
// TODO @puhui999 propertyName id + uniapp if (res.specType) {
const propertyIds = res.skus[0]?.properties.map((item) => item.propertyId) // TODO @puhui999 propertyName id + uniapp
const PropertyS = await PropertyApi.getPropertyListAndValue({ propertyIds }) // fix: sku sku
await nextTick() const propertyIds = []
// res.skus.forEach((sku) =>
basicInfoRef.value.addAttribute(PropertyS) sku.properties
?.map((property) => property.propertyId)
.forEach((propertyId) => {
if (propertyIds.indexOf(propertyId) === -1) {
propertyIds.push(propertyId)
}
})
)
const properties = await PropertyApi.getPropertyListAndValue({ propertyIds })
await nextTick()
//
basicInfoRef.value.addAttribute(properties)
}
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -119,10 +141,26 @@ const submitForm = async () => {
await unref(descriptionRef)?.validate() await unref(descriptionRef)?.validate()
await unref(otherSettingsRef)?.validate() await unref(otherSettingsRef)?.validate()
const deepCopyFormData = cloneDeep(unref(formData.value)) // fix: server const deepCopyFormData = cloneDeep(unref(formData.value)) // fix: server
// // TODO sku SkuList TODO
formData.value.skus.forEach((sku) => {
//
if (sku.barCode === '') {
const index = deepCopyFormData.skus.findIndex(
(item) => JSON.stringify(item.properties) === JSON.stringify(sku.properties)
)
// sku
deepCopyFormData.skus.splice(index, 1)
}
})
deepCopyFormData.skus.forEach((item) => { deepCopyFormData.skus.forEach((item) => {
// sku name // sku name
item.name = deepCopyFormData.name item.name = deepCopyFormData.name
// sku
item.price = convertToInteger(item.price)
item.marketPrice = convertToInteger(item.marketPrice)
item.costPrice = convertToInteger(item.costPrice)
item.subCommissionFirstPrice = convertToInteger(item.subCommissionFirstPrice)
item.subCommissionSecondPrice = convertToInteger(item.subCommissionSecondPrice)
}) })
// //
const newSliderPicUrls = [] const newSliderPicUrls = []
@ -148,34 +186,6 @@ const submitForm = async () => {
} }
} }
/**
* 重置表单
* fix:先注释保留如果后期没有使用到则移除
*/
// const resetForm = async () => {
// formData.value = {
// name: '', //
// categoryId: 0, //
// keyword: '', //
// unit: '', //
// picUrl: '', //
// sliderPicUrls: [], //
// introduction: '', //
// deliveryTemplateId: 0, //
// selectRule: '',
// specType: false, //
// subCommissionType: false, //
// description: '', //
// sort: 1, //
// giveIntegral: 1, //
// virtualSalesCount: 1, //
// recommendHot: false, //
// recommendBenefit: false, //
// recommendBest: false, //
// recommendNew: false, //
// recommendGood: false //
// }
// }
/** 关闭按钮 */ /** 关闭按钮 */
const close = () => { const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))

View File

@ -59,13 +59,23 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="运费模板" prop="deliveryTemplateId"> <el-form-item label="运费模板" prop="deliveryTemplateId">
<el-select v-model="formData.deliveryTemplateId" class="w-1/1" placeholder="请选择"> <el-select v-model="formData.deliveryTemplateId" placeholder="请选择">
<el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
<el-button class="ml-20px">运费模板</el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-button class="ml-20px">运费模板</el-button> <el-form-item label="品牌" prop="brandId">
<el-select v-model="formData.brandId" placeholder="请选择">
<el-option
v-for="item in brandList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="商品规格" props="specType"> <el-form-item label="商品规格" props="specType">
@ -108,14 +118,15 @@
</template> </template>
<script lang="ts" name="ProductSpuBasicInfoForm" setup> <script lang="ts" name="ProductSpuBasicInfoForm" setup>
import { PropType } from 'vue' import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { defaultProps, handleTree } from '@/utils/tree' import { defaultProps, handleTree } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import type { SpuType } from '@/api/mall/product/spu' import type { SpuType } from '@/api/mall/product/spu'
import { UploadImg, UploadImgs } from '@/components/UploadFile' import { UploadImg, UploadImgs } from '@/components/UploadFile'
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index' import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
import * as ProductCategoryApi from '@/api/mall/product/category' import * as ProductCategoryApi from '@/api/mall/product/category'
import { propTypes } from '@/utils/propTypes' import { getSimpleBrandList } from '@/api/mall/product/brand'
import { copyValueToTarget } from '@/utils'
const message = useMessage() // const message = useMessage() //
@ -135,15 +146,20 @@ const propertyList = ref([]) // 商品属性列表
const addAttribute = (property: any) => { const addAttribute = (property: any) => {
Array.isArray(property) ? (propertyList.value = property) : propertyList.value.push(property) Array.isArray(property) ? (propertyList.value = property) : propertyList.value.push(property)
} }
/** 调用 SkuList generateTableData 方法*/
// const generateSkus(propertyList){
// skuList.value.generateTableData()
// }
const formData = reactive<SpuType>({ const formData = reactive<SpuType>({
name: '', // name: '', //
categoryId: undefined, // categoryId: null, //
keyword: '', // keyword: '', //
unit: '', // unit: '', //
picUrl: '', // picUrl: '', //
sliderPicUrls: [], // sliderPicUrls: [], //
introduction: '', // introduction: '', //
deliveryTemplateId: 1, // deliveryTemplateId: 1, //
brandId: null, //
specType: false, // specType: false, //
subCommissionType: false, // subCommissionType: false, //
skus: [] skus: []
@ -157,6 +173,7 @@ const rules = reactive({
picUrl: [required], picUrl: [required],
sliderPicUrls: [required], sliderPicUrls: [required],
// deliveryTemplateId: [required], // deliveryTemplateId: [required],
brandId: [required],
specType: [required], specType: [required],
subCommissionType: [required] subCommissionType: [required]
}) })
@ -232,10 +249,13 @@ const onChangeSpec = () => {
] ]
} }
const categoryList = ref() // const categoryList = ref([]) //
const brandList = ref([]) //
onMounted(async () => { onMounted(async () => {
// //
const data = await ProductCategoryApi.getCategoryList({}) const data = await ProductCategoryApi.getCategoryList({})
categoryList.value = handleTree(data, 'id', 'parentId') categoryList.value = handleTree(data, 'id', 'parentId')
//
brandList.value = await getSimpleBrandList()
}) })
</script> </script>

View File

@ -34,17 +34,29 @@
<!-- TODO @puhui999用户输入的时候是按照元分主要是我们自己用fix --> <!-- TODO @puhui999用户输入的时候是按照元分主要是我们自己用fix -->
<el-table-column align="center" label="销售价(元)" min-width="168"> <el-table-column align="center" label="销售价(元)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.price" :min="0" class="w-100%" /> <el-input-number v-model="row.price" :min="0" :precision="2" :step="0.1" class="w-100%" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="市场价(元)" min-width="168"> <el-table-column align="center" label="市场价(元)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.marketPrice" :min="0" class="w-100%" /> <el-input-number
v-model="row.marketPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="成本价(元)" min-width="168"> <el-table-column align="center" label="成本价(元)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.costPrice" :min="0" class="w-100%" /> <el-input-number
v-model="row.costPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="库存" min-width="168"> <el-table-column align="center" label="库存" min-width="168">
@ -54,42 +66,54 @@
</el-table-column> </el-table-column>
<el-table-column align="center" label="重量(kg)" min-width="168"> <el-table-column align="center" label="重量(kg)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.weight" :min="0" class="w-100%" /> <el-input-number v-model="row.weight" :min="0" :precision="2" :step="0.1" class="w-100%" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="体积(m^3)" min-width="168"> <el-table-column align="center" label="体积(m^3)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.volume" :min="0" class="w-100%" /> <el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
</template> </template>
</el-table-column> </el-table-column>
<template v-if="formData.subCommissionType"> <template v-if="formData.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="168"> <el-table-column align="center" label="一级返佣(元)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.subCommissionFirstPrice" :min="0" class="w-100%" /> <el-input-number
v-model="row.subCommissionFirstPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="二级返佣(元)" min-width="168"> <el-table-column align="center" label="二级返佣(元)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.subCommissionSecondPrice" :min="0" class="w-100%" /> <el-input-number
v-model="row.subCommissionSecondPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template> </template>
</el-table-column> </el-table-column>
</template> </template>
<el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80"> <el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80">
<template #default> <template #default="{ row }">
<el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd"> <el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
批量添加 批量添加
</el-button> </el-button>
<el-button v-else link size="small" type="primary">删除</el-button> <el-button v-else link size="small" type="primary" @click="deleteSku(row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</template> </template>
<script lang="ts" name="SkuList" setup> <script lang="ts" name="SkuList" setup>
import { UploadImg } from '@/components/UploadFile'
import { PropType } from 'vue' import { PropType } from 'vue'
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu'
import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { UploadImg } from '@/components/UploadFile'
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu'
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
@ -124,7 +148,14 @@ const batchAdd = () => {
copyValueToTarget(item, skuList.value[0]) copyValueToTarget(item, skuList.value[0])
}) })
} }
/** 删除 sku */
const deleteSku = (row) => {
const index = formData.value.skus.findIndex(
//
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
formData.value.skus.splice(index, 1)
}
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // const tableHeaders = ref<{ prop: string; label: string }[]>([]) //
/** /**
@ -142,11 +173,11 @@ watch(
} }
) )
// TODO @ chatgpt fix // TODO @ chatgpt fix:
/** 生成表数据 */ /** 生成表数据 */
const generateTableData = (data: any[]) => { const generateTableData = (propertyList: any[]) => {
// fix: 使mapfor // fix: 使mapfor
const propertiesItemList = data.map((item) => const propertiesItemList = propertyList.map((item) =>
item.values.map((v) => ({ item.values.map((v) => ({
propertyId: item.id, propertyId: item.id,
propertyName: item.name, propertyName: item.name,
@ -155,19 +186,14 @@ const generateTableData = (data: any[]) => {
})) }))
) )
const buildList = build(propertiesItemList) const buildList = build(propertiesItemList)
// sku, // sku skus
// fix: if (!validateData(propertyList)) {
if ( // sku
buildList.length === formData.value.skus.length || formData.value!.skus = []
data.some((item) => item.values.length === 0)
) {
return
} }
// for (const item of buildList) {
formData.value!.skus = []
buildList.forEach((item) => {
const row = { const row = {
properties: Array.isArray(item) ? item : [item], properties: Array.isArray(item) ? item : [item], // property
price: 0, price: 0,
marketPrice: 0, marketPrice: 0,
costPrice: 0, costPrice: 0,
@ -179,8 +205,36 @@ const generateTableData = (data: any[]) => {
subCommissionFirstPrice: 0, subCommissionFirstPrice: 0,
subCommissionSecondPrice: 0 subCommissionSecondPrice: 0
} }
const index = formData.value!.skus.findIndex(
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
// sku
if (index !== -1) {
continue
}
/**
* TODO 有一个问题回显数据时已删除的 sku 会被重新添加暂时没想到好办法保存时先手动重新删除一下因为是一条空数据很好辨别 不手动删也没是提交表单时会检测删除空sku来兜底
*
*/
formData.value.skus.push(row) formData.value.skus.push(row)
}) }
}
/**
* 生成 skus 前置校验
*/
const validateData = (propertyList: any[]) => {
const skuPropertyIds = []
formData.value.skus.forEach((sku) =>
sku.properties
?.map((property) => property.propertyId)
.forEach((propertyId) => {
if (skuPropertyIds.indexOf(propertyId) === -1) {
skuPropertyIds.push(propertyId)
}
})
)
const propertyIds = propertyList.map((item) => item.id)
return skuPropertyIds.length === propertyIds.length
} }
/** 构建所有排列组合 */ /** 构建所有排列组合 */
const build = (propertyValuesList: Property[][]) => { const build = (propertyValuesList: Property[][]) => {
@ -237,6 +291,10 @@ watch(
// nameindex // nameindex
tableHeaders.value.push({ prop: `name${index}`, label: item.name }) tableHeaders.value.push({ prop: `name${index}`, label: item.name })
}) })
// sku
if (validateData(propertyList)) return
//
if (propertyList.some((item) => item.values.length === 0)) return
generateTableData(propertyList) generateTableData(propertyList)
}, },
{ {

View File

@ -75,10 +75,10 @@
<template #default="{ row }"> <template #default="{ row }">
<el-form class="demo-table-expand" inline label-position="left"> <el-form class="demo-table-expand" inline label-position="left">
<el-form-item label="市场价:"> <el-form-item label="市场价:">
<span>{{ row.marketPrice }}</span> <span>{{ formatToFraction(row.marketPrice) }}</span>
</el-form-item> </el-form-item>
<el-form-item label="成本价:"> <el-form-item label="成本价:">
<span>{{ row.costPrice }}</span> <span>{{ formatToFraction(row.costPrice) }}</span>
</el-form-item> </el-form-item>
<el-form-item label="虚拟销量:"> <el-form-item label="虚拟销量:">
<span>{{ row.virtualSalesCount }}</span> <span>{{ row.virtualSalesCount }}</span>
@ -99,7 +99,11 @@
</el-table-column> </el-table-column>
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" /> <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
<!-- TODO 价格 / 100.0 --> <!-- TODO 价格 / 100.0 -->
<el-table-column align="center" label="商品售价" min-width="90" prop="price" /> <el-table-column align="center" label="商品售价" min-width="90" prop="price">
<template #default="{ row }">
{{ formatToFraction(row.price) }}
</template>
</el-table-column>
<el-table-column align="center" label="销量" min-width="90" prop="salesCount" /> <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
<el-table-column align="center" label="库存" min-width="90" prop="stock" /> <el-table-column align="center" label="库存" min-width="90" prop="stock" />
<el-table-column align="center" label="排序" min-width="70" prop="sort" /> <el-table-column align="center" label="排序" min-width="70" prop="sort" />
@ -129,9 +133,12 @@
</template> </template>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" fixed="right" label="操作" min-width="150"> <el-table-column align="center" fixed="right" label="操作" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
<!-- TODO @puhui999详情可以后面点做哈 --> <!-- TODO @puhui999详情可以后面点做哈 -->
<el-button v-hasPermi="['product:spu:update']" link type="primary" @click="openDetail">
详情
</el-button>
<template v-if="queryParams.tabType === 4"> <template v-if="queryParams.tabType === 4">
<el-button <el-button
v-hasPermi="['product:spu:delete']" v-hasPermi="['product:spu:delete']"
@ -151,7 +158,9 @@
</el-button> </el-button>
</template> </template>
<template v-else> <template v-else>
<!-- 只有不是上架和回收站的商品可以编辑 -->
<el-button <el-button
v-if="queryParams.tabType !== 0"
v-hasPermi="['product:spu:update']" v-hasPermi="['product:spu:update']"
link link
type="primary" type="primary"
@ -189,6 +198,7 @@ import { defaultProps, handleTree } from '@/utils/tree'
import { ProductSpuStatusEnum } from '@/utils/constants' import { ProductSpuStatusEnum } from '@/utils/constants'
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
import * as ProductCategoryApi from '@/api/mall/product/category' import * as ProductCategoryApi from '@/api/mall/product/category'
import { formatToFraction } from '@/utils'
const message = useMessage() // const message = useMessage() //
const { t } = useI18n() // const { t } = useI18n() //
@ -357,7 +367,12 @@ const openForm = (id?: number) => {
// //
push('/product/productSpuAdd') push('/product/productSpuAdd')
} }
/**
* 查看商品详情
*/
const openDetail = () => {
message.alert('查看详情未完善!!!')
}
// TODO @puhui999fix: // TODO @puhui999fix:
watch( watch(
() => currentRoute.value, () => currentRoute.value,