feat(buyer): 新增功能并优化样式,提升用户体验

- 新增 `getMemberMessage` 接口方法,用于获取会员消息
- 调整全局及商品列表样式,提升可读性与一致性
- 精简 `Search.vue`,移除不必要的店铺搜索切换
- 放宽 `addressManage` 中地址别名的输入长度
- 优化 `ShowGoods.vue` 和 `ShowGoodsDetail.vue` 的布局与样式
- 更新 `Cart.vue` 中的购物车步骤展示,让流程导航更清晰
- 调整多个组件,统一字号并优化界面元素
This commit is contained in:
pikachu1995@126.com
2026-09-29 18:11:31 +08:00
parent 867b03ebfc
commit 4777eb2dd6
54 changed files with 5816 additions and 3938 deletions

View File

@@ -57,7 +57,7 @@ export const afterSaleStatusList = [
status: 'REFUSE'
},
{
name: '买家退货,待卖家收货',
name: '待卖家收货',
status: 'BUYER_RETURN'
},
{

View File

@@ -1,6 +1,6 @@
<template>
<div class="wrapper">
<UserCenterLayout title="账户安全" :tabs="['账户安全']">
<UserCenterLayout>
<div class="safeList">
<!-- 密码 -->
<div class="safeItem">
@@ -365,7 +365,7 @@ export default {
.wechatQrEmpty {
color: $light_sub_color;
font-size: 12px;
font-size: 14px;
text-align: center;
padding: 0 8px;
}
@@ -379,7 +379,7 @@ export default {
.wechatQrTip {
margin-top: 10px;
color: $light_sub_color;
font-size: 12px;
font-size: 14px;
line-height: 1.6;
}
</style>

View File

@@ -1,50 +1,38 @@
<template>
<div class="wrapper">
<UserCenterLayout title="评论/晒单" :tabs="['评论/晒单']">
<UserCenterLayout>
<div class="order">
<div class="order-title">
<div class="order-row title">
<div class="order-detail-title col-10">订单详情</div>
<div class="col-4">收货人</div>
<div class="col-4">评价等级</div>
<div class="col-4">评价内容</div>
<div class="col-2"></div>
</div>
<empty v-if="list.length === 0" />
<div class="comment-list" v-else>
<div class="comment-cols">
<span>订单详情</span>
<span>评价等级</span>
<span>评价内容</span>
<span>操作</span>
</div>
<empty v-if="list.length === 0" />
<div class="order-item" v-else v-for="(item, index) in list" :key="index">
<div>
<div class="title order-item-title">
<span>订单号:{{item.orderNo}}</span>
<span class="color999 ml_10">{{item.createTime}}</span>
<span class="hover-pointer fontsize_12 eval-detail" @click="evaluateDetail(item.id)">评价详情</span>
<div class="comment-card" v-for="(item, index) in list" :key="item.id || index">
<div class="comment-head">
<div class="order-sn">
{{ item.createTime }}<span>订单号 {{ item.orderNo }}</span>
</div>
<div class="order-item-view">
<div class="item-view-name col-10">
<div class="order-img hover-color" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
<img :src="item.goodsImage" alt="" />
</div>
<div class="order-name hover-color" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
{{item.goodsName}}
</div>
</div>
<div class="comment-body">
<div class="goods">
<div class="goods-thumb" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
<img
:src="item.goodsImage"
alt=""
@error="$event.target.style.visibility = 'hidden'"
/>
</div>
<div class="col-4">{{ $filters.secrecyMobile(item.createBy ) }}</div>
<div class="col-4">
{{item.grade==='GOOD'?'好评' : item.grade === 'WORSE'?'差评' : '中评'}}
<div class="goods-name" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
{{ item.goodsName }}
</div>
<div class="col-4">
<el-tooltip>
<div class="content">{{item.content}}</div>
<template #content>
<div style="white-space: normal;word-break:break-all">
{{item.content}}
</div>
</template>
</el-tooltip>
</div>
<div class="col-2"></div>
</div>
<div class="comment-cell">{{ item.grade === 'GOOD' ? '好评' : item.grade === 'WORSE' ? '差评' : '中评' }}</div>
<div class="comment-content">{{ item.content }}</div>
<div class="comment-ops">
<button type="button" class="text-link" @click="evaluateDetail(item.id)">评价详情</button>
</div>
</div>
</div>
@@ -53,7 +41,7 @@
<!-- 分页 -->
<div class="page-size">
<el-pagination v-model:current-page="params.pageNumber" v-model:page-size="params.pageSize"
:total="total" @current-change="changePageNum"
:total="total" :page-sizes="[20, 50, 100]" @current-change="changePageNum"
@size-change="changePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</UserCenterLayout>
@@ -72,7 +60,7 @@ export default {
total: 0, // 评价总数
params: { // 请求参数
pageNumber: 1,
pageSize: 10
pageSize: 20
}
};
},
@@ -112,108 +100,143 @@ export default {
.page-size {
display: flex;
justify-content: flex-end;
}
.order-img {
> img {
width: 60px;
height: 60px;
border: 1px solid $border_color;
box-sizing: border-box;
}
}
.title {
@include background_color($light_background_color);
}
.item-view-name {
display: flex;
}
.order-name {
display: -webkit-box;
margin-top: 16px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-align: left;
padding: 0 10px;
font-size: 13px;
@include content_color($light_content_color);
}
.order-item-title {
padding: 5px 20px;
text-align: left;
font-size: 13px;
position: relative;
}
.order-row,
.order-item-view {
display: flex;
align-items: center;
> [class*='col-'] {
box-sizing: border-box;
padding: 0 8px;
}
> .col-12 {
flex: 0 0 50%;
max-width: 50%;
}
> .col-10 {
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
> .col-4 {
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
> .col-2 {
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
> .col-6 {
flex: 0 0 25%;
max-width: 25%;
:deep(.el-pagination) {
justify-content: flex-end;
}
}
.order-item-view {
padding: 10px 20px;
align-items: flex-start;
.comment-list {
--comment-cols: minmax(220px, 1.3fr) 90px minmax(160px, 1.6fr) 96px;
--comment-gap: 12px;
padding-top: 8px;
}
.order-item {
text-align: center;
border: 1px solid $border_color;
margin: 10px 0;
.comment-cols,
.comment-body {
display: grid;
grid-template-columns: var(--comment-cols);
column-gap: var(--comment-gap);
align-items: start;
}
.order-row {
padding: 10px 0;
text-align: center;
}
.order-detail-title {
text-align: left;
padding-left: 20px !important;
}
.content {
.comment-cols {
padding: 0 16px 10px;
color: #999;
max-height: 60px;
overflow: hidden;
// word-wrap: break-word;
display:-webkit-box;
-webkit-box-orient:vertical;/*设置方向*/
-webkit-line-clamp:3;/*设置超过为省略号的行数*/
word-break:break-all;
font-size: 13px;
span:first-child {
padding-left: 72px;
}
span:nth-child(n + 2) {
text-align: center;
}
}
.eval-detail {
position: absolute;
right: 20px;
&:hover{
.comment-card {
margin-bottom: 12px;
border: 1px solid #eee;
border-radius: 8px;
background: #fff;
overflow: hidden;
}
.comment-head {
padding: 10px 16px;
background: #f7f7f7;
border-bottom: 1px solid #f0f0f0;
}
.order-sn {
color: #171A26;
font-size: 14px;
line-height: 22px;
span {
margin-left: 16px;
}
}
.comment-body {
padding: 14px 16px;
}
.goods {
display: grid;
grid-template-columns: 60px minmax(0, 1fr);
column-gap: 12px;
align-items: start;
min-width: 0;
}
.goods-thumb {
width: 60px;
height: 60px;
border: 1px solid #f0f0f0;
overflow: hidden;
background: #f5f5f5;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
.goods-name {
color: #333;
font-size: 16px;
line-height: 22px;
max-height: 44px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.comment-cell,
.comment-ops {
text-align: center;
color: #171A26;
font-size: 14px;
line-height: 22px;
}
.comment-content {
min-width: 0;
color: #666;
font-size: 14px;
line-height: 22px;
white-space: pre-wrap;
word-break: break-all;
}
.comment-ops {
display: flex;
justify-content: center;
}
.text-link {
border: none;
background: none;
padding: 0;
color: #171A26;
font-family: inherit;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="add-eval">
<UserCenterLayout title="订单投诉" :tabs="['订单投诉']">
<UserCenterLayout>
<div class="title">
<p>
<span class="fontsize_16 global_color">{{ statusLabel[detail.complainStatus] }}</span>
@@ -123,7 +123,7 @@
</tbody>
</table>
<el-dialog title="View Image" v-model="visible">
<el-dialog title="图片预览" v-model="visible">
<img :src="previewImage" v-if="visible" style="width: 100%">
</el-dialog>
</UserCenterLayout>

View File

@@ -1,51 +1,46 @@
<template>
<div class="wrapper">
<UserCenterLayout title="我的投诉" :tabs="['我的投诉']">
<UserCenterLayout>
<div class="order">
<div class="order-title">
<div class="order-row title">
<div class="order-detail-title col-10">商品信息</div>
<div class="col-4">投诉状态</div>
<div class="col-4">投诉主题</div>
<div class="col-4">投诉内容</div>
<div class="col-2"></div>
</div>
<empty v-if="list.length === 0" />
<div class="complain-list" v-else>
<div class="complain-cols">
<span>商品信息</span>
<span>投诉状态</span>
<span>投诉主题</span>
<span>投诉内容</span>
<span>操作</span>
</div>
<empty v-if="list.length === 0" />
<div class="order-item" v-else v-for="(item, index) in list" :key="index">
<div>
<div class="title order-item-title">
<span>投诉单号:{{item.id}}</span>
<span class="color999 ml_10">{{item.createTime}}</span>
<span class="hover-pointer fontsize_12 eval-detail" @click="goDetail(item.id)">投诉详情</span>
<span class="hover-pointer fontsize_12 eval-detail" style="right: 90px" v-if="item.complainStatus === 'APPLYING' || item.complainStatus === 'NEW'" @click="cancel(item.id)">取消投诉</span>
<div class="complain-card" v-for="(item, index) in list" :key="item.id || index">
<div class="complain-head">
<div class="order-sn">
{{ item.createTime }}<span>投诉单号 {{ item.id }}</span>
</div>
<div class="order-item-view">
<div class="item-view-name col-10">
<div class="order-img hover-color" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
<img :src="item.goodsImage" alt="" />
</div>
<div class="order-name hover-color" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
{{item.goodsName}}
</div>
</div>
<div class="complain-body">
<div class="goods">
<div class="goods-thumb" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
<img
:src="item.goodsImage"
alt=""
@error="$event.target.style.visibility = 'hidden'"
/>
</div>
<div class="col-4">{{statusLabel[item.complainStatus]}}</div>
<div class="col-4">
<div class="content">{{item.complainTopic}}</div>
<div class="goods-name" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
{{ item.goodsName }}
</div>
<div class="col-4">
<el-tooltip>
<div class="content">{{item.content}}</div>
<template #content>
<div style="white-space: normal;">
{{item.content}}
</div>
</template>
</el-tooltip>
</div>
<div class="col-2"></div>
</div>
<div class="complain-cell">{{ statusLabel[item.complainStatus] || item.complainStatus }}</div>
<div class="complain-topic">{{ item.complainTopic }}</div>
<div class="complain-content">{{ item.content }}</div>
<div class="complain-ops">
<button type="button" class="text-link" @click="goDetail(item.id)">投诉详情</button>
<button
v-if="item.complainStatus === 'APPLYING' || item.complainStatus === 'NEW'"
type="button"
class="text-link"
@click="cancel(item.id)"
>取消投诉</button>
</div>
</div>
</div>
@@ -54,7 +49,7 @@
<!-- 分页 -->
<div class="page-size">
<el-pagination v-model:current-page="params.pageNumber" v-model:page-size="params.pageSize"
:total="total" @current-change="changePageNum"
:total="total" :page-sizes="[20, 50, 100]" @current-change="changePageNum"
@size-change="changePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</UserCenterLayout>
@@ -81,7 +76,7 @@ export default {
total: 0, // 投诉总数
params: { // 请求参数
pageNumber: 1,
pageSize: 10
pageSize: 20
}
};
},
@@ -130,101 +125,158 @@ export default {
</script>
<style scoped lang="scss">
.order-img {
> img {
width: 60px;
height: 60px;
border: 1px solid $border_color;
box-sizing: border-box;
}
}
.title {
@include background_color($light_background_color);
}
.item-view-name {
.page-size {
display: flex;
}
.order-name {
display: -webkit-box;
justify-content: flex-end;
margin-top: 16px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-align: left;
padding: 0 10px;
font-size: 13px;
@include content_color($light_content_color);
}
.order-item-title {
padding: 5px 20px;
text-align: left;
font-size: 13px;
position: relative;
}
.order-row,
.order-item-view {
display: flex;
align-items: center;
> [class*='col-'] {
box-sizing: border-box;
padding: 0 8px;
}
> .col-10 {
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
> .col-4 {
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
> .col-2 {
flex: 0 0 8.333333%;
max-width: 8.333333%;
:deep(.el-pagination) {
justify-content: flex-end;
}
}
.order-item-view {
padding: 10px 20px;
align-items: flex-start;
.complain-list {
--complain-cols: minmax(220px, 1.3fr) 90px minmax(100px, 0.8fr) minmax(160px, 1.4fr) 96px;
--complain-gap: 12px;
padding-top: 8px;
}
.order-item {
text-align: center;
border: 1px solid $border_color;
margin: 10px 0;
.complain-cols,
.complain-body {
display: grid;
grid-template-columns: var(--complain-cols);
column-gap: var(--complain-gap);
align-items: start;
}
.order-row {
padding: 10px 0;
text-align: center;
}
.order-detail-title {
text-align: left;
padding-left: 20px !important;
}
.content {
.complain-cols {
padding: 0 16px 10px;
color: #999;
max-height: 60px;
font-size: 13px;
span:first-child {
padding-left: 72px;
}
span:nth-child(n + 2) {
text-align: center;
}
}
.complain-card {
margin-bottom: 12px;
border: 1px solid #eee;
border-radius: 8px;
background: #fff;
overflow: hidden;
}
.complain-head {
padding: 10px 16px;
background: #f7f7f7;
border-bottom: 1px solid #f0f0f0;
}
.order-sn {
color: #171A26;
font-size: 14px;
line-height: 22px;
span {
margin-left: 16px;
}
}
.complain-body {
padding: 14px 16px;
}
.goods {
display: grid;
grid-template-columns: 60px minmax(0, 1fr);
column-gap: 12px;
align-items: start;
min-width: 0;
}
.goods-thumb {
width: 60px;
height: 60px;
border: 1px solid #f0f0f0;
overflow: hidden;
background: #f5f5f5;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
.goods-name {
color: #333;
font-size: 16px;
line-height: 22px;
max-height: 44px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
cursor: pointer;
.eval-detail {
position: absolute;
right: 20px;
&:hover{
&:hover {
color: $theme_color;
}
}
.page-size{
.complain-cell,
.complain-topic,
.complain-ops {
color: #171A26;
font-size: 14px;
line-height: 22px;
}
.complain-cell,
.complain-ops {
text-align: center;
}
.complain-topic,
.complain-content {
min-width: 0;
white-space: pre-wrap;
word-break: break-all;
}
.complain-content {
color: #666;
font-size: 14px;
line-height: 22px;
}
.complain-ops {
display: flex;
justify-content: flex-end;
flex-direction: column;
align-items: center;
gap: 8px;
}
.text-link {
border: none;
background: none;
padding: 0;
color: #171A26;
font-family: inherit;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
</style>

View File

@@ -2,78 +2,80 @@
<div class="wrapper">
<UserCenterLayout
v-if="!homePage"
title="近期收藏"
:tabs="favoriteWay"
:active-tab="activeTabIndex"
@tab-change="change"
>
<div v-if="list.length">
<div v-for="(item, index) in list" :key="index">
<div class="goodsItem" :key="item.skuId">
<div class="goodsImg hover-pointer" v-if="params.type === 'GOODS'">
<img :src="item.image" />
<empty v-if="!list.length" />
<div v-else class="fav-list" :class="{ 'is-store': params.type === 'STORE' }">
<div class="fav-cols">
<span>{{ params.type === 'GOODS' ? '商品' : '店铺' }}</span>
<span v-if="params.type === 'GOODS'">金额</span>
<span>操作</span>
</div>
<div class="fav-row" v-for="(item, index) in list" :key="item.id || item.skuId || index">
<div class="fav-goods">
<div class="fav-thumb" @click="openItem(item)">
<img
:src="params.type === 'GOODS' ? item.image : item.storeLogo"
alt=""
@error="$event.target.style.visibility = 'hidden'"
/>
</div>
<div class="goodsImg hover-pointer" v-else>
<img :src="item.storeLogo" />
</div>
<div class="goodsTitle hover-color" v-if="params.type === 'GOODS'" @click="buynow(item.skuId, item.goodsId,item.id)">
{{ item.goodsName }}
</div>
<div v-else class="goodsTitle hover-pointer" @click="buynow(item.skuId, item.goodsId,item.id)" >
{{ item.storeName }}
<el-tag color="error" class="operated" v-if="item.selfOperated">商家自营</el-tag>
</div>
<div class="goodsPrice">
<span v-if="params.type === 'GOODS'">{{ $filters.unitPrice(item.price, '¥') }}</span>
</div>
<div class="goodsBuy">
<el-button size="small" type="primary" @click="buynow(item.skuId, item.goodsId)"
v-if="params.type === 'GOODS'">立即购买</el-button>
<el-button size="small" type="primary" @click="goShop(item.id)" v-else>店铺购买</el-button>
<el-button size="small" v-if="params.type === 'GOODS'" @click="cancel(item.skuId)">取消收藏</el-button>
<el-button size="small" v-if="params.type === 'STORE'" @click="cancelStore(item.id)">取消收藏</el-button>
<div class="fav-info">
<div class="fav-name" @click="openItem(item)">
{{ params.type === 'GOODS' ? item.goodsName : item.storeName }}
</div>
<span v-if="params.type === 'STORE' && item.selfOperated" class="self-tag">商家自营</span>
</div>
</div>
<div v-if="params.type === 'GOODS'" class="fav-price">{{ $filters.unitPrice(item.price, '¥') }}</div>
<div class="fav-ops">
<button v-if="params.type === 'GOODS'" type="button" class="op-btn" @click="buynow(item.skuId, item.goodsId)">立即购买</button>
<button v-else type="button" class="op-btn" @click="goShop(item.id)">查看店铺</button>
<button v-if="params.type === 'GOODS'" type="button" class="text-link" @click="cancel(item.skuId)">取消收藏</button>
<button v-else type="button" class="text-link" @click="cancelStore(item.id)">取消收藏</button>
</div>
</div>
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
</div>
<empty v-else />
</UserCenterLayout>
<template v-else>
<card _Title="近期收藏" :_Size="16" :_Tabs="favoriteWay" :_ActiveTab="activeTabIndex" @_Change="change" _More="全部收藏" _Src="/home/Favorites"></card>
<div v-if="list.length">
<div v-for="(item, index) in list" :key="index">
<div class="goodsItem" :key="item.skuId">
<div class="goodsImg hover-pointer" v-if="params.type === 'GOODS'">
<img :src="item.image" />
<empty v-if="!list.length" />
<div v-else class="fav-list" :class="{ 'is-store': params.type === 'STORE' }">
<div class="fav-cols">
<span>{{ params.type === 'GOODS' ? '商品' : '店铺' }}</span>
<span v-if="params.type === 'GOODS'">金额</span>
<span>操作</span>
</div>
<div class="fav-row" v-for="(item, index) in list" :key="item.id || item.skuId || index">
<div class="fav-goods">
<div class="fav-thumb" @click="openItem(item)">
<img
:src="params.type === 'GOODS' ? item.image : item.storeLogo"
alt=""
@error="$event.target.style.visibility = 'hidden'"
/>
</div>
<div class="goodsImg hover-pointer" v-else>
<img :src="item.storeLogo" />
</div>
<div class="goodsTitle hover-color" v-if="params.type === 'GOODS'" @click="buynow(item.skuId, item.goodsId,item.id)">
{{ item.goodsName }}
</div>
<div v-else class="goodsTitle hover-pointer" @click="buynow(item.skuId, item.goodsId,item.id)" >
{{ item.storeName }}
<el-tag color="error" class="operated" v-if="item.selfOperated">商家自营</el-tag>
</div>
<div class="goodsPrice">
<span v-if="params.type === 'GOODS'">{{ $filters.unitPrice(item.price, '¥') }}</span>
</div>
<div class="goodsBuy">
<el-button size="small" type="primary" @click="buynow(item.skuId, item.goodsId)"
v-if="params.type === 'GOODS'">立即购买</el-button>
<el-button size="small" type="primary" @click="goShop(item.id)" v-else>店铺购买</el-button>
<el-button size="small" v-if="params.type === 'GOODS'" @click="cancel(item.skuId)">取消收藏</el-button>
<el-button size="small" v-if="params.type === 'STORE'" @click="cancelStore(item.id)">取消收藏</el-button>
<div class="fav-info">
<div class="fav-name" @click="openItem(item)">
{{ params.type === 'GOODS' ? item.goodsName : item.storeName }}
</div>
<span v-if="params.type === 'STORE' && item.selfOperated" class="self-tag">商家自营</span>
</div>
</div>
<div v-if="params.type === 'GOODS'" class="fav-price">{{ $filters.unitPrice(item.price, '¥') }}</div>
<div class="fav-ops">
<button v-if="params.type === 'GOODS'" type="button" class="op-btn" @click="buynow(item.skuId, item.goodsId)">立即购买</button>
<button v-else type="button" class="op-btn" @click="goShop(item.id)">查看店铺</button>
<button v-if="params.type === 'GOODS'" type="button" class="text-link" @click="cancel(item.skuId)">取消收藏</button>
<button v-else type="button" class="text-link" @click="cancelStore(item.id)">取消收藏</button>
</div>
</div>
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
</div>
<empty v-else />
</template>
</div>
</template>
@@ -172,6 +174,13 @@ export default {
}
});
},
openItem (item) {
if (this.params.type === 'STORE') {
this.goShop(item.id)
return
}
this.buynow(item.skuId, item.goodsId)
},
buynow(skuId, goodsId,storeId) { // 跳转详情
console.log(this.params.type)
let url
@@ -206,73 +215,148 @@ export default {
</script>
<style scoped lang="scss">
.goodsShop,
.goodsImg,
.goodsPrice,
.goodsShop,
.goodsTitle {
margin: 0 6px;
}
.operated {
margin-left: 10px;
}
.wrapper {
margin-bottom: 40px;
}
.goodsItem {
display: flex;
.fav-list {
--fav-cols: minmax(0, 1fr) 140px 200px;
--fav-gap: 12px;
padding-top: 8px;
&.is-store {
--fav-cols: minmax(0, 1fr) 200px;
}
}
.fav-cols,
.fav-row {
display: grid;
grid-template-columns: var(--fav-cols);
column-gap: var(--fav-gap);
align-items: center;
height: 60px;
margin-bottom: 10px;
}
>.goodsImg {
width: 60px;
height: 60px;
overflow: hidden;
flex-shrink: 0;
.fav-cols {
padding: 0 8px 10px;
color: #999;
font-size: 13px;
>img {
width: 100%;
height: 100%;
}
span:first-child {
padding-left: 72px;
}
>.goodsPrice,
.goodsShop {
width: 120px;
flex-shrink: 0;
span:nth-child(n + 2) {
text-align: center;
}
}
>.goodsTitle {
flex: 1;
min-width: 0;
line-height: 60px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.fav-row {
padding: 14px 8px;
border-top: 1px solid #f0f0f0;
}
.fav-goods {
display: grid;
grid-template-columns: 60px minmax(0, 1fr);
column-gap: 12px;
align-items: center;
min-width: 0;
}
.fav-thumb {
width: 60px;
height: 60px;
border: 1px solid #f0f0f0;
overflow: hidden;
background: #f5f5f5;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
.goodsBuy {
.fav-info {
min-width: 0;
}
.fav-name {
color: #333;
font-size: 16px;
line-height: 22px;
max-height: 44px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.self-tag {
display: inline-block;
margin-top: 6px;
padding: 0 6px;
border-radius: 4px;
background: #fff1f0;
color: $theme_color;
font-size: 12px;
line-height: 20px;
}
.fav-price {
text-align: center;
color: #333;
font-size: 16px;
font-weight: 600;
line-height: 1.4;
}
.fav-ops {
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
gap: 8px;
width: 170px;
margin-left: 16px;
line-height: normal;
justify-content: center;
gap: 16px;
}
:deep(.el-button) {
margin: 0;
.op-btn {
min-width: 96px;
height: 32px;
padding: 0 12px;
border: 1px solid $theme_color;
border-radius: 4px;
background: $theme_color;
color: #fff;
font-family: inherit;
font-size: 14px;
line-height: 30px;
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
.page-size {
text-align: right;
.text-link {
border: none;
background: none;
padding: 0;
color: #171A26;
font-family: inherit;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
</style>

View File

@@ -1,83 +1,73 @@
<template>
<div class="memberGrade">
<UserCenterLayout title="我的等级" :tabs="['我的等级']">
<div class="summary">
<div class="gradeCard">
<div class="gradeLeft">
<img v-if="currentGrade.gradeImage" class="gradeIcon" :src="currentGrade.gradeImage" alt="等级图标" />
<div class="gradeInfo">
<div class="gradeNameRow">
<span class="gradeNameText">{{ currentGrade.gradeName || '暂无等级' }}</span>
<span v-if="currentGradeBenefits.length" class="benefitTagsInline">
<el-tag v-for="b in currentGradeBenefits" :key="b.id" class="benefitTag" type="primary">{{ b.benefitName || '-' }}</el-tag>
</span>
</div>
<div class="gradeExp">
当前经验值:<span class="num">{{ currentExperience || 0 }}</span>
</div>
<div class="gradeNext" v-if="nextGrade">
距 {{ nextGrade.gradeName }} 还差 <span class="num">{{ diffExperience }}</span> 经验值
</div>
<div class="gradeNext" v-else>已达最高等级</div>
</div>
<UserCenterLayout>
<div class="grade-card">
<img v-if="currentGrade.gradeImage" class="grade-icon" :src="currentGrade.gradeImage" alt="" />
<div class="grade-info">
<div class="grade-name-row">
<span class="grade-name">{{ currentGrade.gradeName || '暂无等级' }}</span>
<span v-for="b in currentGradeBenefits" :key="b.id" class="benefit-tag">{{ b.benefitName || '-' }}</span>
</div>
<div class="grade-exp">当前经验值:<span class="num">{{ currentExperience || 0 }}</span></div>
<div class="grade-next" v-if="nextGrade">
距 {{ nextGrade.gradeName }} 还差 <span class="num">{{ diffExperience }}</span> 经验值
</div>
<div class="grade-next" v-else>已达最高等级</div>
</div>
</div>
<el-tabs v-model="activeGradeTab">
<el-tab-pane label="获取经验值方式" name="rules">
<el-table v-loading="rulesLoading" :data="ruleList">
<el-table-column label="获取方式" min-width="720">
<template #default="{ row }">
<div class="ruleItem">
<div class="ruleTop">
<div class="ruleName">
<span>{{ row.ruleName || row.ruleKey || '-' }}</span>
</div>
<el-tag v-if="row.completed === true" color="success">已完成</el-tag>
<el-tag v-else color="default">未完成</el-tag>
</div>
<div class="ruleDesc">{{ getRuleDesc(row.ruleKey) }}</div>
<div class="ruleMeta">
<span class="metaItem">单次获得:<b>+{{ row.value == null ? 0 : row.value }}</b></span>
<span v-if="row.maxValue != null && Number(row.maxValue) > 0" class="metaItem">限额:<b>{{ row.maxValue }}</b></span>
<span class="metaItem">已获得:<b>{{ row.gainedExperience == null ? 0 : row.gainedExperience }}</b></span>
</div>
</div>
</template>
</el-table-column>
</el-table>
<div class="rulesDesc" v-if="rulesDescription">{{ rulesDescription }}</div>
</el-tab-pane>
<el-tab-pane label="经验值记录" name="logs">
<el-table v-loading="logLoading" :data="experienceLogs.records || []">
<el-table-column prop="createTime" label="时间" align="center" min-width="160" />
<el-table-column label="规则" align="center" min-width="160">
<template #default="{ row }">
<span>{{ getRuleName(row.ruleKey) }}</span>
</template>
</el-table-column>
<el-table-column label="变动值" align="center" min-width="120">
<template #default="{ row }">
<div :style="{ color: getLogDeltaColor(row) }">
<span v-if="getLogDeltaSign(row) === '+'">+</span>{{ getLogDeltaAbsValue(row) }}
</div>
</template>
</el-table-column>
</el-table>
<div class="grade-pagination">
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="experienceLogs.total || 0"
@current-change="changePage"
@size-change="changePageSize"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next, jumper"
/>
<div class="grade-tabs">
<div class="grade-tab" :class="{ active: activeGradeTab === 'rules' }" @click="activeGradeTab = 'rules'">获取经验值方式</div>
<div class="grade-tab" :class="{ active: activeGradeTab === 'logs' }" @click="activeGradeTab = 'logs'">经验值记录</div>
</div>
<div v-show="activeGradeTab === 'rules'" v-loading="rulesLoading">
<empty v-if="!rulesLoading && !ruleList.length" />
<div v-else class="rule-list">
<div class="rule-item" v-for="row in ruleList" :key="row.ruleKey || row.id">
<div class="rule-top">
<div class="rule-name">{{ row.ruleName || row.ruleKey || '-' }}</div>
<span class="rule-status" :class="{ done: row.completed === true }">{{ row.completed === true ? '已完成' : '未完成' }}</span>
</div>
<div class="rule-desc">{{ getRuleDesc(row.ruleKey) }}</div>
<div class="rule-meta">
<span>单次获得 <b>+{{ row.value == null ? 0 : row.value }}</b></span>
<span v-if="row.maxValue != null && Number(row.maxValue) > 0">限额 <b>{{ row.maxValue }}</b></span>
<span>已获得 <b>{{ row.gainedExperience == null ? 0 : row.gainedExperience }}</b></span>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<div class="rules-desc" v-if="rulesDescription">{{ rulesDescription }}</div>
</div>
<div v-show="activeGradeTab === 'logs'" v-loading="logLoading">
<empty v-if="!(experienceLogs.records || []).length" />
<div v-else class="log-list">
<div class="log-head">
<span>时间</span>
<span>规则</span>
<span>变动值</span>
</div>
<div class="log-row" v-for="(row, index) in experienceLogs.records" :key="row.id || index">
<div class="log-time">{{ row.createTime }}</div>
<div class="log-rule">{{ getRuleName(row.ruleKey) }}</div>
<div class="log-point" :class="getLogDeltaSign(row) === '+' ? 'is-plus' : 'is-minus'">
{{ getLogDeltaSign(row) }}{{ getLogDeltaAbsValue(row) }}
</div>
</div>
</div>
<div class="grade-pagination">
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="experienceLogs.total || 0"
@current-change="changePage"
@size-change="changePageSize"
:page-sizes="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</div>
</UserCenterLayout>
</div>
@@ -106,7 +96,7 @@ export default {
gradeLoading: false,
params: {
pageNumber: 1,
pageSize: 10
pageSize: 20
},
experienceLogs: {}
}
@@ -288,131 +278,220 @@ export default {
</script>
<style scoped lang="scss">
h3 {
font-size: 16px;
margin: 20px 10px;
}
.summary {
margin-bottom: 20px;
}
.rulesDesc {
margin: 10px 0 0 0;
padding: 10px 12px;
border: 1px solid $border_color;
border-radius: 4px;
background: #fff;
color: $light_sub_color;
font-size: 12px;
line-height: 1.7;
}
.gradeCard {
padding: 16px;
border: 1px solid $border_color;
border-radius: 6px;
background: #fff;
background-repeat: no-repeat;
background-size: cover;
}
.gradeLeft {
.grade-card {
display: flex;
align-items: flex-start;
gap: 12px;
align-items: center;
gap: 16px;
margin-bottom: 8px;
padding: 16px;
background: #fafafa;
border-radius: 8px;
}
.gradeIcon {
width: 48px;
height: 48px;
.grade-icon {
width: 56px;
height: 56px;
object-fit: contain;
border-radius: 4px;
background: #fff;
flex-shrink: 0;
}
.gradeNameRow {
.grade-name-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
gap: 8px;
}
.grade-name {
color: #171A26;
font-size: 18px;
font-weight: 600;
line-height: 1.4;
}
.gradeNameText {
font-size: 16px;
color: $light_title_color;
font-weight: 600;
.benefit-tag {
padding: 0 8px;
border-radius: 4px;
background: #fff;
color: $theme_color;
font-size: 14px;
line-height: 22px;
}
.benefitTagsInline {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px 8px;
vertical-align: middle;
}
.benefitTag {
margin: 0 !important;
}
.gradeExp,
.gradeNext {
.grade-exp,
.grade-next {
margin-top: 6px;
font-size: 12px;
color: #000;
color: #666;
font-size: 14px;
line-height: 22px;
}
.num {
color: $theme_color;
font-size: 16px;
font-weight: 600;
}
.ruleItem {
padding: 10px 8px;
.grade-tabs {
display: flex;
gap: 24px;
margin: 16px 0;
border-bottom: 1px solid #f0f0f0;
}
.ruleTop {
.grade-tab {
position: relative;
padding: 10px 0;
color: #666;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&.active {
color: $theme_color;
font-weight: 600;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: $theme_color;
}
}
}
.rule-item {
padding: 14px 0;
border-top: 1px solid #f0f0f0;
&:first-child {
border-top: none;
}
}
.rule-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
gap: 12px;
}
.ruleName {
display: flex;
align-items: center;
gap: 8px;
color: $light_title_color;
.rule-name {
color: #171A26;
font-size: 16px;
line-height: 22px;
}
.rule-status {
flex-shrink: 0;
padding: 0 8px;
border-radius: 4px;
background: #f5f5f5;
color: #999;
font-size: 14px;
line-height: 22px;
&.done {
background: #f6ffed;
color: #19be6b;
}
}
.ruleDesc {
.rule-desc {
margin-top: 6px;
color: $light_sub_color;
font-size: 12px;
line-height: 1.6;
color: #666;
font-size: 14px;
line-height: 22px;
}
.ruleMeta {
margin-top: 8px;
.rule-meta {
display: flex;
flex-wrap: wrap;
gap: 16px;
color: $light_sub_color;
font-size: 12px;
margin-top: 8px;
color: #666;
font-size: 14px;
line-height: 22px;
b {
color: $theme_color;
font-weight: 600;
}
}
.metaItem b {
color: $theme_color;
.rules-desc {
margin-top: 16px;
padding: 12px 16px;
border-radius: 8px;
background: #fafafa;
color: #666;
font-size: 14px;
line-height: 22px;
white-space: pre-wrap;
word-break: break-all;
}
.log-list {
--log-cols: 180px minmax(0, 1fr) 100px;
}
.log-head,
.log-row {
display: grid;
grid-template-columns: var(--log-cols);
column-gap: 16px;
align-items: start;
}
.log-head {
padding: 0 8px 10px;
color: #999;
font-size: 13px;
span:nth-child(n + 2) {
text-align: center;
}
}
.log-row {
padding: 14px 8px;
border-top: 1px solid #f0f0f0;
font-size: 14px;
line-height: 22px;
}
.log-time,
.log-rule {
color: #171A26;
}
.log-rule,
.log-point {
text-align: center;
}
.log-point {
font-weight: 600;
&.is-plus {
color: #19be6b;
}
&.is-minus {
color: $theme_color;
}
}
.grade-pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
margin-top: 16px;
:deep(.el-pagination) {
justify-content: flex-end;
}
}
</style>

View File

@@ -1,11 +1,11 @@
<template>
<div class="wrapper">
<UserCenterLayout title="账户安全" :tabs="['账户安全']">
<UserCenterLayout>
<!-- 手机号验证 -->
<el-form ref="formData" :model="formData" label-position="left" label-width="100px" :rules="ruleInLines"
v-if="(status == 2 || status == 3) && !showPwd">
<el-form-item label="手机号">
<div class="phone">1234567890</div>
<div class="phone">{{ displayMobile }}</div>
</el-form-item>
<el-form-item label="图片验证码" prop="picture" style="width: 350px">
<el-input v-model="formData.picture" size="large" maxlength="4"></el-input>
@@ -80,6 +80,8 @@ import {
setPwd,
editPwd
} from '@/api/account';
import { getMemberMsg } from '@/api/login.js';
import storage from '@/plugins/storage.js';
import {md5} from '@/plugins/md5.js'
export default {
@@ -89,6 +91,7 @@ export default {
return {
// 1为修改 2为设置 3为提交校验的下一步
status: '',
mobile: '',
showPwd: false, // 显示密码
formData: { // 验证表单
picture: '',
@@ -136,10 +139,33 @@ export default {
}
}
},
computed: {
displayMobile () {
if (!this.mobile) return '未绑定手机号';
return this.$filters.secrecyMobile(this.mobile);
}
},
mounted () {
this.status = this.$route.query.status
this.loadMobile()
},
methods: {
loadMobile () {
const rawUserInfo = storage.getItem('userInfo');
if (rawUserInfo) {
try {
const userInfo = JSON.parse(rawUserInfo);
if (userInfo && userInfo.mobile) this.mobile = userInfo.mobile;
} catch (e) {}
}
if (!storage.getItem('accessToken')) return;
getMemberMsg().then(res => {
if (res.success && res.result) {
storage.setItem('userInfo', res.result);
this.mobile = res.result.mobile || '';
}
}).catch(() => {});
},
normalizePwdError (message) {
if (!message) return '修改失败,请稍后重试';
if (String(message).includes('旧密码') || String(message).includes('密码不正确')) {

View File

@@ -1,22 +1,32 @@
<template>
<div class="point">
<UserCenterLayout title="我的积分" :tabs="['我的积分']">
<div class="point-content">
<span>剩余积分:<span>{{ pointObj.point || 0 }}</span></span>
<span>累计获得:<span>{{ pointObj.totalPoint || 0 }}</span></span>
<UserCenterLayout>
<div class="point-summary">
<div class="point-stat">
<div class="label">剩余积分</div>
<div class="value">{{ pointObj.point || 0 }}</div>
</div>
<div class="point-stat">
<div class="label">累计获得</div>
<div class="value">{{ pointObj.totalPoint || 0 }}</div>
</div>
</div>
<h3>积分日志</h3>
<el-table :data="logData.records || []">
<el-table-column prop="content" label="日志内容" align="center" />
<el-table-column prop="createTime" label="时间" align="center" />
<el-table-column label="积分明细" align="center">
<template #default="{ row }">
<div :style="{ color: row.pointType === 'INCREASE' ? 'green' : 'red' }">
<span v-if="row.pointType === 'INCREASE'">+</span>{{ row.variablePoint }}
</div>
</template>
</el-table-column>
</el-table>
<empty v-if="!(logData.records || []).length" />
<div v-else class="log-list">
<div class="log-head">
<span>日志内容</span>
<span>时间</span>
<span>积分明细</span>
</div>
<div class="log-row" v-for="(row, index) in logData.records" :key="row.id || index">
<div class="log-content">{{ row.content }}</div>
<div class="log-time">{{ row.createTime }}</div>
<div class="log-point" :class="row.pointType === 'INCREASE' ? 'is-plus' : 'is-minus'">
<span v-if="row.pointType === 'INCREASE'">+</span>{{ row.variablePoint }}
</div>
</div>
</div>
<!-- 分页 -->
<div class="point-pagination">
<el-pagination
@@ -25,8 +35,8 @@
:total="logData.total"
@current-change="changePage"
@size-change="changePageSize"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next, jumper"
:page-sizes="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</UserCenterLayout>
@@ -44,7 +54,7 @@ export default {
loading: false, // 请求接口加载框
params: { // 积分列表请求参数
pageNumber: 1,
pageSize: 10
pageSize: 20
},
}
},
@@ -77,28 +87,101 @@ export default {
</script>
<style lang="scss" scoped>
h3 {
font-size: 16px;
margin: 20px 10px;
margin: 8px 0 12px;
font-size: 15px;
font-weight: 600;
color: #333;
line-height: 1.4;
}
.point-content {
.point-summary {
display: flex;
gap: 16px;
margin-bottom: 20px;
}
.point-stat {
flex: 1;
padding: 16px 20px;
background: #fafafa;
border-radius: 8px;
.label {
color: #666;
font-size: 14px;
line-height: 22px;
}
.value {
margin-top: 4px;
color: $theme_color;
font-size: 28px;
font-weight: 600;
line-height: 1.2;
}
}
.log-list {
--log-cols: minmax(0, 1.6fr) 180px 100px;
}
.log-head,
.log-row {
display: grid;
grid-template-columns: var(--log-cols);
column-gap: 16px;
align-items: start;
}
.log-head {
padding: 0 8px 10px;
color: #999;
font-size: 13px;
span:nth-child(n + 2) {
text-align: center;
}
}
.log-row {
padding: 14px 8px;
border-top: 1px solid #f0f0f0;
font-size: 14px;
line-height: 22px;
}
.log-content {
min-width: 0;
color: #171A26;
white-space: pre-wrap;
word-break: break-all;
}
.log-time {
text-align: center;
margin-bottom: 30px;
color: #171A26;
}
> span {
color: #999;
margin-right: 100px;
.log-point {
text-align: center;
font-weight: 600;
span {
color: $theme_color;
font-size: 24px;
}
&.is-plus {
color: #19be6b;
}
&.is-minus {
color: $theme_color;
}
}
.point-pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
margin-top: 16px;
:deep(.el-pagination) {
justify-content: flex-end;
}
}
</style>

View File

@@ -1,191 +1,248 @@
<template>
<div class="wrapper">
<UserCenterLayout title="用户信息" :tabs="['用户信息']">
<el-form :model="formItem" :rules="rules" ref="form" label-width="80px">
<el-form-item label="头像" class="avatar-form-item">
<div class="avatar-upload">
<el-avatar v-if="formItem.face" :src="formItem.face" :size="80" />
<el-avatar v-else :size="80"><el-icon><User /></el-icon></el-avatar>
<el-upload
:show-file-list="false"
:on-success="handleSuccess"
:format="['jpg','jpeg','png']"
:action="action"
:headers="accessToken"
>
<el-button>上传头像</el-button>
</el-upload>
</div>
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input class="profile-field" v-model="formItem.nickName" placeholder="" />
</el-form-item>
<el-form-item label="生日">
<el-date-picker
class="profile-field"
type="date"
placeholder="选择您的生日"
v-model="formItem.birthday"
/>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="formItem.sex" type="button" button-style="solid">
<el-radio :label="1">男</el-radio>
<el-radio :label="0">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">确认修改</el-button>
</el-form-item>
</el-form>
</UserCenterLayout>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { editMemberInfo } from '@/api/account.js';
import { getMemberMsg } from '@/api/login.js';
import { buyerUrl } from '@/plugins/request.js';
import storage from '@/plugins/storage.js';
import { User } from '@element-plus/icons-vue';
export default {
components: { User },
name: 'Profile',
data () {
return {
rules: { // 验证规则
nickName: [{required: true, message: '用户昵称不能为空'}, { max: 16, message: '用户昵称不能超过15个字符' }]
},
formItem: {}, // 表单数据
action: buyerUrl + '/buyer/common/upload/file', // 上传接口
accessToken: {} // 验证token
}
},
mounted () {
this.accessToken.accessToken = storage.getItem('accessToken');
this.loadUserInfo();
},
methods: {
loadUserInfo () {
const rawUserInfo = storage.getItem('userInfo');
if (rawUserInfo) {
this.applyUserInfo(JSON.parse(rawUserInfo));
return;
}
if (!storage.getItem('accessToken')) {
this.redirectToLogin();
return;
}
getMemberMsg().then(res => {
if (res.success && res.result) {
storage.setItem('userInfo', res.result);
this.applyUserInfo(res.result);
} else {
this.redirectToLogin();
}
}).catch(() => {
this.redirectToLogin();
});
},
applyUserInfo (userInfo) {
this.formItem = { ...userInfo };
this.formItem.birthday = this.normalizeBirthday(this.formItem.birthday);
},
<template>
<div class="wrapper">
<UserCenterLayout>
<el-form class="profile-form" :model="formItem" :rules="rules" ref="form" label-width="72px" label-position="left">
<el-form-item label="头像" class="avatar-form-item">
<div class="avatar-upload">
<el-avatar v-if="formItem.face" :src="formItem.face" :size="72" />
<el-avatar v-else :size="72"><el-icon><User /></el-icon></el-avatar>
<el-upload
:show-file-list="false"
:on-success="handleSuccess"
:format="['jpg','jpeg','png']"
:action="action"
:headers="accessToken"
>
<button type="button" class="ghost-btn">上传头像</button>
</el-upload>
</div>
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input class="profile-field" v-model="formItem.nickName" maxlength="16" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="生日">
<el-date-picker
class="profile-field"
type="date"
placeholder="选择您的生日"
v-model="formItem.birthday"
/>
</el-form-item>
<el-form-item label="性别">
<div class="sex-group">
<button type="button" class="sex-btn" :class="{ active: formItem.sex === 1 }" @click="formItem.sex = 1">男</button>
<button type="button" class="sex-btn" :class="{ active: formItem.sex === 0 }" @click="formItem.sex = 0">女</button>
</div>
</el-form-item>
<el-form-item>
<button type="button" class="op-btn" @click="save">确认修改</button>
</el-form-item>
</el-form>
</UserCenterLayout>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { editMemberInfo } from '@/api/account.js';
import { getMemberMsg } from '@/api/login.js';
import { buyerUrl } from '@/plugins/request.js';
import storage from '@/plugins/storage.js';
import { User } from '@element-plus/icons-vue';
export default {
components: { User },
name: 'Profile',
data () {
return {
rules: { // 验证规则
nickName: [{required: true, message: '用户昵称不能为空'}, { max: 16, message: '用户昵称不能超过15个字符' }]
},
formItem: {}, // 表单数据
action: buyerUrl + '/buyer/common/upload/file', // 上传接口
accessToken: {} // 验证token
}
},
mounted () {
this.accessToken.accessToken = storage.getItem('accessToken');
this.loadUserInfo();
},
methods: {
loadUserInfo () {
const rawUserInfo = storage.getItem('userInfo');
if (rawUserInfo) {
this.applyUserInfo(JSON.parse(rawUserInfo));
return;
}
if (!storage.getItem('accessToken')) {
this.redirectToLogin();
return;
}
getMemberMsg().then(res => {
if (res.success && res.result) {
storage.setItem('userInfo', res.result);
this.applyUserInfo(res.result);
} else {
this.redirectToLogin();
}
}).catch(() => {
this.redirectToLogin();
});
},
applyUserInfo (userInfo) {
this.formItem = { ...userInfo };
this.formItem.birthday = this.normalizeBirthday(this.formItem.birthday);
},
redirectToLogin () {
this.$router.push({
path: '/login',
query: {
rePath: this.$route.path,
query: JSON.stringify(this.$route.query || {}),
},
});
},
normalizeBirthday (value) {
if (!value) return ''
if (value instanceof Date) return value
if (typeof value === 'number') return new Date(value)
if (typeof value === 'string') {
const m = value.match(/^(\d{4})-(\d{2})-(\d{2})/)
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]))
const d = new Date(value)
return isNaN(d.getTime()) ? '' : d
}
return ''
},
formatBirthday (value) {
if (!value) return null
if (typeof value === 'string') {
const m = value.match(/^(\d{4})-(\d{2})-(\d{2})/)
return m ? `${m[1]}-${m[2]}-${m[3]}` : null
}
const d = value instanceof Date ? value : new Date(value)
if (isNaN(d.getTime())) return null
const yyyy = d.getFullYear()
const mm = String(d.getMonth() + 1).padStart(2, '0')
const dd = String(d.getDate()).padStart(2, '0')
return `${yyyy}-${mm}-${dd}`
},
save () { // 保存
this.$refs.form.validate(valid => {
if (valid) {
let params = {
birthday: this.formatBirthday(this.formItem.birthday),
face: this.formItem.face,
nickName: this.formItem.nickName,
sex: this.formItem.sex
}
editMemberInfo(params).then(res => {
if (res.success) {
Message.success('修改个人资料成功')
storage.setItem('userInfo', res.result)
this.$router.go(0)
}
})
}
})
},
handleSuccess (res, file) { // 上传成功
this.formItem['face'] = res.result
},
}
}
</script>
<style scoped lang="scss">
.profile-form {
max-width: 480px;
padding-top: 4px;
:deep(.el-form-item) {
margin-bottom: 22px;
align-items: center;
}
:deep(.el-form-item__label) {
height: auto;
line-height: 22px;
color: #666;
font-size: 14px;
}
:deep(.el-input__wrapper),
:deep(.el-date-editor.el-input__wrapper) {
min-height: 36px;
}
}
.avatar-form-item {
align-items: center;
}
.avatar-upload {
display: flex;
align-items: center;
gap: 16px;
:deep(.el-upload) {
display: flex;
align-items: center;
}
}
.profile-field {
width: 320px;
}
:deep(.profile-field.el-date-editor) {
width: 320px;
}
.sex-group {
display: flex;
gap: 8px;
}
.sex-btn,
.ghost-btn,
.op-btn {
height: 32px;
padding: 0 16px;
border-radius: 4px;
font-family: inherit;
font-size: 14px;
line-height: 30px;
cursor: pointer;
}
.sex-btn,
.ghost-btn {
border: 1px solid #ddd;
background: #fff;
color: #171A26;
&:hover {
border-color: $theme_color;
color: $theme_color;
}
}
.sex-btn.active {
border-color: $theme_color;
background: #fff5f4;
color: $theme_color;
}
.op-btn {
min-width: 96px;
border: 1px solid $theme_color;
background: $theme_color;
color: #fff;
&:hover {
opacity: 0.9;
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="add-eval">
<UserCenterLayout title="订单评价" :tabs="['订单评价']">
<UserCenterLayout>
<div class="title">
<p>
<span class="color999">订单号:</span><span>{{$route.query.sn}}</span>
@@ -68,7 +68,7 @@
<div class="submit-row">
<el-button type="primary" :loading="loading" @click="save">发表</el-button>
</div>
<el-dialog title="View Image" v-model="visible">
<el-dialog title="图片预览" v-model="visible">
<img :src="previewImage" v-if="visible" style="width: 100%">
</el-dialog>
</UserCenterLayout>
@@ -326,7 +326,7 @@ export default {
}
.describe {
font-size: 12px;
font-size: 14px;
color: #999;
margin-top: 8px;
}

View File

@@ -1,97 +1,81 @@
<template>
<div class="add-eval">
<UserCenterLayout title="评价详情" :tabs="['评价详情']">
<div class="title">
<p>
<span class="color999">创建人:</span><span>{{ $filters.secrecyMobile( orderGoods.createBy ) }}</span>
<span class="color999 ml_20">{{ orderGoods.createTime }}</span>
</p>
</div>
<!-- 物流评分、服务评分 -->
<div class="delivery-rate">
<div class="fontsize_16">物流服务评价:</div>
<div class="color999 delivery-rate-items">
<span>物流评价:<el-rate disabled :model-value="Number(orderGoods.deliveryScore) || 0" :colors="rateThemeColors" class="theme-rate" /></span>
<span>服务评价:<el-rate disabled :model-value="Number(orderGoods.serviceScore) || 0" :colors="rateThemeColors" class="theme-rate" /></span>
<span>描述评价:<el-rate disabled :model-value="Number(orderGoods.descriptionScore) || 0" :colors="rateThemeColors" class="theme-rate" /></span>
</div>
</div>
<!-- 添加订单评价 左侧商品详情 右侧评价框 -->
<ul class="goods-eval">
<li>
<div class="goods-con">
<UserCenterLayout>
<div class="order-card status-card" v-if="orderGoods.goodsName || orderGoods.content">
<div class="status-head">
<div class="status-main">
<p class="status-text">{{ gradeText }}</p>
<p class="status-sn">评价时间:{{ orderGoods.createTime }}</p>
</div>
</div>
<div class="goods-row" v-if="orderGoods.goodsName">
<img
:src="orderGoods.goodsImage"
class="hover-pointer"
alt=""
width="100"
@click="goGoodsDetail(orderGoods.skuId, orderGoods.goodsId)"
@error="$event.target.style.visibility = 'hidden'"
/>
<p
class="hover-pointer color999"
@click="goGoodsDetail(orderGoods.skuId, orderGoods.goodsId)"
>
<div class="goods-name" @click="goGoodsDetail(orderGoods.skuId, orderGoods.goodsId)">
{{ orderGoods.goodsName }}
</p>
</div>
</div>
</div>
<div class="eval-con">
<div>
<span class="color999">商品评价:</span>
<el-radio-group
style="margin-bottom: 5px; color: #999"
v-model="orderGoods.grade"
type="button"
button-style="solid"
>
<el-radio label="GOOD" disabled>好评</el-radio>
<el-radio label="MODERATE" disabled>中评</el-radio>
<el-radio label="WORSE" disabled>差评</el-radio>
</el-radio-group>
<el-input
type="textarea"
maxlength="500"
readonly
show-word-limit
:rows="4"
v-model="orderGoods.content"
/>
<div class="order-card">
<h3>物流服务评价</h3>
<div class="rate-list">
<div class="rate-row">
<span>物流评价</span>
<el-rate disabled :model-value="Number(orderGoods.deliveryScore) || 0" :colors="rateThemeColors" class="theme-rate" />
</div>
<div style="display: flex; align-items: center">
<template v-if="orderGoods.images">
<div class="demo-upload-list" v-for="(img, index) in orderGoods.images.split(',')" :key="index">
<img :src="img" />
<div class="demo-upload-list-cover"><el-icon @click="handleView(img)"><View /></el-icon></div>
</div>
</template>
<div class="rate-row">
<span>服务评价</span>
<el-rate disabled :model-value="Number(orderGoods.serviceScore) || 0" :colors="rateThemeColors" class="theme-rate" />
</div>
<div style="margin-top: 20px;" v-if="orderGoods.reply || orderGoods.replyImage">
<span class="color999">商家回复:</span>
<span>{{ orderGoods.reply }}</span>
<div style="display: flex; align-items: center">
<template v-if="orderGoods.replyImage">
<div class="demo-upload-list" v-for="(img, index) in orderGoods.replyImage.split(',')" :key="index">
<img :src="img" />
<div class="demo-upload-list-cover"><el-icon @click="handleView(img)"><View /></el-icon></div>
</div>
</template>
</div>
<div class="rate-row">
<span>描述评价</span>
<el-rate disabled :model-value="Number(orderGoods.descriptionScore) || 0" :colors="rateThemeColors" class="theme-rate" />
</div>
</div>
</li>
</ul>
<el-dialog title="View Image" v-model="visible">
<img :src="previewImage" v-if="visible" style="width: 100%" />
</el-dialog>
</div>
<div class="order-card">
<h3>评价内容</h3>
<p class="eval-text">{{ orderGoods.content || '暂无评价内容' }}</p>
<div class="image-list" v-if="orderGoods.images">
<img
v-for="(img, index) in orderGoods.images.split(',')"
:key="index"
:src="img"
alt=""
@click="handleView(img)"
/>
</div>
</div>
<div class="order-card" v-if="orderGoods.reply || orderGoods.replyImage">
<h3>商家回复</h3>
<p class="eval-text" v-if="orderGoods.reply">{{ orderGoods.reply }}</p>
<div class="image-list" v-if="orderGoods.replyImage">
<img
v-for="(img, index) in orderGoods.replyImage.split(',')"
:key="index"
:src="img"
alt=""
@click="handleView(img)"
/>
</div>
</div>
<el-dialog title="查看图片" v-model="visible" width="640px">
<img :src="previewImage" v-if="visible" class="preview-img" alt="" />
</el-dialog>
</UserCenterLayout>
</div>
</template>
<script>
import { View } from '@element-plus/icons-vue';
import { evaluationDetail } from "@/api/member.js";
export default {
components: { View },
data() {
return {
order: {}, // 订单详情
@@ -103,6 +87,12 @@ export default {
rateThemeColors: ["var(--theme-color)", "var(--theme-color)", "var(--theme-color)"],
};
},
computed: {
gradeText () {
const map = { GOOD: '好评', MODERATE: '中评', WORSE: '差评' }
return map[this.orderGoods.grade] || '评价详情'
}
},
methods: {
getDetail() {
// 获取评价详情
@@ -130,103 +120,140 @@ export default {
};
</script>
<style lang="scss" scoped>
.delivery-rate {
display: flex;
align-items: center;
margin-top: 20px;
min-height: 50px;
border-bottom: 1px solid #eee;
> div:nth-child(1) {
width: 120px;
font-weight: bold;
.add-eval {
color: #333;
}
.order-card {
padding: 4px 0 16px;
margin-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
h3 {
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
color: #333;
line-height: 1.4;
}
&:last-of-type {
border-bottom: none;
}
}
.delivery-rate-items {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px;
.status-card {
padding-top: 8px;
}
.delivery-rate-items :deep(.theme-rate.el-rate) {
.status-text {
margin: 0;
color: $theme_color;
font-size: 18px;
font-weight: 600;
line-height: 1.4;
}
.status-sn {
margin: 6px 0 0;
color: #171A26;
font-size: 14px;
line-height: 22px;
span {
margin-left: 16px;
}
}
.goods-row {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
padding: 12px;
background: #fafafa;
border-radius: 8px;
img {
width: 64px;
height: 64px;
object-fit: cover;
border: 1px solid #f0f0f0;
background: #f5f5f5;
flex-shrink: 0;
cursor: pointer;
}
}
.goods-name {
color: #333;
font-size: 16px;
line-height: 22px;
word-break: break-all;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.rate-list {
display: flex;
flex-wrap: wrap;
gap: 8px 32px;
}
.rate-row {
display: flex;
align-items: center;
min-width: 180px;
color: #171A26;
font-size: 14px;
line-height: 22px;
span {
width: 72px;
flex-shrink: 0;
}
}
.rate-row :deep(.theme-rate.el-rate) {
--el-rate-fill-color: #{$theme_color};
--el-rate-disabled-fill-color: #{$theme_color};
height: 22px;
}
:deep(.theme-rate .el-rate__icon.is-active) {
color: $theme_color !important;
}
.goods-eval li {
.eval-text {
margin: 0;
color: #171A26;
font-size: 14px;
line-height: 22px;
white-space: pre-wrap;
word-break: break-all;
}
.image-list {
display: flex;
border-bottom: 1px solid #eee;
flex-wrap: wrap;
gap: 12px;
margin-top: 12px;
.goods-con {
width: 30%;
padding: 20px;
text-align: center;
p {
word-wrap: wrap;
&:hover {
color: $theme_color;
}
}
}
.eval-con {
width: 70%;
padding: 20px;
}
}
.demo-upload-list {
display: inline-block;
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
border: 1px solid transparent;
border-radius: 4px;
overflow: hidden;
background: #fff;
position: relative;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
margin-right: 4px;
margin-top: 10px;
}
.demo-upload-list img {
width: 100%;
height: 100%;
}
.demo-upload-list-cover {
display: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
}
.demo-upload-list:hover .demo-upload-list-cover {
display: block;
}
.demo-upload-list-cover i {
color: #fff;
font-size: 20px;
cursor: pointer;
margin: 0 2px;
}
.icon-upload {
width: 58px;
height: 58px;
line-height: 58px;
text-align: center;
display: inline-block;
border: 1px dashed #999;
border-radius: 4px;
margin-top: 10px;
&:hover {
img {
width: 88px;
height: 88px;
object-fit: cover;
border: 1px solid #f0f0f0;
border-radius: 6px;
cursor: pointer;
border-color: $theme_color;
}
}
.preview-img {
display: block;
width: 100%;
}
</style>

View File

@@ -1,17 +1,138 @@
<template>
<div class="msg-list">
会员消息详情
<div class="msg-detail-page">
<UserCenterLayout title="消息详情">
<div v-if="detail.id" class="msg-detail">
<div class="msg-detail-head">
<h2>{{ detail.title || '站内消息' }}</h2>
<p>{{ detail.createTime }}</p>
</div>
<div class="msg-detail-content">{{ detail.content || '暂无内容' }}</div>
<div class="msg-detail-actions">
<el-button @click="goBack">返回</el-button>
<el-button v-if="detail.status !== 'ALREADY_REMOVE'" type="danger" @click="removeMessage">删除</el-button>
</div>
</div>
<div v-else-if="loaded">
<empty _Title="消息不存在或已失效" />
<div class="msg-detail-actions is-empty">
<el-button @click="goBack">返回消息列表</el-button>
</div>
</div>
</UserCenterLayout>
</div>
</template>
<script>
import { Message, Modal } from "@/utils/message";
import { delMemberMsg, getMemberMessage, readMemberMsg } from '@/api/member.js';
const CACHE_KEY = 'buyer-member-message';
export default {
data() {
name: 'MsgDetail',
data () {
return {
detail: {}, // 消息详情
detail: {},
loaded: false
}
},
mounted () {
this.loadDetail()
},
methods: {
loadDetail () {
const id = this.$route.query.id
if (!id) {
this.loaded = true
return
}
const cached = this.readCache(id)
if (cached) this.detail = cached
getMemberMessage(id).then(res => {
if (res && res.success && res.result && res.result.id) {
this.detail = res.result
sessionStorage.setItem(CACHE_KEY, JSON.stringify(res.result))
}
}).catch(() => {}).finally(() => {
this.loaded = true
this.markRead()
})
},
readCache (id) {
try {
const raw = sessionStorage.getItem(CACHE_KEY)
if (!raw) return null
const message = JSON.parse(raw)
return message && String(message.id) === String(id) ? message : null
} catch (e) {
return null
}
},
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'
}).catch(() => {})
},
goBack () {
this.$router.push({ name: 'MsgList' })
},
removeMessage () {
Modal.confirm({
title: '确认删除',
content: '确认要删除此消息?',
loading: true,
onOk: () =>
delMemberMsg(this.detail.id).then((res) => {
if (res.success) {
Message.success('消息已成功放入回收站')
this.goBack()
}
})
})
}
}
}
</script>
<style lang="scss" scoped>
</style>
.msg-detail-head {
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
h2 {
margin: 0;
color: #171A26;
font-size: 18px;
font-weight: 600;
line-height: 1.5;
}
p {
margin: 8px 0 0;
color: #999;
font-size: 14px;
line-height: 22px;
}
}
.msg-detail-content {
padding: 20px 0 8px;
color: #333;
font-size: 14px;
line-height: 1.8;
white-space: pre-wrap;
word-break: break-all;
}
.msg-detail-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
&.is-empty {
justify-content: center;
}
}
</style>

View File

@@ -1,43 +1,47 @@
<template>
<div class="msg-list">
<UserCenterLayout
title="我的消息"
:tabs="status"
:active-tab="activeStatusIndex"
@tab-change="statusChange"
>
<el-table v-if="params.status != 'ALREADY_REMOVE'" :data="messageData.records || []">
<el-table-column prop="title" label="消息标题" align="left" show-overflow-tooltip />
<el-table-column prop="content" label="消息内容" align="left" show-overflow-tooltip />
<el-table-column prop="createTime" label="发送时间" align="left" width="240" />
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="{ row }">
<el-button
v-if="row.status === 'UN_READY'"
type="primary"
size="small"
style="margin-right: 5px"
@click="setRead(row.id)"
>已读</el-button>
<el-button type="danger" size="small" @click="removeMessage(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-table v-else :data="messageData.records || []">
<el-table-column prop="title" label="消息标题" align="left" show-overflow-tooltip />
<el-table-column prop="content" label="消息内容" align="left" show-overflow-tooltip />
<el-table-column prop="createTime" label="发送时间" align="left" width="240" />
</el-table>
<div class="msg-pagination">
<empty v-if="!(messageData.records || []).length" />
<div v-else class="msg-table" :class="{ 'is-recycle': params.status === 'ALREADY_REMOVE' }">
<div class="msg-head">
<span>消息标题</span>
<span>消息内容</span>
<span>发送时间</span>
<span v-if="params.status !== 'ALREADY_REMOVE'">操作</span>
</div>
<div
class="msg-row"
v-for="row in messageData.records"
:key="row.id"
:class="{ unread: row.status === 'UN_READY' }"
>
<div class="msg-title" @click="openDetail(row)">
<i v-if="row.status === 'UN_READY'" class="unread-dot"></i>
<span>{{ row.title }}</span>
</div>
<div class="msg-content" @click="openDetail(row)">{{ row.content }}</div>
<div class="msg-time">{{ row.createTime }}</div>
<div class="msg-ops" v-if="params.status !== 'ALREADY_REMOVE'">
<button type="button" class="text-link" @click="openDetail(row)">查看</button>
<button v-if="row.status === 'UN_READY'" type="button" class="text-link" @click="setRead(row.id)">已读</button>
<button type="button" class="text-link" @click="removeMessage(row.id)">删除</button>
</div>
</div>
</div>
<div class="msg-pagination" v-if="messageData.total">
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="messageData.total"
@current-change="changePage"
@size-change="changePageSize"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next, jumper"
:page-sizes="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</UserCenterLayout>
@@ -53,7 +57,7 @@ export default {
status: ['未读', '已读', '回收站'],
params: {
pageNumber: 1,
pageSize: 10,
pageSize: 20,
status: 'UN_READY'
},
}
@@ -70,6 +74,7 @@ export default {
if (index === 0) { this.params.status = 'UN_READY' }
if (index === 1) { this.params.status = 'ALREADY_READY' }
if (index === 2) { this.params.status = 'ALREADY_REMOVE' }
this.params.pageNumber = 1
this.getList()
},
changePage (v) {
@@ -77,9 +82,14 @@ export default {
this.getList();
},
changePageSize (v) {
this.params.pageNumber = 1
this.params.pageSize = v;
this.getList();
},
openDetail (row) {
sessionStorage.setItem('buyer-member-message', JSON.stringify(row))
this.$router.push({ name: 'MsgDetail', query: { id: row.id } })
},
getList () {
memberMsgList(this.params).then(res => {
if (res.success) {
@@ -115,9 +125,112 @@ export default {
}
</script>
<style lang="scss" scoped>
.msg-table {
--msg-cols: minmax(140px, 1.1fr) minmax(0, 2.2fr) 180px 168px;
padding-top: 8px;
}
.msg-table.is-recycle {
--msg-cols: minmax(140px, 1.1fr) minmax(0, 2.2fr) 180px;
}
.msg-head,
.msg-row {
display: grid;
grid-template-columns: var(--msg-cols);
column-gap: 16px;
align-items: start;
}
.msg-head {
padding: 0 8px 10px;
color: #999;
font-size: 13px;
span:last-child {
text-align: center;
}
}
.msg-row {
padding: 14px 8px;
border-top: 1px solid #f0f0f0;
font-size: 14px;
line-height: 22px;
}
.msg-title {
display: flex;
align-items: center;
min-width: 0;
color: #171A26;
cursor: pointer;
&:hover span {
color: $theme_color;
}
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.unread .msg-title {
font-weight: 600;
}
.unread-dot {
width: 6px;
height: 6px;
margin-right: 8px;
border-radius: 50%;
background: $theme_color;
flex-shrink: 0;
}
.msg-content {
min-width: 0;
color: #666;
cursor: pointer;
white-space: pre-wrap;
word-break: break-all;
line-height: 22px;
}
.msg-time {
color: #171A26;
}
.msg-ops {
display: flex;
justify-content: center;
gap: 16px;
}
.text-link {
border: none;
background: none;
padding: 0;
color: #171A26;
font-family: inherit;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.msg-pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
margin-top: 16px;
:deep(.el-pagination) {
justify-content: flex-end;
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<UserCenterLayout title="收货地址" :tabs="['收货地址']">
<UserCenterLayout>
<div class="add-box">
<el-form :model="formData" ref="form" label-position="left" label-width="100px" :rules="ruleInline">
<el-form-item label="收件人" prop="name">
@@ -18,7 +18,7 @@
<el-input v-model="formData.mobile" placeholder="请输入收件人手机号" style="width: 600px"></el-input>
</el-form-item>
<el-form-item label="地址别名">
<el-input v-model="formData.alias" length :maxlength="4" placeholder="请输入地址别名,例如公司" style="width: 600px">
<el-input v-model="formData.alias" :maxlength="5" show-word-limit placeholder="请输入地址别名,例如公司" style="width: 600px">
</el-input>
</el-form-item>
<el-form-item label="默认地址">

View File

@@ -1,78 +1,85 @@
<template>
<div class="wrapper">
<UserCenterLayout title="我的售后" :tabs="['我的售后']">
<UserCenterLayout
:tabs="changeWay.map((item) => item.name)"
:active-tab="activeTab"
@tab-change="change"
>
<template #extra>
<el-input
class="width_300"
v-model="params.keywords"
@keyup.enter="getList"
placeholder="请输入订单号搜索"
/>
<div class="order-search">
<el-input
v-model="params.keywords"
size="large"
@keyup.enter="getList"
placeholder="输入订单号搜索"
/>
<el-button type="primary" @click="getList">搜索</el-button>
</div>
</template>
<!-- 列表 -->
<empty v-if="orderList.length === 0"/>
<div class="order-content" v-else>
<template v-for="(order, onderIndex) in orderList" :key="order.sn || onderIndex">
<div class="order-list">
<div class="order-header">
<div>
<div v-if="order.serviceStatus">{{ filterOrderStatus(order.serviceStatus) }}</div>
<div>
售后单号:{{ order.sn }} &nbsp; &nbsp; &nbsp;{{ order.createTime }}
&nbsp; &nbsp;{{ $filters.secrecyMobile( order.memberName ) }}
<div class="order-cols">
<span>订单详情</span>
<span>金额</span>
<span>操作</span>
</div>
<div
class="order-list"
v-for="(order, orderIndex) in orderList"
:key="order.sn || orderIndex"
>
<div class="order-header">
<div class="order-sn">
{{ order.createTime }}<span>售后单号 {{ order.sn }}</span><span class="order-store" @click="shopPage(order.storeId)">{{ order.storeName }}</span>
</div>
<div class="order-status">{{ filterOrderStatus(order.serviceStatus) }}</div>
</div>
<div class="order-body">
<div class="col-detail">
<div class="goods-item">
<div class="goods-thumb">
<img
@click="goodsDetail(order.skuId, order.goodsId)"
class="hover-color"
:src="order.goodsImage"
alt=""
@error="$event.target.style.visibility = 'hidden'"
/>
</div>
<div class="goods-info">
<div class="goods-name hover-color" @click="goodsDetail(order.skuId, order.goodsId)">{{ order.goodsName }}</div>
<div class="goods-num">共{{ order.num }}件</div>
</div>
</div>
<div>
<span>申请退款金额:<span class="global_color">{{ $filters.unitPrice(order.applyRefundPrice, "¥") }}</span></span>
</div>
</div>
<div class="order-body">
<div class="goods-list">
<img @click="goodsDetail(order.skuId, order.goodsId)" class="hover-color" :src="order.goodsImage" alt=""/>
<div>
<div class="hover-color" @click="goodsDetail(order.skuId, order.goodsId)">{{ order.goodsName }}</div>
<div class="mt_10"> x {{ order.num }}
</div>
</div>
</div>
<div>
<span @click="shopPage(order.storeId)">{{ order.storeName }}</span>
</div>
<div class="order-actions">
<!-- 订单基础操作 -->
<el-button
@click="goDetail(order.sn)"
type="warning"
size="small"
class="after-sale-action-btn"
>售后详情</el-button>
<el-button
@click="openModal(order)"
v-if="order.serviceStatus == 'PASS' && order.serviceType != 'RETURN_MONEY'"
size="small"
class="after-sale-action-btn submit-logistics-btn"
>提交物流</el-button>
<el-button
@click="cancel(order.sn)"
type="danger"
v-if="order.afterSaleAllowOperationVO.cancel"
size="small"
class="after-sale-action-btn"
>取消售后</el-button>
</div>
<div class="col-amount">
<div class="order-amount">{{ $filters.unitPrice(order.applyRefundPrice, "¥") }}</div>
</div>
<div class="col-ops">
<button
v-if="order.serviceStatus == 'PASS' && order.serviceType != 'RETURN_MONEY'"
type="button"
class="op-btn"
@click="openModal(order)"
>提交物流</button>
<button type="button" class="text-link" @click="goDetail(order.sn)">售后详情</button>
<button
v-if="order.afterSaleAllowOperationVO && order.afterSaleAllowOperationVO.cancel"
type="button"
class="text-link"
@click="cancel(order.sn)"
>取消售后</button>
</div>
</div>
</template>
</div>
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
</div>
<!-- 分页 -->
<div class="page-size">
<el-pagination v-model:current-page="params.pageNumber" v-model:page-size="params.pageSize"
:total="total" @current-change="changePageNum"
@size-change="changePageSize" layout="sizes, prev, pager, next, jumper"></el-pagination>
:total="total" :page-sizes="[20, 50, 100]" @current-change="changePageNum"
@size-change="changePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</UserCenterLayout>
<el-dialog v-model="logisticsShow" width="530">
@@ -135,7 +142,8 @@ export default {
orderList: [], // 订单列表
params: { // 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20,
keywords: '',
sn: '',
sort: 'createTime',
order: 'desc'
@@ -158,7 +166,14 @@ export default {
logisticsNo: [{required: true, message: '请填写物流编号'}],
mDeliverTime: [{required: true, message: '请选择发货时间'}]
},
submitLoading: false // 提交加载状态
submitLoading: false, // 提交加载状态
changeWay: [
{ name: '全部', status: '' },
{ name: '申请中', status: 'APPLY' },
{ name: '通过', status: 'PASS' },
{ name: '完成售后', status: 'COMPLETE' },
],
activeTab: 0,
};
},
mounted() {
@@ -203,6 +218,17 @@ export default {
});
window.open(routeUrl.href, '_blank');
},
change(index) {
this.activeTab = index
this.params.pageNumber = 1
const current = this.changeWay[index]
if (!current || !current.status) {
delete this.params.serviceStatus
} else {
this.params.serviceStatus = current.status
}
this.getList()
},
getList() { // 获取售后列表
this.spinShow = true;
let params = JSON.parse(JSON.stringify(this.params))
@@ -227,7 +253,7 @@ export default {
const ob = this.afterSaleStatusList.filter(e => {
return e.status === status
});
return ob[0].name
return ob && ob[0] ? ob[0].name : status
},
// 获取物流公司列表
getCompany() {
@@ -275,109 +301,220 @@ export default {
margin-bottom: 40px;
}
.box {
overflow: hidden;
.order-search {
display: flex;
align-items: center;
width: 330px;
:deep(.el-input) {
flex: 1;
}
:deep(.el-button) {
margin-left: 8px;
}
}
.order-content {
--order-cols: minmax(0, 1fr) 120px 120px;
--order-gap: 12px;
padding-top: 14px;
}
.page-size {
margin: 15px 0px;
margin: 15px 0;
display: flex;
justify-content: flex-end;
align-items: center;
:deep(.el-pagination) {
justify-content: flex-end;
}
}
.order-cols {
display: grid;
grid-template-columns: var(--order-cols);
column-gap: var(--order-gap);
align-items: center;
padding: 0 16px 10px;
color: #999;
font-size: 13px;
span:first-child {
padding-left: 72px;
}
span:nth-child(n + 2) {
text-align: center;
}
}
/** 订单列表 */
.order-list {
border: 1px solid #ddd;
border-radius: 3px;
margin-bottom: 10px;
&:hover {
.del-btn {
visibility: visible;
}
}
.del-btn {
visibility: hidden;
}
border: 1px solid #eee;
border-radius: 8px;
margin-bottom: 12px;
background: #fff;
overflow: hidden;
.order-header {
display: grid;
grid-template-columns: var(--order-cols);
column-gap: var(--order-gap);
align-items: center;
padding: 10px 16px;
background: #f7f7f7;
border-bottom: 1px solid #f0f0f0;
}
.order-sn {
grid-column: 1 / 3;
display: flex;
align-items: center;
padding: 10px;
justify-content: space-between;
border-bottom: 1px solid #ddd;
min-width: 0;
font-size: 14px;
color: #171A26;
line-height: 1.5;
> div:nth-child(1) > div:nth-child(2) {
font-size: 12px;
color: #999;
margin-top: 3px;
span {
margin-left: 16px;
}
}
.order-store {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #438cde;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.order-status {
grid-column: 3;
min-width: 0;
text-align: center;
font-size: 14px;
color: #666;
line-height: 1.4;
}
.order-body {
display: flex;
justify-content: space-between;
color: #999;
padding: 10px;
display: grid;
grid-template-columns: var(--order-cols);
column-gap: var(--order-gap);
align-items: center;
padding: 14px 16px;
}
> div:nth-child(2) {
width: 150px;
text-align: center;
.goods-item {
display: grid;
grid-template-columns: 60px minmax(0, 1fr);
column-gap: 12px;
align-items: start;
width: 100%;
min-width: 0;
}
span {
color: #438cde;
cursor: pointer;
.goods-thumb {
width: 60px;
height: 60px;
border: 1px solid #f0f0f0;
overflow: hidden;
background: #f5f5f5;
&:hover {
color: $theme_color;
}
}
.el-icon {
color: #ff8f23;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
.order-actions {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
min-width: 100px;
.goods-info {
min-width: 0;
overflow: hidden;
}
.after-sale-action-btn {
display: block;
margin: 0;
min-width: 88px;
}
.goods-name {
max-width: 100%;
color: #333;
font-size: 16px;
line-height: 22px;
max-height: 44px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
}
:deep(.submit-logistics-btn) {
background-color: #ff9900 !important;
border-color: #ff9900 !important;
color: #fff !important;
.goods-num {
margin-top: 6px;
color: #999;
font-size: 14px;
line-height: 1.4;
}
&:hover,
&:focus {
background-color: #e68a00 !important;
border-color: #e68a00 !important;
color: #fff !important;
}
}
.col-amount,
.col-ops {
text-align: center;
}
.order-amount {
color: #333;
font-size: 16px;
font-weight: 600;
line-height: 1.4;
}
.col-ops {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.op-btn {
min-width: 96px;
height: 32px;
padding: 0 12px;
border: 1px solid $theme_color;
border-radius: 4px;
background: $theme_color;
color: #fff;
font-family: inherit;
font-size: 14px;
line-height: 30px;
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
.text-link {
border: none;
background: none;
padding: 0;
color: #171A26;
font-family: inherit;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
}
.goods-list {
// width: 500px;
.modal-goods {
display: flex;
padding: 5px;
margin-bottom: 10px;
img {
@@ -390,8 +527,4 @@ export default {
flex: 1;
}
}
.modal-goods {
padding: 5px;
}
</style>

View File

@@ -1,84 +1,102 @@
<template>
<div class="order-detail">
<UserCenterLayout title="售后详情" :tabs="['售后详情']">
<!-- 操作按钮 -->
<el-card class="mb_10" shadow="never" v-if="(afterSale.serviceStatus == 'PASS' &&
afterSale.serviceType != 'RETURN_MONEY') || (afterSale.afterSaleAllowOperationVO && afterSale.afterSaleAllowOperationVO.cancel)">
<el-button
@click="openModal"
v-if="afterSale.serviceStatus == 'PASS' && afterSale.serviceType != 'RETURN_MONEY'"
size="small"
class="submit-logistics-btn"
>提交物流</el-button>&nbsp;
<el-button type="danger" @click="cancel(afterSale.sn)" v-if="afterSale.afterSaleAllowOperationVO && afterSale.afterSaleAllowOperationVO.cancel" size="small">取消售后</el-button>
</el-card>
<div class="order-card">
<h3>{{afterSale.serviceName}}</h3>
<p class="global_color fontsize_18">{{ afterSale.orderStatusValue }}</p>
<p class="order-meta">
<span>售后单号:{{ afterSale.sn }} &nbsp;&nbsp;&nbsp;订单号:{{ afterSale.orderSn }}</span>
<span class="order-meta-time">创建时间:{{ afterSale.createTime }}</span>
</p>
<div class="service-after">
<div>
本次售后服务由<span>{{afterSale.storeName}}</span>为您提供
<UserCenterLayout>
<div class="order-card status-card" v-if="afterSale.sn">
<div class="status-head">
<div class="status-main">
<p class="status-text">{{ afterSale.serviceName }}</p>
<p class="status-sn">
售后单号:{{ afterSale.sn }}
<span>订单号:{{ afterSale.orderSn }}</span>
</p>
</div>
<div class="service-after-goods">
<img :src="afterSale.goodsImage" alt="" width="60" height="60">
<span>{{afterSale.goodsName}}</span>
<div class="status-side">
<div class="operation-time">创建时间:{{ afterSale.createTime }}</div>
<div class="status-actions" v-if="canSubmitLogistics || canCancel">
<button v-if="canSubmitLogistics" type="button" class="op-btn" @click="openModal">提交物流</button>
<button v-if="canCancel" type="button" class="text-link" @click="cancel(afterSale.sn)">取消售后</button>
</div>
</div>
</div>
<div class="goods-row">
<img :src="afterSale.goodsImage" alt="" @error="$event.target.style.visibility = 'hidden'">
<div class="goods-info">
<div class="goods-name">{{ afterSale.goodsName }}</div>
<div class="store-line">
本次售后服务由<span @click="shopPage(afterSale.storeId)">{{ afterSale.storeName }}</span>为您提供
</div>
</div>
</div>
</div>
<div class="order-card">
<div class="order-card" v-if="logList.length">
<h3>售后进程</h3>
<el-steps class="progress" :current="logList.length" direction="vertical">
<el-step :content="'操作人:'+ log.operatorName + ' ' + log.createTime"
<el-steps
class="progress"
:active="logList.length - 1"
direction="vertical"
>
<el-step
:title="log.message"
:description="logDesc(log)"
v-for="(log, index) in logList"
:key="index"
></el-step>
</el-steps>
</div>
<div class="order-card">
<h3 class="mb_10">服务单信息</h3>
<table border="1" cellpadding='0' cellspacing="0">
<tbody>
<tr>
<td>退款方式</td><td>{{afterSale.refundWay == 'ORIGINAL' ? '原路退回' : '账号退款'}}</td>
</tr>
<tr>
<td>申请退款金额</td><td>{{ $filters.unitPrice(afterSale.applyRefundPrice, '¥') }}</td>
</tr>
<tr v-if="afterSale.actualRefundPrice">
<td>实际退款金额</td><td>{{ $filters.unitPrice(afterSale.actualRefundPrice, '¥') }}</td>
</tr>
<h3>服务单信息</h3>
<div class="info-list">
<div class="info-row">
<span class="label">退款方式</span>
<span>{{ afterSale.refundWay == 'ORIGINAL' ? '原路退回' : '账号退款' }}</span>
</div>
<div class="info-row">
<span class="label">申请退款金额</span>
<span>{{ $filters.unitPrice(afterSale.applyRefundPrice, '¥') }}</span>
</div>
<div class="info-row" v-if="afterSale.actualRefundPrice">
<span class="label">实际退款金额</span>
<span>{{ $filters.unitPrice(afterSale.actualRefundPrice, '¥') }}</span>
</div>
<template v-if="afterSale.refundWay === 'OFFLINE'">
<tr>
<td>退款开户行</td><td>{{afterSale.bankDepositName}}</td>
</tr>
<tr>
<td>退款开户名</td><td>{{afterSale.bankAccountName}}</td>
</tr>
<tr>
<td>退款卡号</td><td>{{afterSale.bankAccountNumber}}</td>
</tr>
<div class="info-row">
<span class="label">退款开户行</span>
<span>{{ afterSale.bankDepositName }}</span>
</div>
<div class="info-row">
<span class="label">退款开户名</span>
<span>{{ afterSale.bankAccountName }}</span>
</div>
<div class="info-row">
<span class="label">退款卡号</span>
<span>{{ afterSale.bankAccountNumber }}</span>
</div>
</template>
<tr>
<td>商品处理方式</td><td>{{afterSale.serviceType == 'RETURN_MONEY' ? '退款' : '退货'}}</td>
</tr>
<tr>
<td>退款原因</td><td>{{afterSale.reasonName}}</td>
</tr>
<tr>
<td>问题描述</td><td>{{afterSale.problemDesc}}</td>
</tr>
</tbody>
</table>
<div class="info-row">
<span class="label">商品处理方式</span>
<span>{{ afterSale.serviceType == 'RETURN_MONEY' ? '退款' : '退货' }}</span>
</div>
<div class="info-row">
<span class="label">退款原因</span>
<span>{{ afterSale.reasonName }}</span>
</div>
<div class="info-row">
<span class="label">问题描述</span>
<span>{{ afterSale.problemDesc }}</span>
</div>
</div>
</div>
<div class="order-card" v-if="afterSale.afterSaleImage">
<h3 class="mb_10">图片信息</h3>
<div v-for="img in afterSale.afterSaleImage.split(',')" :key="img">
<img :src="img" width="200" height="200" @click="perviewImg(img)" class="hover-pointer" alt="">
<h3>图片信息</h3>
<div class="image-list">
<img
v-for="img in afterSale.afterSaleImage.split(',')"
:key="img"
:src="img"
@click="perviewImg(img)"
class="hover-pointer"
alt=""
>
</div>
</div>
<el-dialog v-model="logisticsShow" width="530">
@@ -144,6 +162,14 @@ export default {
submitLoading: false // 提交加载状态
};
},
computed: {
canSubmitLogistics () {
return this.afterSale.serviceStatus == 'PASS' && this.afterSale.serviceType != 'RETURN_MONEY'
},
canCancel () {
return this.afterSale.afterSaleAllowOperationVO && this.afterSale.afterSaleAllowOperationVO.cancel
}
},
methods: {
getDetail () { // 售后详情
afterSaleDetail(this.$route.query.sn).then(res => {
@@ -174,9 +200,23 @@ export default {
},
getLog () { // 获取售后日志
afterSaleLog(this.$route.query.sn).then(res => {
this.logList = res.result;
this.logList = (res.result || []).slice().sort((a, b) => {
return new Date(a.createTime).getTime() - new Date(b.createTime).getTime()
})
})
},
logDesc (log) {
const operator = log.operatorName ? `操作人:${log.operatorName}` : ''
return [operator, log.createTime].filter(Boolean).join(' ')
},
shopPage (id) {
if (!id) return
const routeUrl = this.$router.resolve({
path: '/merchant',
query: { id }
})
window.open(routeUrl.href, '_blank')
},
filterOrderStatus (status) { // 获取订单状态中文
const ob = this.afterSaleStatusList.filter(e => { return e.status === status });
if (ob.length) return ob[0].name
@@ -240,97 +280,235 @@ export default {
};
</script>
<style lang="scss" scoped>
.order-detail {
color: #333;
}
.order-card {
padding: 10px;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #e8eaec;
position: relative;
.global_color {
padding: 4px 0 16px;
margin-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
h3 {
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
color: #333;
line-height: 1.4;
}
&:last-child {
border-bottom: none;
}
}
.status-card {
padding-top: 8px;
}
.status-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.status-text {
margin: 0;
color: $theme_color;
font-size: 18px;
font-weight: 600;
line-height: 1.4;
}
.status-sn {
margin: 6px 0 0;
color: #171A26;
font-size: 14px;
line-height: 22px;
span {
margin-left: 16px;
}
}
.status-side {
flex-shrink: 0;
text-align: right;
}
.operation-time {
color: #171A26;
font-size: 14px;
line-height: 22px;
}
.status-actions {
display: flex;
justify-content: flex-end;
align-items: center;
flex-wrap: wrap;
gap: 12px;
margin-top: 10px;
}
.goods-row {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
padding: 12px;
background: #fafafa;
border-radius: 8px;
img {
width: 64px;
height: 64px;
object-fit: cover;
border: 1px solid #f0f0f0;
background: #f5f5f5;
flex-shrink: 0;
}
}
.goods-info {
min-width: 0;
}
.goods-name {
color: #333;
font-size: 16px;
line-height: 22px;
word-break: break-all;
}
.store-line {
margin-top: 6px;
color: #666;
font-size: 14px;
line-height: 22px;
span {
margin: 0 4px;
color: #438cde;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
}
.progress {
max-width: 640px;
margin: 4px 0 0;
:deep(.el-step.is-vertical) {
padding-bottom: 20px;
}
:deep(.el-step.is-vertical:last-child) {
padding-bottom: 0;
}
:deep(.el-step__head.is-finish) {
color: #c0c4cc;
border-color: #c0c4cc;
}
:deep(.el-step__title.is-finish),
:deep(.el-step__description.is-finish) {
color: #909399;
font-weight: 400;
}
:deep(.el-step__head.is-process) {
color: $theme_color;
border-color: $theme_color;
}
:deep(.el-step__title.is-process),
:deep(.el-step__description.is-process) {
color: $theme_color;
}
p {
color: #999;
margin: 3px;
}
.info-list {
border: 1px solid #f0f0f0;
border-radius: 8px;
overflow: hidden;
}
.info-row {
display: grid;
grid-template-columns: 140px minmax(0, 1fr);
min-height: 44px;
border-top: 1px solid #f0f0f0;
font-size: 14px;
line-height: 22px;
&:first-child {
border-top: none;
}
.order-meta {
span {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 10px 16px;
color: #171A26;
}
.order-meta-time {
flex-shrink: 0;
white-space: nowrap;
}
}
h3 {
font-weight: normal;
font-size: 16px;
}
&:last-child{
border: none;
.label {
background: #fafafa;
color: #666;
}
}
.service-after {
border: 1px solid #eee;
.image-list {
display: flex;
height: 80px;
padding: 10px;
>div:nth-child(1) {
width: 400px;
font-size: 15px;
font-weight: bold;
text-align: center;
line-height: 60px;
span{color: $theme_color;}
border-right: 1px solid #eee;
}
>div:nth-child(2){
padding-left: 15px;
}
.service-after-goods {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
gap: 12px;
img {
flex-shrink: 0;
}
span {
color: #999;
}
}
}
/** 售后进度条 */
.progress {
margin: 15px 0;
}
table{
border-color: #eee;
width: 100%;
color: #999;
tr{
td:nth-child(1){
width: 200px;
}
}
td{
padding: 5px;
img {
width: 120px;
height: 120px;
object-fit: cover;
border: 1px solid #f0f0f0;
border-radius: 6px;
cursor: pointer;
}
}
:deep(.submit-logistics-btn) {
background-color: #ff9900 !important;
border-color: #ff9900 !important;
color: #fff !important;
.op-btn {
min-width: 96px;
height: 32px;
padding: 0 12px;
border: 1px solid $theme_color;
border-radius: 4px;
background: $theme_color;
color: #fff;
font-family: inherit;
font-size: 14px;
line-height: 30px;
cursor: pointer;
&:hover,
&:focus {
background-color: #e68a00 !important;
border-color: #e68a00 !important;
color: #fff !important;
&:hover {
opacity: 0.9;
}
}
.text-link {
border: none;
background: none;
padding: 0;
color: #171A26;
font-family: inherit;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="apply-after-sale">
<UserCenterLayout title="申请售后" :tabs="['申请售后']">
<UserCenterLayout>
<el-table :data="goodsData" border>
<el-table-column label="商品名称">
<template #default="{ row }">
@@ -94,7 +94,7 @@
<el-button type="primary" @click="apply">提交申请</el-button>
</el-form-item>
</el-form>
<el-dialog title="View Image" v-model="visible">
<el-dialog title="图片预览" v-model="visible">
<img :src="previewImage" v-if="visible" style="width: 100%">
</el-dialog>
</div>

View File

@@ -1,7 +1,7 @@
<template>
<div class="add-eval">
<UserCenterLayout title="商品投诉" :tabs="['商品投诉']">
<UserCenterLayout>
<div class="title">
<p>
<span class="color999">订单号:</span><span>{{$route.query.sn}}</span>
@@ -55,7 +55,7 @@
</div>
</div>
</div>
<el-dialog title="View Image" v-model="visible">
<el-dialog title="图片预览" v-model="visible">
<img :src="previewImage" v-if="visible" style="width: 100%">
</el-dialog>
</UserCenterLayout>

View File

@@ -1,6 +1,6 @@
<template>
<div class="wrapper">
<UserCenterLayout title="收货地址" :tabs="['收货地址']" more-text="添加新地址" more-to="/home/addAddress">
<UserCenterLayout>
<template #extra>
<el-button type="primary" @click="add">添加新地址</el-button>
</template>

View File

@@ -4,7 +4,6 @@
<UserCenterLayout
v-if="!homePage"
title="我的订单"
:tabs="changeWay"
:active-tab="activeOrderTab"
@tab-change="change"
@@ -13,6 +12,7 @@
<div class="order-search">
<el-input
v-model="params.keywords"
size="large"
@keyup.enter="getList"
placeholder="输入订单号搜索"
/>
@@ -23,68 +23,67 @@
<!-- 订单列表 -->
<empty v-if="orderList.length === 0" />
<div class="order-content" v-else>
<div class="order-cols">
<span>订单详情</span>
<span>金额</span>
<span>操作</span>
</div>
<div
class="order-list"
v-for="(order, onderIndex) in orderList"
:key="onderIndex"
>
<div class="order-header">
<div class="order-header-main">
<div class="order-status">{{ order.isAfterSale ? filterAfterSaleStatus(order.serviceStatus) : filterOrderStatus(order.orderStatus) }}</div>
<div class="order-sn" v-if="order.isAfterSale">
售后单号:{{ order.serviceSn }}<span>{{ order.createTime }}</span>
</div>
<div class="order-sn" v-else>
订单号:{{ order.sn }}<span>{{ order.createTime }}</span>
</div>
<div class="order-sn" v-if="order.isAfterSale">
{{ order.createTime }}<span>售后单号 {{ order.serviceSn }}</span><span class="order-store" @click="shopPage(order.storeId)">{{ order.storeName }}</span>
</div>
<div class="order-header-side">
<el-button v-if="!order.isAfterSale && order.orderStatus === 'COMPLETED'" class="del-btn" link @click="delOrder(order.sn)">
<el-icon><Delete /></el-icon>
</el-button>
<span class="order-amount">{{ $filters.unitPrice(order.flowPrice, "¥") }}</span>
<div class="order-sn" v-else>
{{ order.createTime }}<span>订单号 {{ order.sn }}</span><span class="order-store" @click="shopPage(order.storeId)">{{ order.storeName }}</span>
</div>
<div class="order-status">{{ order.isAfterSale ? filterAfterSaleStatus(order.serviceStatus) : filterOrderStatus(order.orderStatus) }}</div>
</div>
<div class="order-body">
<div class="goods-list">
<div class="col-detail">
<div
v-for="(goods, goodsIndex) in order.orderItems"
:key="goodsIndex"
class="goods-item"
>
<img
@click="goodsDetail(goods.skuId, goods.goodsId)"
class="hover-color"
:src="goods.image"
alt=""
/>
<div class="goods-thumb">
<img
@click="goodsDetail(goods.skuId, goods.goodsId)"
class="hover-color"
:src="goods.image"
alt=""
@error="$event.target.style.visibility = 'hidden'"
/>
</div>
<div class="goods-info">
<div class="goods-name hover-color" @click="goodsDetail(goods.skuId, goods.goodsId)">{{ goods.name }}</div>
<div class="goods-meta">
<span class="global_color">{{ $filters.unitPrice(goods.goodsPrice, "¥") }}</span>
<span class="goods-num">x {{ goods.num }}</span>
<div class="goods-num">共{{ goods.num }}件</div>
<div class="goods-extra">
<span v-if="refundPriceList(goods.isRefund)" class="refund-tag">{{ refundPriceList(goods.isRefund) }}</span>
</div>
<div class="goods-ops" v-if="goods.commentStatus == 'UNFINISHED' || goods.complainStatus == 'NO_APPLY'">
<el-button v-if="goods.commentStatus == 'UNFINISHED'" @click="comment(order.sn, goodsIndex)" size="small" type="success">评价</el-button>
<el-button v-if="goods.complainStatus == 'NO_APPLY'" @click="complain(order.sn, goodsIndex)" type="danger" size="small">投诉</el-button>
<button v-if="goods.complainStatus == 'NO_APPLY'" type="button" class="sub-btn" @click="complain(order.sn, goodsIndex)">投诉</button>
</div>
</div>
</div>
<button v-if="canAfterSale(order)" type="button" class="sub-btn" @click="applyAfterSale(order.orderItems)">申请售后</button>
</div>
<div class="store-name">
<span @click="shopPage(order.storeId)">{{ order.storeName }}</span>
<div class="col-amount">
<div class="order-amount">{{ $filters.unitPrice(order.flowPrice, "¥") }}</div>
<div v-if="paymentLabel(order)" class="pay-method">{{ paymentLabel(order) }}</div>
</div>
<div class="order-actions" v-if="order.isAfterSale">
<el-button @click="goAfterSaleDetail(order.serviceSn)" type="warning" size="small">售后详情</el-button>
</div>
<div class="order-actions" v-else>
<el-button @click="orderDetail(order.sn)" type="warning" size="small">订单详情</el-button>
<el-button @click="handleCancelOrder(order.sn)" type="danger" v-if="order.allowOperationVO.cancel" size="small">取消订单</el-button>
<el-button @click="goPay(order.sn)" size="small" type="success" v-if="order.allowOperationVO.pay">去支付</el-button>
<el-button @click="received(order.sn)" size="small" type="primary" v-if="order.allowOperationVO.rog">确认收货</el-button>
<el-button v-if="order.orderType !== 'E_COUPON' && order.groupAfterSaleStatus && (order.groupAfterSaleStatus.includes('NOT_APPLIED')|| order.groupAfterSaleStatus.includes('PART_AFTER_SALE'))"
@click="applyAfterSale(order.orderItems)" size="small">申请售后</el-button>
<div class="col-ops">
<template v-if="order.isAfterSale">
<button type="button" class="text-link" @click="goAfterSaleDetail(order.serviceSn)">售后详情</button>
</template>
<template v-else>
<button v-if="order.allowOperationVO && order.allowOperationVO.pay" type="button" class="op-btn" @click="goPay(order.sn)">去支付</button>
<button v-if="order.allowOperationVO && order.allowOperationVO.rog" type="button" class="op-btn" @click="received(order.sn)">确认收货</button>
<button type="button" class="text-link" @click="orderDetail(order.sn)">订单详情</button>
<button v-if="order.allowOperationVO && order.allowOperationVO.cancel" type="button" class="text-link" @click="handleCancelOrder(order.sn)">取消订单</button>
<button v-if="order.orderStatus === 'COMPLETED'" type="button" class="text-link" @click="delOrder(order.sn)">删除订单</button>
</template>
</div>
</div>
</div>
@@ -94,7 +93,7 @@
<!-- 分页 -->
<div class="page-size">
<el-pagination v-model:current-page="params.pageNumber" v-model:page-size="params.pageSize"
:total="total" @current-change="changePageNum"
:total="total" :page-sizes="[20, 50, 100]" @current-change="changePageNum"
@size-change="changePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</UserCenterLayout>
@@ -102,68 +101,67 @@
<!-- 个人中心首页订单列表 -->
<empty v-if="homePage && orderList.length === 0" />
<div class="order-content" v-else-if="homePage">
<div class="order-cols">
<span>订单详情</span>
<span>金额</span>
<span>操作</span>
</div>
<div
class="order-list"
v-for="(order, onderIndex) in orderList"
:key="onderIndex"
>
<div class="order-header">
<div class="order-header-main">
<div class="order-status">{{ order.isAfterSale ? filterAfterSaleStatus(order.serviceStatus) : filterOrderStatus(order.orderStatus) }}</div>
<div class="order-sn" v-if="order.isAfterSale">
售后单号:{{ order.serviceSn }}<span>{{ order.createTime }}</span>
</div>
<div class="order-sn" v-else>
订单号:{{ order.sn }}<span>{{ order.createTime }}</span>
</div>
<div class="order-sn" v-if="order.isAfterSale">
{{ order.createTime }}<span>售后单号 {{ order.serviceSn }}</span><span class="order-store" @click="shopPage(order.storeId)">{{ order.storeName }}</span>
</div>
<div class="order-header-side">
<el-button v-if="!order.isAfterSale && order.orderStatus === 'COMPLETED'" class="del-btn" link @click="delOrder(order.sn)">
<el-icon><Delete /></el-icon>
</el-button>
<span class="order-amount">{{ $filters.unitPrice(order.flowPrice, "¥") }}</span>
<div class="order-sn" v-else>
{{ order.createTime }}<span>订单号 {{ order.sn }}</span><span class="order-store" @click="shopPage(order.storeId)">{{ order.storeName }}</span>
</div>
<div class="order-status">{{ order.isAfterSale ? filterAfterSaleStatus(order.serviceStatus) : filterOrderStatus(order.orderStatus) }}</div>
</div>
<div class="order-body">
<div class="goods-list">
<div class="col-detail">
<div
v-for="(goods, goodsIndex) in order.orderItems"
:key="goodsIndex"
class="goods-item"
>
<img
@click="goodsDetail(goods.skuId, goods.goodsId)"
class="hover-color"
:src="goods.image"
alt=""
/>
<div class="goods-thumb">
<img
@click="goodsDetail(goods.skuId, goods.goodsId)"
class="hover-color"
:src="goods.image"
alt=""
@error="$event.target.style.visibility = 'hidden'"
/>
</div>
<div class="goods-info">
<div class="goods-name hover-color" @click="goodsDetail(goods.skuId, goods.goodsId)">{{ goods.name }}</div>
<div class="goods-meta">
<span class="global_color">{{ $filters.unitPrice(goods.goodsPrice, "¥") }}</span>
<span class="goods-num">x {{ goods.num }}</span>
<div class="goods-num">共{{ goods.num }}件</div>
<div class="goods-extra">
<span v-if="refundPriceList(goods.isRefund)" class="refund-tag">{{ refundPriceList(goods.isRefund) }}</span>
</div>
<div class="goods-ops" v-if="goods.commentStatus == 'UNFINISHED' || goods.complainStatus == 'NO_APPLY'">
<el-button v-if="goods.commentStatus == 'UNFINISHED'" @click="comment(order.sn, goodsIndex)" size="small" type="success">评价</el-button>
<el-button v-if="goods.complainStatus == 'NO_APPLY'" @click="complain(order.sn, goodsIndex)" type="danger" size="small">投诉</el-button>
<button v-if="goods.complainStatus == 'NO_APPLY'" type="button" class="sub-btn" @click="complain(order.sn, goodsIndex)">投诉</button>
</div>
</div>
</div>
<button v-if="canAfterSale(order)" type="button" class="sub-btn" @click="applyAfterSale(order.orderItems)">申请售后</button>
</div>
<div class="store-name">
<span @click="shopPage(order.storeId)">{{ order.storeName }}</span>
<div class="col-amount">
<div class="order-amount">{{ $filters.unitPrice(order.flowPrice, "¥") }}</div>
<div v-if="paymentLabel(order)" class="pay-method">{{ paymentLabel(order) }}</div>
</div>
<div class="order-actions" v-if="order.isAfterSale">
<el-button @click="goAfterSaleDetail(order.serviceSn)" type="warning" size="small">售后详情</el-button>
</div>
<div class="order-actions" v-else>
<el-button @click="orderDetail(order.sn)" type="warning" size="small">订单详情</el-button>
<el-button @click="handleCancelOrder(order.sn)" type="danger" v-if="order.allowOperationVO.cancel" size="small">取消订单</el-button>
<el-button @click="goPay(order.sn)" size="small" type="success" v-if="order.allowOperationVO.pay">去支付</el-button>
<el-button @click="received(order.sn)" size="small" type="primary" v-if="order.allowOperationVO.rog">确认收货</el-button>
<el-button v-if="order.orderType !== 'E_COUPON' && order.groupAfterSaleStatus && (order.groupAfterSaleStatus.includes('NOT_APPLIED')|| order.groupAfterSaleStatus.includes('PART_AFTER_SALE'))"
@click="applyAfterSale(order.orderItems)" size="small">申请售后</el-button>
<div class="col-ops">
<template v-if="order.isAfterSale">
<button type="button" class="text-link" @click="goAfterSaleDetail(order.serviceSn)">售后详情</button>
</template>
<template v-else>
<button v-if="order.allowOperationVO && order.allowOperationVO.pay" type="button" class="op-btn" @click="goPay(order.sn)">去支付</button>
<button v-if="order.allowOperationVO && order.allowOperationVO.rog" type="button" class="op-btn" @click="received(order.sn)">确认收货</button>
<button type="button" class="text-link" @click="orderDetail(order.sn)">订单详情</button>
<button v-if="order.allowOperationVO && order.allowOperationVO.cancel" type="button" class="text-link" @click="handleCancelOrder(order.sn)">取消订单</button>
<button v-if="order.orderStatus === 'COMPLETED'" type="button" class="text-link" @click="delOrder(order.sn)">删除订单</button>
</template>
</div>
</div>
</div>
@@ -198,12 +196,10 @@
<script>
import { Message, Modal } from "@/utils/message";
import { getOrderList, sureReceived, cancelOrder, delOrder } from '@/api/order';
import { afterSaleReason, afterSaleList } from '@/api/member';
import { afterSaleReason } from '@/api/member';
import { orderStatusList, afterSaleStatusList } from '../enumeration.js'
import { Delete } from '@element-plus/icons-vue'
export default {
name: 'MyOrder',
components: { Delete },
props: {
homePage: { // 判断是否个人中心首页展示内容
type: Boolean,
@@ -215,7 +211,7 @@ export default {
orderList: [], // 订单列表
params: { // 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20,
// orderStatus: 'ALL',
keywords: '',
tag: 'ALL'
@@ -227,7 +223,7 @@ export default {
// 状态数组
orderStatusList,
afterSaleStatusList,
changeWay: ['全部', '待支付', '待发货', '待收货/使用', '评价', '售后'],
changeWay: ['全部', '待支付', '待发货', '待收货/使用'],
activeOrderTab: 0,
total: 0, // 数据总数
spinShow: false, // 加载状态
@@ -270,11 +266,8 @@ export default {
this.activeOrderTab = index;
this.params.pageNumber = 1;
delete this.params.commentStatus;
const tags = ['ALL', 'WAIT_PAY', 'WAIT_SHIP', 'WAIT_USE', 'ALL', 'AFTER_SALE'];
const tags = ['ALL', 'WAIT_PAY', 'WAIT_SHIP', 'WAIT_USE'];
this.params.tag = tags[index] || 'ALL';
if (index === 4) {
this.params.commentStatus = 'UNFINISHED';
}
this.getList()
},
// 跳转店铺首页
@@ -320,9 +313,6 @@ export default {
afterSaleSelect (item) {
this.$router.push({name: 'ApplyAfterSale', query: {sn: item.sn}})
},
comment (sn, goodsIndex) { // 评价
this.$router.push({path: '/home/addEval', query: {sn, index: goodsIndex}})
},
complain (sn, goodsIndex) { // 投诉
this.$router.push({name: 'Complain', query: {sn, index: goodsIndex}})
},
@@ -343,29 +333,10 @@ export default {
},
getList () {
this.spinShow = true;
if (this.activeOrderTab === 5) {
afterSaleList({
pageNumber: this.params.pageNumber,
pageSize: this.params.pageSize,
keywords: this.params.keywords,
sort: 'createTime',
order: 'desc',
}).then(res => {
this.spinShow = false
if (res.success) {
this.orderList = (res.result.records || []).map(item => this.mapAfterSaleOrder(item));
this.total = res.result.total;
}
});
return;
}
let params = JSON.parse(JSON.stringify(this.params))
if (params.orderStatus === 'ALL') {
delete params.orderStatus
}
if (params.tag === 'AFTER_SALE') {
delete params.tag
}
getOrderList(params).then(res => {
this.spinShow = false
if (res.success) {
@@ -431,6 +402,27 @@ export default {
filterAfterSaleStatus (status) {
const ob = this.afterSaleStatusList.filter(e => e.status === status);
return ob && ob[0] ? ob[0].name : status
},
canAfterSale (order) {
if (order.isAfterSale || order.orderType === 'E_COUPON' || !order.groupAfterSaleStatus) {
return false
}
return order.groupAfterSaleStatus.includes('NOT_APPLIED') || order.groupAfterSaleStatus.includes('PART_AFTER_SALE')
},
paymentLabel (order) {
const map = {
WECHAT: '微信支付',
ALIPAY: '支付宝',
WALLET: '余额支付',
BANK_TRANSFER: '线下转账'
}
if (order.paymentMethod && map[order.paymentMethod]) {
return map[order.paymentMethod]
}
if (order.payStatus === 'UNPAID' || order.orderStatus === 'UNPAID') {
return '待支付'
}
return ''
}
}
};
@@ -454,6 +446,8 @@ export default {
}
}
.order-content {
--order-cols: minmax(0, 1fr) 120px 120px;
--order-gap: 12px;
padding-top: 14px;
}
.page-size {
@@ -466,6 +460,23 @@ export default {
}
}
/** 订单列表 */
.order-cols {
display: grid;
grid-template-columns: var(--order-cols);
column-gap: var(--order-gap);
align-items: center;
padding: 0 16px 10px;
color: #999;
font-size: 13px;
span:first-child {
padding-left: 72px;
}
span:nth-child(n + 2) {
text-align: center;
}
}
.order-list {
border: 1px solid #eee;
border-radius: 8px;
@@ -473,120 +484,121 @@ export default {
background: #fff;
overflow: hidden;
&:hover {
.del-btn { visibility: visible; }
}
.del-btn {
visibility: hidden;
padding: 0;
height: auto;
color: #999;
}
.order-header {
display: flex;
display: grid;
grid-template-columns: var(--order-cols);
column-gap: var(--order-gap);
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 16px;
background: #fafafa;
padding: 10px 16px;
background: #f7f7f7;
border-bottom: 1px solid #f0f0f0;
}
.order-header-main {
min-width: 0;
}
.order-status {
font-size: 14px;
font-weight: 600;
color: #333;
line-height: 1.4;
}
.order-sn {
margin-top: 4px;
font-size: 12px;
color: #999;
line-height: 1.4;
grid-column: 1 / 3;
display: flex;
align-items: center;
min-width: 0;
font-size: 14px;
color: #171A26;
line-height: 1.5;
span {
margin-left: 16px;
}
}
.order-header-side {
display: flex;
align-items: center;
flex-shrink: 0;
gap: 8px;
.order-store {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #438cde;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.order-amount {
min-width: 88px;
text-align: right;
font-size: 16px;
font-weight: 600;
color: $theme_color;
.order-status {
grid-column: 3;
min-width: 0;
text-align: center;
font-size: 14px;
color: #666;
line-height: 1.4;
}
.order-body {
display: grid;
grid-template-columns: minmax(0, 1fr) 120px 100px;
gap: 12px;
align-items: start;
grid-template-columns: var(--order-cols);
column-gap: var(--order-gap);
align-items: center;
padding: 14px 16px;
color: #999;
}
.goods-item {
display: flex;
align-items: flex-start;
display: grid;
grid-template-columns: 60px minmax(0, 1fr);
column-gap: 12px;
align-items: start;
width: 100%;
min-width: 0;
& + .goods-item {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f5f5f5;
}
}
.goods-thumb {
width: 60px;
height: 60px;
border: 1px solid #f0f0f0;
overflow: hidden;
background: #f5f5f5;
img {
width: 72px;
height: 72px;
margin-right: 12px;
border-radius: 6px;
width: 100%;
height: 100%;
object-fit: cover;
flex-shrink: 0;
background: #f5f5f5;
display: block;
}
}
.goods-info {
flex: 1;
min-width: 0;
overflow: hidden;
}
.goods-name {
max-width: 100%;
color: #333;
font-size: 14px;
line-height: 1.4;
font-size: 16px;
line-height: 22px;
max-height: 44px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
}
.goods-meta {
.goods-num {
margin-top: 6px;
color: #999;
font-size: 14px;
line-height: 1.4;
}
.goods-extra {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
font-size: 13px;
line-height: 1.4;
}
.goods-num {
color: #666;
margin-top: 6px;
}
.refund-tag {
@@ -594,47 +606,88 @@ export default {
border-radius: 4px;
background: #fff7e8;
color: #ff9900;
font-size: 12px;
line-height: 20px;
font-size: 14px;
line-height: 22px;
}
.goods-ops {
display: flex;
flex-wrap: wrap;
gap: 8px;
.sub-btn {
height: 28px;
margin-top: 8px;
padding: 0 10px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
color: #666;
font-size: 14px;
line-height: 26px;
cursor: pointer;
.el-button {
margin: 0;
&:hover {
border-color: $theme_color;
color: $theme_color;
}
}
.store-name {
padding-top: 4px;
.goods-extra .sub-btn {
margin-top: 0;
}
.col-amount,
.col-ops {
text-align: center;
font-size: 13px;
line-height: 1.4;
word-break: break-all;
span {
color: #438cde;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
}
.order-actions {
.order-amount {
color: #333;
font-size: 16px;
font-weight: 600;
line-height: 1.4;
}
.pay-method {
margin-top: 4px;
color: #999;
font-size: 14px;
line-height: 1.4;
}
.col-ops {
display: flex;
flex-direction: column;
align-items: stretch;
align-items: center;
gap: 8px;
}
.el-button {
margin: 0;
width: 100%;
.op-btn {
min-width: 96px;
height: 32px;
padding: 0 12px;
border: 1px solid $theme_color;
border-radius: 4px;
background: #fff;
color: #171A26;
font-family: inherit;
font-size: 14px;
line-height: 30px;
cursor: pointer;
&:hover {
background: #fff5f4;
}
}
.text-link {
border: none;
background: none;
padding: 0;
color: #171A26;
font-family: inherit;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
}

View File

@@ -1,56 +1,66 @@
<template>
<div class="order-detail" v-if="order.order">
<UserCenterLayout title="订单详情" :tabs="['订单详情']">
<el-card
class="mb_10"
shadow="never"
v-if="
order.allowOperationVO.pay ||
order.allowOperationVO.rog ||
order.allowOperationVO.cancel
"
>
<el-button
type="success"
@click="goPay(order.order.sn)"
size="small"
v-if="order.allowOperationVO.pay"
>去支付</el-button>
<el-button
type="primary"
@click="received(order.order.sn)"
size="small"
v-if="order.allowOperationVO.rog"
>确认收货</el-button>
<el-button
type="danger"
@click="handleCancelOrder(order.order.sn)"
v-if="order.allowOperationVO.cancel"
size="small"
>取消订单</el-button>
<el-button v-if="order.allowOperationVO.showLogistics || orderPackage.length > 0 || logistics" type="info" @click="logisticsList()" size="small">查看物流</el-button>
</el-card>
<p class="verificationCode" v-if="order.order.verificationCode && !isECouponOrder">
核验码:<span>{{ order.order.verificationCode }}</span>
</p>
<div class="order-card">
<p class="global_color fontsize_18">{{ order.orderStatusValue }}</p>
<p>订单号:{{ order.order.sn }}</p>
<div style="color: #999" class="operation-time">
操作时间:{{ order.order.updateTime || order.order.createTime }}
<UserCenterLayout>
<div class="order-card status-card">
<div class="status-head">
<div class="status-main">
<p class="status-text">{{ order.orderStatusValue }}</p>
<p class="status-sn">订单号:{{ order.order.sn }}</p>
</div>
<div class="status-side">
<div class="operation-time">
操作时间:{{ order.order.updateTime || order.order.createTime }}
</div>
<div
class="status-actions"
v-if="
order.allowOperationVO.pay ||
order.allowOperationVO.rog ||
order.allowOperationVO.cancel ||
order.allowOperationVO.showLogistics ||
orderPackage.length > 0 ||
logistics
"
>
<el-button
type="success"
@click="goPay(order.order.sn)"
size="small"
v-if="order.allowOperationVO.pay"
>去支付</el-button>
<el-button
type="primary"
@click="received(order.order.sn)"
size="small"
v-if="order.allowOperationVO.rog"
>确认收货</el-button>
<el-button
type="danger"
@click="handleCancelOrder(order.order.sn)"
v-if="order.allowOperationVO.cancel"
size="small"
>取消订单</el-button>
<el-button v-if="order.allowOperationVO.showLogistics || orderPackage.length > 0 || logistics" type="info" @click="logisticsList()" size="small">查看物流</el-button>
</div>
</div>
</div>
<el-steps
v-if="progressList.length"
class="progress"
:current="progressList.length"
:active="progressList.length - 1"
direction="vertical"
>
<el-step :title="progress.message"
:content="progress.createTime"
<el-step
:title="progress.message"
:description="progress.createTime"
v-for="(progress, index) in progressList"
:key="index"
></el-step>
</el-steps>
</div>
<p class="verificationCode" v-if="order.order.verificationCode && !isECouponOrder">
核验码:<span>{{ order.order.verificationCode }}</span>
</p>
<div class="order-card" v-if="order.order.deliveryMethod === 'LOGISTICS' && !isNonPhysicalOrder">
<h3>收货人信息</h3>
<p>收货人:{{ order.order.consigneeName }}</p>
@@ -191,28 +201,26 @@
<table>
<thead>
<tr>
<th width="30%">商品</th>
<th width="15%">货号</th>
<th width="28%">商品</th>
<th width="16%">货号</th>
<th width="10%">单价</th>
<th width="5%">数量</th>
<th width="10%" v-if="!isECouponOrder">退款状态</th>
<th width="10%" v-if="!isECouponOrder">实际退款金额</th>
<th width="8%">数量</th>
<th width="10%">小计</th>
<th width="10%">操作</th>
<th width="8%">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(goods, goodsIndex) in order.orderItems" :key="goodsIndex">
<td>
<img
@click="goodsDetail(goods.skuId, goods.goodsId)"
:src="goods.image"
alt=""
/>
<div>
<div class="goods-cell">
<img
@click="goodsDetail(goods.skuId, goods.goodsId)"
:src="goods.image"
alt=""
/>
<p
@click="goodsDetail(goods.skuId, goods.goodsId)"
class="hover-color"
class="hover-color goods-name"
>
{{ goods.goodsName }}
</p>
@@ -221,23 +229,19 @@
<td>{{ goods.id }}</td>
<td>{{ $filters.unitPrice(goods.goodsPrice, "¥") }}</td>
<td>{{ goods.num }}</td>
<td v-if="!isECouponOrder">{{refundPriceList(goods.isRefund)}}</td>
<td v-if="!isECouponOrder">{{ $filters.unitPrice(goods.refundPrice, "¥") }}</td>
<td>{{ $filters.unitPrice((goods.goodsPrice * goods.num), "¥") }}</td>
<td class="order-item-actions">
<el-button
<button
v-if="goods.commentStatus == 'UNFINISHED'"
type="button"
class="comment-text"
@click="comment(order.order.sn, goodsIndex)"
size="small"
type="success"
class="order-item-action-btn fontsize_12 mb_5"
>评价</el-button
>
>评价</button>
<el-button
v-if="goods.complainStatus == 'NO_APPLY'"
@click="complain(order.order.sn, goodsIndex)"
type="danger"
class="order-item-action-btn fontsize_12 mb_5"
class="order-item-action-btn mb_5"
size="small"
>投诉</el-button
>
@@ -575,21 +579,6 @@ export default {
}
window.open(invoiceAddress, "_blank");
},
// 退款状态枚举
refundPriceList(status) {
switch (status) {
case 'ALL_REFUND':
return "全部退款";
case 'PART_REFUND':
return "部分退款";
case 'NO_REFUND':
return "未退款";
case 'REFUNDING':
return "退款中";
default:
return "未退款";
}
},
goodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = this.$router.resolve({
@@ -621,7 +610,9 @@ export default {
this.order["receipt"] = receiptRes.result;
}
}
this.progressList = res.result.orderLogs;
this.progressList = (res.result.orderLogs || []).slice().sort((a, b) => {
return new Date(a.createTime).getTime() - new Date(b.createTime).getTime();
});
if (this.order.order.deliveryMethod === 'LOGISTICS') {
this.getOrderPackage(this.order.order.sn);
}
@@ -727,109 +718,237 @@ export default {
.mb_5 {
margin-bottom: 5px;
}
.order-detail {
color: #333;
}
.order-card {
padding: 10px;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #e8eaec;
position: relative;
.global_color {
color: $theme_color;
}
p {
color: #999;
margin: 3px;
margin-left: 5px;
}
padding: 4px 0 16px;
margin-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
h3 {
font-weight: normal;
font-size: 16px;
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
color: #333;
line-height: 1.4;
}
.operation-time {
position: absolute;
right: 20px;
top: 20px;
p {
margin: 0;
padding: 4px 0;
color: #666;
line-height: 22px;
}
}
/** 店铺名称 */
.status-card {
padding-top: 8px;
}
.status-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.status-text {
color: $theme_color;
font-size: 18px;
font-weight: 600;
line-height: 1.4;
}
.status-sn {
margin-top: 6px;
color: #666;
}
.status-side {
flex-shrink: 0;
text-align: right;
}
.operation-time {
color: #999;
font-size: 13px;
line-height: 22px;
}
.status-actions {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
.el-button {
margin: 0;
}
}
.progress {
max-width: 640px;
margin: 20px 0 0;
:deep(.el-step.is-vertical) {
padding-bottom: 20px;
}
:deep(.el-step.is-vertical:last-child) {
padding-bottom: 0;
}
:deep(.el-step__head.is-finish) {
color: #c0c4cc;
border-color: #c0c4cc;
}
:deep(.el-step__title.is-finish),
:deep(.el-step__description.is-finish) {
color: #909399;
font-weight: 400;
}
:deep(.el-step__head.is-process) {
color: $theme_color;
border-color: $theme_color;
}
:deep(.el-step__title.is-process),
:deep(.el-step__description.is-process) {
color: $theme_color;
}
}
.goods {
padding-top: 8px;
}
.shop-name {
margin: 15px 0;
margin: 8px 0 12px;
font-size: 14px;
font-weight: 600;
span {
color: #438cde;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.el-icon {
color: #ff8f23;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
}
/** 商品列表 */
table {
border: 1px solid #ddd;
color: #999;
border: 1px solid #eee;
color: #666;
border-collapse: collapse;
width: 100%;
tr {
border-top: 1px solid #ddd;
border-top: 1px solid #eee;
}
thead > tr {
height: 40px;
background: #eee;
background: #fafafa;
}
th {
font-size: 12px;
font-weight: normal;
padding: 0 8px;
font-size: 13px;
font-weight: 500;
color: #666;
}
td {
padding: 5px;
padding: 12px 8px;
text-align: center;
vertical-align: middle;
&:first-child {
text-align: left;
display: flex;
img {
width: 70px;
height: 70px;
margin-right: 10px;
margin-left: 10px;
cursor: pointer;
}
}
&:last-child {
color: $theme_color;
}
}
}
.goods-cell {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
img {
width: 64px;
height: 64px;
border-radius: 6px;
object-fit: cover;
flex-shrink: 0;
background: #f5f5f5;
cursor: pointer;
}
}
.goods-name {
min-width: 0;
color: #333;
line-height: 20px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-word;
cursor: pointer;
}
.order-item-actions {
vertical-align: middle;
.comment-text {
display: block;
margin: 0 auto 6px;
padding: 0;
border: 0;
background: transparent;
color: $theme_color;
font-size: 14px;
line-height: 22px;
cursor: pointer;
}
.order-item-action-btn {
display: block;
margin: 0 auto 5px;
margin: 0 auto 6px;
min-width: 88px;
}
}
/** 订单价格 */
.order-price {
text-align: right;
margin-top: 30px;
font-size: 16px;
color: #999;
> div > span:nth-child(2) {
width: 130px;
text-align: right;
display: inline-block;
margin-top: 10px;
margin-top: 16px;
padding-top: 8px;
font-size: 14px;
color: #666;
> div {
display: flex;
justify-content: flex-end;
align-items: center;
line-height: 28px;
}
> div > span:nth-child(2) {
width: 140px;
margin-top: 0;
text-align: right;
color: #333;
}
.actrual-price {
color: $theme_color;
font-weight: bold;
font-weight: 600;
font-size: 20px;
}
}
@@ -875,11 +994,6 @@ table {
display: none;
}
}
/** 订单进度条 */
.progress {
margin: 15px 0;
}
.layui-layer-wrap {
dl {
border-top: solid 1px #f5f5f5;

View File

@@ -1,7 +1,6 @@
<template>
<div class="wrapper">
<UserCenterLayout
title="优惠券列表"
:tabs="statusNameList"
:active-tab="activeStatusIndex"
@tab-change="change"
@@ -13,16 +12,16 @@
<div>
<span
v-if="item.couponType === 'PRICE'"
class="fontsize_12 global_color"
class="global_color coupon-amount"
>¥<span class="price">{{ $filters.unitPrice(item.price) }}</span></span>
<span
v-if="item.couponType === 'DISCOUNT'"
class="fontsize_12 global_color"
class="global_color coupon-amount"
><span class="price">{{ item.discount }}</span>折</span>
<span class="describe">满{{ item.consumeThreshold }}元可用</span>
</div>
<p>使用范围:{{ useScope(item.scopeType, item.storeName) }}</p>
<p>有效期:{{ item.endTime }}</p>
<p class="coupon-expire">有效期:{{ formatValidity(item) }}</p>
</div>
<b></b>
<a
@@ -40,6 +39,7 @@
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="total"
:page-sizes="[20, 50, 100]"
@current-change="changePageNum"
@size-change="changePageSize"
layout="total, sizes, prev, pager, next, jumper"
@@ -67,7 +67,7 @@ export default {
params: {
// 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20,
memberCouponStatus: "NEW",
},
total: 0, // 优惠券总数
@@ -84,13 +84,19 @@ export default {
getList() {
// 获取优惠券列表
this.loading = true;
memberCouponList(this.params).then((res) => {
this.loading = false;
if (res.success) {
this.list = res.result.records;
this.total = res.result.total;
}
});
memberCouponList(this.params)
.then((res) => {
this.loading = false;
if (res.success) {
this.list = res.result.records || [];
this.total = res.result.total || 0;
}
})
.catch(() => {
this.loading = false;
this.list = [];
this.total = 0;
});
},
// 切换优惠券状态
change(index) {
@@ -139,6 +145,25 @@ export default {
}
return `${shop}${goods}可用`;
},
formatValidity(item) {
const start = this.formatCouponTime(item && item.startTime);
const end = this.formatCouponTime(item && item.endTime);
if (start && end) return `${start} 至 ${end}`;
if (end) return `至 ${end}`;
if (start) return `${start} 起`;
return "长期有效";
},
formatCouponTime(value) {
if (!value) return "";
const text = String(value).trim();
if (/^\d{4}-\d{2}-\d{2}/.test(text)) {
return text.slice(0, 16);
}
const date = new Date(text);
if (Number.isNaN(date.getTime())) return text;
const pad = (n) => String(n).padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
},
},
mounted() {
this.getList();
@@ -150,19 +175,31 @@ export default {
@import "../../../assets/styles/coupon.scss";
.coupon-item {
overflow: hidden;
height: 145px;
height: auto;
min-height: 168px;
margin-left: 0;
$claim-width: 55px;
.c-left {
width: calc(100% - #{$claim-width});
box-sizing: border-box;
padding: 20px;
padding: 16px 16px 14px;
.coupon-amount {
font-size: 14px;
}
.describe {
font-size: 14px;
}
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
overflow: visible;
text-overflow: unset;
white-space: normal;
word-break: break-all;
line-height: 20px;
}
}

View File

@@ -1,19 +1,21 @@
<template>
<div class="wrapper">
<UserCenterLayout title="资金管理" :tabs="['资金管理']">
<UserCenterLayout>
<div class="box">
<div class="mb_20 account-price">
<span class="subTips">账户余额:</span>
<span class="global_color mr_10" style="font-size:26px">¥{{ $filters.unitPrice(memberDeposit) }}</span>
<span class="subTips">冻结金额:</span>
<span class="">¥{{ $filters.unitPrice(frozenDeposit) }}</span>
<div class="wallet-summary">
<div class="wallet-stat">
<div class="label">账户余额</div>
<div class="value">¥{{ $filters.unitPrice(memberDeposit) }}</div>
</div>
<div class="account-btns">
<el-button type="primary" @click="recharge">在线充值</el-button>
<el-button @click="withdrawalApply">申请提现</el-button>
<div class="wallet-stat">
<div class="label">冻结金额</div>
<div class="value is-muted">¥{{ $filters.unitPrice(frozenDeposit) }}</div>
</div>
</div>
<div class="account-btns">
<button type="button" class="op-btn" @click="recharge">在线充值</button>
<button type="button" class="ghost-btn" @click="withdrawalApply">申请提现</button>
</div>
<el-dialog v-model="modal" width="530">
<template #header><p>
<el-icon><Edit /></el-icon>
@@ -87,103 +89,93 @@
<el-button size="large" type="success" @click="withdrawal">提现</el-button>
</div></template>
</el-dialog>
<!-- 余额日志 -->
<el-tabs v-model="activeWalletTab" @tab-click="(tab) => tabPaneChange(tab.paneName)">
<el-tab-pane label="余额日志" name="log">
<el-table ref="logTable" :data="logColumnsData.records || []">
<el-table-column prop="createTime" label="时间" width="190" />
<el-table-column label="金额" width="180">
<template #default="{ row }">
<span v-if="row.money > 0" style="color: green">{{ $filters.unitPrice(row.money, '+ ¥') }}</span>
<span v-else-if="row.money < 0" style="color: red">{{ $filters.unitPrice(0 - row.money, '- ¥') }}</span>
</template>
</el-table-column>
<el-table-column prop="detail" label="变动日志" />
</el-table>
<!-- 分页 -->
<div class="page-size">
<el-pagination v-model:current-page="walletForm.pageNumber"
v-model:page-size="walletForm.pageSize"
:page-sizes="[10, 20, 50]"
:total="logColumnsData.total"
@current-change="changePage"
@size-change="changePageSize"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</el-tab-pane>
<!-- 充值记录 -->
<el-tab-pane label="充值记录" name="recharge">
<el-table ref="rechargeTable" :data="rechargeListData.records || []">
<el-table-column prop="createTime" label="充值时间" width="168" />
<el-table-column prop="rechargeSn" label="支付单号" width="200" />
<el-table-column label="充值金额">
<template #default="{ row }">
<span v-if="row.payStatus === 'PAID'" style="color: green">{{ $filters.unitPrice(row.rechargeMoney, '+ ¥') }}</span>
<span v-else>{{ $filters.unitPrice(row.rechargeMoney, '¥') }}</span>
</template>
</el-table-column>
<el-table-column label="支付状态">
<template #default="{ row }">
<span v-if="row.payStatus === 'PAID'">已付款</span>
<span v-else-if="row.payStatus === 'UNPAID'">未付款</span>
<span v-else-if="row.payStatus === 'CANCEL'">已取消</span>
</template>
</el-table-column>
<el-table-column label="支付方式">
<template #default="{ row }">
<span v-if="row.rechargeWay === 'ALIPAY'">支付宝</span>
<span v-else-if="row.rechargeWay === 'WECHAT'">微信</span>
<span v-else-if="row.rechargeWay === 'BANK_TRANSFER'">线下转账</span>
</template>
</el-table-column>
<el-table-column prop="payTime" label="支付时间" width="180" />
</el-table>
<!-- 分页 -->
<div class="page-size">
<el-pagination v-model:current-page="rechargeForm.pageNumber"
v-model:page-size="rechargeForm.pageSize"
:page-sizes="[10, 20, 50]"
:total="rechargeListData.total"
@current-change="rechargeChangePage"
@size-change="rechargeChangePageSize"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</el-tab-pane>
<div class="wallet-tabs">
<div class="wallet-tab" :class="{ active: activeWalletTab === 'log' }" @click="tabPaneChange('log')">余额日志</div>
<div class="wallet-tab" :class="{ active: activeWalletTab === 'recharge' }" @click="tabPaneChange('recharge')">充值记录</div>
<div class="wallet-tab" :class="{ active: activeWalletTab === 'withdrawApply' }" @click="tabPaneChange('withdrawApply')">提现记录</div>
</div>
<el-tab-pane label="提现记录" name="withdrawApply">
<el-table ref="withdrawTable" :data="withdrawApplyColumnsListData.records || []">
<el-table-column prop="createTime" label="申请时间" width="168" />
<el-table-column prop="sn" label="提现单号" width="200" />
<el-table-column label="提现金额" width="110">
<template #default="{ row }">
<span v-if="row.applyStatus === 'VIA_AUDITING'" style="color: green">{{ $filters.unitPrice(row.applyMoney, '+ ¥') }}</span>
<span v-else>{{ $filters.unitPrice(row.applyMoney, '¥') }}</span>
</template>
</el-table-column>
<el-table-column label="提现状态" width="95">
<template #default="{ row }">
<span v-if="row.applyStatus === 'APPLY'">申请中</span>
<span v-else-if="row.applyStatus === 'VIA_AUDITING'">审核通过</span>
<span v-else-if="row.applyStatus === 'SUCCESS'">提现成功</span>
<span v-else-if="row.applyStatus === 'ERROR'">提现失败</span>
<span v-else>审核拒绝</span>
</template>
</el-table-column>
<el-table-column prop="inspectTime" label="审核时间" width="168" />
<el-table-column prop="inspectRemark" label="审核备注" show-overflow-tooltip />
</el-table>
<!-- 分页 -->
<div class="page-size">
<el-pagination v-model:current-page="withdrawApplyForm.pageNumber"
v-model:page-size="withdrawApplyForm.pageSize"
:page-sizes="[10, 20, 50]"
:total="withdrawApplyColumnsListData.total"
@current-change="withdrawChangePage"
@size-change="withdrawChangePageSize"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
<div v-show="activeWalletTab === 'log'">
<empty v-if="!(logColumnsData.records || []).length" />
<div v-else class="wallet-list log-list">
<div class="wallet-head">
<span>时间</span>
<span>金额</span>
<span>变动日志</span>
</div>
</el-tab-pane>
</el-tabs>
<div class="wallet-row" v-for="(row, index) in logColumnsData.records" :key="row.id || index">
<div class="wallet-time">{{ row.createTime }}</div>
<div class="wallet-money" :class="row.money > 0 ? 'is-plus' : 'is-minus'">
<span v-if="row.money > 0">{{ $filters.unitPrice(row.money, '+ ¥') }}</span>
<span v-else-if="row.money < 0">{{ $filters.unitPrice(0 - row.money, '- ¥') }}</span>
</div>
<div class="wallet-detail">{{ row.detail }}</div>
</div>
</div>
<div class="page-size">
<el-pagination v-model:current-page="walletForm.pageNumber" v-model:page-size="walletForm.pageSize"
:page-sizes="[20, 50, 100]" :total="logColumnsData.total" @current-change="changePage"
@size-change="changePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</div>
<div v-show="activeWalletTab === 'recharge'">
<empty v-if="!(rechargeListData.records || []).length" />
<div v-else class="wallet-list recharge-list">
<div class="wallet-head">
<span>充值时间</span>
<span>支付单号</span>
<span>充值金额</span>
<span>支付状态</span>
<span>支付方式</span>
<span>支付时间</span>
</div>
<div class="wallet-row" v-for="(row, index) in rechargeListData.records" :key="row.rechargeSn || index">
<div>{{ row.createTime }}</div>
<div class="wallet-detail">{{ row.rechargeSn }}</div>
<div class="wallet-money" :class="row.payStatus === 'PAID' ? 'is-plus' : ''">
{{ $filters.unitPrice(row.rechargeMoney, row.payStatus === 'PAID' ? '+ ¥' : '¥') }}
</div>
<div>{{ rechargeStatusText(row.payStatus) }}</div>
<div>{{ rechargeWayText(row.rechargeWay) }}</div>
<div>{{ row.payTime || '—' }}</div>
</div>
</div>
<div class="page-size">
<el-pagination v-model:current-page="rechargeForm.pageNumber" v-model:page-size="rechargeForm.pageSize"
:page-sizes="[20, 50, 100]" :total="rechargeListData.total" @current-change="rechargeChangePage"
@size-change="rechargeChangePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</div>
<div v-show="activeWalletTab === 'withdrawApply'">
<empty v-if="!(withdrawApplyColumnsListData.records || []).length" />
<div v-else class="wallet-list withdraw-list">
<div class="wallet-head">
<span>申请时间</span>
<span>提现单号</span>
<span>提现金额</span>
<span>提现状态</span>
<span>审核时间</span>
<span>审核备注</span>
</div>
<div class="wallet-row" v-for="(row, index) in withdrawApplyColumnsListData.records" :key="row.sn || index">
<div>{{ row.createTime }}</div>
<div class="wallet-detail">{{ row.sn }}</div>
<div class="wallet-money" :class="row.applyStatus === 'VIA_AUDITING' ? 'is-plus' : ''">
{{ $filters.unitPrice(row.applyMoney, row.applyStatus === 'VIA_AUDITING' ? '+ ¥' : '¥') }}
</div>
<div>{{ withdrawStatusText(row.applyStatus) }}</div>
<div>{{ row.inspectTime || '—' }}</div>
<div class="wallet-detail">{{ row.inspectRemark || '—' }}</div>
</div>
</div>
<div class="page-size">
<el-pagination v-model:current-page="withdrawApplyForm.pageNumber" v-model:page-size="withdrawApplyForm.pageSize"
:page-sizes="[20, 50, 100]" :total="withdrawApplyColumnsListData.total" @current-change="withdrawChangePage"
@size-change="withdrawChangePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</div>
</UserCenterLayout>
</div>
</template>
@@ -221,19 +213,19 @@ export default {
walletForm: {
// 搜索框初始化对象
pageNumber: 1,
pageSize: 10
pageSize: 20
},
// 充值记录
rechargeForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10 // 页面大小
pageSize: 20 // 页面大小
},
// 提现记录
withdrawApplyForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10 // 页面大小
pageSize: 20 // 页面大小
},
// 提现申请校验
withdrawApplyFormValidate: {
@@ -280,31 +272,26 @@ export default {
getDepositLog(this.walletForm).then((res) => {
if (res.message === 'success') {
this.logColumnsData = res.result;
this.refreshActiveTableLayout();
}
});
},
tabPaneChange(v) {
// 如果查询充值记录
this.activeWalletTab = v;
if (v === 'recharge') {
this.getRechargeData();
}
// 如果是余额日志
if (v === 'log') {
this.init();
}
// 如果是提现记录
if (v === 'withdrawApply') {
this.getWithdrawApplyData();
}
this.refreshActiveTableLayout();
},
// 充值记录
getRechargeData() {
getRecharge(this.rechargeForm).then((res) => {
if (res.message === 'success') {
this.rechargeListData = res.result;
this.refreshActiveTableLayout();
}
});
},
@@ -313,19 +300,25 @@ export default {
getWithdrawApply(this.withdrawApplyForm).then((res) => {
if (res.message === 'success') {
this.withdrawApplyColumnsListData = res.result;
this.refreshActiveTableLayout();
}
});
},
refreshActiveTableLayout() {
this.$nextTick(() => {
const tableMap = {
log: this.$refs.logTable,
recharge: this.$refs.rechargeTable,
withdrawApply: this.$refs.withdrawTable
};
tableMap[this.activeWalletTab]?.doLayout?.();
});
rechargeStatusText (status) {
const map = { PAID: '已付款', UNPAID: '未付款', CANCEL: '已取消' }
return map[status] || status || '—'
},
rechargeWayText (way) {
const map = { ALIPAY: '支付宝', WECHAT: '微信', BANK_TRANSFER: '线下转账' }
return map[way] || way || '—'
},
withdrawStatusText (status) {
const map = {
APPLY: '申请中',
VIA_AUDITING: '审核通过',
SUCCESS: '提现成功',
ERROR: '提现失败'
}
return map[status] || '审核拒绝'
},
// 余额日志
changePage(v) {
@@ -413,31 +406,167 @@ export default {
</script>
<style lang="scss" scoped>
.box {
margin: 20px 0;
}
.page-size {
margin: 15px 0px;
.wallet-summary {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 16px;
margin-bottom: 16px;
}
.account-price {
font-weight: bold;
}
.wallet-stat {
flex: 1;
padding: 16px 20px;
background: #fafafa;
border-radius: 8px;
.subTips {
margin-left: 10px;
.label {
color: #666;
font-size: 14px;
line-height: 22px;
}
.value {
margin-top: 4px;
color: $theme_color;
font-size: 28px;
font-weight: 600;
line-height: 1.2;
&.is-muted {
color: #171A26;
font-size: 22px;
}
}
}
.account-btns {
margin: 10px 0;
display: flex;
gap: 12px;
margin-bottom: 8px;
}
.el-button {
margin: 0 4px;
.op-btn,
.ghost-btn {
height: 32px;
padding: 0 16px;
border-radius: 4px;
font-family: inherit;
font-size: 14px;
line-height: 30px;
cursor: pointer;
}
.op-btn {
border: 1px solid $theme_color;
background: $theme_color;
color: #fff;
&:hover {
opacity: 0.9;
}
}
.ghost-btn {
border: 1px solid #ddd;
background: #fff;
color: #171A26;
&:hover {
border-color: $theme_color;
color: $theme_color;
}
}
.wallet-tabs {
display: flex;
gap: 24px;
margin: 16px 0;
border-bottom: 1px solid #f0f0f0;
}
.wallet-tab {
position: relative;
padding: 10px 0;
color: #666;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&.active {
color: $theme_color;
font-weight: 600;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: $theme_color;
}
}
}
.wallet-list {
--wallet-cols: 180px 120px minmax(0, 1fr);
}
.recharge-list,
.withdraw-list {
--wallet-cols: 156px minmax(120px, 1.1fr) 100px 80px 80px 156px;
}
.withdraw-list {
--wallet-cols: 156px minmax(120px, 1fr) 100px 80px 156px minmax(100px, 1fr);
}
.wallet-head,
.wallet-row {
display: grid;
grid-template-columns: var(--wallet-cols);
column-gap: 12px;
align-items: start;
}
.wallet-head {
padding: 0 8px 10px;
color: #999;
font-size: 13px;
}
.wallet-row {
padding: 14px 8px;
border-top: 1px solid #f0f0f0;
color: #171A26;
font-size: 14px;
line-height: 22px;
}
.wallet-detail {
min-width: 0;
white-space: pre-wrap;
word-break: break-all;
}
.wallet-money {
font-weight: 600;
&.is-plus {
color: #19be6b;
}
&.is-minus {
color: $theme_color;
}
}
.page-size {
margin: 16px 0;
display: flex;
justify-content: flex-end;
:deep(.el-pagination) {
justify-content: flex-end;
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="wrapper my-gift-cards">
<UserCenterLayout title="我的礼品卡" :tabs="['我的礼品卡']">
<UserCenterLayout>
<div class="bind-section mb_20">
<div class="section-title">兑换礼品卡</div>
@@ -11,7 +11,6 @@
<el-form-item label="兑换码" prop="cardSecret" label-width="70px">
<el-input
v-model="bindForm.cardSecret"
type="password"
placeholder="请输入兑换码(卡密)"
maxlength="128"
style="width: 220px"
@@ -75,7 +74,7 @@
<div class="page-size">
<el-pagination v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:page-sizes="[10, 20, 50]"
:page-sizes="[20, 50, 100]"
:total="total"
@current-change="changePage"
@size-change="changePageSize"
@@ -111,7 +110,7 @@ export default {
list: [],
params: {
pageNumber: 1,
pageSize: 10,
pageSize: 20,
sort: "createTime",
order: "desc",
memberCardStatus: "AVAILABLE",
@@ -372,12 +371,12 @@ export default {
}
.gcc-face {
margin-top: 8px;
font-size: 13px;
font-size: 14px;
opacity: 0.95;
}
.gcc-cardno {
margin-top: 22px;
font-size: 12px;
font-size: 14px;
letter-spacing: 0.02em;
opacity: 0.75;
word-break: break-all;
@@ -393,7 +392,7 @@ export default {
}
.gcc-expire {
margin-top: 10px;
font-size: 12px;
font-size: 14px;
opacity: 0.9;
white-space: nowrap;
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="wrapper">
<UserCenterLayout title="我的足迹" :tabs="['我的足迹']">
<UserCenterLayout>
<template #extra>
<el-button class="del-btn" @click="clearAll" type="primary">删除全部</el-button>
</template>
@@ -12,9 +12,11 @@
:key="index"
@click="goodsDetail(item.id, item.goodsId)"
>
<img :src="item.thumbnail" :alt="item.goodsName" />
<p class="ellipsis">{{ item.goodsName }}</p>
<p>{{ $filters.unitPrice(item.price, "¥") }}</p>
<div class="thumb">
<img :src="item.thumbnail" :alt="item.goodsName" />
</div>
<p class="goods-name">{{ item.goodsName }}</p>
<p class="goods-price">{{ $filters.unitPrice(item.price, "¥") }}</p>
<span class="del-icon" @click.stop="clearById(item.goodsId)">
<el-icon><Delete /></el-icon>
</span>
@@ -26,7 +28,7 @@
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="total"
:page-sizes="[10, 20, 30, 40, 50, 100]"
:page-sizes="[20, 50, 100]"
layout="sizes, prev, pager, next, jumper"
@current-change="changePageNum"
@size-change="changePageSize"
@@ -49,7 +51,7 @@ export default {
spinShow: false, // 控制loading是否加载
params: {
pageNumber: 1,
pageSize: 10,
pageSize: 20,
order: "desc",
sort: "updateTime",
},
@@ -135,61 +137,86 @@ export default {
margin-bottom: 40px;
}
.del-btn {
margin: 0 0 10px 15px;
margin: 0;
}
.track-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
padding: 0 15px;
box-sizing: border-box;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px;
margin: 4px 0 0;
padding: 0;
list-style: none;
li {
width: 100%;
display: flex;
flex-direction: column;
min-width: 0;
border: 1px solid #f0f0f0;
border-radius: 8px;
overflow: hidden;
border: 1px solid #eee;
background: #fff;
position: relative;
box-sizing: border-box;
&:hover {
cursor: pointer;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
.del-icon {
display: flex;
}
}
}
.thumb {
width: 100%;
aspect-ratio: 1;
background: #f5f5f5;
overflow: hidden;
img {
width: 100%;
aspect-ratio: 1;
height: 100%;
object-fit: cover;
display: block;
}
}
&:hover {
cursor: pointer;
box-shadow: 1px 1px 3px #999;
.del-icon {
display: block;
}
}
p {
padding: 0 5px;
margin: 3px 0;
}
p:nth-child(2) {
color: #999;
}
p:nth-child(3) {
color: $theme_color;
}
.del-icon {
display: none;
font-size: 30px;
background-color: rgba(0, 0, 0, 0.3);
position: absolute;
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
right: 0;
top: 0;
cursor: pointer;
}
.goods-name {
margin: 10px 12px 0;
color: #333;
font-size: 13px;
line-height: 1.4;
height: 36px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.goods-price {
margin: 8px 12px 12px;
color: $theme_color;
font-size: 16px;
font-weight: 600;
line-height: 1.2;
}
.del-icon {
display: none;
align-items: center;
justify-content: center;
position: absolute;
right: 8px;
top: 8px;
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.45);
color: #fff;
font-size: 14px;
cursor: pointer;
}
}
.page-size {