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:
1192
single_uniapp22miao/pages/integral/cart.vue
Normal file
1192
single_uniapp22miao/pages/integral/cart.vue
Normal file
File diff suppressed because it is too large
Load Diff
1099
single_uniapp22miao/pages/integral/confirm.vue
Normal file
1099
single_uniapp22miao/pages/integral/confirm.vue
Normal file
File diff suppressed because it is too large
Load Diff
849
single_uniapp22miao/pages/integral/detail.vue
Normal file
849
single_uniapp22miao/pages/integral/detail.vue
Normal 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>
|
||||
1059
single_uniapp22miao/pages/integral/index.vue
Normal file
1059
single_uniapp22miao/pages/integral/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
862
single_uniapp22miao/pages/integral/order-detail.vue
Normal file
862
single_uniapp22miao/pages/integral/order-detail.vue
Normal 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>
|
||||
|
||||
1013
single_uniapp22miao/pages/integral/orders.vue
Normal file
1013
single_uniapp22miao/pages/integral/orders.vue
Normal file
File diff suppressed because it is too large
Load Diff
921
single_uniapp22miao/pages/integral/points.vue
Normal file
921
single_uniapp22miao/pages/integral/points.vue
Normal 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 store(store内部会自动保存到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('调用getWaUserInfo,userId:', 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, // 积分明细的type,2=支出
|
||||
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>
|
||||
|
||||
230
single_uniapp22miao/pages/integral/rules.vue
Normal file
230
single_uniapp22miao/pages/integral/rules.vue
Normal 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>
|
||||
|
||||
531
single_uniapp22miao/pages/integral/search.vue
Normal file
531
single_uniapp22miao/pages/integral/search.vue
Normal 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>
|
||||
|
||||
Reference in New Issue
Block a user