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

@@ -446,6 +446,15 @@ export function memberMsgList (params) {
* @param {String} messageId 消息id
*/
export function getMemberMessage (id) {
return request({
url: `/buyer/message/member/${id}`,
method: Method.GET,
needToken: true,
skipMessage: true
});
}
export function readMemberMsg (id) {
return request({
url: `/buyer/message/member/${id}`,

View File

@@ -355,16 +355,17 @@ li {
margin-top: 6px;
}
.goods-show-detail {
font-size: 12px;
font-size: 14px;
line-height: 21px;
margin: 6px 0px;
}
.goods-show-num {
font-size: 12px;
font-size: 14px;
margin-bottom: 6px;
color: #666;
}
.goods-show-num {
font-size: 12px;
font-size: 14px;
margin-bottom: 6px;
color: #009688;
}
@@ -373,7 +374,7 @@ li {
font-weight: bold;
}
.goods-show-seller {
font-size: 12px;
font-size: 14px;
color: $theme_color;
}
.goods-page {

View File

@@ -36,8 +36,9 @@
margin-top: 6px;
}
.goods-show-detail {
height: 36px;
font-size: 12px;
height: 42px;
font-size: 14px;
line-height: 21px;
margin: 6px 0px;
overflow:hidden;
text-overflow: ellipsis;
@@ -51,12 +52,12 @@
color:#E23A3A;
}
.goods-show-num {
font-size: 12px;
font-size: 14px;
margin-bottom: 6px;
color: #666;
}
.goods-show-num {
font-size: 12px;
font-size: 14px;
margin-bottom: 6px;
color: #009688;
}
@@ -65,7 +66,7 @@
font-weight: bold;
}
.goods-show-seller {
font-size: 12px;
font-size: 14px;
color: $theme_color;
}
.goods-page {

View File

@@ -37,10 +37,6 @@
</div>
</div>
<template v-if="showTag">
<div class="only-store" v-if="storeId" @click="research()">
切换为{{!onlyStore ? '店铺内' : '平台'}}搜索
</div>
<div v-if="promotionTags.length === 0"></div>
<div v-else class="history-list flex">
<div
@@ -88,15 +84,9 @@ export default {
default:''
}
},
watch:{
storeId(val){
this.onlyStore = val ? true : false
}
},
data() {
return {
searchData: '', // 搜索内容
onlyStore:false,
};
},
methods: {
@@ -104,9 +94,6 @@ export default {
this.searchData = item;
this.search();
},
research(){
this.onlyStore = !this.onlyStore
},
search () { // 全平台搜索商品
const url = this.$route.path;
if(url == '/goodsList'){
@@ -116,8 +103,7 @@ export default {
path:'/goodsList',
query: { keyword: this.searchData },
}
if(this.storeId && this.onlyStore) pushData.query.storeId = this.storeId
if(this.storeId) pushData.query.storeId = this.storeId
this.$router.push(pushData);
}
@@ -162,11 +148,6 @@ export default {
};
</script>
<style scoped lang="scss">
.only-store{
text-align: right;
color:$theme_color;
cursor: pointer;
}
.navbar {
height: 113px;
background: #fff;

View File

@@ -24,8 +24,8 @@
<el-form-item label="地址别名">
<el-input
v-model="formData.alias"
length
:maxlength="4"
:maxlength="5"
show-word-limit
placeholder="请输入地址别名,例如公司"
style="width: 600px"
></el-input>

View File

@@ -19,6 +19,7 @@
<div
class="item-detail-img-small"
:class="{ 'is-active': imgIndex === index }"
@mouseover="imgIndex = index"
v-for="(item, index) in imgList"
:key="index"
@@ -84,7 +85,7 @@
>
<p>
<span class="item-price-title" v-if="promotionMap['SECKILL']"
>秒 &nbsp;杀&nbsp;价</span>
>秒杀价</span>
<span class="item-price">{{ $filters.unitPrice(skuDetail.promotionPrice, "¥") }}</span>
<span class="item-price-old">{{ $filters.unitPrice(skuDetail.price, "¥") }}</span>
</p>
@@ -94,9 +95,7 @@
<!-- 批发价格 -->
<div v-if="!isECouponGoods && wholesaleNum && wholesaleNum.length">
<div class="flex">
<div class="item-price-title">
价 &nbsp;&nbsp;&nbsp;&nbsp;格
</div>
<div class="item-price-title">价格</div>
<div
v-for="(item, index) in wholesalePrice"
@@ -107,7 +106,7 @@
</div>
</div>
<div class="flex">
<div class="item-price-title">起 批 量</div>
<div class="item-price-title">起批量</div>
<div
v-for="(item, index) in wholesaleNum"
:key="index"
@@ -120,15 +119,14 @@
<!-- 普通价格 -->
<div v-else>
<span class="item-price-title"
>价 &nbsp;&nbsp;&nbsp;&nbsp;格</span>
<span class="item-price-title">价格</span>
<span class="item-price">{{ $filters.unitPrice(skuDetail.price, "¥") }}</span>
</div>
</div>
<!-- 优惠券展示 -->
<div class="item-price-coupon-row" v-if="promotionMap['COUPON'].length">
<div class="Ellipsis">
<span class="item-price-title">优 惠 券</span>
<span class="item-price-title">优惠券</span>
<span>
<span
class="item-coupon"
@@ -168,7 +166,7 @@
<!-- 限时折扣 -->
<div class="item-price-row" v-if="promotionMap['DISCOUNT']">
<p>
<span class="item-price-title">促&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;销</span>
<span class="item-price-title">促销</span>
<span class="item-promotion">限时折扣</span>
<span class="item-desc-pintuan" v-if="promotionMap['DISCOUNT'].discount">
享 {{ promotionMap["DISCOUNT"].discount }} 折
@@ -178,7 +176,7 @@
<!-- 第N件优惠 -->
<div class="item-price-row" v-if="promotionMap['NTH']">
<p>
<span class="item-price-title">促&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;销</span>
<span class="item-price-title">促销</span>
<span class="item-promotion">第N件</span>
<span class="item-desc-pintuan">
第 {{ promotionMap["NTH"].nthNum || promotionMap["NTH"].num }} 件优惠
@@ -188,7 +186,7 @@
<!-- 拼团 -->
<div class="item-price-row" v-if="promotionMap['PINTUAN']">
<p>
<span class="item-price-title">拼&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;团</span>
<span class="item-price-title">拼团</span>
<span class="item-promotion">拼团价</span>
<span class="item-desc-pintuan" v-if="promotionMap['PINTUAN'].price">
{{ $filters.unitPrice(promotionMap["PINTUAN"].price, "¥") }}
@@ -198,8 +196,7 @@
<!-- 满减展示 -->
<div class="item-price-row" v-if="promotionMap['FULL_DISCOUNT']">
<p>
<span class="item-price-title"
>促&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;销</span>
<span class="item-price-title">促销</span>
<span class="item-promotion">满减</span>
<span
class="item-desc-pintuan"
@@ -300,7 +297,7 @@
<div class="item-select-column">
<div class="add-buy-car">
<el-button
type="danger"
class="goods-action-btn is-solid"
:loading="loading"
:disabled="skuDetail.quantity === 0"
@click="pointPay"
@@ -324,7 +321,7 @@
>加入购物车</el-button
>
<el-button
class="goods-action-btn"
class="goods-action-btn is-solid"
v-if="promotionMap['PINTUAN']"
:loading="loadingPintuan"
:disabled="isOutOfStock"
@@ -332,7 +329,7 @@
>拼团购买</el-button
>
<el-button
class="goods-action-btn"
class="goods-action-btn is-solid"
:loading="loading1"
:disabled="isOutOfStock"
@click="buyNow"
@@ -778,23 +775,28 @@ export default {
.item-detail-show {
width: 1200px;
margin: 0 auto;
padding: 30px;
padding: 24px;
display: flex;
flex-direction: row;
background: #fff;
border-radius: 4px;
box-sizing: border-box;
}
.item-detail-left {
width: 350px;
margin-right: 30px;
margin-right: 32px;
}
.item-detail-big-img {
width: 350px;
height: 350px;
box-shadow: 0px 0px 8px $border_color;
border: 1px solid #eee;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
margin-bottom: 24px;
margin-bottom: 12px;
background: #fff;
}
#dplayer{
width: 100%;
@@ -807,14 +809,21 @@ export default {
.item-detail-img-row {
margin-top: 0;
display: flex;
gap: 8px;
}
.item-detail-img-small {
width: 68px;
height: 68px;
box-shadow: 0px 0px 8px #ccc;
width: 64px;
height: 64px;
border: 1px solid #eee;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
margin-left: 5px;
box-sizing: border-box;
&.is-active {
border-color: $theme_color;
}
}
.item-detail-img-small img {
@@ -838,14 +847,17 @@ export default {
}
.item-detail-name {
@include content_color($light_content_color);
font-weight: bold;
color: #171A26;
font-weight: 600;
font-size: 20px;
line-height: 1.4;
line-height: 28px;
}
.goods-type-tag {
flex-shrink: 0;
--el-tag-bg-color: #fafafa;
--el-tag-border-color: #eee;
--el-tag-text-color: #666;
}
.item-detail-express {
@@ -859,17 +871,18 @@ export default {
/*商品标签*/
.item-detail-tag {
padding: 8px 0;
font-size: 12px;
font-size: 14px;
color: $theme_color;
}
/*价格详情等*/
.item-detail-price-row {
padding: 10px;
padding: 16px 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
background: url("../../assets/images/goodsDetail/price-bg.png");
background: #fafafa;
border-radius: 4px;
}
.item-price-left {
@@ -878,19 +891,30 @@ export default {
}
.item-price-title {
color: #999999;
display: inline-block;
width: 56px;
margin-right: 12px;
color: #666;
font-size: 14px;
margin-right: 15px;
text-align: left;
}
.item-price-row {
margin: 5px 0px;
margin: 4px 0;
> div,
> p {
display: flex;
align-items: center;
margin: 0;
}
}
.item-price {
color: $theme_color;
font-size: 20px;
cursor: pointer;
font-size: 28px;
font-weight: 600;
line-height: 1;
}
.item-price-old {
color: gray;
@@ -925,7 +949,7 @@ export default {
align-items: center;
color: $theme_color;
margin: 5px 0;
font-size: 12px;
font-size: 14px;
background-color: var(--theme-tint-12, #ffdedf);
border: 1px dotted $theme_color;
cursor: pointer;
@@ -950,7 +974,7 @@ export default {
margin-right: 5px;
padding: 0 5px;
color: $theme_color;
font-size: 12px;
font-size: 14px;
background-color: var(--theme-tint-12, #ffdedf);
border: 1px dotted $theme_color;
cursor: pointer;
@@ -963,7 +987,7 @@ export default {
margin-right: 5px;
padding: 3px;
color: $theme_color;
font-size: 12px;
font-size: 14px;
border: 1px solid $theme_color;
}
@@ -974,22 +998,25 @@ export default {
}
.item-remarks-sum {
width: 70px;
min-width: 72px;
text-align: center;
padding: 0 10px;
border-left: 1px solid $border_color;
padding: 0 4px 0 16px;
border-left: 1px solid #eee;
}
.item-remarks-sum p {
color: #999999;
font-size: 12px;
line-height: 10px;
color: #999;
font-size: 13px;
line-height: 20px;
text-align: center;
margin: 0;
}
.item-remarks-num {
line-height: 18px;
color: #005eb7;
line-height: 24px;
color: #171A26;
font-size: 16px;
font-weight: 600;
}
.item-select {
@@ -1000,10 +1027,10 @@ export default {
}
.item-select-title {
@include content_color($light_content_color);
color: #666;
font-size: 14px;
margin-right: 15px;
width: 60px;
margin-right: 16px;
width: 56px;
flex-shrink: 0;
display: flex;
align-items: center;
@@ -1033,32 +1060,39 @@ export default {
}
.item-select-box {
padding: 5px;
min-height: 36px;
padding: 0 16px;
margin-right: 8px;
@include background_color($light_background_color);
border: 0.5px solid $border_color;
background: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
cursor: pointer;
@include content_color($light_content_color);
color: #171A26;
box-sizing: border-box;
}
.item-select-box:hover {
border: 0.5px solid $theme_color;
border-color: $theme_color;
color: $theme_color;
}
.item-select-box-active {
border: 0.5px solid $theme_color;
border-color: $theme_color;
background: #fff5f4;
color: $theme_color;
}
.item-select-intro p {
margin: 0px;
padding: 5px;
margin: 0;
padding: 8px 0;
font-size: 14px;
line-height: 20px;
}
.add-buy-car-box {
width: 100%;
margin-top: 15px;
border-top: 1px dotted $border_color;
margin-top: 8px;
border-top: 1px solid #f0f0f0;
}
.e-coupon-after-sale-notice {
@@ -1072,7 +1106,7 @@ export default {
}
:deep(.el-alert__title) {
font-size: 12px;
font-size: 14px;
line-height: 1.5;
}
}
@@ -1107,23 +1141,36 @@ export default {
}
.goods-action-btn {
background-color: #ff475d !important;
border-color: #ff475d !important;
color: #fff !important;
height: 40px;
min-width: 140px;
padding: 0 24px;
border-radius: 4px;
font-size: 16px;
background: #fff !important;
border: 1px solid $theme_color !important;
color: $theme_color !important;
&:hover,
&:focus {
background-color: #e63e52 !important;
border-color: #e63e52 !important;
background: #fff5f4 !important;
border-color: $theme_color !important;
color: $theme_color !important;
}
&.is-solid {
background: $theme_color !important;
color: #fff !important;
&:hover,
&:focus {
background: $theme_color !important;
color: #fff !important;
}
}
&.is-disabled,
&.is-disabled:hover,
&.is-disabled:focus {
background-color: #ff475d !important;
border-color: #ff475d !important;
color: #fff !important;
opacity: 0.5;
}
}
@@ -1160,9 +1207,16 @@ export default {
}
}
.sell-point {
font-size: 12px;
color: red;
margin-bottom: 5px;
font-size: 14px;
line-height: 22px;
color: #999;
margin-bottom: 12px;
}
.inventory {
padding-left: 8px;
color: #999;
font-size: 13px;
}
.view-video{
cursor: pointer;

File diff suppressed because it is too large Load Diff

View File

@@ -238,8 +238,8 @@ export default {
.box {
width: 100%;
font-size: 12px !important;
height: 36px;
font-size: 14px;
height: 40px;
background: #333;
color: #fff;
display: flex;
@@ -266,7 +266,7 @@ export default {
.location > ul.flex > li {
cursor: pointer;
float: none;
line-height: 36px;
line-height: 40px;
margin-right: 15px;
display: flex;
align-items: center;
@@ -274,7 +274,7 @@ export default {
.nav a,
.nav-item {
font-size: 13px;
font-size: 14px;
font-weight: normal;
text-decoration: none;
cursor: pointer;
@@ -297,7 +297,7 @@ export default {
margin: 0;
padding: 0;
vertical-align: middle;
line-height: 36px;
line-height: 40px;
}
:deep(.el-tooltip__trigger),
@@ -429,7 +429,7 @@ export default {
justify-content: space-between;
margin-top: 8px;
color: #999;
font-size: 12px;
font-size: 14px;
}
.shopping-price {
@@ -477,7 +477,7 @@ export default {
padding: 8px 16px;
line-height: 1.4;
color: #666;
font-size: 13px;
font-size: 14px;
font-weight: normal;
text-align: center;
white-space: nowrap;
@@ -556,7 +556,7 @@ export default {
}
.my-info p {
font-size: 12px;
font-size: 14px;
}
.sign-out {
@@ -576,7 +576,7 @@ export default {
}
.sign-out p {
font-size: 12px;
font-size: 14px;
}
.goods-title:hover {

View File

@@ -1,8 +1,8 @@
<template>
<el-dialog width="800" v-model="enableMap">
<el-radio-group @change="changeMap" v-model="mapDefault" type="button">
<el-radio label="select">级联选择</el-radio>
<el-radio label="map" v-if="aMapSwitch">高德地图</el-radio>
<el-radio-group v-if="aMapSwitch" @change="changeMap" v-model="mapDefault">
<el-radio label="select">地区选择</el-radio>
<el-radio label="map">高德地图</el-radio>
</el-radio-group>
<div>
<div v-if="mapDefault === 'select'">

View File

@@ -26,31 +26,22 @@
:style="{'top':!showNavBar ?'60px':'46px'}"
v-show="showAlways || showFirstList"
@mouseenter="showFirstList = true"
@mouseleave="showFirstList = false"
@mouseleave="closePanel"
>
<!-- 第一级分类 -->
<div
class="nav-side"
:class="{ 'large-nav': large, 'opacity-nav': opacity }"
@mouseleave="panel = false"
>
<ul>
<li
v-for="(item, index) in cateList"
:key="index"
:class="{ 'is-active': panel && panelIndex === index }"
@mouseenter="showDetail(index)"
>
<span class="nav-side-item" @click="goGoodsList(item.id)">{{
item.name
}}</span>
<span v-for="(second, secIndex) in item.children" :key="secIndex">
<span v-if="secIndex < 2"> / </span>
<span
@click="goGoodsList(second.id, second.parentId)"
class="nav-side-item"
v-if="secIndex < 2"
>{{ second.name }}</span>
</span>
<span class="nav-side-item" @click="goGoodsList(item.id)">{{ item.name }}</span>
<el-icon><ArrowRight /></el-icon>
</li>
</ul>
</div>
@@ -139,6 +130,7 @@ export default {
data() {
return {
panel: false, // 二级分类展示
panelIndex: -1,
panelData: [], // 二级分类数据
showFirstList: false, // 始终展示一级列表
cateList: [], // 商品分类
@@ -189,8 +181,14 @@ export default {
showDetail(index) {
// 展示全部分类
this.panel = true;
this.panelIndex = index;
this.panelData = this.cateList[index].children;
},
closePanel() {
this.showFirstList = false;
this.panel = false;
this.panelIndex = -1;
},
goGoodsList(id, secondId, firstId) {
// 分类共有三级,传全部分类过去
const arr = [firstId, secondId, id];
@@ -278,16 +276,15 @@ export default {
display: flex;
align-items: center;
height: 46px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding-left: 37.4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
padding-left: 16px;
background: #FFFFFF;
width: 263.2px;
border-bottom: none;
font-size: 16.8px;
font-weight: normal;
color: #333333;
letter-spacing: 0px;
font-size: 15px;
font-weight: 600;
color: #171A26;
box-sizing: border-box;
}
.nav-item {
@@ -304,9 +301,9 @@ export default {
display: flex;
li {
float: left;
font-size: 17px;
font-weight: normal;
color: #333333;
font-size: 15px;
font-weight: 400;
color: #171A26;
&:hover {
@@ -325,14 +322,13 @@ export default {
.nav-side {
width: 263.2px;
color: #666;
color: #171A26;
float: left;
overflow:hidden;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
overflow: hidden;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
background-color: #fff;
height: 340px;
height: 344px;
////自动移滚动条样式
//&::-webkit-scrollbar {
// width: 1px;
@@ -349,24 +345,53 @@ export default {
}
.large-nav {
height: 470px;
ul > li {
line-height: 20px;
}
}
.opacity-nav {
background-color: rgba(0, 0, 0, 0.5);
}
.nav-side ul {
width: 100%;
height: 100%;
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
}
.nav-side li {
padding: 0 0 16.2px 37.4px;
font-size: 13px;
line-height: 18px;
display: flex;
align-items: center;
justify-content: space-between;
flex: 1 1 0;
min-height: 0;
height: auto;
padding: 0 12px 0 16px;
font-size: 14px;
line-height: 20px;
box-sizing: border-box;
cursor: pointer;
.el-icon {
flex-shrink: 0;
color: #ccc;
font-size: 12px;
}
&:hover,
&.is-active {
background: #fff5f4;
color: $theme_color;
.el-icon {
color: $theme_color;
}
}
}
.nav-side-item {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nav-side-item:hover {
cursor: pointer;
@@ -375,63 +400,102 @@ export default {
/*显示商品详细信息*/
.detail-item-panel {
width: 1000px;
width: 936px;
min-height: 340px;
background-color: #fff;
box-shadow: 0px 0px 15px #ccc;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
border-radius: 0 4px 4px 0;
position: absolute;
top: 0;
left: 200px;
left: 263.2px;
z-index: 1000;
padding: 15px;
padding: 8px 20px 16px;
box-sizing: border-box;
}
.nav-detail-item {
margin-top: 5px;
margin-bottom: 15px;
display: flex;
flex-wrap: wrap;
gap: 4px 20px;
margin: 8px 0 4px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
color: #eee;
}
.nav-detail-item span {
padding: 6px;
padding-left: 12px;
margin-right: 15px;
font-size: 12px;
color: #333;
display: inline-flex;
align-items: center;
gap: 2px;
padding: 4px 0;
font-size: 14px;
font-weight: 600;
color: #171A26;
.el-icon {
font-size: 12px;
color: #ccc;
}
}
.nav-detail-item span:hover {
background-color: $theme_color;
color: #fff;
color: $theme_color;
.el-icon {
color: $theme_color;
}
}
.detail-item-panel li {
line-height: 30px;
line-height: 28px;
}
.detail-item-title {
font-weight: bold;
font-size: 12px;
display: inline-flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
width: 108px;
font-weight: 600;
font-size: 14px;
line-height: 28px;
cursor: pointer;
color: #555555;
padding-right: 10px;
width: 81px;
text-align: right;
color: #171A26;
text-align: left;
.glyphicon {
display: none;
}
.el-icon {
font-size: 12px;
color: #ccc;
}
}
.detail-item-title:hover {
color: $theme_color;
.el-icon {
color: $theme_color;
}
}
.detail-item-row {
display: flex;
align-items: flex-start;
padding: 6px 0;
> div {
flex: 1;
min-width: 0;
display: flex;
flex-wrap: wrap;
}
}
.detail-item {
font-size: 12px;
padding-left: 8px;
padding-right: 8px;
font-size: 14px;
line-height: 28px;
padding: 0 12px;
color: #666;
cursor: pointer;
border-left: 1px solid #ccc;
border: none;
&:first-child {
border: none;
padding-left: 0;
padding-left: 12px;
}
}
.detail-item:hover {

View File

@@ -29,9 +29,9 @@
</template>
<!-- 无商品分类,展示搜索结果 -->
<template v-else>
<div style="font-size:14px">全部结果</div>
<div class="result-label">全部结果</div>
<el-icon><ArrowRight /></el-icon>
<div style="font-weight:bold;" class="mr_10">“{{params.keyword}}”</div>
<div class="result-keyword">“{{params.keyword}}”</div>
</template>
<!-- 所选分类 -->
<a
@@ -50,43 +50,26 @@
<!-- 品牌, 有图片,独立出来 -->
<div class="brand" v-show="tagsContent[0].show && tagsContent[0].values.length">
<div>
<strong>{{ tagsContent[0].key }}:</strong>
{{ tagsContent[0].key }}:
</div>
<div>
<ul :class="{ 'show-more': tagsContent[0].more }">
<li :class="{ 'is-active': !isGroupSelected(0) }" @click="selectAll(0)">
<span>全部</span>
</li>
<li
@click="selectBrand(item.name, 0)"
:class="{ 'border-color': multSelected.includes(item) }"
v-for="(item, index) in tagsContent[0].values"
:key="index"
>
<img :src="item.url" alt="" /><span>{{ item.name }}</span>
<div
class="corner-icon"
v-show="multSelected.includes(item.name)"
>
<div></div>
<el-icon><Check /></el-icon>
</div>
<span>{{ item.name }}</span>
</li>
</ul>
<div class="btn" v-show="multiple !== 0">
<div class="btn" v-show="rowOverflow[0]">
<span @click="moreBrand(0)"
>{{ tagsContent[0].more ? "收起" : "更多"
}}<el-icon><ArrowUp v-if="tagsContent[0].more" /><ArrowDown v-else /></el-icon></span>
<span @click="multSelectBrand(0)"><el-icon><Plus /></el-icon>多选</span>
</div>
<div class="multBtn" v-show="multiple === 0">
<el-button
type="primary"
size="small"
:disabled="!multSelected.length"
@click="sure(0)"
>确定</el-button
>
<el-button size="small" @click="cancel">取消</el-button>
</div>
</div>
</div>
@@ -95,14 +78,19 @@
<template v-for="(tag, tagIndex) in tagsContent">
<div class="other" v-if="tag.show && tagIndex !== 0" v-show="tagIndex < showTagCount" :key="tagIndex">
<div>
<strong>{{ tag.key }}:</strong>
{{ tag.key }}:
</div>
<div>
<ul
:class="{ 'show-more': tag.more }"
:data-index="tagIndex"
class="list"
v-show="multiple !== tagIndex"
>
<li
class="item"
:class="{ 'is-active': !isGroupSelected(tagIndex) }"
@click="selectAll(tagIndex)"
>全部</li>
<li
@click="selectBrand(item, tagIndex)"
class="item"
@@ -113,38 +101,10 @@
</li>
</ul>
<el-checkbox-group
:class="{ 'show-more': tag.more }"
class="list"
v-model="multSelected"
v-show="multiple === tagIndex"
>
<el-checkbox
class="item"
:value="item"
v-for="(item, index) in tag.values"
:key="index"
>{{ item }}</el-checkbox
>
</el-checkbox-group>
<div class="btn" v-show="multiple !== tagIndex">
<span @click="moreBrand(tagIndex)" v-show="tag.values.length > 9"
<div class="btn" v-show="rowOverflow[tagIndex]">
<span @click="moreBrand(tagIndex)"
>{{ tag.more ? "收起" : "更多"
}}<el-icon><ArrowUp v-if="tag.more" /><ArrowDown v-else /></el-icon></span>
<span @click="multSelectBrand(tagIndex)"
><el-icon><Plus /></el-icon>多选</span>
</div>
<div class="multBtn" v-show="multiple === tagIndex">
<el-button
type="primary"
size="small"
:disabled="!multSelected.length"
@click="sure(tagIndex)"
>确定</el-button
>
<el-button size="small" @click="cancel">取消</el-button>
</div>
</div>
</div>
@@ -157,10 +117,10 @@
</template>
<script>
import { ArrowDown, ArrowRight, ArrowUp, Check, Close, Plus } from '@element-plus/icons-vue';
import { ArrowDown, ArrowRight, ArrowUp, Close } from '@element-plus/icons-vue';
import * as APIGoods from '@/api/goods';
export default {
components: { ArrowDown, ArrowRight, ArrowUp, Check, Close, Plus },
components: { ArrowDown, ArrowRight, ArrowUp, Close },
name: 'GoodsClassNav',
data () {
return {
@@ -171,7 +131,6 @@ export default {
second: null
},
showTagCount: 5, // 展示的搜索项数量
multiple: false, // 多选
tagsContent: [
// 标签
{
@@ -181,10 +140,10 @@ export default {
values: []
}
],
multSelected: [], // 多选分类
selectedItem: [], // 已选分类集合 顶部展示
brandIds: [], // 品牌id合集
params: {} // 请求参数
params: {}, // 请求参数
rowOverflow: {} // 一行排不下时才显示更多
};
},
computed: {
@@ -280,33 +239,30 @@ export default {
break;
}
},
isGroupSelected (index) {
const group = this.tagsContent[index];
if (!group) return false;
return this.selectedItem.some((selected) => selected.type === group.key);
},
selectAll (index) {
const group = this.tagsContent[index];
if (!group) return;
const selectedIndex = this.selectedItem.findIndex((selected) => selected.type === group.key);
if (selectedIndex === -1) return;
this.cancelSelected(this.selectedItem[selectedIndex], selectedIndex);
},
selectBrand (item, index) {
// 选择筛选项
if (this.multiple !== false) {
// 非多选直接在顶部栏展示,多选则添加选择状态
let key = this.multSelected.indexOf(item);
if (key > -1) {
this.multSelected.splice(key, 1);
} else {
this.multSelected.push(item);
}
} else {
const type = this.tagsContent[index].key;
if (this.selectedItem.some((selected) => selected.type === type)) {
return;
}
this.selectedItem.push({
type,
name: item
});
this.tagsContent[index].show = false;
if (index === 0) {
// 如果是品牌,获取品牌id
this.appendBrandIdsByNames([item]);
}
const type = this.tagsContent[index].key;
if (this.selectedItem.some((selected) => selected.type === type)) {
return;
}
this.selectedItem.push({
type,
name: item
});
this.tagsContent[index].show = false;
if (index === 0) {
this.appendBrandIdsByNames([item]);
}
},
@@ -328,37 +284,25 @@ export default {
// 更多按钮
const flag = !this.tagsContent[index].more
this.tagsContent[index]['more'] = flag
this.$nextTick(() => this.measureRows())
},
multSelectBrand (index) {
// 多选按钮
this.tagsContent[index]['more'] = true
this.multiple = index;
},
sure (index) {
// 多选确认按钮
const type = this.tagsContent[index].key;
if (this.selectedItem.some((selected) => selected.type === type)) {
this.cancel();
return;
measureRows () {
if (!this.$el) return
const next = {}
const brand = this.$el.querySelector('.brand ul')
if (brand && brand.offsetParent) {
next[0] = !!(this.tagsContent[0] && this.tagsContent[0].more) || brand.scrollHeight > brand.clientHeight + 1
}
this.selectedItem.push({
type,
name: this.multSelected.join('、')
});
if (index === 0) {
// 如果是品牌,获取品牌id
this.appendBrandIdsByNames(this.multSelected);
}
this.tagsContent[index].show = false;
this.cancel();
},
cancel () {
// 多选取消按钮
this.multSelected = [];
this.tagsContent[0].more = false;
this.multiple = false;
this.$el.querySelectorAll('.other .list').forEach((list) => {
if (!list.offsetParent) return
const index = Number(list.dataset.index)
const tag = this.tagsContent[index]
next[index] = !!(tag && tag.more) || list.scrollHeight > list.clientHeight + 1
})
const prev = this.rowOverflow
const keys = new Set([...Object.keys(prev), ...Object.keys(next)])
const changed = [...keys].some((key) => !!prev[key] !== !!next[key])
if (changed) this.rowOverflow = next
},
appendBrandIdsByNames (names) {
const selectedNames = names || [];
@@ -385,12 +329,14 @@ export default {
item.show = !hiddenKeys.has(item.key);
item.more = false;
});
this.$nextTick(() => this.measureRows())
}
});
},
// 展示更多搜索项
moreOptions () {
this.showTagCount = this.showTagCount === 5 ? 100 : 5
this.$nextTick(() => this.measureRows())
}
},
mounted () {
@@ -404,6 +350,10 @@ export default {
}
this.getFilterList(this.params);
this.getNav();
window.addEventListener('resize', this.measureRows)
},
beforeUnmount () {
window.removeEventListener('resize', this.measureRows)
}
}
</script>
@@ -413,70 +363,100 @@ export default {
.head-bar {
width: 100%;
background: #fff;
margin-top: -13px;
display: flex;
height: 40px;
flex-wrap: wrap;
gap: 8px;
min-height: 48px;
align-items: center;
padding: 8px 16px;
border: 1px solid #eee;
border-bottom: none;
border-radius: 4px 4px 0 0;
color: #666;
font-size: 14px;
box-sizing: border-box;
> div:first-child {
padding: 0 8px;
font-size: 18px;
font-weight: bold;
padding: 0;
font-size: 14px;
font-weight: 400;
color: #666;
&:hover {
color: $theme_color;
cursor: pointer;
}
}
.bar {
.result-label {
color: #666;
}
.result-keyword {
margin-left: 4px;
color: #171A26;
font-size: 15px;
font-weight: 600;
}
> .el-icon {
color: #ccc;
font-size: 12px;
}
.bar {
position: relative;
display: inline-flex;
align-items: center;
gap: 4px;
height: 28px;
margin: 0 4px;
padding: 0 10px;
border: 1px solid #e5e5e5;
border-radius: 4px;
background: #fff;
border: 1px solid #999;
padding: 0 8px;
min-width: 85px;
text-align: center;
margin: 0 3px;
color: #171A26;
font-size: 14px;
line-height: 26px;
&:hover {
color: $theme_color;
border-color: $theme_color;
border-bottom-color: #fff;
color: $theme_color;
cursor: pointer;
ul {
display: block;
display: flex;
}
.el-icon {
> .el-icon {
transform: rotate(180deg);
}
}
ul {
display: none;
position: absolute;
top: 18px;
left: -1px;
width: 300px;
padding: 5px 10px;
top: calc(100% + 6px);
left: 0;
z-index: 5;
width: 280px;
margin: 0;
padding: 8px;
flex-wrap: wrap;
list-style: none;
background: #fff;
border: 1px solid $theme_color;
z-index: 1;
&::before {
content: "";
position: absolute;
width: 83px;
left: 0;
top: -1px;
z-index: 2;
border-top: 1px solid #fff;
}
border: 1px solid #eee;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
&:hover {
display: block;
}
clear: left;
li {
color: #999;
float: left;
width: 30%;
margin: 3px 0;
width: 33.33%;
padding: 6px 8px;
box-sizing: border-box;
color: #171A26;
font-size: 14px;
line-height: 20px;
text-align: left;
&:hover {
color: $theme_color;
cursor: pointer;
@@ -486,252 +466,257 @@ export default {
}
//所选分类
.selected-item {
font-size: 12px;
color: #000;
padding: 2px 22px 2px 8px;
margin-right: 5px;
max-width: 250px;
height: 24px;
overflow: hidden;
position: relative;
background-color: #f3f3f3;
border: 1px solid #ddd;
&:hover {
border-color: $theme_color;
background-color: #fff;
.el-icon {
color: #fff;
background-color: $theme_color;
}
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 240px;
height: 28px;
margin: 0;
padding: 0 8px;
border: 1px solid #ffd0cb;
border-radius: 4px;
background: #fff5f4;
color: #666;
font-size: 13px;
line-height: 26px;
text-decoration: none;
box-sizing: border-box;
cursor: pointer;
span:first-child {
flex-shrink: 0;
color: #666;
}
span:nth-child(2) {
min-width: 0;
overflow: hidden;
color: $theme_color;
text-overflow: ellipsis;
white-space: nowrap;
}
.el-icon {
position: absolute;
right: 0;
top: 0;
color: $theme_color;
line-height: 22px;
width: 21px;
height: 22px;
font-size: 14px;
position: static;
flex-shrink: 0;
width: 14px;
height: 14px;
color: #999;
font-size: 12px;
line-height: 1;
background: transparent;
}
&:hover {
border-color: $theme_color;
background: #fff5f4;
.el-icon {
color: $theme_color;
background: transparent;
}
}
}
}
/** 筛选主体 */
.content {
background: #fff;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 10px 0;
border: 1px solid #eee;
border-top: none;
margin: 0 0 12px;
border-radius: 0 0 4px 4px;
}
/** 品牌 start */
.brand {
border-bottom: 1px solid #ddd;
border-bottom: 1px solid #f0f0f0;
display: flex;
// min-height: 120px;
font-size: 12px;
min-height: 44px;
font-size: 14px;
> div:first-child {
width: 100px;
background: #eee;
padding: 10px 0 0 10px;
width: 88px;
background: transparent;
padding-left: 16px;
line-height: 44px;
color: #666;
font-weight: 400;
flex-shrink: 0;
}
> div:last-child {
width: 1100px;
padding: 10px;
flex: 1;
min-width: 0;
width: auto;
padding: 0 16px;
position: relative;
ul {
width: 900px;
max-height: 100px;
display: flex;
flex-wrap: wrap;
align-items: center;
width: calc(100% - 72px);
height: 44px;
margin: 0;
padding: 0;
overflow: hidden;
padding-top: 1px;
clear: left;
list-style: none;
li {
width: 100px;
height: 50px;
float: left;
line-height: 45px;
border: 1px solid #ddd;
margin: -1px -1px 0 0;
overflow: hidden;
position: relative;
padding: 2px;
img {
width: 100%;
height: 100%;
flex: 0 0 auto;
width: auto;
max-width: none;
height: 44px;
margin: 0 40px 0 0;
line-height: 44px;
overflow: visible;
padding: 0;
cursor: pointer;
&:hover span {
color: $theme_color;
}
&:hover {
border-color: $theme_color;
border: 2px solid $theme_color;
top: 0;
left: 0;
position: relative;
z-index: 1;
img {
display: none;
}
&.is-active span {
color: $theme_color;
font-weight: 400;
}
span {
display: inline-block;
width: 100%;
height: 100%;
color: $theme_color;
text-align: center;
font-size: 12px;
display: block;
overflow: visible;
color: #171A26;
text-align: left;
font-size: 14px;
font-weight: 400;
line-height: 44px;
white-space: nowrap;
text-overflow: clip;
cursor: pointer;
}
.corner-icon {
position: absolute;
right: -1px;
bottom: -1px;
div {
width: 0;
border-top: 20px solid transparent;
border-right: 20px solid $theme_color;
}
.el-icon {
font-size: 12px;
position: absolute;
bottom: 0;
right: 1px;
transform: rotate(-15deg);
color: #fff;
}
}
}
.border-color {
border-color: $theme_color;
z-index: 1;
}
}
.show-more {
height: auto;
max-height: 200px;
overflow: scroll;
max-height: none;
overflow: visible;
}
.btn {
position: absolute;
right: 10px;
top: 10px;
right: 16px;
top: 8px;
span {
border: 1px solid #ddd;
margin-left: 10px;
color: #999;
display: inline-block;
padding: 1px 3px;
font-size: 12px;
margin-left: 12px;
color: #438cde;
display: inline-flex;
align-items: center;
gap: 2px;
padding: 0;
border: none;
font-size: 14px;
line-height: 28px;
&:hover {
cursor: pointer;
color: $theme_color;
border-color: $theme_color;
}
}
}
.multBtn {
text-align: center;
margin-top: 10px;
.el-button {
font-size: 12px !important;
}
.el-button:last-child {
margin-left: 10px;
}
}
}
}
/** 品牌 end */
/** 其他筛选项 start */
.other {
border-bottom: 1px solid #ddd;
border-bottom: 1px solid #f0f0f0;
display: flex;
min-height: 30px;
font-size: 12px;
min-height: 44px;
font-size: 14px;
&:last-child {
border: none;
}
> div:first-child {
width: 100px;
background: #eee;
padding-left: 10px;
line-height: 30px;
width: 88px;
background: transparent;
padding-left: 16px;
line-height: 44px;
color: #666;
font-weight: 400;
flex-shrink: 0;
}
> div:last-child {
width: 1100px;
padding: 0 10px;
flex: 1;
min-width: 0;
width: auto;
padding: 0 16px;
position: relative;
.list {
width: 900px;
height: 30px;
display: flex;
flex-wrap: wrap;
align-items: center;
width: calc(100% - 72px);
height: 44px;
margin: 0;
padding: 0;
overflow: hidden;
clear: left;
list-style: none;
.item {
width: 100px;
height: 30px;
float: left;
line-height: 30px;
color:#4d9cf1;
overflow: hidden;
position: relative;
font-size: 12px;
padding: 2px;
flex: 0 0 auto;
width: auto;
max-width: none;
height: 44px;
margin: 0 40px 0 0;
line-height: 44px;
color: #171A26;
overflow: visible;
white-space: nowrap;
text-overflow: clip;
font-size: 14px;
font-weight: 400;
padding: 0;
cursor: pointer;
&:hover {
color: $theme_color;
}
&.is-active {
color: $theme_color;
font-weight: 400;
}
}
}
.show-more {
height: auto;
max-height: none;
overflow: visible;
}
.btn {
position: absolute;
right: 10px;
top: 5px;
right: 16px;
top: 8px;
span {
border: 1px solid #ddd;
margin-left: 10px;
color: #999;
display: inline-block;
padding: 1px 3px;
font-size: 12px;
margin-left: 12px;
color: #438cde;
display: inline-flex;
align-items: center;
gap: 2px;
padding: 0;
border: none;
font-size: 14px;
line-height: 28px;
&:hover {
cursor: pointer;
color: $theme_color;
border-color: $theme_color;
}
}
}
.multBtn {
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
.el-button {
font-size: 12px !important;
}
.el-button:last-child {
margin-left: 10px;
}
}
}
}
.more-options{
margin: 5px;
color: #4d9cf1;
font-size: 12px;
margin: 0;
padding: 10px 16px;
color: #438cde;
font-size: 14px;
cursor: pointer;
text-align: right;
}
.more-options:hover{
color:#0165d1;
color: $theme_color;
}
/** 其他筛选项 end */
</style>

View File

@@ -1,8 +1,8 @@
<template>
<div class="user-center-page">
<div class="user-center-title">{{ title }}</div>
<div v-if="title" class="user-center-title">{{ title }}</div>
<div class="user-center-panel">
<div v-if="showToolbar" class="user-center-toolbar">
<div v-if="showToolbar" class="user-center-toolbar" :class="{ 'is-actions-only': !tabs.length }">
<div class="user-center-tabs">
<div
v-for="(item, index) in tabs"
@@ -33,7 +33,7 @@ export default {
props: {
title: {
type: String,
required: true,
default: "",
},
tabs: {
type: Array,
@@ -101,21 +101,35 @@ export default {
justify-content: space-between;
min-height: 54px;
border-bottom: 1px solid #eee;
gap: 16px;
}
.user-center-toolbar.is-actions-only {
justify-content: flex-start;
.user-center-tabs {
display: none;
}
}
.user-center-tabs {
display: flex;
align-items: center;
height: 54px;
flex: 1;
min-width: 0;
overflow: hidden;
}
.user-center-tab {
position: relative;
flex-shrink: 0;
height: 54px;
line-height: 54px;
margin-right: 28px;
color: #333;
cursor: pointer;
white-space: nowrap;
&:hover,
&.active {
@@ -127,7 +141,7 @@ export default {
position: absolute;
left: 0;
right: 0;
bottom: -1px;
bottom: 0;
height: 2px;
background: $theme_color;
}
@@ -136,6 +150,7 @@ export default {
.user-center-extra {
display: flex;
align-items: center;
flex-shrink: 0;
}
.user-center-content {

View File

@@ -9,20 +9,17 @@
购物车(<span>{{ goodsTotal }}</span>)
</div>
</div>
<div class="cart-steps">
<span :class="stepIndex == 0 ? 'active' : ''">1.我的购物车</span>
<i class="icomoon icon-next" :class="stepIndex == 0 ? 'active-arrow' : ''"></i>
<span :class="stepIndex == 1 ? 'active' : ''">2.填写订单信息</span>
<i class="icomoon icon-next" :class="stepIndex == 1 ? 'active-arrow' : ''"></i>
<span :class="stepIndex == 2 ? 'active' : ''">3.成功提交订单</span>
</div>
</div>
<el-divider />
<!-- 购物车主体 -->
<div class="cart-content width_1200 center">
<div class="available-area">
<div class="cart-steps">
<span :class="stepIndex == 0 ? 'active' : ''">1.我的购物车</span>
<i class="icomoon icon-next" :class="stepIndex == 0 ? 'active-arrow' : ''"></i>
<span :class="stepIndex == 1 ? 'active' : ''">2.填写订单信息</span>
<i class="icomoon icon-next" :class="stepIndex == 1 ? 'active-arrow' : ''"></i>
<span :class="stepIndex == 2 ? 'active' : ''">3.成功提交订单</span>
</div>
</div>
<!-- 购物车商品列表 -->
<div class="cart-goods">
<div class="cart-goods-table">
@@ -168,7 +165,7 @@
v-model="goods.num"
@change="changeNum(goods.num, goods.goodsSku.id)"
></el-input-number>
<div class="fontsize_12 cart-qty-stock">
<div class="cart-qty-stock">
{{ goods.goodsSku.quantity > 0 ? "有货" : "无货" }}
</div>
</div>
@@ -538,7 +535,7 @@ export default {
}
}
.cart-content {
margin: 0 auto;
margin: 20px auto;
width: 1200px;
position: relative;
}
@@ -548,7 +545,8 @@ export default {
height: 30px;
display: flex;
align-items: center;
margin-bottom: 15px;
gap: 12px;
span {
@include content_color($light_content_color);
height: 30px;
@@ -556,45 +554,40 @@ export default {
line-height: 30px;
display: inline-block;
padding: 0 15px;
flex-shrink: 0;
}
.el-icon {
@include content_color($light_content_color);
font-size: 20px;
margin: 0 15px;
.icon-next {
flex-shrink: 0;
font-size: 14px;
}
.active {
border-radius: 50px;
background-color: #ff8f23;
background-color: $theme_color;
color: #fff;
}
.active-arrow {
color: #ff8f23;
color: $theme_color;
}
}
/** 步骤条和配送区域总体 */
.available-area {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
}
/** 商品列表 */
.cart-goods {
.cart-goods-table {
border: 1px solid #e0e0e0;
@include background_color($light_white_background_color);
border: 1px solid #eee;
background: #fff;
padding: 0 8px;
}
&-title {
height: 50px;
@include background_color($light_white_background_color);
@include title_color($title_color);
height: 44px;
background: #fff;
color: #999;
font-size: 13px;
display: flex;
align-items: center;
padding: 0 20px;
border-bottom: 1px solid #e0e0e0;
padding: 0 12px;
border-bottom: 1px solid #f0f0f0;
div:not(.cart-col-check) {
text-align: center;
}
@@ -629,10 +622,14 @@ export default {
border-bottom: 1px solid #e0e0e0;
}
.shop-name {
height: 50px;
min-height: 48px;
height: auto;
display: flex;
align-items: center;
padding: 0 20px;
padding: 8px 12px;
color: #171A26;
font-size: 15px;
font-weight: 600;
position: relative;
@include title_color($light_title_color);
.shop-info {
@@ -693,14 +690,14 @@ export default {
border-radius: 3px;
}
span:nth-child(2) {
font-size: 12px;
font-size: 14px;
margin-left: 5px;
color: #999;
}
.coupon-btn {
height: 26px;
height: 28px;
float: right;
font-size: 12px;
font-size: 14px;
}
&::after {
display: block;
@@ -712,7 +709,9 @@ export default {
}
.promotion-notice {
text-align: right;
font-size: 12px;
font-size: 13px;
color: #999;
font-weight: 400;
flex-shrink: 0;
margin-left: 10px;
}
@@ -725,7 +724,7 @@ export default {
@extend .cart-goods-title;
background: transparent;
border-top: 1px solid #f0f0f0;
padding: 10px 20px;
padding: 16px 12px 28px;
height: auto;
.cart-col-check {
padding-left: 20px;
@@ -741,14 +740,19 @@ export default {
padding-left: 20px;
position: relative;
img {
width: 70px;
height: 70px;
width: 72px;
height: 72px;
border-radius: 4px;
object-fit: cover;
background: #f5f5f5;
flex-shrink: 0;
}
> div > p {
@include content_color($light_content_color);
font-size: 13px;
color: #171A26;
font-size: 15px;
line-height: 22px;
text-align: left;
margin-left: 10px;
margin-left: 12px;
&:hover {
color: $theme_color;
}
@@ -761,9 +765,12 @@ export default {
justify-content: center;
flex-shrink: 0;
text-align: center;
color: #171A26;
font-size: 15px;
}
.cart-col-subtotal {
font-weight: bold;
color: $theme_color;
font-weight: 600;
}
.cart-col-qty {
position: relative;
@@ -777,6 +784,7 @@ export default {
}
.cart-qty-stock {
font-size: 14px;
position: absolute;
top: calc(100% + 4px);
left: 50%;
@@ -784,6 +792,7 @@ export default {
white-space: nowrap;
line-height: 1.2;
color: #999;
font-size: 13px;
}
}
.num-input {
@@ -813,11 +822,14 @@ export default {
@extend .cart-goods-title;
position: sticky;
bottom: 0;
border-top: 1px solid #ddd;
margin-top: 10px;
padding: 0 0 0 20px;
line-height: 50px;
height: 64px;
border-top: 1px solid #f0f0f0;
margin-top: 0;
padding: 0 0 0 12px;
line-height: 64px;
justify-content: space-between;
color: #171A26;
font-size: 14px;
> div {
display: flex;
}
@@ -832,21 +844,26 @@ export default {
}
.total-price div {
color: $theme_color;
font-size: 20px;
font-size: 22px;
font-weight: 600;
}
.pay {
background-color: $theme_color;
width: 150px;
font-size: 20px;
width: 160px;
font-size: 16px;
color: #fff;
height: 100%;
line-height: 50px;
line-height: 64px;
cursor: pointer;
&:hover {
opacity: 0.92;
}
}
}
.handle-btn {
font-size: 12px;
color: $handle-btn-color;
font-size: 14px;
color: #666;
cursor: pointer;
&:hover {
color: $theme_color;
@@ -881,12 +898,12 @@ export default {
> span {
border: 1px solid $theme_color;
color: $theme_color;
font-size: 12px;
font-size: 14px;
border-radius: 2px;
padding: 0 2px;
}
> p {
font-size: 12px;
font-size: 14px;
margin-left: 10px;
color: #999;
}
@@ -915,44 +932,25 @@ export default {
flex-wrap: nowrap;
}
.cart-del-btn {
background-color: #ff7d79 !important;
border-color: #ff7d79 !important;
color: #fff !important;
.cart-del-btn,
.cart-collect-btn {
height: auto !important;
margin: 0 !important;
padding: 0 4px !important;
border: none !important;
background: transparent !important;
color: #171A26 !important;
font-size: 14px !important;
&:hover,
&:focus {
background-color: #ff6b66 !important;
border-color: #ff6b66 !important;
color: #fff !important;
color: $theme_color !important;
background: transparent !important;
}
}
.cart-collect-btn {
background-color: #fa6419 !important;
border-color: #fa6419 !important;
color: #fff !important;
margin: 0 !important;
&:hover,
&:focus {
background-color: #e55a15 !important;
border-color: #e55a15 !important;
color: #fff !important;
}
&.is-collected {
background-color: $theme_color !important;
border-color: $theme_color !important;
&:hover,
&:focus {
background-color: $theme_color !important;
border-color: $theme_color !important;
opacity: 0.9;
}
}
.cart-collect-btn.is-collected {
color: $theme_color !important;
}
</style>
<style>

View File

@@ -1,5 +1,5 @@
<template>
<div style="background: #fff">
<div class="goods-detail-page">
<BaseHeader></BaseHeader>
<Search></Search>
<div class="shop-item-path">
@@ -178,7 +178,10 @@ export default {
arr.push(e.id);
}
});
return location.origin + "/goodsList?categoryId=" + arr.toString();
return {
path: "/goodsList",
query: { categoryId: arr.toString() },
};
},
async collect () {
// 收藏店铺
@@ -214,10 +217,14 @@ export default {
};
</script>
<style scoped lang="scss">
.goods-detail-page {
background: #f5f5f5;
}
.shop-item-path {
height: 38px;
@include background_color($light_background_color);
color: #2c2c2c;
height: 44px;
background: transparent;
color: #666;
}
.shop-nav-container {
@@ -230,51 +237,65 @@ export default {
:deep(.el-breadcrumb) {
line-height: 1;
font-size: 14px;
}
:deep(.el-breadcrumb__inner) {
color: #666;
font-weight: 400;
}
:deep(.el-breadcrumb__item:last-child .el-breadcrumb__inner) {
color: #171A26;
}
.store-collect {
position: absolute;
right: 20px;
right: 0;
top: 50%;
transform: translateY(-50%);
color: #999;
display: flex;
align-items: center;
gap: 16px;
color: #171A26;
font-size: 14px;
.store-collect-btn,
.contact-service-btn {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
color: #171A26;
span:not(.store-collect-btn):not(.contact-service-btn) {
&:hover {
cursor: pointer;
color: $theme_color;
}
}
.store-collect-btn,
.contact-service-btn {
cursor: pointer;
}
.store-collect-btn {
.store-collect-icon {
color: #666;
vertical-align: middle;
color: #999;
font-size: 16px;
}
&.is-collected,
&.is-collected .store-collect-icon {
color: $theme_color;
}
}
.store-name-link {
color: $theme_color;
color: #438cde;
text-decoration: none;
&:hover {
color: $theme_color;
text-decoration: none;
}
}
}
}
.sale-btn{
margin:10px 0
}
</style>

View File

@@ -63,12 +63,12 @@
<span>{{ item.goodsName }}</span>
</div>
<div class="goods-show-num">
已有<span>{{ item.commentNum || 0 }}</span>人评价
<span>{{ formatGrade(item.grade) }}%好评</span>
<span class="goods-show-sales">销量 {{ item.buyCount || 0 }}</span>
</div>
<div class="goods-show-seller">
<span
class="text-bottom hover-pointer"
style="color: #e4393c"
@click.stop="goShopPage(item.storeId)"
>{{ item.storeName }}</span>
</div>
@@ -80,25 +80,6 @@
>
自营
</el-tag>
<!-- E_COUPON 卡密商品类型标识 -->
<el-tag
class="goods-show-tag goods-show-tag-physical goods-show-tag-ecoupon"
v-if="item.goodsType === 'E_COUPON'"
>
电子卡券
</el-tag>
<el-tag
class="goods-show-tag goods-show-tag-physical"
v-else-if="item.goodsType === 'VIRTUAL_GOODS'"
>
虚拟
</el-tag>
<el-tag
class="goods-show-tag goods-show-tag-physical"
v-else-if="item.goodsType === 'PHYSICAL_GOODS'"
>
实物
</el-tag>
</div>
</div>
</div>
@@ -125,7 +106,6 @@ import { Message } from "@/utils/message";
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import GoodsClassNav from "@/components/nav/GoodsClassNav";
import * as apiGoods from "@/api/goods";
import { isBuyerLoggedIn, redirectToLogin } from "@/utils/buyerLogin";
export default {
name: "GoodsList",
beforeRouteEnter(to, from, next) {
@@ -201,15 +181,22 @@ export default {
}
this.getGoodsList();
},
formatGrade(grade) {
if (grade === null || grade === undefined || grade === "") {
return 100;
}
const num = Number(grade);
if (Number.isNaN(num)) {
return 100;
}
const fixed = Number(num.toFixed(1));
return fixed % 1 === 0 ? fixed.toFixed(0) : String(fixed);
},
goGoodsDetail(skuId, goodsId) {
const location = {
path: "/goodsDetail",
query: { skuId, goodsId },
};
if (!isBuyerLoggedIn()) {
redirectToLogin(this.$router, location);
return;
}
window.open(this.$router.resolve(location).href, "_blank");
},
goShopPage(id) {
@@ -308,10 +295,6 @@ export default {
}
}
.goods-show-tag-ecoupon {
padding: 0 5px;
}
.goods-show-tag-self {
--el-tag-bg-color: #fa2c19;
--el-tag-border-color: #fa2c19;
@@ -336,28 +319,18 @@ export default {
}
}
.goods-show-tag-physical {
background-color: #e6f4ff;
border-color: #b3d8ff;
color: #409eff;
:deep(.el-tag__content) {
color: #409eff;
}
}
.goods-show-seller {
// padding:3px 0;
vertical-align: middle;
}
.container {
margin:25px auto 15px auto;
width: 1184px;
margin: 20px auto 24px;
width: 1200px;
min-width: 1000px;
position: relative;
}
.price-sort:hover {
color: #e23a3a;
color: $theme_color;
}
.goods-box {
display: flex;
@@ -383,16 +356,16 @@ export default {
}
.goods-list-tool {
width: 100%;
height: 38px;
border: 1px solid #ccc;
background-color: #f1f1f1;
height: 48px;
border: 1px solid #eee;
background-color: #fff;
}
.goods-list-tool ul {
display: flex;
align-items: center;
height: 100%;
padding-left: 15px;
font-size: 12px;
font-size: 14px;
margin: 0;
list-style: none;
@@ -435,33 +408,105 @@ export default {
display: inline-flex;
align-items: center;
gap: 2px;
padding: 3px 5px;
border: 1px solid #ccc;
margin-left: -1px;
background-color: #fff;
height: 48px;
padding: 0 16px;
border: none;
margin-left: 0;
background-color: transparent;
color: #666;
line-height: 1;
white-space: nowrap;
box-sizing: border-box;
}
.goods-list-tool li > span:hover {
border-color: $theme_color;
color: $theme_color;
position: relative;
text-decoration: none;
z-index: 1;
}
.goods-list-tool li > span .el-icon {
font-size: 12px;
font-size: 14px;
line-height: 1;
}
.goods-list-tool-active {
color: #fff;
border-left: 1px solid #ccc;
background-color: $theme_color !important;
color: $theme_color !important;
background-color: transparent !important;
font-weight: 600;
.price-color {
color: rgba(255, 255, 255, 0.65) !important;
color: $theme_color !important;
}
}
.goods-list {
column-gap: 12px;
padding: 4px 12px 16px;
background: #fff;
border: 1px solid #eee;
border-top: none;
box-sizing: border-box;
}
.goods-show-info {
width: calc(20% - 10px);
margin: 12px 0 0;
padding: 12px;
border: 1px solid #f0f0f0;
border-radius: 4px;
}
.goods-show-info:hover {
border-color: #f0d0cc;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.seckill-price {
font-size: 20px;
font-weight: 600;
}
.goods-show-detail {
height: 44px;
margin: 8px 0 6px;
color: #171A26;
font-size: 15px;
line-height: 22px;
}
.goods-show-num {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 6px;
color: #999;
font-size: 13px;
font-weight: 400;
line-height: 20px;
}
.goods-show-num span {
color: #999;
font-size: 13px;
font-weight: 400;
}
.goods-show-sales {
flex-shrink: 0;
}
.goods-show-seller {
color: #438cde;
font-size: 14px;
span:hover {
color: $theme_color;
}
}
.goods-page {
margin: 16px 0 8px;
}
/* ---------------商品栏结束------------------- */
</style>

View File

@@ -12,8 +12,8 @@
<span v-if="item.couponType === 'PRICE'" class="fontsize_12 global_color" style="font-size: 15px;">¥<span class="price">{{ $filters.unitPrice(item.price) }}</span></span>
<span v-if="item.couponType === 'DISCOUNT'" class="fontsize_12 global_color" style="font-size: 15px;"><span class="price">{{item.discount}}</span>折</span>
<span class="describe" style="font-size: 15px;margin-left: 5px;">满{{item.consumeThreshold}}元可用</span>
<p style="font-size: 10px;">使用范围:{{useScope(item.scopeType, item.storeName)}}</p>
<p style="font-size: 10px;">有效期:{{item.endTime}}</p>
<p style="font-size: 14px;">使用范围:{{useScope(item.scopeType, item.storeName)}}</p>
<p style="font-size: 14px;">有效期:{{item.endTime}}</p>
</el-card>
</div>
<template #footer>

View File

@@ -19,55 +19,13 @@
</el-carousel>
<!-- 登录模块 -->
<div class="form-box" @click="hideVerify">
<div class="account-number">
<div class="tab-switch">
<span>{{ type ? '账号登录' : '验证码登录' }}</span>
<span @click="type = !type,scannerCodeLoginFLag=false">{{ type ? '验证码登录' : '账号登录' }}</span>
</div>
<!---->
<div @click="scannerCodeLoginFLag=!scannerCodeLoginFLag">{{!scannerCodeLoginFLag ? '扫码登录' : '返回'}}</div>
</div>
<!--扫码登录-->
<div v-show="scannerCodeLoginFLag">
<div class="qr-container">
<div class='qr-shadow flex' v-show="qrCodeStatus == 'fail'">
<span>
二维码已失效
</span>
<el-button size='small' @click="createPCLoginSession">刷新二维码</el-button>
</div>
<vue-qr
:text="qrCode"
:margin="0"
colorDark="#000"
colorLight="#fff"
:size="150"
></vue-qr>
</div>
<div class="drag-area">
<!-- 等待扫码-->
<div v-if="scannerCodeLoginStatus === 0" class="pending-scan">
<p>打开手机App/小程序,扫码登录</p>
</div>
<!-- 已经扫码-->
<div v-else-if="scannerCodeLoginStatus === 1" class="scanned">
<p>扫码成功,等待确认</p>
</div>
<!-- 存在session,等待发送给客户端验证-->
<div v-if="scannerCodeLoginStatus === 2" class="scanned">
<p>登录成功,正在页面跳转</p>
</div>
<!-- 已经发送登录请求-->
<div v-else-if="scannerCodeLoginStatus === 3" class="quick-logining">
<p>取消登录</p>
</div>
</div>
<div class="login-tabs">
<span :class="{ active: type }" @click="type = true">账号登录</span>
<span :class="{ active: !type }" @click="type = false">验证码登录</span>
</div>
<div>
<div v-show="!scannerCodeLoginFLag">
<div>
<!-- 账号密码登录 -->
<el-form ref="formInline" :model="formData" :rules="ruleInline" v-show="type === true"
@click.self="hideVerify">
@@ -82,7 +40,7 @@
</el-input>
</el-form-item>
<el-form-item>
<el-button type="danger" @click.stop="handleSubmit('formInline')" style="width:100%">登录</el-button>
<el-button class="login-btn" @click.stop="handleSubmit('formInline')">登录</el-button>
</el-form-item>
</el-form>
<!-- 验证码登录 -->
@@ -90,22 +48,17 @@
@click.self="hideVerify">
<el-form-item prop="mobile">
<el-input type="text" v-model="formSms.mobile" clearable placeholder="手机号">
<template #prepend><el-icon><Lock /></el-icon></template>
<template #prepend><el-icon><Iphone /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input type="text" v-model="formSms.code" placeholder="手机验证码">
<template #prepend><el-icon style="font-weight: bold"><Document /></el-icon></template>
<template #append><el-button @click="sendCode">{{ codeMsg }}</el-button></template>
<template #append><el-button @click.stop="sendCode">{{ codeMsg }}</el-button></template>
</el-input>
</el-form-item>
<el-form-item>
<el-button @click.stop="verifyBtnClick" style="width:100%"
:type="verifyStatus?'success':'default'">{{ verifyStatus ? '验证通过' : '点击完成安全验证' }}
</el-button>
</el-form-item>
<el-form-item>
<el-button type="danger" @click="handleSubmit('formSms')" style="width:100%">登录</el-button>
<el-button class="login-btn" @click="handleSubmit('formSms')">登录</el-button>
</el-form-item>
</el-form>
</div>
@@ -126,7 +79,7 @@
</svg>
</div>
<div class="register">
<span style="color:red" @click.stop="$router.push('signUp')">还没有账号?点击立即注册</span>
<span class="register-link" @click.stop="$router.push('signUp')">立即注册</span>
<span @click.stop="$router.push('forgetPassword')">忘记密码</span>
</div>
</div>
@@ -159,33 +112,23 @@ import * as RegExp from "@/plugins/RegExp.js";
import { md5 } from "@/plugins/md5.js";
import * as apiLogin from "@/api/login.js";
import { sendSms } from "@/api/common.js";
import { webLogin, loginCallback,sCLogin,getSCLoginCode} from "@/api/login.js";
import { webLogin, loginCallback } from "@/api/login.js";
import storage from "@/plugins/storage.js";
import verify from "@/components/verify";
import vueQr from "vue-qr";
import { User, Lock, Document } from "@element-plus/icons-vue";
import { User, Lock, Document, Iphone } from "@element-plus/icons-vue";
import config from "@/config";
export default {
name: "Login",
components: {
verify,
vueQr,
User,
Lock,
Document,
Iphone,
},
data() {
return {
qrCodeStatus:"success", //
qrCode: '',
qrSessionToken:'',
//是否是二维码登录
scannerCodeLoginFLag: false,
scannerCodeLoginStatus: 0,
qrCodeTimer:null,
qrExpireMs: 20000,
qrCreateSeq: 0,
config,
type: true, // true 账号登录 false 验证码登录
formData: {
@@ -234,10 +177,6 @@ export default {
if (this.type) {
this.$refs.verify?.init();
} else {
if (!this.verifyStatus) {
Message.warning("请先完成安全验证");
return;
}
if (!this.codeSent) {
Message.warning("请先获取短信验证码");
return;
@@ -274,41 +213,44 @@ export default {
},
// 发送手机验证码
sendCode() {
if (this.time === 60) {
if (this.formSms.mobile === "") {
Message.warning("请先填写手机号");
return;
}
if (!this.verifyStatus) {
Message.warning("请先完成安全验证");
return;
}
let params = {
mobile: this.formSms.mobile,
verificationEnums: "LOGIN",
};
sendSms(params).then((res) => {
if (res.success) {
Message.success("验证码发送成功");
this.codeSent = true;
let that = this;
this.interval = setInterval(() => {
that.time--;
if (that.time === 0) {
that.time = 60;
that.codeMsg = "重新发送";
that.verifyStatus = false;
that.codeSent = false;
clearInterval(that.interval);
} else {
that.codeMsg = that.time;
}
}, 1000);
} else {
Message.warning(res.message);
}
}).catch(() => {});
if (this.time !== 60) return;
if (this.formSms.mobile === "") {
Message.warning("请先填写手机号");
return;
}
if (!RegExp.mobile.test(this.formSms.mobile)) {
Message.warning("请输入正确的手机号");
return;
}
this.$refs.verify?.init();
},
dispatchSms() {
sendSms({
mobile: this.formSms.mobile,
verificationEnums: "LOGIN",
}).then((res) => {
if (res.success) {
Message.success("验证码发送成功");
this.codeSent = true;
this.interval = setInterval(() => {
this.time--;
if (this.time === 0) {
this.time = 60;
this.codeMsg = "重新发送";
this.verifyStatus = false;
this.codeSent = false;
clearInterval(this.interval);
} else {
this.codeMsg = this.time;
}
}, 1000);
} else {
this.verifyStatus = false;
Message.warning(res.message);
}
}).catch(() => {
this.verifyStatus = false;
});
},
verifyChange(con) {
// 拼图验证码回显
@@ -336,12 +278,7 @@ export default {
} else {
this.verifyStatus = true;
this.hideVerify();
}
},
// 开启滑块验证
verifyBtnClick() {
if (!this.verifyStatus) {
this.$refs.verify?.init();
this.dispatchSms();
}
},
handleWebLogin(type) {
@@ -369,92 +306,9 @@ export default {
});
},
async createPCLoginSession() {
const requestId = ++this.qrCreateSeq;
this.clearQRLoginInfo();
getSCLoginCode({}).then(response=>{
if (requestId !== this.qrCreateSeq) return;
if (response.code == 200 && response.result) {
this.qrCodeStatus = 'success'
let session = response.result;
this.qrSessionToken = session.token;
this.qrExpireMs = Number(session.duration) > 0 ? Number(session.duration) : 20000;
if (session.status === 0) {
this.qrCode = session.token;
this.refreshQrCode();
}
this.qrLogin();
} else {
this.markQrExpired();
}
}).catch(() => {
if (requestId !== this.qrCreateSeq) return;
this.markQrExpired();
});
},
refreshQrCode() {
this.clearQrTimer();
this.qrCodeTimer = setTimeout(() => {
this.qrCodeStatus = 'fail';
this.clearQrTimer();
}, this.qrExpireMs);
},
clearQrTimer() {
if (this.qrCodeTimer) {
clearTimeout(this.qrCodeTimer);
}
this.qrCodeTimer = null;
},
markQrExpired() {
this.qrCodeStatus = 'fail';
this.scannerCodeLoginStatus = 0;
this.qrSessionToken = '';
this.clearQrTimer();
},
clearQRLoginInfo(){
this.scannerCodeLoginStatus=0;
this.qrSessionToken='';
this.clearQrTimer();
},
async qrLogin() {
const token = this.qrSessionToken;
if(!token) return;
sCLogin(token,{beforeSessionStatus:this.scannerCodeLoginStatus}).then(response=>{
if (token !== this.qrSessionToken) return;
if (response.success) {
this.scannerCodeLoginStatus = response.result.status;
switch (response.result.status) {
case 0:
case 1:
this.qrLogin();break;
case 2:
this.clearQRLoginInfo();
this.loginSuccess(response.result.token.accessToken,response.result.token.refreshToken);
break;
case 3:
this.createPCLoginSession();
break;
default:
this.markQrExpired();
break
}
} else{
this.markQrExpired();
}
}).catch(() => {
if (token !== this.qrSessionToken) return;
this.markQrExpired();
});
},
},
beforeUnmount() {
this.qrCreateSeq++;
this.clearQRLoginInfo();
clearInterval(this.interval);
},
mounted() {
let uuid = this.$route.query.state;
@@ -468,16 +322,6 @@ export default {
}
},
watch: {
scannerCodeLoginFLag(v){
if(v){
this.createPCLoginSession();
console.log("二维码登录");
}else{
console.log("取消二维码登录");
this.qrCreateSeq++;
this.clearQRLoginInfo();
}
},
type(v) {
if (v) {
this.$refs.formInline.resetFields();
@@ -502,83 +346,44 @@ export default {
};
</script>
<style scoped lang="scss">
.drag-area{
margin: 10px 0;
}
.login {
height: 100%;
background-color: #f0f2f5;
}
.other{
display: flex;
align-items: center;
justify-content: space-between;
}
.qr-container{
text-align: center;
margin: 20px 0;
position: relative;
>.qr-shadow{
background: rgba(0, 0, 0, 0.45);
position: absolute;
left: 50%;
margin-left: -75px;
top: 0;
z-index: 99;
width: 150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: #fff;
>span{
margin-bottom: 20px;
font-size: 13px;
letter-spacing: 2px;
}
}
min-height: 100vh;
background-color: #f5f5f5;
}
.top-content {
width: 100%;
height: 80px;
height: 72px;
position: relative;
z-index: 1;
box-shadow: 0 1px 1px #ddd;
background-color: #fff;
border-bottom: 1px solid #eee;
.logo-box {
width: 80%;
max-width: 1200px;
height: 80px;
width: 1200px;
max-width: calc(100% - 48px);
height: 72px;
margin: 0 auto;
display: flex;
align-items: center;
img {
width: 150px;
height: 36px;
width: auto;
cursor: pointer;
}
div {
font-size: 20px;
margin-top: 10px;
margin-left: 16px;
padding-left: 16px;
border-left: 1px solid #e5e5e5;
font-size: 18px;
font-weight: 600;
color: #171A26;
line-height: 24px;
}
}
}
.pending-scan{
text-align: center;
color:black;
}
.scanned{
text-align: center;
color:green;
}
.quick-logining{
text-align: center;
color:red;
}
.login-carousel {
width: 100%;
@@ -586,118 +391,213 @@ export default {
.demo-carousel {
height: 550px;
width: inherit;
display: flex;
justify-content: center;
width: 100%;
overflow: hidden;
img {
display: block;
width: 100%;
height: 550px;
object-fit: cover;
object-position: center;
}
}
}
.login-container {
--login-card-right: max(96px, 10%);
position: relative;
width: 100%;
height: 550px;
}
.form-box {
width: 350px;
width: 460px;
box-sizing: border-box;
position: absolute;
top: 80px;
right: 15%;
padding: 20px;
background: rgba(255, 255, 255, 0.8);
top: 48px;
right: var(--login-card-right);
padding: 32px 32px 24px;
background: #fff;
border-radius: 4px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
.account-number {
display: flex;
justify-content: space-between;
align-items: baseline;
font-weight: bold;
:deep(.el-form-item) {
margin-bottom: 22px;
}
> div:nth-child(2) {
color: $theme_color;
cursor: pointer;
}
:deep(.el-form-item__error) {
padding-top: 2px;
font-size: 12px;
line-height: 16px;
}
.tab-switch {
height: 40px;
font-size: 14px;
:deep(.el-input__wrapper),
:deep(.el-input-group__prepend),
:deep(.el-input-group__append) {
box-shadow: none;
background: #fff;
}
span:nth-child(1) {
font-size: 16px;
border-right: 1px solid #ddd;
padding-right: 10px;
}
:deep(.el-input) {
height: 48px;
border: 1px solid #e5e5e5;
border-radius: 4px;
}
span:nth-child(2) {
cursor: pointer;
padding-left: 10px;
:deep(.el-input__inner) {
height: 48px;
font-size: 16px;
color: #171A26;
}
&:hover {
color: $theme_color;
}
:deep(.el-input-group__prepend) {
padding: 0 14px;
color: #999;
font-size: 18px;
border-right: 1px solid #f0f0f0;
}
:deep(.el-input-group__append) {
padding: 0;
border-left: 1px solid #f0f0f0;
}
:deep(.el-input-group__append .el-button) {
height: 46px;
margin: 0;
padding: 0 14px;
border: none;
color: $theme_color;
font-size: 16px;
background: transparent;
}
}
.login-tabs {
display: flex;
gap: 32px;
margin-bottom: 28px;
border-bottom: 1px solid #f0f0f0;
span {
position: relative;
padding-bottom: 14px;
color: #666;
font-size: 18px;
line-height: 26px;
cursor: pointer;
&.active {
color: #171A26;
font-weight: 600;
&::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -1px;
height: 2px;
background: $theme_color;
}
}
}
}
.login-btn {
width: 100%;
height: 48px;
border-radius: 4px;
font-size: 18px;
}
.login-btn {
background: $theme_color !important;
border-color: $theme_color !important;
color: #fff !important;
}
.verify-con {
position: absolute;
right: 16%;
right: var(--login-card-right);
top: 90px;
z-index: 10;
}
.other {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 4px;
}
.other-login {
display: flex;
align-items: center;
gap: 12px;
> svg {
cursor: pointer;
width: 24px;
margin-right: 10px;
height: 24px;
width: 32px;
height: 32px;
}
}
.register {
display: flex;
margin-top: -10px;
font-size: 12px;
align-items: center;
gap: 16px;
font-size: 15px;
span {
margin-left: 10px;
font-size: 12px;
color: #666;
cursor: pointer;
&:hover {
cursor: pointer;
color: $theme_color;
}
}
.register-link {
color: $theme_color;
}
}
.foot {
position: fixed;
bottom: 4vh;
width: 368px;
left: calc(50% - 184px);
color: rgba(0, 0, 0, 0.45);
bottom: 24px;
width: 420px;
left: calc(50% - 210px);
color: #999;
font-size: 14px;
text-align: center;
.help {
margin: 0 auto;
margin-bottom: 1vh;
width: 60%;
display: flex;
justify-content: center;
gap: 24px;
margin-bottom: 8px;
.item {
color: rgba(0, 0, 0, 0.45);
}
color: #666;
text-decoration: none;
:hover {
color: rgba(0, 0, 0, 0.65);
&:hover {
color: $theme_color;
}
}
}
.copyright {
display: flex;
justify-content: center;
align-items: center;
a {
color: #666;
text-decoration: none;
}
}
}
.icon-hover {
cursor: pointer;
}
</style>

View File

@@ -3,65 +3,63 @@
<BaseHeader/>
<!-- 搜索栏 -->
<Search :storeId="storeMsg.storeId || $route.query.id" @search="search"></Search>
<!-- 店铺logo -->
<div class="shop-logo">
<div class="shop-logo-inner">
<div class="store-head">
<div class="store-head-main">
<div class="shop-info">
<p class="shop-name">{{ storeMsg.storeName || '店铺' }}</p>
<p
v-if="storeMsg.storeDesc"
:alt="storeMsg.storeDesc"
class="shop-desc ellipsis"
v-html="storeMsg.storeDesc"
></p>
</div>
<div class="shop-actions">
<span class="hover-pointer store-collect-btn" @click="collect">
<el-icon :color="storeCollected ? '#ffc107' : '#fff'">
<StarFilled />
</el-icon>
<span>{{
storeCollected ? '已收藏店铺' : '收藏店铺'
}}</span>
</span>
<span
class="hover-pointer store-service-btn"
@click="IMService(storeMsg.storeId, null, null)"
class="store-collect-btn"
:class="{ 'is-collected': storeCollected }"
@click="collect"
>
<i class="icomoon icon-customer-service"></i>联系客服
<el-icon><StarFilled /></el-icon>
<span>{{ storeCollected ? '已收藏店铺' : '收藏店铺' }}</span>
</span>
<span class="store-service-btn" @click="IMService(storeMsg.storeId, null, null)">联系客服</span>
</div>
</div>
</div>
<div class="store-category">
<ul class="cate-list">
<li
class="cate-item"
@click="searchByCate({ id: '', labelName: '店铺推荐' })"
>
首页
</li>
<li v-for="(cate, index) in cateList" :key="index" class="cate-item">
<el-dropdown v-if="cate.children.length" trigger="hover">
<div @click.self="searchByCate(cate)">
{{ cate.labelName }}
<el-icon><ArrowDown /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="sec in cate.children"
:key="sec.id"
@click="searchByCate(sec)"
>{{ sec.labelName }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span v-else @click.self="searchByCate(cate)">{{
cate.labelName
}}</span>
</li>
</ul>
<div class="store-category">
<ul class="cate-list">
<li
class="cate-item"
:class="{ active: !params.storeCatId }"
@click="searchByCate({ id: '', labelName: '店铺推荐' })"
>
首页
</li>
<li
v-for="(cate, index) in cateList"
:key="index"
class="cate-item"
:class="{ active: isCateActive(cate) }"
@click="searchByCate(cate)"
>
<el-dropdown v-if="cate.children && cate.children.length" trigger="hover">
<div class="cate-label">
{{ cate.labelName }}
<el-icon><ArrowDown /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="sec in cate.children"
:key="sec.id"
@click.stop="searchByCate(sec)"
>{{ sec.labelName }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span v-else>{{ cate.labelName }}</span>
</li>
</ul>
</div>
</div>
@@ -72,42 +70,49 @@
<div v-else class="store-goods-section">
<div class="promotion-decorate">{{ cateName }}</div>
<div class="store-goods-hd">{{ cateName }}</div>
<div class="goods-list">
<empty v-if="goodsList.length === 0"/>
<template v-else>
<div
v-for="(item, index) in goodsList"
v-else
:key="index"
class="goods-show-info"
@click="goGoodsDetail(item.id, item.goodsId)"
>
<div class="goods-show-img">
<img :src="item.small" alt=""/>
<img :src="item.thumbnail || item.small" alt=""/>
</div>
<div class="goods-show-price">
<span>
<span class="seckill-price text-danger">{{ $filters.unitPrice(item.price, "¥") }}</span>
</span>
<span class="seckill-price text-danger">{{ $filters.unitPrice(item.price, "¥") }}</span>
</div>
<div class="goods-show-detail">
<el-tag
v-if="item.salesModel === 'WHOLESALE'"
class="goods-show-tag goods-show-tag-wholesale"
>批发</el-tag>
<span>{{ item.goodsName }}</span>
</div>
<div class="goods-show-num">
已有<span>{{ item.commentNum || 0 }}</span>人评价
<span>{{ formatGrade(item.grade) }}%好评</span>
<span class="goods-show-sales">销量 {{ item.buyCount || 0 }}</span>
</div>
<div class="goods-show-right" v-if="item.selfOperated">
<el-tag class="goods-show-tag goods-show-tag-self">自营</el-tag>
</div>
</div>
</template>
</div>
<div class="goods-page">
<el-pagination v-model:current-page="params.pageNumber"
<div class="goods-page" v-if="total > 0">
<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"></el-pagination>
layout="total, prev, pager, next, sizes"
></el-pagination>
</div>
</div>
<BaseFooter/>
@@ -311,9 +316,22 @@ export default {
searchByCate(cate) {
// 搜索同分类下商品
this.params.storeCatId = cate.id;
this.params.pageNumber = 1;
this.cateName = cate.labelName;
this.getGoodsList();
},
isCateActive(cate) {
if (!cate || !this.params.storeCatId) return false;
if (this.params.storeCatId === cate.id) return true;
return Array.isArray(cate.children) && cate.children.some((sec) => sec.id === this.params.storeCatId);
},
formatGrade(grade) {
if (grade === null || grade === undefined || grade === "") return 100;
const num = Number(grade);
if (Number.isNaN(num)) return 100;
const fixed = Number(num.toFixed(1));
return fixed % 1 === 0 ? fixed.toFixed(0) : String(fixed);
},
// 分页 修改页码
changePageNum(val) {
this.params.pageNumber = val;
@@ -364,123 +382,285 @@ export default {
</script>
<style lang="scss" scoped>
@import "../assets/styles/goodsList.scss";
.merchant {
margin: 0 auto;
min-height: 100%;
background: #f5f5f5;
}
.shop-logo {
position: relative;
width: 100%;
background-color: #666;
padding: 4px;
color: #fff;
.store-head {
width: 1200px;
margin: 0 auto;
background: #fff;
border-radius: 4px;
}
.shop-logo-inner {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
width: 1200px;
margin: 0 auto;
.store-head-main {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 20px 24px 16px;
}
.shop-info {
min-width: 0;
}
.shop-name {
margin: 0;
color: #171A26;
font-size: 20px;
font-weight: 600;
line-height: 28px;
}
.shop-desc {
margin: 6px 0 0;
max-width: 760px;
color: #999;
font-size: 13px;
line-height: 20px;
}
.shop-actions {
display: flex;
align-items: center;
flex-shrink: 0;
gap: 20px;
color: #171A26;
font-size: 14px;
}
.store-collect-btn,
.store-service-btn {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.store-collect-btn {
.el-icon {
color: #999;
font-size: 16px;
}
.shop-info {
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
min-width: 0;
}
.shop-name {
margin: 0;
font-size: 20px;
flex-shrink: 0;
}
.shop-desc {
margin: 0;
font-size: 14px;
max-height: 40px;
max-width: 400px;
}
.shop-actions {
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
flex-shrink: 0;
}
.store-collect-btn {
display: inline-flex;
align-items: center;
gap: 6px;
}
.store-service-btn {
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 80px;
&.is-collected,
&.is-collected .el-icon {
color: $theme_color;
}
}
.store-category {
background-color: #005aa0;
color: #fff;
border-top: 1px solid #f0f0f0;
}
.cate-list {
width: 1200px;
margin: 0 auto;
clear: left;
height: 30px;
line-height: 30px;
.cate-list {
display: flex;
align-items: stretch;
width: 100%;
height: 48px;
margin: 0;
padding: 0 8px;
list-style: none;
box-sizing: border-box;
}
.cate-item {
margin-right: 25px;
float: left;
.cate-item {
position: relative;
height: 48px;
padding: 0 18px;
color: #666;
font-size: 15px;
line-height: 48px;
cursor: pointer;
&:hover {
color: #171A26;
}
&.active {
color: #171A26;
font-weight: 600;
&::after {
content: "";
position: absolute;
left: 18px;
right: 18px;
bottom: 0;
height: 2px;
border-radius: 1px;
background: $theme_color;
}
}
.cate-item:hover {
cursor: pointer;
}
:deep(.el-dropdown) {
color: inherit;
font-size: inherit;
line-height: inherit;
vertical-align: top;
outline: none;
}
:deep(.el-tooltip__trigger) {
outline: none;
}
}
.promotion-decorate::before,
.promotion-decorate::after {
background-image: url("/src/assets/images/sprite@2x.png");
.cate-label {
display: inline-flex;
align-items: center;
gap: 2px;
color: inherit;
line-height: 48px;
.el-icon {
font-size: 12px;
}
}
.store-goods-section {
width: 1200px;
max-width: 100%;
margin: 0 auto;
padding: 0 10px;
margin: 16px auto 24px;
background: #fff;
border-radius: 4px;
box-sizing: border-box;
overflow: hidden;
}
.goods-list {
width: 100%;
margin: 0;
column-gap: 10px;
}
.store-goods-hd {
padding: 16px 24px 0;
color: #171A26;
font-size: 16px;
font-weight: 600;
line-height: 24px;
}
.goods-show-info {
width: calc(20% - 8px);
min-width: 0;
}
.goods-list {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
width: 100%;
margin: 0;
padding: 12px 16px 4px;
box-sizing: border-box;
}
.seckill-price {
font-size: 16px;
}
.goods-show-info {
width: auto;
min-width: 0;
margin: 0;
padding: 12px;
border: 1px solid #f0f0f0;
border-radius: 4px;
background: #fff;
box-sizing: border-box;
cursor: pointer;
.goods-show-detail {
height: 32px;
line-height: 16px;
&:hover {
border-color: #f0d0cc;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
}
.goods-show-img img {
display: block;
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
.goods-show-price {
margin-top: 8px;
}
.seckill-price {
color: $theme_color;
font-size: 20px;
font-weight: 600;
line-height: 28px;
}
.goods-show-detail {
height: 44px;
margin: 6px 0;
overflow: hidden;
color: #171A26;
font-size: 14px;
line-height: 22px;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.goods-show-num {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin: 0;
color: #999;
font-size: 13px;
font-weight: 400;
line-height: 20px;
span {
color: #999;
font-size: 13px;
font-weight: 400;
}
}
.goods-show-sales {
flex-shrink: 0;
}
.goods-show-right {
display: flex;
margin-top: 6px;
}
.goods-show-tag {
display: inline-flex;
align-items: center;
height: 18px;
margin-right: 4px;
padding: 0 4px;
line-height: 1;
vertical-align: 1px;
}
.goods-show-tag-self {
--el-tag-bg-color: #fa2c19;
--el-tag-border-color: #fa2c19;
--el-tag-text-color: #fff;
background-color: #fa2c19;
border-color: #fa2c19;
color: #fff;
}
.goods-show-tag-wholesale {
--el-tag-bg-color: #f9f0ff;
--el-tag-border-color: #d3adf7;
--el-tag-text-color: #9254de;
background-color: #f9f0ff;
border-color: #d3adf7;
color: #9254de;
}
.goods-page {
display: flex;
justify-content: flex-end;
width: 100%;
margin: 0;
padding: 16px;
box-sizing: border-box;
}
</style>

View File

@@ -1,16 +1,21 @@
<template>
<div class="sign-up" @click="hideVerify">
<div style="height:50px;"></div>
<div class="logo-box">
<img
width="150"
:src="$store.state.logoImg"
@click="$router.push('/')"
/>
<div>注册</div>
<div class="top-content">
<div class="top-inner">
<div class="logo-box">
<img :src="$store.state.logoImg" @click="$router.push('/')" />
<div>欢迎注册</div>
</div>
<div class="to-login">已有账号?<a @click.stop="$router.push('login')">立即登录</a></div>
</div>
</div>
<div class="login-container">
<!-- 注册 -->
<div class="signup-banner">
<img
class="signup-bg"
src="https://lili-system.oss-cn-beijing.aliyuncs.com/background.jpg"
alt=""
/>
<div class="signup-card">
<el-form
ref="formRegist"
:model="formRegist"
@@ -55,27 +60,21 @@
placeholder="手机验证码"
>
<template #prepend><el-icon style="font-weight: bold"><Document /></el-icon></template>
<template #append><el-button @click="sendCode">{{ codeMsg }}</el-button></template>
<template #append><el-button @click.stop="sendCode">{{ codeMsg }}</el-button></template>
</el-input>
</el-form-item>
<el-form-item>
<el-button @click="verifyBtnClick" style="width:100%" :type="verifyStatus?'success':'default'">{{verifyStatus?'验证通过':'点击完成安全验证'}}</el-button>
<el-button class="signup-btn" @click="handleRegist">注册</el-button>
</el-form-item>
<el-form-item>
<el-button type="danger" size="large" @click="handleRegist" style="width:100%"
>注册</el-button
>
</el-form-item>
<el-form-item><span class="color999">点击注册,表示您同意《<router-link to="/article?id=1371992704333905920" target="_blank">商城用户协议</router-link>》</span></el-form-item>
<p class="agreement">点击注册,表示您同意《<router-link to="/article?id=1371992704333905920" target="_blank">商城用户协议</router-link>》</p>
</el-form>
<!-- 拼图验证码 -->
<Verify
ref="verify"
class="verify-con"
:verifyType="verifyType"
@change="verifyChange"
></Verify>
<div class="login-btn">已有账号?<a @click.stop="$router.push('login')">立即登录</a></div>
</div>
</div>
<div class="foot">
<div type="flex" justify="space-around" class="help">
@@ -164,140 +163,259 @@ export default {
},
// 发送短信验证码
sendCode () {
if (this.time === 60) {
if (this.formRegist.mobilePhone === '') {
Message.warning('请先填写手机号');
return;
}
if (!this.verifyStatus) {
Message.warning('请先完成安全验证');
return;
}
let params = {
mobile: this.formRegist.mobilePhone,
verificationEnums: 'REGISTER'
};
sendSms(params).then(res => {
if (res.success) {
Message.success('验证码发送成功');
let that = this;
this.interval = setInterval(() => {
that.time--;
if (that.time === 0) {
that.time = 60;
that.codeMsg = '重新发送';
that.verifyStatus = false;
clearInterval(that.interval);
} else {
that.codeMsg = that.time;
}
}, 1000);
} else {
Message.warning(res.message || '验证码发送失败');
}
}).catch(() => {});
if (this.time !== 60) return;
if (this.formRegist.mobilePhone === '') {
Message.warning('请先填写手机号');
return;
}
if (!RegExp.mobile.test(this.formRegist.mobilePhone)) {
Message.warning('请输入正确的手机号');
return;
}
this.$refs.verify?.init();
},
dispatchSms () {
sendSms({
mobile: this.formRegist.mobilePhone,
verificationEnums: 'REGISTER'
}).then(res => {
if (res.success) {
Message.success('验证码发送成功');
this.interval = setInterval(() => {
this.time--;
if (this.time === 0) {
this.time = 60;
this.codeMsg = '重新发送';
this.verifyStatus = false;
clearInterval(this.interval);
} else {
this.codeMsg = this.time;
}
}, 1000);
} else {
this.verifyStatus = false;
Message.warning(res.message || '验证码发送失败');
}
}).catch(() => {
this.verifyStatus = false;
});
},
// 图片验证码成功回调
verifyChange (con) {
if (!con.status) return;
this.hideVerify();
this.verifyStatus = true;
},
// 打开图片验证码
verifyBtnClick () {
if (!this.verifyStatus) {
this.$refs.verify?.init();
}
this.dispatchSms();
}
},
mounted () {
this.$refs.formRegist.resetFields();
document.querySelector('.sign-up').style.height = window.innerHeight + 'px'
}
};
</script>
<style scoped lang="scss">
.logo-box {
width: 600px;
height: 80px;
margin: 0 auto;
display: flex;
align-items: center;
img {
width: 150px;
cursor: pointer;
}
div {
font-size: 20px;
margin-top: 10px;
}
}
.login-container {
border-top: 2px solid $theme_color;
position: relative;
margin: 0 auto;
width: 600px;
background-color: #fff;
padding: 20px 40px;
.sign-up {
min-height: 100vh;
display: flex;
flex-direction: column;
background: #f5f5f5;
}
.top-content {
background: #fff;
border-bottom: 1px solid #eee;
}
.top-inner {
width: 1200px;
max-width: calc(100% - 48px);
height: 72px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo-box {
display: flex;
align-items: center;
.signup-form {
width: 300px;
img {
height: 36px;
width: auto;
cursor: pointer;
}
.login-btn{
position: absolute;
right: 20px;
top: -45px;
div {
margin-left: 16px;
padding-left: 16px;
border-left: 1px solid #e5e5e5;
font-size: 18px;
font-weight: 600;
color: #171A26;
line-height: 24px;
}
}
.verify-con{
.to-login {
font-size: 15px;
color: #666;
a {
margin-left: 4px;
color: $theme_color;
cursor: pointer;
}
}
.signup-banner {
position: relative;
height: 680px;
overflow: hidden;
}
.signup-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.signup-card {
position: absolute;
top: 50%;
right: max(72px, 8%);
transform: translateY(-50%);
z-index: 1;
width: 480px;
box-sizing: border-box;
padding: 36px 40px 28px;
background: #fff;
border-radius: 4px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}
.signup-form {
width: 100%;
}
.signup-card {
:deep(.el-form-item) {
margin-bottom: 22px;
}
:deep(.el-form-item__error) {
padding-top: 2px;
font-size: 12px;
line-height: 16px;
}
:deep(.el-input__wrapper),
:deep(.el-input-group__prepend),
:deep(.el-input-group__append) {
box-shadow: none;
background: #fff;
}
:deep(.el-input) {
height: 48px;
border: 1px solid #e5e5e5;
border-radius: 4px;
}
:deep(.el-input__inner) {
height: 48px;
font-size: 16px;
color: #171A26;
}
:deep(.el-input-group__prepend) {
padding: 0 14px;
color: #999;
font-size: 18px;
border-right: 1px solid #f0f0f0;
}
:deep(.el-input-group__append) {
padding: 0;
border-left: 1px solid #f0f0f0;
}
:deep(.el-input-group__append .el-button) {
height: 46px;
margin: 0;
padding: 0 14px;
border: none;
color: $theme_color;
font-size: 16px;
background: transparent;
}
}
.signup-btn {
width: 100%;
height: 48px;
border-radius: 4px;
font-size: 18px;
background: $theme_color !important;
border-color: $theme_color !important;
color: #fff !important;
}
.agreement {
margin: 0;
font-size: 14px;
line-height: 22px;
color: #999;
a {
color: $theme_color;
text-decoration: none;
}
}
.verify-con {
position: absolute;
left: 50%;
top: 80px;
top: 120px;
transform: translateX(-50%);
z-index: 10;
}
.other-login {
margin: 0 auto;
.el-icon {
font-size: 24px;
}
}
.regist {
display: flex;
justify-content: flex-end;
margin-top: -10px;
span {
margin-left: 10px;
&:hover {
cursor: pointer;
color: $theme_color;
}
}
}
.foot {
position: fixed;
bottom: 4vh;
width: 368px;
left: calc(50% - 184px);
color: rgba(0, 0, 0, 0.45);
bottom: 24px;
width: 420px;
left: calc(50% - 210px);
color: #999;
font-size: 14px;
text-align: center;
.help {
margin: 0 auto;
margin-bottom: 1vh;
width: 60%;
display: flex;
justify-content: center;
gap: 24px;
margin-bottom: 8px;
.item {
color: rgba(0, 0, 0, 0.45);
color: #666;
text-decoration: none;
&:hover {
color: $theme_color;
}
}
:hover {
color: rgba(0, 0, 0, 0.65);
}
.copyright {
display: flex;
justify-content: center;
align-items: center;
a {
color: #666;
text-decoration: none;
}
}
}

View File

@@ -3,11 +3,11 @@
<BaseHeader></BaseHeader>
<Search></Search>
<div class="title-bg"><p>文章帮助中心</p></div>
<div class="container width_1200">
<h1 class="page-title">文章帮助中心</h1>
<div class="layoutAll">
<el-aside class="side-bar" width="200px">
<div class="article-cate">文章分类列表</div>
<el-aside class="side-bar" width="220px">
<div class="article-cate">文章分类</div>
<el-menu
class="side-menu"
:key="menuKey"
@@ -15,36 +15,33 @@
:default-openeds="openName"
@select="onSelect"
>
<el-sub-menu
v-for="(menu, index) in list"
v-show="menu.children"
:key="index"
:index="menu.articleCategoryName"
>
<template #title>
<span>{{ menu.articleCategoryName }}</span>
</template>
<el-menu-item
v-for="(chlidren, i) in menu.children"
:key="i"
:index="String(chlidren.id)"
>{{ chlidren.articleCategoryName }}</el-menu-item>
</el-sub-menu>
<template v-for="(menu, index) in list" :key="menu.id || index">
<el-sub-menu v-if="menu.children && menu.children.length" :index="String(menu.id)">
<template #title>
<span @click="onSelect(menu.id)">{{ menu.articleCategoryName }}</span>
</template>
<el-menu-item
v-for="(child, i) in menu.children"
:key="child.id || i"
:index="String(child.id)"
>{{ child.articleCategoryName }}</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="String(menu.id)">{{ menu.articleCategoryName }}</el-menu-item>
</template>
</el-menu>
</el-aside>
<el-main class="content ml_10">
<el-main class="content">
<ul class="article-list" v-show="showList">
<li v-for="(article, index) in articleList" :key="index" @click="getDetail(article.id)">
<li v-if="!articleList.length" class="article-empty">暂无文章</li>
<li v-for="(article, index) in articleList" :key="article.id || index" @click="getDetail(article.id)">
{{ article.title }}
</li>
</ul>
<transition mode="out-in">
<div v-show="!showList">
<a class="back-btn" @click="showList = true">&lt;返回上一级</a>
<h2 class="mt_10 mb_10">{{ detail.title }}</h2>
<div class="mt_10 mb_10" v-html="detail.content"></div>
</div>
</transition>
<div v-show="!showList" class="article-detail">
<button type="button" class="back-btn" @click="showList = true">返回列表</button>
<h2>{{ detail.title }}</h2>
<div class="article-html" v-html="detail.content"></div>
</div>
</el-main>
</div>
</div>
@@ -82,26 +79,28 @@ export default {
getCateList () {
articleCateList().then(res => {
if (res.success) {
this.list = res.result
if (this.$route.query.id) {
this.list = res.result || []
this.openName = []
if (this.$route.query.id && this.detail && this.detail.categoryId) {
this.activeName = this.detail.categoryId
this.list.forEach(e => {
if (e.children.length) {
e.children.forEach(i => {
if (i.id === this.detail.categoryId) {
this.openName.push(e.articleCategoryName)
}
})
} else {
delete e.children
const children = e.children || []
const hitParent = String(e.id) === String(this.activeName)
const hitChild = children.some(i => i && String(i.id) === String(this.activeName))
if ((hitParent || hitChild) && children.length) {
this.openName.push(String(e.id))
}
})
} else {
this.activeName = this.list[0].children[0].id
this.openName.push(this.list[0].articleCategoryName)
} else if (this.list[0] && this.list[0].id) {
this.activeName = this.list[0].id
if (this.list[0].children && this.list[0].children.length) {
this.openName.push(String(this.list[0].id))
}
}
this.menuKey++
this.getList(this.activeName)
if (this.activeName) {
this.getList(this.activeName)
}
}
})
},
@@ -133,76 +132,152 @@ export default {
</script>
<style scoped lang="scss">
.content {
padding: 15px 50px;
.container {
margin: 0 auto;
padding: 20px 0 40px;
}
.header {
@include background_color($light_background_color);
}
.side-menu,
.side-bar,
.content {
@include white_background_color();
@include title_color($light_title_color);
}
.side-bar {
min-height: 600px;
height: auto;
.page-title {
margin: 0 0 16px;
color: #171A26;
font-size: 20px;
font-weight: 600;
line-height: 28px;
}
.layoutAll {
display: flex;
min-height: 1200px;
@include background_color($light_background_color);
align-items: stretch;
gap: 16px;
}
.container {
margin: 0 auto;
padding: 20px 0;
.side-bar,
.content {
background: #fff;
border: 1px solid #eee;
border-radius: 4px;
}
.side-bar a {
@include title_color($light_title_color);
.side-bar {
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow: hidden;
}
.layout-footer-center {
padding: 0px 15px;
padding-bottom: 15px;
text-align: center;
.article-cate {
height: 48px;
padding: 0 16px;
border-bottom: 1px solid #f0f0f0;
color: #171A26;
font-size: 15px;
font-weight: 600;
line-height: 48px;
}
.title-bg{
height: 100px;
width: 100%;
background-color: #7b7b7b;
p{
width: 1200px;
font-size: 30px;
height: 100px;
line-height: 100px;
color: #fff;
margin: 0 auto;
padding-left: 10px;
.side-menu {
flex: 1;
border-right: none;
padding: 8px 0 12px;
:deep(.el-sub-menu__title),
:deep(.el-menu-item) {
height: 40px;
line-height: 40px;
color: #171A26;
font-size: 14px;
}
:deep(.el-sub-menu__title:hover),
:deep(.el-menu-item:hover) {
background: #fafafa;
color: $theme_color;
}
:deep(.el-menu-item.is-active),
:deep(.el-sub-menu.is-active > .el-sub-menu__title) {
background: #fff5f4;
color: $theme_color;
}
}
.article-cate{
width: 200px;
height: 30px;
color: #fff;
line-height: 30px;
text-align: center;
font-size: 18px;
background-color: #666;
.content {
flex: 1;
min-width: 0;
padding: 4px 28px 12px;
}
.article-list {
li{
margin: 10px 0;
color: #2D8CF0;
&:hover{
cursor: pointer;
margin: 0;
padding: 0;
list-style: none;
li {
margin: 0 -28px;
padding: 16px 28px;
border-bottom: 1px solid #f0f0f0;
color: #171A26;
font-size: 15px;
line-height: 22px;
cursor: pointer;
&:last-child {
border-bottom: none;
}
&:hover {
background: #fafafa;
color: $theme_color;
}
}
}
.article-empty {
color: #999 !important;
cursor: default !important;
&:hover {
color: #999 !important;
}
}
.back-btn {
margin-top: 16px;
padding: 0;
border: none;
background: none;
color: #438cde;
font-family: inherit;
font-size: 14px;
line-height: 22px;
cursor: pointer;
&:hover {
color: $theme_color;
}
}
.article-detail h2 {
margin: 16px 0 20px;
color: #171A26;
font-size: 22px;
font-weight: 600;
line-height: 32px;
}
.article-html {
color: #333;
font-size: 15px;
line-height: 1.8;
word-break: break-word;
:deep(img) {
max-width: 100%;
height: auto;
}
:deep(p) {
margin: 0 0 12px;
}
}
</style>

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 {

File diff suppressed because it is too large Load Diff

View File

@@ -177,7 +177,7 @@ export default {
}
.left-tips-count-down {
font-size: 10px;
font-size: 14px;
color: red;
}

View File

@@ -193,7 +193,7 @@ export default {
.qrcode-status-tip {
margin-top: 8px;
color: #909399;
font-size: 12px;
font-size: 14px;
}
.intro {

View File

@@ -183,7 +183,7 @@
>
</el-form-item>
</el-form>
<el-dialog title="View Image" v-model="visible">
<el-dialog title="图片预览" v-model="visible">
<img :src="previewPicture" v-if="visible" style="width: 100%" />
</el-dialog>

View File

@@ -86,7 +86,7 @@
>
</el-form-item>
</el-form>
<el-dialog title="View Image" v-model="visible">
<el-dialog title="图片预览" v-model="visible">
<img :src="previewPicture" v-if="visible" style="width: 100%" />
</el-dialog>
<multipleMap ref="map" @callback="getAddress" />

View File

@@ -1,5 +1,5 @@
<template>
<div>
<div class="user-home-page">
<BaseHeader></BaseHeader>
<div class="container width_1200">
@@ -68,6 +68,12 @@ export default {
return {};
},
},
mounted() {
document.body.classList.add("buyer-user-center");
},
beforeUnmount() {
document.body.classList.remove("buyer-user-center");
},
methods: {
onSelect(name) {
this.$router.push({ name });
@@ -134,3 +140,38 @@ export default {
text-align: center;
}
</style>
<style lang="scss">
/* 会员中心按钮统一放大一号:small→默认,默认→large,large→48px。弹窗挂到 body,靠页面 class 一起放大。 */
body.buyer-user-center {
.user-home-page .content .el-button.el-button--small:not(.is-link):not(.is-text),
.el-overlay .el-button.el-button--small:not(.is-link):not(.is-text) {
height: 32px;
padding: 8px 15px;
font-size: var(--el-font-size-base);
border-radius: var(--el-border-radius-base);
}
.user-home-page .content .el-button:not(.el-button--small):not(.el-button--large):not(.is-link):not(.is-text),
.el-overlay .el-button:not(.el-button--small):not(.el-button--large):not(.is-link):not(.is-text) {
height: 40px;
padding: 12px 19px;
font-size: var(--el-font-size-base);
border-radius: var(--el-border-radius-base);
}
.user-home-page .content .el-button.el-button--large:not(.is-link):not(.is-text),
.el-overlay .el-button.el-button--large:not(.is-link):not(.is-text) {
--el-button-size: 48px;
height: var(--el-button-size);
padding: 16px 23px;
font-size: var(--el-font-size-base);
border-radius: var(--el-border-radius-base);
}
.user-home-page .content .el-button.is-link.el-button--small,
.el-overlay .el-button.is-link.el-button--small {
font-size: var(--el-font-size-base);
}
}
</style>

View File

@@ -303,7 +303,7 @@ export const router = createRouter({
path: "MsgDetail",
name: "MsgDetail",
component: MsgDetail,
meta: { title: "我的消息" },
meta: { title: "消息详情" },
},
{
path: "MyOrder",

View File

@@ -1,59 +1,16 @@
/**
* 买家端登录态与商品详情跳转校验
* 买家端内部链接跳转(首页装修、列表等共用)
*
* @author Mike
* @date 2026-08-05
*/
import storage from "@/plugins/storage";
export function isBuyerLoggedIn() {
const rawUserInfo = storage.getItem("userInfo");
if (rawUserInfo) {
try {
return !!JSON.parse(rawUserInfo).username;
} catch {
return false;
}
}
return !!storage.getItem("accessToken");
}
export function parseInternalLink(url) {
const [path, search = ""] = url.split("?");
const query = {};
if (search) {
new URLSearchParams(search).forEach((value, key) => {
query[key] = value;
});
}
return { path, query };
}
export function isGoodsDetailPath(path) {
return path === "/goodsDetail";
}
export function redirectToLogin(router, location) {
router.push({
path: "/login",
query: {
rePath: location.path,
query: JSON.stringify(location.query || {}),
},
});
}
/** 内部链接跳转;访问商品详情前校验登录(首页装修、列表等共用) */
/** 内部链接新窗口打开,外链直接打开。浏览商品详情不要求登录。 */
export function openLink(url, router) {
if (!url) return;
if (url.charAt(0) !== "/") {
window.open(url, "_blank");
return;
}
const location = parseInternalLink(url);
if (!isBuyerLoggedIn() && isGoodsDetailPath(location.path)) {
redirectToLogin(router, location);
return;
}
window.open(router.resolve(url).href, "_blank");
}