优惠卷

This commit is contained in:
zpc 2025-06-22 12:31:15 +08:00
parent c2b3f4d268
commit f4d3ca854a
9 changed files with 753 additions and 69 deletions

View File

@ -26,5 +26,28 @@ let pageContainer = ref(null);
</script>
<style lang="scss" scoped></style>
```
# 新文件2
```vue
<template>
<page-container ref="pageContainer" title="优惠卷" show-back :show-not-data="showNotData" :refresh="onRefresh">
</page-container>
</template>
<script setup>
//# 必须要引入
import PageContainer from '@/components/youdas-container/page-container.vue'
let pageContainer = ref(null);
let showNotData = ref(false);
// 刷新方法
const onRefresh = async (paging) => {
await yds.sleep(500);
paging.complete();
};
</script>
<style lang="scss" scoped></style>
```

View File

@ -57,9 +57,9 @@ export const mobileLogin = async (phone, code, pid = 0) => {
* @returns {Promise} 是否成功
*/
export const updateUserInfo = async (nickname, avatar, imagebase, gender = 3) => {
const res = await HttpRequest.post('/update_userinfo', {
nickname,
headimg: avatar,
const res = await HttpRequest.post('/update_userinfo', {
nickname,
headimg: avatar,
imagebase,
gender
});
@ -67,3 +67,20 @@ export const updateUserInfo = async (nickname, avatar, imagebase, gender = 3) =>
}
/**
* 获取用户优惠卷
* @param {Number} status 状态(0-未使用 1-已使用 2-已过期)
* @param {Number} page 页码
* @param {Number} pageSize 每页条数
* @returns {Promise} 优惠卷列表
*/
export const getUserCoupon = async (status, page, pageSize=10) => {
const res = await HttpRequest.get('/get_coupon_list', {
type: 1,
status: status,
page,
limit: pageSize,
});
return res;
}

View File

@ -30,6 +30,13 @@ export const getAccountInfo = async () => {
});
}
/**
* 清除用户信息
*/
export const clearAccountInfo = async () => {
removeLocalStorage('user');
}
/**
* 判断用户是否登录,如果用户未登录会跳转到登录页面
* @returns {Boolean} 是否登录

View File

@ -10,6 +10,9 @@
<view class="page-container__not-data" v-if="showNotData">
<no-data />
</view>
<view class="page-container__loading" v-if="showLoading" @click.stop="mengban">
<loading-data />
</view>
<slot></slot>
</view>
</view>
@ -38,6 +41,10 @@ const props = defineProps({
default: function (refresh) {
refresh.complete();
}
},
showLoading: {
type: Boolean,
default: false
}
});
let paging = ref(null);
@ -60,6 +67,9 @@ const onRefresh = () => {
}
const getPaging = () => {
return paging.value;
}
const mengban = () => {
}
defineExpose({
getPaging
@ -101,5 +111,17 @@ defineExpose({
justify-content: center;
align-items: center;
}
&__loading {
width: 100%;
height: 100%;
position: fixed;
top: 0px;
left: 0;
z-index: 1000;
justify-content: center;
align-items: center;
// background-color: rgba(0, 0, 0, 0.5);
}
}
</style>

View File

@ -63,6 +63,12 @@
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/other/coupon",
"style": {
"navigationStyle": "custom"
}
}
],
// "globalStyle": {

View File

@ -1,57 +1,57 @@
<!-- 详情 -->
<template>
<view>
<page-container ref="pageContainer" title="我的信息" show-back :show-not-data="false" :refresh="onRefresh">
<view class="content">
<view class="form-container">
<view class="form-item" @click="onClickAvatar">
<view class="form-label">头像:</view>
<view class="form-input avatar-container">
<image class="avatar" :src="avatarUrl"></image>
<image class="arrow-icon" src="/static/app-plus/arrow-right.png" mode="aspectFill"
style="width: 24rpx;height: 24rpx;"></image>
</view>
</view>
<view class="form-item">
<view class="form-label">昵称:</view>
<view class="form-input">
<input type="text" v-model="nickname" placeholder="请输入您的昵称" />
</view>
</view>
<page-container ref="pageContainer" title="我的信息" show-back :show-not-data="false" :refresh="onRefresh">
<view class="content">
<view class="form-container">
<view class="form-item" @click="onClickAvatar">
<view class="form-label">头像:</view>
<view class="form-input avatar-container">
<image class="avatar" :src="avatarUrl"></image>
<image class="arrow-icon" src="/static/app-plus/arrow-right.png" mode="aspectFill"
style="width: 24rpx;height: 24rpx;"></image>
<view class="form-item" @click="copyUid">
<view class="form-label">ID:</view>
<view class="form-input">
<text class="form-text">{{ uid }}</text>
<image class="copy-icon" src="/static/app-plus/copy.png" mode="aspectFill"
style="width: 24rpx;height: 24rpx;"></image>
</view>
</view>
<view class="form-item" @click="showGenderPicker">
<view class="form-label">性别:</view>
<view class="form-input">
<text class="form-text">{{ genderText || '请选择性别' }}</text>
<image class="arrow-icon" src="/static/ic_arrow.png" mode="aspectFill"
style="width: 24rpx;height: 24rpx;"></image>
</view>
</view>
<view class="form-item">
<view class="form-label">已绑定手机号:</view>
<view class="form-input">
<input disabled :value="phoneNumber" placeholder="暂未绑定手机号" />
</view>
</view>
</view>
<view class="submit-btn" @click="handleUpdateUserInfo">确认修改</view>
<view class="form-item">
<view class="form-label">昵称:</view>
<view class="form-input">
<input type="text" v-model="nickname" placeholder="请输入您的昵称" />
</view>
</view>
<view class="form-item" @click="copyUid">
<view class="form-label">ID:</view>
<view class="form-input">
<text class="form-text">{{ uid }}</text>
<image class="copy-icon" src="/static/app-plus/copy.png" mode="aspectFill"
style="width: 24rpx;height: 24rpx;"></image>
</view>
</view>
<view class="form-item" @click="showGenderPicker">
<view class="form-label">性别:</view>
<view class="form-input">
<text class="form-text">{{ genderText || '请选择性别' }}</text>
<image class="arrow-icon" src="/static/ic_arrow.png" mode="aspectFill"
style="width: 24rpx;height: 24rpx;"></image>
</view>
</view>
<view class="form-item">
<view class="form-label">已绑定手机号:</view>
<view class="form-input">
<input disabled :value="phoneNumber" placeholder="暂未绑定手机号" />
</view>
</view>
</view>
</page-container>
</view>
<view class="submit-btn" @click="handleUpdateUserInfo">确认修改</view>
</view>
</page-container>
</template>
<script setup>
@ -74,15 +74,17 @@ const phoneNumber = ref(""); // 已绑定手机号
//
onMounted(async () => {
const userInfo = await getAccountInfo();
if (userInfo) {
avatarUrl.value = userInfo.userIcon || "";
nickname.value = userInfo.username || "";
uid.value = userInfo.uid || "";
gender.value = userInfo.gender || 3; //
gender.value = yds.getCache("user_gender") || 3; //
genderText.value = gender.value === 1 ? "男" : (gender.value === 2 ? "女" : "保密");
phoneNumber.value = userInfo.mobile || "";
}
console.log(userInfo);
});
@ -125,27 +127,22 @@ const handleUpdateUserInfo = async () => {
}
const result = await updateUserInfo(
nickname.value,
avatarUrl.value,
nickname.value,
avatarUrl.value,
imageBase64.value,
gender.value
);
if (result) {
uni.showToast({
title: "修改成功",
icon: "success",
});
//
setTimeout(() => {
uni.navigateBack();
}, 1000);
yds.showToast("修改成功");
yds.userInfo.clearAccountInfo();
yds.setCache("user_gender", gender.value);
// //
// setTimeout(() => {
// uni.navigateBack();
// }, 1000);
} else {
uni.showToast({
title: "修改失败",
icon: "none",
});
yds.showToast("修改失败");
}
};
@ -232,7 +229,7 @@ const onRefresh = async (paging) => {
font-size: 24rpx;
color: #333333;
}
.copy-icon {
margin-left: 10rpx;
width: 24rpx;
@ -244,7 +241,7 @@ const onRefresh = async (paging) => {
width: 24rpx;
height: 24rpx;
}
.form-text {
flex: 1;
text-align: right;
@ -271,7 +268,7 @@ const onRefresh = async (paging) => {
display: flex;
align-items: center;
justify-content: flex-end;
.avatar {
width: 80rpx;
height: 80rpx;

View File

@ -83,6 +83,9 @@ const loadMenu = async () => {
}, {
id: 3,
title: "优惠券",
onClick: (res) => {
navigateTo("/pages/other/coupon");
}
}, {
id: 4,
title: "收货地址管理",

238
pages/other/coupon.vue Normal file
View File

@ -0,0 +1,238 @@
<template>
<page-container ref="pageContainer" title="优惠卷" show-back :show-not-data="showNotData" :refresh="onRefresh"
:showLoading="showLoading">
<view class="tab-list">
<view @click="tabChange(i)" v-for="(item, i) in tabList" :key="i" class="tab-list-item"
:class="{ active: tabCur == i }">
{{ item.title }}
<view v-if="tabCur == i" class="arrow"></view>
</view>
</view>
<view v-if="listData.length > 0">
<view class="list-item" v-for="(item, i) in listData" :key="i" :class="{ dis: tabList[tabCur].type == 2 }">
<view class="money">
¥
<text>{{ Number(item.price) }}</text>
</view>
<view class="info">
<view class="title">{{ item.man_price }}{{ item.price }}</view>
<view class="time">{{ item.end_time }}到期</view>
</view>
<view class="btn">
{{ item.mark }}
</view>
</view>
</view>
<view v-else class="not-data">
<NoData></NoData>
</view>
</page-container>
</template>
<script setup>
import { getUserCoupon } from '@/common/server/user';
//#
import PageContainer from '@/components/youdas-container/page-container.vue'
let showLoading = ref(false);
let pageContainer = ref(null);
let showNotData = ref(false);
let tabList = ref([
{ title: '未使用', type: 0 },
{ title: '已使用', type: 1 },
{ title: '已过期', type: 2 }
]);
let tabCur = ref(0);
let listData = ref([]);
const tabChange = (index) => {
tabCur.value = index;
getList();
};
const getList = async () => {
showLoading.value = true;
let res = await getUserCoupon(tabCur.value, 1, 50);
listData.value = res.data.list;
showLoading.value = false;
};
//
const onRefresh = async (paging) => {
await getList();
paging.complete();
};
</script>
<style lang="scss" scoped>
.tab-list {
display: flex;
padding: 30rpx;
background: #fff;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
margin: 20rpx 20rpx 30rpx;
.tab-list-item {
flex: 1;
text-align: center;
position: relative;
padding: 20rpx 18rpx;
font-size: 24rpx;
font-weight: 500;
border-radius: 8rpx;
color: #999999;
transition: all 0.3s;
&.active {
font-size: 24rpx;
color: #333333;
background-color: #E6F791;
box-shadow: 0 2rpx 8rpx rgba(230, 247, 145, 0.6);
.arrow {
position: absolute;
bottom: -10rpx;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-top: 10rpx solid #E6F791;
}
}
}
}
.list-item {
width: 710rpx;
box-sizing: border-box;
padding: 0;
display: flex;
align-items: center;
margin: 0 auto 30rpx;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 8rpx;
background: linear-gradient(to bottom, #F39205, #FFBB5C);
}
.money {
width: 180rpx;
text-align: center;
position: relative;
font-size: 28rpx;
font-weight: 500;
color: #F39205;
padding: 30rpx 0;
background: rgba(243, 146, 5, 0.05);
text {
font-weight: 600;
font-size: 70rpx;
color: #F39205;
}
&::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 1rpx;
height: 116rpx;
background: #EEEEEE;
}
}
.info {
flex: 1;
padding: 30rpx 20rpx;
.title {
font-size: 28rpx;
font-weight: 500;
color: #333333;
}
.time {
margin-top: 20rpx;
font-size: 20rpx;
font-weight: 400;
color: #8A8A8A;
display: flex;
align-items: center;
&::before {
content: '';
display: inline-block;
width: 20rpx;
height: 20rpx;
background: #E6F791;
border-radius: 50%;
margin-right: 8rpx;
}
}
}
.btn {
width: 120rpx;
height: 60rpx;
background: linear-gradient(to right, #E6F791, #D7E87D);
border-radius: 30rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 22rpx;
font-weight: 500;
color: #333333;
margin-right: 20rpx;
box-shadow: 0 4rpx 8rpx rgba(230, 247, 145, 0.4);
transition: all 0.3s;
&:active {
transform: scale(0.95);
}
}
&.dis {
position: relative;
opacity: 0.8;
&::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.2);
backdrop-filter: grayscale(100%);
}
.btn {
background: #CCCCCC;
box-shadow: none;
}
}
}
.not-data {
margin-top: 200rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>

371
static/coupon.vue Normal file
View File

@ -0,0 +1,371 @@
<template>
<view class="content">
<uni-nav-bar left-icon="left" color="#000000" backgroundColor="transparent" :fixed="true" :statusBar="true"
:border="false" @clickLeft="$common.back">
<view style="width: 100%; font-size: 32rpx; display: flex; align-items: center; justify-content: center; font-weight: 600;">
优惠券
</view>
</uni-nav-bar>
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="downOption" :up="upOption" @down="downCallback"
@up="getList">
<view class="tab-list">
<view @click="tabChange(i)" v-for="(item, i) in tabList" :key="i" class="tab-list-item"
:class="{ active: tabCur == i }">
{{ item.title }}
<view v-if="tabCur == i" class="arrow"></view>
</view>
</view>
<view class="list-item common_bg" v-for="(item, i) in listData" :key="i"
:class="{ dis: tabList[tabCur].type1 == 2 }">
<view class="money">
¥
<text>{{ Number(item.price) }}</text>
</view>
<view class="info">
<view class="title">{{ item.man_price }}{{ item.price }}</view>
<view class="time">{{ item.end_time }}到期</view>
</view>
<view v-if="tabList[tabCur].type1 == 1" @click="toUse(item)" class="btn">
去使用
</view>
<view v-if="tabList[tabCur].type1 == 2" class="btn">
{{ item.mark }}
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
export default {
data() {
return {
optData: '',
tabList: [{
id: 0,
title: '可使用',
type1: 1
},
{
id: 0,
title: '不可用',
type1: 2
}
],
tabCur: 0,
downOption: {
auto: false //
},
//
upOption: {
page: {
size: 10 // ,10
}
},
listData: [],
pageType: 1
}
},
onLoad(options) {
this.optData = options
if (options.type) {
this.pageType = options.type
}
// setTimeout(() => {
// this.listData = [
// {
// price: '23',
// man_price: '23',
// end_time: '2333-23-23 23:23:23',
// type1: 1,
// mark: '使'
// },
// {
// price: '23',
// man_price: '23',
// end_time: '2333-23-23 23:23:23',
// type1: 2,
// mark: '使'
// }
// ]
// }, 1000)
},
methods: {
toUse(item) {
if (this.pageType == 1) {
this.$customRouter.navigateTo('/pages/main/index', {}, 'switchTab');
}
if (this.pageType == 2) {
uni.$emit('chooseCoupon', item)
uni.navigateBack({
delta: 1
})
}
},
tabChange(i) {
this.tabCur = i
this.listData = []
this.mescroll.resetUpScroll()
this.mescroll.scrollTo(0, 0)
},
getList({
num,
size
}) {
let data = {
page: num,
type: this.pageType,
type1: this.tabList[this.tabCur].type1,
status: this.tabList[this.tabCur].id
}
if (this.pageType == 2) {
data.total_price = this.optData.total_price
}
this.req({
url: 'used',
data,
Loading: true,
success: res => {
if (res.status == 1) {
if (num == 1) {
this.listData = []
}
this.listData = this.listData.concat(res.data.data)
console.log(this.listData)
this.mescroll.endByPage(res.data.data.length, res.data.last_page)
}
}
})
},
back() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss">
.content {
padding: 0 0 30rpx;
background-color: #F7F7F7;
.tab-list {
display: flex;
padding: 30rpx;
.tab-list-item {
margin-right: 40rpx;
position: relative;
padding: 16rpx 18rpx;
font-size: 20rpx;
// font-family: Source Han Sans CN;
font-weight: 400;
border-radius: 8rpx;
color: #cccccc;
// .arrow {
// position: absolute;
// left: 50%;
// bottom: -14rpx;
// transform: translateX(-50%);
// width: 18rpx;
// height: 8rpx;
// background: #333333;
// border-radius: 28rpx;
// }
&.active {
font-size: 20rpx;
color: #333333;
background-color: #E6F791;
}
}
}
.list-item {
width: 710rpx;
box-sizing: border-box;
padding: 40rpx 30rpx 40rpx 10rpx;
display: flex;
align-items: center;
margin: 0 auto 20rpx;
background: url($imgurl+'my/coupon.png') no-repeat 0 0 / 100% 100%;
.money {
width: 160rpx;
text-align: center;
position: relative;
font-size: 26rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #F39205;
text {
font-weight: 400;
font-size: 70rpx;
color: #F39205;
}
&::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 1rpx;
height: 116rpx;
background: #666666;
}
}
.info {
flex: 1;
padding-left: 20rpx;
.title {
font-size: 25rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #333333;
}
.time {
margin-top: 20rpx;
font-size: 16rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #8A8A8A;
}
}
.btn {
width: 108rpx;
height: 56rpx;
background: #E6F791;
border-radius: 16rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 20rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #333333;
}
&.dis {
position: relative;
&::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.4);
}
}
}
// .coupon {
// width: 690rpx;
// margin: 30rpx auto 0;
// display: flex;
// align-items: center;
// position: relative;
// padding: 20rpx 0;
// &.dis {
// filter: grayscale(1);
// }
// .tag {
// position: absolute;
// left: 0;
// top: 0;
// border-radius: 20rpx 0 20rpx 0;
// overflow: hidden;
// background: linear-gradient(0deg, #85b9ff 0%, #b01bff 100%);
// height: 36rpx;
// padding: 0 10rpx;
// display: flex;
// align-items: center;
// font-size: 22rpx;
// font-family: Source Han Sans CN;
// font-weight: 400;
// color: #ffffff;
// }
// .coupon-l {
// width: 198rpx;
// height: 146rpx;
// display: flex;
// justify-content: center;
// align-items: center;
// border-right: 1rpx dashed #eeeeee;
// font-size: 24rpx;
// font-family: Source Han Sans CN;
// font-weight: 500;
// color: #ffffff;
// text {
// font-size: 40rpx;
// }
// }
// .coupon-info {
// width: 360rpx;
// box-sizing: border-box;
// padding-left: 20rpx;
// .rule {
// font-size: 28rpx;
// font-family: Source Han Sans CN;
// font-weight: 500;
// color: #ffffff;
// }
// .time {
// margin-top: 20rpx;
// font-size: 24rpx;
// font-family: Source Han Sans CN;
// font-weight: 400;
// color: #ffffff;
// }
// }
// .coupon-r {
// flex: 1;
// text-align: center;
// font-size: 28rpx;
// font-family: Source Han Sans CN;
// font-weight: 500;
// color: #ffffff;
// }
// }
}
</style>