742 lines
20 KiB
Vue
742 lines
20 KiB
Vue
<template>
|
|
<div class="edit-detail-wrapper flex-1">
|
|
<seller-header class="edit-detail-header">
|
|
<template #right>
|
|
<div class="operate-menu flex">
|
|
<div class="menu-btn flex align-center save" @click="handleClickMenu('draft')">
|
|
<span>{{ $t("SellerListEdit.saveDraft") }}</span>
|
|
<SvgIcon name="CSave" size="16" />
|
|
</div>
|
|
<div
|
|
class="menu-btn flex align-center publish"
|
|
@click="handleClickMenu('publish')"
|
|
>
|
|
<span>{{ $t("SellerListEdit.publish") }}</span>
|
|
<SvgIcon name="CPublish" size="16" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</seller-header>
|
|
<div class="edit-detail-content flex">
|
|
<div class="left">
|
|
<TopImageSection :images="previewImageMap" @crop="handleClickCrop" />
|
|
<ProductImageList
|
|
:image-list="prodImgList"
|
|
:first-selected-index="currentListing.firstSelectedIndex"
|
|
@select="handleSelectProdImg"
|
|
/>
|
|
<ApparelSketchList
|
|
:sketch-list="currentListing.sketchList"
|
|
@crop="handleClickCrop"
|
|
/>
|
|
</div>
|
|
<div class="right">
|
|
<ListingForm
|
|
:product-name="currentListing.productName"
|
|
:price="currentListing.price"
|
|
:desc="currentListing.desc"
|
|
:gender="currentListing.gender"
|
|
:category="currentListing.category"
|
|
:gender-options="genderOptions"
|
|
:category-options="categoryOptions"
|
|
@update:product-name="currentListing.productName = $event"
|
|
@update:price="currentListing.price = $event"
|
|
@update:desc="currentListing.desc = $event"
|
|
@update:gender="handleUpdateGender"
|
|
@update:category="currentListing.category = $event"
|
|
/>
|
|
<div class="page-control flex align-center" v-if="selectList.length > 1">
|
|
<a-pagination
|
|
v-model:current="currentPage"
|
|
:total="selectList.length"
|
|
:page-size="1"
|
|
showQuickJumper
|
|
showLessItems
|
|
responsive
|
|
:showSizeChanger="false"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<ImageClipDialog
|
|
ref="imageClipDialogRef"
|
|
fixedBox
|
|
isProduct
|
|
:info="false"
|
|
v-bind="$attrs"
|
|
:type="cropType"
|
|
/>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, onMounted } from "vue"
|
|
import { useRouter } from "vue-router"
|
|
import { useI18n } from "vue-i18n"
|
|
import { message } from "ant-design-vue"
|
|
import SellerHeader from "../../seller-header.vue"
|
|
import ImageClipDialog from "../../BrandProfile/image-clip-dialog.vue"
|
|
import ApparelSketchList from "./components/ApparelSketchList.vue"
|
|
import ListingForm from "./components/ListingForm.vue"
|
|
import ProductImageList from "./components/ProductImageList.vue"
|
|
import TopImageSection from "./components/TopImageSection.vue"
|
|
import { useStore } from "vuex"
|
|
import {
|
|
fetchSketchDetail,
|
|
uploadFile,
|
|
fetchListingDetailById,
|
|
fetchUpdateListing
|
|
} from "./api"
|
|
import type {
|
|
CoverSourceType,
|
|
CropType,
|
|
ListingDetailImage,
|
|
ListingDetailResponse,
|
|
ListingImageCategory,
|
|
ListingItem,
|
|
ProductMediaItem,
|
|
RadioOption,
|
|
SketchDetailResponse,
|
|
SketchDetailVideo,
|
|
StatusType
|
|
} from "./types"
|
|
|
|
const ROUTER = useRouter()
|
|
const { t } = useI18n()
|
|
|
|
const imageClipDialogRef = ref<InstanceType<typeof ImageClipDialog> | null>(null)
|
|
|
|
defineOptions({
|
|
name: "EditDetail"
|
|
})
|
|
|
|
const STORE = useStore()
|
|
|
|
const createListingItem = (
|
|
sketch: string | null = null,
|
|
designItemId: number | string | null = null
|
|
): ListingItem => ({
|
|
designItemId,
|
|
sketch,
|
|
mainProductImage: "",
|
|
cover: "",
|
|
productImage: [],
|
|
apparelSketch: [],
|
|
productName: "",
|
|
price: "",
|
|
desc: "",
|
|
gender: "FEMALE",
|
|
category: null,
|
|
coverFrom: "sketch",
|
|
firstSelectedIndex: null,
|
|
prodImageList: [],
|
|
sketchList: []
|
|
})
|
|
|
|
const genderOptions = computed(() => {
|
|
return STORE.state.UserHabit?.sex.value
|
|
})
|
|
|
|
const fallbackCategoryOptions: Record<string, RadioOption[]> = {
|
|
MALE: STORE.state.UserHabit?.MalePosition || [],
|
|
FEMALE: STORE.state.UserHabit?.FemalePosition || []
|
|
}
|
|
|
|
const currentPage = ref(1)
|
|
const currentIndex = computed(() => currentPage.value - 1)
|
|
|
|
const itemId = ref("")
|
|
const selectList = ref<ListingItem[]>([createListingItem()])
|
|
|
|
const prodImgList = computed(() => currentListing.value.prodImageList || [])
|
|
|
|
const categoryOptions = computed(() => {
|
|
const gender = selectList.value[currentIndex.value].gender
|
|
return fallbackCategoryOptions[gender] || []
|
|
})
|
|
|
|
const currentListing = computed(() => selectList.value[currentIndex.value])
|
|
|
|
const handleUpdateGender = (gender: string) => {
|
|
if (currentListing.value.gender === gender) return
|
|
|
|
currentListing.value.gender = gender
|
|
currentListing.value.category = null
|
|
}
|
|
|
|
const previewImageMap = computed(() => ({
|
|
sketch: currentListing.value.sketch,
|
|
mainProductImage: currentListing.value.mainProductImage,
|
|
cover: currentListing.value.cover
|
|
}))
|
|
|
|
const getSortedDetailImages = (images: ListingDetailImage[] = []) => {
|
|
return [...images].sort((prev, next) => (prev.sortOrder ?? 0) - (next.sortOrder ?? 0))
|
|
}
|
|
|
|
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"] => {
|
|
const categories = Array.isArray(value) ? value : value ? [value] : []
|
|
const normalized = categories
|
|
.filter((category) => category !== null && typeof category !== "undefined")
|
|
.map((category) => String(category).toLowerCase())
|
|
|
|
return normalized.length ? normalized : null
|
|
}
|
|
|
|
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 =
|
|
detail.price === null || typeof detail.price === "undefined" ? "" : String(detail.price)
|
|
listing.desc = detail.description || ""
|
|
listing.gender = normalizeDetailGender(detail.designFor)
|
|
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
|
|
|
|
if (image.category === "cover") {
|
|
listing.cover = imageUrl
|
|
return
|
|
}
|
|
|
|
if (image.category === "sketch") {
|
|
listing.sketch = imageUrl
|
|
return
|
|
}
|
|
|
|
if (image.category === "mainProductImage" || image.category === "main_product") {
|
|
listing.mainProductImage = imageUrl
|
|
return
|
|
}
|
|
|
|
if (image.category === "product") {
|
|
listing.prodImageList.push({
|
|
url: imageUrl,
|
|
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 (coverFromImageUrl) {
|
|
listing.coverFrom = resolveCoverSourceFromImageUrl(coverFromImageUrl, listing)
|
|
}
|
|
|
|
const mainProductIndex = listing.prodImageList.findIndex(
|
|
(item) => !item.isVideo && item.url === listing.mainProductImage
|
|
)
|
|
listing.firstSelectedIndex = mainProductIndex === -1 ? null : mainProductIndex
|
|
|
|
listing.productImage = listing.prodImageList.map((item) => item.url)
|
|
listing.apparelSketch = listing.sketchList
|
|
.map((item) => item.url)
|
|
.filter((url): url is string => Boolean(url))
|
|
|
|
return listing
|
|
}
|
|
|
|
const createProductVideoItem = (
|
|
video: SketchDetailVideo,
|
|
selected = false
|
|
): ProductMediaItem | null => {
|
|
const firstFrameUrl = video?.firstFrameUrl || ""
|
|
const gifUrl = video?.gifUrl || ""
|
|
const videoUrl = video?.videoUrl || ""
|
|
|
|
if (!firstFrameUrl || !videoUrl) return null
|
|
|
|
return {
|
|
url: firstFrameUrl,
|
|
firstFrameUrl,
|
|
gifUrl,
|
|
videoUrl,
|
|
isVideo: true,
|
|
selected
|
|
}
|
|
}
|
|
|
|
const handleSelectProdImg = (index: number) => {
|
|
const listing = currentListing.value
|
|
const target = prodImgList.value[index]
|
|
const willSelect = !target.selected
|
|
|
|
target.selected = willSelect
|
|
|
|
if (willSelect && listing.firstSelectedIndex === null) {
|
|
if (target.isVideo) {
|
|
message.warning(t("Seller.VideoWarning"))
|
|
return
|
|
}
|
|
listing.mainProductImage = target.url
|
|
listing.firstSelectedIndex = index
|
|
return
|
|
}
|
|
|
|
if (!willSelect && listing.mainProductImage === target.url) {
|
|
listing.firstSelectedIndex = null
|
|
listing.mainProductImage = ""
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
// console.log(data, type)
|
|
// console.log(selectList.value[currentIndex.value])
|
|
const currentItem = selectList.value[currentIndex.value]
|
|
const origin = type === "cover" ? getCoverOriginList(currentItem) : []
|
|
const titleList: Record<CropType, string> = {
|
|
sketch: "Crop Sketch",
|
|
mainProductImage: "Crop Main Product Image",
|
|
cover: "Crop Cover",
|
|
apparel: "Crop Apparel Sketch"
|
|
}
|
|
const ratio = type === "cover" ? [4, 5] : [9, 16]
|
|
cropType.value = type
|
|
imageClipDialogRef.value.open(
|
|
data,
|
|
(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,
|
|
coverFrom: currentItem.coverFrom
|
|
},
|
|
origin
|
|
)
|
|
}
|
|
|
|
const hasValue = (value: unknown) =>
|
|
value !== null && value !== undefined && String(value).trim() !== ""
|
|
|
|
const getMissingRequiredField = (item: ListingItem) => {
|
|
const requiredFields = [
|
|
{ value: item.sketch, label: t("SellerListEdit.sketch") },
|
|
{ value: item.cover, label: t("SellerListEdit.cover") },
|
|
{ value: item.productName, label: t("SellerListEdit.productName") },
|
|
{ value: item.price, label: t("SellerListEdit.price") },
|
|
{ value: item.desc, label: t("SellerListEdit.productDescription") },
|
|
{ value: item.gender, label: t("SellerListEdit.designFor") },
|
|
{ value: item.category, label: t("SellerListEdit.productCategory") }
|
|
]
|
|
const missingField = requiredFields.find((field) => !hasValue(field.value))
|
|
if (missingField) return missingField.label
|
|
|
|
const missingSketchIndex = item.sketchList.findIndex((sketch) => !hasValue(sketch.url))
|
|
if (item.sketchList.length === 0 || missingSketchIndex !== -1) {
|
|
return `${t("SellerListEdit.apparelSketchTitle").trim()} ${
|
|
missingSketchIndex === -1 ? 1 : missingSketchIndex + 1
|
|
}`
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
const validatePublishRequired = () => {
|
|
for (let index = 0; index < selectList.value.length; index += 1) {
|
|
const field = getMissingRequiredField(selectList.value[index])
|
|
if (!field) continue
|
|
|
|
currentPage.value = index + 1
|
|
message.warning(
|
|
t(
|
|
selectList.value.length > 1
|
|
? "SellerListEdit.requiredFieldTipsWithPage"
|
|
: "SellerListEdit.requiredFieldTips",
|
|
{ index: index + 1, field }
|
|
)
|
|
)
|
|
return false
|
|
}
|
|
|
|
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.map((item: ListingItem) => {
|
|
return {
|
|
id: itemId.value,
|
|
title: item.productName,
|
|
description: item.desc,
|
|
price: item.price,
|
|
status: type === "draft" ? 0 : 1,
|
|
images: buildListingImages(item),
|
|
designFor: getListingDesignFor(item.gender),
|
|
productCategory: item.category
|
|
}
|
|
})
|
|
await fetchUpdateListing(paramsList)
|
|
}
|
|
const handleClickMenu = async (status: StatusType) => {
|
|
if (!validatePublishRequired()) return
|
|
|
|
await handleSaveForm(status)
|
|
if (status === "draft") {
|
|
ROUTER.push({
|
|
name: "Status",
|
|
params: { status: "draft" },
|
|
state: {
|
|
type: history.state?.type,
|
|
collectionId: history.state?.collectionId
|
|
}
|
|
})
|
|
} else if (status === "publish") {
|
|
ROUTER.push({
|
|
name: "Status",
|
|
params: { status: "publish" },
|
|
state: {
|
|
type: history.state?.type,
|
|
collectionId: history.state?.collectionId
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
const handleFetchItemDetial = (list) => {
|
|
fetchSketchDetail(list).then((res: SketchDetailResponse[]) => {
|
|
res.forEach((item, index) => {
|
|
if (!selectList.value[index]) return
|
|
selectList.value[index].sketchList = (item.clothes || []).map((el) => ({ url: el }))
|
|
const imageItems = (item.toProductImageUrls || []).map((el) => ({
|
|
url: el,
|
|
selected: false
|
|
}))
|
|
const videoItems = (item.videos || [])
|
|
.map((video) => createProductVideoItem(video))
|
|
.filter((video): video is ProductMediaItem => Boolean(video))
|
|
selectList.value[index].prodImageList = [...imageItems, ...videoItems]
|
|
})
|
|
})
|
|
}
|
|
|
|
const handleGetDetailById = () => {
|
|
fetchListingDetailById(itemId.value).then((res: ListingDetailResponse) => {
|
|
const listing = createListingItemFromDetail(res)
|
|
|
|
currentPage.value = 1
|
|
selectList.value = [listing]
|
|
})
|
|
}
|
|
|
|
onMounted(() => {
|
|
const data = history.state
|
|
if (data?.type === "edit") {
|
|
itemId.value = history.state?.id
|
|
handleGetDetailById()
|
|
} else {
|
|
const designItemIds = history.state?.designItemIds || []
|
|
|
|
if (!designItemIds.length) return
|
|
|
|
currentPage.value = 1
|
|
selectList.value = designItemIds.map((item) =>
|
|
createListingItem(item.designOutfitUrl, item.designItemId)
|
|
)
|
|
const list = designItemIds.map((el) => el.designItemId)
|
|
// console.log("list", list.length, list)
|
|
handleFetchItemDetial(list)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.c-svg {
|
|
width: initial;
|
|
height: initial;
|
|
}
|
|
|
|
.edit-detail-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
min-height: 0;
|
|
margin-top: -1rem;
|
|
overflow: hidden;
|
|
|
|
&::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.menu-btn {
|
|
height: 6rem;
|
|
border: 0.15rem solid #000000;
|
|
border-radius: 4rem;
|
|
text-align: center;
|
|
line-height: 6rem;
|
|
padding: 0 2rem;
|
|
font-size: 1.6rem;
|
|
column-gap: 0.8rem;
|
|
cursor: pointer;
|
|
transition: all 0.25s ease;
|
|
|
|
&:hover {
|
|
background: #000;
|
|
color: #fff;
|
|
}
|
|
|
|
// &.publish:hover {
|
|
// background: #fff;
|
|
// color: #000;
|
|
// }
|
|
}
|
|
|
|
.edit-detail-header {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.operate-menu {
|
|
column-gap: 2rem;
|
|
|
|
// .publish {
|
|
// background-color: #000000;
|
|
// color: #ffffff;
|
|
// }
|
|
}
|
|
}
|
|
|
|
.edit-detail-content {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
justify-content: space-between;
|
|
|
|
&::-webkit-scrollbar {
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.right {
|
|
width: 55.2rem;
|
|
flex-shrink: 0;
|
|
|
|
.page-control {
|
|
justify-content: flex-end;
|
|
margin-top: 4rem;
|
|
}
|
|
}
|
|
}
|
|
</style>
|