feat: 商品详情提交

This commit is contained in:
zc 2025-10-26 10:18:40 +08:00
parent c198ca35ea
commit c0c64aee57
6 changed files with 249 additions and 26 deletions

View File

@ -22,7 +22,7 @@ const login = {
*/
getCommodityList: ['/product/list'], // 获取商品列表
getCommodityDetail: ['/product/detail'], // 获取商品详情
addOrUpdateCommodity: [''] // 修改商品详情
addOrUpdateCommodity: ['/product/insertOrUpadate'] // 修改商品详情
}
export default login

View File

@ -20,7 +20,7 @@
interface Props {
/** 是否允许多选文件 */
multiple?: boolean
size?: 'small' | 'normal'
size?: 'small' | 'default'
/** 接受的文件类型 */
accept?: 'image/*,video/*' | 'image/*' | 'video/*'
/** 按钮文本 */
@ -31,7 +31,7 @@ withDefaults(defineProps<Props>(), {
multiple: false,
accept: 'image/*,video/*',
buttonText: '选择文件',
size: 'normal'
size: 'default'
})
const emit = defineEmits<{

View File

@ -13,7 +13,7 @@ export const useImportFile = (fileList: Ref<PreviewItem[]>) => {
videoUrl: '视频',
subImageUrl: '副图'
}
const curFileType = ref<'mainImageUrl' | 'videoUrl' | 'subImage'>('mainImageUrl')
const curFileType = ref<'mainImageUrl' | 'videoUrl' | 'subImageUrl'>('mainImageUrl')
const currentPathArray = ref<{ name: string; id: number }[]>([{ name: '根目录', id: 0 }])
// 处理文件选择
@ -21,7 +21,7 @@ export const useImportFile = (fileList: Ref<PreviewItem[]>) => {
handleChooseFiles(curFileType.value, files, fileList.value)
}
const onClickChooseResourceBtn = (type: 'mainImageUrl' | 'videoUrl' | 'subImage') => {
const onClickChooseResourceBtn = (type: 'mainImageUrl' | 'videoUrl' | 'subImageUrl') => {
curFileType.value = type
showFileExplorer.value = true
}
@ -34,14 +34,14 @@ export const useImportFile = (fileList: Ref<PreviewItem[]>) => {
}
export const handleChooseFiles = (
fileType: 'mainImageUrl' | 'videoUrl' | 'subImage',
fileType: 'mainImageUrl' | 'videoUrl' | 'subImageUrl',
files: any = [],
fileList: PreviewItem[]
) => {
const fileNameMap = {
mainImageUrl: '主图',
videoUrl: '视频',
subImage: '副图'
subImageUrl: '副图'
}
if (['mainImageUrl', 'videoUrl'].includes(fileType)) {
const index = fileList.findIndex((item: any) => item.fileType === fileType)

View File

@ -4,8 +4,8 @@ export const initConfig = () => {
configData.value = pageConfig({
dialog: [
{
title: { label: '商品标题', class: '!w-full' },
appCategoryIds: { label: 'app类目', slot: 'appCategoryId' },
title: { label: '商品标题', class: '!w-full', rule: true },
appCategoryIds: { label: 'app类目', slot: 'appCategoryId', rule: true },
mainImageUrl: { label: '主图', slot: 'mainFile' },
videoUrl: { label: '视频', slot: 'videoFile' },
subImageUrl: { label: '副图', slot: 'subFile' },

View File

@ -22,7 +22,7 @@
<template #mainFile>
<file-upload-btn
accept="image/*"
size="normal"
size="default"
@change="(val) => handleChooseFiles('mainImageUrl', val, fileList)"
/>
<el-button class="ml-2" @click="onClickChooseResourceBtn('mainImageUrl')"
@ -31,7 +31,7 @@
</template>
<template #videoFile>
<file-upload-btn
size="normal"
size="default"
accept="video/*"
@change="(val) => handleChooseFiles('videoUrl', val, fileList)"
/>
@ -41,12 +41,12 @@
</template>
<template #subFile>
<file-upload-btn
size="normal"
size="default"
multiple
accept="image/*"
@change="(val) => handleChooseFiles('subImage', val, fileList)"
@change="(val) => handleChooseFiles('subImageUrl', val, fileList)"
/>
<el-button class="ml-2" @click="onClickChooseResourceBtn('subImage')"
<el-button class="ml-2" @click="onClickChooseResourceBtn('subImageUrl')"
>资源库导入</el-button
>
</template>
@ -67,7 +67,9 @@
:default-expanded-keys="defaultCheckedNodes.admin"
@check="
(checkedNode: any, checkedStatus: any) =>
handleCheckChange(checkedNode, checkedStatus, 'admin')
handleAfterChangeCategory(
handleCheckChange(checkedNode, checkedStatus, 'admin', $dialog.data)
)
"
:load="handleLoadNode"
lazy
@ -77,6 +79,20 @@
</el-form-item>
<el-form-item label="测试商品">
<el-switch v-model="$dialog.data.isTest" active-value="1" inactive-value="0" />
<el-popover
:visible="visiblePropertyLine"
placement="bottom"
title="请输入属性类型标题"
:width="200"
trigger="click"
>
<template #reference>
<el-button type="primary" class="ml-2 !text-white" @click="visiblePropertyLine = true"
>新增属性类型</el-button
>
</template>
<el-input v-model="inputPropertyLineValue" @keyup.enter="onAddPropertyLine"></el-input>
</el-popover>
</el-form-item>
</el-form>
<div ref="dragRef">
@ -138,7 +154,6 @@
<script setup lang="ts">
import { initConfig } from './config'
import { categoryDataMock } from './mock'
import {
generateSkuList,
usePropertyValue,
@ -151,7 +166,11 @@ import {
defaultCheckedNodes,
handleGetDialogData,
fileList,
TSkuList
TSkuList,
handleGetNewAdminCategoryData,
handleGetNewSkuList,
handleValidFormData,
handleGetSubmitParams
} from './use-method'
import categoryConfig from './category-config.vue'
import wanEditor from './editor.vue'
@ -160,6 +179,7 @@ import resourceReview from '@/components/ResourceReview/index.vue'
import { useImportFile, handleChooseFiles } from './choose-file-method'
import FileExplorerDialog from '@/components/FileExplorerDialog/index.vue'
import { Atrans$DialogRes } from '@/utils/page/config'
import { ElMessage } from 'element-plus'
const route = useRoute()
const $dialog = ref<Atrans$DialogRes>({} as Atrans$DialogRes)
@ -170,12 +190,15 @@ const init = async () => {
$dialog.value.config = data.dialog1
$dialog.value.data = { appCategoryId: '', adminCategoryId: '', isTest: 0, title: '' }
adminCategoryData.value = []
skuList.value = []
if (route.query.id) {
const res = await api.commodity.getCommodityDetail.post!<any>({ productId: route.query.id })
handleGetDialogData(res.data, $dialog, fileList)
adminCategoryData.value = categoryDataMock
adminCategoryData.value = handleGetNewAdminCategoryData(res.data.vvProductPropertyList)
initPropertyData(adminCategoryData.value)
skuList.value = handleGetNewSkuList(res.data.vvSkuList)
} else {
initPropertyData(adminCategoryData.value)
skuList.value = generateSkuList(adminCategoryData.value)
}
}
@ -189,25 +212,55 @@ const {
const adminCategoryData = ref([])
const {
inputValue,
visiblePropertyLine,
inputPropertyLineValue,
InputRefs,
inputVisibles,
initPropertyData,
onAddPropertyValue,
onClosePropertyValue,
handleInputConfirm
handleInputConfirm,
onAddPropertyLine
} = usePropertyValue()
const { dragRef, createDrag } = useDrag(adminCategoryData)
createDrag()
const skuList = ref<TSkuList[]>([])
// sku
const handleAfterChangeCategory = (data: any) => {
console.warn('----- my data is data.vvCategoryPropertyDTOList: ', data)
adminCategoryData.value = data.categoryPropertyList || []
initPropertyData(adminCategoryData.value)
skuList.value = generateSkuList(adminCategoryData.value)
}
const htmlContent = ref('自定义')
const onDeletePropertyValue = (index: number, id: number) =>
onClosePropertyValue(index, id, skuList)
const onSubmit = () => {
const msg = handleValidFormData(
$dialog.value.data,
fileList.value,
adminCategoryData.value,
htmlContent.value,
skuList.value
)
// if (msg) return ElMessage.error(msg)
console.warn('----- my data is htmlContent: ', htmlContent.value)
const params = handleGetSubmitParams(
$dialog.value.data,
fileList.value,
adminCategoryData.value,
htmlContent.value,
skuList.value
)
console.warn('----- my data is params111: ', params)
api.commodity.addOrUpdateCommodity.post!(params).then((res) => {
console.warn('----- my data is res: ', res)
})
}
init()
</script>

View File

@ -27,8 +27,11 @@ export interface TSkuList {
vvSkuPropertyValueList: any[]
}
// 创建sku列表
// 当改变后台类目的时候,重新创建sku列表
export const generateSkuList = (adminCategoryData: CategoryDataMock): TSkuList[] => {
if (adminCategoryData.length === 0) {
return []
}
const result: TSkuList[] = []
const optionGroups: Option[][] = adminCategoryData.map((category) => category.vvPropertyValueList)
@ -69,6 +72,8 @@ export const usePropertyValue = () => {
const InputRefs = ref()
const inputValue = ref('')
const visiblePropertyLine = ref(false)
const inputPropertyLineValue = ref('')
const onAddPropertyValue = (index: number) => {
inputVisibles.value[index] = true
nextTick(() => {
@ -147,11 +152,26 @@ export const usePropertyValue = () => {
console.warn('----- my data is result111: ', result)
return result
}
const onAddPropertyLine = () => {
visiblePropertyLine.value = false
adminCategoryData.push({
id: Math.random(),
categoryId: 17,
categoryName: '',
visiblePropertyLine,
categoryPropertyName: inputPropertyLineValue.value,
vvPropertyValueList: []
})
inputPropertyLineValue.value = ''
}
return {
inputVisibles,
visiblePropertyLine,
inputPropertyLineValue,
InputRefs,
inputValue,
initPropertyData,
onAddPropertyLine,
onAddPropertyValue,
onClosePropertyValue,
handleInputConfirm
@ -194,7 +214,7 @@ export const handleLoadNode = (node: any, resolve: any) => {
label: item.name,
value: item.id,
hasChild: item.hasChild,
disabled: item.hasChild,
disabled: !!item.hasChild,
categoryPropertyList: item.vvCategoryPropertyDTOList
}))
)
@ -209,7 +229,7 @@ export const handleLoadNode2 = (node: any, resolve: any) => {
label: item.name,
value: item.id,
hasChild: item.hasChild,
disabled: item.hasChild,
disabled: !!item.hasChild,
categoryPropertyList: item.vvCategoryPropertyDTOList
}))
)
@ -220,9 +240,15 @@ export const handleLoadNode2 = (node: any, resolve: any) => {
export const fileList = ref<PreviewItem[]>([])
// 选中后台类目和app类目获取父子id
export const defaultCheckedNodes = ref<{ admin: number[]; app: number[] }>({ admin: [], app: [] })
export const handleCheckChange = (_: any, checkedStatus: any, type: 'admin' | 'app', data: any) => {
export const handleCheckChange = (
checkedData: any,
checkedStatus: any,
type: 'admin' | 'app',
data: any
) => {
// checkedStatus: { checkedKeys, checkedNodes, halfCheckedKeys, halfCheckedNodes }
data[type + 'CategoryIds'] = [...checkedStatus.halfCheckedKeys, ...checkedStatus.checkedKeys]
return checkedData
}
interface PreviewItem {
@ -230,6 +256,7 @@ interface PreviewItem {
name: string
resourceUrl: string
type: 'image' | 'video'
fileType: 'mainImageUrl' | 'videoUrl' | 'subImageUrl'
}
// 编辑的时候从接口响应数据提取$dialog数据
@ -246,8 +273,20 @@ export const handleGetDialogData = (
result.isTest = data.isTest
result.title = data.title
const files = [
{ fileName: '主图', name: '主图', resourceUrl: data.mainImageUrl, type: 'image' },
{ fileName: '视频', name: '视频', resourceUrl: data.videoUrl, type: 'video' }
{
fileName: '主图',
name: '主图',
fileType: 'mainImageUrl',
resourceUrl: data.mainImageUrl,
type: 'image'
},
{
fileName: '视频',
name: '视频',
fileType: 'videoUrl',
resourceUrl: data.videoUrl,
type: 'video'
}
]
files.push(
...data.vvProductDetailList
@ -256,6 +295,7 @@ export const handleGetDialogData = (
fileName: '副图',
name: '副图',
resourceUrl: item.resourceUrl,
fileType: 'subImageUrl',
type: 'image'
}))
)
@ -276,3 +316,133 @@ function extractAdminCategoryIds(obj: Record<string, any>): number[] {
.map(([, value]) => value)
.filter((val) => ![undefined, null].includes(val)) as number[]
}
// 编辑的时候对后台类目的数据进行重组
export const handleGetNewAdminCategoryData = (data: any) => {
return data.map((item: any) => ({
...item,
categoryPropertyName: item.productPropertyName,
vvPropertyValueList: item.vvProductPropertyValueList.map((item: any) => ({
...item,
categoryPropertyValue: item.productPropertyValue
}))
}))
}
// 编辑的时候对sku进行重组
export const handleGetNewSkuList = (data: any) => {
return data.map((item: any) => ({
...item,
name: item.vvSkuPropertyValueList.reduce((str: string, cur: any) => {
str += cur.productPropertyValue
return str
}, '')
}))
}
export const handleValidFormData = (
$dialogData: any,
fileList: PreviewItem[],
adminCategoryData: CategoryDataMock,
htmlContent: string,
skuList: TSkuList[]
) => {
console.warn(
'----- my data is fileList, adminCategoryData, skuList: ',
fileList,
adminCategoryData,
skuList
)
if (!$dialogData.title) {
return '请输入商品标题'
}
if (!$dialogData.appCategoryId) {
return '请选择app类目'
}
if (!$dialogData.adminCategoryId) {
return '请选择后台类目'
}
if (fileList.length === 0) {
return '请上传主图'
}
if (fileList.some((item) => item.fileType === 'mainImageUrl' && !item.resourceUrl)) {
return '请上传主图'
}
if (fileList.some((item) => item.fileType === 'videoUrl' && !item.resourceUrl)) {
return '请上传视频'
}
if (fileList.some((item) => item.fileType === 'subImageUrl' && !item.resourceUrl)) {
return '请上传副图'
}
if (adminCategoryData.length === 0) {
return '请添加商品属性'
}
let msg = ''
skuList.some((item) => {
const nameMap = {
stock: '库存',
originPrice: '成本价',
salePrice: '原价',
promotionPrice: '售价'
}
if (!item.stock) {
msg = `${item.name}${nameMap.stock}不能为空`
}
if (item.originPrice) {
msg = `${item.name}${nameMap.originPrice}不能为空`
}
if (!item.salePrice) {
msg = `${item.name}${nameMap.salePrice}不能为空`
}
if (!item.promotionPrice) {
msg = `${item.name}${nameMap.promotionPrice}不能为空`
}
return msg
})
if (msg) return msg
if (!htmlContent) {
return '请填写商品详情'
}
return ''
}
export const handleGetSubmitParams = (
$dialogData: any,
fileList: PreviewItem[],
adminCategoryData: CategoryDataMock,
htmlContent: string,
skuList: TSkuList[]
) => {
return {
...$dialogData,
mainImageUrl: fileList.find((item) => item.fileType === 'mainImageUrl')?.resourceUrl,
videoUrl: fileList.find((item) => item.fileType === 'videoUrl')?.resourceUrl,
appCategoryId1: $dialogData.appCategoryIds?.[0] || defaultCheckedNodes.value.app[0],
appCategoryId2: $dialogData.appCategoryIds?.[1] || defaultCheckedNodes.value.app[1],
...($dialogData.adminCategoryIds || defaultCheckedNodes.value.admin).reduce((acc, cur, i) => {
acc[`adminCategoryId${i + 1}`] = cur
return acc
}, {}),
vvProductDetailList: fileList
.filter((item) => item.fileType === 'subImageUrl')
.map((item) => ({
detail: item.resourceUrl,
type: 1
})),
vvProductPropertyList: adminCategoryData.map((item) => ({
...item,
productPropertyName: item.categoryPropertyName,
vvProductPropertyValueList: item.vvPropertyValueList.map((item) => ({
...item,
productPropertyValue: item.categoryPropertyValue
}))
})),
vvSkuList: skuList.map((item) => ({
...item,
vvSkuPropertyValueList: item.vvSkuPropertyValueList.map((item) => ({
...item,
productPropertyValue: item.categoryPropertyValue
}))
}))
}
}