diff --git a/app.js b/app.js index 42ebb66..9608f5e 100644 --- a/app.js +++ b/app.js @@ -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; diff --git a/pages/community/create/index.js b/pages/community/create/index.js index e1eefa9..adefeec 100644 --- a/pages/community/create/index.js +++ b/pages/community/create/index.js @@ -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' }); diff --git a/pages/community/index.js b/pages/community/index.js index f11744a..4e1eda3 100644 --- a/pages/community/index.js +++ b/pages/community/index.js @@ -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() }); diff --git a/pages/garden/add/index.js b/pages/garden/add/index.js index 7f446a7..9f61efb 100644 --- a/pages/garden/add/index.js +++ b/pages/garden/add/index.js @@ -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({ diff --git a/pages/garden/index.js b/pages/garden/index.js index 30e92a6..f1a2219 100644 --- a/pages/garden/index.js +++ b/pages/garden/index.js @@ -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); diff --git a/pages/plant-detail/edit/index.js b/pages/plant-detail/edit/index.js index 2aee1c9..c6606ff 100644 --- a/pages/plant-detail/edit/index.js +++ b/pages/plant-detail/edit/index.js @@ -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(); diff --git a/pages/plant-detail/edit/index.wxml b/pages/plant-detail/edit/index.wxml index 8380327..200cdf0 100644 --- a/pages/plant-detail/edit/index.wxml +++ b/pages/plant-detail/edit/index.wxml @@ -10,7 +10,7 @@ > - + 点击设置封面图 - - - 更换照片 - diff --git a/pages/plant-detail/growth-record/index.js b/pages/plant-detail/growth-record/index.js index c025df8..e67aba4 100644 --- a/pages/plant-detail/growth-record/index.js +++ b/pages/plant-detail/growth-record/index.js @@ -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); diff --git a/pages/plant-detail/index.js b/pages/plant-detail/index.js index afb7a48..f539878 100644 --- a/pages/plant-detail/index.js +++ b/pages/plant-detail/index.js @@ -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 }); } }); diff --git a/pages/profile/badges/badge-wall/index.js b/pages/profile/badges/badge-wall/index.js index 3966a94..585ea92 100644 --- a/pages/profile/badges/badge-wall/index.js +++ b/pages/profile/badges/badge-wall/index.js @@ -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 || []); diff --git a/pages/profile/badges/index.js b/pages/profile/badges/index.js index 88c273f..669cca7 100644 --- a/pages/profile/badges/index.js +++ b/pages/profile/badges/index.js @@ -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) { diff --git a/pages/profile/badges/level-detail/index.js b/pages/profile/badges/level-detail/index.js index 20437c3..f2bf963 100644 --- a/pages/profile/badges/level-detail/index.js +++ b/pages/profile/badges/level-detail/index.js @@ -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 }); diff --git a/pages/profile/exchange/index.js b/pages/profile/exchange/index.js index 5427a03..bd74bc7 100644 --- a/pages/profile/exchange/index.js +++ b/pages/profile/exchange/index.js @@ -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 diff --git a/pages/profile/exchange/orders/index.js b/pages/profile/exchange/orders/index.js index c8ace9b..d899491 100644 --- a/pages/profile/exchange/orders/index.js +++ b/pages/profile/exchange/orders/index.js @@ -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; diff --git a/pages/profile/favorites/index.js b/pages/profile/favorites/index.js index 208fa10..4d835d1 100644 --- a/pages/profile/favorites/index.js +++ b/pages/profile/favorites/index.js @@ -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: '提示', diff --git a/pages/profile/identify-history/index.js b/pages/profile/identify-history/index.js index 6532157..bfca187 100644 --- a/pages/profile/identify-history/index.js +++ b/pages/profile/identify-history/index.js @@ -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); diff --git a/pages/profile/index.js b/pages/profile/index.js index e0c2627..f621746 100644 --- a/pages/profile/index.js +++ b/pages/profile/index.js @@ -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); diff --git a/pages/profile/posts/index.js b/pages/profile/posts/index.js index b398fca..fe51bdd 100644 --- a/pages/profile/posts/index.js +++ b/pages/profile/posts/index.js @@ -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 diff --git a/pages/tasks/index.js b/pages/tasks/index.js index 049688f..7398053 100644 --- a/pages/tasks/index.js +++ b/pages/tasks/index.js @@ -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) { diff --git a/pages/wiki/chat/history/index.js b/pages/wiki/chat/history/index.js index 1073cb8..7f8c1a4 100644 --- a/pages/wiki/chat/history/index.js +++ b/pages/wiki/chat/history/index.js @@ -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); }); diff --git a/pages/wiki/chat/index.js b/pages/wiki/chat/index.js index d779032..2dcb6f2 100644 --- a/pages/wiki/chat/index.js +++ b/pages/wiki/chat/index.js @@ -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: ')) { diff --git a/pages/wiki/detail/index.js b/pages/wiki/detail/index.js index 2f516ab..47b7cc7 100644 --- a/pages/wiki/detail/index.js +++ b/pages/wiki/detail/index.js @@ -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 diff --git a/pages/wiki/identify/index.js b/pages/wiki/identify/index.js index c221ac9..f4bc4e0 100644 --- a/pages/wiki/identify/index.js +++ b/pages/wiki/identify/index.js @@ -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 diff --git a/pages/wiki/identify/index.wxml b/pages/wiki/identify/index.wxml index d12e746..22e55ff 100644 --- a/pages/wiki/identify/index.wxml +++ b/pages/wiki/identify/index.wxml @@ -6,6 +6,7 @@ + diff --git a/pages/wiki/identify/index.wxss b/pages/wiki/identify/index.wxss index 09fb58c..1258449 100644 --- a/pages/wiki/identify/index.wxss +++ b/pages/wiki/identify/index.wxss @@ -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 { diff --git a/pages/wiki/index.js b/pages/wiki/index.js index 782acbf..7dccf8d 100644 --- a/pages/wiki/index.js +++ b/pages/wiki/index.js @@ -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({ diff --git a/project.config.json b/project.config.json index c7de474..dcb6cc9 100644 --- a/project.config.json +++ b/project.config.json @@ -3,8 +3,8 @@ "packOptions": { "ignore": [ { - "type": "glob", - "value": "assets/*.png" + "value": "assets/*.png", + "type": "glob" } ], "include": [] diff --git a/project.private.config.json b/project.private.config.json index e1ffeca..8d4ed2f 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -1,5 +1,5 @@ { - "libVersion": "3.14.1", + "libVersion": "3.16.1", "projectname": "plant-mp", "condition": {}, "setting": { diff --git a/utils/request.js b/utils/request.js index c085f83..fe3e4d4 100644 --- a/utils/request.js +++ b/utils/request.js @@ -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' }