feat(buyer): 增强搜索功能并提升用户体验

- 新增 `getStoreById` 接口方法,用于获取包含评分在内的店铺信息
- `Search.vue` 新增搜索建议面板,展示搜索历史和热词,优化搜索体验
- 新增 `SearchSuggest.vue` 组件,用于展示搜索建议和搜索历史
- `App.vue` 根据路由元信息按条件设置页面标题
- 优化多个组件的布局与样式,让整体风格更统一
- 重构多个组件的跳转逻辑,改用路由跳转(`router.push`)替代新开窗口(`window.open`),提升用户体验
This commit is contained in:
pikachu1995@126.com
2026-10-02 15:26:58 +08:00
parent 4777eb2dd6
commit 8410179885
44 changed files with 2116 additions and 674 deletions

View File

@@ -281,11 +281,10 @@ export default {
},
// 跳转店铺首页
goShopPage(id) {
let routeUrl = this.$router.resolve({
this.$router.push({
path: "/merchant",
query: { id },
});
window.open(routeUrl.href, "_blank");
},
// 同步购物车商品收藏状态
async syncCartCollectionStatus() {
@@ -959,3 +958,6 @@ export default {
}
</style>

View File

@@ -2,55 +2,43 @@
<div class="coupon-center">
<BaseHeader></BaseHeader>
<div class="content">
<div>
<div class="coupon-title">
<router-link to="/">
<img src="../assets/images/logo.png" width="120" alt="" />
</router-link>
<p>领券中心</p>
<el-input
v-model="keyword"
style="width: 400px"
@keyup.enter="search"
placeholder="搜索优惠券"
/>
</div>
<h2 class="coupon-page-title">领券中心</h2>
<div class="fontsize_18 recommend">推荐好券</div>
<empty v-if="!loading && list.length === 0" />
<ul class="coupon-list" v-else-if="list.length > 0">
<li v-for="(item, index) in list" class="coupon-item" :key="index">
<div class="c-left">
<div>
<span
v-if="item.couponType === 'PRICE'"
class="fontsize_12 global_color"
>¥<span class="price">{{ $filters.unitPrice(item.price) }}</span></span>
<span
v-if="item.couponType === 'DISCOUNT'"
class="fontsize_12 global_color"
><span class="price">{{ item.couponDiscount || item.discount }}</span>折</span>
<span class="describe"
>满{{ item.consumeThreshold }}元可用</span>
</div>
<p>使用范围:{{ useScope(item.scopeType, item.storeName) }}</p>
<p>有效期:{{ item.endTime }}</p>
<empty v-if="!loading && list.length === 0" />
<ul class="coupon-list" v-else-if="list.length > 0">
<li v-for="item in list" class="coupon-item" :key="item.id">
<div class="c-left">
<div class="coupon-amount">
<span v-if="item.couponType === 'PRICE'" class="global_color">
¥<span class="price">{{ $filters.unitPrice(item.price) }}</span>
</span>
<span v-if="item.couponType === 'DISCOUNT'" class="global_color">
<span class="price">{{ item.couponDiscount || item.discount }}</span>折
</span>
<span class="describe">{{ thresholdText(item) }}</span>
</div>
<b></b>
<a class="c-right" @click="receive(item)">立即领取</a>
<i class="circle-top"></i>
<i class="circle-bottom"></i>
</li>
</ul>
<el-pagination v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="total"
@current-change="changePageNum"
class="pageration"
@size-change="changePageSize"
layout="total, sizes, prev, pager, next"></el-pagination>
</div>
<p class="coupon-name">
<span class="scope-tag">{{ scopeTag(item.scopeType, item.storeName) }}</span>
<span>{{ item.couponName }}</span>
</p>
<p class="coupon-time">有效期:{{ item.endTime }}</p>
</div>
<a
class="c-right"
:class="{ 'is-finished': isFinished(item) }"
@click="receive(item)"
>{{ isFinished(item) ? "已领完" : "立即领取" }}</a>
</li>
</ul>
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="total"
@current-change="changePageNum"
class="pageration"
@size-change="changePageSize"
layout="total, sizes, prev, pager, next"
></el-pagination>
</div>
<BaseFooter></BaseFooter>
</div>
@@ -65,7 +53,6 @@ export default {
list: [], // 优惠券列表
total: 0, // 优惠券总数
loading: false,
keyword: "",
params: {
// 请求参数
getType: "FREE",
@@ -75,13 +62,6 @@ export default {
};
},
methods: {
// 搜索优惠券
search() {
this.params.couponName = this.keyword.trim();
this.params.pageNumber = 1;
this.getList();
},
// 获取优惠券列表
getList() {
if (this.loading) return;
this.loading = true;
@@ -116,6 +96,7 @@ export default {
},
// 领取优惠券
receive(item) {
if (this.isFinished(item)) return;
const userInfo = storage.getItem("userInfo");
const isLogin = userInfo && JSON.parse(userInfo).username;
if (!isLogin) {
@@ -158,23 +139,22 @@ export default {
})
.catch(() => {});
},
// 优惠券可用范围
useScope(type, storeName) {
let shop = "平台";
let goods = "全部商品";
if (storeName !== "platform") shop = storeName;
switch (type) {
case "ALL":
goods = "全部商品";
break;
case "PORTION_GOODS":
goods = "部分商品";
break;
case "PORTION_GOODS_CATEGORY":
goods = "部分分类商品";
break;
}
return `${shop}${goods}可用`;
// 发行量已领完
isFinished(item) {
return Number(item.publishNum) > 0 && Number(item.receivedNum) >= Number(item.publishNum);
},
// 使用门槛文案
thresholdText(item) {
const threshold = Number(item.consumeThreshold);
if (!threshold) return "无使用门槛";
return `满${item.consumeThreshold}元可用`;
},
// 券范围。店铺名为 platform 时是平台通用券
scopeTag(type, storeName) {
if (type === "PORTION_GOODS") return "商品券";
if (type === "PORTION_GOODS_CATEGORY") return "分类券";
if (storeName && storeName !== "platform") return "店铺券";
return "通用券";
},
},
mounted() {
@@ -184,84 +164,156 @@ export default {
</script>
<style lang="scss" scoped>
@import "../assets/styles/coupon.scss";
.coupon-center {
background: #f5f5f5;
}
.content {
width: 100%;
background-color: #fff;
> div {
margin: 10px auto;
width: 1200px;
}
width: 1200px;
margin: 0 auto 48px;
background: transparent;
}
.coupon-title {
display: flex;
align-items: center;
p {
font-size: 18px;
margin-right: 500px;
}
border-bottom: 2px solid $theme_color;
}
.recommend {
margin: 20px auto;
font-weight: bold;
width: 200px;
.coupon-page-title {
margin: 28px 0 20px;
color: #171A26;
font-size: 28px;
font-weight: 600;
text-align: center;
}
.coupon-item {
overflow: hidden;
$claim-width: 55px;
.c-left {
width: calc(100% - #{$claim-width});
box-sizing: border-box;
.coupon-list {
gap: 16px;
margin: 0;
padding: 0;
list-style: none;
}
.coupon-item {
width: calc((100% - 32px) / 3);
height: auto;
min-height: 132px;
margin: 0;
display: flex;
align-items: stretch;
overflow: hidden;
border: none;
border-radius: 8px;
background: #fff;
b,
i.circle-top,
i.circle-bottom {
display: none;
}
b {
right: $claim-width;
width: 0;
background: none;
border-right: 1px dashed #e0e0e0;
.c-left {
flex: 1;
width: auto;
min-width: 0;
padding: 16px 16px 14px;
box-sizing: border-box;
.coupon-amount {
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
}
.price {
font-size: 28px;
font-weight: 600;
line-height: 1;
}
.describe {
margin-left: 0;
padding: 0;
background: transparent;
font-size: 13px;
line-height: 20px;
}
.coupon-name {
display: flex;
align-items: center;
gap: 6px;
margin: 0 0 8px;
color: #171A26;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.scope-tag {
flex-shrink: 0;
padding: 0 6px;
border-radius: 2px;
background: #fff5f4;
color: $theme_color;
font-size: 12px;
line-height: 20px;
}
.coupon-name span:last-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.coupon-time {
margin: 0;
color: #999;
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
}
.c-right {
width: $claim-width;
position: relative;
right: auto;
top: auto;
flex-shrink: 0;
box-sizing: border-box;
width: 48px;
height: auto;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
writing-mode: vertical-rl;
text-orientation: mixed;
letter-spacing: 4px;
font-size: 16px;
white-space: nowrap;
font-size: 14px;
font-weight: 600;
line-height: 1;
cursor: pointer;
text-decoration: none;
user-select: none;
&:hover {
opacity: 0.9;
opacity: 0.92;
}
&.is-finished {
background: #c8c8c8;
cursor: default;
&:hover {
opacity: 1;
}
}
}
i.circle-top,
i.circle-bottom {
right: calc(#{$claim-width} - 9px);
width: 18px;
height: 18px;
}
i.circle-top {
top: -9px;
}
i.circle-bottom {
bottom: -9px;
}
}
.pageration {
text-align: right;
padding-bottom: 10px;
display: flex;
justify-content: flex-end;
margin-top: 20px;
padding-bottom: 0;
}
</style>

View File

@@ -6,27 +6,10 @@
<div class="shop-nav-container">
<el-breadcrumb>
<el-breadcrumb-item to="/">首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in categoryBar" :to="goGoodsList(index)" target="_blank" :key="index">
<el-breadcrumb-item v-for="(item, index) in categoryBar" :to="goGoodsList(index)" :key="index">
{{ item.name }}
</el-breadcrumb-item>
</el-breadcrumb>
<div class="store-collect" v-if="!takeDownSale && goodsMsg.data">
<span class="mr_10" v-if="goodsMsg.data">
<router-link
class="store-name-link"
:to="{ path: '/merchant', query: { id: goodsMsg.data.storeId } }"
>{{ goodsMsg.data.storeName }}</router-link>
</span>
<span
class="store-collect-btn"
:class="{ 'is-collected': storeCollected }"
@click="collect"
>
<el-icon class="store-collect-icon"><StarFilled /></el-icon>
{{ storeCollected? "已收藏店铺": "收藏店铺" }}
</span>
<span class="ml_10 contact-service-btn" @click="IMService(goodsMsg.data.storeId,goodsMsg.data.goodsId,goodsMsg.data.id)">联系客服</span>
</div>
</div>
</div>
@@ -48,21 +31,13 @@
<script>
import { Message } from "@/utils/message";
import { StarFilled } from '@element-plus/icons-vue';
import Search from "@/components/Search";
import ShowGoods from "@/components/goodsDetail/ShowGoods";
import empty from "@/components/empty/Main";
import ShowGoodsDetail from "@/components/goodsDetail/ShowGoodsDetail";
import { goodsSkuDetail } from "@/api/goods";
import {
cancelStoreCollect,
collectStore,
isStoreCollection,
getGoodsDistribution,
} from "@/api/member";
import { getDetailById } from "@/api/shopentry";
import imTalk from '@/components/mixes/talkIm'
import { getGoodsDistribution } from "@/api/member";
export default {
name: "GoodsDetail",
beforeRouteEnter (to, from, next) {
@@ -72,14 +47,11 @@ export default {
created () {
this.getGoodsDetail();
},
mixins: [imTalk],
data () {
return {
goodsMsg: {}, // 商品信息
isLoading: false, // 加载状态
categoryBar: [], // 分类
storeCollected: false, // 商品收藏
storeMsg: {}, // 店铺信息
takeDownSale:false, // 是否下架
};
@@ -140,24 +112,6 @@ export default {
if (!this.goodsMsg.data.intro) {
this.goodsMsg.data.intro = ''
}
// 判断是否收藏(须同时有 token,避免未登录/ token 失效时误跳登录页)
if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) {
isStoreCollection("STORE", this.goodsMsg.data.storeId).then((res) => {
if (res.success && res.result) {
this.storeCollected = true;
}
});
}
if (!this.storeMsg) {
// 获取店铺信息
getDetailById(this.goodsMsg.data.storeId).then((res) => {
if (res.success) {
this.storeMsg = res.result;
}
});
}
} else {
Message.error(res.message);
this.isLoading = false
@@ -183,37 +137,12 @@ export default {
query: { categoryId: arr.toString() },
};
},
async collect () {
// 收藏店铺
const storeId = this.goodsMsg.data && this.goodsMsg.data.storeId;
if (!storeId) {
return;
}
if (!this.Cookies.getItem("userInfo")) {
Message.warning("请先登录");
this.$router.push("/login");
return;
}
if (this.storeCollected) {
let cancel = await cancelStoreCollect("STORE", storeId);
if (cancel.success) {
Message.success("已取消收藏");
this.storeCollected = false;
}
} else {
let collect = await collectStore("STORE", storeId);
if (collect.code === 200) {
this.storeCollected = true;
Message.success("收藏店铺成功,可以前往个人中心我的收藏查看");
}
}
},
},
watch: {},
components: { Search,
ShowGoods,
ShowGoodsDetail,
empty, StarFilled },
empty },
};
</script>
<style scoped lang="scss">
@@ -248,52 +177,6 @@ export default {
:deep(.el-breadcrumb__item:last-child .el-breadcrumb__inner) {
color: #171A26;
}
.store-collect {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
gap: 16px;
color: #171A26;
font-size: 14px;
.store-collect-btn,
.contact-service-btn {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
color: #171A26;
&:hover {
color: $theme_color;
}
}
.store-collect-btn {
.store-collect-icon {
color: #999;
font-size: 16px;
}
&.is-collected,
&.is-collected .store-collect-icon {
color: $theme_color;
}
}
.store-name-link {
color: #438cde;
text-decoration: none;
&:hover {
color: $theme_color;
}
}
}
}
.sale-btn{
margin:10px 0

View File

@@ -21,32 +21,34 @@
>
<span :class="{ 'goods-list-tool-active': index === sortIndex }">
{{ item.title }}
<el-icon><ArrowDown /></el-icon>
<el-icon v-if="index === sortIndex" class="sort-arrow"><ArrowDown /></el-icon>
</span>
</li>
<li @click="orderBy('price', 5, 'up')" class="price-sort">
<span :class="{ 'goods-list-tool-active': 5 === sortIndex }">
<li @click="orderBy('price', priceSortIndex)" class="price-sort">
<span :class="{ 'goods-list-tool-active': priceSortIndex === sortIndex }">
价格
<span class="price-arrows">
<el-icon :class="{ 'price-color': sortPriceIndex == 'desc' }"><ArrowUp /></el-icon>
<el-icon :class="{ 'price-color': sortPriceIndex == 'asc' }"><ArrowDown /></el-icon>
<el-icon :class="{ 'is-on': priceSortIndex === sortIndex && sortPriceIndex === 'asc' }"><ArrowUp /></el-icon>
<el-icon :class="{ 'is-on': priceSortIndex === sortIndex && sortPriceIndex === 'desc' }"><ArrowDown /></el-icon>
</span>
</span>
</li>
</ul>
</div>
<!-- 列表 -->
<div class="goods-list">
<empty v-if="goodsList.length === 0" />
<div v-if="searched && !loading && goodsList.length === 0" class="list-empty">
<empty :_Title="emptyTitle" />
<div v-if="recommendList.length" class="recommend-title">为你推荐</div>
</div>
<div class="goods-list" v-if="displayGoods.length">
<div
v-else
class="goods-show-info"
v-for="(item, index) in goodsList"
:key="index"
v-for="(item, index) in displayGoods"
:key="item.id || index"
@click="goGoodsDetail(item.id, item.goodsId)"
>
<div class="goods-show-img">
<img :src="item.thumbnail" />
<GoodsThumb :src="item.thumbnail" :alt="item.goodsName" />
</div>
<div class="goods-show-price">
<span>
@@ -85,7 +87,7 @@
</div>
</div>
</div>
<div class="goods-page">
<div class="goods-page" v-if="total > 0">
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
@@ -105,6 +107,7 @@
import { Message } from "@/utils/message";
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import GoodsClassNav from "@/components/nav/GoodsClassNav";
import GoodsThumb from "@/components/goods/GoodsThumb.vue";
import * as apiGoods from "@/api/goods";
export default {
name: "GoodsList",
@@ -115,7 +118,10 @@ export default {
data() {
return {
sortIndex: 0, // 排序状态
priceSortIndex: 4,
sortPriceIndex: false, // 判断价格升序还是降序
searched: false,
recommendList: [],
goodsTool: [
// 排序类型
{ title: "综合", en: "" },
@@ -154,6 +160,16 @@ export default {
this.getGoodsList();
},
},
computed: {
displayGoods() {
if (this.goodsList.length) return this.goodsList;
return this.searched && !this.loading ? this.recommendList : [];
},
emptyTitle() {
const keyword = this.params.keyword;
return keyword ? `没有找到与“${keyword}”相关的商品` : "暂无商品";
},
},
methods: {
// 搜索
handleSearch(key) {
@@ -204,11 +220,10 @@ export default {
Message.warning("店铺信息不存在");
return;
}
const routeUrl = this.$router.resolve({
this.$router.push({
path: "/merchant",
query: { id },
});
window.open(routeUrl.href, "_blank");
},
// 分页 修改页码
changePageNum(val) {
@@ -230,14 +245,28 @@ export default {
.then((res) => {
this.loading = false;
if (res.success) {
this.goodsList = res.result.records;
this.goodsList = res.result.records || [];
this.total = res.result.total;
if (!this.goodsList.length) this.loadRecommend();
}
})
.catch(() => {
this.goodsList = [];
})
.finally(() => {
this.loading = false;
this.searched = true;
});
},
loadRecommend() {
if (this.recommendList.length) return;
apiGoods
.goodsList({ pageNumber: 1, pageSize: 10, sort: "buyCount", order: "desc" })
.then((res) => {
if (res.success) this.recommendList = res.result.records || [];
})
.catch(() => {});
},
getParams(val) {
// 筛选条件回显
Object.assign(this.params, val);
@@ -256,6 +285,7 @@ export default {
},
components: {
GoodsClassNav,
GoodsThumb,
ArrowDown,
ArrowUp,
},
@@ -395,8 +425,12 @@ export default {
}
}
.price-color {
color: #b3b3b3;
.el-icon {
color: #c8c8c8;
}
.el-icon.is-on {
color: $theme_color;
}
}
}
@@ -433,9 +467,30 @@ export default {
background-color: transparent !important;
font-weight: 600;
.price-color {
color: $theme_color !important;
.sort-arrow {
color: $theme_color;
}
.price-arrows .el-icon {
color: #c8c8c8;
}
.price-arrows .el-icon.is-on {
color: $theme_color;
}
}
.list-empty {
background: #fff;
border: 1px solid #eee;
border-top: none;
}
.recommend-title {
padding: 4px 16px 12px;
color: #333;
font-size: 16px;
font-weight: 600;
}
.goods-list {

View File

@@ -92,9 +92,9 @@
</div>
<div class="foot">
<div type="flex" justify="space-around" class="help">
<router-link to="/article" class="item" target="_blank">帮助</router-link>
<router-link to="/article?id=1371779927900160000" class="item" target="_blank">隐私</router-link>
<router-link to="/article?id=1371992704333905920" class="item" target="_blank">条款</router-link>
<router-link to="/article" class="item">帮助</router-link>
<router-link to="/article?id=1371779927900160000" class="item">隐私</router-link>
<router-link to="/article?id=1371992704333905920" class="item">条款</router-link>
</div>
<div type="flex" justify="center" class="copyright">
Copyright © {{year}} - Present
@@ -601,3 +601,5 @@ export default {
}
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div class="not-found-page">
<BaseHeader></BaseHeader>
<Search :show-tag="false"></Search>
<div class="not-found">
<h1>页面不存在</h1>
<p>你访问的地址没有对应页面</p>
<el-button type="primary" @click="$router.push('/')">返回首页</el-button>
</div>
<BaseFooter></BaseFooter>
</div>
</template>
<script>
export default {
name: "NotFound",
};
</script>
<style scoped lang="scss">
.not-found {
width: 1200px;
min-height: 420px;
margin: 24px auto 40px;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.not-found h1 {
margin: 0;
color: #333;
font-size: 28px;
font-weight: 600;
}
.not-found p {
margin: 12px 0 24px;
color: #999;
font-size: 14px;
}
</style>

View File

@@ -66,7 +66,7 @@
<el-form-item>
<el-button class="signup-btn" @click="handleRegist">注册</el-button>
</el-form-item>
<p class="agreement">点击注册,表示您同意《<router-link to="/article?id=1371992704333905920" target="_blank">商城用户协议</router-link>》</p>
<p class="agreement">点击注册,表示您同意《<router-link to="/article?id=1371992704333905920">商城用户协议</router-link>》</p>
</el-form>
<Verify
ref="verify"
@@ -78,9 +78,9 @@
</div>
<div class="foot">
<div type="flex" justify="space-around" class="help">
<router-link to="/article" class="item" target="_blank">帮助</router-link>
<router-link to="/article?id=1371779927900160000" class="item" target="_blank">隐私</router-link>
<router-link to="/article?id=1371992704333905920" class="item" target="_blank">条款</router-link>
<router-link to="/article" class="item">帮助</router-link>
<router-link to="/article?id=1371779927900160000" class="item">隐私</router-link>
<router-link to="/article?id=1371992704333905920" class="item">条款</router-link>
</div>
<div type="flex" justify="center" class="copyright">
Copyright © {{year}} - Present

View File

@@ -1,35 +1,271 @@
<template>
<div>
<div class="userBox">
<div class="box">
<!-- 我的订单组件 -->
<myOrderPage :homePage="true" />
</div>
<div class="box">
<!-- 近期收藏 -->
<myFavorites :homePage="true" />
</div>
<div class="box">
<div class="member-home">
<div class="home-head">
<div>
<div class="home-hello">你好,{{ displayName }}</div>
<div class="home-sub">查看订单进度、优惠券和账户余额</div>
</div>
<button type="button" class="msg-entry" @click="go('MsgList')">
<el-icon :size="18"><Bell /></el-icon>
<span>我的消息</span>
<i v-if="unreadMsg > 0" class="msg-badge">{{ unreadLabel }}</i>
</button>
</div>
<div class="status-grid">
<button
v-for="item in orderStats"
:key="item.key"
type="button"
class="stat-card"
@click="item.action()"
>
<span class="stat-count">{{ item.count }}</span>
<span class="stat-label">{{ item.label }}</span>
</button>
</div>
<div class="asset-grid">
<button type="button" class="asset-card" @click="go('Coupons')">
<div>
<div class="asset-label">优惠券</div>
<div class="asset-value">{{ couponCount }}<span>张可用</span></div>
</div>
<span class="asset-link">查看</span>
</button>
<button type="button" class="asset-card" @click="go('MoneyManagement')">
<div>
<div class="asset-label">账户余额</div>
<div class="asset-value">¥{{ $filters.unitPrice(balance) }}</div>
</div>
<span class="asset-link">查看</span>
</button>
</div>
</div>
</template>
<script>
import myOrderPage from '@/pages/home/orderCenter/MyOrder'
import myFavorites from '@/pages/home/memberCenter/Favorites'
import { Bell } from "@element-plus/icons-vue";
import storage from "@/plugins/storage";
import { getOrderList } from "@/api/order";
import { afterSaleList, getMembersWallet, memberCouponList } from "@/api/member";
import { refreshUnreadMessage } from "@/utils/unreadMessage";
function totalOf(res) {
return res && res.success ? Number(res.result && res.result.total) || 0 : 0;
}
export default {
name: 'main',
components: {
myOrderPage,
myFavorites
}
}
name: "UserCenterHome",
components: { Bell },
data() {
return {
displayName: "会员",
waitPay: 0,
waitShip: 0,
waitReceive: 0,
afterSale: 0,
couponCount: 0,
balance: 0,
};
},
computed: {
unreadMsg() {
return Number(this.$store.state.unreadMsg) || 0;
},
unreadLabel() {
return this.unreadMsg > 99 ? "99+" : String(this.unreadMsg);
},
orderStats() {
return [
{ key: "pay", label: "待付款", count: this.waitPay, action: () => this.goOrder("WAIT_PAY") },
{ key: "ship", label: "待发货", count: this.waitShip, action: () => this.goOrder("WAIT_SHIP") },
{ key: "receive", label: "待收货", count: this.waitReceive, action: () => this.goOrder("WAIT_USE") },
{ key: "after", label: "售后", count: this.afterSale, action: () => this.go("AfterSale") },
];
},
},
mounted() {
this.readUser();
this.loadSummary();
},
methods: {
readUser() {
try {
const raw = storage.getItem("userInfo");
const user = raw ? JSON.parse(raw) : {};
this.displayName = user.nickName || user.username || "会员";
} catch {
this.displayName = "会员";
}
},
loadSummary() {
const page = { pageNumber: 1, pageSize: 1 };
Promise.all([
getOrderList({ ...page, tag: "WAIT_PAY" }).catch(() => null),
getOrderList({ ...page, tag: "WAIT_SHIP" }).catch(() => null),
getOrderList({ ...page, tag: "WAIT_USE" }).catch(() => null),
afterSaleList({ ...page, sort: "createTime", order: "desc" }).catch(() => null),
memberCouponList({ ...page, memberCouponStatus: "NEW" }).catch(() => null),
getMembersWallet().catch(() => null),
refreshUnreadMessage(),
]).then(([pay, ship, receive, afterSale, coupon, wallet]) => {
this.waitPay = totalOf(pay);
this.waitShip = totalOf(ship);
this.waitReceive = totalOf(receive);
this.afterSale = totalOf(afterSale);
this.couponCount = totalOf(coupon);
this.balance = wallet && wallet.success ? wallet.result.memberWallet || 0 : 0;
});
},
go(name) {
this.$router.push({ name });
},
goOrder(tag) {
this.$router.push({ name: "MyOrder", query: { tag } });
},
},
};
</script>
<style scoped lang="scss">
.userBox {
padding: 0 0 20px 0;
.member-home {
display: flex;
flex-direction: column;
gap: 16px;
}
.home-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 4px 4px 8px;
}
.home-hello {
color: #333;
font-size: 18px;
font-weight: 600;
line-height: 26px;
}
.home-sub {
margin-top: 4px;
color: #999;
font-size: 14px;
line-height: 22px;
}
.msg-entry {
display: inline-flex;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 14px;
border: 1px solid #eee;
border-radius: 18px;
background: #fff;
color: #333;
font-size: 14px;
cursor: pointer;
&:hover {
color: $theme_color;
border-color: $theme_color;
}
}
.msg-badge {
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 8px;
background: $theme_color;
color: #fff;
font-size: 12px;
font-style: normal;
line-height: 16px;
text-align: center;
}
.status-grid,
.asset-grid {
display: grid;
gap: 12px;
}
.status-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.asset-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.stat-card,
.asset-card {
border: 1px solid #eee;
border-radius: 8px;
background: #fff;
cursor: pointer;
text-align: left;
&:hover {
border-color: $theme_color;
}
}
.stat-card {
display: flex;
flex-direction: column;
gap: 6px;
padding: 18px 16px;
}
.stat-count {
color: $theme_color;
font-size: 24px;
font-weight: 600;
line-height: 32px;
}
.stat-label {
color: #666;
font-size: 14px;
line-height: 22px;
}
.asset-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 20px;
}
.asset-label {
color: #999;
font-size: 14px;
line-height: 22px;
}
.asset-value {
margin-top: 4px;
color: #333;
font-size: 20px;
font-weight: 600;
line-height: 28px;
span {
margin-left: 4px;
color: #999;
font-size: 14px;
font-weight: 400;
}
}
.asset-link {
color: $theme_color;
font-size: 14px;
}
</style>

View File

@@ -1,44 +1,36 @@
<template>
<div class="wrapper">
<UserCenterLayout>
<div class="safeList">
<!-- 密码 -->
<div class="safeItem">
<div class="safeItem-icon">
<el-icon :size="40"><Key /></el-icon>
<div class="safe-list">
<div class="safe-card">
<div class="safe-icon">
<el-icon :size="22"><Key /></el-icon>
</div>
<div class="safeItem-content">
<div class="setDivItem">登录密码</div>
<div class="setDivItem theme">互联网账号存在被盗风险,建议您定期更改密码以保护账户安全。</div>
</div>
<div class="safeItem-action">
<el-button @click="modifyPwd">修改密码</el-button>
<div class="safe-body">
<p class="safe-title">登录密码</p>
<p class="safe-desc">互联网账号存在被盗风险,建议您定期更改密码以保护账户安全。</p>
</div>
<el-button @click="modifyPwd">修改密码</el-button>
</div>
<div class="safeItem safeItem-connect">
<div class="safeItem-main">
<div class="safeItem-icon">
<el-icon :size="40"><Link /></el-icon>
<div class="safe-card safe-card-connect">
<div class="safe-card-main">
<div class="safe-icon">
<el-icon :size="22"><Link /></el-icon>
</div>
<div class="safeItem-content">
<div class="setDivItem">第三方账户绑定</div>
<div class="setDivItem theme">绑定后可使用第三方账号快捷登录。</div>
</div>
<div class="safeItem-action">
<el-button v-if="!isBound(wechatConnect.type)" type="primary" @click="openBindModal(wechatConnect)">绑定</el-button>
<el-button v-else @click="confirmUnbind(wechatConnect)">解绑</el-button>
<div class="safe-body">
<p class="safe-title">第三方账户绑定</p>
<p class="safe-desc">绑定后可使用第三方账号快捷登录。</p>
</div>
<el-button v-if="!isBound(wechatConnect.type)" type="primary" @click="openBindModal(wechatConnect)">绑定</el-button>
<el-button v-else @click="confirmUnbind(wechatConnect)">解绑</el-button>
</div>
<div class="connectList">
<div class="connectRow">
<div class="connectRowLeft">
<span class="connectName">{{ wechatConnect.label }}</span>
<el-tag v-if="isBound(wechatConnect.type)" color="success">已绑定</el-tag>
<el-tag v-else>未绑定</el-tag>
</div>
</div>
<el-skeleton size="large" fix v-if="connectLoading"></el-skeleton>
<div class="connect-row">
<span class="connect-name">{{ wechatConnect.label }}</span>
<span class="connect-status" :class="{ 'is-bound': isBound(wechatConnect.type) }">
{{ isBound(wechatConnect.type) ? "已绑定" : "未绑定" }}
</span>
<el-skeleton v-if="connectLoading" animated class="connect-skeleton" />
</div>
</div>
</div>
@@ -273,73 +265,101 @@ export default {
</script>
<style scoped lang="scss">
.theme {
color: $theme_color;
.safe-list {
display: flex;
flex-direction: column;
gap: 12px;
padding-top: 4px;
}
.setDivItem {
line-height: 1.75;
}
.safeItem {
.safe-card,
.safe-card-main {
display: flex;
align-items: center;
gap: 16px;
border-bottom: 1px solid $border_color;
padding: 16px 0;
}
.safeItem-connect {
.safe-card {
padding: 20px;
border: 1px solid #f0f0f0;
border-radius: 8px;
}
.safe-card-connect {
display: block;
padding: 0;
}
.safeItem-main {
display: flex;
align-items: center;
gap: 16px;
.safe-card-main {
padding: 20px 20px 16px;
}
.safeItem-icon {
.safe-icon {
flex-shrink: 0;
width: 56px;
width: 48px;
height: 48px;
border-radius: 8px;
background: #fff5f4;
color: $theme_color;
display: flex;
align-items: center;
justify-content: center;
color: $theme_color;
}
.safeItem-content {
.safe-body {
flex: 1;
min-width: 0;
}
.safeItem-action {
flex-shrink: 0;
display: flex;
align-items: center;
.safe-title {
margin: 0;
color: #171A26;
font-size: 16px;
font-weight: 600;
line-height: 24px;
}
.connectList {
.safe-desc {
margin: 6px 0 0;
color: #999;
font-size: 13px;
line-height: 20px;
}
.connect-row {
position: relative;
margin-top: 8px;
margin-left: 72px;
}
.connectRow {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 0;
}
.connectRowLeft {
display: flex;
align-items: center;
gap: 10px;
margin: 0 20px 16px 84px;
padding-top: 12px;
border-top: 1px solid #f5f5f5;
}
.connectName {
color: $title_color;
.connect-name {
color: #171A26;
font-size: 14px;
line-height: 22px;
}
.connect-status {
height: 22px;
padding: 0 8px;
border-radius: 4px;
background: #f5f5f5;
color: #999;
font-size: 12px;
line-height: 22px;
}
.connect-status.is-bound {
background: #f0faf3;
color: #1f9d55;
}
.connect-skeleton {
width: 120px;
height: 22px;
}
.wechatQrBox {

View File

@@ -280,3 +280,9 @@ export default {
}
}
</style>

View File

@@ -200,11 +200,10 @@ export default {
},
goShop(id) { // 跳转店铺页面
let url = this.$router.resolve({
this.$router.push({
path: '/merchant',
query: { id }
})
window.open(url.href, '_blank')
}
},
mounted() {

View File

@@ -25,6 +25,7 @@
<script>
import { Message, Modal } from "@/utils/message";
import { delMemberMsg, getMemberMessage, readMemberMsg } from '@/api/member.js';
import { refreshUnreadMessage } from '@/utils/unreadMessage';
const CACHE_KEY = 'buyer-member-message';
@@ -71,7 +72,10 @@ export default {
markRead () {
if (!this.detail.id || this.detail.status !== 'UN_READY') return
readMemberMsg(this.detail.id).then(res => {
if (res && res.success) this.detail.status = 'ALREADY_READY'
if (res && res.success) {
this.detail.status = 'ALREADY_READY'
refreshUnreadMessage()
}
}).catch(() => {})
},
goBack () {
@@ -86,6 +90,7 @@ export default {
delMemberMsg(this.detail.id).then((res) => {
if (res.success) {
Message.success('消息已成功放入回收站')
refreshUnreadMessage()
this.goBack()
}
})

View File

@@ -94,6 +94,9 @@ export default {
memberMsgList(this.params).then(res => {
if (res.success) {
this.messageData = res.result;
if (this.params.status === 'UN_READY') {
this.$store.commit('SET_UNREAD_MSG', Number(res.result.total) || 0);
}
}
})
},

View File

@@ -212,11 +212,10 @@ export default {
},
// 跳转店铺首页
shopPage(id) {
let routeUrl = this.$router.resolve({
this.$router.push({
path: '/merchant',
query: {id: id}
});
window.open(routeUrl.href, '_blank');
},
change(index) {
this.activeTab = index

View File

@@ -211,11 +211,10 @@ export default {
},
shopPage (id) {
if (!id) return
const routeUrl = this.$router.resolve({
this.$router.push({
path: '/merchant',
query: { id }
})
window.open(routeUrl.href, '_blank')
},
filterOrderStatus (status) { // 获取订单状态中文
const ob = this.afterSaleStatusList.filter(e => { return e.status === status });

View File

@@ -223,7 +223,7 @@ export default {
// 状态数组
orderStatusList,
afterSaleStatusList,
changeWay: ['全部', '待支付', '待发货', '待收货/使用'],
changeWay: ['全部', '待付款', '待发货', '待收货/使用'],
activeOrderTab: 0,
total: 0, // 数据总数
spinShow: false, // 加载状态
@@ -235,8 +235,18 @@ export default {
},
mounted () {
if (this.homePage) this.params.pageSize = 5;
if (!this.homePage) this.applyRouteTag();
this.getList();
},
watch: {
'$route.query.tag' () {
if (this.homePage || this.$route.name !== 'MyOrder') return;
const next = this.$route.query.tag || 'ALL';
if (this.params.tag === next) return;
this.applyRouteTag();
this.getList();
}
},
methods: {
// 退款状态枚举
refundPriceList(status) {
@@ -261,22 +271,39 @@ export default {
});
window.open(routeUrl.href, '_blank');
},
orderTags () {
return ['ALL', 'WAIT_PAY', 'WAIT_SHIP', 'WAIT_USE'];
},
applyRouteTag () {
const tags = this.orderTags();
const tag = this.$route.query.tag;
const index = tags.indexOf(tag);
this.activeOrderTab = index > -1 ? index : 0;
this.params.tag = tags[this.activeOrderTab];
this.params.pageNumber = 1;
},
// 切换订单状态
change (index) {
this.activeOrderTab = index;
this.params.pageNumber = 1;
delete this.params.commentStatus;
const tags = ['ALL', 'WAIT_PAY', 'WAIT_SHIP', 'WAIT_USE'];
const tags = this.orderTags();
this.params.tag = tags[index] || 'ALL';
const query = { ...this.$route.query };
if (index === 0) delete query.tag;
else query.tag = this.params.tag;
const current = this.$route.query.tag || 'ALL';
if (current !== this.params.tag) {
this.$router.replace({ name: 'MyOrder', query });
}
this.getList()
},
// 跳转店铺首页
shopPage (id) {
let routeUrl = this.$router.resolve({
this.$router.push({
path: '/merchant',
query: { id: id }
});
window.open(routeUrl.href, '_blank');
},
orderDetail (sn) {
this.$router.push({ name: 'OrderDetail', query: {sn} });

View File

@@ -589,11 +589,10 @@ export default {
},
// 跳转店铺首页
shopPage(id) {
let routeUrl = this.$router.resolve({
this.$router.push({
path: "/merchant",
query: { id: id },
});
window.open(routeUrl.href, "_blank");
},
async getDetail() {
// 获取订单详情

View File

@@ -72,11 +72,10 @@ export default {
},
// 跳转店铺首页
shopPage(id) {
let routeUrl = this.$router.resolve({
this.$router.push({
path: "/merchant",
query: { id: id },
});
window.open(routeUrl.href, "_blank");
},
clearAll() {
// 清除全部足迹

View File

@@ -836,11 +836,10 @@ export default {
},
// 跳转店铺首页
goShopPage(id) {
let routeUrl = this.$router.resolve({
this.$router.push({
path: "/merchant",
query: { id: id },
});
window.open(routeUrl.href, "_blank");
},
useCoupon(id, used) {
// 使用优惠券

View File

@@ -29,46 +29,32 @@
<div v-else class="goods-show-info1" v-for="(item, index) in goodsList" :key="index"
@click="goGoodsDetail(item.skuId, item.goodsId)">
<div class="goods-show-img">
<img width="200" height="200" :src="item.goodsImage" />
<img :src="item.goodsImage" :alt="item.goodsName" />
</div>
<div class="goods-show-price">
<span>
<span class="seckill-price text-danger">{{ $filters.unitPrice(item.price, "¥") }}</span>
<span style="color: #999; text-decoration: line-through">{{ $filters.unitPrice(item.originalPrice, "¥") }}</span>
</span>
<span class="seckill-price text-danger">{{ $filters.unitPrice(item.price, "¥") }}</span>
<span class="origin-price">{{ $filters.unitPrice(item.originalPrice, "¥") }}</span>
</div>
<div class="goods-show-detail" style="margin-bottom: 20px">
<div class="goods-show-detail">
<span>{{ item.goodsName }}</span>
</div>
<div class="goods-seckill-btn" :class="{
'goods-seckill-btn-gray': nowHour < list[currIndex].timeLine,
}">
{{ nowHour >= list[currIndex].timeLine ? "立即抢购" : "即将开始" }}
</div>
<div v-if="nowHour >= list[currIndex].timeLine && item.quantity <= item.salesNum"
class="goods-seckill-btn goods-seckill-btn-gray">
已售罄
</div>
<div v-if="nowHour >= list[currIndex].timeLine" class="goods-show-num1">
<span>已售{{
(item.quantity && item.quantity > 0
? Math.ceil(
(item.salesNum / (item.quantity)) * 100
)
: 100) + "%"
}}</span><el-progress
<span>已售{{ salePercent(item) }}%</span>
<el-progress
:show-text="false"
color="#df0021"
style="width: 110px"
class="ml_10"
:percentage="item.quantity && item.quantity > 0
? Math.ceil((item.salesNum / item.quantity) * 100)
: 100"
:color="'var(--theme-color, #ff3c2a)'"
:percentage="salePercent(item)"
/>
</div>
<div class="goods-show-seller">
<span>{{ item.storeName }}</span>
</div>
<div
class="goods-seckill-btn"
:class="{ 'is-disabled': !canBuy(item) }"
>
{{ buyText(item) }}
</div>
</div>
</div>
</div>
@@ -142,6 +128,25 @@ export default {
});
window.open(routeUrl.href, "_blank");
},
// 已售占比。没有库存时按售罄展示
salePercent (item) {
if (!item.quantity || item.quantity <= 0) return 100;
return Math.min(100, Math.ceil((item.salesNum / item.quantity) * 100));
},
// 场次已开始,且销量达到库存
isSoldOut (item) {
return this.nowHour >= this.list[this.currIndex].timeLine && item.quantity <= item.salesNum;
},
// 场次已开始且未售罄才可抢
canBuy (item) {
return this.nowHour >= this.list[this.currIndex].timeLine && !this.isSoldOut(item);
},
// 按钮文案:未开始 / 已售罄 / 立即抢购
buyText (item) {
if (this.nowHour < this.list[this.currIndex].timeLine) return "即将开始";
if (this.isSoldOut(item)) return "已售罄";
return "立即抢购";
},
countDown (currIndex) {
// 倒计时
// 0点时间戳
@@ -175,73 +180,96 @@ export default {
<style lang="scss" scoped>
@import "../../assets/styles/goodsList.scss";
.goods-seckill-btn {
position: absolute;
right: 0;
bottom: 0;
width: 80px;
color: #fff;
height: 35px;
text-align: center;
line-height: 35px;
font-size: 14px;
background-color: $theme_color;
margin-bottom: 1px;
margin-right: 1px;
.seckill-list {
background: #f5f5f5;
}
.goods-seckill-btn-gray {
background-color: #666;
}
.promotion-decorate {
width: auto;
margin: 28px auto 16px;
color: #171A26;
font-size: 22px;
font-weight: 600;
.promotion-decorate::before,
.promotion-decorate::after {
background-image: url("/src/assets/images/sprite@2x.png");
&::before,
&::after {
display: none;
}
}
.time-line {
width: 1200px;
max-width: 1200px;
height: 60px;
height: auto;
margin: 0 auto;
background-color: #fff;
background: #fff;
display: flex;
box-sizing: border-box;
border-radius: 8px;
overflow: hidden;
li {
padding: 0 30px;
font-size: 16px;
font-weight: bold;
width: 240px;
height: 100%;
flex: 1;
width: auto;
min-height: 78px;
padding: 12px 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 4px;
font-size: 18px;
font-weight: 600;
color: #171A26;
box-sizing: border-box;
& + li {
box-shadow: inset 1px 0 0 #f2f2f2;
}
&:hover {
cursor: pointer;
background: #fafafa;
}
p {
margin: 0;
font-size: 13px;
font-weight: 400;
line-height: 18px;
}
.not-curr {
border: 1px solid #999;
border-radius: 20px;
padding: 3px 10px;
margin-left: 10px;
font-size: 12px;
font-weight: normal;
border: none;
border-radius: 0;
padding: 0;
margin: 0;
font-size: 13px;
font-weight: 400;
color: #999;
}
}
.currTimeline {
background-color: $theme_color;
background: $theme_color;
color: #fff;
>div:nth-child(1) {
font-size: 20px;
&:hover {
background: $theme_color;
}
>div:nth-child(2) {
font-size: 14px;
margin-left: 10px;
> div:nth-child(1) {
font-size: 22px;
line-height: 28px;
}
> div:nth-child(2) {
margin-left: 0;
}
.not-curr {
color: #fff;
}
}
}
@@ -249,55 +277,116 @@ export default {
.seckill-goods-wrap {
width: 1200px;
max-width: 1200px;
margin: 0 auto;
margin: 16px auto 40px;
padding: 16px;
box-sizing: border-box;
background: #fff;
border-radius: 8px;
min-height: 320px;
.goods-list {
width: 100%;
margin: 0;
column-gap: 10px;
column-gap: 16px;
}
:deep(.empty) {
margin: 48px 0;
color: #999;
}
}
.goods-show-info1 {
width: calc(25% - 8px);
width: calc(25% - 12px);
min-width: 0;
padding: 6px;
margin: 10px 0;
padding: 12px;
margin: 0 0 16px;
position: relative;
border: 1px solid #fff;
border: 1px solid #f0f0f0;
border-radius: 8px;
cursor: pointer;
background-color: #fff;
box-sizing: border-box;
}
display: flex;
flex-direction: column;
.goods-show-info1:hover {
border: 1px solid #ccc;
box-shadow: 0px 0px 15px #ccc;
&:hover {
border-color: #eee;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06);
}
}
.goods-show-img {
display: flex;
justify-content: center;
border-radius: 6px;
overflow: hidden;
background: #fafafa;
img {
width: 100%;
max-width: 200px;
height: auto;
aspect-ratio: 1;
object-fit: cover;
}
}
.goods-show-price {
display: flex;
align-items: baseline;
gap: 8px;
margin-top: 10px;
}
.origin-price {
color: #999;
font-size: 13px;
text-decoration: line-through;
}
.goods-show-detail {
height: 40px;
margin: 8px 0;
color: #333;
font-size: 14px;
line-height: 20px;
}
.goods-show-num1 {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
margin-bottom: 6px;
margin-bottom: 8px;
color: #666;
span {
flex-shrink: 0;
}
:deep(.el-progress) {
flex: 1;
}
}
.el-progress-bar__inner {
background-color: #df0021;
.goods-show-seller {
margin-bottom: 10px;
color: #999;
font-size: 13px;
}
.goods-seckill-btn {
margin-top: auto;
height: 34px;
border-radius: 4px;
color: #fff;
text-align: center;
line-height: 34px;
font-size: 14px;
background-color: $theme_color;
&.is-disabled {
background: #c8c8c8;
}
}
</style>

View File

@@ -24,17 +24,25 @@
<p>{{ userInfo.nickName }}</p>
</div>
<el-menu-item index="UserCenter">
<el-icon><House /></el-icon>
<span>我的主页</span>
</el-menu-item>
<template v-for="(menu, index) in menuList" :key="index">
<el-sub-menu v-if="menu.display" :index="menu.title">
<template #title>
<el-icon><Location /></el-icon>
<el-icon><component :is="menuIcon(menu.icon)" /></el-icon>
<span>{{ menu.title }}</span>
</template>
<el-menu-item
v-for="(chlidren, i) in menu.menus"
:key="i"
:index="chlidren.path"
>{{ chlidren.title }}</el-menu-item>
>
<el-icon><component :is="menuIcon(chlidren.icon)" /></el-icon>
<span>{{ chlidren.title }}</span>
<span v-if="chlidren.path === 'MsgList' && unreadMsg > 0" class="menu-badge">{{ unreadLabel }}</span>
</el-menu-item>
</el-sub-menu>
</template>
</el-menu>
@@ -48,12 +56,51 @@
</template>
<script>
import { Location, User } from '@element-plus/icons-vue';
import {
Bell,
ChatDotRound,
Clock,
Coin,
Goods,
House,
List,
Location,
Lock,
Present,
RefreshLeft,
Star,
Ticket,
Trophy,
User,
Wallet,
Warning,
} from '@element-plus/icons-vue';
import menuList from "./menu";
import Storage from "@/plugins/storage.js";
import { refreshUnreadMessage } from "@/utils/unreadMessage";
const MENU_ICONS = {
Bell,
ChatDotRound,
Clock,
Coin,
Goods,
House,
List,
Location,
Lock,
Present,
RefreshLeft,
Star,
Ticket,
Trophy,
User,
Wallet,
Warning,
};
export default {
components: { Location, User },
components: { House, User },
name: "Home",
data() {
return {
@@ -67,14 +114,24 @@ export default {
}
return {};
},
unreadMsg() {
return Number(this.$store.state.unreadMsg) || 0;
},
unreadLabel() {
return this.unreadMsg > 99 ? "99+" : String(this.unreadMsg);
},
},
mounted() {
document.body.classList.add("buyer-user-center");
refreshUnreadMessage();
},
beforeUnmount() {
document.body.classList.remove("buyer-user-center");
},
methods: {
menuIcon(name) {
return MENU_ICONS[name] || Goods;
},
onSelect(name) {
this.$router.push({ name });
},
@@ -134,6 +191,38 @@ export default {
overflow: hidden;
}
.side-menu {
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
display: flex;
align-items: center;
}
:deep(.el-menu-item .el-icon),
:deep(.el-sub-menu__title .el-icon) {
margin-right: 6px;
}
}
.menu-badge {
margin-left: auto;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 8px;
background: $theme_color;
color: #fff;
font-size: 12px;
font-style: normal;
line-height: 16px;
text-align: center;
}
/* 选中项会把文字设成主题红。角标用 span,避免 i 继承成红底红字 */
.side-menu :deep(.el-menu-item.is-active) .menu-badge {
color: #fff;
}
.layout-footer-center {
padding: 0px 15px;
padding-bottom: 15px;

View File

@@ -6,20 +6,20 @@
// 订单中心
const order = [{
icon: '',
icon: 'List',
title: '订单中心',
menus: [{
icon: '',
icon: 'Goods',
title: '我的订单',
path: 'MyOrder'
},
{
icon: '',
icon: 'Location',
title: '收货地址',
path: 'MyAddress'
},
{
icon: '',
icon: 'RefreshLeft',
title: '售后订单',
path: 'AfterSale'
}
@@ -29,66 +29,66 @@ const order = [{
// 会员中心
const member = [{
icon: '',
icon: 'User',
title: '会员中心',
menus: [{
icon: '',
icon: 'User',
title: '用户信息',
path: 'Profile'
},
{
icon: '',
icon: 'Lock',
title: '账户安全',
path: 'AccountSafe'
},
{
icon: '',
icon: 'Bell',
title: '我的消息',
path: 'MsgList'
},
{
icon: '',
icon: 'Clock',
title: '我的足迹',
path: 'MyTracks'
},
{
icon: '',
icon: 'Star',
title: '我的收藏',
path: 'Favorites'
},
{
icon: '',
icon: 'ChatDotRound',
title: '我的评论',
path: 'CommentList'
},
{
icon: '',
icon: 'Warning',
title: '我的投诉',
path: 'ComplainList'
},
{
icon: '',
icon: 'Coin',
title: '我的积分',
path: 'Point'
},
{
icon: '',
icon: 'Trophy',
title: '我的等级',
path: 'MemberGrade'
},
{
icon: '',
icon: 'Ticket',
title: '我的优惠券',
path: 'Coupons'
},
{
icon: '',
icon: 'Wallet',
title: '我的余额',
path: 'MoneyManagement'
},
{
icon: '',
icon: 'Present',
title: '我的礼品卡',
path: 'MyGiftCards'
}