diff --git a/app.js b/app.js
index 438c558..2da3956 100644
--- a/app.js
+++ b/app.js
@@ -57,6 +57,18 @@ App({
});
},
+ // Force refresh login (e.g. on 401)
+ forceRefreshLogin() {
+ // Reset Promise
+ this.loginPromise = new Promise((resolve, reject) => {
+ this._resolveLogin = resolve;
+ this._rejectLogin = reject;
+ });
+ wx.removeStorageSync('token');
+ this.doLogin();
+ return this.loginPromise;
+ },
+
// Method for other pages/utils to wait for login
ensureLogin() {
// If token exists, resolve immediately
diff --git a/app.json b/app.json
index f259092..2f5a002 100644
--- a/app.json
+++ b/app.json
@@ -14,7 +14,8 @@
"pages/wiki/identify/index",
"pages/profile/identify-history/index",
"pages/profile/badges/index",
- "pages/profile/badges/level-detail/index"
+ "pages/profile/badges/level-detail/index",
+ "pages/profile/badges/badge-wall/index"
],
"window": {
"backgroundTextStyle": "light",
diff --git a/pages/profile/badges/badge-wall/index.js b/pages/profile/badges/badge-wall/index.js
new file mode 100644
index 0000000..f96832c
--- /dev/null
+++ b/pages/profile/badges/badge-wall/index.js
@@ -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() { }
+});
diff --git a/pages/profile/badges/badge-wall/index.json b/pages/profile/badges/badge-wall/index.json
new file mode 100644
index 0000000..cec49f6
--- /dev/null
+++ b/pages/profile/badges/badge-wall/index.json
@@ -0,0 +1,8 @@
+{
+ "navigationBarTitleText": "我的成就与徽章",
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-image": "tdesign-miniprogram/image/image",
+ "t-loading": "tdesign-miniprogram/loading/loading"
+ }
+}
\ No newline at end of file
diff --git a/pages/profile/badges/badge-wall/index.wxml b/pages/profile/badges/badge-wall/index.wxml
new file mode 100644
index 0000000..704860e
--- /dev/null
+++ b/pages/profile/badges/badge-wall/index.wxml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{badge.tier === 1 ? '铜' : (badge.tier === 2 ? '银' : '金')}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{selectedBadge.name}}
+
+
+ {{achievedMap[selectedBadge.id] ? '已获得' : '未解锁'}}
+
+
+
+ {{selectedBadge.description}}
+
+
+
+ 奖励
+
+ ☀️
+ +{{selectedBadge.rewardSunlight}} 阳光值
+
+
+
+
+ 解锁条件:{{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' ? '次深夜养护' : '次操作'))))))}}
+
+
+
+
+
diff --git a/pages/profile/badges/badge-wall/index.wxss b/pages/profile/badges/badge-wall/index.wxss
new file mode 100644
index 0000000..7bd9e5e
--- /dev/null
+++ b/pages/profile/badges/badge-wall/index.wxss
@@ -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;
+}
diff --git a/pages/profile/badges/index.js b/pages/profile/badges/index.js
index 5597fd4..88c273f 100644
--- a/pages/profile/badges/index.js
+++ b/pages/profile/badges/index.js
@@ -102,4 +102,10 @@ Page({
url: `/pages/profile/badges/level-detail/index?sunlight=${this.data.currentExp}`
});
},
+
+ openBadgeWall() {
+ wx.navigateTo({
+ url: '/pages/profile/badges/badge-wall/index'
+ });
+ },
});
diff --git a/pages/profile/badges/index.wxml b/pages/profile/badges/index.wxml
index 84e0044..6b075c8 100644
--- a/pages/profile/badges/index.wxml
+++ b/pages/profile/badges/index.wxml
@@ -24,17 +24,22 @@
点击查看等级详情 >
- 所有徽章 ({{badges.length}})
+ 我的成就
-
-
-
-
-
+
+
+
+
+
- {{item.name}}
- {{item.desc}}
- {{item.progress}}
+
+ 成就徽章墙
+ 查看所有成就与收集进度
+
+
+
+ 去查看
+
diff --git a/pages/profile/badges/index.wxss b/pages/profile/badges/index.wxss
index c3ec71f..cf6dc14 100644
--- a/pages/profile/badges/index.wxss
+++ b/pages/profile/badges/index.wxss
@@ -218,56 +218,74 @@ page {
margin-left: 12rpx;
}
-.badges-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 20rpx;
-}
-
-.badge-item {
- background: #fff;
- border-radius: 24rpx;
- padding: 32rpx 16rpx;
+.badge-wall-entry {
+ margin: 10rpx 0 40rpx;
+ background: linear-gradient(120deg, #1976D2, #64B5F6);
+ border-radius: 32rpx;
+ padding: 32rpx 40rpx;
display: flex;
- flex-direction: column;
+ justify-content: space-between;
align-items: center;
- text-align: center;
- box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.02);
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 16rpx 32rpx rgba(25, 118, 210, 0.25);
}
-.badge-item.locked {
- background: #F8F9FA;
- border: 2rpx dashed #E5E7EB;
- box-shadow: none;
+.entry-bg-bloom {
+ position: absolute;
+ right: -40rpx;
+ top: -60rpx;
+ width: 240rpx;
+ height: 240rpx;
+ background: radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 70%);
+ border-radius: 50%;
+ z-index: 1;
}
-.badge-icon-circle {
- width: 88rpx;
- height: 88rpx;
- border-radius: 30rpx;
+.wall-entry-left {
+ display: flex;
+ align-items: center;
+ gap: 24rpx;
+ z-index: 2;
+}
+
+.entry-icon-box {
+ width: 96rpx;
+ height: 96rpx;
+ background: rgba(255,255,255,0.2);
+ border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
- margin-bottom: 20rpx;
+ border: 2rpx solid rgba(255,255,255,0.1);
}
-.badge-name {
- font-size: 26rpx;
+.wall-entry-text {
+ display: flex;
+ flex-direction: column;
+ gap: 6rpx;
+}
+
+.entry-title {
+ color: white;
+ font-size: 32rpx;
font-weight: 700;
- color: #374151;
- margin-bottom: 6rpx;
}
-.badge-desc {
- font-size: 20rpx;
- color: #9CA3AF;
+.entry-desc {
+ color: rgba(255,255,255,0.8);
+ font-size: 24rpx;
}
-.badge-progress {
- margin-top: 12rpx;
- font-size: 20rpx;
- background: #F3F4F6;
- padding: 4rpx 12rpx;
- border-radius: 12rpx;
- color: #6B7280;
+.wall-entry-right {
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+ z-index: 2;
+}
+
+.entry-action {
+ color: rgba(255,255,255,0.9);
+ font-size: 26rpx;
+ font-weight: 600;
}
diff --git a/pages/profile/index.js b/pages/profile/index.js
index d9463cd..1dd2e9c 100644
--- a/pages/profile/index.js
+++ b/pages/profile/index.js
@@ -233,14 +233,16 @@ Page({
this.setData({ view: 'about' });
},
- goToAgreement() {
- // TODO: Navigate to agreement page or show inline
- wx.showToast({ title: '功能开发中', icon: 'none' });
- },
-
- goToPrivacy() {
- // TODO: Navigate to privacy page or show inline
- wx.showToast({ title: '功能开发中', icon: 'none' });
+ openDoc(e) {
+ if (wx.openPrivacyContract) {
+ wx.openPrivacyContract({
+ fail: () => {
+ wx.showToast({ title: '无法打开协议', icon: 'none' });
+ }
+ });
+ } else {
+ wx.showToast({ title: '当前微信版本不支持查看', icon: 'none' });
+ }
},
// ======== Profile Editor Popup ========
diff --git a/pages/profile/index.wxml b/pages/profile/index.wxml
index ccc9578..032fe05 100644
--- a/pages/profile/index.wxml
+++ b/pages/profile/index.wxml
@@ -81,6 +81,13 @@
一款专注于家庭植物养护的小程序。帮助你记录植物成长、制定养护计划、识别未知植物,与花友们分享养花心得。
+
+
@@ -117,7 +124,7 @@
{{taskDoneCount}}
- 养护
+ 养护次数
@@ -167,7 +174,7 @@
-
+