feat: 面包屑导航
This commit is contained in:
@@ -1,8 +1,37 @@
|
|||||||
import { createRouter, createWebHistory, RouteRecordRaw, createWebHashHistory } from "vue-router"
|
import { createRouter, createWebHistory, RouteRecordRaw, createWebHashHistory } from "vue-router"
|
||||||
|
import type { RouteLocationNormalizedLoaded, RouteLocationRaw } from "vue-router"
|
||||||
import store from "@/store"
|
import store from "@/store"
|
||||||
import { Https } from "@/tool/https"
|
import { Https } from "@/tool/https"
|
||||||
import { getCookie, setCookie } from "@/tool/cookie"
|
import { getCookie, setCookie } from "@/tool/cookie"
|
||||||
|
|
||||||
|
type SellerRouteMetaValue<T> = T | ((route: RouteLocationNormalizedLoaded) => T)
|
||||||
|
type SellerBreadcrumbItem = {
|
||||||
|
title?: SellerRouteMetaValue<string>
|
||||||
|
titleKey?: SellerRouteMetaValue<string>
|
||||||
|
to?: SellerRouteMetaValue<RouteLocationRaw>
|
||||||
|
}
|
||||||
|
|
||||||
|
const myListingsBreadcrumb: SellerBreadcrumbItem = {
|
||||||
|
title: "My Listings",
|
||||||
|
to: { name: "myListingsIndex" }
|
||||||
|
}
|
||||||
|
const selectCollectionBreadcrumb: SellerBreadcrumbItem = {
|
||||||
|
title: "Select Collection",
|
||||||
|
to: { name: "myListingsSelect" }
|
||||||
|
}
|
||||||
|
const selectSketchBreadcrumb: SellerBreadcrumbItem = {
|
||||||
|
title: "Select Sketch"
|
||||||
|
}
|
||||||
|
const editListingBreadcrumb: SellerBreadcrumbItem = {
|
||||||
|
title: "Edit Listing Details"
|
||||||
|
}
|
||||||
|
const statusBreadcrumb: SellerBreadcrumbItem = {
|
||||||
|
titleKey: (route) =>
|
||||||
|
route.params.status === "publish"
|
||||||
|
? "SellerListEdit.listingLive"
|
||||||
|
: "SellerListEdit.draftSaved"
|
||||||
|
}
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
@@ -167,7 +196,11 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "becomeSeller",
|
path: "becomeSeller",
|
||||||
name: "becomeSeller",
|
name: "becomeSeller",
|
||||||
meta: { enter: "all" },
|
meta: {
|
||||||
|
enter: "all",
|
||||||
|
sellerHeaderTitle: "Apply to Become a Seller",
|
||||||
|
sellerHeaderTip: "Join the Stylish Parade and start selling your design work"
|
||||||
|
},
|
||||||
component: () => import("@/views/SellerDashboard/BecomeSeller/index.vue")
|
component: () => import("@/views/SellerDashboard/BecomeSeller/index.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -185,7 +218,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "myListings",
|
path: "myListings",
|
||||||
name: "myListings",
|
name: "myListings",
|
||||||
meta: { enter: "all" },
|
meta: { enter: "all", sellerBreadcrumb: myListingsBreadcrumb },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
@@ -196,35 +229,74 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "index",
|
path: "index",
|
||||||
name: "myListingsIndex",
|
name: "myListingsIndex",
|
||||||
meta: { enter: "all" },
|
meta: {
|
||||||
|
enter: "all",
|
||||||
|
sellerHeaderTitle: "My Listings",
|
||||||
|
sellerHeaderTip: "Active listings and unpublished inventory.",
|
||||||
|
sellerBreadcrumbs: [myListingsBreadcrumb]
|
||||||
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/SellerDashboard/MyListings/main/index.vue")
|
import("@/views/SellerDashboard/MyListings/main/index.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "select",
|
path: "select",
|
||||||
name: "myListingsSelect",
|
name: "myListingsSelect",
|
||||||
meta: { enter: "all" },
|
meta: {
|
||||||
|
enter: "all",
|
||||||
|
sellerHeaderTitle: "Select Collection",
|
||||||
|
sellerBreadcrumbs: [
|
||||||
|
myListingsBreadcrumb,
|
||||||
|
selectCollectionBreadcrumb
|
||||||
|
]
|
||||||
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/SellerDashboard/MyListings/createSelect/index.vue")
|
import("@/views/SellerDashboard/MyListings/createSelect/index.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "select/:collectionId",
|
path: "select/:collectionId",
|
||||||
name: "myListingsSelectItem",
|
name: "myListingsSelectItem",
|
||||||
meta: { enter: "all" },
|
meta: {
|
||||||
|
enter: "all",
|
||||||
|
sellerHeaderTitle: "Select Collection",
|
||||||
|
sellerBreadcrumbs: [
|
||||||
|
myListingsBreadcrumb,
|
||||||
|
selectCollectionBreadcrumb,
|
||||||
|
selectSketchBreadcrumb
|
||||||
|
]
|
||||||
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/SellerDashboard/MyListings/createSelectItem/index.vue")
|
import("@/views/SellerDashboard/MyListings/createSelectItem/index.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "edit",
|
path: "edit",
|
||||||
name: "EditDetail",
|
name: "EditDetail",
|
||||||
meta: { enter: "all" },
|
meta: {
|
||||||
|
enter: "all",
|
||||||
|
sellerHeaderTitle: "Edit Listing Details",
|
||||||
|
sellerBreadcrumbs: [
|
||||||
|
myListingsBreadcrumb,
|
||||||
|
selectCollectionBreadcrumb,
|
||||||
|
selectSketchBreadcrumb,
|
||||||
|
editListingBreadcrumb
|
||||||
|
]
|
||||||
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/SellerDashboard/MyListings/EditDetail/index.vue")
|
import("@/views/SellerDashboard/MyListings/EditDetail/index.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:'edit/status/:status',
|
path: "edit/status/:status",
|
||||||
name:'Status',
|
name: "Status",
|
||||||
meta:{enter:'all'},
|
meta: {
|
||||||
|
enter: "all",
|
||||||
|
sellerHeaderTitle: "Edit Listing Details",
|
||||||
|
sellerBreadcrumbs: [
|
||||||
|
myListingsBreadcrumb,
|
||||||
|
selectCollectionBreadcrumb,
|
||||||
|
selectSketchBreadcrumb,
|
||||||
|
editListingBreadcrumb,
|
||||||
|
statusBreadcrumb
|
||||||
|
]
|
||||||
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/SellerDashboard/MyListings/EditDetail/Status.vue")
|
import("@/views/SellerDashboard/MyListings/EditDetail/Status.vue")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="become-seller">
|
<div class="become-seller">
|
||||||
<seller-header
|
<seller-header />
|
||||||
title="Apply to Become a Seller"
|
|
||||||
tip="Join the Stylish Parade and start selling your design work"
|
|
||||||
/>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<seller-apply v-if="applyStatus === null" @submit="onSubmit" />
|
<seller-apply v-if="applyStatus === null" @submit="onSubmit" />
|
||||||
<seller-review v-else />
|
<seller-review v-else />
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
<div class="status-wrapper flex flex-col flex-1">
|
<div class="status-wrapper flex flex-col flex-1">
|
||||||
<seller-header
|
<seller-header
|
||||||
class="edit-detail-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">
|
<div class="status-container flex flex-col flex-1 flex-center">
|
||||||
<img src="@/assets/images/seller/success-0.png" class="icon" alt="" />
|
<img src="@/assets/images/seller/success-0.png" class="icon" alt="" />
|
||||||
|
|||||||
@@ -2,13 +2,6 @@
|
|||||||
<div class="edit-detail-wrapper flex-1">
|
<div class="edit-detail-wrapper flex-1">
|
||||||
<seller-header
|
<seller-header
|
||||||
class="edit-detail-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' }
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<template #right>
|
<template #right>
|
||||||
<div class="operate-menu flex">
|
<div class="operate-menu flex">
|
||||||
|
|||||||
@@ -40,14 +40,7 @@ defineExpose({})
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="create-select">
|
<div class="create-select">
|
||||||
<seller-header
|
<seller-header />
|
||||||
title="Select Collection"
|
|
||||||
:breadcrumbs="[
|
|
||||||
{title:'My Listings', name:'myListingsIndex'},
|
|
||||||
{title:'Select Collection', name: 'myListingsSelect' }
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
</seller-header>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
|
|||||||
@@ -149,14 +149,7 @@ const {} = toRefs(data);
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="create-select-item">
|
<div class="create-select-item">
|
||||||
<seller-header
|
<seller-header>
|
||||||
title="Select Collection"
|
|
||||||
:breadcrumbs="[
|
|
||||||
{title:'My Listings', name:'myListingsIndex'},
|
|
||||||
{title:'Select Collection', name: 'myListingsSelect' },
|
|
||||||
{title:'Select Sketch', name: 'myListingsSelectItem' }
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<template #right>
|
<template #right>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="chooseNum">
|
<div class="chooseNum">
|
||||||
|
|||||||
@@ -41,10 +41,7 @@ const {} = toRefs(data);
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="myListings-seller">
|
<div class="myListings-seller">
|
||||||
<seller-header
|
<seller-header>
|
||||||
title="My Listings"
|
|
||||||
tip="Active listings and unpublished inventory."
|
|
||||||
>
|
|
||||||
<template #right>
|
<template #right>
|
||||||
<div class="button" @click="newListing">
|
<div class="button" @click="newListing">
|
||||||
<span>New Listing</span>
|
<span>New Listing</span>
|
||||||
|
|||||||
@@ -4,24 +4,20 @@
|
|||||||
<svg-icon name="seller-back" size="24" />
|
<svg-icon name="seller-back" size="24" />
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title" v-show="title">{{ title }}</span>
|
<span class="title" v-show="displayTitle">{{ displayTitle }}</span>
|
||||||
<span class="tip" v-show="tip">{{ tip }}</span>
|
<span class="tip" v-show="displayTip">{{ displayTip }}</span>
|
||||||
<div class="breadcrumbs" v-show="breadcrumbs.length > 0">
|
<div class="breadcrumbs" v-show="breadcrumbList.length > 1">
|
||||||
<template v-for="(v, i) in breadcrumbs" :key="i">
|
<template v-for="(v, i) in breadcrumbList" :key="`${v.title}-${i}`">
|
||||||
<span
|
<span
|
||||||
class="title"
|
class="title"
|
||||||
:class="{
|
:class="{
|
||||||
last: i === breadcrumbs.length - 1
|
last: i === breadcrumbList.length - 1,
|
||||||
|
clickable: i < breadcrumbList.length - 1
|
||||||
}"
|
}"
|
||||||
@click="
|
@click="onBreadcrumbClick(v, i)"
|
||||||
() => {
|
|
||||||
const index = -(breadcrumbs.length - i - 1)
|
|
||||||
if (index < 0) router.go(index)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>{{ v.title }}</span
|
>{{ v.title }}</span
|
||||||
>
|
>
|
||||||
<span class="icon" v-show="i < breadcrumbs.length - 1">
|
<span class="icon" v-show="i < breadcrumbList.length - 1">
|
||||||
<svg-icon name="seller-arrow_right_solid" size="10" />
|
<svg-icon name="seller-arrow_right_solid" size="10" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -33,25 +29,150 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { ref, computed } from "vue"
|
import { computed } from "vue"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
const props = defineProps({
|
import type { RouteLocationNormalizedLoaded, RouteLocationRaw } from "vue-router"
|
||||||
title: {
|
|
||||||
type: String,
|
type RouteMetaValue<T> = T | ((route: RouteLocationNormalizedLoaded) => T)
|
||||||
default: ""
|
type SellerBreadcrumbSource =
|
||||||
},
|
| string
|
||||||
tip: {
|
| {
|
||||||
type: String,
|
title?: RouteMetaValue<string>
|
||||||
default: ""
|
titleKey?: RouteMetaValue<string>
|
||||||
},
|
to?: RouteMetaValue<RouteLocationRaw>
|
||||||
breadcrumbs: {
|
name?: string
|
||||||
type: Array, // { title: string, name: string }
|
path?: string
|
||||||
default: () => []
|
|
||||||
}
|
}
|
||||||
})
|
type SellerBreadcrumbItem = {
|
||||||
|
title: string
|
||||||
|
to?: RouteLocationRaw
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
title?: string
|
||||||
|
tip?: string
|
||||||
|
breadcrumbs?: SellerBreadcrumbSource[]
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
tip: "",
|
||||||
|
breadcrumbs: () => []
|
||||||
|
}
|
||||||
|
)
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const resolveMetaValue = <T,>(value?: RouteMetaValue<T>) => {
|
||||||
|
if (typeof value === "function") {
|
||||||
|
return (value as (route: RouteLocationNormalizedLoaded) => T)(route)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveRouteLocation = (to?: RouteLocationRaw) => {
|
||||||
|
if (!to || typeof to === "string") return to
|
||||||
|
const location = to as any
|
||||||
|
if (location.name) {
|
||||||
|
return {
|
||||||
|
...location,
|
||||||
|
params: {
|
||||||
|
...route.params,
|
||||||
|
...location.params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return to
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveBreadcrumb = (source: SellerBreadcrumbSource) => {
|
||||||
|
if (typeof source === "string") {
|
||||||
|
return {
|
||||||
|
title: source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleKey = resolveMetaValue(source.titleKey)
|
||||||
|
const title = titleKey ? t(titleKey) : resolveMetaValue(source.title)
|
||||||
|
const to = resolveRouteLocation(resolveMetaValue(source.to))
|
||||||
|
const fallbackTo = source.name ? { name: source.name } : source.path
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: title || "",
|
||||||
|
to: resolveRouteLocation(to || fallbackTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveTitle = (title?: RouteMetaValue<string>, titleKey?: RouteMetaValue<string>) => {
|
||||||
|
const key = resolveMetaValue(titleKey)
|
||||||
|
return key ? t(key) : resolveMetaValue(title) || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoBreadcrumbs = computed(() => {
|
||||||
|
const currentRecord = route.matched[route.matched.length - 1]
|
||||||
|
const customBreadcrumbs = currentRecord?.meta?.sellerBreadcrumbs
|
||||||
|
|
||||||
|
if (Array.isArray(customBreadcrumbs)) {
|
||||||
|
return customBreadcrumbs
|
||||||
|
.map((breadcrumb) => resolveBreadcrumb(breadcrumb as SellerBreadcrumbSource))
|
||||||
|
.filter((breadcrumb) => breadcrumb.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
return route.matched
|
||||||
|
.map((record) => record.meta?.sellerBreadcrumb)
|
||||||
|
.filter(
|
||||||
|
(breadcrumb): breadcrumb is SellerBreadcrumbSource =>
|
||||||
|
typeof breadcrumb === "string" ||
|
||||||
|
(typeof breadcrumb === "object" && breadcrumb !== null)
|
||||||
|
)
|
||||||
|
.map(resolveBreadcrumb)
|
||||||
|
.filter((breadcrumb) => breadcrumb.title)
|
||||||
|
})
|
||||||
|
|
||||||
|
const breadcrumbList = computed<SellerBreadcrumbItem[]>(() => {
|
||||||
|
if (props.breadcrumbs.length) {
|
||||||
|
return props.breadcrumbs
|
||||||
|
.map(resolveBreadcrumb)
|
||||||
|
.filter((breadcrumb) => breadcrumb.title)
|
||||||
|
}
|
||||||
|
return autoBreadcrumbs.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const displayTitle = computed(() => {
|
||||||
|
return (
|
||||||
|
props.title ||
|
||||||
|
resolveTitle(
|
||||||
|
route.meta.sellerHeaderTitle as RouteMetaValue<string> | undefined,
|
||||||
|
route.meta.sellerHeaderTitleKey as RouteMetaValue<string> | undefined
|
||||||
|
) ||
|
||||||
|
breadcrumbList.value[breadcrumbList.value.length - 1]?.title ||
|
||||||
|
""
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const displayTip = computed(() => {
|
||||||
|
return (
|
||||||
|
props.tip ||
|
||||||
|
resolveTitle(
|
||||||
|
route.meta.sellerHeaderTip as RouteMetaValue<string> | undefined,
|
||||||
|
route.meta.sellerHeaderTipKey as RouteMetaValue<string> | undefined
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onBreadcrumbClick = (breadcrumb: SellerBreadcrumbItem, index: number) => {
|
||||||
|
if (index >= breadcrumbList.value.length - 1) return
|
||||||
|
if (breadcrumb.to) {
|
||||||
|
router.push(breadcrumb.to)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const historyIndex = -(breadcrumbList.value.length - index - 1)
|
||||||
|
if (historyIndex < 0) router.go(historyIndex)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.seller-header {
|
.seller-header {
|
||||||
@@ -90,8 +211,8 @@
|
|||||||
font-family: "pingfang_regular";
|
font-family: "pingfang_regular";
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
|
&.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:not(.last) {
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user