320 lines
10 KiB
JavaScript
320 lines
10 KiB
JavaScript
// pages/tasks/index.js
|
|
import request from '../../utils/request';
|
|
|
|
Page({
|
|
data: {
|
|
tasks: [],
|
|
groupedTasks: [],
|
|
progress: 0,
|
|
completingTask: null,
|
|
remark: '',
|
|
remarkPlaceholder: '',
|
|
scrollTop: 0,
|
|
|
|
// Reward Modals
|
|
showLevelUpModal: false,
|
|
levelUpData: null,
|
|
showBadgeModal: false,
|
|
badgeData: null
|
|
},
|
|
|
|
onLoad() {
|
|
this.fetchTodayTasks();
|
|
this._popupQueue = [];
|
|
},
|
|
|
|
onShow() {
|
|
if (typeof this.getTabBar === 'function' &&
|
|
this.getTabBar()) {
|
|
this.getTabBar().setData({ selected: 1 });
|
|
}
|
|
// Refresh on show
|
|
this.fetchTodayTasks();
|
|
},
|
|
|
|
// ... (fetchTodayTasks, processTaskData Omitted) ...
|
|
|
|
// Reward Queue Processing
|
|
processPopupQueue() {
|
|
if (this._popupQueue.length === 0) return;
|
|
|
|
const next = this._popupQueue[0];
|
|
if (next.type === 'level') {
|
|
this.setData({
|
|
levelUpData: next.data,
|
|
showLevelUpModal: true
|
|
});
|
|
} else if (next.type === 'badge') {
|
|
this.setData({
|
|
badgeData: next.data,
|
|
showBadgeModal: true
|
|
});
|
|
}
|
|
},
|
|
|
|
closeLevelUpModal() {
|
|
this.setData({ showLevelUpModal: false });
|
|
this._popupQueue.shift();
|
|
setTimeout(() => {
|
|
this.processPopupQueue();
|
|
}, 300);
|
|
},
|
|
|
|
closeBadgeModal() {
|
|
this.setData({ showBadgeModal: false });
|
|
this._popupQueue.shift();
|
|
setTimeout(() => {
|
|
this.processPopupQueue();
|
|
}, 300);
|
|
},
|
|
|
|
fetchTodayTasks() {
|
|
request.get('/plant/todayTask').then(res => {
|
|
// Check if res is array (list of PlantTaskVO)
|
|
const list = Array.isArray(res) ? res : (res.list || []);
|
|
this.processTaskData(list);
|
|
}).catch(err => {
|
|
console.error('Fetch tasks failed', err);
|
|
wx.stopPullDownRefresh();
|
|
});
|
|
},
|
|
|
|
processTaskData(plantTaskVOList) {
|
|
let totalPacketTasks = 0;
|
|
let completedPacketTasks = 0;
|
|
|
|
const groups = plantTaskVOList.map(vo => {
|
|
const plant = vo.MyPlant || vo.myPlant;
|
|
if (!plant) return null;
|
|
|
|
// Parse Image
|
|
let imageUrl = '';
|
|
if (plant.imgList && plant.imgList.length > 0) {
|
|
imageUrl = plant.imgList[0].url || '';
|
|
}
|
|
|
|
const plantGroup = {
|
|
plantName: plant.name,
|
|
plantImage: imageUrl,
|
|
tasks: [], // Placeholder, will fill below
|
|
hasOverdue: vo.hasExpired
|
|
};
|
|
|
|
const rawTasks = vo.tasks || [];
|
|
|
|
// 1. Update Global Counters
|
|
rawTasks.forEach(t => {
|
|
totalPacketTasks++;
|
|
if (t.status == 2 || t.status == 3) {
|
|
completedPacketTasks++;
|
|
}
|
|
});
|
|
|
|
// 2. Filter and Map Tasks for Display
|
|
const displayTasks = rawTasks
|
|
.filter(t => t.status == 1 || t.status == 2)
|
|
.map(t => {
|
|
// Status: 1 Pending, 2 Done
|
|
const isCompleted = t.status == 2;
|
|
|
|
// Parse Icon
|
|
let taskIcon = null;
|
|
if (t.icon && t.icon.startsWith('{')) {
|
|
try {
|
|
taskIcon = JSON.parse(t.icon);
|
|
} catch (e) { }
|
|
}
|
|
|
|
// Check overdue (only for pending tasks)
|
|
let isOverdue = false;
|
|
let overdueDays = 0;
|
|
if (!isCompleted && t.dueDate) {
|
|
const due = new Date(t.dueDate);
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
|
|
if (due < today) {
|
|
isOverdue = true;
|
|
const diffTime = Math.abs(today - due);
|
|
overdueDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
}
|
|
}
|
|
|
|
return {
|
|
id: t.id,
|
|
taskType: t.name,
|
|
taskIcon: taskIcon,
|
|
isOverdue: isOverdue,
|
|
overdueDays: overdueDays,
|
|
plantName: plant.name,
|
|
isCompleted: isCompleted,
|
|
original: t
|
|
};
|
|
});
|
|
|
|
// Sorting Removed: Tasks stay in original order
|
|
// displayTasks.sort((a, b) => {
|
|
// if (a.isCompleted === b.isCompleted) return 0;
|
|
// return a.isCompleted ? 1 : -1;
|
|
// });
|
|
|
|
plantGroup.tasks = displayTasks;
|
|
|
|
if (plantGroup.tasks.length === 0) return null;
|
|
return plantGroup;
|
|
}).filter(g => g !== null);
|
|
|
|
// Calculate Progress
|
|
let progress = 0;
|
|
if (totalPacketTasks > 0) {
|
|
progress = Math.round((completedPacketTasks / totalPacketTasks) * 100);
|
|
}
|
|
|
|
// Sorting Groups: Overdue first
|
|
groups.sort((a, b) => {
|
|
if (a.hasOverdue && !b.hasOverdue) return -1;
|
|
if (!a.hasOverdue && b.hasOverdue) return 1;
|
|
return 0;
|
|
});
|
|
|
|
this.setData({
|
|
groupedTasks: groups,
|
|
progress,
|
|
tasks: groups
|
|
});
|
|
|
|
wx.stopPullDownRefresh();
|
|
},
|
|
|
|
handleTaskClick(e) {
|
|
const task = e.currentTarget.dataset.task;
|
|
if (task.isCompleted) return;
|
|
|
|
// Compute placeholder based on task type or name
|
|
let placeholder = '添加备注 (可选)...';
|
|
// Get name from taskIcon first, then name.
|
|
// name from backend is usually the name (e.g. "浇水").
|
|
const typeRaw = (task.taskIcon ? task.taskIcon.name : task.name) || '';
|
|
const typeStr = typeRaw.toLowerCase();
|
|
|
|
if (typeStr.includes('浇水')) {
|
|
placeholder = '例如:浇了 200ml 水...';
|
|
} else if (typeStr.includes('施肥')) {
|
|
placeholder = '例如:施了通用液肥...';
|
|
} else if (typeStr.includes('修剪')) {
|
|
placeholder = '例如:修剪了枯叶和黄叶...';
|
|
} else if (typeStr.includes('换盆')) {
|
|
placeholder = '例如:更换了陶盆,加了底肥...';
|
|
} else if (typeStr.includes('换土')) {
|
|
placeholder = '例如:更换了新配方土...';
|
|
} else if (typeStr.includes('喷雾')) {
|
|
placeholder = '例如:给叶面喷了水...';
|
|
} else if (typeStr.includes('用药') || typeStr.includes('虫')) {
|
|
placeholder = '例如:喷洒了杀虫剂...';
|
|
} else if (typeStr.includes('晒太阳') || typeStr.includes('日照')) {
|
|
placeholder = '例如:晒了 2 小时太阳...';
|
|
}
|
|
|
|
this.setData({
|
|
completingTask: task,
|
|
remark: '',
|
|
remarkPlaceholder: placeholder
|
|
});
|
|
},
|
|
|
|
onPopupVisibleChange(e) {
|
|
const visible = e.detail ? e.detail.visible : e.currentTarget.dataset.visible;
|
|
if (!visible) {
|
|
this.setData({ completingTask: null });
|
|
}
|
|
},
|
|
|
|
onRemarkInput(e) {
|
|
this.setData({ remark: e.detail.value });
|
|
},
|
|
|
|
handleConfirmComplete() {
|
|
if (!this.data.completingTask) return;
|
|
|
|
const taskId = this.data.completingTask.id;
|
|
const remark = this.data.remark || '';
|
|
|
|
wx.showLoading({ title: '提交中...', mask: true });
|
|
|
|
request.post('/plant/completeTask', {
|
|
taskId: taskId,
|
|
remark: remark
|
|
}).then(res => {
|
|
wx.hideLoading();
|
|
|
|
// Handle Rewards
|
|
const queue = [];
|
|
// Check if res has level up or badge data
|
|
// Note: res is already data.data from request.js
|
|
if (res && res.isLevelUp && res.currentLevel) {
|
|
queue.push({ type: 'level', data: res.currentLevel });
|
|
}
|
|
|
|
// Check for Badge using IsGetBadge flag (allowing for casing variance)
|
|
if (res && (res.IsGetBadge === true || res.isGetBadge === true) && res.newBadge) {
|
|
queue.push({ type: 'badge', data: res.newBadge });
|
|
}
|
|
|
|
this._popupQueue = queue;
|
|
|
|
// Optimistic UI Update Logic
|
|
const groups = this.data.groupedTasks;
|
|
let updated = false;
|
|
// Need to deep clone possibly, but here we modify and set back
|
|
for (let g of groups) {
|
|
// g is an object. groupedTasks is array of objects.
|
|
if (g.tasks) {
|
|
const t = g.tasks.find(x => x && x.id === taskId);
|
|
if (t) {
|
|
t.isCompleted = true;
|
|
t.isOverdue = false;
|
|
updated = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Trigger Animation and Close Modal
|
|
this.setData({
|
|
completingTask: null,
|
|
remark: '',
|
|
groupedTasks: groups, // Update UI
|
|
tasks: groups,
|
|
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' });
|
|
});
|
|
},
|
|
|
|
gotoGarden() {
|
|
wx.switchTab({
|
|
url: '/pages/garden/index'
|
|
});
|
|
},
|
|
|
|
onTabItemTap() {
|
|
this.setData({ scrollTop: Math.random() * 0.01 });
|
|
}
|
|
})
|