miao33: 从 main 同步 single_uniapp22miao,dart-sass 兼容修复,DEPLOY.md 更新

- 从 main 获取 single_uniapp22miao 子项目
- dart-sass: /deep/ -> ::v-deep,calc 运算符加空格
- DEPLOY.md 采用 shccd159 版本(4 子项目架构说明)

Made-with: Cursor
This commit is contained in:
apple
2026-03-16 11:16:42 +08:00
parent 9c29721dc4
commit 079076a70e
356 changed files with 569762 additions and 129 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,849 @@
<template>
<view class="goods-detail">
<!-- 顶部导航栏 -->
<view class="nav-bar">
<view class="nav-back" @click="goBack">
<text class="iconfont icon-fanhui"></text>
</view>
<text class="nav-title">商品详情</text>
<view class="nav-right"></view>
</view>
<!-- 商品图片轮播 -->
<swiper
class="goods-swiper"
:indicator-dots="true"
indicator-color="rgba(0,0,0,0.3)"
indicator-active-color="#FF4D4F"
>
<swiper-item v-for="(image, index) in sliderImage" :key="index">
<image
:src="image"
class="swiper-image"
mode="aspectFill"
@click="previewImage(index)"
/>
</swiper-item>
</swiper>
<!-- 商品信息 -->
<view class="wrapper" v-if="attr.productSelect">
<view class="share-section">
<view class="money-box">
<text class="points-symbol">积分</text>
<text class="points-num">{{ attr.productSelect.price || 0 }}</text>
</view>
</view>
<view class="introduce">{{ productInfo.storeName }}</view>
<!-- <view class="label">
<view>原价:{{ attr.productSelect.otPrice || 0 }}</view>
<view>库存:{{ attr.productSelect.stock || 0 }}{{ productInfo.unitName }}</view>
<view>销量:{{ Number(productInfo.sales) + Number(productInfo.ficti) }}{{ productInfo.unitName }}</view>
</view> -->
</view>
<!-- 规格选择 -->
<view class="attribute" @click="selecAttr">
<view class="attr-row">
<view class="attr-text">{{attrTxt}}<text class="selected-attr">{{attrValue}}</text></view>
<view class="iconfont icon-jiantou arrow-right"></view>
</view>
</view>
<!-- 属性选择组件 -->
<productWindow
:attr="attr"
:isShow='1'
:iSplus='1'
@myevent="onMyEvent"
@ChangeAttr="ChangeAttr"
@ChangeCartNum="ChangeCartNum"
@attrVal="attrVal"
@iptCartNum="iptCartNum">
</productWindow>
<!-- 商品详情 -->
<view class="detail-section">
<view class="detail-info-list" v-if="attr.productSelect">
<view class="detail-item">库存{{ attr.productSelect.stock || 0 }}</view>
<!-- <view class="detail-item">已兑换{{ Number(productInfo.sales) + Number(productInfo.ficti) }}</view> -->
</view>
<view class="detail-content">
<rich-text :nodes="description" />
</view>
</view>
<!-- 浮动购物车按钮 -->
<view class="floating-cart animated" :class="animated ? 'bounceIn' : ''" @click="goToCart">
<view class='iconfont icon-gouwuche1'>
<text v-if="Math.floor(CartCount) > 0" class='cart-badge'>{{CartCount}}</text>
</view>
<!-- <view class="cart-text">购物车</view> -->
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar safe-area-inset-bottom">
<view class="btn-group">
<view class="btn-cart" @click="onCartAdd">
<text>加入购物车</text>
</view>
<view
class="btn-buy"
:class="{ disabled: attr.productSelect && attr.productSelect.stock <= 0 }"
@click="onExchange"
>
<text>{{ exchangeButtonText }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
import { collectIntegralGoods } from '@/api/miao.js'
import { getProductDetail, postCartAdd } from '@/api/store.js';
import { getCartCounts } from '@/api/order.js';
import { toLogin } from '@/libs/login.js';
import { mapGetters } from "vuex";
import productWindow from '@/components/productWindow';
export default {
components: {
productWindow
},
data() {
return {
goodsId: 0,
productInfo: {
sales: 0,
ficti: 0,
unitName: '件'
},
productValue: {},
skuArr: [],
sliderImage: [],
attr: {
cartAttr: false,
productAttr: [],
productSelect: {
price: 0,
stock: 0,
otPrice: 0,
unique: '',
cart_num: 1
}
},
attrValue: '',
attrTxt: '请选择',
reply: [],
replyCount: 0,
replyChance: 0,
description: '',
isOpen: false, // 是否打开过属性选择器
CartCount: 0, // 购物车数量
animated: false // 购物车动画
}
},
computed: {
...mapGetters(['isLogin', 'uid']),
exchangeButtonText() {
if (this.attr.productSelect && this.attr.productSelect.stock <= 0) {
return '库存不足'
}
return '立即兑换'
}
},
onLoad(options) {
if (options.id) {
this.goodsId = parseInt(options.id)
this.getDetail()
}
},
onShow() {
// 从兑换页面返回时刷新数据
if (this.goodsId) {
this.getDetail()
}
// 获取购物车数量
this.getCartCount();
},
methods: {
// 获取商品详情
getDetail() {
let that = this;
uni.showLoading({
title: '加载中...'
})
getProductDetail(that.goodsId, 1).then(res => {
let productInfo = res.data.productInfo;
// 字符串数组转数组;
let arrayImg = productInfo.sliderImage;
let sliderImage = JSON.parse(arrayImg);
that.$set(that, 'sliderImage', sliderImage);
that.$set(that, 'productInfo', productInfo);
that.$set(that, 'description', productInfo.content);
that.$set(that.attr, 'productAttr', res.data.productAttr);
that.$set(that, 'productValue', res.data.productValue);
that.skuArr = [];
for (let key in res.data.productValue) {
let obj = res.data.productValue[key];
that.skuArr.push(obj)
}
let productAttr = that.attr.productAttr.map(item => {
return {
attrName: item.attrName,
attrValues: item.attrValues.split(','),
id: item.id,
isDel: item.isDel,
productId: item.productId,
type: item.type
}
});
that.$set(that.attr, 'productAttr', productAttr);
that.DefaultSelect();
uni.hideLoading();
}).catch(err => {
uni.hideLoading();
uni.showToast({
title: err.msg || '加载失败',
icon: 'none'
})
})
},
/**
* 默认选中属性
*
*/
DefaultSelect: function() {
let productAttr = this.attr.productAttr;
let value = [];
//默认选中每种规格的第一个
for (let key in this.productValue) {
if (this.productValue[key].stock > 0) {
value = this.attr.productAttr.length ? key.split(",") : [];
break;
}
}
for (let i = 0; i < value.length; i++) {
this.$set(productAttr[i], "index", value[i]);
}
//sort();排序函数:数字-英文-汉字;
let productSelect = this.productValue[value.join(",")];
if (productSelect && productAttr.length) {
this.$set(this.attr.productSelect, "storeName", this.productInfo.storeName);
this.$set(this.attr.productSelect, "image", productSelect.image);
this.$set(this.attr.productSelect, "price", productSelect.price);
this.$set(this.attr.productSelect, "stock", productSelect.stock);
this.$set(this.attr.productSelect, "unique", productSelect.id);
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", productSelect.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', productSelect.otPrice);
this.$set(this, "attrValue", value.join(","));
this.$set(this, "attrTxt", "已选择");
} else if (!productSelect && productAttr.length) {
this.$set(this.attr.productSelect, "storeName", this.productInfo.storeName);
this.$set(this.attr.productSelect, "image", this.productInfo.image);
this.$set(this.attr.productSelect, "price", this.productInfo.price);
this.$set(this.attr.productSelect, "stock", 0);
this.$set(this.attr.productSelect, "unique", this.productInfo.id);
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", this.productInfo.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', this.productInfo.otPrice);
this.$set(this, "attrValue", "");
this.$set(this, "attrTxt", "请选择");
} else if (!productSelect && !productAttr.length) {
this.$set(this.attr.productSelect, "storeName", this.productInfo.storeName);
this.$set(this.attr.productSelect, "image", this.productInfo.image);
this.$set(this.attr.productSelect, "price", this.productInfo.price);
this.$set(this.attr.productSelect, "stock", this.productInfo.stock);
this.$set(this.attr.productSelect, "unique", this.productInfo.id || "");
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", this.productInfo.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', this.productInfo.otPrice);
this.$set(this, "attrValue", "");
this.$set(this, "attrTxt", "已选择");
}
},
// 预览图片
previewImage(index) {
uni.previewImage({
urls: this.sliderImage,
current: index
})
},
// 打开属性选择器
selecAttr() {
this.$set(this.attr, 'cartAttr', true);
this.$set(this, 'isOpen', true);
},
// 关闭属性选择器
onMyEvent() {
this.$set(this.attr, 'cartAttr', false);
this.$set(this, 'isOpen', false);
},
// 属性变动赋值
ChangeAttr(res) {
let productSelect = this.productValue[res];
if (productSelect) {
this.$set(this.attr.productSelect, "image", productSelect.image);
this.$set(this.attr.productSelect, "price", productSelect.price);
this.$set(this.attr.productSelect, "stock", productSelect.stock);
this.$set(this.attr.productSelect, "unique", productSelect.id);
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", productSelect.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', productSelect.otPrice);
this.$set(this, "attrValue", res);
this.$set(this, "attrTxt", "已选择");
} else {
this.$set(this.attr.productSelect, "image", this.productInfo.image);
this.$set(this.attr.productSelect, "price", this.productInfo.price);
this.$set(this.attr.productSelect, "stock", 0);
this.$set(this.attr.productSelect, "unique", this.productInfo.id);
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", this.productInfo.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', this.productInfo.otPrice);
this.$set(this, "attrValue", "");
this.$set(this, "attrTxt", "请选择");
}
},
// 购物车数量加减
ChangeCartNum(changeValue) {
let productSelect = this.productValue[this.attrValue];
if (productSelect === undefined && !this.attr.productAttr.length)
productSelect = this.attr.productSelect;
if (productSelect === undefined) return;
let stock = productSelect.stock || 0;
let num = this.attr.productSelect;
if (changeValue) {
num.cart_num++;
if (num.cart_num > stock) {
this.$set(this.attr.productSelect, "cart_num", stock);
}
} else {
num.cart_num--;
if (num.cart_num < 1) {
this.$set(this.attr.productSelect, "cart_num", 1);
}
}
},
// 属性值选择
attrVal(val) {
this.$set(this.attr.productAttr[val.indexw], 'index', this.attr.productAttr[val.indexw].attrValues[val.indexn]);
},
// 购物车手动填写
iptCartNum(e) {
this.$set(this.attr.productSelect, 'cart_num', e ? e : 1);
},
// 加入购物车
onCartAdd() {
// 检查是否登录
if (this.isLogin === false) {
toLogin();
return;
}
// 调用统一的处理方法1 表示加入购物车
this.goCat(1);
},
// 立即兑换
onExchange() {
// 检查库存
if (this.attr.productSelect && this.attr.productSelect.stock <= 0) {
uni.showToast({
title: '库存不足',
icon: 'none'
});
return;
}
// 检查是否登录
if (this.isLogin === false) {
toLogin();
return;
}
// 调用统一的处理方法0 表示立即购买
this.goCat(0);
},
/**
* 统一处理加入购物车和立即兑换
* @param {Number} num 1-加入购物车 0-立即购买/兑换
*/
goCat(num) {
let that = this;
let productSelect = that.productValue[this.attrValue];
// 打开属性选择器
if (that.attrValue) {
// 默认选中了属性,但是没有打开过属性弹窗还是自动打开让用户查看默认选中的属性
that.attr.cartAttr = !that.isOpen ? true : false;
} else {
if (that.isOpen) that.attr.cartAttr = true;
else that.attr.cartAttr = !that.attr.cartAttr;
}
// 只有关闭属性弹窗时进行操作
if (that.attr.cartAttr === true && that.isOpen === false) {
return (that.isOpen = true);
}
// 如果有属性,没有选择或库存为0,提示用户选择
if (
that.attr.productAttr.length &&
productSelect &&
productSelect.stock === 0 &&
that.isOpen === true
) {
uni.showToast({
title: "产品库存不足,请选择其它",
icon: 'none'
});
return;
}
// 执行对应操作
if (num === 1) {
// 加入购物车
let q = {
productId: parseFloat(that.goodsId),
cartNum: parseFloat(that.attr.productSelect.cart_num),
isNew: false,
productAttrUnique: that.attr.productSelect !== undefined ?
that.attr.productSelect.unique : that.productInfo.id
};
postCartAdd(q).then(function(res) {
that.isOpen = false;
that.attr.cartAttr = false;
uni.showToast({
title: "添加购物车成功",
icon: 'success'
});
// 更新购物车数量
that.getCartCount(true);
}).catch(res => {
that.isOpen = false;
that.attr.cartAttr = false;
uni.showToast({
title: res.msg || '添加失败',
icon: 'none'
});
});
} else {
// 立即兑换 - 直接跳转到积分商城下单页面
this.goToConfirm();
}
},
// 直接跳转到积分商城下单页面
goToConfirm() {
// 保存商品信息到缓存
const goodsInfo = {
id: this.goodsId,
name: this.productInfo.storeName,
storeName: this.productInfo.storeName,
image: this.attr.productSelect.image || this.productInfo.image,
pic: this.attr.productSelect.image || this.productInfo.image,
points: this.attr.productSelect.price || this.productInfo.price,
integral: this.attr.productSelect.price || this.productInfo.price,
quantity: this.attr.productSelect.cart_num || 1,
attrValueId: this.attr.productSelect.unique || '',
unique: this.attr.productSelect.unique || '',
productAttrUnique: this.attr.productSelect.unique || '',
sku: this.attrValue || ''
};
uni.setStorageSync('buy_now_goods', goodsInfo);
this.isOpen = false;
this.attr.cartAttr = false;
// 跳转到积分商城下单页面
uni.navigateTo({
url: `/pages/integral/confirm?id=${this.goodsId}&quantity=${this.attr.productSelect.cart_num || 1}`
});
},
/**
* 获取购物车数量
* @param {Boolean} isAnima 是否展示购物车动画和重置属性
*/
getCartCount(isAnima) {
let that = this;
const isLogin = that.isLogin;
if (isLogin) {
getCartCounts(true, 'total').then(res => {
that.CartCount = res.data.count;
// 加入购物车后重置属性
if (isAnima) {
that.animated = true;
setTimeout(function() {
that.animated = false;
}, 500);
}
});
}
},
// 跳转购物车
goToCart() {
uni.navigateTo({
url: '/pages/integral/cart',
fail: (err) => {
console.error('跳转购物车失败:', err);
uni.showToast({
title: '跳转失败',
icon: 'none'
});
}
});
},
// 返回上一页
goBack() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss" scoped>
.goods-detail {
min-height: 100vh;
background-color: #F5F5F5;
padding-bottom: 120rpx;
}
// 顶部导航栏
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 113.344rpx;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0) 100%);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 31.996rpx;
z-index: 200;
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
}
.nav-back {
width: 72rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.3);
border-radius: 50%;
.iconfont {
font-size: 36rpx;
color: #FFFFFF;
}
}
.nav-title {
flex: 1;
font-size: 32rpx;
color: #FFFFFF;
font-weight: normal;
text-align: center;
line-height: 48rpx;
}
.nav-right {
width: 72rpx;
height: 64rpx;
}
// 图片轮播
.goods-swiper {
width: 100%;
height: 750rpx;
background-color: #FFFFFF;
}
.swiper-image {
width: 100%;
height: 100%;
}
// 仿照 goods_details 的 wrapper 样式
.wrapper {
background-color: #fff;
margin: 20rpx;
padding: 30rpx;
border-radius: 14rpx;
}
.share-section {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 20rpx;
}
.money-box {
display: flex;
align-items: baseline;
color: #FF4D4F;
}
.points-num {
font-size: 48rpx;
font-weight: bold;
}
.points-symbol {
font-size: 24rpx;
margin-right: 4rpx;
font-weight: bold;
}
.introduce {
font-size: 32rpx;
color: #282828;
font-weight: bold;
line-height: 1.5;
margin-bottom: 20rpx;
}
.label {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 24rpx;
color: #999;
}
// 仿照 goods_details 的 attribute 样式
.attribute {
background-color: #fff;
margin: 20rpx;
padding: 30rpx;
border-radius: 14rpx;
}
.attr-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.attr-text {
font-size: 28rpx;
color: #282828;
width: 600rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.selected-attr {
color: #666;
margin-left: 10rpx;
}
.arrow-right {
font-size: 24rpx;
color: #999;
}
// 兑换规则
.rules-section,
.detail-section {
margin: 20rpx;
padding: 30rpx;
background-color: #FFFFFF;
border-radius: 20rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333333;
margin-bottom: 24rpx;
}
.detail-info-list {
margin-bottom: 30rpx;
}
.detail-item {
font-size: 26rpx;
color: #666666;
margin-bottom: 16rpx;
line-height: 1.5;
}
// 底部操作栏
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: #FFFFFF;
border-top: 1px solid #EEEEEE;
padding: 20rpx 30rpx;
z-index: 100;
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
gap: 20rpx;
}
// 浮动购物车按钮
.floating-cart {
position: fixed;
right: 30rpx;
bottom: calc(240rpx + constant(safe-area-inset-bottom));
bottom: calc(240rpx + env(safe-area-inset-bottom));
width: 100rpx;
height: 100rpx;
background: #FFFFFF;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
z-index: 101;
.iconfont {
font-size: 44rpx;
color: #FF4D4F;
position: relative;
}
.cart-badge {
position: absolute;
top: -8rpx;
right: -16rpx;
background: linear-gradient(90deg, #FF6900 0%, #FF4D4F 100%);
color: #FFFFFF;
font-size: 18rpx;
padding: 2rpx 8rpx 3rpx;
border-radius: 200rpx;
min-width: 28rpx;
text-align: center;
}
.cart-text {
font-size: 18rpx;
color: #666666;
margin-top: 2rpx;
}
}
.animated {
animation-duration: 0.5s;
animation-fill-mode: both;
}
.bounceIn {
animation-name: bounceIn;
}
@keyframes bounceIn {
from,
20%,
40%,
60%,
80%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
transform: scale3d(0.97, 0.97, 0.97);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
.btn-group {
flex: 1;
display: flex;
gap: 20rpx;
}
.btn-cart,
.btn-buy {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 500;
}
.btn-cart {
background-color: #FFFFFF;
border: 1px solid #EEEEEE;
color: #333333;
}
.btn-buy {
background: linear-gradient(90deg, #FF6900 0%, #FF4D4F 100%);
color: #FFFFFF;
&.disabled {
background: #CCCCCC;
color: #FFFFFF;
}
}
.detail-content img, .detail-content image{
width: 100%;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,862 @@
<template>
<view class="order-detail-page">
<!-- 橙色头部 -->
<view class="header-section">
<view class="header-content">
<text class="header-icon" @click="goBack"></text>
<text class="header-title">订单详情</text>
<view class="header-placeholder"></view>
</view>
</view>
<scroll-view class="content" scroll-y>
<!-- 状态和进度卡片 -->
<view class="card-section status-card">
<!-- 状态头部 -->
<view class="status-header">
<text class="status-text">{{ orderInfo.status_text }}</text>
<text class="order-no-text">订单号: {{ orderInfo.order_no }}</text>
</view>
<!-- 进度条 -->
<view class="progress-bar">
<view
v-for="(step, index) in progressSteps"
:key="index"
class="progress-step"
:class="{ 'active': index <= currentProgressIndex, 'current': index === currentProgressIndex }"
>
<view class="step-icon-wrap">
<view class="step-icon">
<text class="icon-text">{{ getStepIcon(index) }}</text>
</view>
<view v-if="index < progressSteps.length - 1" class="step-line"></view>
</view>
<text class="step-text">{{ step.name }}</text>
</view>
</view>
</view>
<!-- 收货信息 -->
<view class="card-section address-card">
<view class="address-icon">
<text class="icon-location">📍</text>
</view>
<view class="address-content">
<view class="address-row">
<text class="receiver-name">{{ orderInfo.receiver_name }}</text>
<text class="receiver-phone">{{ orderInfo.receiver_phone }}</text>
</view>
<text class="address-text">{{ orderInfo.receiver_address }}</text>
</view>
</view>
<!-- 商品信息 -->
<view class="card-section goods-card">
<view class="card-header">
<text class="card-title">商品信息</text>
</view>
<!-- 商品列表 -->
<view class="goods-list">
<view
v-for="(goods, index) in orderInfo.orderInfoList"
:key="index"
class="goods-item"
>
<text class="goods-name">{{ goods.storeName || goods.productName }}</text>
<view class="goods-price-row">
<text class="goods-price">{{ formatPoints(goods.integral || goods.vipPrice || goods.price) }}积分</text>
<text class="goods-qty">x{{ goods.cartNum }}</text>
</view>
</view>
<!-- 无商品时显示默认 -->
<view v-if="!orderInfo.orderInfoList || orderInfo.orderInfoList.length === 0" class="goods-item">
<text class="goods-name">{{ orderInfo.goods_name }}</text>
<view class="goods-price-row">
<text class="goods-price">{{ formatPoints(orderInfo.points) }}积分</text>
<text class="goods-qty">x{{ orderInfo.quantity }}</text>
</view>
</view>
</view>
<!-- 汇总信息 -->
<view class="goods-summary">
<view class="summary-row">
<text class="summary-label">商品数量</text>
<text class="summary-value">{{ orderInfo.quantity }}</text>
</view>
<view class="summary-row">
<text class="summary-label">总计</text>
<text class="summary-total">{{ formatPoints(orderInfo.points) }}积分</text>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="card-section order-info-card">
<view class="card-header">
<text class="card-title">订单信息</text>
</view>
<view class="info-content">
<view class="info-row">
<text class="info-label">订单编号</text>
<text class="info-value" @click="copyText(orderInfo.order_no)">{{ orderInfo.order_no }}</text>
</view>
<view class="info-row">
<text class="info-label">下单时间</text>
<text class="info-value">{{ orderInfo.created_at }}</text>
</view>
<view class="info-row">
<text class="info-label">订单状态</text>
<text class="info-value status-value">{{ orderInfo.status_text }}</text>
</view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view v-if="showActions" class="bottom-actions">
<!-- 待兑换(未付款): 取消订单立即支付 -->
<template v-if="!orderInfo.paid">
<button class="action-btn cancel-btn" @click="cancelOrder">取消订单</button>
<button class="action-btn primary-btn" @click="goPay">立即兑换</button>
</template>
<!-- 待收货: 确认收货 -->
<template v-else-if="orderInfo.paid && orderInfo.status === 1">
<button class="action-btn primary-btn" @click="confirmReceipt">确认收货</button>
</template>
</view>
</view>
</template>
<script>
import { getOrderDetail, orderCancel, orderTake } from '@/api/order.js';
import { mapGetters } from 'vuex';
import { toLogin } from '@/libs/login.js';
export default {
data() {
return {
orderId: '',
orderInfo: {
id: '',
order_no: '',
goods_id: '',
goods_name: '',
goods_image: '',
quantity: 1,
points: 0,
status: 1,
paid: true,
status_text: '',
express_company: '',
express_no: '',
receiver_name: '',
receiver_phone: '',
receiver_address: '',
created_at: '',
pay_at: '',
ship_at: '',
finish_at: '',
orderInfoList: []
},
progressSteps: [
{ name: '待兑换', status: 0 },
{ name: '待发货', status: 1 },
{ name: '待收货', status: 2 },
{ name: '已完成', status: 3 }
],
loading: false
}
},
computed: {
...mapGetters(['isLogin']),
showActions() {
// 待兑换(未付款)显示取消按钮,待收货显示确认收货按钮
return (!this.orderInfo.paid) || (this.orderInfo.paid && this.orderInfo.status === 1)
},
// 计算总积分
totalPoints() {
return this.orderInfo.points || 0;
},
// 当前进度索引
currentProgressIndex() {
if (!this.orderInfo.paid) return 0; // 待兑换
// status: 0-待发货 1-待收货 2-待评价 3-已完成
if (this.orderInfo.status === 0) return 1; // 待发货
if (this.orderInfo.status === 1) return 2; // 待收货
if (this.orderInfo.status >= 2) return 3; // 已完成
return 0;
}
},
onLoad(options) {
if (options.id) {
this.orderId = options.id
this.loadOrderDetail()
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
// 格式化积分数字(添加千分位逗号)
formatPoints(points) {
if (!points && points !== 0) return '0'
return points.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
},
// 获取步骤图标
getStepIcon(index) {
const icons = ['⏱', '📦', '🚚', '✓'];
return icons[index] || '○';
},
// 加载订单详情
async loadOrderDetail() {
if (this.loading) return
// 检查登录状态
if (!this.isLogin) {
toLogin();
return;
}
this.loading = true
uni.showLoading({ title: '加载中...' })
try {
const res = await getOrderDetail(this.orderId);
if (res.data) {
const order = res.data;
const orderInfoList = order.orderInfoList || [];
// 获取第一个商品信息
let goodsName = '商品名称';
let goodsImage = '/static/images/default-goods.png';
let totalNum = order.totalNum || 1;
// 计算总积分优先使用payIntegral/useIntegral否则从商品列表计算
let totalPoints = 0;
// 优先使用订单级别的积分字段
if (order.payIntegral !== undefined && order.payIntegral !== null) {
totalPoints = parseFloat(order.payIntegral) || 0;
} else if (order.useIntegral !== undefined && order.useIntegral !== null) {
totalPoints = parseFloat(order.useIntegral) || 0;
} else if (order.integral !== undefined && order.integral !== null) {
totalPoints = parseFloat(order.integral) || 0;
} else {
// 从商品列表计算总积分
totalPoints = orderInfoList.reduce((sum, item) => {
// 优先使用integral字段否则使用vipPrice或price
const itemPoints = parseFloat(item.integral) || parseFloat(item.vipPrice) || parseFloat(item.price) || 0;
const quantity = parseInt(item.cartNum) || 1;
return sum + (itemPoints * quantity);
}, 0);
// 如果计算结果为0尝试使用payPrice或totalPrice
if (totalPoints === 0) {
totalPoints = parseFloat(order.payPrice) || parseFloat(order.totalPrice) || 0;
}
}
console.log('订单详情积分计算:', {
orderId: order.orderId,
payIntegral: order.payIntegral,
useIntegral: order.useIntegral,
integral: order.integral,
payPrice: order.payPrice,
totalPrice: order.totalPrice,
calculatedPoints: totalPoints,
orderInfoList: orderInfoList.map(item => ({
name: item.storeName,
integral: item.integral,
vipPrice: item.vipPrice,
price: item.price,
cartNum: item.cartNum
}))
});
if (orderInfoList.length > 0) {
const firstGoods = orderInfoList[0];
goodsName = firstGoods.storeName || firstGoods.productName || goodsName;
goodsImage = firstGoods.image || goodsImage;
}
// 格式化订单数据
this.orderInfo = {
id: order.id,
order_no: order.orderId || order.orderNo || '',
goods_id: orderInfoList.length > 0 ? orderInfoList[0].productId : '',
goods_name: goodsName,
goods_image: goodsImage,
quantity: totalNum,
points: totalPoints,
status: order.status,
paid: order.paid,
status_text: order.orderStatus || this.getStatusText(order.status, order.paid),
express_company: order.deliveryName || '',
express_no: order.deliveryId || '',
receiver_name: order.realName || '',
receiver_phone: order.userPhone || '',
receiver_address: order.userAddress || '',
created_at: order.createTime || '',
pay_at: order.payTime || '',
ship_at: order.deliveryTime || '',
finish_at: order.finishTime || '',
orderInfoList: orderInfoList
};
} else {
throw new Error(res.msg || '加载失败');
}
} catch (error) {
console.error('加载订单详情失败:', error)
uni.showToast({
title: error.msg || error.message || '加载失败',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} finally {
this.loading = false
uni.hideLoading()
}
},
// 获取状态文本
getStatusText(status, paid) {
if (!paid) return '待兑换';
const statusMap = {
0: '待发货',
1: '待收货',
2: '待评价',
3: '已完成'
};
return statusMap[status] || '未知状态';
},
// 取消订单
cancelOrder() {
uni.showModal({
title: '提示',
content: '确定要取消这个订单吗?积分将退回到您的账户',
confirmColor: '#F54900',
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: '取消中...' })
try {
await orderCancel(this.orderInfo.id);
uni.hideLoading()
uni.showToast({
title: '订单已取消',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.msg || '取消失败,请重试',
icon: 'none'
})
}
}
}
})
},
// 确认收货
confirmReceipt() {
uni.showModal({
title: '确认收货',
content: '确认已收到商品?',
confirmColor: '#F54900',
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: '确认中...' })
try {
const result = await orderTake(this.orderInfo.id);
console.log('确认收货接口返回:', result)
uni.hideLoading()
// 接口返回 code: 200 表示成功
if (result.code === 200 || result.code === 0) {
uni.showToast({
title: '确认收货成功',
icon: 'success'
})
// 重新加载订单详情
setTimeout(() => {
this.loadOrderDetail()
}, 1500)
} else {
uni.showToast({
title: result.msg || '操作失败,请重试',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('确认收货失败:', error)
uni.showToast({
title: error.msg || error.message || '操作失败,请重试',
icon: 'none'
})
}
}
}
})
},
// 复制文本
copyText(text) {
uni.setClipboardData({
data: text,
success: () => {
uni.showToast({
title: '已复制',
icon: 'success'
})
}
})
},
// 获取状态图标
getStatusIcon(status) {
const icons = {
1: '⏳', // 待发货
2: '🚚', // 待收货
3: '✅', // 已完成
4: '❌' // 已取消
}
return icons[status] || '📦'
},
// 获取状态提示
getStatusTip(status) {
if (!this.orderInfo.paid) {
return '请尽快完成支付,积分商品数量有限';
}
const tips = {
0: '商家正在准备发货,请耐心等待',
1: '商品已发货,请注意查收',
2: '请对本次交易进行评价',
3: '交易已完成,感谢您的支持'
}
return tips[status] || ''
},
// 去支付
goPay() {
uni.navigateTo({
url: `/pages/order/order_payment/index?orderNo=${this.orderInfo.order_no}&payPrice=${this.orderInfo.points}`
})
}
}
}
</script>
<style lang="scss" scoped>
.order-detail-page {
min-height: 100vh;
height: 100vh;
background-color: #F5F5F5;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
// 橙色头部
.header-section {
background-color: #F54900;
}
.header-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
}
.header-icon {
font-size: 48rpx;
color: #FFFFFF;
font-weight: 300;
width: 60rpx;
}
.header-title {
flex: 1;
font-size: 34rpx;
color: #FFFFFF;
font-weight: 500;
text-align: center;
}
.header-placeholder {
width: 60rpx;
}
.content {
flex: 1;
padding: 24rpx 32rpx;
padding-bottom: 140rpx;
box-sizing: border-box;
height: calc(100vh - 88rpx - 120rpx);
overflow-y: auto;
}
// 卡片通用样式
.card-section {
background-color: #FFFFFF;
border-radius: 24rpx;
margin-bottom: 24rpx;
overflow: hidden;
}
// 状态卡片
.status-card {
padding: 32rpx;
}
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
}
.status-text {
font-size: 36rpx;
color: #F54900;
font-weight: 500;
}
.order-no-text {
font-size: 26rpx;
color: #666666;
}
// 进度条
.progress-bar {
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
}
.progress-step {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
&.active {
.step-icon {
background-color: #F54900;
border-color: #F54900;
.icon-text {
color: #FFFFFF;
}
}
.step-text {
color: #F54900;
}
}
&.current {
.step-icon {
background-color: #F54900;
border-color: #F54900;
}
}
}
.step-icon-wrap {
position: relative;
display: flex;
align-items: center;
width: 100%;
justify-content: center;
}
.step-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background-color: #FFFFFF;
border: 4rpx solid #E5E7EB;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
position: relative;
}
.icon-text {
font-size: 28rpx;
color: #E5E7EB;
}
.step-line {
position: absolute;
top: 50%;
left: calc(50% + 36rpx);
width: calc(100% - 72rpx);
height: 4rpx;
background-color: #E5E7EB;
z-index: 1;
transform: translateY(-50%);
}
.progress-step.active .step-line {
background-color: #F54900;
}
.progress-step:last-child .step-line {
display: none;
}
.step-text {
margin-top: 16rpx;
font-size: 24rpx;
color: #999999;
white-space: nowrap;
text-align: center;
}
// 收货信息卡片
.address-card {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 20rpx;
padding: 32rpx;
}
.address-icon {
flex-shrink: 0;
}
.icon-location {
font-size: 36rpx;
}
.address-content {
flex: 1;
}
.address-row {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 12rpx;
}
.receiver-name {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
.receiver-phone {
font-size: 30rpx;
color: #333333;
}
.address-text {
font-size: 28rpx;
color: #666666;
line-height: 1.5;
}
// 商品信息卡片
.goods-card {
display: flex;
flex-direction: column;
}
.card-header {
padding: 32rpx;
border-bottom: 1rpx solid #F5F5F5;
}
.card-title {
font-size: 28rpx;
color: #999999;
font-weight: normal;
}
.goods-list {
padding: 0 32rpx;
}
.goods-item {
padding: 32rpx 0;
border-bottom: 1rpx solid #F5F5F5;
&:last-child {
border-bottom: none;
}
}
.goods-name {
font-size: 30rpx;
color: #333333;
line-height: 1.5;
margin-bottom: 16rpx;
display: block;
}
.goods-price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.goods-price {
font-size: 30rpx;
color: #F54900;
font-weight: 500;
}
.goods-qty {
font-size: 28rpx;
color: #999999;
}
// 商品汇总
.goods-summary {
padding: 24rpx 32rpx;
border-top: 1rpx solid #F5F5F5;
}
.summary-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.summary-label {
font-size: 28rpx;
color: #333333;
}
.summary-value {
font-size: 28rpx;
color: #333333;
}
.summary-total {
font-size: 32rpx;
color: #F54900;
font-weight: 500;
}
// 订单信息卡片
.order-info-card {
display: flex;
flex-direction: column;
}
.info-content {
padding: 24rpx 32rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.status-value {
color: #F54900 !important;
}
.info-label {
font-size: 28rpx;
color: #999999;
}
.info-value {
font-size: 28rpx;
color: #333333;
}
/* 底部操作栏 - 固定在页面底部 */
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #FFFFFF;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #EEEEEE;
display: flex;
justify-content: flex-end;
gap: 20rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.action-btn {
padding: 0 50rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
border: none;
&::after {
border: none;
}
}
.cancel-btn {
background-color: #F5F5F5;
color: #666666;
}
.primary-btn {
background-color: #F54900;
color: #FFFFFF;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,921 @@
<template>
<view class="points-page">
<!-- 白色导航栏 -->
<view class="nav-bar">
<view class="nav-back" @click="goBack">
<text class="iconfont icon-fanhui"></text>
</view>
<text class="nav-title">我的奖金</text>
<view class="nav-right-btn" @click="goToRushBuy">
<text class="rush-btn-text"> 采购 </text>
</view>
</view>
<!-- 奖金概览卡片 -->
<view class="balance-section">
<view class="balance-card">
<text class="balance-label">我的奖金</text>
<text class="balance-total">{{ totalBalance }}</text>
<!-- 奖金和积分卡片 -->
<view class="balance-cards">
<!-- 奖金卡片 -->
<view class="card prize-card">
<text class="card-label">奖金</text>
<text class="card-amount prize-amount">¥{{ prizeAmount }}</text>
<text class="card-tip">可提现</text>
</view>
<!-- 易积分卡片 -->
<view class="card points-card">
<view class="card-top-row">
<text class="card-label">易积分</text>
<view class="exchange-btn" @click="goToShop">
<text class="exchange-text">兑换</text>
</view>
</view>
<text class="card-amount points-amount">{{ pointsInfo.available_points }}</text>
<text class="card-tip">可兑换商品</text>
</view>
</view>
</view>
</view>
<!-- 标签栏 -->
<view class="tabs-section">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-btn"
:class="{ 'active': currentTab === index }"
@click="switchTab(index)"
>
<text class="tab-text">{{ tab.name }}</text>
</view>
</view>
<!-- 积分明细列表 -->
<scroll-view
class="points-list"
scroll-y
@scrolltolower="loadMore"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<view v-if="pointsList.length > 0" class="list-container">
<view
v-for="item in pointsList"
:key="item.id"
class="point-item"
>
<view class="item-info">
<view class="item-title-row">
<text class="item-title">{{ item.title }}</text>
<text class="item-source" v-if="item.source === 'points'">积分</text>
<!-- <text class="item-source bonus" v-else-if="item.source === 'bonus'">奖金</text> -->
</view>
<text class="item-time">{{ item.created_at }}</text>
</view>
<view class="item-right">
<text
class="item-points"
:class="{ 'income': item.type === 1, 'expense': item.type === 2 }"
>
{{ item.type === 1 ? '+' : '-' }}{{ formatNumber(item.points) }}
</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else-if="!loading" class="empty-state">
<image class="empty-image" src="/static/images/empty.png" mode="aspectFit"></image>
<text class="empty-text">暂无积分记录</text>
</view>
<!-- 加载更多 -->
<view v-if="pointsList.length > 0" class="load-more">
<text v-if="loading" class="load-text">加载中...</text>
<text v-else-if="noMore" class="load-text">没有更多了</text>
</view>
</scroll-view>
</view>
</template>
<script>
import { getWaUserInfo, getWaSelfBonusList, getIntegralList, loginV2, getUserInfo, getIntegralUserByAccount } from '@/api/user.js'
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['isLogin', 'userInfo','uid']),
// 我的奖金 = 奖金(prizeAmount) + 易积分(available_points)
totalBalance() {
const p = Number(this.prizeAmount) || 0
const a = Number(this.pointsInfo.available_points) || 0
return (p + a).toFixed(3)
},
// 获取用户账号
userAccount() {
if (this.userInfo) {
return this.userInfo.account || this.userInfo.phone || this.userInfo.phoneNumber || ''
}
return ''
},
// 获取寄卖商城用户ID优先使用wa_user_id否则使用uid
waUserId() {
// 优先使用明确的wa用户ID字段
if (this.userInfo && (this.userInfo.wa_user_id || this.userInfo.waUserId)) {
return this.userInfo.wa_user_id || this.userInfo.waUserId
}
// 使用store中的uid
if (this.uid) {
return this.uid
}
// 最后使用userInfo中的uid或id
if (this.userInfo) {
return this.userInfo.uid || this.userInfo.id
}
return null
},
// 获取用户手机号用于WA系统关联
userPhone() {
if (this.userInfo) {
return this.userInfo.phone || this.userInfo.mobile || this.userInfo.account
}
return null
}
},
data() {
return {
account: '', // 从URL获取的用户名
password: '123456',
prizeAmount: '0.00', // 可提现奖金money
pointsInfo: {
total_points: 0,
available_points: '0.00', // 易积分score
frozen_points: 0,
today_earn: 0
},
tabs: [
{ name: '收入明细', type: 1 },
{ name: '支出明细', type: 2 }
],
currentTab: 0,
pointsList: [],
page: 1,
limit: 20,
loading: false,
refreshing: false,
noMore: false
}
},
async onLoad(options) {
console.log('onLoad options:', options)
// 获取username优先从options获取否则从window.location获取兼容iframe嵌入场景
let username = (options && options.username) ? options.username : null
// 如果options中没有username尝试从URL中获取兼容H5 iframe嵌入
if (!username) {
username = this.getUrlParam('username')
console.log('从URL获取username:', username)
}
// URL中有username参数时必须调用autoLogin
if (username) {
console.log('获取到username:', username)
this.account = username
// 强制调用autoLogin无论当前是否已登录都要重新登录刷新用户信息
await this.autoLogin()
console.log('autoLogin执行完成isLogin:', this.isLogin)
} else {
// URL没有username时尝试从已登录的用户信息获取账号
if (this.isLogin && this.userInfo && this.userInfo.account) {
this.account = this.userInfo.account
}
// 未登录且有账号则自动登录
if (!this.isLogin && this.account) {
await this.autoLogin()
}
}
this.getUserPoints()
await this.loadUserInfo()
this.loadPointsList()
},
onShow() {
// 如果已登录但account为空从userInfo同步
if (this.isLogin && this.userInfo && this.userInfo.account && !this.account) {
this.account = this.userInfo.account
}
// 刷新用户信息和积分明细(需要登录后才能调用)
if (this.isLogin) {
this.getUserPoints()
this.loadUserInfo()
this.loadPointsList()
}
},
methods: {
// 从URL中获取参数兼容多种URL格式
getUrlParam(name) {
// #ifdef H5
try {
// 尝试从当前页面URL获取
let url = window.location.href
// 如果在iframe中尝试获取父页面URL
try {
if (window.parent && window.parent !== window) {
url = window.parent.location.href
}
} catch (e) {
// 跨域情况下无法访问parent.location
console.log('无法访问父页面URL')
}
console.log('解析URL:', url)
// 解析URL参数
const urlObj = new URL(url)
let value = urlObj.searchParams.get(name)
// 如果没找到尝试从hash中获取兼容hash路由
if (!value && url.includes('#')) {
const hashPart = url.split('#')[1]
if (hashPart && hashPart.includes('?')) {
const hashParams = new URLSearchParams(hashPart.split('?')[1])
value = hashParams.get(name)
}
}
return value
} catch (e) {
console.error('解析URL参数失败:', e)
return null
}
// #endif
return null
},
// 自动登录
async autoLogin() {
console.log('autoLogin开始执行account:', this.account)
// 检查账号是否存在
if (!this.account) {
console.warn('autoLogin: 账号为空,跳过登录')
return
}
try {
console.log('正在调用loginV2接口account:', this.account)
const res = await loginV2({
account: this.account,
password: this.password,
spread_spid: this.$Cache.get("spread") || ''
})
console.log("loginV2返回结果:", res)
if (res.data && res.data.token) {
// 保存token到store
this.$store.commit("LOGIN", {
token: res.data.token
})
console.log('token已保存')
// 保存UID
if (res.data.uid) {
this.$store.commit("SETUID", res.data.uid)
console.log('uid已保存:', res.data.uid)
}
// 获取并保存用户信息
console.log('正在获取用户信息...')
const userInfoRes = await getUserInfo()
console.log('getUserInfo返回:', userInfoRes)
if (userInfoRes.data) {
// 更新vuex storestore内部会自动保存到Cache
this.$store.commit("UPDATE_USERINFO", userInfoRes.data)
console.log('用户信息已更新到store:', userInfoRes.data)
// 同步account到本地变量确保后续API调用可用
if (userInfoRes.data.account) {
this.account = userInfoRes.data.account
} else if (userInfoRes.data.phone) {
this.account = userInfoRes.data.phone
}
}
console.log('自动登录成功完成')
} else {
console.warn('loginV2返回数据异常无token:', res)
}
} catch (error) {
console.error('自动登录失败:', error)
uni.showToast({
title: '自动登录失败',
icon: 'none',
duration: 2000
})
} finally {
uni.hideLoading()
}
},
// 返回上一页
goBack() {
uni.navigateBack()
},
// 跳转到抢购页面
goToRushBuy() {
// #ifdef H5
// window.location.href = 'https://shop.wenjinhui.com/?#/pages/personal/index'
//window.location.href = 'https://anyue.szxingming.com/?#/pages/personal/index'
window.location.href = 'https://xiashengjun.com/?#/pages/personal/index'
// window.location.href = 'http://shop.bosenyuan.com/?#/pages/personal/index'
// #endif
// #ifndef H5
uni.navigateTo({
url: '/pages/web-view/index?url=' + encodeURIComponent('https://xiashengjun.com/?#/pages/personal/index')
})
// #endif
},
// 获取用户积分
async getUserPoints() {
// 优先使用登录后的userInfo.account否则使用URL传入的account
const accountToUse = (this.userInfo && this.userInfo.account) || this.userAccount || this.account
if (!accountToUse) {
// 未提供账号时静默返回,不影响其他功能
return
}
try {
const res = await getIntegralUserByAccount(accountToUse)
if (res.data) {
this.pointsInfo.available_points = res.data.integral || 0
}
} catch (error) {
console.error('获取积分失败:', error)
}
},
// 加载寄卖商城用户信息
async loadUserInfo() {
console.log('当前userInfo:', this.userInfo)
console.log('waUserId:', this.waUserId)
console.log('userPhone:', this.userPhone)
// 优先使用waUserId如果没有则使用手机号
const userId = this.waUserId || this.userPhone
if (!userId) {
console.warn('未获取到寄卖商城用户ID或手机号')
return
}
try {
console.log('调用getWaUserInfouserId:', userId)
const res = await getWaUserInfo(userId)
// 接口返回 code: 0 表示成功
if (res.code === 0 && res.data) {
console.log('用户信息接口返回:', res.data)
const data = res.data
// 映射API返回数据到页面展示
// selfBonus -> 我的奖金
// this.totalBalance = this.formatDecimal(data.selfBonus || 0, 3)
// money -> 可提现奖金
this.prizeAmount = this.formatDecimal(data.selfBonus/2 || 0, 2)
}
} catch (error) {
console.error('加载用户信息失败:', error)
}
},
// 切换标签
switchTab(index) {
if (this.currentTab === index) return
this.currentTab = index
this.resetList()
this.loadPointsList()
},
// 重置列表
resetList() {
this.pointsList = []
this.page = 1
this.noMore = false
},
// 加载奖金明细列表
async loadPointsList() {
if (this.loading || this.noMore) return
// 优先使用waUserId如果没有则使用手机号
const userId = this.waUserId || this.userPhone
if (!userId) {
console.warn('未获取到寄卖商城用户ID或手机号')
this.loading = false
return
}
this.loading = true
try {
const type = this.tabs[this.currentTab].type
// 如果是支出明细type === 2同时加载奖金支出和积分支出
if (type === 2) {
// 并行加载奖金明细和积分明细
const [bonusRes, pointsRes] = await Promise.all([
getWaSelfBonusList({
userId: userId,
type: type,
page: this.page,
limit: this.limit
}),
getIntegralList({
page: this.page,
limit: this.limit,
type: 2
})
])
let bonusList = []
let pointsList = []
// 处理奖金明细
if (bonusRes.code === 0 && bonusRes.data) {
bonusList = (bonusRes.data.list || []).map(item => ({
id: `bonus_${item.id}`,
source: 'bonus', // 标记来源:奖金
type: item.type,
points: Math.abs(parseFloat(item.money) || 0),
balance: item.after,
title: item.memo || '奖金支出',
remark: item.memo || '',
created_at: item.createdAt || ''
}))
}
// 处理积分明细(使用 getIntegralList需要过滤出 type === 2 的支出记录)
if (pointsRes.code === 0 && pointsRes.data) {
// 过滤出支出记录type === 2兼容后端返回数字或字符串
const expenseRecords = (pointsRes.data.list || []).filter(item => Number(item.type) === 2)
pointsList = expenseRecords.map(item => ({
id: `points_${item.id}`,
source: 'points', // 标记来源:积分(与模板中的判断保持一致)
type: item.type || 2, // 积分明细的type2=支出
points: Math.abs(parseFloat(item.integral) || 0), // getIntegralList 返回的是 integral 字段
balance: 0, // getIntegralList 可能没有 balance 字段设为0
title: item.title || '积分支出',
remark: item.title || '',
created_at: item.updateTime || '' // getIntegralList 返回的是 updateTime 字段
}))
}
// 合并两个列表并按时间倒序排序
const mergedList = [...bonusList, ...pointsList].sort((a, b) => {
const timeA = new Date(a.created_at).getTime()
const timeB = new Date(b.created_at).getTime()
return timeB - timeA // 倒序:最新的在前
})
// 如果当前是第一页,直接替换;否则追加
if (this.page === 1) {
this.pointsList = mergedList
} else {
this.pointsList = [...this.pointsList, ...mergedList]
}
// 判断是否还有更多数据(两个接口都返回较少数据时认为没有更多)
const totalCount = bonusList.length + pointsList.length
if (totalCount < this.limit) {
this.noMore = true
}
} else {
// 收入明细只加载奖金明细
const res = await getWaSelfBonusList({
userId: userId,
type: type,
page: this.page,
limit: this.limit
})
// 接口返回 code: 0 表示成功
if (res.code === 0 && res.data) {
console.log('奖金明细接口返回:', res.data)
// 映射API返回数据到页面列表格式
const list = (res.data.list || []).map(item => ({
id: `bonus_${item.id}`,
source: 'bonus',
type: item.type,
points: Math.abs(parseFloat(item.money) || 0),
balance: item.after,
title: item.memo || (item.type === 1 ? '收入' : '支出'),
remark: item.memo || '',
created_at: item.createdAt || ''
}))
this.pointsList = this.page === 1 ? list : [...this.pointsList, ...list]
// 判断是否还有更多数据
if (list.length < this.limit || res.data.page >= res.data.totalPage) {
this.noMore = true
}
}
}
} catch (error) {
console.error('加载明细失败:', error)
uni.showToast({
title: '加载失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
this.refreshing = false
}
},
// 下拉刷新
onRefresh() {
this.refreshing = true
this.getUserPoints()
this.loadUserInfo()
this.resetList()
this.loadPointsList()
},
// 加载更多
loadMore() {
if (!this.noMore && !this.loading) {
this.page++
this.loadPointsList()
}
},
// 跳转到积分规则
goToRules() {
uni.navigateTo({
url: '/pages/integral/rules'
})
},
// 跳转到商城
goToShop() {
uni.navigateTo({
url: '/pages/integral/index'
})
},
// 格式化数字(保留指定位小数)
formatNumber(num) {
if (num === null || num === undefined) return '0.000'
return Number(num).toFixed(3)
},
// 格式化小数(指定精度)
formatDecimal(num, precision = 3) {
if (num === null || num === undefined) return '0.' + '0'.repeat(precision)
return Number(num).toFixed(precision)
},
// 获取类型图标
getTypeIcon(type, title) {
if (title.includes('签到')) return '📅'
if (title.includes('购物')) return '🛍️'
if (title.includes('邀请')) return '👥'
if (title.includes('分享')) return '📤'
if (title.includes('兑换')) return '🎁'
if (title.includes('系统')) return '⚙️'
return type === 1 ? '📈' : '📉'
}
}
}
</script>
<style lang="scss" scoped>
.points-page {
min-height: 100vh;
background-color: #F9FAFB;
display: flex;
flex-direction: column;
}
// 白色导航栏
.nav-bar {
background-color: #FFFFFF;
height: 113.344rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 31.996rpx;
border-bottom: 1.344rpx solid rgba(0, 0, 0, 0.1);
}
.nav-back {
width: 72rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 36rpx;
color: #0A0A0A;
}
}
.nav-title {
flex: 1;
font-size: 32rpx;
color: #0A0A0A;
font-weight: normal;
text-align: center;
line-height: 48rpx;
}
.nav-right-btn {
background: linear-gradient(90deg, #FF6900 0%, #F54900 100%);
border-radius: 24rpx;
padding: 8rpx 24rpx;
display: flex;
align-items: center;
justify-content: center;
}
.rush-btn-text {
font-size: 28rpx;
color: #FFFFFF;
font-weight: 500;
line-height: 40rpx;
}
// 余额卡片区域
.balance-section {
padding: 30rpx 31rpx 0;
}
.balance-card {
background-color: #FFFFFF;
border: 1rpx solid rgba(0, 0, 0, 0.1);
border-radius: 28rpx;
padding: 40rpx;
display: flex;
flex-direction: column;
gap: 30rpx;
}
.balance-label {
font-size: 32rpx;
color: #4A5565;
display: block;
line-height: 48rpx;
}
.balance-total {
font-size: 60rpx;
color: #0A0A0A;
font-weight: normal;
display: block;
line-height: 72rpx;
}
// 奖金和积分卡片
.balance-cards {
display: flex;
gap: 16rpx;
}
.card {
flex: 1;
border-radius: 20rpx;
padding: 31.996rpx;
min-height: 231.92rpx;
display: flex;
flex-direction: column;
gap: 15.988rpx;
}
.prize-card {
background: linear-gradient(140.01deg, #FFF7ED 0%, #FFEDD4 100%);
}
.points-card {
background: linear-gradient(140.01deg, #EFF6FF 0%, #DBEAFE 100%);
}
.card-top-row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.card-label {
font-size: 28rpx;
color: #4A5565;
line-height: 40rpx;
display: block;
}
.card-amount {
font-size: 48rpx;
font-weight: normal;
line-height: 64rpx;
display: block;
}
.prize-amount {
color: #F54900;
}
.points-amount {
color: #155DFC;
}
.card-tip {
font-size: 24rpx;
color: #6A7282;
line-height: 32rpx;
}
.exchange-btn {
background-color: #155DFC;
border-radius: 16rpx;
padding: 0 24rpx;
height: 47.984rpx;
display: flex;
align-items: center;
justify-content: center;
}
.exchange-text {
font-size: 28rpx;
color: #FFFFFF;
font-weight: 500;
line-height: 40rpx;
}
// 标签栏(底部边框设计)
.tabs-section {
padding: 36rpx;
display: flex;
border-bottom: 2rpx solid rgba(0, 0, 0, 0.1);
position: relative;
}
.tab-btn {
flex: 1;
height: 95.99rpx;
line-height: 95.99rpx;
text-align: center;
background-color: transparent;
position: relative;
padding: 9.344rpx 17.344rpx;
display: flex;
align-items: center;
justify-content: center;
&.active {
border-bottom: 2rpx solid #F54900;
border-left: 2rpx solid #F54900;
border-right: 2rpx solid #F54900;
border-top: 2rpx solid #F54900;
.tab-text {
color: #F54900;
font-weight: 500;
}
}
}
.tab-text {
font-size: 28rpx;
color: #0A0A0A;
line-height: 40rpx;
transition: all 0.3s;
}
.points-list {
flex: 1;
padding: 1rpx 31.996rpx 0;
}
.list-container {
padding-bottom: 23.992rpx;
display: flex;
flex-direction: column;
gap: 23.992rpx;
}
.point-item {
background-color: #FFFFFF;
border: 1.344rpx solid rgba(0, 0, 0, 0.1);
border-radius: 28rpx;
padding: 33.34rpx 33.34rpx 1.344rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.item-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 7.984rpx;
}
.item-title-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.item-title {
font-size: 32rpx;
color: #0A0A0A;
font-weight: normal;
line-height: 48rpx;
}
.item-source {
font-size: 20rpx;
color: #FFFFFF;
background-color: #155DFC;
border-radius: 8rpx;
padding: 4rpx 12rpx;
line-height: 28rpx;
&.bonus {
background-color: #F54900;
}
}
.item-time {
font-size: 28rpx;
color: #6A7282;
line-height: 40rpx;
}
.item-right {
margin-left: 40rpx;
}
.item-points {
font-size: 40rpx;
font-weight: normal;
line-height: 56rpx;
&.income {
color: #00A63E;
}
&.expense {
color: #0A0A0A;
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
}
.empty-image {
width: 300rpx;
height: 300rpx;
margin-bottom: 40rpx;
}
.empty-text {
font-size: 28rpx;
color: #999999;
}
.load-more {
padding: 47.954rpx 0;
text-align: center;
}
.load-text {
font-size: 32rpx;
color: #99A1AF;
line-height: 48rpx;
}
</style>

View File

@@ -0,0 +1,230 @@
<template>
<view class="rules-page">
<scroll-view class="content" scroll-y>
<view class="rules-content" v-html="rulesContent"></view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
rulesContent: ''
}
},
onLoad() {
this.loadRules()
},
methods: {
async loadRules() {
uni.showLoading({ title: '加载中...' })
try {
// TODO: 调用积分规则API
// const res = await this.$api.integral.getPointsRules()
// 模拟数据 - 使用HTML格式
const res = {
code: 0,
data: {
content: this.getDefaultRules()
}
}
if (res.code === 0) {
this.rulesContent = res.data.content
}
} catch (error) {
console.error('加载积分规则失败:', error)
uni.showToast({
title: '加载失败',
icon: 'none'
})
// 显示默认规则
this.rulesContent = this.getDefaultRules()
} finally {
uni.hideLoading()
}
},
getDefaultRules() {
return `
<div class="rules-section">
<h2 class="section-title">如何获得积分</h2>
<div class="rule-item">
<h3 class="item-title">📅 每日签到</h3>
<ul class="item-list">
<li>连续签到第1天10积分</li>
<li>连续签到第2天20积分</li>
<li>连续签到第3天30积分</li>
<li>连续签到第4天40积分</li>
<li>连续签到第5天50积分</li>
<li>连续签到第6天60积分</li>
<li>连续签到第7天100积分</li>
</ul>
<p class="item-note">注:中断签到后重新开始计算</p>
</div>
<div class="rule-item">
<h3 class="item-title">🛍️ 购物返积分</h3>
<ul class="item-list">
<li>消费1元返1积分</li>
<li>单笔订单最高返500积分</li>
<li>订单完成后自动到账</li>
<li>退货退款将扣除对应积分</li>
</ul>
</div>
<div class="rule-item">
<h3 class="item-title">👥 邀请好友</h3>
<ul class="item-list">
<li>成功邀请1位好友注册50积分</li>
<li>好友首次下单额外100积分</li>
<li>无邀请上限,多邀多得</li>
</ul>
</div>
<div class="rule-item">
<h3 class="item-title">📤 分享商品</h3>
<ul class="item-list">
<li>分享商品到朋友圈10积分/次</li>
<li>每日最多获得30积分</li>
</ul>
</div>
<div class="rule-item">
<h3 class="item-title">🎁 新人奖励</h3>
<ul class="item-list">
<li>注册成功500积分</li>
<li>首次下单额外500积分</li>
</ul>
</div>
</div>
<div class="rules-section">
<h2 class="section-title">积分使用规则</h2>
<div class="rule-item">
<ul class="item-list">
<li>100积分 = 1元</li>
<li>积分不可提现</li>
<li>积分有效期2年</li>
<li>积分可用于兑换积分商城商品</li>
<li>兑换订单取消后积分自动退回</li>
<li>部分商品可能有限购要求</li>
</ul>
</div>
</div>
<div class="rules-section">
<h2 class="section-title">注意事项</h2>
<div class="rule-item">
<ul class="item-list warning">
<li>严禁通过非法手段刷积分,一经发现将清零并封号</li>
<li>积分不可转赠他人</li>
<li>积分过期后自动清零,不予找回</li>
<li>平台有权根据运营需要调整积分规则</li>
<li>如有疑问请联系客服</li>
</ul>
</div>
</div>
<div class="rules-footer">
<p>本规则最终解释权归平台所有</p>
<p>更新时间2025年12月17日</p>
</div>
`
}
}
}
</script>
<style lang="scss" scoped>
.rules-page {
min-height: 100vh;
background-color: #F5F5F5;
}
.content {
height: 100vh;
}
.rules-content {
padding: 30rpx;
::v-deep .rules-section {
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
::v-deep .section-title {
font-size: 36rpx;
color: #333333;
font-weight: bold;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #F0F0F0;
}
::v-deep .rule-item {
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
}
::v-deep .item-title {
font-size: 30rpx;
color: #333333;
font-weight: bold;
margin-bottom: 20rpx;
}
::v-deep .item-list {
padding-left: 40rpx;
li {
font-size: 28rpx;
color: #666666;
line-height: 2;
list-style: disc;
margin-bottom: 8rpx;
}
&.warning li {
color: #FF4D4F;
}
}
::v-deep .item-note {
font-size: 24rpx;
color: #999999;
margin-top: 16rpx;
padding: 16rpx;
background-color: #FFF9E6;
border-radius: 8rpx;
}
::v-deep .rules-footer {
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 40rpx 30rpx;
text-align: center;
p {
font-size: 24rpx;
color: #999999;
line-height: 1.8;
}
}
}
</style>

View File

@@ -0,0 +1,531 @@
<template>
<view class="search-page">
<!-- 搜索框 -->
<view class="search-bar">
<view class="search-input-wrapper">
<text class="search-icon">🔍</text>
<input
class="search-input"
v-model="keyword"
placeholder="搜索商品名称"
placeholder-class="placeholder"
confirm-type="search"
@input="onInput"
@confirm="doSearch"
:focus="true"
/>
<text v-if="keyword" class="clear-icon" @click="clearKeyword"></text>
</view>
<text class="cancel-btn" @click="goBack">取消</text>
</view>
<!-- 搜索历史/热门搜索 -->
<view v-if="!keyword && searchResult.length === 0" class="search-suggest">
<!-- 搜索历史 -->
<view v-if="searchHistory.length > 0" class="suggest-section">
<view class="section-header">
<text class="section-title">搜索历史</text>
<text class="clear-all" @click="clearHistory">清空</text>
</view>
<view class="tags-list">
<view
v-for="(item, index) in searchHistory"
:key="index"
class="tag-item"
@click="searchByKeyword(item)"
>
{{ item }}
</view>
</view>
</view>
<!-- 热门搜索 -->
<view class="suggest-section">
<view class="section-header">
<text class="section-title">热门搜索</text>
</view>
<view class="tags-list">
<view
v-for="(item, index) in hotSearchList"
:key="index"
class="tag-item hot"
@click="searchByKeyword(item)"
>
<text v-if="index < 3" class="hot-badge">🔥</text>
{{ item }}
</view>
</view>
</view>
</view>
<!-- 搜索结果 -->
<scroll-view
v-else
class="search-result"
scroll-y
@scrolltolower="loadMore"
>
<view v-if="searchResult.length > 0" class="result-list">
<view
v-for="goods in searchResult"
:key="goods.id"
class="goods-item"
@click="goToDetail(goods.id)"
>
<image class="goods-image" :src="goods.image" mode="aspectFill"></image>
<view class="goods-info">
<text class="goods-name" v-html="highlightKeyword(goods.name)"></text>
<view class="goods-bottom">
<view class="goods-price">
<text class="points">{{ goods.points }}</text>
<text class="points-text">积分</text>
</view>
<text class="goods-sales">已兑{{ goods.sales }}</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else-if="!loading && keyword" class="empty-state">
<image class="empty-image" src="/static/images/empty.png" mode="aspectFit"></image>
<text class="empty-text">没有找到相关商品</text>
<text class="empty-tip">换个关键词试试吧</text>
</view>
<!-- 加载更多 -->
<view v-if="searchResult.length > 0" class="load-more">
<text v-if="loading" class="load-text">搜索中...</text>
<text v-else-if="noMore" class="load-text">没有更多了</text>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
keyword: '',
searchHistory: [],
hotSearchList: [
'iPhone',
'小米手机',
'华为耳机',
'iPad',
'美的电饭煲',
'优惠券',
'零食大礼包',
'AirPods'
],
searchResult: [],
page: 1,
limit: 20,
loading: false,
noMore: false,
searchTimer: null
}
},
onLoad(options) {
if (options.keyword) {
this.keyword = options.keyword
this.doSearch()
}
this.loadSearchHistory()
},
methods: {
// 输入事件 - 实时搜索
onInput(e) {
this.keyword = e.detail.value
// 防抖处理
if (this.searchTimer) {
clearTimeout(this.searchTimer)
}
if (this.keyword) {
this.searchTimer = setTimeout(() => {
this.doSearch()
}, 500)
} else {
this.searchResult = []
}
},
// 执行搜索
async doSearch() {
if (!this.keyword.trim()) return
this.resetList()
await this.loadSearchResult()
this.saveSearchHistory()
},
// 重置列表
resetList() {
this.searchResult = []
this.page = 1
this.noMore = false
},
// 加载搜索结果
async loadSearchResult() {
if (this.loading || this.noMore) return
this.loading = true
try {
// TODO: 调用搜索API
// const res = await this.$api.integral.searchGoods({
// keyword: this.keyword,
// page: this.page,
// limit: this.limit
// })
// 模拟数据
const res = {
code: 0,
data: {
list: this.getMockGoods(),
total: 50
}
}
if (res.code === 0) {
const list = res.data.list || []
this.searchResult = this.page === 1 ? list : [...this.searchResult, ...list]
if (list.length < this.limit) {
this.noMore = true
}
}
} catch (error) {
console.error('搜索失败:', error)
uni.showToast({
title: '搜索失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
// 加载更多
loadMore() {
if (!this.noMore && !this.loading) {
this.page++
this.loadSearchResult()
}
},
// 点击搜索词
searchByKeyword(keyword) {
this.keyword = keyword
this.doSearch()
},
// 清空关键词
clearKeyword() {
this.keyword = ''
this.searchResult = []
},
// 高亮关键词
highlightKeyword(text) {
if (!this.keyword) return text
const reg = new RegExp(this.keyword, 'gi')
return text.replace(reg, `<span style="color: #FF4D4F">${this.keyword}</span>`)
},
// 保存搜索历史
saveSearchHistory() {
if (!this.keyword.trim()) return
// 去重并添加到首位
const history = this.searchHistory.filter(item => item !== this.keyword)
history.unshift(this.keyword)
// 最多保存10条
this.searchHistory = history.slice(0, 10)
// 保存到本地
uni.setStorageSync('integral_search_history', this.searchHistory)
},
// 加载搜索历史
loadSearchHistory() {
try {
const history = uni.getStorageSync('integral_search_history')
if (history) {
this.searchHistory = history
}
} catch (error) {
console.error('加载搜索历史失败:', error)
}
},
// 清空搜索历史
clearHistory() {
uni.showModal({
title: '提示',
content: '确定要清空搜索历史吗?',
success: (res) => {
if (res.confirm) {
this.searchHistory = []
uni.removeStorageSync('integral_search_history')
uni.showToast({
title: '已清空',
icon: 'success'
})
}
}
})
},
// 跳转商品详情
goToDetail(goodsId) {
uni.navigateTo({
url: `/pages/integral/detail?id=${goodsId}`
})
},
// 返回
goBack() {
uni.navigateBack()
},
// 模拟商品数据
getMockGoods() {
const goods = []
for (let i = 0; i < 10; i++) {
goods.push({
id: Date.now() + i,
name: `${this.keyword}商品${i + 1}`,
image: 'https://via.placeholder.com/200',
points: 1000 + i * 100,
sales: Math.floor(Math.random() * 1000)
})
}
return goods
}
}
}
</script>
<style lang="scss" scoped>
.search-page {
min-height: 100vh;
background-color: #F5F5F5;
display: flex;
flex-direction: column;
}
.search-bar {
background-color: #FFFFFF;
padding: 20rpx 30rpx;
display: flex;
align-items: center;
gap: 20rpx;
}
.search-input-wrapper {
flex: 1;
background-color: #F5F5F5;
border-radius: 40rpx;
padding: 0 30rpx;
display: flex;
align-items: center;
height: 70rpx;
}
.search-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #333333;
}
.placeholder {
color: #999999;
}
.clear-icon {
width: 36rpx;
height: 36rpx;
line-height: 36rpx;
text-align: center;
font-size: 24rpx;
color: #999999;
background-color: #DDDDDD;
border-radius: 50%;
}
.cancel-btn {
font-size: 28rpx;
color: #333333;
}
.search-suggest {
padding: 30rpx;
}
.suggest-section {
margin-bottom: 40rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.section-title {
font-size: 30rpx;
color: #333333;
font-weight: bold;
}
.clear-all {
font-size: 26rpx;
color: #999999;
}
.tags-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.tag-item {
padding: 14rpx 28rpx;
background-color: #F5F5F5;
border-radius: 40rpx;
font-size: 26rpx;
color: #666666;
&.hot {
background-color: #FFF1F0;
color: #FF4D4F;
}
}
.hot-badge {
margin-right: 4rpx;
}
.search-result {
flex: 1;
padding: 20rpx 30rpx;
}
.result-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.goods-item {
width: calc((100% - 20rpx) / 2);
background-color: #FFFFFF;
border-radius: 16rpx;
overflow: hidden;
}
.goods-image {
width: 100%;
height: 330rpx;
background-color: #F5F5F5;
}
.goods-info {
padding: 20rpx;
}
.goods-name {
font-size: 28rpx;
color: #333333;
line-height: 1.5;
height: 84rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
::v-deep span {
color: #FF4D4F;
font-weight: bold;
}
}
.goods-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 16rpx;
}
.goods-price {
display: flex;
align-items: baseline;
}
.points {
font-size: 36rpx;
color: #FF4D4F;
font-weight: bold;
}
.points-text {
font-size: 24rpx;
color: #FF4D4F;
margin-left: 4rpx;
}
.goods-sales {
font-size: 24rpx;
color: #999999;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
}
.empty-image {
width: 300rpx;
height: 300rpx;
margin-bottom: 40rpx;
}
.empty-text {
font-size: 30rpx;
color: #333333;
margin-bottom: 16rpx;
}
.empty-tip {
font-size: 26rpx;
color: #999999;
}
.load-more {
padding: 30rpx 0;
text-align: center;
}
.load-text {
font-size: 26rpx;
color: #999999;
}
</style>