feat: 视频保存
This commit is contained in:
@@ -88,8 +88,11 @@
|
||||
fetchUpdateListing
|
||||
} from "./api"
|
||||
import type {
|
||||
CoverSourceType,
|
||||
CropType,
|
||||
ListingDetailImage,
|
||||
ListingDetailResponse,
|
||||
ListingImageCategory,
|
||||
ListingItem,
|
||||
ProductMediaItem,
|
||||
RadioOption,
|
||||
@@ -124,6 +127,7 @@
|
||||
desc: "",
|
||||
gender: "FEMALE",
|
||||
category: null,
|
||||
coverFrom: "sketch",
|
||||
firstSelectedIndex: null,
|
||||
prodImageList: [],
|
||||
sketchList: []
|
||||
@@ -170,14 +174,48 @@
|
||||
return [...images].sort((prev, next) => (prev.sortOrder ?? 0) - (next.sortOrder ?? 0))
|
||||
}
|
||||
|
||||
const getImageSelected = (value: ListingDetailImage["isSelected"]) =>
|
||||
value === true || value === 1 || value === "1"
|
||||
const getImageSelected = (value: ListingDetailImage["isSelected"]) => {
|
||||
if (value === true || value === 1 || value === "1") return true
|
||||
if (typeof value === "string") return value.toLowerCase() === "true"
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const getDetailImageSelected = (image: ListingDetailImage) =>
|
||||
getImageSelected(image.isSelected) ||
|
||||
getImageSelected(image.isSeleted) ||
|
||||
getImageSelected(image.selected)
|
||||
|
||||
const normalizeCoverSource = (value: unknown): CoverSourceType =>
|
||||
value === "mainProductImage" ? "mainProductImage" : "sketch"
|
||||
|
||||
const isCoverSource = (value: unknown): value is CoverSourceType =>
|
||||
value === "sketch" || value === "mainProductImage"
|
||||
|
||||
const resolveCoverSourceFromImageUrl = (
|
||||
imageUrl: string,
|
||||
listing: ListingItem
|
||||
): CoverSourceType => {
|
||||
if (imageUrl === listing.mainProductImage) return "mainProductImage"
|
||||
if (imageUrl === listing.sketch) return "sketch"
|
||||
|
||||
return normalizeCoverSource(imageUrl)
|
||||
}
|
||||
|
||||
const videoImageCategories = ["firstFrame", "gif", "video"] as const
|
||||
type VideoImageCategory = (typeof videoImageCategories)[number]
|
||||
|
||||
const isVideoImageCategory = (category: ListingDetailImage["category"]): category is VideoImageCategory =>
|
||||
videoImageCategories.includes(category as VideoImageCategory)
|
||||
|
||||
const normalizeDetailGender = (value: ListingDetailResponse["designFor"]) => {
|
||||
const gender = String(value || "").toUpperCase()
|
||||
return gender === "MALE" || gender === "FEMALE" ? gender : "FEMALE"
|
||||
}
|
||||
|
||||
const getListingDesignFor = (gender: ListingItem["gender"]): "male" | "female" =>
|
||||
gender === "MALE" ? "male" : "female"
|
||||
|
||||
const normalizeDetailCategory = (
|
||||
value: ListingDetailResponse["productCategory"]
|
||||
): ListingItem["category"] => {
|
||||
@@ -191,6 +229,17 @@
|
||||
|
||||
const createListingItemFromDetail = (detail: ListingDetailResponse): ListingItem => {
|
||||
const listing = createListingItem()
|
||||
let coverFromImageUrl = ""
|
||||
const videoGroupMap = new Map<
|
||||
number,
|
||||
{
|
||||
sortOrder: number
|
||||
firstFrameUrl?: string
|
||||
gifUrl?: string
|
||||
videoUrl?: string
|
||||
selected: boolean
|
||||
}
|
||||
>()
|
||||
|
||||
listing.productName = detail.title || ""
|
||||
listing.price =
|
||||
@@ -200,6 +249,11 @@
|
||||
listing.category = normalizeDetailCategory(detail.productCategory)
|
||||
|
||||
getSortedDetailImages(detail.images || []).forEach((image) => {
|
||||
if (image.category === "cover_from") {
|
||||
coverFromImageUrl = image.imageUrl || ""
|
||||
return
|
||||
}
|
||||
|
||||
const imageUrl = image.imageUrl || ""
|
||||
if (!imageUrl) return
|
||||
|
||||
@@ -213,7 +267,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (image.category === "mainProductImage") {
|
||||
if (image.category === "mainProductImage" || image.category === "main_product") {
|
||||
listing.mainProductImage = imageUrl
|
||||
return
|
||||
}
|
||||
@@ -221,22 +275,57 @@
|
||||
if (image.category === "product") {
|
||||
listing.prodImageList.push({
|
||||
url: imageUrl,
|
||||
selected: getImageSelected(image.isSelected)
|
||||
selected: getDetailImageSelected(image)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (isVideoImageCategory(image.category)) {
|
||||
if (image.sortOrder === null || typeof image.sortOrder === "undefined") return
|
||||
|
||||
const sortOrder = Number(image.sortOrder)
|
||||
if (!Number.isFinite(sortOrder)) return
|
||||
|
||||
const group = videoGroupMap.get(sortOrder) || {
|
||||
sortOrder,
|
||||
selected: false
|
||||
}
|
||||
|
||||
if (image.category === "firstFrame") group.firstFrameUrl = imageUrl
|
||||
if (image.category === "gif") group.gifUrl = imageUrl
|
||||
if (image.category === "video") group.videoUrl = imageUrl
|
||||
group.selected = group.selected || getDetailImageSelected(image)
|
||||
videoGroupMap.set(sortOrder, group)
|
||||
return
|
||||
}
|
||||
|
||||
if (image.category === "apparel") {
|
||||
listing.sketchList.push({ url: imageUrl })
|
||||
}
|
||||
})
|
||||
|
||||
Array.from(videoGroupMap.values())
|
||||
.sort((prev, next) => prev.sortOrder - next.sortOrder)
|
||||
.forEach((video) => {
|
||||
const videoItem = createProductVideoItem(video, video.selected)
|
||||
if (videoItem) listing.prodImageList.push(videoItem)
|
||||
})
|
||||
|
||||
if (!listing.mainProductImage) {
|
||||
listing.mainProductImage =
|
||||
listing.prodImageList.find((item) => item.selected)?.url || ""
|
||||
listing.prodImageList.find((item) => item.selected && !item.isVideo)?.url || ""
|
||||
}
|
||||
if (coverFromImageUrl) {
|
||||
listing.coverFrom = resolveCoverSourceFromImageUrl(coverFromImageUrl, listing)
|
||||
}
|
||||
|
||||
const selectedIndex = listing.prodImageList.findIndex((item) => item.selected)
|
||||
const mainProductIndex = listing.prodImageList.findIndex(
|
||||
(item) => !item.isVideo && item.url === listing.mainProductImage
|
||||
)
|
||||
const selectedIndex =
|
||||
mainProductIndex === -1
|
||||
? listing.prodImageList.findIndex((item) => item.selected && !item.isVideo)
|
||||
: mainProductIndex
|
||||
listing.firstSelectedIndex = selectedIndex === -1 ? null : selectedIndex
|
||||
|
||||
listing.productImage = listing.prodImageList.map((item) => item.url)
|
||||
@@ -247,7 +336,10 @@
|
||||
return listing
|
||||
}
|
||||
|
||||
const createProductVideoItem = (video: SketchDetailVideo): ProductMediaItem | null => {
|
||||
const createProductVideoItem = (
|
||||
video: SketchDetailVideo,
|
||||
selected = false
|
||||
): ProductMediaItem | null => {
|
||||
const firstFrameUrl = video?.firstFrameUrl || ""
|
||||
const gifUrl = video?.gifUrl || ""
|
||||
const videoUrl = video?.videoUrl || ""
|
||||
@@ -260,7 +352,7 @@
|
||||
gifUrl,
|
||||
videoUrl,
|
||||
isVideo: true,
|
||||
selected: false
|
||||
selected
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,25 +379,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
const cropType = ref("")
|
||||
const handleClickCrop = (data: any, type: string, paramThree: any = []) => {
|
||||
const getCoverOriginList = (item: ListingItem) => {
|
||||
const origin: Array<{ type: CoverSourceType; url: string }> = []
|
||||
if (item.sketch) {
|
||||
origin.push({ type: "sketch", url: item.sketch })
|
||||
}
|
||||
if (item.mainProductImage) {
|
||||
origin.push({ type: "mainProductImage", url: item.mainProductImage })
|
||||
}
|
||||
|
||||
return origin
|
||||
}
|
||||
|
||||
const cropType = ref<CropType | "">("")
|
||||
const handleClickCrop = (data: string | null, type: CropType, paramThree: number | unknown[] = []) => {
|
||||
// 处理来自TopImageSection的调用: (data, type, list)
|
||||
// 处理来自ApparelSketchList的调用: (data, type, index)
|
||||
const index = typeof paramThree === "number" ? paramThree : undefined
|
||||
const list = Array.isArray(paramThree) ? paramThree : []
|
||||
|
||||
// console.log(data, type)
|
||||
// console.log(selectList.value[currentIndex.value])
|
||||
let origin = []
|
||||
const currentItem = selectList.value[currentIndex.value]
|
||||
if (currentItem.sketch) {
|
||||
origin.push({ type: "sketch", url: currentItem.sketch })
|
||||
}
|
||||
if (currentItem.mainProductImage) {
|
||||
origin.push({ type: "mainProductImage", url: currentItem.mainProductImage })
|
||||
}
|
||||
if (type !== "cover") origin = []
|
||||
const titleList = {
|
||||
const origin = type === "cover" ? getCoverOriginList(currentItem) : []
|
||||
const titleList: Record<CropType, string> = {
|
||||
sketch: "Crop Sketch",
|
||||
mainProductImage: "Crop Main Product Image",
|
||||
cover: "Crop Cover",
|
||||
@@ -315,17 +411,28 @@
|
||||
cropType.value = type
|
||||
imageClipDialogRef.value.open(
|
||||
data,
|
||||
(file) => {
|
||||
(file: File, coverFrom?: CoverSourceType) => {
|
||||
// console.log(file)
|
||||
uploadFile(file).then((res) => {
|
||||
if (type === "apparel" && typeof index !== "undefined") {
|
||||
selectList.value[currentIndex.value].sketchList[index].url = res
|
||||
} else if (type === "cover") {
|
||||
selectList.value[currentIndex.value].cover = res
|
||||
if (isCoverSource(coverFrom)) {
|
||||
selectList.value[currentIndex.value].coverFrom = coverFrom
|
||||
}
|
||||
} else {
|
||||
selectList.value[currentIndex.value][type] = res
|
||||
}
|
||||
})
|
||||
},
|
||||
{ ratio, isPreview: true, title: titleList[type], isProduct: true },
|
||||
{
|
||||
ratio,
|
||||
isPreview: true,
|
||||
title: titleList[type],
|
||||
isProduct: true,
|
||||
coverFrom: currentItem.coverFrom
|
||||
},
|
||||
origin
|
||||
)
|
||||
}
|
||||
@@ -377,49 +484,107 @@
|
||||
return true
|
||||
}
|
||||
|
||||
type SaveListingImage = {
|
||||
category: ListingImageCategory
|
||||
imageUrl: string | null
|
||||
isSelected: number
|
||||
sortOrder: number
|
||||
}
|
||||
|
||||
const isMainProductMedia = (item: ListingItem, media: ProductMediaItem, index: number) =>
|
||||
!media.isVideo &&
|
||||
(item.firstSelectedIndex === index ||
|
||||
(Boolean(item.mainProductImage) && item.mainProductImage === media.url))
|
||||
|
||||
const getSortedProductMedia = (item: ListingItem) => {
|
||||
return item.prodImageList
|
||||
.map((media, index) => ({ media, index }))
|
||||
.filter(({ media }) => !media.isVideo)
|
||||
.sort((prev, next) => {
|
||||
const prevRank = isMainProductMedia(item, prev.media, prev.index)
|
||||
? 0
|
||||
: prev.media.selected
|
||||
? 1
|
||||
: 2
|
||||
const nextRank = isMainProductMedia(item, next.media, next.index)
|
||||
? 0
|
||||
: next.media.selected
|
||||
? 1
|
||||
: 2
|
||||
|
||||
return prevRank - nextRank || prev.index - next.index
|
||||
})
|
||||
}
|
||||
|
||||
const buildListingImages = (item: ListingItem) => {
|
||||
const images: SaveListingImage[] = []
|
||||
const sortOrderMap: Partial<Record<ListingImageCategory, number>> = {}
|
||||
const getNextSortOrder = (category: ListingImageCategory) => {
|
||||
const sortOrder = (sortOrderMap[category] || 0) + 1
|
||||
sortOrderMap[category] = sortOrder
|
||||
return sortOrder
|
||||
}
|
||||
const pushImage = (
|
||||
category: ListingImageCategory,
|
||||
imageUrl: string | null,
|
||||
isSelected = 1,
|
||||
sortOrder = getNextSortOrder(category)
|
||||
) => {
|
||||
images.push({
|
||||
category,
|
||||
imageUrl,
|
||||
isSelected,
|
||||
sortOrder
|
||||
})
|
||||
}
|
||||
const getCoverFromImageUrl = () => {
|
||||
if (item.coverFrom === "mainProductImage") return item.mainProductImage || null
|
||||
return item.sketch
|
||||
}
|
||||
|
||||
pushImage("sketch", item.sketch)
|
||||
pushImage("cover", item.cover)
|
||||
pushImage("cover_from", getCoverFromImageUrl())
|
||||
|
||||
if (item.mainProductImage) {
|
||||
pushImage("main_product", item.mainProductImage)
|
||||
}
|
||||
|
||||
getSortedProductMedia(item).forEach(({ media }) => {
|
||||
pushImage("product", media.url, Number(!!media.selected))
|
||||
})
|
||||
|
||||
item.sketchList.forEach((sketch) => {
|
||||
pushImage("apparel", sketch.url)
|
||||
})
|
||||
|
||||
let videoSortOrder = 0
|
||||
item.prodImageList
|
||||
.filter((media) => media.isVideo)
|
||||
.forEach((media) => {
|
||||
videoSortOrder += 1
|
||||
const isSelected = Number(!!media.selected)
|
||||
|
||||
pushImage("firstFrame", media.firstFrameUrl || media.url, isSelected, videoSortOrder)
|
||||
pushImage("gif", media.gifUrl || "", isSelected, videoSortOrder)
|
||||
pushImage("video", media.videoUrl || "", isSelected, videoSortOrder)
|
||||
})
|
||||
|
||||
return images
|
||||
}
|
||||
|
||||
const handleSaveForm = async (type: StatusType) => {
|
||||
const paramsList = []
|
||||
selectList.value.forEach((item: ListingItem) => {
|
||||
const params = {
|
||||
const paramsList = selectList.value.map((item: ListingItem) => {
|
||||
return {
|
||||
id: itemId.value,
|
||||
title: item.productName,
|
||||
description: item.desc,
|
||||
price: item.price,
|
||||
status: type === "draft" ? 0 : 1,
|
||||
images: [],
|
||||
designFor: (item.gender || "FEMALE").toLowerCase(),
|
||||
images: buildListingImages(item),
|
||||
designFor: getListingDesignFor(item.gender),
|
||||
productCategory: item.category
|
||||
}
|
||||
|
||||
;["sketch", "cover"].forEach((el) => {
|
||||
params.images.push({
|
||||
category: el,
|
||||
imageUrl: item[el],
|
||||
isSelected: 1
|
||||
})
|
||||
})
|
||||
if (item.mainProductImage) {
|
||||
params.images.push({
|
||||
category: "main_product",
|
||||
imageUrl: item.mainProductImage,
|
||||
isSeleted: 1
|
||||
})
|
||||
}
|
||||
item.prodImageList.forEach((item) => {
|
||||
params.images.push({
|
||||
category: "product",
|
||||
imageUrl: item.url,
|
||||
isSelected: Number(!!item.selected)
|
||||
})
|
||||
})
|
||||
item.sketchList.forEach((item) => {
|
||||
params.images.push({
|
||||
category: "apparel",
|
||||
imageUrl: item.url,
|
||||
isSelected: 1
|
||||
})
|
||||
})
|
||||
paramsList.push(params)
|
||||
})
|
||||
await fetchUpdateListing(paramsList)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user