feat: 商品详情编辑
This commit is contained in:
parent
e1f682b95c
commit
c198ca35ea
@ -1,5 +1,12 @@
|
||||
interface PreviewItem {
|
||||
fileName: string
|
||||
name: string
|
||||
resourceUrl: string
|
||||
type: 'image' | 'video'
|
||||
}
|
||||
|
||||
// 选择资源库图片
|
||||
export const useImportFile = () => {
|
||||
export const useImportFile = (fileList: Ref<PreviewItem[]>) => {
|
||||
const showFileExplorer = ref(false)
|
||||
const fileType = {
|
||||
mainImageUrl: '主图',
|
||||
@ -10,8 +17,8 @@ export const useImportFile = () => {
|
||||
const currentPathArray = ref<{ name: string; id: number }[]>([{ name: '根目录', id: 0 }])
|
||||
|
||||
// 处理文件选择
|
||||
const handleChooseResourceFileCallback = (files: any[] = []) => {
|
||||
handleChooseFiles(curFileType.value, files)
|
||||
const handleChooseResourceFileCallback = (files: PreviewItem[] = []) => {
|
||||
handleChooseFiles(curFileType.value, files, fileList.value)
|
||||
}
|
||||
|
||||
const onClickChooseResourceBtn = (type: 'mainImageUrl' | 'videoUrl' | 'subImage') => {
|
||||
@ -26,10 +33,10 @@ export const useImportFile = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export const fileList = ref<any[]>([])
|
||||
export const handleChooseFiles = (
|
||||
fileType: 'mainImageUrl' | 'videoUrl' | 'subImage',
|
||||
files: any = []
|
||||
files: any = [],
|
||||
fileList: PreviewItem[]
|
||||
) => {
|
||||
const fileNameMap = {
|
||||
mainImageUrl: '主图',
|
||||
@ -37,12 +44,10 @@ export const handleChooseFiles = (
|
||||
subImage: '副图'
|
||||
}
|
||||
if (['mainImageUrl', 'videoUrl'].includes(fileType)) {
|
||||
const index = fileList.value.findIndex((item: any) => item.fileType === fileType)
|
||||
const index = fileList.findIndex((item: any) => item.fileType === fileType)
|
||||
if (index !== -1) {
|
||||
fileList.value.splice(index, 1)
|
||||
fileList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
fileList.value.push(
|
||||
...files.map((item: any) => ({ ...item, fileType, name: fileNameMap[fileType] }))
|
||||
)
|
||||
fileList.push(...files.map((item: any) => ({ ...item, fileType, name: fileNameMap[fileType] })))
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ export const initConfig = () => {
|
||||
dialog: [
|
||||
{
|
||||
title: { label: '商品标题', class: '!w-full' },
|
||||
appCategoryId: { label: 'app类目', slot: 'appCategoryId' },
|
||||
appCategoryIds: { label: 'app类目', slot: 'appCategoryId' },
|
||||
mainImageUrl: { label: '主图', slot: 'mainFile' },
|
||||
videoUrl: { label: '视频', slot: 'videoFile' },
|
||||
subImageUrl: { label: '副图', slot: 'subFile' },
|
||||
|
||||
@ -9,9 +9,10 @@
|
||||
v-model="$dialog.data.appCategoryId"
|
||||
:props="defaultAdminCategoryTreeProps"
|
||||
:render-after-expand="false"
|
||||
:default-expanded-keys="defaultCheckedNodes.app"
|
||||
@check="
|
||||
(checkedNode: any, checkedStatus: any) =>
|
||||
handleCheckChange(checkedNode, checkedStatus, 'app')
|
||||
handleCheckChange(checkedNode, checkedStatus, 'app', $dialog.data)
|
||||
"
|
||||
:load="handleLoadNode2"
|
||||
lazy
|
||||
@ -22,7 +23,7 @@
|
||||
<file-upload-btn
|
||||
accept="image/*"
|
||||
size="normal"
|
||||
@change="(val) => handleChooseFiles('mainImageUrl', val)"
|
||||
@change="(val) => handleChooseFiles('mainImageUrl', val, fileList)"
|
||||
/>
|
||||
<el-button class="ml-2" @click="onClickChooseResourceBtn('mainImageUrl')"
|
||||
>资源库导入</el-button
|
||||
@ -32,7 +33,7 @@
|
||||
<file-upload-btn
|
||||
size="normal"
|
||||
accept="video/*"
|
||||
@change="(val) => handleChooseFiles('videoUrl', val)"
|
||||
@change="(val) => handleChooseFiles('videoUrl', val, fileList)"
|
||||
/>
|
||||
<el-button class="ml-2" @click="onClickChooseResourceBtn('videoUrl')"
|
||||
>资源库导入</el-button
|
||||
@ -43,7 +44,7 @@
|
||||
size="normal"
|
||||
multiple
|
||||
accept="image/*"
|
||||
@change="(val) => handleChooseFiles('subImage', val)"
|
||||
@change="(val) => handleChooseFiles('subImage', val, fileList)"
|
||||
/>
|
||||
<el-button class="ml-2" @click="onClickChooseResourceBtn('subImage')"
|
||||
>资源库导入</el-button
|
||||
@ -60,9 +61,10 @@
|
||||
<el-form-item label="后台类目">
|
||||
<el-tree-select
|
||||
show-checkbox
|
||||
v-model="$dialog.data.categoryId"
|
||||
v-model="$dialog.data.adminCategoryId"
|
||||
:props="defaultAdminCategoryTreeProps"
|
||||
:render-after-expand="false"
|
||||
:default-expanded-keys="defaultCheckedNodes.admin"
|
||||
@check="
|
||||
(checkedNode: any, checkedStatus: any) =>
|
||||
handleCheckChange(checkedNode, checkedStatus, 'admin')
|
||||
@ -78,7 +80,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div ref="dragRef">
|
||||
<dl v-for="(item, index) in categoryData" :key="item.id" class="flex items-center">
|
||||
<dl v-for="(item, index) in adminCategoryData" :key="item.id" class="flex items-center">
|
||||
<dt class="text-sm text-[#666] mb-2">{{ item.categoryPropertyName }}:</dt>
|
||||
<dd class="flex mb-2">
|
||||
<div v-for="child in item.vvPropertyValueList" :key="item.id + '-' + child.id">
|
||||
@ -146,44 +148,58 @@ import {
|
||||
handleLoadNode2,
|
||||
handleCheckChange,
|
||||
defaultAdminCategoryTreeProps,
|
||||
checkedNodes
|
||||
defaultCheckedNodes,
|
||||
handleGetDialogData,
|
||||
fileList,
|
||||
TSkuList
|
||||
} from './use-method'
|
||||
import categoryConfig from './category-config.vue'
|
||||
import wanEditor from './editor.vue'
|
||||
import fileUploadBtn from '@/components/FileUploadBtn/index.vue'
|
||||
import resourceReview from '@/components/ResourceReview/index.vue'
|
||||
import { useImportFile, fileList, handleChooseFiles } from './choose-file-method'
|
||||
import { useImportFile, handleChooseFiles } from './choose-file-method'
|
||||
import FileExplorerDialog from '@/components/FileExplorerDialog/index.vue'
|
||||
import { Atrans$DialogRes } from '@/utils/page/config'
|
||||
|
||||
const route = useRoute()
|
||||
const $dialog = ref<Atrans$DialogRes>({} as Atrans$DialogRes)
|
||||
const init = async () => {
|
||||
checkedNodes.value = { admin: [], app: [] }
|
||||
await api.commodity.getCommodityDetail.post!({ productId: route.query.id })
|
||||
defaultCheckedNodes.value = { admin: [], app: [] }
|
||||
const data = initConfig().value!
|
||||
$dialog.value = data.$dialog
|
||||
$dialog.value.config = data.dialog1
|
||||
$dialog.value.data = { appCategoryId: '', adminCategoryId: '', isTest: 0, title: '' }
|
||||
adminCategoryData.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
|
||||
initPropertyData(adminCategoryData.value)
|
||||
skuList.value = generateSkuList(adminCategoryData.value)
|
||||
}
|
||||
}
|
||||
|
||||
const { $dialog, dialog1 } = toRefs(initConfig().value!)
|
||||
$dialog.value.config = dialog1.value
|
||||
|
||||
const {
|
||||
showFileExplorer,
|
||||
currentPathArray,
|
||||
handleChooseResourceFileCallback,
|
||||
onClickChooseResourceBtn
|
||||
} = useImportFile()
|
||||
} = useImportFile(fileList)
|
||||
|
||||
const categoryData = ref(categoryDataMock)
|
||||
const adminCategoryData = ref([])
|
||||
const {
|
||||
inputValue,
|
||||
InputRefs,
|
||||
inputVisibles,
|
||||
initPropertyData,
|
||||
onAddPropertyValue,
|
||||
onClosePropertyValue,
|
||||
handleInputConfirm
|
||||
} = usePropertyValue(categoryData.value)
|
||||
} = usePropertyValue()
|
||||
|
||||
const { dragRef, createDrag } = useDrag(categoryData)
|
||||
const { dragRef, createDrag } = useDrag(adminCategoryData)
|
||||
createDrag()
|
||||
const skuList = generateSkuList(categoryData.value)
|
||||
const skuList = ref<TSkuList[]>([])
|
||||
|
||||
const htmlContent = ref('自定义')
|
||||
|
||||
|
||||
@ -207,6 +207,152 @@ export const categoryDataMock = [
|
||||
}
|
||||
]
|
||||
|
||||
/* export const categoryDataMock = [
|
||||
{
|
||||
id: 7,
|
||||
isDelete: 0,
|
||||
createTime: 1755940247000,
|
||||
modifyTime: 1755940247000,
|
||||
productId: 5,
|
||||
productPropertyName: '珍珠直径',
|
||||
defalutSort: 1,
|
||||
vvProductPropertyValueList: [
|
||||
{
|
||||
id: 21,
|
||||
isDelete: 0,
|
||||
createTime: 1755940416000,
|
||||
modifyTime: 1755940416000,
|
||||
productId: 5,
|
||||
productPropertyName: '珍珠直径',
|
||||
productPropertyValue: '7-8mm',
|
||||
productPropertyId: 7,
|
||||
defalutSort: 1
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
isDelete: 0,
|
||||
createTime: 1755940416000,
|
||||
modifyTime: 1755940416000,
|
||||
productId: 5,
|
||||
productPropertyName: '珍珠直径',
|
||||
productPropertyValue: '8-9mm',
|
||||
productPropertyId: 7,
|
||||
defalutSort: 2
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
isDelete: 0,
|
||||
createTime: 1755940416000,
|
||||
modifyTime: 1755940416000,
|
||||
productId: 5,
|
||||
productPropertyName: '珍珠直径',
|
||||
productPropertyValue: '9-10mm',
|
||||
productPropertyId: 7,
|
||||
defalutSort: 3
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
isDelete: 0,
|
||||
createTime: 1755940416000,
|
||||
modifyTime: 1755940416000,
|
||||
productId: 5,
|
||||
productPropertyName: '珍珠直径',
|
||||
productPropertyValue: '10-11mm',
|
||||
productPropertyId: 7,
|
||||
defalutSort: 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
isDelete: 0,
|
||||
createTime: 1755940247000,
|
||||
modifyTime: 1755940247000,
|
||||
productId: 5,
|
||||
productPropertyName: '项链长度',
|
||||
defalutSort: 2,
|
||||
vvProductPropertyValueList: [
|
||||
{
|
||||
id: 17,
|
||||
isDelete: 0,
|
||||
createTime: 1755940428000,
|
||||
modifyTime: 1755940428000,
|
||||
productId: 5,
|
||||
productPropertyName: '项链长度',
|
||||
productPropertyValue: '43cm',
|
||||
productPropertyId: 6,
|
||||
defalutSort: 1
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
isDelete: 0,
|
||||
createTime: 1755940519000,
|
||||
modifyTime: 1755940519000,
|
||||
productId: 5,
|
||||
productPropertyName: '项链长度',
|
||||
productPropertyValue: '45cm',
|
||||
productPropertyId: 6,
|
||||
defalutSort: 2
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
isDelete: 0,
|
||||
createTime: 1755940519000,
|
||||
modifyTime: 1755940519000,
|
||||
productId: 5,
|
||||
productPropertyName: '项链长度',
|
||||
productPropertyValue: '47cm',
|
||||
productPropertyId: 6,
|
||||
defalutSort: 3
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
isDelete: 0,
|
||||
createTime: 1755940519000,
|
||||
modifyTime: 1755940519000,
|
||||
productId: 5,
|
||||
productPropertyName: '项链长度',
|
||||
productPropertyValue: '50cm',
|
||||
productPropertyId: 6,
|
||||
defalutSort: 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
isDelete: 0,
|
||||
createTime: 1755940247000,
|
||||
modifyTime: 1755940247000,
|
||||
productId: 5,
|
||||
productPropertyName: '颜色分类',
|
||||
defalutSort: 3,
|
||||
vvProductPropertyValueList: [
|
||||
{
|
||||
id: 13,
|
||||
isDelete: 0,
|
||||
createTime: 1755940570000,
|
||||
modifyTime: 1755940570000,
|
||||
productId: 5,
|
||||
productPropertyName: '颜色分类',
|
||||
productPropertyValue: '【极光白透粉】大几乎无瑕大配证书',
|
||||
productPropertyId: 5,
|
||||
defalutSort: 1
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
isDelete: 0,
|
||||
createTime: 1755940570000,
|
||||
modifyTime: 1755940570000,
|
||||
productId: 5,
|
||||
productPropertyName: '颜色分类',
|
||||
productPropertyValue: '【极光冷白】大几乎无瑕大配证书',
|
||||
productPropertyId: 5,
|
||||
defalutSort: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
] */
|
||||
|
||||
//
|
||||
export const a = [
|
||||
{
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Atrans$DialogRes } from '@/utils/page/config'
|
||||
import { useDraggable } from 'vue-draggable-plus'
|
||||
// 定义源数据的结构类型
|
||||
interface Option {
|
||||
@ -15,7 +16,7 @@ interface Category {
|
||||
type CategoryDataMock = Category[]
|
||||
|
||||
// 定义目标数据的结构类型
|
||||
interface TSkuList {
|
||||
export interface TSkuList {
|
||||
serialNo: string
|
||||
name: string
|
||||
salePrice: string
|
||||
@ -27,9 +28,9 @@ interface TSkuList {
|
||||
}
|
||||
|
||||
// 创建sku列表
|
||||
export const generateSkuList = (categoryData: CategoryDataMock): Ref<TSkuList[]> => {
|
||||
export const generateSkuList = (adminCategoryData: CategoryDataMock): TSkuList[] => {
|
||||
const result: TSkuList[] = []
|
||||
const optionGroups: Option[][] = categoryData.map((category) => category.vvPropertyValueList)
|
||||
const optionGroups: Option[][] = adminCategoryData.map((category) => category.vvPropertyValueList)
|
||||
|
||||
// 使用递归生成所有可能的组合
|
||||
const combine = (index: number, current: Option[]) => {
|
||||
@ -54,24 +55,29 @@ export const generateSkuList = (categoryData: CategoryDataMock): Ref<TSkuList[]>
|
||||
}
|
||||
|
||||
combine(0, [])
|
||||
return ref(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// 增加属性、删除属性
|
||||
export const usePropertyValue = (categoryData: CategoryDataMock) => {
|
||||
const inputVisibles = ref(Array.from({ length: categoryData.length }, (_) => false))
|
||||
export const usePropertyValue = () => {
|
||||
let adminCategoryData: CategoryDataMock = []
|
||||
const inputVisibles = ref<boolean[]>([])
|
||||
const initPropertyData = (initData: CategoryDataMock) => {
|
||||
inputVisibles.value = Array.from({ length: initData.length }, (_) => false)
|
||||
adminCategoryData = initData
|
||||
}
|
||||
|
||||
const InputRefs = ref()
|
||||
const inputValue = ref('')
|
||||
const onAddPropertyValue = (index: number) => {
|
||||
inputVisibles.value[index] = true
|
||||
nextTick(() => {
|
||||
InputRefs.value[index].input!.focus()
|
||||
InputRefs.value[0].input!.focus()
|
||||
})
|
||||
}
|
||||
const onClosePropertyValue = (index: number, id: number, skuList: Ref<TSkuList[]>) => {
|
||||
const i = categoryData[index].vvPropertyValueList.findIndex((item: any) => item.id === id)
|
||||
categoryData[index].vvPropertyValueList.splice(i, 1)
|
||||
const i = adminCategoryData[index].vvPropertyValueList.findIndex((item: any) => item.id === id)
|
||||
adminCategoryData[index].vvPropertyValueList.splice(i, 1)
|
||||
const deleteIndexs: number[] = []
|
||||
skuList.value.forEach((item: TSkuList, skuIndex: number) => {
|
||||
if (item.vvSkuPropertyValueList.find((it) => it.id === id)) {
|
||||
@ -86,7 +92,7 @@ export const usePropertyValue = (categoryData: CategoryDataMock) => {
|
||||
const handleInputConfirm = (lineIndex: number, skuList: TSkuList[] = []) => {
|
||||
if (inputValue.value) {
|
||||
const id = Math.random()
|
||||
categoryData[lineIndex].vvPropertyValueList.push({
|
||||
adminCategoryData[lineIndex].vvPropertyValueList.push({
|
||||
id,
|
||||
categoryPropertyValue: inputValue.value,
|
||||
isAdd: true
|
||||
@ -95,7 +101,7 @@ export const usePropertyValue = (categoryData: CategoryDataMock) => {
|
||||
...generateAddSkuList(
|
||||
lineIndex,
|
||||
{ id, categoryPropertyValue: inputValue.value },
|
||||
categoryData
|
||||
adminCategoryData
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -107,16 +113,16 @@ export const usePropertyValue = (categoryData: CategoryDataMock) => {
|
||||
const generateAddSkuList = (
|
||||
lineIndex: number,
|
||||
addData: Option,
|
||||
categoryData: CategoryDataMock
|
||||
adminCategoryData: CategoryDataMock
|
||||
): TSkuList[] => {
|
||||
const result: TSkuList[] = []
|
||||
const optionGroups: Option[][] = categoryData
|
||||
const optionGroups: Option[][] = adminCategoryData
|
||||
.filter((_, index) => index !== lineIndex)
|
||||
.map((category) => category.vvPropertyValueList)
|
||||
|
||||
// 使用递归生成所有可能的组合
|
||||
const combine = (index: number, current: Option[]) => {
|
||||
if (index === categoryData.length) {
|
||||
if (index === optionGroups.length) {
|
||||
const serialNo = current.map((opt) => opt.id.toString()).join('-')
|
||||
const name = current.map((opt) => opt.categoryPropertyValue).join('-')
|
||||
result.push({
|
||||
@ -136,13 +142,16 @@ export const usePropertyValue = (categoryData: CategoryDataMock) => {
|
||||
}
|
||||
}
|
||||
|
||||
combine(1, [addData])
|
||||
combine(0, [addData])
|
||||
//bug
|
||||
console.warn('----- my data is result111: ', result)
|
||||
return result
|
||||
}
|
||||
return {
|
||||
inputVisibles,
|
||||
InputRefs,
|
||||
inputValue,
|
||||
initPropertyData,
|
||||
onAddPropertyValue,
|
||||
onClosePropertyValue,
|
||||
handleInputConfirm
|
||||
@ -207,9 +216,63 @@ export const handleLoadNode2 = (node: any, resolve: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const checkedNodes = ref<{ admin: number[]; app: number[] }>({ admin: [], app: [] })
|
||||
export const handleCheckChange = (_: any, checkedStatus: any, type: 'admin' | 'app') => {
|
||||
// 图片预览列表
|
||||
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) => {
|
||||
// checkedStatus: { checkedKeys, checkedNodes, halfCheckedKeys, halfCheckedNodes }
|
||||
checkedNodes.value[type] = [...checkedStatus.halfCheckedKeys, ...checkedStatus.checkedKeys]
|
||||
console.warn('----- my data is checkedNodes: ', checkedNodes)
|
||||
data[type + 'CategoryIds'] = [...checkedStatus.halfCheckedKeys, ...checkedStatus.checkedKeys]
|
||||
}
|
||||
|
||||
interface PreviewItem {
|
||||
fileName: string
|
||||
name: string
|
||||
resourceUrl: string
|
||||
type: 'image' | 'video'
|
||||
}
|
||||
|
||||
// 编辑的时候从接口响应数据提取$dialog数据
|
||||
export const handleGetDialogData = (
|
||||
data: any,
|
||||
$dialog: Ref<Atrans$DialogRes>,
|
||||
fileList: Ref<PreviewItem[]>
|
||||
) => {
|
||||
const result = { ...$dialog.value.data }
|
||||
result.appCategoryId = data.appCategoryId2
|
||||
result.adminCategoryId = data.adminCategoryId
|
||||
defaultCheckedNodes.value.admin = extractAdminCategoryIds(data)
|
||||
defaultCheckedNodes.value.app = [data.appCategoryId1, data.appCategoryId2]
|
||||
result.isTest = data.isTest
|
||||
result.title = data.title
|
||||
const files = [
|
||||
{ fileName: '主图', name: '主图', resourceUrl: data.mainImageUrl, type: 'image' },
|
||||
{ fileName: '视频', name: '视频', resourceUrl: data.videoUrl, type: 'video' }
|
||||
]
|
||||
files.push(
|
||||
...data.vvProductDetailList
|
||||
.filter((item: any) => item.type === 1)
|
||||
.map((item: any) => ({
|
||||
fileName: '副图',
|
||||
name: '副图',
|
||||
resourceUrl: item.resourceUrl,
|
||||
type: 'image'
|
||||
}))
|
||||
)
|
||||
fileList.value = files as PreviewItem[]
|
||||
$dialog.value.data = result
|
||||
}
|
||||
|
||||
// 将adminCategoryId1, adminCategoryId2, ... 提取出来并排序
|
||||
function extractAdminCategoryIds(obj: Record<string, any>): number[] {
|
||||
return Object.entries(obj)
|
||||
.filter(([key]) => key.startsWith('adminCategoryId'))
|
||||
.sort(([keyA], [keyB]) => {
|
||||
// 按数字部分排序 (adminCategoryId1 < adminCategoryId2)
|
||||
const numA = parseInt(keyA.replace('adminCategoryId', ''))
|
||||
const numB = parseInt(keyB.replace('adminCategoryId', ''))
|
||||
return numA - numB
|
||||
})
|
||||
.map(([, value]) => value)
|
||||
.filter((val) => ![undefined, null].includes(val)) as number[]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user