1755 lines
42 KiB
Vue
1755 lines
42 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="profile-card">
|
||
<view class="profile-header">
|
||
<view class="avatar-wrapper" @click="uploadAvatar">
|
||
<view class="avatar">
|
||
<image :src="avatarUrl" mode="aspectFill" @error="handleImageError"></image>
|
||
</view>
|
||
<view class="avatar-upload">📷</view>
|
||
</view>
|
||
<view class="profile-info">
|
||
<view class="title-row">
|
||
<text class="name">{{ userInfo.username }}</text>
|
||
|
||
</view>
|
||
<view class="meta-row">
|
||
<text class="meta-item">加入于 2025-01-15</text>
|
||
<view class="vip-badge">
|
||
<text>品鉴会员</text>
|
||
</view>
|
||
</view>
|
||
<view class="meta-row">
|
||
<text class="meta-item">上海市浦东新区张江高科技园区</text>
|
||
</view>
|
||
</view>
|
||
<view class="edit-btn" @click="toEditInfoPage()">编辑资料</view>
|
||
</view>
|
||
<view class="stats-grid">
|
||
<view class="stat-card" v-for="item in stats" :key="item.label"
|
||
@click="handleStatCardClick(item)">
|
||
<view class="stat-icon" :style="{ color: item.color }">{{ item.icon }}</view>
|
||
<text class="stat-value" :style="{ color: item.color }">{{ item.value }}</text>
|
||
<text class="stat-label">{{ item.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="signin-card">
|
||
<view class="signin-header">
|
||
<view class="signin-headline">
|
||
<text class="signin-title">每日签到</text>
|
||
<text class="signin-desc">已连续签到 {{ signInfo.continuousDays }} 天</text>
|
||
</view>
|
||
<view class="signin-badge">+{{ signInfo.todayReward || 0 }} 积分</view>
|
||
</view>
|
||
<view class="signin-calendar">
|
||
<view class="signin-calendar-header">
|
||
<text class="signin-month">{{ signInfo.monthLabel }}</text>
|
||
<text class="signin-month-summary">本月已签 {{ signInfo.monthSignedDays || 0 }} 天</text>
|
||
</view>
|
||
<view class="signin-weekdays">
|
||
<text class="signin-weekday" v-for="label in weekdayLabels" :key="label">{{ label }}</text>
|
||
</view>
|
||
<view class="signin-calendar-grid">
|
||
<view class="signin-date" v-for="(day, index) in signCalendar" :key="index"
|
||
:class="[{ placeholder: day.isPlaceholder, signed: day.signed, today: day.isToday, missed: day.missed }]">
|
||
<text class="signin-date-number">{{ day.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="signin-footer">
|
||
<view class="signin-stats">
|
||
<view class="signin-stat">
|
||
<text class="signin-stat-value">{{ signInfo.totalPoints }}</text>
|
||
<text class="signin-stat-label">累计积分</text>
|
||
</view>
|
||
<view class="signin-stat">
|
||
<text class="signin-stat-value">{{ signInfo.continuousDays }}</text>
|
||
<text class="signin-stat-label">连续天数</text>
|
||
</view>
|
||
</view>
|
||
<button class="signin-btn" :class="{ disabled: signInfo.signedToday || signLoading }"
|
||
@click="handleSignIn" :disabled="signInfo.signedToday || signLoading">
|
||
{{ signInfo.signedToday ? '今日已签到' : (signLoading ? '签到中...' : '立即签到') }}
|
||
</button>
|
||
<text class="signin-tip">{{ signInfo.nextRewardText || '坚持签到,更多奖励等你拿' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="tabs">
|
||
<view class="tab-item" v-for="(tab, index) in tabs" :key="tab" :class="{ active: index === currentTab }"
|
||
@click="handleTabClick(index)">
|
||
{{ tab }}
|
||
</view>
|
||
</view>
|
||
|
||
<view class="monthly-card" v-if="currentTab === 0">
|
||
<view class="monthly-title">本月数据</view>
|
||
<view class="monthly-list">
|
||
<view class="monthly-item" v-for="item in monthlyData" :key="item.label">
|
||
<text class="monthly-label">{{ item.label }}</text>
|
||
<text class="monthly-value" :style="{ color: item.color || '#1A1A1A' }">
|
||
{{ item.value }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="orders-card" v-if="currentTab === 1">
|
||
<view class="orders-header">
|
||
<text class="orders-title">我的订单</text>
|
||
<view class="orders-more" @click="navigateTo('/pages/order/index')">查看全部</view>
|
||
</view>
|
||
<view class="order-list">
|
||
<view class="order-item" v-for="order in orders" :key="order.order_id || order.id">
|
||
<view class="order-top">
|
||
<text class="order-no">{{ order.order_id || '-' }}</text>
|
||
<view class="order-tags">
|
||
<!-- <view class="order-tag type">{{ order.typeLabel }}</view> -->
|
||
<view class="order-tag status" :class="order.statusClass">{{ order.statusText }}</view>
|
||
</view>
|
||
<text class="order-date">{{ order.displayDate }}</text>
|
||
</view>
|
||
<view class="order-body">
|
||
<view class="order-thumb">
|
||
<image v-if="order.productImage" :src="order.productImage" mode="aspectFill"></image>
|
||
<text v-else>60x60</text>
|
||
</view>
|
||
<view class="order-info">
|
||
<text class="order-name">{{ order.productName }}</text>
|
||
<text class="order-spec">{{ order.productSpec }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="order-footer">
|
||
<view class="order-amount">
|
||
<text class="amount-label">订单总额</text>
|
||
<text class="amount-value">¥{{ order.amountText }}</text>
|
||
</view>
|
||
<view class="order-actions">
|
||
<view class="order-action" v-for="action in order.actions" :key="action.label" @click="handleOrderAction(action, order)">
|
||
查看详情
|
||
</view>
|
||
<!-- <view class="order-action" v-for="action in order.actions" :key="action.label" @click="handleOrderAction(action, order)">
|
||
{{ action.label }}
|
||
</view> -->
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="order-empty" v-if="orders.length === 0">
|
||
<text class="order-empty-icon">📦</text>
|
||
<text class="order-empty-text">暂无订单</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="favorites-card" v-if="currentTab === 2">
|
||
<view class="favorites-wrapper">
|
||
<view class="favorite-item" v-for="favorite in favorites" :key="favorite.title">
|
||
<view class="favorite-thumb">150x150</view>
|
||
<view class="favorite-info">
|
||
<view class="favorite-top">
|
||
<text class="favorite-title">{{ favorite.title }}</text>
|
||
<view class="favorite-heart">♡</view>
|
||
</view>
|
||
<view class="favorite-meta">
|
||
<text class="favorite-rating">⭐ {{ favorite.rating }}</text>
|
||
<text class="favorite-count">({{ favorite.count }})</text>
|
||
</view>
|
||
<view class="favorite-price">
|
||
<text class="favorite-current">{{ favorite.price }}</text>
|
||
<text class="favorite-origin">{{ favorite.origin }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="achievements-card" v-if="currentTab === 3">
|
||
<view class="achievement-item" v-for="achievement in achievements" :key="achievement.title"
|
||
:class="{ highlight: achievement.highlight }">
|
||
<view class="achievement-left">
|
||
<view class="achievement-icon" :style="{ backgroundColor: achievement.iconBg }">
|
||
<text class="achievement-icon-text">{{ achievement.icon }}</text>
|
||
</view>
|
||
<view class="achievement-info">
|
||
<text class="achievement-title">{{ achievement.title }}</text>
|
||
<text class="achievement-desc">{{ achievement.desc }}</text>
|
||
<view class="achievement-progress" v-if="achievement.progressText">
|
||
<view class="achievement-progress-bar">
|
||
<view class="achievement-progress-fill"
|
||
:style="{ width: achievement.progress + '%' }"></view>
|
||
</view>
|
||
<text class="achievement-progress-text">{{ achievement.progressText }}</text>
|
||
</view>
|
||
<text class="achievement-reward">奖励:{{ achievement.reward }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="achievement-trophy">{{ achievement.trophy }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="settings-card" v-if="currentTab === 4">
|
||
<view class="settings-section">
|
||
<text class="settings-section-title">账户设置</text>
|
||
<view class="settings-list">
|
||
<view class="settings-item" v-for="item in accountSettings" :key="item.title">
|
||
<view class="settings-left">
|
||
<view class="settings-icon">{{ item.icon }}</view>
|
||
<view class="settings-text">
|
||
<text class="settings-title">{{ item.title }}</text>
|
||
<text class="settings-desc">{{ item.desc }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="settings-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="settings-section">
|
||
<text class="settings-section-title">其他设置</text>
|
||
<view class="settings-list">
|
||
<view class="settings-item" v-for="item in otherSettings" :key="item.title">
|
||
<view class="settings-left">
|
||
<view class="settings-icon">{{ item.icon }}</view>
|
||
<view class="settings-text">
|
||
<text class="settings-title">{{ item.title }}</text>
|
||
<text class="settings-desc">{{ item.desc }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="settings-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="logout-btn" @click="logout">退出登录</view>
|
||
</view>
|
||
<view class="quick-card">
|
||
<view class="quick-title">快捷功能</view>
|
||
<view class="quick-grid">
|
||
<view class="quick-item" v-for="action in quickActions" :key="action.label" @click="handleQuickAction(action)">
|
||
<view class="quick-icon">{{ action.icon }}</view>
|
||
<text class="quick-label">{{ action.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import request from '@/api/request.js';
|
||
import uploadFile from '@/api/upload.js';
|
||
export default {
|
||
data() {
|
||
return {
|
||
userInfo: {},
|
||
currentTab: 0,
|
||
signInfo: {
|
||
signedToday: false,
|
||
continuousDays: 0,
|
||
totalPoints: 0,
|
||
todayReward: 0,
|
||
nextRewardText: '',
|
||
monthLabel: '',
|
||
monthSignedDays: 0
|
||
},
|
||
signCalendar: [],
|
||
signLoading: false,
|
||
weekdayLabels: ['日', '一', '二', '三', '四', '五', '六'],
|
||
stats: [
|
||
{ icon: '🎁', value: '1280', label: '当前积分', color: '#7B43FF' },
|
||
{ icon: '🛍️', value: '23', label: '总订单数', color: '#2877FF' },
|
||
{ icon: '📈', value: '¥321', label: '累计省钱', color: '#26B95A' },
|
||
{ icon: '👥', value: '8', label: '团队人数', color: '#FF7A00' }
|
||
],
|
||
tabs: ['概览', '订单', '收藏', '成就', '设置'],
|
||
monthlyData: [
|
||
{ label: '本月订单', value: '5单' },
|
||
{ label: '本月消费', value: '¥450.00', color: '#26B95A' },
|
||
{ label: '分享次数', value: '18次', color: '#7B43FF' },
|
||
{ label: '分销佣金', value: '¥568.00', color: '#FF7A00' }
|
||
],
|
||
quickActions: [
|
||
{ icon: '💳', label: '我的钱包' },
|
||
{ icon: '📍', label: '地址管理', path: '/pages/address/index' },
|
||
{ icon: '🤝', label: '邀请用户', path: '/pages/inviteUsers/index' },
|
||
{ icon: '🔔', label: '消息通知' },
|
||
{ icon: '❓', label: '帮助中心' }
|
||
],
|
||
favorites: [
|
||
{
|
||
title: '经典玫瑰香水30ml',
|
||
rating: '4.8',
|
||
count: 1250,
|
||
price: '¥199',
|
||
origin: '¥299'
|
||
}
|
||
],
|
||
achievements: [
|
||
{
|
||
title: '拼团达人',
|
||
desc: '成功参与10次拼团',
|
||
progress: 80,
|
||
progressText: '8/10',
|
||
reward: '专属优惠券',
|
||
icon: '👥',
|
||
iconBg: '#f3f4f8',
|
||
trophy: '',
|
||
highlight: false
|
||
},
|
||
{
|
||
title: '分享达人',
|
||
desc: '分享商品20次',
|
||
progress: 90,
|
||
progressText: '18/20',
|
||
reward: '500积分',
|
||
icon: '🔗',
|
||
iconBg: '#f3f4f8',
|
||
trophy: '',
|
||
highlight: false
|
||
},
|
||
{
|
||
title: '消费达人',
|
||
desc: '累计消费满2000元',
|
||
progress: 100,
|
||
progressText: '',
|
||
reward: 'VIP会员体验',
|
||
icon: '🛍️',
|
||
iconBg: '#ffeeb7',
|
||
trophy: '🏆',
|
||
highlight: true
|
||
}
|
||
],
|
||
accountSettings: [
|
||
{ icon: '👤', title: '个人信息', desc: '姓名、手机、邮箱' },
|
||
{ icon: '🛡️', title: '账户安全', desc: '密码、支付设置' },
|
||
{ icon: '🔔', title: '通知设置', desc: '消息推送偏好' }
|
||
],
|
||
otherSettings: [
|
||
{ icon: '📄', title: '隐私政策', desc: '查看隐私条款' },
|
||
{ icon: '❓', title: '帮助中心', desc: '常见问题解答' }
|
||
],
|
||
orders: []
|
||
};
|
||
},
|
||
computed: {
|
||
avatarUrl() {
|
||
// 如果有头像URL且不为空,则使用头像URL,否则使用默认头像
|
||
if (this.userInfo && this.userInfo.avatar_url && this.userInfo.avatar_url.trim() !== '') {
|
||
return this.userInfo.avatar_url;
|
||
}
|
||
// 默认头像:使用base64编码的SVG头像占位符(预编码,兼容所有平台)
|
||
// 这是一个圆形灰色背景,带有用户图标的默认头像
|
||
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYwIiBoZWlnaHQ9IjE2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSI4MCIgY3k9IjgwIiByPSI4MCIgZmlsbD0iI2U1ZTdlYSIvPjxjaXJjbGUgY3g9IjgwIiBjeT0iNjUiIHI9IjI1IiBmaWxsPSIjOWZhMGFmIi8+PHBhdGggZD0iTSA4MCAxMDAgUSA1MCAxMDAgNTAgMTMwIEwgNTAgMTYwIEwgMTEwIDE2MCBMIDExMCAxMzAgUSAxMTAgMTAwIDgwIDEwMCBaIiBmaWxsPSIjOWZhMGFmIi8+PC9zdmc+';
|
||
}
|
||
},
|
||
onShow: function () {
|
||
const token = wx.getStorageSync('access_token');
|
||
if (!token) {
|
||
uni.navigateTo({
|
||
url: '/pages/login/index'
|
||
});
|
||
return;
|
||
} else {
|
||
this.loadProfile();
|
||
this.loadSignInfo();
|
||
this.loadOrders();
|
||
}
|
||
},
|
||
methods: {
|
||
async handleOrderAction(action, order) {
|
||
if (!order) return;
|
||
|
||
if (action.label === '查看详情') {
|
||
const orderId = order.order_id || order.id || order.order_no || order.no;
|
||
if (!orderId) {
|
||
console.warn('缺少订单ID,无法跳转详情', order);
|
||
return;
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/order/detail?id=${orderId}`
|
||
});
|
||
} else if (action.label === '加入购物车') {
|
||
const skuList = order.product_list || order.productList || [];
|
||
if (!Array.isArray(skuList) || !skuList.length) {
|
||
console.warn('订单中没有商品列表,无法加入购物车', order);
|
||
return;
|
||
}
|
||
|
||
for (const sku of skuList) {
|
||
try {
|
||
await request('xcx/cart', 'POST', {
|
||
product_id: sku.product_id || order.product_id,
|
||
sku_id: sku.sku_id,
|
||
quantity: sku.quantity || 1
|
||
});
|
||
} catch (error) {
|
||
console.error('加入购物车失败', error);
|
||
}
|
||
}
|
||
uni.showToast({
|
||
title: '已加入购物车',
|
||
icon: 'success'
|
||
});
|
||
}
|
||
},
|
||
handleImageError(e) {
|
||
// 图片加载失败时,使用默认头像
|
||
console.log('头像加载失败,使用默认头像', e);
|
||
},
|
||
loadProfile() {
|
||
try {
|
||
request('xcx/get_user_info', 'GET').then(res => {
|
||
this.userInfo = res
|
||
});
|
||
} catch (error) {
|
||
console.warn('加载个人信息失败', error);
|
||
}
|
||
},
|
||
loadOrders() {
|
||
try {
|
||
request('xcx/orders', 'GET', {
|
||
page: 1,
|
||
page_size: 3
|
||
}).then(res => {
|
||
// 处理返回数据
|
||
let orderList = [];
|
||
if (Array.isArray(res)) {
|
||
orderList = res;
|
||
} else if (res.list && Array.isArray(res.list)) {
|
||
orderList = res.list;
|
||
} else if (res.data && Array.isArray(res.data)) {
|
||
orderList = res.data;
|
||
}
|
||
|
||
// 只取前三条
|
||
orderList = orderList.slice(0, 3);
|
||
|
||
// 订单状态映射(后端:1待支付 2支付失败 3待发货 4待收货 5已完成 6已取消 7已退款)
|
||
const statusMap = {
|
||
// 兼容旧的字符串状态
|
||
'pending': { text: '待支付', class: 'pending' },
|
||
'paid': { text: '待发货', class: 'warning' },
|
||
'shipped': { text: '待收货', class: 'warning' },
|
||
'completed': { text: '已完成', class: 'success' },
|
||
'cancelled': { text: '已取消', class: 'cancelled' },
|
||
'refunded': { text: '已退款', class: 'cancelled' },
|
||
// 数字状态(你提供的规范)
|
||
'1': { text: '待支付', class: 'pending' },
|
||
'2': { text: '支付失败', class: 'cancelled' },
|
||
'3': { text: '待发货', class: 'warning' },
|
||
'4': { text: '待收货', class: 'warning' },
|
||
'5': { text: '已完成', class: 'success' },
|
||
'6': { text: '已取消', class: 'cancelled' },
|
||
'7': { text: '已退款', class: 'cancelled' }
|
||
};
|
||
|
||
const formatPrice = (price) => {
|
||
if (price === null || price === undefined || price === '') return '0.00';
|
||
const numericPrice = Number(price);
|
||
if (Number.isNaN(numericPrice)) return '0.00';
|
||
// if (numericPrice >= 1000 && numericPrice % 100 === 0) {
|
||
// return (numericPrice / 100).toFixed(2);
|
||
// }
|
||
return (numericPrice / 100).toFixed(2);
|
||
};
|
||
|
||
const formatDate = (dateStr) => {
|
||
if (!dateStr) return '';
|
||
const date = new Date(dateStr.replace(/-/g, '/'));
|
||
if (Number.isNaN(date.getTime())) {
|
||
return dateStr.split(' ')[0] || dateStr;
|
||
}
|
||
const year = date.getFullYear();
|
||
const month = this.padZero(date.getMonth() + 1);
|
||
const day = this.padZero(date.getDate());
|
||
return `${year}-${month}-${day}`;
|
||
};
|
||
|
||
const getActions = (status) => {
|
||
const statusNumber = typeof status === 'number' ? status : parseInt(status, 10);
|
||
|
||
// 1: 待支付
|
||
if (status === 'pending' || statusNumber === 1) {
|
||
return [{ label: '查看详情' }, { label: '立即付款' }];
|
||
}
|
||
// 4: 待收货
|
||
if (status === 'shipped' || statusNumber === 4) {
|
||
return [{ label: '查看详情' }, { label: '确认收货' }];
|
||
}
|
||
// 5: 已完成
|
||
if (status === 'completed' || statusNumber === 5) {
|
||
return [{ label: '查看详情' }, { label: '加入购物车' }];
|
||
}
|
||
// 其他状态统一只有「查看详情」
|
||
return [{ label: '查看详情' }];
|
||
};
|
||
|
||
const getFirstProduct = (order) => {
|
||
if (Array.isArray(order.product_list) && order.product_list.length) {
|
||
return order.product_list[0];
|
||
}
|
||
if (Array.isArray(order.productList) && order.productList.length) {
|
||
return order.productList[0];
|
||
}
|
||
if (Array.isArray(order.items) && order.items.length) {
|
||
return order.items[0];
|
||
}
|
||
return order || {};
|
||
};
|
||
|
||
const getFirstValidNumber = (...values) => {
|
||
for (let i = 0; i < values.length; i++) {
|
||
const value = values[i];
|
||
if (value !== undefined && value !== null && value !== '') {
|
||
return value;
|
||
}
|
||
}
|
||
return 0;
|
||
};
|
||
|
||
// 转换为页面需要的格式
|
||
this.orders = orderList.map(order => {
|
||
const firstItem = getFirstProduct(order);
|
||
const orderId = order.order_id || order.orderId || order.order_no || order.no || order.id || '';
|
||
|
||
// 兼容多种后端状态字段:status / order_status / orderStatus / state / order_state
|
||
const rawStatus =
|
||
order.status !== undefined && order.status !== null
|
||
? order.status
|
||
: (order.order_status !== undefined && order.order_status !== null
|
||
? order.order_status
|
||
: (order.orderStatus !== undefined && order.orderStatus !== null
|
||
? order.orderStatus
|
||
: (order.state !== undefined && order.state !== null
|
||
? order.state
|
||
: (order.order_state !== undefined && order.order_state !== null
|
||
? order.order_state
|
||
: ''))));
|
||
const statusKey = rawStatus !== '' && rawStatus !== undefined && rawStatus !== null ? String(rawStatus) : '';
|
||
const statusInfo = statusMap[statusKey] || statusMap[rawStatus] || {
|
||
text: rawStatus === '' || rawStatus === undefined || rawStatus === null ? '未知状态' : String(rawStatus),
|
||
class: ''
|
||
};
|
||
const quantity = firstItem && firstItem.quantity ? firstItem.quantity : (order.quantity || 1);
|
||
const skuLabel = firstItem && (firstItem.sku_name || firstItem.spec) ? (firstItem.sku_name || firstItem.spec) : '';
|
||
const specParts = [];
|
||
if (skuLabel) {
|
||
specParts.push(skuLabel);
|
||
}
|
||
specParts.push(`x${quantity}`);
|
||
|
||
const amountSource = getFirstValidNumber(
|
||
order.payable_amount,
|
||
order.total_amount,
|
||
order.final_price,
|
||
order.total_price,
|
||
order.amount
|
||
);
|
||
|
||
return {
|
||
...order,
|
||
order_id: orderId,
|
||
typeLabel: (firstItem && firstItem.category_name) || order.order_type || '普通订单',
|
||
statusText: statusInfo.text,
|
||
statusClass: statusInfo.class,
|
||
displayDate: formatDate(order.created_at || order.date),
|
||
productName: (firstItem && (firstItem.product_name || firstItem.name)) || order.product_name || '商品',
|
||
productSpec: specParts.join(' · '),
|
||
productImage: (firstItem && (firstItem.product_main_image_url || firstItem.image_url)) || '',
|
||
amountText: formatPrice(amountSource),
|
||
actions: getActions(order.status)
|
||
};
|
||
});
|
||
}).catch(error => {
|
||
console.warn('加载订单列表失败', error);
|
||
// 失败时保持默认数据或清空
|
||
this.orders = [];
|
||
});
|
||
} catch (error) {
|
||
console.warn('获取订单列表异常', error);
|
||
this.orders = [];
|
||
}
|
||
},
|
||
loadSignInfo() {
|
||
try {
|
||
return request('xcx/user_checkins', 'GET', {
|
||
time_month: this.getMonthLabel()
|
||
}).then(res => {
|
||
const info = res || {};
|
||
// 处理签到列表,提取日期
|
||
const checkinList = info.list || [];
|
||
const monthLabel = this.getMonthLabel();
|
||
|
||
// 判断今天是否已签到
|
||
const today = new Date();
|
||
const todayStr = `${today.getFullYear()}-${this.padZero(today.getMonth() + 1)}-${this.padZero(today.getDate())}`;
|
||
const signedToday = checkinList.some(item => {
|
||
if (!item.checkin_time) return false;
|
||
// 从 "2025-11-23 14:51:27" 提取日期部分 "2025-11-23"
|
||
const dateStr = item.checkin_time.split(' ')[0];
|
||
return dateStr === todayStr;
|
||
});
|
||
|
||
const calendar = this.buildMonthlyCalendar(checkinList, monthLabel);
|
||
this.signCalendar = calendar;
|
||
const updatedSignInfo = {
|
||
...this.signInfo,
|
||
signedToday,
|
||
continuousDays: info.continuous_days || 0,
|
||
totalPoints: info.total_points || 0,
|
||
todayReward: info.today_reward || 0,
|
||
nextRewardText: info.next_reward_text || '',
|
||
monthLabel,
|
||
monthSignedDays: calendar.filter(day => day.signed && !day.isPlaceholder).length
|
||
};
|
||
this.signInfo = updatedSignInfo;
|
||
if (this.stats && this.stats.length) {
|
||
const newPoints = updatedSignInfo.totalPoints;
|
||
this.$set(this.stats, 0, {
|
||
...this.stats[0],
|
||
value: newPoints ? String(newPoints) : this.stats[0].value
|
||
});
|
||
}
|
||
}).catch(error => {
|
||
console.warn('加载签到信息失败', error);
|
||
this.signCalendar = this.buildMonthlyCalendar();
|
||
this.signInfo = {
|
||
...this.signInfo,
|
||
signedToday: false,
|
||
monthLabel: this.getMonthLabel(),
|
||
monthSignedDays: 0
|
||
};
|
||
});
|
||
} catch (error) {
|
||
console.warn('获取签到状态异常', error);
|
||
this.signCalendar = this.buildMonthlyCalendar();
|
||
this.signInfo = {
|
||
...this.signInfo,
|
||
signedToday: false,
|
||
monthLabel: this.getMonthLabel(),
|
||
monthSignedDays: 0
|
||
};
|
||
}
|
||
},
|
||
buildMonthlyCalendar(list = [], referenceMonth = '') {
|
||
const today = new Date();
|
||
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||
let year = today.getFullYear();
|
||
let month = today.getMonth() + 1;
|
||
if (referenceMonth && /^\d{4}-\d{2}$/.test(referenceMonth)) {
|
||
const parts = referenceMonth.split('-');
|
||
year = parseInt(parts[0], 10) || year;
|
||
month = parseInt(parts[1], 10) || month;
|
||
}
|
||
const firstDay = new Date(year, month - 1, 1);
|
||
const totalDays = new Date(year, month, 0).getDate();
|
||
const prefixDays = firstDay.getDay();
|
||
|
||
// 从签到列表中提取日期,构建日期映射表
|
||
// list 格式: [{ checkin_time: "2025-11-23 14:51:27" }, ...]
|
||
const signedDates = new Set();
|
||
if (Array.isArray(list)) {
|
||
list.forEach(item => {
|
||
if (item.checkin_time) {
|
||
// 从 "2025-11-23 14:51:27" 提取日期部分 "2025-11-23"
|
||
const dateStr = item.checkin_time.split(' ')[0];
|
||
if (dateStr && /^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
||
signedDates.add(dateStr);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
const calendar = [];
|
||
for (let i = 0; i < prefixDays; i++) {
|
||
calendar.push({ label: '', isPlaceholder: true });
|
||
}
|
||
for (let day = 1; day <= totalDays; day++) {
|
||
const dateStr = `${year}-${this.padZero(month)}-${this.padZero(day)}`;
|
||
const targetDate = new Date(year, month - 1, day);
|
||
const isToday = this.isSameDate(targetDate, todayStart);
|
||
const isFuture = targetDate.getTime() > todayStart.getTime();
|
||
const signed = signedDates.has(dateStr);
|
||
calendar.push({
|
||
label: day,
|
||
date: dateStr,
|
||
signed,
|
||
isToday,
|
||
isFuture,
|
||
isPlaceholder: false,
|
||
missed: !signed && !isFuture && !isToday
|
||
});
|
||
}
|
||
while (calendar.length % 7 !== 0) {
|
||
calendar.push({ label: '', isPlaceholder: true });
|
||
}
|
||
return calendar;
|
||
},
|
||
getMonthLabel() {
|
||
const today = new Date();
|
||
return `${today.getFullYear()}-${this.padZero(today.getMonth() + 1)}`;
|
||
},
|
||
padZero(num) {
|
||
return num < 10 ? `0${num}` : `${num}`;
|
||
},
|
||
isSameDate(dateA, dateB) {
|
||
return (
|
||
dateA.getFullYear() === dateB.getFullYear() &&
|
||
dateA.getMonth() === dateB.getMonth() &&
|
||
dateA.getDate() === dateB.getDate()
|
||
);
|
||
},
|
||
async handleSignIn() {
|
||
if (this.signInfo.signedToday || this.signLoading) {
|
||
return;
|
||
}
|
||
this.signLoading = true;
|
||
try {
|
||
const res = await request('xcx/user_checkin', 'POST');
|
||
const rewardText = res?.message || '签到成功';
|
||
uni.showToast({
|
||
title: rewardText,
|
||
icon: 'success'
|
||
});
|
||
await this.loadSignInfo();
|
||
} catch (error) {
|
||
console.error('签到失败', error);
|
||
uni.showToast({
|
||
title: '签到失败,请稍后再试',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
this.signLoading = false;
|
||
}
|
||
},
|
||
uploadAvatar() {
|
||
console.log('uploadAvatar 被点击');
|
||
uni.chooseImage({
|
||
count: 1,
|
||
mediaType: ['image'],
|
||
sizeType: ['compressed', 'original'],
|
||
sourceType: ['album', 'camera'],
|
||
success: (res) => {
|
||
console.log('选择图片成功', res);
|
||
const tempFilePaths = res.tempFilePaths;
|
||
if (tempFilePaths && tempFilePaths.length > 0) {
|
||
uni.showLoading({
|
||
title: '上传中...'
|
||
});
|
||
uploadFile(tempFilePaths[0]).then((res) => {
|
||
console.log('上传成功', res);
|
||
this.userInfo.avatar_url = res;
|
||
request('xcx/set_user_info', 'POST', {
|
||
avatar_url: res,
|
||
username: this.userInfo.username,
|
||
sex: this.userInfo.sex,
|
||
mobile: this.userInfo.mobile,
|
||
}).then(res => {
|
||
uni.hideLoading({
|
||
title: '上传成功...'
|
||
});
|
||
console.log('设置头像成功', res);
|
||
}).catch((err) => {
|
||
uni.hideLoading({
|
||
title: '上传失败...'
|
||
});
|
||
console.error('设置头像失败', err);
|
||
})
|
||
});
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('选择图片失败', err);
|
||
uni.showToast({
|
||
title: '选择图片失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
});
|
||
},
|
||
handleTabClick(index) {
|
||
this.currentTab = index;
|
||
},
|
||
navigateTo(url) {
|
||
uni.navigateTo({
|
||
url: url
|
||
});
|
||
},
|
||
handleQuickAction(action) {
|
||
if (action.path) {
|
||
this.navigateTo(action.path);
|
||
return;
|
||
}
|
||
this.$nextTick(() => {
|
||
uni.showToast({
|
||
title: '功能开发中',
|
||
icon: 'none'
|
||
});
|
||
});
|
||
},
|
||
handleStatCardClick(item) {
|
||
// 点击"当前积分"卡片时跳转到积分流水记录页面
|
||
if (item.label === '当前积分') {
|
||
this.navigateTo('/pages/pointsLog/index');
|
||
}
|
||
if (item.label === '总订单数') {
|
||
this.navigateTo('/pages/order/index');
|
||
}
|
||
},
|
||
toEditInfoPage() {
|
||
uni.navigateTo({
|
||
url: '/pages/my/editInfo/index'
|
||
});
|
||
},
|
||
logout() {
|
||
wx.removeStorageSync('access_token');
|
||
uni.reLaunch({
|
||
url: '/pages/login/index'
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.page {
|
||
min-height: 100vh;
|
||
/* padding: 24rpx; */
|
||
background-color: #fff;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
padding-bottom: 24rpx;
|
||
}
|
||
|
||
.profile-card {
|
||
background-color: #ffffff;
|
||
/* border-radius: 24rpx; */
|
||
|
||
padding-bottom: 24rpx;
|
||
}
|
||
|
||
.signin-card {
|
||
margin: 0 24rpx;
|
||
padding: 32rpx;
|
||
background: linear-gradient(120deg, #f6f1ff 0%, #ffeef5 100%);
|
||
border-radius: 28rpx;
|
||
box-shadow: 0 16rpx 32rpx rgba(139, 64, 255, 0.12);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.signin-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.signin-headline {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.signin-title {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #33195d;
|
||
}
|
||
|
||
.signin-desc {
|
||
font-size: 26rpx;
|
||
color: #7b43ff;
|
||
}
|
||
|
||
.signin-badge {
|
||
padding: 12rpx 28rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(255, 60, 141, 0.12);
|
||
color: #ff3c8d;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.signin-calendar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.signin-calendar-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.signin-month {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #33195d;
|
||
}
|
||
|
||
.signin-month-summary {
|
||
font-size: 24rpx;
|
||
color: #7b43ff;
|
||
}
|
||
|
||
.signin-weekdays {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
font-size: 24rpx;
|
||
color: #7b43ff;
|
||
text-align: center;
|
||
}
|
||
|
||
.signin-weekday {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.signin-calendar-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 16rpx 12rpx;
|
||
}
|
||
|
||
.signin-date {
|
||
height: 80rpx;
|
||
border-radius: 18rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: rgba(255, 255, 255, 0.7);
|
||
border: 2rpx dashed rgba(139, 64, 255, 0.16);
|
||
color: #4a2d71;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.signin-date.placeholder {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
}
|
||
|
||
.signin-date.signed {
|
||
background: linear-gradient(135deg, #8b40ff 0%, #ff3c8d 100%);
|
||
color: #fff;
|
||
border-style: solid;
|
||
}
|
||
|
||
.signin-date.today {
|
||
border-color: #ff3c8d;
|
||
box-shadow: 0 0 0 3rpx rgba(255, 60, 141, 0.3);
|
||
}
|
||
|
||
.signin-date.missed {
|
||
border-style: solid;
|
||
border-color: rgba(255, 60, 141, 0.3);
|
||
color: rgba(51, 25, 93, 0.4);
|
||
}
|
||
|
||
.signin-date-number {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.signin-footer {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.signin-stats {
|
||
display: flex;
|
||
gap: 32rpx;
|
||
}
|
||
|
||
.signin-stat {
|
||
flex: 1;
|
||
background-color: rgba(255, 255, 255, 0.7);
|
||
border-radius: 24rpx;
|
||
padding: 16rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.signin-stat-value {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #33195d;
|
||
}
|
||
|
||
.signin-stat-label {
|
||
font-size: 24rpx;
|
||
color: #7b43ff;
|
||
}
|
||
|
||
.signin-btn {
|
||
width: 100%;
|
||
// padding: 24rpx 0;
|
||
border-radius: 999rpx;
|
||
background: linear-gradient(135deg, #8b40ff 0%, #ff3c8d 100%);
|
||
color: #ffffff;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
}
|
||
|
||
.signin-btn.disabled {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.signin-tip {
|
||
font-size: 24rpx;
|
||
color: #7b43ff;
|
||
text-align: center;
|
||
}
|
||
|
||
.profile-header {
|
||
background: linear-gradient(135deg, #8b40ff 0%, #ff3c8d 100%);
|
||
/* border-radius: 24rpx 24rpx 0 0; */
|
||
padding: 40rpx 32rpx;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 24rpx;
|
||
color: #ffffff;
|
||
position: relative;
|
||
}
|
||
|
||
.avatar-wrapper {
|
||
position: relative;
|
||
}
|
||
|
||
.avatar {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
border-radius: 50%;
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 26rpx;
|
||
color: #ffffff;
|
||
border: 4rpx solid rgba(255, 255, 255, 0.4);
|
||
position: relative;
|
||
z-index: 0;
|
||
image {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
}
|
||
}
|
||
|
||
.avatar-upload {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 50%;
|
||
background-color: #ffffff;
|
||
color: #8b40ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: absolute;
|
||
z-index: 10;
|
||
bottom: 0;
|
||
right: 0;
|
||
font-size: 32rpx;
|
||
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.1);
|
||
pointer-events: auto;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.profile-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.name {
|
||
font-size: 40rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.vip-badge {
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
border-radius: 999rpx;
|
||
padding: 8rpx 20rpx;
|
||
font-size: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.vip-badge text {
|
||
color: #ffe178;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.meta-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
font-size: 24rpx;
|
||
opacity: 0.88;
|
||
}
|
||
|
||
.meta-item {
|
||
color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
|
||
.edit-btn {
|
||
position: absolute;
|
||
right: 32rpx;
|
||
top: 40rpx;
|
||
padding: 10rpx 24rpx;
|
||
border-radius: 999rpx;
|
||
background-color: rgba(255, 255, 255, 0.15);
|
||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.stats-grid {
|
||
padding: 32rpx;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 24rpx;
|
||
|
||
}
|
||
|
||
.stat-card {
|
||
background-color: #f9f9ff;
|
||
border-radius: 24rpx;
|
||
padding: 32rpx 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
box-shadow: inset 0 0 0 2rpx rgba(139, 64, 255, 0.05);
|
||
}
|
||
|
||
.stat-icon {
|
||
font-size: 44rpx;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 44rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 26rpx;
|
||
color: #5f5f5f;
|
||
}
|
||
|
||
.tabs {
|
||
display: flex;
|
||
background-color: #ffffff;
|
||
border-radius: 999rpx;
|
||
padding: 12rpx;
|
||
margin: 0 24rpx;
|
||
gap: 12rpx;
|
||
box-shadow: 0 12rpx 24rpx 0 rgba(123, 67, 255, 0.08);
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 16rpx 0;
|
||
border-radius: 999rpx;
|
||
font-size: 28rpx;
|
||
color: #757c8e;
|
||
}
|
||
|
||
.tab-item.active {
|
||
background: linear-gradient(135deg, #8b40ff 0%, #ff3c8d 100%);
|
||
color: #ffffff;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.monthly-card {
|
||
background-color: #ffffff;
|
||
border-radius: 24rpx;
|
||
padding: 32rpx;
|
||
margin: 0 24rpx;
|
||
box-shadow: 0 12rpx 24rpx 0 rgba(123, 67, 255, 0.08);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.monthly-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.monthly-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.monthly-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 28rpx;
|
||
color: #404040;
|
||
}
|
||
|
||
.monthly-label {
|
||
color: #757c8e;
|
||
}
|
||
|
||
.monthly-value {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.quick-card {
|
||
background-color: #ffffff;
|
||
border-radius: 24rpx;
|
||
padding: 32rpx;
|
||
margin: 0 24rpx;
|
||
box-shadow: 0 12rpx 24rpx 0 rgba(123, 67, 255, 0.08);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 32rpx;
|
||
}
|
||
|
||
.quick-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.quick-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.quick-item {
|
||
background-color: #f6f8ff;
|
||
border-radius: 24rpx;
|
||
padding: 32rpx 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
border: 2rpx solid rgba(79, 116, 255, 0.1);
|
||
box-shadow: 0 6rpx 18rpx rgba(79, 116, 255, 0.08);
|
||
}
|
||
|
||
.quick-icon {
|
||
font-size: 48rpx;
|
||
color: #1a274c;
|
||
}
|
||
|
||
.quick-label {
|
||
font-size: 28rpx;
|
||
color: #1a274c;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.favorites-card {
|
||
margin: 0 24rpx;
|
||
padding: 24rpx 24rpx 36rpx;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 12rpx 24rpx 0 rgba(123, 67, 255, 0.08);
|
||
border-radius: 24rpx;
|
||
}
|
||
|
||
.favorites-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.favorite-item {
|
||
background-color: #f6f8ff;
|
||
border-radius: 24rpx;
|
||
padding: 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
border: 2rpx solid rgba(79, 116, 255, 0.12);
|
||
box-shadow: inset 0 0 0 2rpx rgba(79, 116, 255, 0.04);
|
||
}
|
||
|
||
.favorite-thumb {
|
||
width: 100%;
|
||
height: 400rpx;
|
||
border-radius: 16rpx;
|
||
background-color: #eef2ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 44rpx;
|
||
color: #b3bac9;
|
||
}
|
||
|
||
.favorite-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.favorite-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.favorite-title {
|
||
font-size: 30rpx;
|
||
color: #273248;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.favorite-heart {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 12rpx;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 6rpx 18rpx rgba(79, 116, 255, 0.08);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 26rpx;
|
||
color: #273248;
|
||
}
|
||
|
||
.favorite-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
font-size: 24rpx;
|
||
color: #8c94a3;
|
||
}
|
||
|
||
.favorite-rating {
|
||
color: #ffb400;
|
||
}
|
||
|
||
.favorite-price {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.favorite-current {
|
||
font-size: 32rpx;
|
||
color: #ff2e2e;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.favorite-origin {
|
||
font-size: 26rpx;
|
||
color: #b3bac9;
|
||
text-decoration: line-through;
|
||
}
|
||
|
||
.settings-card {
|
||
margin: 0 24rpx;
|
||
padding: 24rpx;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 12rpx 24rpx 0 rgba(123, 67, 255, 0.08);
|
||
border-radius: 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 32rpx;
|
||
}
|
||
|
||
.settings-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
background-color: #f8faff;
|
||
padding: 24rpx;
|
||
border-radius: 24rpx;
|
||
border: 2rpx solid rgba(79, 116, 255, 0.08);
|
||
box-shadow: inset 0 0 0 2rpx rgba(79, 116, 255, 0.04);
|
||
}
|
||
|
||
.settings-section-title {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.settings-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.settings-item {
|
||
background-color: #ffffff;
|
||
border-radius: 20rpx;
|
||
padding: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border: 2rpx solid rgba(79, 116, 255, 0.1);
|
||
box-shadow: 0 8rpx 18rpx rgba(79, 116, 255, 0.06);
|
||
}
|
||
|
||
.settings-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.settings-icon {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 20rpx;
|
||
background-color: #f0f4ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 34rpx;
|
||
color: #4f74ff;
|
||
}
|
||
|
||
.settings-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.settings-title {
|
||
font-size: 28rpx;
|
||
color: #273248;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.settings-desc {
|
||
font-size: 24rpx;
|
||
color: #8c94a3;
|
||
}
|
||
|
||
.settings-arrow {
|
||
font-size: 36rpx;
|
||
color: #c4cad6;
|
||
}
|
||
|
||
.logout-btn {
|
||
margin-top: 8rpx;
|
||
padding: 28rpx 0;
|
||
text-align: center;
|
||
border-radius: 20rpx;
|
||
border: 2rpx solid rgba(255, 99, 132, 0.2);
|
||
color: #ff4d6d;
|
||
font-size: 28rpx;
|
||
background-color: rgba(255, 99, 132, 0.08);
|
||
}
|
||
|
||
.achievements-card {
|
||
margin: 0 24rpx;
|
||
padding: 24rpx;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 12rpx 24rpx 0 rgba(123, 67, 255, 0.08);
|
||
border-radius: 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.achievement-item {
|
||
background-color: #f8faff;
|
||
border-radius: 24rpx;
|
||
padding: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border: 2rpx solid rgba(79, 116, 255, 0.08);
|
||
box-shadow: inset 0 0 0 2rpx rgba(79, 116, 255, 0.04);
|
||
}
|
||
|
||
.achievement-item.highlight {
|
||
background: linear-gradient(135deg, rgba(255, 238, 183, 0.55), rgba(255, 255, 255, 0.9));
|
||
border: 2rpx solid rgba(255, 200, 64, 0.5);
|
||
}
|
||
|
||
.achievement-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
flex: 1;
|
||
}
|
||
|
||
.achievement-icon {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 999rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.achievement-icon-text {
|
||
font-size: 40rpx;
|
||
}
|
||
|
||
.achievement-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.achievement-title {
|
||
font-size: 30rpx;
|
||
color: #273248;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.achievement-desc {
|
||
font-size: 24rpx;
|
||
color: #8c94a3;
|
||
}
|
||
|
||
.achievement-progress {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.achievement-progress-bar {
|
||
flex: 1;
|
||
height: 18rpx;
|
||
border-radius: 12rpx;
|
||
background-color: rgba(79, 116, 255, 0.15);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.achievement-progress-fill {
|
||
height: 100%;
|
||
border-radius: 12rpx;
|
||
background: linear-gradient(135deg, #fccc75, #f5b34b);
|
||
}
|
||
|
||
.achievement-progress-text {
|
||
font-size: 24rpx;
|
||
color: #273248;
|
||
}
|
||
|
||
.achievement-reward {
|
||
margin-top: 16rpx;
|
||
font-size: 24rpx;
|
||
color: #f5b34b;
|
||
padding: 16rpx 20rpx;
|
||
background-color: rgba(255, 190, 60, 0.15);
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.achievement-trophy {
|
||
font-size: 44rpx;
|
||
color: #f5b34b;
|
||
align-self: flex-start;
|
||
}
|
||
|
||
.orders-card {
|
||
background-color: #ffffff;
|
||
padding: 32rpx;
|
||
margin: 0 24rpx;
|
||
box-shadow: 0 12rpx 24rpx 0 rgba(123, 67, 255, 0.08);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 32rpx;
|
||
}
|
||
|
||
.orders-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.orders-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.orders-more {
|
||
font-size: 24rpx;
|
||
color: #4f5f7a;
|
||
padding: 12rpx 28rpx;
|
||
border-radius: 999rpx;
|
||
background-color: #f2f7ff;
|
||
}
|
||
|
||
.order-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.order-item {
|
||
background-color: #ffffff;
|
||
border-radius: 24rpx;
|
||
border: 2rpx solid rgba(79, 116, 255, 0.12);
|
||
padding: 32rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
box-shadow: 0 10rpx 26rpx rgba(79, 116, 255, 0.06);
|
||
}
|
||
|
||
.order-top {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.order-no {
|
||
font-size: 26rpx;
|
||
color: #212a3a;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.order-tags {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.order-tag {
|
||
font-size: 22rpx;
|
||
padding: 8rpx 20rpx;
|
||
border-radius: 999rpx;
|
||
}
|
||
|
||
.order-tag.type {
|
||
background-color: rgba(79, 116, 255, 0.12);
|
||
color: #4f74ff;
|
||
}
|
||
|
||
.order-tag.status {
|
||
color: #ffffff;
|
||
}
|
||
|
||
.order-tag.status.success {
|
||
background-color: #6cd198;
|
||
}
|
||
|
||
.order-tag.status.warning {
|
||
background-color: #c79bff;
|
||
}
|
||
|
||
.order-tag.status.pending {
|
||
background-color: #f7c96b;
|
||
color: #5a3b00;
|
||
}
|
||
|
||
.order-tag.status.cancelled {
|
||
background-color: #c9ced8;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.order-date {
|
||
margin-left: auto;
|
||
font-size: 24rpx;
|
||
color: #8c94a3;
|
||
}
|
||
|
||
.order-body {
|
||
display: flex;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.order-thumb {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 16rpx;
|
||
background-color: #eef2ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24rpx;
|
||
color: #8c94a3;
|
||
}
|
||
|
||
.order-thumb image {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.order-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
justify-content: center;
|
||
}
|
||
|
||
.order-name {
|
||
font-size: 30rpx;
|
||
color: #273248;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.order-spec {
|
||
font-size: 24rpx;
|
||
color: #8c94a3;
|
||
}
|
||
|
||
.order-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.order-amount {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.amount-label {
|
||
font-size: 24rpx;
|
||
color: #8c94a3;
|
||
}
|
||
|
||
.amount-value {
|
||
font-size: 32rpx;
|
||
color: #ff2e2e;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.order-actions {
|
||
margin-left: auto;
|
||
display: flex;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.order-action {
|
||
padding: 12rpx 28rpx;
|
||
border-radius: 999rpx;
|
||
border: 2rpx solid rgba(79, 116, 255, 0.35);
|
||
font-size: 24rpx;
|
||
color: #4f5f7a;
|
||
}
|
||
|
||
.order-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 80rpx 32rpx;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.order-empty-icon {
|
||
font-size: 80rpx;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.order-empty-text {
|
||
font-size: 28rpx;
|
||
color: #8c94a3;
|
||
}
|
||
</style> |