Merge branch 'dev_vite' of http://18.167.251.121:10003/aidlab/aida_front into dev_vite
This commit is contained in:
BIN
src/assets/images/seller/selectCollectionNullStatus.png
Normal file
BIN
src/assets/images/seller/selectCollectionNullStatus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1737,7 +1737,7 @@ export default {
|
||||
GetStarted: '开始体验',
|
||||
},
|
||||
SellerListEdit:{
|
||||
saveDraft: "保存草稿",
|
||||
saveDraft: "全部保存",
|
||||
publish: "发布",
|
||||
sketch: "线稿图",
|
||||
mainProductImage: "产品主图",
|
||||
|
||||
@@ -1788,7 +1788,7 @@ export default {
|
||||
GetStarted: 'Get Started',
|
||||
},
|
||||
SellerListEdit:{
|
||||
saveDraft:'Save Draft',
|
||||
saveDraft:'Save All New',
|
||||
publish:'Publish',
|
||||
sketch:'Sketch',
|
||||
mainProductImage:'Main Product Image',
|
||||
|
||||
@@ -144,7 +144,6 @@ const open = (url, callback, options, origin) => {
|
||||
coverOrigin.value = origin
|
||||
data.url = origin[0].url
|
||||
}
|
||||
console.log("-------", origin)
|
||||
show.value = true
|
||||
}
|
||||
const onCancel = () => {
|
||||
|
||||
@@ -204,23 +204,23 @@
|
||||
.cropper-box-canvas {
|
||||
background-color: #ffffff;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
// img {
|
||||
// height: 100%;
|
||||
// }
|
||||
}
|
||||
}
|
||||
&.is-cover {
|
||||
:deep(.vue-cropper) {
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.cropper-box-canvas) {
|
||||
width: 31.1rem !important;
|
||||
left: 50% !important;
|
||||
transform: translateX(-50%) !important;
|
||||
img {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
// :deep(.cropper-box-canvas) {
|
||||
// width: 31.1rem !important;
|
||||
// left: 50% !important;
|
||||
// transform: translateX(-50%) !important;
|
||||
// img {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import type { ListingItem } from "../types"
|
||||
|
||||
defineProps<{
|
||||
sketchList: ListingItem["sketchList"]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "crop", data: string | null, type: "apparel", index?: number): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="apparel-container">
|
||||
<div class="title">
|
||||
<span class="main-title">{{ $t("SellerListEdit.apparelSketchTitle") }}</span>
|
||||
<span class="sub-title">{{ $t("SellerListEdit.apparelSketchSubTitle") }}</span>
|
||||
</div>
|
||||
<div class="sketch-list-container flex">
|
||||
<div
|
||||
v-for="(item, index) in sketchList"
|
||||
:key="index"
|
||||
class="sketch-element flex flex-center"
|
||||
>
|
||||
<img class="img-src" :src="item.url || ''" alt="" />
|
||||
<div class="crop-tool flex flex-center" @click="emit('crop', item.url, 'apparel', index)">
|
||||
<SvgIcon name="CCrop" color="#fff" size="12" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.apparel-container {
|
||||
margin-top: 3rem;
|
||||
|
||||
.title {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.8rem;
|
||||
|
||||
.main-title {
|
||||
font-weight: 400;
|
||||
font-style: bold;
|
||||
|
||||
&::after {
|
||||
content: "*";
|
||||
color: #df2b2c;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 1.2rem;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.sketch-list-container {
|
||||
column-gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.sketch-element {
|
||||
width: 10rem;
|
||||
height: 14.6rem;
|
||||
border: 0.15rem solid #c7c7c7;
|
||||
border-radius: 1.2rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.img-src {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.crop-tool {
|
||||
position: absolute;
|
||||
top: 0.4rem;
|
||||
right: 0.4rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
background-color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,222 @@
|
||||
<script setup lang="ts">
|
||||
import Radio from "./Radio.vue"
|
||||
import type { RadioOption } from "../types"
|
||||
|
||||
defineProps<{
|
||||
productName: string
|
||||
price: string
|
||||
desc: string
|
||||
gender: string
|
||||
category: string[] | null
|
||||
genderOptions: RadioOption[]
|
||||
categoryOptions: RadioOption[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:productName", value: string): void
|
||||
(e: "update:price", value: string): void
|
||||
(e: "update:desc", value: string): void
|
||||
(e: "update:gender", value: string): void
|
||||
(e: "update:category", value: string[] | null): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-container flex flex-col">
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">
|
||||
{{ $t("SellerListEdit.productName") }}
|
||||
</div>
|
||||
<div class="form-item-value product-name">
|
||||
<a-input
|
||||
:value="productName"
|
||||
show-count
|
||||
placeholder="Enter product name"
|
||||
:bordered="false"
|
||||
:maxlength="60"
|
||||
@update:value="emit('update:productName', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">{{ $t("SellerListEdit.price") }}</div>
|
||||
<div class="form-item-value price flex align-center">
|
||||
<span>HK$</span>
|
||||
<a-input
|
||||
:value="price"
|
||||
placeholder="0.00"
|
||||
:bordered="false"
|
||||
@update:value="emit('update:price', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">
|
||||
{{ $t("SellerListEdit.productDescription") }}
|
||||
</div>
|
||||
<div class="form-item-value desc">
|
||||
<a-textarea
|
||||
:value="desc"
|
||||
show-count
|
||||
:rows="4"
|
||||
placeholder="Enter product description"
|
||||
:bordered="false"
|
||||
:maxlength="500"
|
||||
@update:value="emit('update:desc', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">
|
||||
{{ $t("SellerListEdit.designFor") }}
|
||||
</div>
|
||||
<div class="form-item-value no-border">
|
||||
<Radio
|
||||
:options="genderOptions"
|
||||
:model-value="gender"
|
||||
@update:model-value="emit('update:gender', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label with-tip">
|
||||
<span class="required">{{ $t("SellerListEdit.productCategory") }}</span>
|
||||
<span class="help-text">{{ $t("SellerListEdit.categoryTips") }}</span>
|
||||
</div>
|
||||
<div class="form-item-value no-border">
|
||||
<Radio
|
||||
multiple
|
||||
:options="categoryOptions"
|
||||
:model-value="category"
|
||||
@update:model-value="emit('update:category', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="license-note flex align-center">
|
||||
<img src="@/assets/images/seller/tips.png" class="info-icon" />
|
||||
<div class="note-copy">
|
||||
{{ $t("SellerListEdit.policy") }}
|
||||
<a href="javascript:void(0)">{{ $t("SellerListEdit.learnMore") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.required {
|
||||
&::after {
|
||||
content: "*";
|
||||
color: #df2b2c;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
row-gap: 3rem;
|
||||
|
||||
.form-item {
|
||||
.form-item-label {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
font-style: bold;
|
||||
margin-bottom: 0.6rem;
|
||||
line-height: 1.5;
|
||||
|
||||
&.with-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 0.8rem;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 1rem;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item-value {
|
||||
border: 0.16rem solid #d1d1d1;
|
||||
border-radius: 1.2rem;
|
||||
position: relative;
|
||||
padding: 1.6rem;
|
||||
box-sizing: border-box;
|
||||
font-size: 1.2rem;
|
||||
color: #000;
|
||||
|
||||
&.no-border {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.price {
|
||||
column-gap: 0.6rem;
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-input-affix-wrapper),
|
||||
:deep(.ant-input-textarea textarea) {
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 1.2rem;
|
||||
color: #000;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.ant-input) {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
:deep(.ant-input-show-count-suffix) {
|
||||
color: #df2c2c;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:deep(textarea.ant-input) {
|
||||
resize: none;
|
||||
min-height: 5.4rem;
|
||||
padding-bottom: 1.8rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
:deep(.ant-input-textarea-show-count) {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
float: none;
|
||||
position: absolute;
|
||||
color: #df2c2c;
|
||||
bottom: 0;
|
||||
right: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.license-note {
|
||||
padding: 1.6rem;
|
||||
column-gap: 1.6rem;
|
||||
background: #f7f7f7;
|
||||
border-radius: 0.8rem;
|
||||
|
||||
.info-icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.note-copy {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.5;
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
font-style: medium;
|
||||
|
||||
a {
|
||||
color: #0080ed;
|
||||
text-decoration: underline;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,154 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
import type { ListingItem } from "../types"
|
||||
|
||||
const props = defineProps<{
|
||||
imageList: ListingItem["prodImageList"]
|
||||
firstSelectedIndex: number | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "select", index: number): void
|
||||
}>()
|
||||
|
||||
const selectedCount = computed(() => props.imageList.filter((item) => item.selected).length)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="product-image-list-container">
|
||||
<div class="title flex align-center space-between">
|
||||
<div class="title-left">
|
||||
<span class="main-title">{{ $t("SellerListEdit.productImageMainTitle") }}</span>
|
||||
<span class="sub-title">{{ $t("SellerListEdit.productImageSubTitle") }}</span>
|
||||
</div>
|
||||
<div class="title-right">{{ selectedCount }}/{{ imageList.length }} selected</div>
|
||||
</div>
|
||||
<div class="product-image-list flex">
|
||||
<div
|
||||
v-for="(item, index) in imageList"
|
||||
:key="index"
|
||||
class="product-image-item flex flex-center"
|
||||
:class="{ selected: item.selected }"
|
||||
@click="emit('select', index)"
|
||||
>
|
||||
<img
|
||||
v-if="item.selected"
|
||||
src="@/assets/images/seller/checked.png"
|
||||
class="checked"
|
||||
alt=""
|
||||
/>
|
||||
<img class="img-src" :src="item.url" alt="" />
|
||||
<div v-if="item.selected && index === firstSelectedIndex" class="main-pic">
|
||||
main
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.product-image-list-container {
|
||||
margin-top: 3rem;
|
||||
|
||||
.title {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 1.2rem;
|
||||
|
||||
.main-title {
|
||||
font-weight: 400;
|
||||
font-style: bold;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 1.2rem;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.title-right {
|
||||
color: #585858;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.product-image-list {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
column-gap: 0.8rem;
|
||||
max-width: 80.2rem;
|
||||
padding-bottom: 1.2rem;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 0.8rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
border-radius: 0.8rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #000000;
|
||||
border-radius: 0.8rem;
|
||||
}
|
||||
|
||||
.product-image-item {
|
||||
width: 11.6rem;
|
||||
height: 20.6rem;
|
||||
border-radius: 1rem;
|
||||
border: 0.15rem solid #c7c7c7;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
background-color: #fcfcfc;
|
||||
opacity: 0.7;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: #000;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.checked {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
position: absolute;
|
||||
top: 0.8rem;
|
||||
right: 0.8rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.img-src {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main-pic {
|
||||
position: absolute;
|
||||
height: 2.4rem;
|
||||
line-height: 2.4rem;
|
||||
left: 0.8rem;
|
||||
right: 0.8rem;
|
||||
bottom: 0.8rem;
|
||||
z-index: 1;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
border-radius: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,7 @@
|
||||
'radio-button',
|
||||
{
|
||||
'is-active': multiple
|
||||
? selectedValues.includes(item.key)
|
||||
? selectedValues.includes(normalizeValue(item.key))
|
||||
: modelValue === item.key
|
||||
}
|
||||
]"
|
||||
@@ -20,54 +20,73 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
import { computed } from "vue"
|
||||
|
||||
interface Option {
|
||||
name: string | number
|
||||
value: string | number | boolean
|
||||
key: string
|
||||
optype: boolean
|
||||
interface Option {
|
||||
name: string | number
|
||||
value: string | number | boolean
|
||||
key: string
|
||||
optype: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string | number | boolean | null | undefined>
|
||||
| null
|
||||
options: Option[] // 按钮选项数组
|
||||
multiple?: boolean // 是否支持多选,默认为 false
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: any): void
|
||||
}>()
|
||||
|
||||
const multiple = props.multiple === true
|
||||
|
||||
const normalizeValue = (value: any) =>
|
||||
typeof value === "string" ? value.toLocaleLowerCase() : value
|
||||
|
||||
const selectedValues = computed(() => {
|
||||
if (!multiple) {
|
||||
return typeof props.modelValue === "undefined" || props.modelValue === null
|
||||
? []
|
||||
: [props.modelValue]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | number | boolean | Array<string | number | boolean> | null
|
||||
options: Option[] // 按钮选项数组
|
||||
multiple?: boolean // 是否支持多选,默认为 false
|
||||
}>()
|
||||
return Array.isArray(props.modelValue)
|
||||
? props.modelValue
|
||||
.filter((value) => value !== null && typeof value !== "undefined")
|
||||
.map(normalizeValue)
|
||||
: []
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: any): void
|
||||
}>()
|
||||
const selectOption = (value: any) => {
|
||||
if (multiple) {
|
||||
const selectedValue = normalizeValue(value)
|
||||
const current = Array.isArray(props.modelValue)
|
||||
? props.modelValue
|
||||
.filter((item) => item !== null && typeof item !== "undefined")
|
||||
.map(normalizeValue)
|
||||
: []
|
||||
const index = current.indexOf(selectedValue)
|
||||
|
||||
const multiple = props.multiple === true
|
||||
|
||||
const selectedValues = computed(() => {
|
||||
if (!multiple) {
|
||||
return typeof props.modelValue === "undefined" || props.modelValue === null
|
||||
? []
|
||||
: [props.modelValue]
|
||||
if (index >= 0) {
|
||||
current.splice(index, 1)
|
||||
} else {
|
||||
current.push(selectedValue)
|
||||
}
|
||||
|
||||
return Array.isArray(props.modelValue) ? props.modelValue : []
|
||||
})
|
||||
|
||||
const selectOption = (value: any) => {
|
||||
if (multiple) {
|
||||
const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
|
||||
const index = current.indexOf(value)
|
||||
if (index >= 0) {
|
||||
current.splice(index, 1)
|
||||
} else {
|
||||
current.push(value.toLocaleLowerCase)
|
||||
}
|
||||
emit("update:modelValue", current)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.modelValue !== value) {
|
||||
emit("update:modelValue", value)
|
||||
}
|
||||
emit("update:modelValue", current.length ? current : null)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.modelValue !== value) {
|
||||
emit("update:modelValue", value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
import type { TopImageType } from "../types"
|
||||
|
||||
defineProps<{
|
||||
images: Record<TopImageType, string | null>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "crop", data: string | null, type: TopImageType): void
|
||||
}>()
|
||||
|
||||
const topImageList: TopImageType[] = ["sketch", "mainProductImage", "cover"]
|
||||
const topImageTitleMap: Record<TopImageType, string> = {
|
||||
sketch: "SellerListEdit.sketch",
|
||||
mainProductImage: "SellerListEdit.mainProductImage",
|
||||
cover: "SellerListEdit.cover"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main-image-container flex">
|
||||
<div
|
||||
v-for="type in topImageList"
|
||||
:key="type"
|
||||
:class="`main-image-item flex flex-col align-center ${type}`"
|
||||
>
|
||||
<div class="title" :class="{ required: type !== 'mainProductImage' }">
|
||||
{{ $t(topImageTitleMap[type]) }}
|
||||
</div>
|
||||
<div class="sketch-item flex flex-center" :class="type">
|
||||
<div
|
||||
v-if="images[type]"
|
||||
class="crop-tool flex flex-center"
|
||||
@click="emit('crop', images[type], type)"
|
||||
>
|
||||
<SvgIcon name="CCrop" color="#fff" size="12" />
|
||||
</div>
|
||||
<img
|
||||
v-if="images[type]"
|
||||
:src="images[type] || ''"
|
||||
class="sketch-img"
|
||||
:class="type"
|
||||
alt=""
|
||||
/>
|
||||
<div v-else class="trigger flex flex-col align-center">
|
||||
<div
|
||||
v-if="type === 'cover'"
|
||||
class="cover-trigger flex flex-col align-center"
|
||||
@click="emit('crop', null, 'cover')"
|
||||
>
|
||||
<SvgIcon class="trigger-icon" name="CCrop" color="#585858" size="24" />
|
||||
<div class="trigger-tips">
|
||||
{{ $t("SellerListEdit.cropDesc") }}
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="trigger-img placeholder"></div>
|
||||
<div class="trigger-tips">
|
||||
{{ $t("SellerListEdit.productImageDesc") }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.c-svg {
|
||||
width: initial;
|
||||
height: initial;
|
||||
}
|
||||
|
||||
.required {
|
||||
&::after {
|
||||
content: "*";
|
||||
color: #df2b2c;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.main-image-container {
|
||||
column-gap: 3.5rem;
|
||||
|
||||
.main-image-item {
|
||||
flex-shrink: 0;
|
||||
|
||||
.title {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sketch-item {
|
||||
width: 11.6rem;
|
||||
height: 20.4rem;
|
||||
border: 0.15rem solid #d1d1d1;
|
||||
border-radius: 1rem;
|
||||
position: relative;
|
||||
background-color: #f6f6f6;
|
||||
overflow: hidden;
|
||||
|
||||
&.cover {
|
||||
width: 16.2rem;
|
||||
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' rx='11' ry='11' fill='none' stroke='%23D1D1D1' stroke-width='1.5' stroke-dasharray='8%2c 5' stroke-linecap='square'/%3e%3c/svg%3e");
|
||||
border: none;
|
||||
}
|
||||
|
||||
.crop-tool {
|
||||
position: absolute;
|
||||
top: 0.8rem;
|
||||
right: 0.8rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
background-color: #000000;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sketch-img {
|
||||
height: 100%;
|
||||
|
||||
&.sketch {
|
||||
height: initial;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.trigger {
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
padding: 6rem 2rem 0;
|
||||
|
||||
&,
|
||||
.cover-trigger {
|
||||
row-gap: 1.2rem;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
border-radius: 0.6rem;
|
||||
background: linear-gradient(135deg, #efefef 0%, #cdcdcd 100%);
|
||||
}
|
||||
|
||||
.trigger-tips {
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
color: #585858;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -28,191 +28,32 @@
|
||||
</seller-header>
|
||||
<div class="edit-detail-content flex">
|
||||
<div class="left">
|
||||
<div class="main-image-container flex">
|
||||
<div
|
||||
v-for="type in topImageList"
|
||||
:key="type"
|
||||
:class="`main-image-item flex flex-col align-center ${type}`"
|
||||
>
|
||||
<div class="title" :class="{ required: type !== 'mainProductImage' }">
|
||||
{{ $t(topImageTitleMap[type]) }}
|
||||
</div>
|
||||
<div class="sketch-item flex flex-center" :class="type">
|
||||
<div
|
||||
v-if="previewImageMap[type]"
|
||||
class="crop-tool flex flex-center"
|
||||
@click="handleClickCrop(previewImageMap[type], type)"
|
||||
>
|
||||
<SvgIcon name="CCrop" color="#fff" size="12" />
|
||||
</div>
|
||||
<img
|
||||
v-if="previewImageMap[type]"
|
||||
:src="previewImageMap[type]"
|
||||
class="sketch-img"
|
||||
:class="type"
|
||||
alt=""
|
||||
/>
|
||||
<div v-else class="trigger flex flex-col align-center">
|
||||
<div
|
||||
v-if="type === 'cover'"
|
||||
class="cover-trigger flex flex-col align-center"
|
||||
@click="handleClickCrop(null, 'cover')"
|
||||
>
|
||||
<SvgIcon
|
||||
class="trigger-icon"
|
||||
name="CCrop"
|
||||
color="#585858"
|
||||
size="24"
|
||||
/>
|
||||
<div class="trigger-tips">
|
||||
{{ $t("SellerListEdit.cropDesc") }}
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="trigger-img placeholder"></div>
|
||||
<div class="trigger-tips">
|
||||
{{ $t("SellerListEdit.productImageDesc") }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-image-list-container">
|
||||
<div class="title flex align-center space-between">
|
||||
<div class="title-left">
|
||||
<span class="main-title">{{
|
||||
$t("SellerListEdit.productImageMainTitle")
|
||||
}}</span>
|
||||
<span class="sub-title">{{
|
||||
$t("SellerListEdit.productImageSubTitle")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="title-right">
|
||||
{{ selectedProdImgs }}/{{ prodImgList.length }} selected
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-image-list flex">
|
||||
<div
|
||||
v-for="(item, index) in prodImgList"
|
||||
:key="index"
|
||||
class="product-image-item flex flex-center"
|
||||
:class="{ selected: item.selected }"
|
||||
@click="handleSelectProdImg(index)"
|
||||
>
|
||||
<img
|
||||
v-if="item.selected"
|
||||
src="@/assets/images/seller/checked.png"
|
||||
class="checked"
|
||||
alt=""
|
||||
/>
|
||||
<img class="img-src" :src="item.url" alt="" />
|
||||
<div
|
||||
v-if="item.selected && index === firstSelectedIndex"
|
||||
class="main-pic"
|
||||
>
|
||||
main
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="apparel-container">
|
||||
<div class="title">
|
||||
<span class="main-title">{{
|
||||
$t("SellerListEdit.apparelSketchTitle")
|
||||
}}</span>
|
||||
<span class="sub-title">
|
||||
{{ $t("SellerListEdit.apparelSketchSubTitle") }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="sketch-list-container flex">
|
||||
<div
|
||||
v-for="(item, index) in selectList[currentIndex].sketchList"
|
||||
:key="index"
|
||||
class="sketch-element flex flex-center"
|
||||
>
|
||||
<img class="img-src" :src="item.url" alt="" />
|
||||
<div
|
||||
class="crop-tool flex flex-center"
|
||||
@click="handleClickCrop(item.url, 'apparel')"
|
||||
>
|
||||
<SvgIcon name="CCrop" color="#fff" size="12" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TopImageSection :images="previewImageMap" @crop="handleClickCrop" />
|
||||
<ProductImageList
|
||||
:image-list="prodImgList"
|
||||
:first-selected-index="firstSelectedIndex"
|
||||
@select="handleSelectProdImg"
|
||||
/>
|
||||
<ApparelSketchList
|
||||
:sketch-list="currentListing.sketchList"
|
||||
@crop="handleClickCrop"
|
||||
/>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="form-container flex flex-col">
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">
|
||||
{{ $t("SellerListEdit.productName") }}
|
||||
</div>
|
||||
<div class="form-item-value product-name">
|
||||
<a-input
|
||||
v-model:value="currentListing.productName"
|
||||
show-count
|
||||
placeholder="Enter product name"
|
||||
:bordered="false"
|
||||
:maxlength="60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">{{ $t("SellerListEdit.price") }}</div>
|
||||
<div class="form-item-value price flex align-center">
|
||||
<span>HK$</span>
|
||||
<a-input
|
||||
v-model:value="currentListing.price"
|
||||
placeholder="0.00"
|
||||
:bordered="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">
|
||||
{{ $t("SellerListEdit.productDescription") }}
|
||||
</div>
|
||||
<div class="form-item-value desc">
|
||||
<a-textarea
|
||||
v-model:value="currentListing.desc"
|
||||
show-count
|
||||
:rows="4"
|
||||
placeholder="Enter product description"
|
||||
:bordered="false"
|
||||
:maxlength="500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">
|
||||
{{ $t("SellerListEdit.designFor") }}
|
||||
</div>
|
||||
<div class="form-item-value no-border">
|
||||
<Radio :options="genderOptions" v-model="currentListing.gender" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label with-tip">
|
||||
<span class="required">{{ $t("SellerListEdit.productCategory") }}</span>
|
||||
<span class="help-text">{{ $t("SellerListEdit.categoryTips") }}</span>
|
||||
</div>
|
||||
<div class="form-item-value no-border">
|
||||
<Radio
|
||||
multiple
|
||||
:options="categoryOptions"
|
||||
v-model="currentListing.category"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="license-note flex align-center">
|
||||
<img src="@/assets/images/seller/tips.png" class="info-icon" />
|
||||
<div class="note-copy">
|
||||
{{ $t("SellerListEdit.policy") }}
|
||||
<a href="javascript:void(0)">{{ $t("SellerListEdit.learnMore") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="currentListing.gender = $event"
|
||||
@update:category="currentListing.category = $event"
|
||||
/>
|
||||
<div class="page-control flex align-center" v-if="selectList.length > 1">
|
||||
<a-pagination
|
||||
v-model:current="currentPage"
|
||||
@@ -238,13 +79,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, onMounted } from "vue"
|
||||
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 Radio from "./components/Radio.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,
|
||||
@@ -252,6 +96,13 @@
|
||||
fetchListingDetailById,
|
||||
fetchUpdateListing
|
||||
} from "./api"
|
||||
import type {
|
||||
ListingDetailImage,
|
||||
ListingDetailResponse,
|
||||
ListingItem,
|
||||
RadioOption,
|
||||
StatusType
|
||||
} from "./types"
|
||||
|
||||
const ROUTER = useRouter()
|
||||
const { t } = useI18n()
|
||||
@@ -264,31 +115,6 @@
|
||||
|
||||
const STORE = useStore()
|
||||
|
||||
type CategoryOption = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type ListingItem = {
|
||||
designItemId: number | string | null
|
||||
sketch: string | null
|
||||
mainProductImage: string
|
||||
cover: string
|
||||
productImage: string[]
|
||||
apparelSketch: string[]
|
||||
productName: string
|
||||
price: string
|
||||
desc: string
|
||||
gender: string
|
||||
category: string[]
|
||||
prodImageList: Array<{
|
||||
url: string
|
||||
selected?: boolean
|
||||
}>
|
||||
sketchList: Array<{ url: string | null }>
|
||||
}
|
||||
type StatusType = "draft" | "publish"
|
||||
|
||||
const createListingItem = (
|
||||
sketch: string | null = null,
|
||||
designItemId: number | string | null = null
|
||||
@@ -308,16 +134,9 @@
|
||||
sketchList: []
|
||||
})
|
||||
|
||||
const topImageList = ["sketch", "mainProductImage", "cover"] as const
|
||||
const topImageTitleMap: Record<(typeof topImageList)[number], string> = {
|
||||
sketch: "SellerListEdit.sketch",
|
||||
mainProductImage: "SellerListEdit.mainProductImage",
|
||||
cover: "SellerListEdit.cover"
|
||||
}
|
||||
|
||||
const genderOptions = STORE.state.UserHabit?.sex.value || []
|
||||
|
||||
const fallbackCategoryOptions: Record<string, CategoryOption[]> = {
|
||||
const fallbackCategoryOptions: Record<string, RadioOption[]> = {
|
||||
MALE: STORE.state.UserHabit?.MalePosition || [],
|
||||
FEMALE: STORE.state.UserHabit?.FemalePosition || []
|
||||
}
|
||||
@@ -343,21 +162,96 @@
|
||||
cover: currentListing.value.cover
|
||||
}))
|
||||
|
||||
const firstSelectedIndex = ref(null) //显示main标签的图片索引
|
||||
const selectedProdImgs = computed(() => {
|
||||
return prodImgList.value.filter((item) => item.selected).length
|
||||
})
|
||||
const firstSelectedIndex = ref<number | null>(null) //显示main标签的图片索引
|
||||
|
||||
const getSortedDetailImages = (images: ListingDetailImage[] = []) => {
|
||||
return [...images].sort((prev, next) => (prev.sortOrder ?? 0) - (next.sortOrder ?? 0))
|
||||
}
|
||||
|
||||
const getImageSelected = (value: ListingDetailImage["isSelected"]) =>
|
||||
value === true || value === 1 || value === "1"
|
||||
|
||||
const normalizeDetailGender = (value: ListingDetailResponse["designFor"]) => {
|
||||
const gender = String(value || "").toUpperCase()
|
||||
return gender === "MALE" || gender === "FEMALE" ? gender : "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()
|
||||
|
||||
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) => {
|
||||
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") {
|
||||
listing.mainProductImage = imageUrl
|
||||
return
|
||||
}
|
||||
|
||||
if (image.category === "main_product" || image.category === "product") {
|
||||
listing.prodImageList.push({
|
||||
url: imageUrl,
|
||||
selected: getImageSelected(image.isSelected)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (image.category === "apparel") {
|
||||
listing.sketchList.push({ url: imageUrl })
|
||||
}
|
||||
})
|
||||
|
||||
if (!listing.mainProductImage) {
|
||||
listing.mainProductImage =
|
||||
listing.prodImageList.find((item) => item.selected)?.url || ""
|
||||
}
|
||||
|
||||
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 handleSelectProdImg = (index: number) => {
|
||||
const target = prodImgList.value[index]
|
||||
|
||||
const willSelect = !target.selected
|
||||
|
||||
target.selected = willSelect
|
||||
|
||||
if (willSelect && !currentListing.value.mainProductImage) {
|
||||
if (willSelect && firstSelectedIndex.value === null) {
|
||||
currentListing.value.mainProductImage = target.url
|
||||
firstSelectedIndex.value = index
|
||||
return
|
||||
}
|
||||
|
||||
if (!willSelect && currentListing.value.mainProductImage === target.url) {
|
||||
@@ -367,7 +261,12 @@
|
||||
}
|
||||
|
||||
const cropType = ref("")
|
||||
const handleClickCrop = (data, type, list = []) => {
|
||||
const handleClickCrop = (data: any, type: string, paramThree: any = []) => {
|
||||
// 处理来自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 = []
|
||||
@@ -392,8 +291,11 @@
|
||||
(file) => {
|
||||
// console.log(file)
|
||||
uploadFile(file).then((res) => {
|
||||
console.log(res)
|
||||
selectList.value[currentIndex.value][type] = res
|
||||
if (type === "apparel" && typeof index !== "undefined") {
|
||||
selectList.value[currentIndex.value].sketchList[index].url = res
|
||||
} else {
|
||||
selectList.value[currentIndex.value][type] = res
|
||||
}
|
||||
})
|
||||
},
|
||||
{ ratio, isPreview: true, title: titleList[type], isProduct: true },
|
||||
@@ -450,35 +352,40 @@
|
||||
|
||||
const handleSaveForm = async (type: StatusType) => {
|
||||
const paramsList = []
|
||||
selectList.value.forEach((item) => {
|
||||
selectList.value.forEach((item: ListingItem) => {
|
||||
const params = {
|
||||
id: null,
|
||||
title: selectList.value[currentIndex.value].productName,
|
||||
description: selectList.value[currentIndex.value].desc,
|
||||
price: selectList.value[currentIndex.value].price,
|
||||
title: item.productName,
|
||||
description: item.desc,
|
||||
price: item.price,
|
||||
status: type === "draft" ? 0 : 1,
|
||||
images: [],
|
||||
designFor: selectList.value[currentIndex.value].gender.toLowerCase,
|
||||
productCategory: selectList.value[currentIndex.value].category
|
||||
designFor: item.gender.toLowerCase,
|
||||
productCategory: item.category
|
||||
}
|
||||
//
|
||||
topImageList.forEach((el) => {
|
||||
|
||||
;["sketch", "cover"].forEach((el) => {
|
||||
params.images.push({
|
||||
category: el,
|
||||
imageUrl: selectList.value[currentIndex.value][el],
|
||||
imageUrl: item[el],
|
||||
isSelected: 1
|
||||
})
|
||||
})
|
||||
selectList.value[currentIndex.value].prodImageList.forEach((item) => {
|
||||
if (item.selected) {
|
||||
params.images.push({
|
||||
category: "main_product",
|
||||
imageUrl: item.url,
|
||||
isSelected: Number(item.selected)
|
||||
})
|
||||
}
|
||||
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)
|
||||
})
|
||||
})
|
||||
selectList.value[currentIndex.value].sketchList.forEach((item) => {
|
||||
item.sketchList.forEach((item) => {
|
||||
params.images.push({
|
||||
category: "apparel",
|
||||
imageUrl: item.url,
|
||||
@@ -488,9 +395,14 @@
|
||||
paramsList.push(params)
|
||||
})
|
||||
console.log(paramsList)
|
||||
fetchUpdateListing(paramsList)
|
||||
debugger
|
||||
await fetchUpdateListing(paramsList)
|
||||
}
|
||||
const handleClickMenu = async (status: StatusType) => {
|
||||
if (status === "draft" && !selectList.value[currentIndex.value].cover) {
|
||||
message.error("请先完成封面制作")
|
||||
return
|
||||
}
|
||||
if (status === "publish" && !validatePublishRequired()) return
|
||||
|
||||
await handleSaveForm(status)
|
||||
@@ -516,23 +428,37 @@
|
||||
}))
|
||||
})
|
||||
})
|
||||
// fetchListingDetailById(itemId.value).then(res => {
|
||||
// console.log('iddetail',res)
|
||||
// })
|
||||
}
|
||||
|
||||
const handleGetDetailById = () => {
|
||||
fetchListingDetailById(itemId.value).then((res: ListingDetailResponse) => {
|
||||
const listing = createListingItemFromDetail(res)
|
||||
const selectedIndex = listing.prodImageList.findIndex((item) => item.selected)
|
||||
|
||||
currentPage.value = 1
|
||||
selectList.value = [listing]
|
||||
firstSelectedIndex.value = selectedIndex === -1 ? null : selectedIndex
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const designItemIds = history.state?.designItemIds || []
|
||||
itemId.value = history.state?.id || ""
|
||||
if (!designItemIds.length) return
|
||||
const data = history.state
|
||||
if (data?.type === "edit") {
|
||||
itemId.value = history.state?.id || ""
|
||||
handleGetDetailById()
|
||||
} else {
|
||||
const designItemIds = history.state?.designItemIds || []
|
||||
|
||||
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)
|
||||
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>
|
||||
|
||||
@@ -602,362 +528,10 @@
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.required {
|
||||
&::after {
|
||||
content: "*";
|
||||
color: #df2b2c;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
// flex: 1;
|
||||
// min-width: 0;
|
||||
|
||||
.main-image-container {
|
||||
// max-width: 80.2rem;
|
||||
column-gap: 3.5rem;
|
||||
.main-image-item {
|
||||
flex-shrink: 0;
|
||||
|
||||
.title {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sketch-item {
|
||||
width: 11.6rem;
|
||||
height: 20.4rem;
|
||||
border: 0.15rem solid #d1d1d1;
|
||||
border-radius: 1rem;
|
||||
position: relative;
|
||||
background-color: #f6f6f6;
|
||||
overflow: hidden;
|
||||
|
||||
&.cover {
|
||||
width: 16.2rem;
|
||||
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' rx='11' ry='11' fill='none' stroke='%23D1D1D1' stroke-width='1.5' stroke-dasharray='8%2c 5' stroke-linecap='square'/%3e%3c/svg%3e");
|
||||
border: none;
|
||||
}
|
||||
|
||||
.crop-tool {
|
||||
position: absolute;
|
||||
top: 0.8rem;
|
||||
right: 0.8rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
background-color: #000000;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sketch-img {
|
||||
height: 100%;
|
||||
&.sketch {
|
||||
height: initial;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.trigger {
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
padding: 6rem 2rem 0;
|
||||
&,
|
||||
.cover-trigger {
|
||||
row-gap: 1.2rem;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
border-radius: 0.6rem;
|
||||
background: linear-gradient(135deg, #efefef 0%, #cdcdcd 100%);
|
||||
}
|
||||
|
||||
.trigger-tips {
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
color: #585858;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-image-list-container {
|
||||
margin-top: 3rem;
|
||||
|
||||
.title {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 1.2rem;
|
||||
|
||||
.main-title {
|
||||
font-weight: 400;
|
||||
font-style: bold;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 1.2rem;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.title-right {
|
||||
color: #585858;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.product-image-list {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
column-gap: 0.8rem;
|
||||
max-width: 80.2rem;
|
||||
padding-bottom: 1.2rem;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 0.8rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
border-radius: 0.8rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #000000;
|
||||
border-radius: 0.8rem;
|
||||
}
|
||||
|
||||
.product-image-item {
|
||||
width: 11.6rem;
|
||||
height: 20.6rem;
|
||||
border-radius: 1rem;
|
||||
border: 0.15rem solid #c7c7c7;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
background-color: #fcfcfc;
|
||||
opacity: 0.7;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: #000;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.checked {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
position: absolute;
|
||||
top: 0.8rem;
|
||||
right: 0.8rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.img-src {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main-pic {
|
||||
position: absolute;
|
||||
height: 2.4rem;
|
||||
line-height: 2.4rem;
|
||||
left: 0.8rem;
|
||||
right: 0.8rem;
|
||||
bottom: 0.8rem;
|
||||
z-index: 1;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
border-radius: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apparel-container {
|
||||
margin-top: 3rem;
|
||||
|
||||
.title {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.8rem;
|
||||
|
||||
.main-title {
|
||||
font-weight: 400;
|
||||
font-style: bold;
|
||||
|
||||
&::after {
|
||||
content: "*";
|
||||
color: #df2b2c;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 1.2rem;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.sketch-list-container {
|
||||
column-gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.sketch-element {
|
||||
width: 10rem;
|
||||
height: 14.6rem;
|
||||
border: 0.15rem solid #c7c7c7;
|
||||
border-radius: 1.2rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.img-src {
|
||||
// height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.crop-tool {
|
||||
position: absolute;
|
||||
top: 0.4rem;
|
||||
right: 0.4rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
background-color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 55.2rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
.form-container {
|
||||
row-gap: 3rem;
|
||||
|
||||
.form-item {
|
||||
.form-item-label {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
font-style: bold;
|
||||
margin-bottom: 0.6rem;
|
||||
line-height: 1.5;
|
||||
|
||||
&.with-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 0.8rem;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 1rem;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item-value {
|
||||
border: 0.16rem solid #d1d1d1;
|
||||
border-radius: 1.2rem;
|
||||
position: relative;
|
||||
padding: 1.6rem;
|
||||
box-sizing: border-box;
|
||||
font-size: 1.2rem;
|
||||
color: #000;
|
||||
|
||||
&.no-border {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.price {
|
||||
column-gap: 0.6rem;
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-input-affix-wrapper),
|
||||
:deep(.ant-input-textarea textarea) {
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 1.2rem;
|
||||
color: #000;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.ant-input) {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
:deep(.ant-input-show-count-suffix) {
|
||||
color: #df2c2c;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:deep(textarea.ant-input) {
|
||||
resize: none;
|
||||
min-height: 5.4rem;
|
||||
padding-bottom: 1.8rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
:deep(.ant-input-textarea-show-count) {
|
||||
position: relative;
|
||||
&::after {
|
||||
float: none;
|
||||
position: absolute;
|
||||
color: #df2c2c;
|
||||
bottom: 0;
|
||||
right: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.license-note {
|
||||
padding: 1.6rem;
|
||||
column-gap: 1.6rem;
|
||||
background: #f7f7f7;
|
||||
border-radius: 0.8rem;
|
||||
|
||||
.info-icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.note-copy {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.5;
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
font-style: medium;
|
||||
|
||||
a {
|
||||
color: #0080ed;
|
||||
text-decoration: underline;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-control {
|
||||
justify-content: flex-end;
|
||||
margin-top: 4rem;
|
||||
|
||||
47
src/views/SellerDashboard/MyListings/EditDetail/types.ts
Normal file
47
src/views/SellerDashboard/MyListings/EditDetail/types.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export type RadioOption = {
|
||||
name: string | number
|
||||
value: string | number | boolean
|
||||
key: string
|
||||
optype: boolean
|
||||
}
|
||||
|
||||
export type TopImageType = "sketch" | "mainProductImage" | "cover"
|
||||
export type CropType = TopImageType | "apparel"
|
||||
|
||||
export type ListingItem = {
|
||||
designItemId: number | string | null
|
||||
sketch: string | null
|
||||
mainProductImage: string
|
||||
cover: string
|
||||
productImage: string[]
|
||||
apparelSketch: string[]
|
||||
productName: string
|
||||
price: string
|
||||
desc: string
|
||||
gender: string
|
||||
category: string[] | null
|
||||
prodImageList: Array<{
|
||||
url: string
|
||||
selected?: boolean
|
||||
}>
|
||||
sketchList: Array<{ url: string | null }>
|
||||
}
|
||||
|
||||
export type ListingDetailImage = {
|
||||
category?: string | null
|
||||
imageUrl?: string | null
|
||||
isSelected?: boolean | number | string | null
|
||||
sortOrder?: number | null
|
||||
}
|
||||
|
||||
export type ListingDetailResponse = {
|
||||
id?: number | string | null
|
||||
title?: string | null
|
||||
description?: string | null
|
||||
price?: number | string | null
|
||||
designFor?: string | null
|
||||
productCategory?: string | string[] | null
|
||||
images?: ListingDetailImage[] | null
|
||||
}
|
||||
|
||||
export type StatusType = "draft" | "publish"
|
||||
@@ -41,7 +41,10 @@ const getCreateList = ()=>{
|
||||
})
|
||||
}
|
||||
const selectCollectionItem = (item:any)=>{
|
||||
emit('selectCollectionItem',item)
|
||||
console.log(item)
|
||||
if(item.userLikeGroupVO?.groupDetails?.length > 0){
|
||||
emit('selectCollectionItem',item)
|
||||
}
|
||||
}
|
||||
onMounted(()=>{
|
||||
getCreateList()
|
||||
@@ -55,9 +58,12 @@ defineExpose({getCreateList})
|
||||
<div class="list">
|
||||
<div v-for="(item,index) in list" :key="index" class="item" @click="selectCollectionItem(item)">
|
||||
<div class="imgList">
|
||||
<div v-for="(img,index) in item.userLikeGroupVO?.groupDetails" :key="index" class="img">
|
||||
<div v-if="item.userLikeGroupVO?.groupDetails?.length > 0" v-for="(img,index) in item.userLikeGroupVO?.groupDetails" :key="index" class="img">
|
||||
<img :src="img.url" alt="">
|
||||
</div>
|
||||
<div v-else class="img null">
|
||||
<img src="@/assets/images/seller/selectCollectionNullStatus.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="name">{{item.name}}</div>
|
||||
@@ -123,6 +129,12 @@ defineExpose({getCreateList})
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
&.null{
|
||||
background-color: transparent;
|
||||
> img{
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
> img{
|
||||
object-fit: cover;
|
||||
height: 100%;
|
||||
|
||||
@@ -45,8 +45,8 @@ const next = ()=>{
|
||||
router.push({
|
||||
path:'/home/seller/myListings/edit',
|
||||
state: {
|
||||
id:route.params.collectionId,
|
||||
designItemIds,
|
||||
type:'create'
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -83,7 +83,7 @@ const setDomSize = (width: number)=>{
|
||||
if(!listingsBoxRef.value)return
|
||||
let listDom = listingsBoxRef.value.querySelector('.list')
|
||||
let listItemDom = listDom.querySelector('.item')
|
||||
let offsetWidth = listItemDom.getBoundingClientRect().width
|
||||
let offsetWidth = listItemDom?.getBoundingClientRect?.()?.width
|
||||
let lineNum = Math.floor(width / offsetWidth)
|
||||
let itemNum = Math.floor((width - (lineNum - 1) * parseInt(gap.value[domSize.value])) / offsetWidth)
|
||||
listDom.style.maxWidth = ((itemNum - 1) * parseInt(gap.value[domSize.value]) + itemNum * (offsetWidth)) + 'px'
|
||||
|
||||
@@ -5,6 +5,8 @@ import contentItem from "./contentItem.vue"
|
||||
import selectMenu from '@/component/modules/selectMenu.vue'
|
||||
import deleteDrafts from './deleteDrafts.vue'
|
||||
import { Https } from '@/tool/https'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
//const props = defineProps({
|
||||
//})
|
||||
@@ -63,10 +65,13 @@ const domSizeList = ref([
|
||||
|
||||
const visible = ref(false)
|
||||
const deleteDraftsRef = ref(null)
|
||||
const router = useRouter()
|
||||
|
||||
const deleteDraft = (item: any)=>{
|
||||
deleteDraftsRef.value.open(()=>{
|
||||
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
||||
deleteDraftsRef.value.open(item,()=>{
|
||||
putListingStatus(item,2).then(()=>{
|
||||
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,6 +104,7 @@ const getPublishedData = async ()=>{
|
||||
}
|
||||
await getPublishList(value).then((res)=>{
|
||||
if(res.content.length == 0)publishData.isNoData = true
|
||||
publishData.pageNum += 1
|
||||
list.value.push(...res.content)
|
||||
})
|
||||
publishData.isShowMark = false
|
||||
@@ -113,6 +119,7 @@ const getNoPublishedData = async ()=>{
|
||||
}
|
||||
await getPublishList(value).then((res)=>{
|
||||
if(res.content.length == 0)noPublishData.isNoData = true
|
||||
noPublishData.pageNum += 1
|
||||
list2.value.push(...res.content)
|
||||
})
|
||||
noPublishData.isShowMark = false
|
||||
@@ -127,28 +134,46 @@ const getPublishList = (data:any)=>{
|
||||
})
|
||||
}
|
||||
|
||||
const putListingStatus = ()=>{
|
||||
let data = {
|
||||
id:'',
|
||||
status:'',
|
||||
}
|
||||
Https.axiosPut(Https.httpUrls.putListingStatus,data).then((res:any)=>{
|
||||
const putListingStatus = async (item,status:number)=>{
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let data = {
|
||||
id:item.id,
|
||||
status:status,
|
||||
}
|
||||
Https.axiosPut(Https.httpUrls.putListingStatus,data).then((res:any)=>{
|
||||
resolve(res)
|
||||
}).catch((err:any)=>{
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const draftListing = (item: any)=>{
|
||||
const draftListing = async (item: any)=>{
|
||||
//数组前面添加item
|
||||
list2.value.unshift(item)
|
||||
list.value = list.value.filter((v: any)=>v.id != item.id)
|
||||
await putListingStatus(item,0).then(()=>{
|
||||
list2.value.unshift(item)
|
||||
list.value = list.value.filter((v: any)=>v.id != item.id)
|
||||
})
|
||||
message.success('Product moved to drafts and stats reset.')
|
||||
}
|
||||
|
||||
const publishListing = (item: any)=>{
|
||||
list.value.unshift(item)
|
||||
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
||||
const publishListing = async (item: any)=>{
|
||||
await putListingStatus(item,1).then(()=>{
|
||||
list.value.unshift(item)
|
||||
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
||||
})
|
||||
message.success('Item is now live on the Marketplace.')
|
||||
}
|
||||
|
||||
const editListing = (item: any)=>{
|
||||
router.push({
|
||||
path:'/home/seller/myListings/edit',
|
||||
state: {
|
||||
id:item.id,
|
||||
type:'edit'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +192,7 @@ const setDomSize = (width: number)=>{
|
||||
if(!listingsBoxRef.value)return
|
||||
let listDom = listingsBoxRef.value.querySelector('.list')
|
||||
let listItemDom = listDom.querySelector('.item')
|
||||
let offsetWidth = listItemDom.getBoundingClientRect().width
|
||||
let offsetWidth = listItemDom?.getBoundingClientRect?.()?.width
|
||||
let lineNum = Math.floor(width / offsetWidth)
|
||||
let itemNum = Math.floor((width - (lineNum - 1) * parseInt(gap.value[domSize.value])) / offsetWidth)
|
||||
listDom.style.maxWidth = ((itemNum - 1) * parseInt(gap.value[domSize.value]) + itemNum * (offsetWidth)) + 'px'
|
||||
|
||||
@@ -32,7 +32,7 @@ const {} = toRefs(data);
|
||||
<template>
|
||||
<div class="item" :draging="true" :class="domSize">
|
||||
<div class="imgBox">
|
||||
<img src="" alt="">
|
||||
<img :src="item.cover" alt="">
|
||||
<div class="maskBtn">
|
||||
<div @click="$emit('editListing',item)">
|
||||
<svgIcon name="seller-edit" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
||||
@@ -50,21 +50,21 @@ const {} = toRefs(data);
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="left">
|
||||
<div class="name">item name</div>
|
||||
<div class="price">$1123</div>
|
||||
<div class="name">{{item.title}}</div>
|
||||
<div class="price">${{item.price}}</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="detailItem" v-if="type == 'listings'">
|
||||
<div class="shopping1">
|
||||
<i class="fi fi-rr-shopping-bag-add"></i>
|
||||
</div>
|
||||
<span>123</span>
|
||||
<span>{{ item.salesVolume || 0 }}</span>
|
||||
</div>
|
||||
<div class="detailItem" v-if="type == 'listings'">
|
||||
<div class="eye1">
|
||||
<i class="fi fi-rs-eye"></i>
|
||||
</div>
|
||||
<span>123</span>
|
||||
<span>{{ item.viewCount }}</span>
|
||||
</div>
|
||||
<div class="detailItem drafts" v-if="type == 'drafts'" @click="$emit('deleteDraft',item)">
|
||||
<div class="">
|
||||
@@ -128,6 +128,11 @@ const {} = toRefs(data);
|
||||
position: relative;
|
||||
height: var(--itemImgHeight);
|
||||
width: 100%;
|
||||
> img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
> .maskBtn{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
@@ -182,6 +187,7 @@ const {} = toRefs(data);
|
||||
gap: var(--detailRightItemGap);
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
> div{
|
||||
color: var(--rightColor);
|
||||
display: flex;
|
||||
|
||||
@@ -15,12 +15,13 @@ const router = useRouter()
|
||||
const {t} = useI18n()
|
||||
let data = reactive({
|
||||
})
|
||||
|
||||
const item = ref<any>()
|
||||
const fun = ref(null)
|
||||
|
||||
let deleteDraftsRef = ref(null)
|
||||
|
||||
const open = (deleteFun)=>{
|
||||
const open = (data:any,deleteFun)=>{
|
||||
item.value = data
|
||||
fun.value = deleteFun
|
||||
emit('update:visible', true)
|
||||
}
|
||||
@@ -35,6 +36,7 @@ const deleteDrafts = ()=>{
|
||||
|
||||
|
||||
const cleardata = ()=>{
|
||||
item.value = null
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
@@ -78,11 +80,11 @@ const { showAgain } = toRefs(data);
|
||||
</div>
|
||||
<div class="deleteContent">
|
||||
<div class="img">
|
||||
<img src="" alt="">
|
||||
<img :src="item?.value?.cover" alt="">
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="name">Item Name</div>
|
||||
<div class="price">HK$392.00 · Draft</div>
|
||||
<div class="name">{{ item?.value?.title }}</div>
|
||||
<div class="price">HK${{ item?.value?.price }} · Draft</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btnBox">
|
||||
|
||||
Reference in New Issue
Block a user