feat: 修复登录逻辑
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import request from '../../../../utils/request';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
dimensions: [],
|
||||
achievedMap: {},
|
||||
isLoading: true,
|
||||
selectedBadge: null,
|
||||
showDetail: false,
|
||||
activeTab: 0
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
switchTab(e) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
if (index !== undefined) {
|
||||
this.setData({ activeTab: index });
|
||||
}
|
||||
},
|
||||
|
||||
async fetchData() {
|
||||
this.setData({ isLoading: true });
|
||||
wx.showLoading({ title: '加载中...' });
|
||||
try {
|
||||
// Fetch Config Tree
|
||||
const treeRes = await request.get('/config/badge/tree');
|
||||
const list = Array.isArray(treeRes) ? treeRes : (treeRes.data || []);
|
||||
|
||||
// DEBUG: Force Unlock All
|
||||
let achievedMap = {};
|
||||
list.forEach(dim => {
|
||||
if (dim.groups) {
|
||||
dim.groups.forEach(grp => {
|
||||
if (grp.badges) {
|
||||
grp.badges.forEach(b => {
|
||||
achievedMap[b.id] = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Original logic commented out for debug
|
||||
/*
|
||||
try {
|
||||
const profile = await request.get('/profile/detail');
|
||||
if (profile && profile.achievedBadges) {
|
||||
profile.achievedBadges.forEach(b => {
|
||||
const id = typeof b === 'string' ? b : b.id;
|
||||
achievedMap[id] = true;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent fail
|
||||
}
|
||||
*/
|
||||
|
||||
this.setData({
|
||||
dimensions: list,
|
||||
achievedMap
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Fetch badge tree failed', e);
|
||||
wx.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
this.setData({ isLoading: false });
|
||||
wx.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
onBadgeTap(e) {
|
||||
const badge = e.currentTarget.dataset.badge;
|
||||
this.setData({
|
||||
selectedBadge: badge,
|
||||
showDetail: true
|
||||
});
|
||||
},
|
||||
|
||||
closeDetail() {
|
||||
this.setData({ showDetail: false });
|
||||
},
|
||||
|
||||
noop() { }
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的成就与徽章",
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-image": "tdesign-miniprogram/image/image",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<view class="badge-wall-page">
|
||||
|
||||
<!-- Dimension Tabs -->
|
||||
<view class="tabs-container">
|
||||
<scroll-view class="dimension-tabs" scroll-x enable-flex show-scrollbar="{{false}}">
|
||||
<block wx:for="{{dimensions}}" wx:key="dimension">
|
||||
<view class="tab-item {{activeTab === index ? 'active' : ''}}"
|
||||
bindtap="switchTab" data-index="{{index}}">
|
||||
<text class="tab-label">{{item.label}}</text>
|
||||
<view class="tab-indicator" wx:if="{{activeTab === index}}"></view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- Content Area -->
|
||||
<scroll-view scroll-y class="wall-scroll" enable-back-to-top show-scrollbar="{{false}}">
|
||||
<view class="wall-container" wx:if="{{dimensions[activeTab]}}">
|
||||
|
||||
<block wx:for="{{dimensions[activeTab].groups}}" wx:key="groupId" wx:for-item="group">
|
||||
<view class="achievement-track-card">
|
||||
<view class="track-header">
|
||||
<view class="track-title-row">
|
||||
<t-icon name="caret-right-small" size="40rpx" color="#558B2F" />
|
||||
<text class="track-title">{{group.groupLabel}}</text>
|
||||
</view>
|
||||
<text class="track-sub">完成 {{0}}/3</text> <!-- Placeholder count -->
|
||||
</view>
|
||||
|
||||
<view class="track-body">
|
||||
<!-- Background Line -->
|
||||
<view class="track-line-bg"></view>
|
||||
|
||||
<view class="track-badges-row">
|
||||
<block wx:for="{{group.badges}}" wx:key="id" wx:for-item="badge">
|
||||
<view class="track-node {{achievedMap[badge.id] ? 'unlocked' : 'locked'}}"
|
||||
bindtap="onBadgeTap" data-badge="{{badge}}">
|
||||
|
||||
<view class="node-icon-wrapper">
|
||||
<image src="{{badge.icon.url}}" mode="aspectFit" class="node-img" />
|
||||
<view class="lock-mask" wx:if="{{!achievedMap[badge.id]}}">
|
||||
<t-icon name="lock-on" size="36rpx" color="rgba(255,255,255,0.8)" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="node-info">
|
||||
<text class="node-tier-tag {{badge.tier===1?'bronze':(badge.tier===2?'silver':'gold')}}">{{badge.tier === 1 ? '铜' : (badge.tier === 2 ? '银' : '金')}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view style="height: 60rpx;"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- Detailed Popup -->
|
||||
<view class="badge-detail-mask {{showDetail ? 'show' : ''}}" bindtap="closeDetail">
|
||||
<view class="badge-detail-card" catchtap="noop">
|
||||
<view class="detail-close" bindtap="closeDetail">
|
||||
<t-icon name="close" size="40rpx" color="#999" />
|
||||
</view>
|
||||
|
||||
<block wx:if="{{selectedBadge}}">
|
||||
<view class="detail-icon-box {{achievedMap[selectedBadge.id] ? 'achieved' : 'locked'}} tier-{{selectedBadge.tier}}">
|
||||
<image src="{{selectedBadge.icon.url}}" mode="aspectFit" class="detail-img" />
|
||||
</view>
|
||||
|
||||
<text class="detail-name">{{selectedBadge.name}}</text>
|
||||
|
||||
<view class="detail-status-tag {{achievedMap[selectedBadge.id] ? 'success' : 'pending'}}">
|
||||
{{achievedMap[selectedBadge.id] ? '已获得' : '未解锁'}}
|
||||
</view>
|
||||
|
||||
<view class="detail-desc-box">
|
||||
<text class="detail-desc">{{selectedBadge.description}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-reward-box">
|
||||
<text class="reward-label">奖励</text>
|
||||
<view class="reward-val">
|
||||
<text>☀️</text>
|
||||
<text>+{{selectedBadge.rewardSunlight}} 阳光值</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-condition">
|
||||
<text>解锁条件:{{selectedBadge.threshold}} {{selectedBadge.targetAction === 'ACT_ALIVE_DAYS' ? '天存活' : (selectedBadge.targetAction === 'ACT_WATER' ? '次浇水' : (selectedBadge.targetAction === 'ACT_FERTILIZE' ? '次施肥' : (selectedBadge.targetAction === 'ACT_PRUNE' ? '次修剪' : (selectedBadge.targetAction === 'ACT_REPOT' ? '次换盆' : (selectedBadge.targetAction === 'ACT_PHOTO' ? '张照片' : (selectedBadge.targetAction === 'ACT_NIGHT_CARE' ? '次深夜养护' : '次操作'))))))}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,378 @@
|
||||
/* Badge Wall Styles */
|
||||
page {
|
||||
background: #F5F7FA;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.badge-wall-page {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.tabs-container {
|
||||
background: white;
|
||||
padding: 0 10rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||||
z-index: 10;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dimension-tabs {
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
height: 96rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 32rpx;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
font-size: 28rpx;
|
||||
color: #90A4AE;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-label {
|
||||
color: #558B2F;
|
||||
font-weight: 700;
|
||||
font-size: 32rpx;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.tab-indicator {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
background: #558B2F;
|
||||
border-radius: 6rpx 6rpx 0 0;
|
||||
}
|
||||
|
||||
/* Content Scroll */
|
||||
.wall-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.wall-container {
|
||||
padding: 30rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
/* Track Card */
|
||||
.achievement-track-card {
|
||||
background: white;
|
||||
border-radius: 32rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(149, 157, 165, 0.08); /* Generic soft shadow */
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.track-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 48rpx;
|
||||
padding: 0 12rpx;
|
||||
}
|
||||
|
||||
.track-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.track-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #263238;
|
||||
}
|
||||
|
||||
.track-sub {
|
||||
font-size: 24rpx;
|
||||
color: #CFD8DC;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.track-body {
|
||||
position: relative;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.track-line-bg {
|
||||
position: absolute;
|
||||
top: 58rpx; /* Center of 120rpx icon approx */
|
||||
left: 40rpx;
|
||||
right: 40rpx;
|
||||
height: 4rpx;
|
||||
background: #E0E0E0;
|
||||
border-radius: 4rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Track Badges Row */
|
||||
.track-badges-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.track-node {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
position: relative;
|
||||
width: 140rpx; /* Tappable area */
|
||||
}
|
||||
|
||||
.node-icon-wrapper {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
background: #FAFAFA;
|
||||
border-radius: 50%;
|
||||
border: 6rpx solid white;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.track-node:active .node-icon-wrapper {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.track-node.unlocked .node-icon-wrapper {
|
||||
background: #FFF;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Unlocked Border Colors */
|
||||
/* Bronze */
|
||||
.track-node:nth-child(1).unlocked .node-icon-wrapper { border-color: #D7CCC8; }
|
||||
/* Silver */
|
||||
.track-node:nth-child(2).unlocked .node-icon-wrapper { border-color: #E0E0E0; }
|
||||
/* Gold */
|
||||
.track-node:nth-child(3).unlocked .node-icon-wrapper { border-color: #FFECB3; box-shadow: 0 0 24rpx rgba(255, 213, 79, 0.4); }
|
||||
|
||||
.node-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
/* Add hover effect for image instead of wrapper */
|
||||
.track-node:active .node-img {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.lock-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
margin: 5%;
|
||||
background: rgba(0,0,0,0.4);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.node-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.node-tier-tag {
|
||||
font-size: 20rpx;
|
||||
color: #90A4AE;
|
||||
background: #ECEFF1;
|
||||
padding: 6rpx 18rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.node-tier-tag.bronze { background: #EFEBE9; color: #8D6E63; }
|
||||
.node-tier-tag.silver { background: #FAFAFA; color: #757575; border: 1px solid #EEEEEE; }
|
||||
.node-tier-tag.gold {
|
||||
background: linear-gradient(135deg, #FFF8E1, #FFECB3);
|
||||
color: #F57F17;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 193, 7, 0.2);
|
||||
}
|
||||
|
||||
/* Detail Popup Styles (Refined) */
|
||||
.badge-detail-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.6);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
backdrop-filter: blur(8rpx);
|
||||
}
|
||||
|
||||
.badge-detail-mask.show {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.badge-detail-card {
|
||||
width: 80%;
|
||||
background: white;
|
||||
border-radius: 40rpx;
|
||||
padding: 56rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
transform: scale(0.95);
|
||||
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
box-shadow: 0 32rpx 64rpx rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.badge-detail-mask.show .badge-detail-card {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.detail-close {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 24rpx;
|
||||
padding: 16rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.detail-icon-box {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 32rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.detail-icon-box.achieved .detail-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: drop-shadow(0 16rpx 32rpx rgba(255, 213, 79, 0.5));
|
||||
}
|
||||
|
||||
.detail-icon-box.locked .detail-img {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
opacity: 0.4;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.detail-name {
|
||||
font-size: 40rpx;
|
||||
font-weight: 800;
|
||||
color: #263238;
|
||||
margin-bottom: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-status-tag {
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 40rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-status-tag.success {
|
||||
background: #E8F5E9;
|
||||
color: #2E7D32;
|
||||
}
|
||||
|
||||
.detail-status-tag.pending {
|
||||
background: #ECEFF1;
|
||||
color: #78909C;
|
||||
}
|
||||
|
||||
.detail-desc-box {
|
||||
background: #F5F7F9;
|
||||
padding: 32rpx;
|
||||
border-radius: 24rpx;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 30rpx;
|
||||
color: #455A64;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.detail-reward-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 32rpx;
|
||||
background: linear-gradient(135deg, #FFF8E1, #FFECB3);
|
||||
border-radius: 20rpx;
|
||||
height: 96rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.2);
|
||||
}
|
||||
|
||||
.reward-label {
|
||||
font-size: 28rpx;
|
||||
color: #E65100;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.reward-val {
|
||||
font-size: 36rpx;
|
||||
font-weight: 800;
|
||||
color: #E65100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.detail-condition {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #90A4AE;
|
||||
border-top: 2rpx dashed #CFD8DC;
|
||||
padding-top: 24rpx;
|
||||
}
|
||||
Reference in New Issue
Block a user