feat: 提交页面
This commit is contained in:
@@ -1748,5 +1748,17 @@ export default {
|
||||
productImageSubTitle:' (来自设计集)',
|
||||
apparelSketchTitle:'服装线稿图 ',
|
||||
apparelSketchSubTitle:' (来自设计集)',
|
||||
productName:'商品名称',
|
||||
price:'价格',
|
||||
productDescription:'商品描述',
|
||||
designFor:'目标人群',
|
||||
productCategory:'商品类别',
|
||||
categoryTips:'请选择所有适用选项',
|
||||
policy:'默认情况下,所有销售均遵循平台的许可政策——买家在下载后将获得使用许可',
|
||||
learnMore:'了解更多',
|
||||
draftSaved: '草稿已保存',
|
||||
draftDesc: '您的商品已保存为草稿。\n您可以继续编辑,或稍后在“我的商品”中发布。',
|
||||
listingLive:'商品已上架',
|
||||
publishDesc:'您的商品现已上架。\n买家可以浏览并购买您的设计。'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1799,5 +1799,17 @@ export default {
|
||||
productImageSubTitle:'(from design collection)',
|
||||
apparelSketchTitle:'Apparel Sketch ',
|
||||
apparelSketchSubTitle:'(from design collection)',
|
||||
productName:'Product Name',
|
||||
price:'Price',
|
||||
productDescription:'Product Description',
|
||||
designFor:'Designed For',
|
||||
productCategory:'Product Category',
|
||||
categoryTips:'select all that apply',
|
||||
policy:'By default, all sales follow the platform\'s licensing policy — buyers will receive a usage license upon download.',
|
||||
learnMore:'Learn more',
|
||||
draftSaved: 'Draft Saved',
|
||||
draftDesc: 'Your listing has been saved as a draft. \nYou can continue editing or publish it later from My Listings.',
|
||||
listingLive:'Listing Live',
|
||||
publishDesc:'Your listing is now live on the marketplace.\nBuyers can discover and purchase your design.'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +220,13 @@ const routes: Array<RouteRecordRaw> = [
|
||||
meta: { enter: "all" },
|
||||
component: () =>
|
||||
import("@/views/SellerDashboard/MyListings/EditDetail/index.vue")
|
||||
},
|
||||
{
|
||||
path:'edit/status/:status',
|
||||
name:'Status',
|
||||
meta:{enter:'all'},
|
||||
component: () =>
|
||||
import("@/views/SellerDashboard/MyListings/EditDetail/Status.vue")
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
78
src/views/SellerDashboard/MyListings/EditDetail/Status.vue
Normal file
78
src/views/SellerDashboard/MyListings/EditDetail/Status.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="status-wrapper flex flex-col flex-1">
|
||||
<seller-header
|
||||
class="edit-detail-header"
|
||||
title="Edit Listing Details"
|
||||
:breadcrumbs="[
|
||||
{ title: 'My Listings', name: 'myListingsIndex' },
|
||||
{ title: 'Select Collection', name: 'myListingsSelect' },
|
||||
{ title: 'Select Sketch', name: 'myListingsSelectItem' },
|
||||
{ title: 'Edit Listing Details', name: 'EditDetail' },
|
||||
{ title: $t(title), name: 'Status' }
|
||||
]"
|
||||
/>
|
||||
<div class="status-container flex flex-col flex-1 flex-center">
|
||||
<img src="@/assets/images/seller/success-0.png" class="icon" alt="" />
|
||||
<div class="title">{{ $t(title) }}</div>
|
||||
<div class="desc">
|
||||
{{ $t(desc) }}
|
||||
</div>
|
||||
<div class="btn">Back to My Listings</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import SellerHeader from "../../seller-header.vue"
|
||||
const ROUTE = useRoute()
|
||||
const title = computed(() => {
|
||||
if (ROUTE.params.status === "publish") return "SellerListEdit.listingLive"
|
||||
if (ROUTE.params.status === "draft") return "SellerListEdit.draftSaved"
|
||||
})
|
||||
|
||||
const desc = computed(() => {
|
||||
if (ROUTE.params.status === "publish") return "SellerListEdit.publishDesc"
|
||||
if (ROUTE.params.status === "draft") return "SellerListEdit.draftDesc"
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.status-wrapper {
|
||||
.status-container {
|
||||
row-gap: 2.4rem;
|
||||
font-weight: 400;
|
||||
.title {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
.icon {
|
||||
width: 8.33rem;
|
||||
height: 8.33rem;
|
||||
}
|
||||
.desc {
|
||||
width: 58.2rem;
|
||||
text-align: center;
|
||||
white-space: pre-line;
|
||||
color: #585858;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 3.6rem;
|
||||
height: 6rem;
|
||||
width: 30rem;
|
||||
border-radius: 4rem;
|
||||
text-align: center;
|
||||
line-height: 6rem;
|
||||
padding: 0 2rem;
|
||||
font-size: 1.6rem;
|
||||
column-gap: 0.8rem;
|
||||
cursor: pointer;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,14 @@
|
||||
v-for="item in options"
|
||||
:key="item.key"
|
||||
type="button"
|
||||
:class="['radio-button', { 'is-active': modelValue === item.key }]"
|
||||
:class="[
|
||||
'radio-button',
|
||||
{
|
||||
'is-active': multiple
|
||||
? selectedValues.includes(item.key)
|
||||
: modelValue === item.key
|
||||
}
|
||||
]"
|
||||
@click="selectOption(item.key)"
|
||||
>
|
||||
{{ item.name }}
|
||||
@@ -13,6 +20,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
|
||||
interface Option {
|
||||
name: string | number
|
||||
value: string | number | boolean
|
||||
@@ -21,15 +30,40 @@ interface Option {
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | number | boolean | null // v-model 绑定的值
|
||||
modelValue: string | number | boolean | Array<string | number | boolean> | null
|
||||
options: Option[] // 按钮选项数组
|
||||
multiple?: boolean // 是否支持多选,默认为 false
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: any): void
|
||||
}>()
|
||||
|
||||
const multiple = props.multiple === true
|
||||
|
||||
const selectedValues = computed(() => {
|
||||
if (!multiple) {
|
||||
return typeof props.modelValue === 'undefined' || props.modelValue === null
|
||||
? []
|
||||
: [props.modelValue]
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
emit("update:modelValue", current)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.modelValue !== value) {
|
||||
emit("update:modelValue", value)
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@
|
||||
>
|
||||
<template #right>
|
||||
<div class="operate-menu flex">
|
||||
<div class="menu-btn flex align-center save">
|
||||
<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">
|
||||
<div
|
||||
class="menu-btn flex align-center publish"
|
||||
@click="handleClickMenu('publish')"
|
||||
>
|
||||
<span>{{ $t("SellerListEdit.publish") }}</span>
|
||||
<SvgIcon name="CPublish" size="16" />
|
||||
</div>
|
||||
@@ -131,7 +134,9 @@
|
||||
<div class="right">
|
||||
<div class="form-container flex flex-col">
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">Product Name</div>
|
||||
<div class="form-item-label required">
|
||||
{{ $t("SellerListEdit.productName") }}
|
||||
</div>
|
||||
<div class="form-item-value product-name">
|
||||
<a-input
|
||||
v-model:value="currentListing.productName"
|
||||
@@ -143,7 +148,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">Price</div>
|
||||
<div class="form-item-label required">{{ $t("SellerListEdit.price") }}</div>
|
||||
<div class="form-item-value price flex align-center">
|
||||
<span>HK$</span>
|
||||
<a-input
|
||||
@@ -154,7 +159,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">Product Description</div>
|
||||
<div class="form-item-label required">
|
||||
{{ $t("SellerListEdit.productDescription") }}
|
||||
</div>
|
||||
<div class="form-item-value desc">
|
||||
<a-textarea
|
||||
v-model:value="currentListing.desc"
|
||||
@@ -167,15 +174,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-item-label required">Design for</div>
|
||||
<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">Product Category</span>
|
||||
<span class="help-text">select all that apply</span>
|
||||
<span class="required">{{ $t("SellerListEdit.productCategory") }}</span>
|
||||
<span class="help-text">{{ $t("SellerListEdit.categoryTips") }}</span>
|
||||
</div>
|
||||
<div class="form-item-value no-border">
|
||||
<Radio :options="categoryOptions" v-model="currentListing.category" />
|
||||
@@ -184,9 +193,8 @@
|
||||
<div class="license-note flex align-center">
|
||||
<img src="@/assets/images/seller/tips.png" class="info-icon" />
|
||||
<div class="note-copy">
|
||||
By default, all sales follow the platform's licensing policy — buyers
|
||||
will receive a usage license upon download.
|
||||
<a href="javascript:void(0)">Learn more</a>
|
||||
{{ $t("SellerListEdit.policy") }}
|
||||
<a href="javascript:void(0)">{{ $t("SellerListEdit.learnMore") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -207,13 +215,20 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { computed, ref, watch, defineOptions } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import SellerHeader from "../../seller-header.vue"
|
||||
import testImg from "@/assets/images/test.png"
|
||||
import Radio from "./components/Radio.vue"
|
||||
import { Https } from "@/tool/https"
|
||||
import { useStore } from "vuex"
|
||||
|
||||
const ROUTER = useRouter()
|
||||
|
||||
defineOptions({
|
||||
name: "EditDetail"
|
||||
})
|
||||
|
||||
const STORE = useStore()
|
||||
|
||||
type CategoryOption = {
|
||||
@@ -245,7 +260,7 @@ const topImageTitleMap: Record<(typeof topImageList)[number], string> = {
|
||||
cover: "Cover"
|
||||
}
|
||||
|
||||
const genderOptions = STORE.state.UserHabit?.sex.value||[]
|
||||
const genderOptions = STORE.state.UserHabit?.sex.value || []
|
||||
|
||||
const fallbackCategoryOptions: Record<string, CategoryOption[]> = {
|
||||
MALE: STORE.state.UserHabit?.MalePosition || [],
|
||||
@@ -362,6 +377,18 @@ const handleSelectProdImg = (index: number) => {
|
||||
currentListing.value.mainProductImage = ""
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickMenu = (status: "draft" | "publish") => {
|
||||
if (status === "draft") {
|
||||
// save draft logic
|
||||
console.log("Saving draft...", currentListing.value)
|
||||
ROUTER.push({ name: "Status", params: { status: "draft" } })
|
||||
} else if (status === "publish") {
|
||||
// publish logic
|
||||
console.log("Publishing...", currentListing.value)
|
||||
ROUTER.push({ name: "Status", params: { status: "publish" } })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -394,15 +421,15 @@ const handleSelectProdImg = (index: number) => {
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
|
||||
&.save:hover {
|
||||
&:hover {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.publish:hover {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
// &.publish:hover {
|
||||
// background: #fff;
|
||||
// color: #000;
|
||||
// }
|
||||
}
|
||||
|
||||
.edit-detail-header {
|
||||
@@ -412,10 +439,10 @@ const handleSelectProdImg = (index: number) => {
|
||||
.operate-menu {
|
||||
column-gap: 2rem;
|
||||
|
||||
.publish {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
// .publish {
|
||||
// background-color: #000000;
|
||||
// color: #ffffff;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user