feat: 后端版本迁移修改

This commit is contained in:
Blizzard
2026-05-24 01:38:28 +08:00
parent 058a575e10
commit f8d71ee800
29 changed files with 304 additions and 291 deletions
+2 -2
View File
@@ -16,7 +16,7 @@ App({
wx.login({
success: res => {
if (res.code) {
request.get('/auth/miniLogin', { code: res.code }).then(async (data) => {
request.post('/auth/miniLogin', { code: res.code, clientId: 'sundynix-plant' }).then(async (data) => {
const token = data.token;
if (token && typeof token === 'string') {
@@ -25,7 +25,7 @@ App({
if (this._resolveLogin) this._resolveLogin(token);
// Background Profile Update
request.get('/profile/detail').then(userDetail => {
request.get('/plant/profile/detail').then(userDetail => {
if (userDetail) {
wx.setStorageSync('userInfo', userDetail);
this.globalData.userInfo = userDetail;
+4 -4
View File
@@ -24,7 +24,7 @@ Page({
},
fetchTopics() {
request.get('/topic/list').then(res => {
request.get('/plant/topic/list').then(res => {
const list = res.list || [];
const topics = list.map(t => t.title);
if (topics.length > 0) {
@@ -229,8 +229,8 @@ Page({
if (images.length > 0) {
const uploadPromises = images.map(filePath => {
return request.upload(filePath).then(res => {
// Res structure: { file: { id: "...", url: "..." } }
return res && res.file ? res.file.id : null;
// Backend returns flat FileInfo: {id, url, name, ...}
return res ? res.id : null;
});
});
@@ -257,7 +257,7 @@ Page({
ossIds: ossIds
};
await request.post('/post/publish', payload);
await request.post('/plant/post/publish', payload);
wx.hideLoading();
wx.showToast({ title: '发布成功', icon: 'success' });
+4 -4
View File
@@ -69,7 +69,7 @@ Page({
try {
// Correct API Endpoint and Params
const res = await request.post('/post/page', { current, pageSize, hasReviewed: 1 });
const res = await request.post('/plant/post/page', { current, pageSize, hasReviewed: 1 });
// Handle response structure: { code: 200, data: { list: [], ... } }
// OR if request.js unwraps it: { list: [], ... }
@@ -183,7 +183,7 @@ Page({
const type = post.likedByMe ? 2 : 1;
try {
await request.get('/post/like', { id: postId, type });
await request.get('/plant/post/like', { id: postId, type });
// Optimistic Update
const updatedPosts = this.data.posts.map(p => {
@@ -225,7 +225,7 @@ Page({
const type = post.isFavorited ? 2 : 1; // 1: Collect, 2: Cancel
try {
await request.get('/post/star', { id: postId, type });
await request.get('/plant/post/star', { id: postId, type });
// Optimistic Update
const updatedPosts = this.data.posts.map(p => {
@@ -283,7 +283,7 @@ Page({
}
try {
await request.post('/post/comment', {
await request.post('/plant/post/comment', {
postId: commentingPostId,
content: commentText.trim()
});
+3 -5
View File
@@ -97,11 +97,9 @@ Page({
request.upload(tempFilePath).then(data => {
wx.hideLoading();
// User provided response format: { data: { file: { url: ..., id: ... } } }
// request.js unwraps 'data', so 'data' here is { file: { ... } }
const fileData = data?.file || {};
const imageUrl = fileData.url;
const imageId = fileData.id;
// request.js unwraps data, response is flat FileInfo {id, url, name, ...}
const imageUrl = data?.url || '';
const imageId = data?.id || '';
if (imageUrl && imageId) {
this.setData({
+2 -2
View File
@@ -107,8 +107,8 @@ Page({
async loadBanners() {
try {
const res = await request.get('/plantBanner/activeList');
const list = (res.list || []).map(item => item.image ? item.image.url : '');
const res = await request.get('/plant/banner/activeList');
const list = (res.list || []).map(item => item.imageUrl || '');
this.setData({ bannerList: list.filter(Boolean) });
} catch (err) {
console.error('Load banners failed', err);
+23 -19
View File
@@ -42,9 +42,9 @@ Page({
if (eventChannel) {
eventChannel.on('acceptDataFromOpenerPage', (data) => {
if (data && data.plant) {
if (data) {
hasReceivedData = true;
this.renderPlantUI(data.plant);
this.renderPlantUI(data);
}
});
}
@@ -63,20 +63,24 @@ Page({
});
},
renderPlantUI(plant) {
renderPlantUI(data) {
if (!data) return;
// Detect if data is the raw API response or a flat eventChannel payload
const isRawResponse = data.hasOwnProperty('plant') && typeof data.plant === 'object';
const plant = isRawResponse ? data.plant : (data.plant || data);
const imgList = data.imgList || [];
const defaultIcon = CARE_TASK_ICONS.find(i => i.id === 'water') || CARE_TASK_ICONS[0];
let tasks = [];
if (plant.careSchedule) {
tasks = plant.careSchedule.map(cp => ({
id: cp.id, name: cp.name, period: cp.period,
taskIcon: cp.taskIcon, isNew: false,
_original: { name: cp.name, period: cp.period, icon: JSON.stringify(cp.taskIcon || {}) }
}));
} else if (plant.carePlans) {
tasks = plant.carePlans.map(cp => {
const carePlansSrc = isRawResponse ? (data.carePlans || []) : (data.carePlans || plant.careSchedule || []);
if (carePlansSrc && carePlansSrc.length > 0) {
tasks = carePlansSrc.map(cp => {
let iconObj = defaultIcon;
if (typeof cp.icon === 'string' && cp.icon.startsWith('{')) {
if (cp.taskIcon) {
iconObj = cp.taskIcon;
} else if (typeof cp.icon === 'string' && cp.icon.startsWith('{')) {
try { iconObj = JSON.parse(cp.icon); } catch (e) { }
}
const iconStr = JSON.stringify(iconObj);
@@ -89,12 +93,11 @@ Page({
}
let imageUrl = '', imageId = '';
if (plant.imgList && plant.imgList.length > 0) {
imageUrl = plant.imgList[0].url || '';
imageId = plant.imgList[0].id || '';
if (imgList && imgList.length > 0) {
imageUrl = imgList[0].url || '';
imageId = imgList[0].id || '';
}
let adoptionDate = plant.plantTime || '';
if (adoptionDate.includes('T')) adoptionDate = adoptionDate.split('T')[0];
@@ -142,9 +145,10 @@ Page({
wx.showLoading({ title: '上传中...' });
request.upload(tempFilePath).then(data => {
wx.hideLoading();
const fileData = data?.file || {};
if (fileData.id) {
this.setData({ uploadedImageId: fileData.id, newPlantImage: fileData.url });
const imageUrl = data?.url || '';
const imageId = data?.id || '';
if (imageId) {
this.setData({ uploadedImageId: imageId, newPlantImage: imageUrl });
}
}).catch(() => {
wx.hideLoading();
+1 -5
View File
@@ -10,7 +10,7 @@
>
<!-- Upload Area -->
<view class="upload-section">
<view class="image-upload-area {{newPlantImage ? 'has-image' : ''}}" bindtap="showActionSheet">
<view class="image-upload-area {{newPlantImage ? 'has-image' : ''}}">
<t-image
wx:if="{{newPlantImage}}"
src="{{newPlantImage}}"
@@ -23,10 +23,6 @@
<t-icon name="upload" size="64rpx" color="#999" />
<text>点击设置封面图</text>
</view>
<view wx:if="{{newPlantImage}}" class="edit-overlay">
<t-icon name="camera" size="32rpx" color="#FFF" />
<text>更换照片</text>
</view>
</view>
</view>
+1 -4
View File
@@ -61,10 +61,7 @@ Page({
if (this.data.image) {
const uploadRes = await request.upload(this.data.image);
// Correctly extract ID from nested 'file' object based on API response
if (uploadRes && uploadRes.file && uploadRes.file.id) {
ossIds.push(uploadRes.file.id);
} else if (uploadRes && uploadRes.id) {
// Fallback just in case
if (uploadRes && uploadRes.id) {
ossIds.push(uploadRes.id);
} else {
console.warn('Upload response structure mismatch:', uploadRes);
+20 -12
View File
@@ -36,11 +36,17 @@ Page({
initData(id) {
request.get('/plant/detail', { id }).then(plant => {
const swiperImages = plant.imgList.map(img => {
return img.url;
});
// Legacy handler returns {plant: PlantInfo, carePlans: [...], growthRecords: [...], imgList: [...], careRecords: [...]}
const plantData = plant.plant || {};
const carePlansList = plant.carePlans || [];
const careRecordsList = plant.careRecords || [];
const growthRecordsList = plant.growthRecords || [];
const imgList = plant.imgList || [];
const swiperImages = imgList.map(img => img.url);
// Parse carePlans icon if it's a string
const carePlans = (plant.carePlans || []).map(cp => {
const carePlans = carePlansList.map(cp => {
let iconObj = {};
if (typeof cp.icon === 'string' && cp.icon.startsWith('{')) {
try {
@@ -54,16 +60,16 @@ Page({
// Calculate days planted and format date
let adoptionDate = '未知';
const daysPlanted = calculateDaysSince(plant.plantTime);
const daysPlanted = calculateDaysSince(plantData.plantTime);
const ageBadge = getPlantAgeBadge(daysPlanted);
if (plant.plantTime) {
adoptionDate = plant.plantTime.split('T')[0];
if (plantData.plantTime) {
adoptionDate = plantData.plantTime.split('T')[0];
}
this.setData({
currentPlant: {
...plant,
location: plant.placement || '',
...plantData,
location: plantData.placement || '',
adoptionDate: adoptionDate,
daysPlanted: daysPlanted,
ageBadge: ageBadge,
@@ -71,8 +77,8 @@ Page({
},
swiperImages: swiperImages,
// Map logs and records directly from plant detail response
careLogs: this.processLogs(plant.careRecords || []),
records: (plant.growthRecords || plant.recordList || []).map(item => {
careLogs: this.processLogs(careRecordsList),
records: growthRecordsList.map(item => {
// Extract image URL safely
let imageUrl = '';
if (item.imgList && item.imgList.length > 0) {
@@ -235,7 +241,9 @@ Page({
success: (res) => {
// Send current data to the opened page
res.eventChannel.emit('acceptDataFromOpenerPage', {
plant: this.data.currentPlant
plant: this.data.currentPlant,
imgList: this.data.swiperImages.map((url, i) => ({ id: i === 0 ? this.data.currentPlant.imageId || 'primary' : '', url })),
carePlans: this.data.currentPlant.careSchedule
});
}
});
+2 -2
View File
@@ -27,8 +27,8 @@ Page({
try {
// Parallel Fetch: Config Tree & User Badges
const [treeRes, userBadgesRes] = await Promise.all([
request.get('/config/badge/tree'),
request.get('/profile/badge')
request.get('/plant/config/badge/tree'),
request.get('/plant/profile/badge')
]);
const list = Array.isArray(treeRes) ? treeRes : (treeRes.data || []);
+2 -2
View File
@@ -30,8 +30,8 @@ Page({
wx.showLoading({ title: '加载中...' });
try {
const [levelRes, profileRes] = await Promise.all([
request.get('/config/level/list'),
request.get('/profile/detail')
request.get('/plant/config/level/list'),
request.get('/plant/profile/detail')
]);
this.processData(levelRes, profileRes);
} catch (e) {
+2 -2
View File
@@ -25,7 +25,7 @@ Page({
wx.showLoading({ title: '加载中...' });
try {
// Fetch levels
const levelRes = await request.get('/config/level/list');
const levelRes = await request.get('/plant/config/level/list');
let list = [];
@@ -47,7 +47,7 @@ Page({
// Fetch profile if sunlight not passed
let currentSunlight = passedSunlight;
if (currentSunlight === undefined) {
const profileRes = await request.get('/profile/detail');
const profileRes = await request.get('/plant/profile/detail');
currentSunlight = profileRes.totalSunlight || 0;
this.setData({ currentSunlight });
+10 -38
View File
@@ -30,7 +30,7 @@ Page({
async fetchProfile() {
try {
const res = await request.get('/profile/detail');
const res = await request.get('/plant/profile/detail');
if (res) {
this.setData({ currentSunlight: res.currentSunlight || 0 });
}
@@ -44,7 +44,7 @@ Page({
this.setData({ isLoading: true, current: 1, items: [] });
}
try {
const res = await request.get('/exchange/list', {
const res = await request.get('/plant/exchange/list', {
current: this.data.current,
pageSize: this.data.pageSize,
type: this.data.activeType
@@ -52,35 +52,11 @@ Page({
const rawList = (res && res.list) ? res.list : [];
const total = (res && res.total) ? res.total : 0;
const now = Date.now();
const list = rawList.map(item => {
const hasStart = !!item.startTime;
const hasEnd = !!item.endTime;
const startTs = hasStart ? new Date(item.startTime).getTime() : 0;
const endTs = hasEnd ? new Date(item.endTime).getTime() : 0;
const notStarted = hasStart && now < startTs;
const hasEnded = hasEnd && now > endTs;
const isActive = !notStarted && !hasEnded;
let timeLabel = '';
if (hasStart && hasEnd) {
timeLabel = this.formatDate(item.startTime) + ' ~ ' + this.formatDate(item.endTime);
} else if (hasStart) {
timeLabel = this.formatDate(item.startTime) + ' 起';
} else if (hasEnd) {
timeLabel = '截止 ' + this.formatDate(item.endTime);
}
return {
...item,
hasTimeLimit: hasStart || hasEnd,
timeLabel,
notStarted,
hasEnded,
isActive
};
});
// Backend ExchangeItemInfo: {id, name, desc, imgId, cost, stock, status}
const list = rawList.map(item => ({
...item,
isActive: item.status === 1 || !item.status
}));
this.setData({
items: append ? [...this.data.items, ...list] : list,
@@ -115,12 +91,8 @@ Page({
// Redeem Flow
onItemTap(e) {
const item = e.currentTarget.dataset.item;
if (item.notStarted) {
wx.showToast({ title: '活动尚未开始', icon: 'none' });
return;
}
if (item.hasEnded) {
wx.showToast({ title: '活动已结束', icon: 'none' });
if (!item.isActive) {
wx.showToast({ title: '当前不可兑换', icon: 'none' });
return;
}
if (item.stock === 0) {
@@ -164,7 +136,7 @@ Page({
wx.showLoading({ title: '兑换中...', mask: true });
try {
await request.post('/exchange/redeem', {
await request.post('/plant/exchange/redeem', {
itemId: item.id,
quantity: 1,
...this.data.redeemForm
+1 -1
View File
@@ -44,7 +44,7 @@ Page({
if (this.data.activeStatus) {
params.status = this.data.activeStatus;
}
const res = await request.get('/exchange/orders', params);
const res = await request.get('/plant/exchange/orders', params);
let list = (res && res.list) ? res.list : [];
const total = (res && res.total) ? res.total : 0;
+2 -2
View File
@@ -33,7 +33,7 @@ Page({
if (this.data.favTab === 'plant') classType = 1;
if (this.data.favTab === 'post') classType = 2;
request.post('/profile/star', {
request.post('/plant/profile/star', {
current,
pageSize: this.data.pageSize,
class: classType
@@ -111,7 +111,7 @@ Page({
const item = e.currentTarget.dataset.item;
const { id, type } = item;
const apiPath = type === 'plant' ? '/wiki/star' : '/post/star';
const apiPath = type === 'plant' ? '/plant/wiki/star' : '/plant/post/star';
wx.showModal({
title: '提示',
+2 -2
View File
@@ -18,7 +18,7 @@ Page({
async loadRecords() {
this.setData({ loading: true });
try {
const res = await request.post('/classify/myClassifyLog', {
const res = await request.post('/plant/classify/myClassifyLog', {
page: this.data.page,
pageSize: this.data.pageSize,
});
@@ -89,7 +89,7 @@ Page({
success: (res) => {
if (res.confirm) {
wx.showLoading({ title: '删除中' });
request.post('/classify/deleteClassifyLog', { ids: [id] }).then(() => {
request.post('/plant/classify/deleteClassifyLog', { ids: [id] }).then(() => {
wx.hideLoading();
wx.showToast({ title: '删除成功', icon: 'success' });
const newRecords = this.data.records.filter(r => r.id !== id);
+23 -24
View File
@@ -56,39 +56,43 @@ Page({
// ======== User Info ========
loadUserInfo() {
request.get('/profile/detail').then(res => {
request.get('/plant/profile/detail').then(res => {
if (!res) return;
// Map stats and level info
const avatarUrl = res.avatar && res.avatar.url ? res.avatar.url : '';
const levelInfo = res.level || {};
const levelTag = levelInfo.level ? `Lv.${levelInfo.level} ${levelInfo.title || ''}` : '';
// Backend returns PlantUserProfile: {nickName, avatarId, levelId, currentSunlight, ...}
// Fetch avatar URL from file service if avatarId exists
const avatarId = res.avatarId || '';
let avatarUrl = '';
if (avatarId) {
request.get('/file/' + avatarId).then(fileInfo => {
if (fileInfo && fileInfo.url) {
this.setData({ userAvatar: fileInfo.url });
}
}).catch(() => {});
}
this.setData({
userName: res.nickname || '植物爱好者',
userName: res.nickName || '植物爱好者',
userAvatar: avatarUrl,
currentAvatarId: res.avatarId || (res.avatar ? res.avatar.id : ''),
currentAvatarId: avatarId,
// Stats
plantCount: res.plantCount || 0,
taskDoneCount: res.careCount || 0,
postCount: res.postCount || 0,
// Level (if available)
userLevel: levelInfo.level || 0,
userLevelTag: levelTag,
// Level (just store levelId, detail fetched on badges page)
userLevel: res.levelId || '',
userLevelTag: '',
// EXP / Sunlight
userExp: res.currentSunlight || 0,
userSunlight: res.currentSunlight || 0,
joinedDays: calculateDaysSince(res.createdAt)
joinedDays: 0 // createdAt not available in profile response
});
// Update global cache
const info = {
...res,
avatarId: res.avatarId || (res.avatar ? res.avatar.id : '')
};
const info = { ...res, avatarId: avatarId };
app.globalData.userInfo = info;
wx.setStorageSync('userInfo', info);
@@ -216,9 +220,7 @@ Page({
// Upload new avatar if changed
if (isAvatarChanged) {
const uploadRes = await request.upload(tempAvatar);
if (uploadRes && uploadRes.file && uploadRes.file.id) {
finalAvatarId = uploadRes.file.id;
} else if (uploadRes && uploadRes.id) {
if (uploadRes && uploadRes.id) {
finalAvatarId = uploadRes.id;
} else {
throw new Error('Avatar upload failed, no ID returned');
@@ -232,7 +234,7 @@ Page({
};
// Call API
await request.post('/profile/update', updatePayload);
await request.post('/plant/profile/update', updatePayload);
// Update UI State
this.setData({
@@ -246,13 +248,10 @@ Page({
// Update Global Data
const userInfo = app.globalData.userInfo || {};
userInfo.nickname = finalNickname;
userInfo.name = finalNickname;
// Update avatar structure in global store too
userInfo.nickName = finalNickname;
if (isAvatarChanged) {
userInfo.avatar = { ...(userInfo.avatar || {}), url: tempAvatar, id: finalAvatarId };
userInfo.avatarId = finalAvatarId;
}
userInfo.avatarId = finalAvatarId;
app.globalData.userInfo = userInfo;
wx.setStorageSync('userInfo', userInfo);
+2 -2
View File
@@ -36,7 +36,7 @@ Page({
const { pageSize } = this.data;
try {
const res = await request.post('/post/myPost', {
const res = await request.post('/plant/post/myPost', {
current,
pageSize
});
@@ -107,7 +107,7 @@ Page({
wx.showLoading({ title: '删除中...' });
// Use new API: POST /post/delete with ids array
request.post('/post/delete', { ids: [postId] }).then(() => {
request.post('/plant/post/delete', { ids: [postId] }).then(() => {
wx.hideLoading();
wx.showToast({ title: '已删除', icon: 'success' });
// Remove from list locally
+107 -94
View File
@@ -71,120 +71,133 @@ Page({
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);
// Backend returns flat CareTaskInfo list: [{id, plantId, planId, name, icon, targetAction, dueDate, status}]
const tasks = Array.isArray(res) ? res : (res.list || []);
this.processTaskData(tasks);
}).catch(err => {
console.error('Fetch tasks failed', err);
wx.stopPullDownRefresh();
});
},
processTaskData(plantTaskVOList) {
processTaskData(tasks) {
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 || '';
// Count stats from flat task list
tasks.forEach(t => {
totalPacketTasks++;
if (t.status == 2 || t.status == 3) {
completedPacketTasks++;
}
});
const plantGroup = {
plantName: plant.name,
plantImage: imageUrl,
tasks: [], // Placeholder, will fill below
hasOverdue: vo.hasExpired
};
// Group tasks by plantId, collect unique plant IDs
const plantTaskMap = {};
const plantIds = [];
tasks.forEach(t => {
if (!plantTaskMap[t.plantId]) {
plantTaskMap[t.plantId] = [];
plantIds.push(t.plantId);
}
plantTaskMap[t.plantId].push(t);
});
const rawTasks = vo.tasks || [];
// Fetch plant details for each unique plantId in parallel
Promise.all(plantIds.map(id =>
request.get('/plant/detail', { id }).catch(() => null)
)).then(plantDetails => {
const groups = [];
// 1. Update Global Counters
rawTasks.forEach(t => {
totalPacketTasks++;
if (t.status == 2 || t.status == 3) {
completedPacketTasks++;
plantIds.forEach((plantId, idx) => {
const plantDetail = plantDetails[idx];
const plant = plantDetail ? plantDetail.plant : null;
const imgList = plantDetail ? plantDetail.imgList : null;
const plantName = plant ? plant.name : '未知植物';
let imageUrl = '';
if (imgList && imgList.length > 0) {
imageUrl = imgList[0].url || '';
}
const rawTasks = plantTaskMap[plantId] || [];
// Check if any task is overdue
let hasOverdue = false;
const displayTasks = rawTasks
.filter(t => t.status == 1 || t.status == 2)
.map(t => {
const isCompleted = t.status == 2;
// Parse icon JSON
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;
hasOverdue = 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: plantName,
isCompleted: isCompleted,
original: t
};
});
if (displayTasks.length === 0) return;
groups.push({
plantName: plantName,
plantImage: imageUrl,
tasks: displayTasks,
hasOverdue: hasOverdue
});
});
// 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;
// Calculate Progress
let progress = 0;
if (totalPacketTasks > 0) {
progress = Math.round((completedPacketTasks / totalPacketTasks) * 100);
}
// Parse Icon
let taskIcon = null;
if (t.icon && t.icon.startsWith('{')) {
try {
taskIcon = JSON.parse(t.icon);
} catch (e) { }
}
// Sorting Groups: Overdue first
groups.sort((a, b) => {
if (a.hasOverdue && !b.hasOverdue) return -1;
if (!a.hasOverdue && b.hasOverdue) return 1;
return 0;
});
// 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);
this.setData({
groupedTasks: groups,
progress,
tasks: groups
});
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;
wx.stopPullDownRefresh();
}).catch(err => {
console.error('Fetch plant details failed', err);
wx.stopPullDownRefresh();
});
this.setData({
groupedTasks: groups,
progress,
tasks: groups
});
wx.stopPullDownRefresh();
},
handleTaskClick(e) {
+2 -2
View File
@@ -23,7 +23,7 @@ Page({
const current = reset ? 1 : this.data.current;
this.setData({ loading: true });
request.get('/plant/chat/history', { current, pageSize: this.data.pageSize })
request.post('/plant/chat/history', { current, pageSize: this.data.pageSize })
.then(res => {
const items = (res.list || []).map(item => ({
...item,
@@ -82,7 +82,7 @@ Page({
content: '确定删除这条问答记录吗?',
success: (res) => {
if (res.confirm) {
request.post('/plant/chat/history/delete', { id }).then(() => {
request.post('/plant/chat/history/delete', { ids: [id] }).then(() => {
wx.showToast({ title: '已删除', icon: 'success' });
this.fetchHistory(true);
});
+1 -1
View File
@@ -96,7 +96,7 @@ Page({
request.stream('/plant/chat/stream', { query }, {
onChunk: (res) => {
const text = this._decode(res.data);
const text = this._decode(res.chunk || res.data);
// Detect non-SSE JSON error (e.g. quota exceeded returns {code:7, msg:"..."})
if (!text.startsWith('data: ')) {
+44 -39
View File
@@ -26,10 +26,10 @@ Page({
},
loadPlantDetail(id) {
request.get('/wiki/detail', { id: id }).then(res => {
request.get('/plant/wiki/detail', { id: id }).then(res => {
const item = res || null;
if (!item) {
if (!item || !item.wiki) {
wx.showToast({ title: '未找到该植物', icon: 'none' });
return;
}
@@ -44,57 +44,62 @@ Page({
setPlantData(item) {
if (!item) return;
wx.setNavigationBarTitle({ title: item.name });
// 兼容两种数据来源:
// 1. 从 API 获取: {wiki: {name:...}, imgList: [...]}
// 2. 从列表页导航: {id:..., name:..., imgList: [...]} (raw item)
const wiki = item.wiki || item;
// Prepare swiper list
wx.setNavigationBarTitle({ title: wiki.name });
// Prepare swiper list — imgList 在顶层
const swiperList = (item.imgList || []).map(img => img.url);
// Parse lists
const commonPests = item.pestsDiseases
? item.pestsDiseases.split(',').map(s => s.trim()).filter(Boolean)
const commonPests = wiki.pestsDiseases
? wiki.pestsDiseases.split(',').map(s => s.trim()).filter(Boolean)
: [];
const aliasesList = item.aliases
? item.aliases.split(/[,,、]/).map(s => s.trim()).filter(Boolean)
const aliasesList = wiki.aliases
? wiki.aliases.split(/[,,、]/).map(s => s.trim()).filter(Boolean)
: [];
const reproductionList = item.reproductionMethod
? item.reproductionMethod.split(/[,,、]/).map(s => s.trim()).filter(Boolean)
const reproductionList = wiki.reproductionMethod
? wiki.reproductionMethod.split(/[,,、]/).map(s => s.trim()).filter(Boolean)
: [];
const diffLabels = { 1: '简单', 2: '中等', 3: '较难', 4: '困难', 5: '专家' };
const plant = {
id: item.id,
name: item.name,
latinName: item.latinName || '',
aliases: item.aliases || '',
id: wiki.id,
name: wiki.name,
latinName: wiki.latinName || '',
aliases: wiki.aliases || '',
aliasesList,
genus: item.genus || '',
distributionArea: item.distributionArea || '',
difficulty: item.difficulty || 0,
difficultyLabel: diffLabels[item.difficulty] || '未知',
isHot: item.isHot === 1,
lifeCycle: item.lifeCycle || '',
growthHabit: item.growthHabit || '',
reproductionMethod: item.reproductionMethod || '',
genus: wiki.genus || '',
distributionArea: wiki.distributionArea || '',
difficulty: wiki.difficulty || 0,
difficultyLabel: diffLabels[wiki.difficulty] || '未知',
isHot: wiki.isHot === true || wiki.isHot === 1,
lifeCycle: wiki.lifeCycle || '',
growthHabit: wiki.growthHabit || '',
reproductionMethod: wiki.reproductionMethod || '',
reproductionList,
lightIntensity: item.lightIntensity || '',
lightType: item.lightType || '',
optimalTempPeriod: item.optimalTempPeriod || '',
stem: item.stem || '',
foliageType: item.foliageType || '',
foliageColor: item.foliageColor || '',
foliageShape: item.foliageShape || '',
height: item.height || 0,
floweringPeriod: item.floweringPeriod || '',
floweringColor: item.floweringColor || '',
floweringShape: item.floweringShape || '',
flowerDiameter: item.flowerDiameter || 0,
fruit: item.fruit || '',
pestsDiseases: item.pestsDiseases || '',
lightIntensity: wiki.lightIntensity || '',
lightType: wiki.lightType || '',
optimalTempPeriod: wiki.optimalTempPeriod || '',
stem: wiki.stem || '',
foliageType: wiki.foliageType || '',
foliageColor: wiki.foliageColor || '',
foliageShape: wiki.foliageShape || '',
height: wiki.height || 0,
floweringPeriod: wiki.floweringPeriod || '',
floweringColor: wiki.floweringColor || '',
floweringShape: wiki.floweringShape || '',
flowerDiameter: wiki.floweringDiameter || 0,
fruit: wiki.fruit || '',
pestsDiseases: wiki.pestsDiseases || '',
commonPests,
classes: (item.classes || []).map(c => c.name),
classes: (item.classIds || []).map(id => ({ id })),
imgList: item.imgList || [],
isFavorited: (item.hasStar === 1 || item.hasStar === '1')
isFavorited: wiki.isStar === true || wiki.isStar === 1
};
this.setData({
@@ -109,7 +114,7 @@ Page({
const { id, isFavorited } = this.data.plant;
const type = isFavorited ? 2 : 1;
request.get('/wiki/star', { id, type }).then(() => {
request.get('/plant/wiki/star', { id, type }).then(() => {
const newStatus = !isFavorited;
this.setData({
'plant.isFavorited': newStatus
+7 -4
View File
@@ -24,10 +24,13 @@ Page({
this.classifyPlant(imagePath);
},
classifyPlant(filePath) {
async classifyPlant(filePath) {
this.setData({ isLoading: true, hasError: false });
request.uploadToUrl('/classify/plant', filePath, 'file').then(res => {
try {
// Directly upload file to classify endpoint without uploading to MinIO file service
const res = await request.uploadToUrl('/plant/classify/plant', filePath);
const results = res.result || [];
// Map results with percentage scores
@@ -46,10 +49,10 @@ Page({
topResult: mappedResults.length > 0 ? mappedResults[0] : null,
isLoading: false
});
}).catch(err => {
} catch (err) {
console.error('Classify failed', err);
this.setData({ isLoading: false, hasError: true });
});
}
},
// Retry identification
+1
View File
@@ -6,6 +6,7 @@
<view class="state-card">
<view class="loading-image-wrap">
<image src="{{imagePath}}" mode="aspectFill" class="loading-preview" bindtap="previewImage" />
<view class="scan-overlay"></view>
<view class="scan-line"></view>
</view>
<view class="loading-info">
+25 -8
View File
@@ -44,21 +44,38 @@
display: block;
}
/* Scan line animation */
/* Scan overlay breathing effect */
.scan-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(85, 139, 47, 0) 30%, rgba(85, 139, 47, 0.15) 100%);
animation: pulseBg 2s ease-in-out infinite alternate;
pointer-events: none;
}
@keyframes pulseBg {
0% { opacity: 0.2; }
100% { opacity: 0.65; }
}
/* Premium neon scan line animation */
.scan-line {
position: absolute;
left: 0;
right: 0;
height: 4rpx;
background: linear-gradient(90deg, transparent, #558B2F, transparent);
animation: scan 2s ease-in-out infinite;
box-shadow: 0 0 16rpx rgba(85, 139, 47, 0.5);
height: 8rpx;
background: linear-gradient(90deg, rgba(85, 139, 47, 0) 0%, rgba(139, 195, 74, 1) 50%, rgba(85, 139, 47, 0) 100%);
animation: scan 2.2s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite;
box-shadow: 0 0 20rpx rgba(139, 195, 74, 0.9), 0 0 40rpx rgba(85, 139, 47, 0.5);
}
@keyframes scan {
0% { top: 0; }
50% { top: 100%; }
100% { top: 0; }
0% { top: 0%; opacity: 0.8; }
50% { top: 98%; opacity: 1; }
100% { top: 0%; opacity: 0.8; }
}
.loading-info {
+5 -5
View File
@@ -50,7 +50,7 @@ Page({
// Fetch categories from API
fetchCategories() {
request.get('/wiki-class/list').then(res => {
request.get('/plant/wiki-class/list').then(res => {
const list = (res && res.list) || (Array.isArray(res) ? res : []);
this.setData({ categories: list });
}).catch(err => {
@@ -83,7 +83,7 @@ Page({
params.classId = [this.data.activeCategory];
}
return request.post('/wiki/page', params).then(res => {
return request.post('/plant/wiki/page', params).then(res => {
const data = res || {};
const list = data.list || [];
const total = data.total || 0;
@@ -96,8 +96,8 @@ Page({
aliases: item.aliases || '',
genus: item.genus || '',
difficulty: item.difficulty || 0,
isHot: item.isHot === 1,
isFavorited: item.hasStar === 1,
isHot: item.isHot === true || item.isHot === 1,
isFavorited: item.hasStar === 1 || item.hasStar === '1',
image: (item.imgList && item.imgList.length > 0) ? item.imgList[0].url : '',
classes: (item.classes || []).map(c => c.name),
// Pass the full item for detail navigation
@@ -140,7 +140,7 @@ Page({
try {
// Attempting consistent API pattern
await request.get('/wiki/star', { id, type });
await request.get('/plant/wiki/star', { id, type });
const key = `displayedList[${index}].isFavorited`;
this.setData({
+2 -2
View File
@@ -3,8 +3,8 @@
"packOptions": {
"ignore": [
{
"type": "glob",
"value": "assets/*.png"
"value": "assets/*.png",
"type": "glob"
}
],
"include": []
+1 -1
View File
@@ -1,5 +1,5 @@
{
"libVersion": "3.14.1",
"libVersion": "3.16.1",
"projectname": "plant-mp",
"condition": {},
"setting": {
+3 -3
View File
@@ -132,7 +132,7 @@ class WxRequest {
upload(filePath, name = 'file', formData = {}) {
// Prepare config
let config = {
url: this.baseUrl + '/oss/upload',
url: this.baseUrl + '/file/upload',
header: { ...this.header }, // Copy default headers
filePath: filePath,
name: name,
@@ -320,8 +320,8 @@ class WxRequest {
// Initialize with default instance
const request = new WxRequest({
//baseUrl: 'http://192.168.0.184:8889',
baseUrl: 'https://go.sundynix.cn/api',
baseUrl: 'http://192.168.100.2:8888/api',
//baseUrl: 'https://go.sundynix.cn/api',
header: {
'Content-Type': 'application/json'
}