This commit is contained in:
parent
bb2f33a333
commit
d43406380c
|
|
@ -5,6 +5,10 @@ export function getMembershipProducts() {
|
||||||
return request.get('/membership/products')
|
return request.get('/membership/products')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createMembershipProduct(data: any) {
|
||||||
|
return request.post('/membership/products', data)
|
||||||
|
}
|
||||||
|
|
||||||
export function updateMembershipProduct(id: string, data: any) {
|
export function updateMembershipProduct(id: string, data: any) {
|
||||||
return request.put(`/membership/products/${id}`, data)
|
return request.put(`/membership/products/${id}`, data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,8 @@ export function setUserMembership(uid: string, data: {
|
||||||
}) {
|
}) {
|
||||||
return request.put(`/user/${uid}/membership`, data)
|
return request.put(`/user/${uid}/membership`, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修改用户积分
|
||||||
|
export function updateUserPoints(uid: string, points: number) {
|
||||||
|
return request.put(`/user/${uid}/points`, { points })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="membership-manage">
|
<div class="membership-manage">
|
||||||
|
<div class="toolbar">
|
||||||
|
<el-button type="primary" @click="handleAdd">新增会员商品</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-table :data="productList" border style="width: 100%">
|
<el-table :data="productList" border style="width: 100%">
|
||||||
<el-table-column label="会员类型" width="120" align="center">
|
<el-table-column label="会员类型" width="120" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
|
@ -27,10 +31,13 @@
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 编辑弹窗 -->
|
<!-- 编辑弹窗 -->
|
||||||
<el-dialog v-model="dialogVisible" title="编辑会员商品" width="620px" destroy-on-close>
|
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑会员商品' : '新增会员商品'" width="620px" destroy-on-close>
|
||||||
<el-form :model="form" label-width="130px">
|
<el-form :model="form" label-width="130px">
|
||||||
<el-form-item label="会员类型">
|
<el-form-item label="会员类型" required>
|
||||||
<el-input :model-value="form.type === 'Monthly' ? '单月会员' : '订阅会员'" disabled />
|
<el-select v-model="form.type" style="width: 100%" :disabled="isEdit">
|
||||||
|
<el-option label="单月会员" value="Monthly" />
|
||||||
|
<el-option label="订阅会员" value="Subscription" />
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="价格" required>
|
<el-form-item label="价格" required>
|
||||||
<el-input-number v-model="form.price" :min="0.01" :precision="2" style="width: 100%" />
|
<el-input-number v-model="form.price" :min="0.01" :precision="2" style="width: 100%" />
|
||||||
|
|
@ -72,7 +79,7 @@
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import ImageUpload from '@/components/ImageUpload.vue'
|
import ImageUpload from '@/components/ImageUpload.vue'
|
||||||
import { getMembershipProducts, updateMembershipProduct } from '@/api/membership'
|
import { getMembershipProducts, createMembershipProduct, updateMembershipProduct } from '@/api/membership'
|
||||||
|
|
||||||
// 会员商品数据类型
|
// 会员商品数据类型
|
||||||
interface ProductItem {
|
interface ProductItem {
|
||||||
|
|
@ -91,6 +98,7 @@ interface ProductItem {
|
||||||
|
|
||||||
const productList = ref<ProductItem[]>([])
|
const productList = ref<ProductItem[]>([])
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
|
const isEdit = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const editingId = ref('')
|
const editingId = ref('')
|
||||||
|
|
||||||
|
|
@ -118,8 +126,17 @@ async function loadProducts() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
function handleAdd() {
|
||||||
|
isEdit.value = false
|
||||||
|
editingId.value = ''
|
||||||
|
form.value = defaultForm()
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 编辑
|
// 编辑
|
||||||
function handleEdit(row: ProductItem) {
|
function handleEdit(row: ProductItem) {
|
||||||
|
isEdit.value = true
|
||||||
editingId.value = row.id
|
editingId.value = row.id
|
||||||
form.value = {
|
form.value = {
|
||||||
type: row.type,
|
type: row.type,
|
||||||
|
|
@ -140,7 +157,11 @@ function handleEdit(row: ProductItem) {
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
await updateMembershipProduct(editingId.value, form.value)
|
if (isEdit.value) {
|
||||||
|
await updateMembershipProduct(editingId.value, form.value)
|
||||||
|
} else {
|
||||||
|
await createMembershipProduct(form.value)
|
||||||
|
}
|
||||||
ElMessage.success('保存成功')
|
ElMessage.success('保存成功')
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
await loadProducts()
|
await loadProducts()
|
||||||
|
|
@ -160,4 +181,7 @@ onMounted(() => {
|
||||||
.membership-manage {
|
.membership-manage {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
.toolbar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,10 @@
|
||||||
<el-descriptions-item label="会员到期时间">
|
<el-descriptions-item label="会员到期时间">
|
||||||
{{ userDetail.membershipExpireAt ? formatDate(userDetail.membershipExpireAt) : '-' }}
|
{{ userDetail.membershipExpireAt ? formatDate(userDetail.membershipExpireAt) : '-' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="积分余额">{{ userDetail.pointsBalance }}</el-descriptions-item>
|
<el-descriptions-item label="积分余额">
|
||||||
|
<span>{{ userDetail.pointsBalance }}</span>
|
||||||
|
<el-button size="small" type="primary" link style="margin-left: 8px" @click="openEditPoints">编辑</el-button>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="注册时间">{{ formatDate(userDetail.createdAt) }}</el-descriptions-item>
|
<el-descriptions-item label="注册时间">{{ formatDate(userDetail.createdAt) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="语言">{{ userDetail.language }}</el-descriptions-item>
|
<el-descriptions-item label="语言">{{ userDetail.language }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
@ -101,6 +104,19 @@
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 编辑积分弹窗 -->
|
||||||
|
<el-dialog v-model="editPointsVisible" title="编辑积分" width="400px" destroy-on-close>
|
||||||
|
<el-form label-width="80px">
|
||||||
|
<el-form-item label="积分值">
|
||||||
|
<el-input-number v-model="editPointsValue" :min="0" style="width: 100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="editPointsVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="editPointsLoading" @click="handleUpdatePoints">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 设置会员弹窗 -->
|
<!-- 设置会员弹窗 -->
|
||||||
<el-dialog v-model="membershipVisible" title="设为会员" width="420px" destroy-on-close>
|
<el-dialog v-model="membershipVisible" title="设为会员" width="420px" destroy-on-close>
|
||||||
<el-form label-width="90px">
|
<el-form label-width="90px">
|
||||||
|
|
@ -125,7 +141,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { getUsers, getUserDetail, setUserMembership } from '@/api/user'
|
import { getUsers, getUserDetail, setUserMembership, updateUserPoints } from '@/api/user'
|
||||||
|
|
||||||
// 用户列表项
|
// 用户列表项
|
||||||
interface UserItem {
|
interface UserItem {
|
||||||
|
|
@ -245,6 +261,34 @@ async function handleCancelMembership(row: UserItem) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑积分相关
|
||||||
|
const editPointsVisible = ref(false)
|
||||||
|
const editPointsValue = ref(0)
|
||||||
|
const editPointsLoading = ref(false)
|
||||||
|
|
||||||
|
function openEditPoints() {
|
||||||
|
editPointsValue.value = userDetail.value?.pointsBalance || 0
|
||||||
|
editPointsVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUpdatePoints() {
|
||||||
|
if (!userDetail.value) return
|
||||||
|
editPointsLoading.value = true
|
||||||
|
try {
|
||||||
|
await updateUserPoints(userDetail.value.uid, editPointsValue.value)
|
||||||
|
ElMessage.success('积分已更新')
|
||||||
|
editPointsVisible.value = false
|
||||||
|
// 刷新详情
|
||||||
|
const res: any = await getUserDetail(userDetail.value.uid)
|
||||||
|
userDetail.value = res.data || null
|
||||||
|
await loadUsers()
|
||||||
|
} catch {
|
||||||
|
// 错误已由拦截器处理
|
||||||
|
} finally {
|
||||||
|
editPointsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadUsers()
|
loadUsers()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,31 @@ public class AdminMembershipController : ControllerBase
|
||||||
return Ok(new { success = true, data = products });
|
return Ok(new { success = true, data = products });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建会员商品
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("products")]
|
||||||
|
public async Task<IActionResult> CreateProduct([FromBody] CreateProductRequest request)
|
||||||
|
{
|
||||||
|
var product = new VendingMachine.Domain.Entities.MembershipProduct
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid().ToString("N")[..12],
|
||||||
|
Type = Enum.TryParse<VendingMachine.Domain.Entities.MembershipType>(request.Type, true, out var t) ? t : VendingMachine.Domain.Entities.MembershipType.Monthly,
|
||||||
|
Price = request.Price,
|
||||||
|
Currency = request.Currency,
|
||||||
|
DurationDays = request.DurationDays,
|
||||||
|
GoogleProductId = request.GoogleProductId,
|
||||||
|
AppleProductId = request.AppleProductId,
|
||||||
|
DescriptionZhCn = request.DescriptionZhCn,
|
||||||
|
DescriptionZhTw = request.DescriptionZhTw,
|
||||||
|
DescriptionEn = request.DescriptionEn
|
||||||
|
};
|
||||||
|
|
||||||
|
_db.MembershipProducts.Add(product);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return Ok(new { success = true, data = product });
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新会员商品(价格、时长、宣传图、商品 ID 等)
|
/// 更新会员商品(价格、时长、宣传图、商品 ID 等)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -68,3 +93,20 @@ public class UpdateProductRequest
|
||||||
public string DescriptionZhTw { get; set; } = string.Empty;
|
public string DescriptionZhTw { get; set; } = string.Empty;
|
||||||
public string DescriptionEn { get; set; } = string.Empty;
|
public string DescriptionEn { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建会员商品请求体
|
||||||
|
/// </summary>
|
||||||
|
public class CreateProductRequest
|
||||||
|
{
|
||||||
|
public string Type { get; set; } = "Monthly";
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
public string Currency { get; set; } = "TWD";
|
||||||
|
public int DurationDays { get; set; } = 30;
|
||||||
|
public string GoogleProductId { get; set; } = string.Empty;
|
||||||
|
public string AppleProductId { get; set; } = string.Empty;
|
||||||
|
public string DescriptionZhCn { get; set; } = string.Empty;
|
||||||
|
public string DescriptionZhTw { get; set; } = string.Empty;
|
||||||
|
public string DescriptionEn { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,22 @@ public class AdminUserController : ControllerBase
|
||||||
return Ok(new { success = true, message = request.IsMember ? "已设为会员" : "已取消会员" });
|
return Ok(new { success = true, message = request.IsMember ? "已设为会员" : "已取消会员" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改用户积分
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut("{uid}/points")]
|
||||||
|
public async Task<IActionResult> UpdatePoints(string uid, [FromBody] UpdatePointsRequest request)
|
||||||
|
{
|
||||||
|
var user = await _db.Users.FirstOrDefaultAsync(u => u.Uid == uid);
|
||||||
|
if (user == null)
|
||||||
|
return NotFound(new { success = false, message = "用户不存在" });
|
||||||
|
|
||||||
|
user.PointsBalance = request.Points;
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(new { success = true, message = "积分已更新", data = new { pointsBalance = user.PointsBalance } });
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取用户详情
|
/// 获取用户详情
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -145,3 +161,13 @@ public class SetMembershipRequest
|
||||||
/// <summary>到期时间(可选)</summary>
|
/// <summary>到期时间(可选)</summary>
|
||||||
public DateTime? ExpireAt { get; set; }
|
public DateTime? ExpireAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改积分请求
|
||||||
|
/// </summary>
|
||||||
|
public class UpdatePointsRequest
|
||||||
|
{
|
||||||
|
/// <summary>设置的积分值</summary>
|
||||||
|
public int Points { get; set; }
|
||||||
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
|
|
@ -75,10 +75,7 @@ async function handleGift() {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.popup-mask {
|
.popup-mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -87,7 +84,7 @@ async function handleGift() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-content {
|
.popup-content {
|
||||||
width: 600rpx;
|
width: 620rpx;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
padding: 40rpx;
|
padding: 40rpx;
|
||||||
|
|
@ -99,21 +96,22 @@ async function handleGift() {
|
||||||
color: #333;
|
color: #333;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 30rpx;
|
margin-bottom: 36rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-body {
|
.popup-body {
|
||||||
margin-bottom: 40rpx;
|
margin-bottom: 36rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-input {
|
.popup-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 80rpx;
|
height: 88rpx;
|
||||||
border: 1rpx solid #e0e0e0;
|
border: 2rpx solid #c0c8b8;
|
||||||
border-radius: 12rpx;
|
border-radius: 16rpx;
|
||||||
padding: 0 24rpx;
|
padding: 0 28rpx;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
margin-bottom: 20rpx;
|
color: #333;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,27 +122,28 @@ async function handleGift() {
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 80rpx;
|
height: 84rpx;
|
||||||
border-radius: 40rpx;
|
border-radius: 12rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-btn {
|
.cancel-btn {
|
||||||
background-color: #f5f5f5;
|
background-color: #ffffff;
|
||||||
|
border: 2rpx solid #6b7c5e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-btn {
|
.confirm-btn {
|
||||||
background-color: #007aff;
|
background-color: #6b7c5e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-text {
|
.btn-text {
|
||||||
font-size: 28rpx;
|
font-size: 30rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-text {
|
.cancel-text {
|
||||||
color: #666;
|
color: #6b7c5e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-text {
|
.confirm-text {
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export default {
|
||||||
mine: {
|
mine: {
|
||||||
title: 'Me',
|
title: 'Me',
|
||||||
loginBtn: 'Login',
|
loginBtn: 'Login',
|
||||||
points: 'Points',
|
points: 'My Points',
|
||||||
giftPoints: 'Gift Points',
|
giftPoints: 'Gift Points',
|
||||||
myCoupons: 'My Coupons',
|
myCoupons: 'My Coupons',
|
||||||
switchLang: 'Language',
|
switchLang: 'Language',
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export default {
|
||||||
mine: {
|
mine: {
|
||||||
title: '我的',
|
title: '我的',
|
||||||
loginBtn: '点击登录',
|
loginBtn: '点击登录',
|
||||||
points: '积分',
|
points: '我的积分',
|
||||||
giftPoints: '赠送积分',
|
giftPoints: '赠送积分',
|
||||||
myCoupons: '我的优惠券',
|
myCoupons: '我的优惠券',
|
||||||
switchLang: '切换语言',
|
switchLang: '切换语言',
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export default {
|
||||||
mine: {
|
mine: {
|
||||||
title: '我的',
|
title: '我的',
|
||||||
loginBtn: '點擊登入',
|
loginBtn: '點擊登入',
|
||||||
points: '積分',
|
points: '我的積分',
|
||||||
giftPoints: '贈送積分',
|
giftPoints: '贈送積分',
|
||||||
myCoupons: '我的優惠券',
|
myCoupons: '我的優惠券',
|
||||||
switchLang: '切換語言',
|
switchLang: '切換語言',
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "首页"
|
"navigationBarTitleText": "首页"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
{
|
{
|
||||||
"path": "pages/stamps/stamps",
|
"path": "pages/stamps/stamps",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "节日印花"
|
"navigationBarTitleText": "节日印花"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -35,12 +37,14 @@
|
||||||
{
|
{
|
||||||
"path": "pages/points/points",
|
"path": "pages/points/points",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "我的积分"
|
"navigationBarTitleText": "我的积分"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/coupons/coupons",
|
"path": "pages/coupons/coupons",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "我的优惠券"
|
"navigationBarTitleText": "我的优惠券"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,69 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="coupons-page">
|
<view class="coupons-page">
|
||||||
<!-- Tab切换 -->
|
<!-- 自定义导航栏 -->
|
||||||
<view class="tab-bar">
|
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
<view
|
<view class="nav-inner">
|
||||||
v-for="tab in tabs"
|
<view class="nav-back" @click="goBack">
|
||||||
:key="tab.value"
|
<image class="back-icon" src="/static/ic_back2.png" mode="aspectFit" />
|
||||||
class="tab-item"
|
</view>
|
||||||
:class="{ active: currentTab === tab.value }"
|
<text class="nav-title">{{ t('coupons.title') }}</text>
|
||||||
@click="switchTab(tab.value)"
|
<view class="nav-placeholder" />
|
||||||
>
|
|
||||||
<text class="tab-text">{{ t(tab.label) }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 优惠券列表 -->
|
<!-- 页面内容 -->
|
||||||
<view class="coupon-list">
|
<view :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
|
||||||
<view
|
<!-- Tab切换 -->
|
||||||
v-for="coupon in coupons"
|
<view class="tab-bar">
|
||||||
:key="coupon.couponId"
|
<view
|
||||||
class="coupon-item"
|
v-for="tab in tabs"
|
||||||
>
|
:key="tab.value"
|
||||||
<CouponCard :coupon="coupon" :redeemed="true" />
|
class="tab-item"
|
||||||
<!-- 状态标识 -->
|
:class="{ active: currentTab === tab.value }"
|
||||||
<view v-if="coupon.status === 'used'" class="status-badge used">
|
@click="switchTab(tab.value)"
|
||||||
<text class="status-text">{{ t('coupons.usedLabel') }}</text>
|
>
|
||||||
</view>
|
<text class="tab-text">{{ t(tab.label) }}</text>
|
||||||
<view v-if="coupon.status === 'expired'" class="status-badge expired">
|
<view v-if="currentTab === tab.value" class="tab-line" />
|
||||||
<text class="status-text">{{ t('coupons.expiredLabel') }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 优惠券列表 -->
|
||||||
<EmptyState v-if="!loading && coupons.length === 0" text="暂无优惠券" />
|
<view class="coupon-list">
|
||||||
|
<view
|
||||||
|
v-for="coupon in coupons"
|
||||||
|
:key="coupon.couponId"
|
||||||
|
class="coupon-card"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="coupon-bg"
|
||||||
|
:src="currentTab === 'available' ? '/static/ic_coupon_bg.png' : '/static/ic_coupon_used_bg.png'"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
<view class="coupon-inner">
|
||||||
|
<view class="coupon-info">
|
||||||
|
<text class="coupon-name">{{ coupon.name }}</text>
|
||||||
|
<view class="coupon-detail-row">
|
||||||
|
<text class="coupon-dot">●</text>
|
||||||
|
<text class="coupon-detail-label">{{ t('couponCard.expireLabel') }}</text>
|
||||||
|
<text class="coupon-detail-value highlight">{{ formatExpire(coupon.expireAt) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="coupon-detail-row">
|
||||||
|
<text class="coupon-dot">●</text>
|
||||||
|
<text class="coupon-detail-label">{{ t('couponCard.conditionLabel') }}</text>
|
||||||
|
<text class="coupon-detail-value highlight">{{ coupon.pointsCost ?? 0 }}{{ t('couponCard.pointsUnit') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="coupon-action" v-if="currentTab === 'available'">
|
||||||
|
<view class="redeem-btn" @click="onUse(coupon)">
|
||||||
|
<text class="redeem-btn-text">{{ t('couponCard.redeemBtn') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<EmptyState v-if="!loading && coupons.length === 0" text="暂无优惠券" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -40,11 +72,17 @@
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { getMyCoupons } from '../../api/coupon.js'
|
import { getMyCoupons } from '../../api/coupon.js'
|
||||||
import CouponCard from '../../components/CouponCard.vue'
|
|
||||||
import EmptyState from '../../components/EmptyState.vue'
|
import EmptyState from '../../components/EmptyState.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 状态栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
try {
|
||||||
|
const sysInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = sysInfo.statusBarHeight || 0
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
// Tab配置
|
// Tab配置
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ label: 'coupons.availableTab', value: 'available' },
|
{ label: 'coupons.availableTab', value: 'available' },
|
||||||
|
|
@ -56,6 +94,11 @@ const currentTab = ref('available')
|
||||||
const coupons = ref([])
|
const coupons = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
// 加载优惠券列表
|
// 加载优惠券列表
|
||||||
async function loadCoupons() {
|
async function loadCoupons() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -77,6 +120,18 @@ function switchTab(tab) {
|
||||||
loadCoupons()
|
loadCoupons()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化到期时间
|
||||||
|
function formatExpire(dateStr) {
|
||||||
|
if (!dateStr) return t('couponCard.noLimit')
|
||||||
|
const d = new Date(dateStr)
|
||||||
|
return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用优惠券
|
||||||
|
function onUse(coupon) {
|
||||||
|
uni.showToast({ title: '请在购买时使用', icon: 'none' })
|
||||||
|
}
|
||||||
|
|
||||||
// 初始加载
|
// 初始加载
|
||||||
loadCoupons()
|
loadCoupons()
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -84,87 +139,153 @@ loadCoupons()
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.coupons-page {
|
.coupons-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f0e8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义导航栏 */
|
||||||
|
.custom-nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #DBDBDB;
|
||||||
|
}
|
||||||
|
.nav-inner {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
}
|
||||||
|
.nav-back {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.back-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.nav-placeholder {
|
||||||
|
width: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab栏 */
|
||||||
.tab-bar {
|
.tab-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 88rpx;
|
height: 88rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item.active::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 60rpx;
|
|
||||||
height: 4rpx;
|
|
||||||
background-color: #007aff;
|
|
||||||
border-radius: 2rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-text {
|
.tab-text {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #666;
|
color: #999;
|
||||||
|
}
|
||||||
|
.tab-item.active .tab-text {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.tab-line {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 48rpx;
|
||||||
|
height: 6rpx;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 3rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item.active .tab-text {
|
/* 优惠券列表 */
|
||||||
color: #007aff;
|
.coupon-list {
|
||||||
|
padding: 24rpx 24rpx;
|
||||||
|
}
|
||||||
|
.coupon-card {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.coupon-bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.coupon-inner {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 240rpx;
|
||||||
|
}
|
||||||
|
.coupon-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.coupon-name {
|
||||||
|
font-size: 34rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
display: block;
|
||||||
|
margin-left: 28rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
.coupon-detail-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 28rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
.coupon-dot {
|
||||||
|
font-size: 14rpx;
|
||||||
|
color: #D9DED7;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
.coupon-detail-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.coupon-detail-value {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.coupon-detail-value.highlight {
|
||||||
|
color: #c9a96e;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.coupon-list {
|
/* 兑换按钮 */
|
||||||
padding: 20rpx 24rpx;
|
.coupon-action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 16rpx;
|
||||||
}
|
}
|
||||||
|
.redeem-btn {
|
||||||
.coupon-item {
|
background: linear-gradient(135deg, #d4a855, #c9a96e);
|
||||||
position: relative;
|
border-radius: 28rpx;
|
||||||
margin-bottom: 20rpx;
|
width: 172rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
.redeem-btn-text {
|
||||||
.status-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: 20rpx;
|
|
||||||
right: 20rpx;
|
|
||||||
padding: 6rpx 16rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge.used {
|
|
||||||
background-color: rgba(153, 153, 153, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge.expired {
|
|
||||||
background-color: rgba(255, 59, 48, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
font-size: 22rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge.used .status-text {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge.expired .status-text {
|
|
||||||
color: #ff3b30;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-tip {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60rpx;
|
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
color: #ffffff;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="home-page">
|
<view class="home-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="nav-inner">
|
||||||
|
<text class="nav-title">首页</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 页面内容 -->
|
||||||
|
<view :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
|
||||||
<!-- Banner轮播图 -->
|
<!-- Banner轮播图 -->
|
||||||
<view class="banner-wrap">
|
<view class="banner-wrap">
|
||||||
<swiper class="banner-swiper" autoplay circular indicator-dots indicator-color="rgba(255,255,255,0.4)"
|
<swiper class="banner-swiper" autoplay circular indicator-dots indicator-color="rgba(255,255,255,0.4)"
|
||||||
|
|
@ -83,6 +92,7 @@
|
||||||
|
|
||||||
<!-- 会员二维码弹窗 -->
|
<!-- 会员二维码弹窗 -->
|
||||||
<QrcodePopup :visible="showQrcode" @close="showQrcode = false" />
|
<QrcodePopup :visible="showQrcode" @close="showQrcode = false" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -122,6 +132,13 @@
|
||||||
} = useI18n()
|
} = useI18n()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 状态栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
try {
|
||||||
|
const sysInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = sysInfo.statusBarHeight || 0
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
const banners = ref([])
|
const banners = ref([])
|
||||||
const entries = ref([])
|
const entries = ref([])
|
||||||
|
|
@ -264,6 +281,27 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 自定义导航栏 */
|
||||||
|
.custom-nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #DBDBDB;
|
||||||
|
}
|
||||||
|
.nav-inner {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
.home-page {
|
.home-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f5f0e8;
|
background-color: #f5f0e8;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@
|
||||||
<!-- 积分区域 -->
|
<!-- 积分区域 -->
|
||||||
<view v-if="userStore.isLoggedIn" class="points-card" @click="goPoints">
|
<view v-if="userStore.isLoggedIn" class="points-card" @click="goPoints">
|
||||||
<view class="points-left">
|
<view class="points-left">
|
||||||
<image class="points-icon" src="/static/ic_stamp.png" mode="aspectFit" />
|
<view class="points-icon-wrap">
|
||||||
|
<image class="points-icon" src="/static/ic_integral.png" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
<text class="points-label">{{ t('mine.points') }}</text>
|
<text class="points-label">{{ t('mine.points') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="points-value">{{ balance }}</text>
|
<text class="points-value">{{ balance }}</text>
|
||||||
|
|
@ -165,6 +167,8 @@ async function handleLogout() {
|
||||||
showLogoutConfirm.value = false
|
showLogoutConfirm.value = false
|
||||||
await userStore.logout()
|
await userStore.logout()
|
||||||
balance.value = 0
|
balance.value = 0
|
||||||
|
// 清除所有页面栈,返回首页
|
||||||
|
uni.reLaunch({ url: '/pages/index/index' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面导航
|
// 页面导航
|
||||||
|
|
@ -277,11 +281,20 @@ onMounted(() => {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.points-icon {
|
.points-icon-wrap {
|
||||||
width: 48rpx;
|
width: 72rpx;
|
||||||
height: 48rpx;
|
height: 72rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #f5f0e8;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
margin-right: 16rpx;
|
margin-right: 16rpx;
|
||||||
}
|
}
|
||||||
|
.points-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
.points-label {
|
.points-label {
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="points-page">
|
<view class="points-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="nav-inner">
|
||||||
|
<view class="nav-back" @click="goBack">
|
||||||
|
<text class="nav-back-icon">‹</text>
|
||||||
|
</view>
|
||||||
|
<text class="nav-title">{{ t('points.title') }}</text>
|
||||||
|
<view class="nav-placeholder" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 页面内容 -->
|
||||||
|
<view :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
|
||||||
<!-- Tab切换 -->
|
<!-- Tab切换 -->
|
||||||
<view class="tab-bar">
|
<view class="tab-bar">
|
||||||
<view
|
<view
|
||||||
|
|
@ -33,10 +46,7 @@
|
||||||
<text class="record-source">{{ record.source }}</text>
|
<text class="record-source">{{ record.source }}</text>
|
||||||
<text class="record-time">{{ formatTime(record.createdAt) }}</text>
|
<text class="record-time">{{ formatTime(record.createdAt) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text
|
<text class="record-amount">
|
||||||
class="record-amount"
|
|
||||||
:class="{ earn: currentTab === 'earn', spend: currentTab === 'spend' }"
|
|
||||||
>
|
|
||||||
{{ currentTab === 'earn' ? '+' : '-' }}{{ record.amount }}
|
{{ currentTab === 'earn' ? '+' : '-' }}{{ record.amount }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -50,6 +60,7 @@
|
||||||
<text>{{ t('common.noMore') }}</text>
|
<text>{{ t('common.noMore') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -61,6 +72,17 @@ import EmptyState from '../../components/EmptyState.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 状态栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
try {
|
||||||
|
const sysInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = sysInfo.statusBarHeight || 0
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
const currentTab = ref('earn')
|
const currentTab = ref('earn')
|
||||||
const records = ref([])
|
const records = ref([])
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
|
|
@ -82,8 +104,8 @@ async function loadRecords(isLoadMore = false) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fetchFn = currentTab.value === 'earn' ? getEarnRecords : getSpendRecords
|
const fetchFn = currentTab.value === 'earn' ? getEarnRecords : getSpendRecords
|
||||||
const res = await fetchFn({ page: page.value, pageSize })
|
const res = await fetchFn({ page: page.value, size: pageSize })
|
||||||
const list = res.data?.records || res.data || []
|
const list = res.data?.items || res.data?.records || res.data || []
|
||||||
|
|
||||||
if (isLoadMore) {
|
if (isLoadMore) {
|
||||||
records.value = [...records.value, ...list]
|
records.value = [...records.value, ...list]
|
||||||
|
|
@ -124,15 +146,48 @@ loadRecords()
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.points-page {
|
.points-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f5f5f5;
|
background-color: #ffffff;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义导航栏 */
|
||||||
|
.custom-nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #DBDBDB;
|
||||||
|
}
|
||||||
|
.nav-inner {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
}
|
||||||
|
.nav-back {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.nav-back-icon {
|
||||||
|
font-size: 48rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.nav-placeholder {
|
||||||
|
width: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.tab-bar {
|
.tab-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
|
|
@ -152,33 +207,30 @@ loadRecords()
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
width: 60rpx;
|
width: 60rpx;
|
||||||
height: 4rpx;
|
height: 4rpx;
|
||||||
background-color: #007aff;
|
background-color: #333;
|
||||||
border-radius: 2rpx;
|
border-radius: 2rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-text {
|
.tab-text {
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
color: #666;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item.active .tab-text {
|
.tab-item.active .tab-text {
|
||||||
color: #007aff;
|
color: #333;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-list {
|
.record-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-item {
|
.record-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background-color: #ffffff;
|
padding: 30rpx 32rpx;
|
||||||
padding: 28rpx 30rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
border-radius: 12rpx;
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-info {
|
.record-info {
|
||||||
|
|
@ -194,7 +246,7 @@ loadRecords()
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-time {
|
.record-time {
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
@ -202,14 +254,7 @@ loadRecords()
|
||||||
.record-amount {
|
.record-amount {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
color: #333;
|
||||||
|
|
||||||
.record-amount.earn {
|
|
||||||
color: #4caf50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-amount.spend {
|
|
||||||
color: #ff3b30;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-tip {
|
.loading-tip {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="stamps-page">
|
<view class="stamps-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="nav-inner">
|
||||||
|
<view class="nav-back" @click="goBack">
|
||||||
|
<text class="nav-back-icon">‹</text>
|
||||||
|
</view>
|
||||||
|
<text class="nav-title">{{ t('stamps.title') }}</text>
|
||||||
|
<view class="nav-placeholder" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 页面内容 -->
|
||||||
|
<view :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
|
||||||
<!-- 印花Banner图 -->
|
<!-- 印花Banner图 -->
|
||||||
<view class="banner-wrap">
|
<view class="banner-wrap">
|
||||||
<image
|
<image
|
||||||
|
|
@ -63,6 +76,7 @@
|
||||||
@confirm="onRedeemConfirm"
|
@confirm="onRedeemConfirm"
|
||||||
@cancel="showRedeem = false"
|
@cancel="showRedeem = false"
|
||||||
/>
|
/>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -80,6 +94,17 @@ import EmptyState from '../../components/EmptyState.vue'
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 状态栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
try {
|
||||||
|
const sysInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = sysInfo.statusBarHeight || 0
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
const bannerUrl = ref('')
|
const bannerUrl = ref('')
|
||||||
const stampCoupons = ref([])
|
const stampCoupons = ref([])
|
||||||
|
|
@ -155,6 +180,40 @@ onMounted(() => {
|
||||||
background-color: #f5f0e8;
|
background-color: #f5f0e8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义导航栏 */
|
||||||
|
.custom-nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #DBDBDB;
|
||||||
|
}
|
||||||
|
.nav-inner {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
}
|
||||||
|
.nav-back {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.nav-back-icon {
|
||||||
|
font-size: 48rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.nav-placeholder {
|
||||||
|
width: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
/* Banner */
|
/* Banner */
|
||||||
.banner-wrap {
|
.banner-wrap {
|
||||||
padding: 24rpx 24rpx 0;
|
padding: 24rpx 24rpx 0;
|
||||||
|
|
|
||||||
BIN
mobile/static/ic_back2.png
Normal file
BIN
mobile/static/ic_back2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 640 B |
BIN
mobile/static/ic_integral.png
Normal file
BIN
mobile/static/ic_integral.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Loading…
Reference in New Issue
Block a user