feat: 完成任务添加订阅

This commit is contained in:
Blizzard
2026-02-14 15:38:48 +08:00
parent 2d8ffd842a
commit 5800466e69
5 changed files with 123 additions and 113 deletions
+22 -28
View File
@@ -25,45 +25,39 @@ Page({
this.setData({ isLoading: true }); this.setData({ isLoading: true });
wx.showLoading({ title: '加载中...' }); wx.showLoading({ title: '加载中...' });
try { try {
// Fetch Config Tree // Parallel Fetch: Config Tree & User Badges
const treeRes = await request.get('/config/badge/tree'); const [treeRes, userBadgesRes] = await Promise.all([
request.get('/config/badge/tree'),
request.get('/profile/badge')
]);
const list = Array.isArray(treeRes) ? treeRes : (treeRes.data || []); const list = Array.isArray(treeRes) ? treeRes : (treeRes.data || []);
// DEBUG: Force Unlock All // Extract user badge list from response structure { list: [...] }
let userBadgeList = [];
if (Array.isArray(userBadgesRes)) {
userBadgeList = userBadgesRes;
} else if (userBadgesRes && Array.isArray(userBadgesRes.list)) {
userBadgeList = userBadgesRes.list;
} else if (userBadgesRes && userBadgesRes.data) {
userBadgeList = userBadgesRes.data || [];
}
// Populate Achieved Map using Badge ID (not Record ID)
let achievedMap = {}; let achievedMap = {};
list.forEach(dim => { userBadgeList.forEach(b => {
if (dim.groups) { const badgeId = b.badgeId || (b.badge ? b.badge.id : null);
dim.groups.forEach(grp => { if (badgeId) {
if (grp.badges) { achievedMap[badgeId] = b;
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({ this.setData({
dimensions: list, dimensions: list,
achievedMap achievedMap
}); });
} catch (e) { } catch (e) {
console.error('Fetch badge tree failed', e); console.error('Fetch badge data failed', e);
wx.showToast({ title: '加载失败', icon: 'none' }); wx.showToast({ title: '加载失败', icon: 'none' });
} finally { } finally {
this.setData({ isLoading: false }); this.setData({ isLoading: false });
+61 -56
View File
@@ -1,5 +1,6 @@
// pages/tasks/index.js // pages/tasks/index.js
import request from '../../utils/request'; import request from '../../utils/request';
import { requestSubscription } from '../../utils/subscribe';
Page({ Page({
data: { data: {
@@ -239,71 +240,75 @@ Page({
const taskId = this.data.completingTask.id; const taskId = this.data.completingTask.id;
const remark = this.data.remark || ''; const remark = this.data.remark || '';
wx.showLoading({ title: '提交中...', mask: true }); // Attempt to subscribe (silent mode avoids error popups if disabled)
// This encourages "Always Allow" behavior for seamless experience
requestSubscription(undefined, true).then(() => {
wx.showLoading({ title: '提交中...', mask: true });
request.post('/plant/completeTask', { request.post('/plant/completeTask', {
taskId: taskId, taskId: taskId,
remark: remark remark: remark
}).then(res => { }).then(res => {
wx.hideLoading(); wx.hideLoading();
// Handle Rewards // Handle Rewards
const queue = []; const queue = [];
// Check if res has level up or badge data // Check if res has level up or badge data
// Note: res is already data.data from request.js // Note: res is already data.data from request.js
if (res && res.isLevelUp && res.currentLevel) { if (res && res.isLevelUp && res.currentLevel) {
queue.push({ type: 'level', data: res.currentLevel }); queue.push({ type: 'level', data: res.currentLevel });
} }
// Check for Badge using IsGetBadge flag (allowing for casing variance) // Check for Badge using IsGetBadge flag (allowing for casing variance)
if (res && (res.IsGetBadge === true || res.isGetBadge === true) && res.newBadge) { if (res && (res.IsGetBadge === true || res.isGetBadge === true) && res.newBadge) {
queue.push({ type: 'badge', data: res.newBadge }); queue.push({ type: 'badge', data: res.newBadge });
} }
this._popupQueue = queue; this._popupQueue = queue;
// Optimistic UI Update Logic // Optimistic UI Update Logic
const groups = this.data.groupedTasks; const groups = this.data.groupedTasks;
let updated = false; let updated = false;
// Need to deep clone possibly, but here we modify and set back // Need to deep clone possibly, but here we modify and set back
for (let g of groups) { for (let g of groups) {
// g is an object. groupedTasks is array of objects. // g is an object. groupedTasks is array of objects.
if (g.tasks) { if (g.tasks) {
const t = g.tasks.find(x => x && x.id === taskId); const t = g.tasks.find(x => x && x.id === taskId);
if (t) { if (t) {
t.isCompleted = true; t.isCompleted = true;
t.isOverdue = false; t.isOverdue = false;
updated = true; updated = true;
break; break;
}
} }
} }
}
// Trigger Animation and Close Modal // Trigger Animation and Close Modal
this.setData({ this.setData({
completingTask: null, completingTask: null,
remark: '', remark: '',
groupedTasks: groups, // Update UI groupedTasks: groups, // Update UI
tasks: groups, tasks: groups,
showSunshine: true showSunshine: true
});
// Hide Animation after duration and Start showing modals
setTimeout(() => {
this.setData({ showSunshine: false });
// Show rewards after sunshine animation
if (this._popupQueue.length > 0) {
this.processPopupQueue();
}
}, 1000); // 1.0s delay usually covers animation
// Sync with backend silently
this.fetchTodayTasks();
}).catch(err => {
wx.hideLoading();
console.error('Complete task failed', err);
wx.showToast({ title: '操作失败', icon: 'none' });
}); });
// Hide Animation after duration and Start showing modals
setTimeout(() => {
this.setData({ showSunshine: false });
// Show rewards after sunshine animation
if (this._popupQueue.length > 0) {
this.processPopupQueue();
}
}, 1000); // 1.0s delay usually covers animation
// Sync with backend silently
this.fetchTodayTasks();
}).catch(err => {
wx.hideLoading();
console.error('Complete task failed', err);
wx.showToast({ title: '操作失败', icon: 'none' });
}); });
}, },
+11 -9
View File
@@ -10,30 +10,32 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 100vh; height: 100vh;
padding: 48rpx; padding: 48rpx;
box-sizing: border-box;
padding-bottom: 20%; /* Push up visually */
} }
.state-card { .state-card {
background: #fff; background: #fff;
border-radius: 40rpx; border-radius: 48rpx;
padding: 56rpx 48rpx; padding: 64rpx 48rpx;
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 32rpx; gap: 40rpx;
box-shadow: 0 12rpx 40rpx rgba(85, 139, 47, 0.08); box-shadow: 0 20rpx 60rpx rgba(85, 139, 47, 0.15);
} }
/* ========== Loading State ========== */ /* ========== Loading State ========== */
.loading-image-wrap { .loading-image-wrap {
width: 280rpx; width: 440rpx;
height: 280rpx; height: 440rpx;
border-radius: 32rpx; border-radius: 40rpx;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.1); box-shadow: 0 12rpx 40rpx rgba(0,0,0,0.15);
} }
.loading-preview { .loading-preview {
+3 -3
View File
@@ -60,12 +60,12 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 36rpx; padding: 0 28rpx;
height: 72rpx; height: 72rpx;
background: #fff; background: #fff;
border-radius: 36rpx; border-radius: 36rpx;
margin-right: 24rpx; margin-right: 16rpx;
font-size: 28rpx; font-size: 26rpx;
color: #546E7A; color: #546E7A;
font-weight: 600; font-weight: 600;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04); box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04);
+26 -17
View File
@@ -1,38 +1,47 @@
/** /**
* Request WeChat Mini Program Subscription Message * Request WeChat Mini Program Subscription Message
* Template ID: R7fh3NDpuV8DYqI83HpEQvC8mLJy5xMWFl1qeGN9JIo * Template ID: iG5GYMPQAgKxIE9zZNOgKS6tCURhM9p9AC8iZ3Uj3uA
*/ */
const TEMPLATE_ID = 'R7fh3NDpuV8DYqI83HpEQvC8mLJy5xMWFl1qeGN9JIo'; const DEFAULT_TEMPLATE_ID = 'iG5GYMPQAgKxIE9zZNOgKS6tCURhM9p9AC8iZ3Uj3uA';
export const requestSubscription = () => { export const requestSubscription = (tmplIds = [DEFAULT_TEMPLATE_ID], silent = false) => {
return new Promise((resolve) => { return new Promise((resolve) => {
// Check if subscription capability is available (basic check)
if (!wx.requestSubscribeMessage) { if (!wx.requestSubscribeMessage) {
console.warn('Current version does not support subscribe message'); if (!silent) console.warn('Current version does not support subscribe message');
resolve({ success: false, errMsg: 'Not supported' }); resolve({ success: false, errMsg: 'Not supported' });
return; return;
} }
wx.requestSubscribeMessage({ wx.requestSubscribeMessage({
tmplIds: [TEMPLATE_ID], tmplIds: tmplIds,
success(res) { success(res) {
if (res[TEMPLATE_ID] === 'accept') { // If any of the requested IDs are accepted
const isAccepted = tmplIds.some(id => res[id] === 'accept');
resolve({ success: true, status: 'accept' }); resolve({ success: isAccepted, res });
} else {
resolve({ success: false, status: res[TEMPLATE_ID] });
}
}, },
fail(err) { fail(err) {
console.error('Subscription failed', err); if (!silent) console.error('Subscription failed', err);
resolve({ success: false, errMsg: err.errMsg });
// 20004: User closed main switch in settings
if (err.errCode === 20004 && !silent) {
wx.showModal({
title: '提示',
content: '请在设置中开启订阅消息通知',
confirmText: '去开启',
success: (modalRes) => {
if (modalRes.confirm) {
wx.openSetting();
}
}
});
}
resolve({ success: false, error: err });
} }
}); });
}); });
}; };
export const checkSubscriptionSettings = () => { export const checkSubscriptionSettings = (tmplId = DEFAULT_TEMPLATE_ID) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!wx.getSetting) { if (!wx.getSetting) {
resolve(undefined); resolve(undefined);
@@ -43,7 +52,7 @@ export const checkSubscriptionSettings = () => {
withSubscriptions: true, withSubscriptions: true,
success(res) { success(res) {
const itemSettings = (res.subscriptionsSetting && res.subscriptionsSetting.itemSettings) || {}; const itemSettings = (res.subscriptionsSetting && res.subscriptionsSetting.itemSettings) || {};
resolve(itemSettings[TEMPLATE_ID]); resolve(itemSettings[tmplId]);
}, },
fail() { fail() {
resolve(undefined); resolve(undefined);