feat: 整体页面优化,删除无用svg

This commit is contained in:
Blizzard
2026-02-14 08:32:47 +08:00
parent daea00ca60
commit cbbe82ef63
59 changed files with 1265 additions and 342 deletions
+3 -1
View File
@@ -15,7 +15,9 @@
"pages/profile/identify-history/index",
"pages/profile/badges/index",
"pages/profile/badges/level-detail/index",
"pages/profile/badges/badge-wall/index"
"pages/profile/badges/badge-wall/index",
"pages/profile/favorites/index",
"pages/profile/posts/index"
],
"window": {
"backgroundTextStyle": "light",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 863 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

+60 -13
View File
@@ -13,7 +13,9 @@ Page({
current: 1,
pageSize: 10,
hasMore: true,
userInfo: null
userInfo: null,
scrollTop: 0,
isRefreshing: false
},
onLoad() {
@@ -33,9 +35,16 @@ Page({
}
},
// Called by create post page
// Called by create post page or Pull Down
onRefresh() {
this.fetchPosts(true);
this.setData({ isRefreshing: true });
this.fetchPosts(true).then(() => {
this.setData({ isRefreshing: false });
});
},
onTabItemTap() {
this.setData({ scrollTop: Math.random() * 0.01 });
},
onPullDownRefresh() {
@@ -60,7 +69,7 @@ Page({
try {
// Correct API Endpoint and Params
const res = await request.post('/post/page', { current, pageSize });
const res = await request.post('/post/page', { current, pageSize, hasReviewed: 1 });
// Handle response structure: { code: 200, data: { list: [], ... } }
// OR if request.js unwraps it: { list: [], ... }
@@ -79,14 +88,16 @@ Page({
content: item.content,
images: (item.imgList || []).map(img => img.url),
time: item.createdAtStr || '刚刚',
likes: (item.likeList || []).map(l => l.liker ? (l.liker.nickName || l.liker.name) : '花友'),
likes: item.hasLiked === 1 ? ['我'] : [],
comments: (item.commentList || []).map(c => ({
id: c.id,
user: c.commentator ? (c.commentator.nickName || c.commentator.name) : '花友',
content: c.content
})),
likedByMe: item.hasLiked === 1,
isFavorited: item.hasStar === 1,
likeCount: item.likeCount || 0,
stars: (item.starList || []).map(s => s.starer ? (s.starer.nickName || s.starer.name) : '花友'),
commentCount: item.commentCount || 0,
isExpanded: false
};
@@ -125,12 +136,7 @@ Page({
// Preview image in full screen
previewImage(e) {
const { url, urls } = e.currentTarget.dataset;
const resolvedUrls = urls.map(img => {
if (img.indexOf('http') === 0 || img.indexOf('wxfile') === 0) {
return img;
}
return `/assets/${img}`;
});
const resolvedUrls = urls.map(img => img);
wx.previewImage({
current: url,
@@ -169,10 +175,15 @@ Page({
try {
await request.get('/post/like', { id: postId, type });
// Optimistic Update: Only toggle button state. Do NOT modify likes list text.
// Optimistic Update
const updatedPosts = this.data.posts.map(p => {
if (p.id === postId) {
return { ...p, likedByMe: !p.likedByMe };
const liked = !p.likedByMe;
return {
...p,
likedByMe: liked,
likes: liked ? ['我'] : []
};
}
return p;
});
@@ -192,6 +203,42 @@ Page({
}
},
// Collect post
async collectPost(e) {
const postId = e.currentTarget.dataset.id;
const post = this.data.posts.find(p => p.id === postId);
if (!post) return;
const type = post.isFavorited ? 2 : 1; // 1: Collect, 2: Cancel
try {
await request.get('/post/star', { id: postId, type });
// Optimistic Update
const updatedPosts = this.data.posts.map(p => {
if (p.id === postId) {
return { ...p, isFavorited: !p.isFavorited };
}
return p;
});
this.setData({
posts: updatedPosts,
displayedPosts: updatedPosts,
activePostId: null
});
// Refresh list to sync detailed data (like IDs if needed)
this.fetchPosts(true);
wx.showToast({ title: type === 1 ? '已收藏' : '已取消', icon: 'success' });
} catch (err) {
console.error('Collect failed', err);
wx.showToast({ title: '操作失败', icon: 'none' });
}
},
// Show comment input bar
showCommentInput(e) {
const postId = e.currentTarget.dataset.id;
+1 -1
View File
@@ -1,5 +1,5 @@
{
"navigationBarTitleText": "友动态",
"navigationBarTitleText": "友动态",
"usingComponents": {
"t-avatar": "tdesign-miniprogram/avatar/avatar",
"t-image": "tdesign-miniprogram/image/image",
+28 -9
View File
@@ -15,6 +15,11 @@
enhanced="{{true}}"
show-scrollbar="{{false}}"
bindtap="hideActionPopup"
scroll-top="{{scrollTop}}"
bindscrolltolower="onReachBottom"
refresher-enabled="{{true}}"
bindrefresherrefresh="onRefresh"
refresher-triggered="{{isRefreshing}}"
>
<block wx:if="{{displayedPosts.length > 0}}">
<view wx:for="{{displayedPosts}}" wx:key="id" class="moment-post">
@@ -49,10 +54,15 @@
<!-- WeChat Style Popup -->
<view class="action-popup {{activePostId === item.id ? 'show' : ''}}" catchtap="stopPropagation">
<view class="popup-btn like-btn" catchtap="likePost" data-id="{{item.id}}">
<t-icon name="heart" size="32rpx" color="#fff" />
<t-icon name="{{item.likedByMe ? 'heart-filled' : 'heart'}}" size="32rpx" color="{{item.likedByMe ? '#FA5151' : '#fff'}}" />
<text>{{item.likedByMe ? '取消' : '赞'}}</text>
</view>
<view class="popup-divider"></view>
<view class="popup-btn fav-btn" catchtap="collectPost" data-id="{{item.id}}">
<t-icon name="{{item.isFavorited ? 'star-filled' : 'star'}}" size="32rpx" color="{{item.isFavorited ? '#FFC107' : '#fff'}}" />
<text>{{item.isFavorited ? '取消' : '收藏'}}</text>
</view>
<view class="popup-divider"></view>
<view class="popup-btn comment-btn" catchtap="showCommentInput" data-id="{{item.id}}">
<t-icon name="chat" size="32rpx" color="#fff" />
<text>评论</text>
@@ -68,17 +78,26 @@
</view>
<!-- Likes & Comments Box -->
<view wx:if="{{item.likes.length > 0 || item.comments.length > 0}}" class="likes-comments-box">
<!-- Likes -->
<view wx:if="{{item.likes.length > 0}}" class="likes-section">
<t-icon name="heart-filled" size="28rpx" color="#FA5151" />
<view class="likes-list">
<text wx:for="{{item.likes}}" wx:for-item="liker" wx:key="*this" class="like-name">{{liker}}{{index < item.likes.length - 1 ? '' : ''}}</text>
</view>
<view wx:if="{{item.likes.length > 0 || item.isFavorited || item.comments.length > 0}}" class="likes-comments-box">
<!-- Likes & Stars Combined -->
<view wx:if="{{item.likes.length > 0 || item.isFavorited}}" class="likes-section" style="flex-wrap: wrap;">
<block wx:if="{{item.likes.length > 0}}">
<t-icon name="{{item.likedByMe ? 'heart-filled' : 'heart'}}" size="28rpx" color="{{item.likedByMe ? '#FA5151' : '#576b95'}}" />
<view class="likes-list" style="flex: unset; margin-right: 24rpx;">
<text wx:for="{{item.likes}}" wx:for-item="liker" wx:key="*this" class="like-name">{{liker}}{{index < item.likes.length - 1 ? '' : ''}}</text>
</view>
</block>
<block wx:if="{{item.isFavorited}}">
<t-icon name="star-filled" size="28rpx" color="#FFC107" />
<view class="likes-list" style="flex: unset;">
<text class="like-name">我</text>
</view>
</block>
</view>
<!-- Divider -->
<view wx:if="{{item.likes.length > 0 && item.comments.length > 0}}" class="divider"></view>
<view wx:if="{{(item.likes.length > 0 || item.isFavorited) && item.comments.length > 0}}" class="divider"></view>
<!-- Comments -->
<view wx:if="{{item.comments.length > 0}}" class="comments-section">
+3 -2
View File
@@ -1,5 +1,5 @@
// pages/garden/add/index.js
import { CARE_TASK_ICONS } from '../../../utils/mockData';
import { CARE_TASK_ICONS } from '../../../utils/constant';
import request from '../../../utils/request';
import { requestSubscription, checkSubscriptionSettings } from '../../../utils/subscribe';
@@ -253,7 +253,8 @@ Page({
const carePlans = newCareTasks.map(task => ({
name: task.taskName || '未命名事项',
period: parseInt(task.frequencyValue) || 1,
icon: JSON.stringify(task.taskIcon || {}) // Serialize icon details
icon: JSON.stringify(task.taskIcon || {}), // Serialize icon details
targetAction: (task.taskIcon && task.taskIcon.targetAction) ? task.taskIcon.targetAction : ''
}));
// Construct Payload
+24 -3
View File
@@ -12,7 +12,8 @@ Page({
pageSize: 6,
total: 0,
isLastPage: false,
isLoading: false
isLoading: false,
scrollTop: 0
},
onLoad(options) {
@@ -66,9 +67,23 @@ Page({
const mappedList = list.map(item => {
let imageUrl = '';
if (item.imgList && item.imgList.length > 0) {
imageUrl = item.imgList[0].url;
imageUrl = item.imgList[0].url || '';
}
return { ...item, images: [imageUrl] };
// Calculate days
let days = 1;
if (item.plantTime) {
try {
const start = new Date(item.plantTime).getTime();
const now = Date.now();
const diff = now - start;
if (diff > 0) {
days = Math.ceil(diff / (1000 * 60 * 60 * 24));
}
} catch (e) { }
}
return { ...item, images: [imageUrl], daysPlanted: days };
});
this.setData({
@@ -139,5 +154,11 @@ Page({
return {
title: '我的植物花园 - Sundynix Plant'
};
},
onTabItemTap() {
// Reset scroll when switching to this tab
// Use random small value to force data change detection
this.setData({ scrollTop: Math.random() * 0.01 });
}
})
+1 -1
View File
@@ -45,7 +45,7 @@
</view>
</view>
<scroll-view wx:else scroll-y class="garden-list-container" enhanced show-scrollbar="{{false}}" bindscrolltolower="onScrollLower">
<scroll-view wx:else scroll-y class="garden-list-container" enhanced show-scrollbar="{{false}}" bindscrolltolower="onScrollLower" scroll-top="{{scrollTop}}">
<view class="plant-grid">
<view wx:for="{{plants}}" wx:key="id" class="plant-card" bindtap="navigateToDetail" data-id="{{item.id}}">
<view class="plant-image-container">
+6 -16
View File
@@ -1,6 +1,6 @@
// pages/plant-detail/edit/index.js
import request from '../../../utils/request';
import { CARE_TASK_ICONS } from '../../../utils/mockData';
import { CARE_TASK_ICONS } from '../../../utils/constant';
Page({
data: {
@@ -296,7 +296,8 @@ Page({
id: String(task.id),
name: task.name,
period: parseInt(task.period) || 1,
icon: JSON.stringify(task.taskIcon || {})
icon: JSON.stringify(task.taskIcon || {}),
targetAction: (task.taskIcon && task.taskIcon.targetAction) ? task.taskIcon.targetAction : ''
}));
// Build payload for /plant/update (UpdateMyPlant struct)
@@ -317,7 +318,8 @@ Page({
plantId: plantId,
name: task.name,
period: parseInt(task.period) || 1,
icon: JSON.stringify(task.taskIcon || {})
icon: JSON.stringify(task.taskIcon || {}),
targetAction: (task.taskIcon && task.taskIcon.targetAction) ? task.taskIcon.targetAction : ''
}))
} : null;
@@ -358,17 +360,5 @@ Page({
});
},
handleDeletePlant() {
wx.showModal({
title: '确认删除',
content: '确定要删除这个植物吗?删除后无法恢复。',
confirmColor: '#EF5350',
success: (res) => {
if (res.confirm) {
wx.showToast({ title: '已删除', icon: 'success' });
setTimeout(() => { wx.switchTab({ url: '/pages/garden/index' }); }, 1000);
}
}
});
}
})
+1 -6
View File
@@ -157,12 +157,7 @@
</view>
<!-- Delete Button for Edit Page -->
<view class="delete-section" style="margin-top: 16rpx; padding-bottom: 40rpx;">
<view class="delete-page-btn" bindtap="handleDeletePlant">
<t-icon name="delete" size="32rpx" />
<text>删除植物档案</text>
</view>
</view>
<view style="height: 180rpx;"></view>
</scroll-view>
+27
View File
@@ -244,4 +244,31 @@ Page({
});
}
},
handleDeletePlant() {
if (!this.data.currentPlant || !this.data.currentPlant.id) return;
wx.showModal({
title: '确认删除',
content: '确定要删除这个植物吗?删除后无法恢复。',
confirmColor: '#EF5350',
success: (res) => {
if (res.confirm) {
wx.showLoading({ title: '删除中...' });
// Attempting to use consistent API pattern: POST /plant/deletePlant with ids array
request.post('/plant/deletePlant', { ids: [this.data.currentPlant.id] }).then(() => {
wx.hideLoading();
wx.showToast({ title: '已删除', icon: 'success' });
setTimeout(() => {
wx.switchTab({ url: '/pages/garden/index' });
}, 1000);
}).catch(err => {
wx.hideLoading();
console.error('Delete plant failed', err);
wx.showToast({ title: '删除失败', icon: 'none' });
});
}
}
});
},
})
+5
View File
@@ -92,6 +92,11 @@
</view>
</view>
</view>
<view class="delete-plant-box" bindtap="handleDeletePlant">
<t-icon name="delete" size="32rpx" color="#EF5350" />
<text class="delete-text">删除植物</text>
</view>
</view>
</scroll-view>
+18
View File
@@ -811,3 +811,21 @@ page {
font-weight: 500;
color: #90A4AE;
}
.delete-plant-box {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
padding: 24rpx;
margin-top: 40rpx;
background: #FFF;
border-radius: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(239, 83, 80, 0.1);
}
.delete-text {
font-size: 28rpx;
color: #EF5350;
font-weight: 500;
}
+34
View File
@@ -0,0 +1,34 @@
import request from '../../../utils/request';
Page({
data: {
favTab: 'all',
favorites: [],
filteredFavorites: []
},
onLoad() {
this.loadFavorites();
},
loadFavorites() {
// TODO: Call API
this.filterFavorites();
},
onFavTabChange(e) {
const val = e.currentTarget.dataset.value;
this.setData({ favTab: val }, () => {
this.filterFavorites();
});
},
filterFavorites() {
const { favorites, favTab } = this.data;
const filtered = favorites.filter(item => {
if (favTab === 'all') return true;
return item.type === favTab;
});
this.setData({ filteredFavorites: filtered });
}
});
+7
View File
@@ -0,0 +1,7 @@
{
"navigationBarTitleText": "我的收藏",
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon",
"t-image": "tdesign-miniprogram/image/image"
}
}
+27
View File
@@ -0,0 +1,27 @@
<view class="favorites-page">
<view class="category-filter">
<view class="filter-chip {{favTab === 'all' ? 'active' : ''}}" bindtap="onFavTabChange" data-value="all">全部</view>
<view class="filter-chip {{favTab === 'plant' ? 'active' : ''}}" bindtap="onFavTabChange" data-value="plant">植物</view>
<view class="filter-chip {{favTab === 'article' ? 'active' : ''}}" bindtap="onFavTabChange" data-value="article">文章</view>
</view>
<scroll-view scroll-y class="fav-scroll" enhanced show-scrollbar="{{false}}">
<view wx:if="{{filteredFavorites.length > 0}}" class="fav-grid">
<view wx:for="{{filteredFavorites}}" wx:key="id" class="fav-card">
<t-image src="{{item.image}}" mode="aspectFill" width="100%" height="240rpx" class="fav-img" />
<view class="fav-info">
<text class="fav-name">{{item.name}}</text>
<view class="fav-meta-row">
<t-icon name="{{item.type === 'plant' ? 'heart' : 'book'}}" size="28rpx" color="#90A4AE" />
<text class="fav-type">{{item.meta}}</text>
</view>
</view>
</view>
</view>
<view wx:else class="empty-state">
<t-icon name="star" size="80rpx" color="#E0E0E0" style="margin-bottom: 24rpx;" />
<text class="empty-text">暂无收藏内容</text>
</view>
<view style="height: 60rpx;"></view>
</scroll-view>
</view>
+98
View File
@@ -0,0 +1,98 @@
.favorites-page {
background: #F4F6F0;
height: 100vh;
padding: 24rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
.category-filter {
display: flex;
gap: 16rpx;
margin-bottom: 24rpx;
}
.filter-chip {
padding: 12rpx 32rpx;
background: #fff;
border: 2rpx solid transparent;
border-radius: 40rpx;
font-size: 26rpx;
color: #6B7280;
transition: all 0.2s;
font-weight: 500;
}
.filter-chip.active {
background: #333;
color: #fff;
font-weight: 600;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
}
.fav-scroll {
flex: 1;
height: 0;
width: 100%;
}
.fav-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.fav-card {
background: white;
border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
display: flex;
flex-direction: column;
}
.fav-img {
background: #f0f0f0;
}
.fav-info {
padding: 16rpx 20rpx;
}
.fav-name {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #1F2937;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fav-meta-row {
display: flex;
align-items: center;
gap: 8rpx;
}
.fav-type {
font-size: 22rpx;
color: #9CA3AF;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #9CA3AF;
margin-top: 16rpx;
}
+54 -149
View File
@@ -29,7 +29,9 @@ Page({
myDrafts: [],
// App version
appVersion: '1.0.0'
// App version
appVersion: '1.0.0',
scrollTop: 0
},
onLoad() {
@@ -44,6 +46,13 @@ Page({
this.loadUserInfo();
},
onTabItemTap() {
this.setData({
view: 'profile',
scrollTop: Math.random() * 0.01
});
},
// ======== User Info ========
loadUserInfo() {
request.get('/profile/detail').then(res => {
@@ -57,6 +66,7 @@ Page({
this.setData({
userName: res.nickname || '植物爱好者',
userAvatar: avatarUrl,
currentAvatarId: res.avatarId || (res.avatar ? res.avatar.id : ''),
// Stats
plantCount: res.plantCount || 0,
@@ -88,127 +98,16 @@ Page({
// ======== Navigation ========
setView(e) {
const view = e.currentTarget.dataset.view;
this.setData({ view });
if (view === 'favorites') {
this.loadFavorites();
} else if (view === 'posts') {
this.loadMyPosts();
this.loadDrafts();
}
},
goBack() {
this.setData({ view: 'profile' });
},
// ======== Favorites ========
loadFavorites() {
// TODO: Call favorites API when available
// request.get('/user/favorites').then(...)
this.filterFavorites();
goToFavorites() {
wx.navigateTo({ url: '/pages/profile/favorites/index' });
},
onFavTabChange(e) {
this.setData({ favTab: e.detail.value }, () => {
this.filterFavorites();
});
},
filterFavorites() {
const { favorites, favTab } = this.data;
const filtered = favorites.filter(item => {
if (favTab === 'all') return true;
return item.type === favTab;
});
this.setData({ filteredFavorites: filtered });
},
// ======== Posts ========
loadMyPosts() {
request.post('/post/page', { current: 1, pageSize: 50, onlyMine: true }).then(res => {
const records = res.records || res.list || [];
const posts = records.map(item => {
const publisher = item.publisher || {};
const imgList = item.imgList || [];
return {
id: item.id,
content: item.content || '',
time: this._formatTime(item.createdAt || item.createTime),
images: imgList.map(img => img.url),
likes: item.likeList || [],
comments: item.commentList || []
};
});
this.setData({ myPublishedPosts: posts });
}).catch(() => {
this.setData({ myPublishedPosts: [] });
});
},
loadDrafts() {
try {
const draft = wx.getStorageSync('post_draft');
if (draft && (draft.content || (draft.images && draft.images.length > 0))) {
this.setData({
myDrafts: [{
id: 'draft_1',
content: draft.content || '',
images: draft.images || [],
selectedTopics: draft.selectedTopics || []
}]
});
} else {
this.setData({ myDrafts: [] });
}
} catch (e) {
this.setData({ myDrafts: [] });
}
},
onPostsTabChange(e) {
this.setData({ postsTab: e.detail.value });
},
deletePost(e) {
const postId = e.currentTarget.dataset.id;
wx.showModal({
title: '删除动态',
content: '确定要删除这条动态吗?',
confirmColor: '#EF5350',
success: (res) => {
if (!res.confirm) return;
wx.showLoading({ title: '删除中...' });
request.get('/post/delete', { id: postId }).then(() => {
wx.hideLoading();
this.loadMyPosts();
wx.showToast({ title: '已删除', icon: 'success' });
}).catch(() => {
wx.hideLoading();
wx.showToast({ title: '删除失败', icon: 'none' });
});
}
});
},
editDraft() {
wx.navigateTo({ url: '/pages/community/create/index' });
},
deleteDraft() {
wx.showModal({
title: '删除草稿',
content: '确定要删除这份草稿吗?',
confirmColor: '#EF5350',
success: (res) => {
if (!res.confirm) return;
try { wx.removeStorageSync('post_draft'); } catch (e) { }
this.setData({ myDrafts: [] });
wx.showToast({ title: '已删除', icon: 'success' });
}
});
goToPosts() {
wx.navigateTo({ url: '/pages/profile/posts/index' });
},
// ======== Menu Actions ========
@@ -284,10 +183,12 @@ Page({
},
async saveProfile() {
const { tempAvatar, tempNickname, userName, userAvatar } = this.data;
const { tempAvatar, tempNickname, userName, userAvatar, currentAvatarId } = this.data;
// Check if anything changed
const isNameChanged = tempNickname && tempNickname !== userName;
// Determine if there are changes
// tempNickname might be undefined if user didn't edit nickname input
const finalNickname = tempNickname !== undefined ? tempNickname : userName;
const isNameChanged = finalNickname !== userName;
const isAvatarChanged = tempAvatar && tempAvatar !== userAvatar;
if (!isNameChanged && !isAvatarChanged) {
@@ -295,61 +196,65 @@ Page({
return;
}
if (!finalNickname.trim()) {
wx.showToast({ title: '昵称不能为空', icon: 'none' });
return;
}
wx.showLoading({ title: '保存中...', mask: true });
try {
const updatePayload = {};
let finalAvatarId = currentAvatarId;
// 1. Upload avatar if changed
// Upload new avatar if changed
if (isAvatarChanged) {
const uploadRes = await request.upload(tempAvatar);
// Correctly extract ID from file object based on known API response
let fileId = '';
if (uploadRes && uploadRes.file && uploadRes.file.id) {
fileId = uploadRes.file.id;
finalAvatarId = uploadRes.file.id;
} else if (uploadRes && uploadRes.id) {
fileId = uploadRes.id;
}
if (fileId) {
updatePayload.avatarId = fileId;
finalAvatarId = uploadRes.id;
} else {
throw new Error('Avatar upload failed, no ID returned');
}
}
// 2. Set nickname if changed
if (isNameChanged) {
updatePayload.nickname = tempNickname;
}
// Construct Full Payload
const updatePayload = {
nickname: finalNickname,
avatarId: finalAvatarId
};
// 3. Call update API
if (Object.keys(updatePayload).length > 0) {
await request.post('/profile/update', updatePayload);
}
// Call API
await request.post('/profile/update', updatePayload);
wx.hideLoading();
// 4. Update local state
// Update UI State
this.setData({
userName: tempNickname || userName,
userAvatar: tempAvatar || userAvatar, // Use tempAvatar for immediate display
userName: finalNickname,
userAvatar: tempAvatar || userAvatar,
currentAvatarId: finalAvatarId,
showProfileEditor: false
});
wx.showToast({ title: '资料已更新', icon: 'success' });
// 5. Update globalData
// Update Global Data
const userInfo = app.globalData.userInfo || {};
if (updatePayload.nickname) userInfo.name = updatePayload.nickname;
if (updatePayload.avatarId) userInfo.avatarId = updatePayload.avatarId;
// Also update URL if we have it locally?
// Better to re-fetch profile to get canonical URL, but optimistic update is fine.
userInfo.avatar = tempAvatar;
userInfo.nickname = finalNickname;
userInfo.name = finalNickname;
// Update avatar structure in global store too
if (isAvatarChanged) {
userInfo.avatar = { ...(userInfo.avatar || {}), url: tempAvatar, id: finalAvatarId };
}
userInfo.avatarId = finalAvatarId;
app.globalData.userInfo = userInfo;
wx.setStorageSync('userInfo', userInfo);
} catch (err) {
wx.hideLoading();
console.error('Save profile failed', err);
wx.showToast({ title: '保存失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
+4 -67
View File
@@ -1,70 +1,7 @@
<view class="profile-page">
<!-- ======== FAVORITES VIEW ======== -->
<view wx:if="{{view === 'favorites'}}" class="sub-view info-view-anim">
<view class="sub-nav" bindtap="goBack">
<t-icon name="chevron-left" size="40rpx" />
<text class="sub-nav-title">我的收藏</text>
</view>
<view class="category-filter">
<view class="filter-chip {{favTab === 'all' ? 'active' : ''}}" bindtap="onFavTabChange" data-value="all">全部</view>
<view class="filter-chip {{favTab === 'plant' ? 'active' : ''}}" bindtap="onFavTabChange" data-value="plant">植物</view>
<view class="filter-chip {{favTab === 'article' ? 'active' : ''}}" bindtap="onFavTabChange" data-value="article">文章</view>
</view>
<scroll-view scroll-y class="sub-scroll">
<view wx:if="{{filteredFavorites.length > 0}}" class="fav-grid">
<view wx:for="{{filteredFavorites}}" wx:key="id" class="fav-card">
<t-image src="{{item.image}}" mode="aspectFill" width="100%" height="240rpx" class="fav-img" />
<view class="fav-info">
<text class="fav-name">{{item.name}}</text>
<view class="fav-meta-row">
<t-icon name="{{item.type === 'plant' ? 'heart' : 'book'}}" size="28rpx" color="#90A4AE" />
<text class="fav-type">{{item.meta}}</text>
</view>
</view>
</view>
</view>
<view wx:else class="empty-state">
<text class="empty-text">暂无收藏内容</text>
</view>
</scroll-view>
</view>
<!-- ======== POSTS VIEW ======== -->
<view wx:elif="{{view === 'posts'}}" class="sub-view info-view-anim">
<view class="sub-nav" bindtap="goBack">
<t-icon name="chevron-left" size="40rpx" />
<text class="sub-nav-title">我的发布</text>
</view>
<scroll-view scroll-y class="sub-scroll">
<view wx:for="{{myPublishedPosts}}" wx:key="id" class="my-post-card">
<view class="my-post-time">{{item.time}}</view>
<view class="my-post-content-wrap">
<text class="post-text">{{item.content}}</text>
<view wx:if="{{item.images.length > 0}}" class="my-post-images">
<t-image wx:for="{{item.images}}" wx:for-item="img" wx:key="*this"
src="{{img}}" mode="aspectFill" width="140rpx" height="140rpx"
shape="round" />
</view>
<view class="my-post-footer">
<view class="footer-item"><t-icon name="heart" size="28rpx" /> <text>{{item.likes.length}}</text></view>
<view class="footer-item"><t-icon name="chat" size="28rpx" /> <text>{{item.comments.length}}</text></view>
<view class="footer-item" catchtap="deletePost" data-id="{{item.id}}" style="margin-left:auto; color: #EF5350;">
<t-icon name="delete" size="28rpx" />
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- ======== ABOUT VIEW ======== -->
<view wx:elif="{{view === 'about'}}" class="sub-view info-view-anim">
<view wx:if="{{view === 'about'}}" class="sub-view info-view-anim">
<view class="sub-nav" bindtap="goBack">
<t-icon name="chevron-left" size="40rpx" />
<text class="sub-nav-title">关于我们</text>
@@ -134,12 +71,12 @@
</view>
</view>
<scroll-view scroll-y class="profile-content" enhanced show-scrollbar="{{false}}">
<scroll-view scroll-y class="profile-content" enhanced show-scrollbar="{{false}}" scroll-top="{{scrollTop}}">
<!-- Menu -->
<view class="profile-menu">
<view class="menu-group-title">常用功能</view>
<view class="menu-item" bindtap="setView" data-view="favorites">
<view class="menu-item" bindtap="goToFavorites">
<view class="menu-left">
<view class="menu-icon-bg" style="background: #FFF3E0">
<t-icon name="star" size="36rpx" color="#FF9800" />
@@ -149,7 +86,7 @@
<t-icon name="chevron-right" size="36rpx" color="#ccc" />
</view>
<view class="menu-item" bindtap="setView" data-view="posts">
<view class="menu-item" bindtap="goToPosts">
<view class="menu-left">
<view class="menu-icon-bg" style="background: #E3F2FD">
<t-icon name="file-copy" size="36rpx" color="#2196F3" />
+123
View File
@@ -0,0 +1,123 @@
import request from '../../../utils/request';
Page({
data: {
myPublishedPosts: [],
current: 1,
pageSize: 10,
hasMore: true,
isLoading: false,
isRefreshing: false
},
onLoad() {
this.loadMyPosts(true);
},
onPullDownRefresh() {
if (this.data.isLoading) return;
this.setData({ isRefreshing: true });
this.loadMyPosts(true).then(() => {
this.setData({ isRefreshing: false });
});
},
onReachBottom() {
if (this.data.hasMore && !this.data.isLoading) {
this.loadMyPosts(false);
}
},
async loadMyPosts(reset = false) {
if (this.data.isLoading) return;
this.setData({ isLoading: true });
const current = reset ? 1 : this.data.current;
const { pageSize } = this.data;
try {
const res = await request.post('/post/myPost', {
current,
pageSize
});
const data = res.data || res || {};
const records = data.records || data.list || [];
const posts = records.map(item => {
const imgList = item.imgList || [];
return {
id: item.id,
content: item.content || '',
time: this._formatTime(item.createdAt || item.createTime),
images: imgList.map(img => img.url),
likes: item.likeList || [],
comments: item.commentList || [],
hasReviewed: item.hasReviewed
};
});
if (reset) {
this.setData({
myPublishedPosts: posts,
current: current + 1,
hasMore: posts.length >= pageSize,
isLoading: false
});
} else {
this.setData({
myPublishedPosts: [...this.data.myPublishedPosts, ...posts],
current: current + 1,
hasMore: posts.length >= pageSize,
isLoading: false
});
}
} catch (err) {
console.error('Load my posts failed', err);
this.setData({ isLoading: false });
wx.showToast({ title: '加载失败', icon: 'none' });
}
},
_formatTime(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr);
const now = new Date();
const diffMs = now - d;
const diffMin = Math.floor(diffMs / 60000);
if (diffMin < 1) return '刚刚';
if (diffMin < 60) return diffMin + '分钟前';
const diffHour = Math.floor(diffMin / 60);
if (diffHour < 24) return diffHour + '小时前';
const diffDay = Math.floor(diffHour / 24);
if (diffDay < 7) return diffDay + '天前';
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const day = d.getDate().toString().padStart(2, '0');
return `${month}-${day}`;
},
deletePost(e) {
const postId = e.currentTarget.dataset.id;
wx.showModal({
title: '删除动态',
content: '确定要删除这条动态吗?',
confirmColor: '#EF5350',
success: (res) => {
if (!res.confirm) return;
wx.showLoading({ title: '删除中...' });
// Use new API: POST /post/delete with ids array
request.post('/post/delete', { ids: [postId] }).then(() => {
wx.hideLoading();
wx.showToast({ title: '已删除', icon: 'success' });
// Remove from list locally
const newList = this.data.myPublishedPosts.filter(p => p.id !== postId);
this.setData({ myPublishedPosts: newList });
}).catch(() => {
wx.hideLoading();
wx.showToast({ title: '删除失败', icon: 'none' });
});
}
});
}
});
+8
View File
@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "我的发布",
"disableScroll": true,
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon",
"t-image": "tdesign-miniprogram/image/image"
}
}
+42
View File
@@ -0,0 +1,42 @@
<view class="posts-page">
<scroll-view
scroll-y
class="sub-scroll"
enhanced
show-scrollbar="{{false}}"
bindscrolltolower="onReachBottom"
refresher-enabled="{{true}}"
bindrefresherrefresh="onPullDownRefresh"
refresher-triggered="{{isRefreshing}}"
>
<view wx:if="{{myPublishedPosts.length > 0}}" class="posts-list">
<view wx:for="{{myPublishedPosts}}" wx:key="id" class="my-post-card">
<view class="my-post-time">{{item.time}}</view>
<view class="my-post-content-wrap">
<view class="post-header" wx:if="{{item.hasReviewed !== undefined}}">
<view class="status-tag pending" wx:if="{{item.hasReviewed === 0}}">待审核</view>
<view class="status-tag success" wx:if="{{item.hasReviewed === 1}}">已发布</view>
</view>
<text class="post-text">{{item.content}}</text>
<view wx:if="{{item.images.length > 0}}" class="my-post-images">
<t-image wx:for="{{item.images}}" wx:for-item="img" wx:key="*this"
src="{{img}}" mode="aspectFill" width="140rpx" height="140rpx"
shape="round" />
</view>
<view class="my-post-footer">
<view class="footer-item"><t-icon name="heart" size="28rpx" /> <text>{{item.likes.length}}</text></view>
<view class="footer-item"><t-icon name="chat" size="28rpx" /> <text>{{item.comments.length}}</text></view>
<view class="footer-item" catchtap="deletePost" data-id="{{item.id}}" style="margin-left:auto; color: #EF5350;">
<t-icon name="delete" size="28rpx" />
</view>
</view>
</view>
</view>
</view>
<view wx:else class="empty-state">
<t-icon name="file-copy" size="80rpx" color="#E0E0E0" style="margin-bottom: 24rpx;" />
<text class="empty-text">暂无发布内容</text>
</view>
<view style="height: 60rpx;"></view>
</scroll-view>
</view>
+170
View File
@@ -0,0 +1,170 @@
.posts-page {
background: #F4F6F0;
height: 100vh;
padding: 32rpx 32rpx 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
.sub-scroll {
flex: 1;
height: 0; /* Crucial for scrolling in flex layout */
width: 100%;
}
.posts-list {
position: relative;
padding-bottom: 60rpx;
}
/* Timeline Line */
.posts-list::before {
content: '';
position: absolute;
top: 40rpx;
bottom: 40rpx;
left: 112rpx; /* Adjust based on time width */
width: 2rpx;
background: #E0E0E0;
z-index: 0;
}
.my-post-card {
display: flex;
gap: 32rpx;
margin-bottom: 48rpx;
position: relative;
z-index: 1;
}
/* Time Column */
.my-post-time {
width: 100rpx;
flex-shrink: 0;
font-size: 26rpx;
color: #90A4AE;
font-weight: 700;
text-align: right;
padding-top: 28rpx;
position: relative;
}
/* Timeline Dot */
.my-post-time::after {
content: '';
position: absolute;
top: 40rpx;
right: -24rpx; /* Center on line */
width: 16rpx;
height: 16rpx;
background: #fff;
border: 4rpx solid #CFD8DC;
border-radius: 50%;
z-index: 2;
box-shadow: 0 0 0 4rpx #F4F6F0; /* Outline mask */
}
/* Highlight dot for today/recent? Optional */
/* Content Card */
.my-post-content-wrap {
flex: 1;
background: #fff;
border-radius: 28rpx;
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.03);
padding: 32rpx;
position: relative;
overflow: hidden;
transition: all 0.2s;
}
.my-post-content-wrap:active {
transform: scale(0.98);
}
/* Post Text */
.post-text {
font-size: 30rpx;
color: #374151;
line-height: 1.6;
margin-bottom: 24rpx;
display: block;
min-height: 48rpx;
margin-top: 8rpx;
}
/* Status Tags - Corner Style */
.status-tag {
position: absolute;
top: 0;
right: 0;
padding: 8rpx 20rpx;
font-size: 22rpx;
font-weight: 700;
border-bottom-left-radius: 24rpx;
z-index: 10;
box-shadow: -4rpx 4rpx 12rpx rgba(0,0,0,0.05);
}
.status-tag.pending {
background: #FFF8E1;
color: #F57C00;
}
.status-tag.success {
background: #E8F5E9;
color: #388E3C;
}
/* Images */
.my-post-images {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-bottom: 24rpx;
}
/* Footer */
.my-post-footer {
display: flex;
align-items: center;
border-top: 2rpx solid #F9FAFB;
padding-top: 24rpx;
margin-top: 12rpx;
}
.footer-item {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 24rpx;
color: #9CA3AF;
margin-right: 32rpx;
}
/* Empty State */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 160rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #B0BEC5;
margin-top: 24rpx;
}
/* Hide Scrollbar Globally */
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
display: none;
}
+101 -13
View File
@@ -7,11 +7,20 @@ Page({
groupedTasks: [],
progress: 0,
completingTask: null,
remark: ''
remark: '',
remarkPlaceholder: '',
scrollTop: 0,
// Reward Modals
showLevelUpModal: false,
levelUpData: null,
showBadgeModal: false,
badgeData: null
},
onLoad() {
this.fetchTodayTasks();
this._popupQueue = [];
},
onShow() {
@@ -23,6 +32,42 @@ Page({
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)
@@ -45,12 +90,7 @@ Page({
// Parse Image
let imageUrl = '';
if (plant.imgList && plant.imgList.length > 0) {
let url = plant.imgList[0].url;
if (url && !url.startsWith('http') && !url.startsWith('/') && !url.startsWith('wxfile')) {
imageUrl = '/assets/' + url;
} else {
imageUrl = url;
}
imageUrl = plant.imgList[0].url || '';
}
const plantGroup = {
@@ -149,9 +189,36 @@ Page({
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: ''
remark: '',
remarkPlaceholder: placeholder
});
},
@@ -172,16 +239,29 @@ Page({
const taskId = this.data.completingTask.id;
const remark = this.data.remark || '';
// Optimistic Update immediately for better feel?
// Or wait for server? Wait is safer.
wx.showLoading({ title: '提交中...', mask: true });
request.post('/plant/completeTask', {
taskId: taskId,
remark: remark
}).then(() => {
}).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;
@@ -208,10 +288,14 @@ Page({
showSunshine: true
});
// Hide Animation after duration
// Hide Animation after duration and Start showing modals
setTimeout(() => {
this.setData({ showSunshine: false });
}, 3000);
// 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();
@@ -227,5 +311,9 @@ Page({
wx.switchTab({
url: '/pages/garden/index'
});
},
onTabItemTap() {
this.setData({ scrollTop: Math.random() * 0.01 });
}
})
+45 -3
View File
@@ -44,7 +44,7 @@
<t-button theme="primary" size="large" shape="round" bind:tap="gotoGarden" class="empty-btn">去看看花园</t-button>
</view>
<scroll-view wx:else scroll-y class="task-list" enhanced show-scrollbar="{{false}}">
<scroll-view wx:else scroll-y class="task-list" enhanced show-scrollbar="{{false}}" scroll-top="{{scrollTop}}">
<view wx:for="{{groupedTasks}}" wx:key="plantName" class="plant-task-card {{item.hasOverdue ? 'has-overdue' : ''}}">
<view class="card-header-row">
<view class="plant-info-brief">
@@ -85,7 +85,7 @@
</view>
<!-- Complete Task Popup -->
<t-popup visible="{{completingTask}}" bind:visible-change="onPopupVisibleChange" placement="center">
<t-popup visible="{{completingTask}}" bind:visible-change="onPopupVisibleChange" placement="center" close-on-overlay-click="{{false}}">
<view class="modal-card">
<view class="modal-header">
<text class="modal-title">确认完成任务</text>
@@ -110,7 +110,7 @@
<text class="remark-label">添加记录备注 (可选)</text>
<textarea
class="remark-input"
placeholder="例如:浇了500ml水..."
placeholder="{{remarkPlaceholder}}"
value="{{remark}}"
bindinput="onRemarkInput"
fixed="{{true}}"
@@ -131,6 +131,48 @@
</view>
</t-popup>
<!-- Level Up Modal -->
<t-popup visible="{{showLevelUpModal}}" bind:visible-change="closeLevelUpModal" placement="center" close-on-overlay-click="{{false}}">
<view class="reward-modal">
<view class="reward-floating-icon level-icon-bg">
<t-icon name="chart-bar" size="80rpx" color="#fff" />
</view>
<view class="reward-content">
<text class="reward-title">恭喜升级!</text>
<view class="reward-level-tag">
<text>Lv.{{levelUpData.level}} {{levelUpData.title}}</text>
</view>
<view class="reward-desc-box" wx:if="{{levelUpData.perks}}">
<text style="font-weight: bold; display: block; margin-bottom: 8rpx; color: #EF6C00;">解锁特权</text>
<text>{{levelUpData.perks}}</text>
</view>
</view>
<view class="reward-btn-container">
<t-button t-class="reward-btn btn-level" size="large" block bindtap="closeLevelUpModal">太棒了</t-button>
</view>
</view>
</t-popup>
<!-- New Badge Modal -->
<t-popup visible="{{showBadgeModal}}" bind:visible-change="closeBadgeModal" placement="center" close-on-overlay-click="{{false}}">
<view class="reward-modal">
<view class="reward-floating-icon badge-icon-bg">
<image class="reward-badge-img-large" src="{{badgeData.icon.url}}" mode="aspectFit" wx:if="{{badgeData.icon && badgeData.icon.url}}" />
<t-icon name="medal" size="80rpx" color="#fff" wx:else />
</view>
<view class="reward-content">
<text class="reward-title">解锁新徽章!</text>
<text class="reward-level-tag" style="background: #E3F2FD; color: #1565C0; border-color: #BBDEFB;">{{badgeData.name}}</text>
<view class="reward-desc-box">
<text>{{badgeData.description}}</text>
</view>
</view>
<view class="reward-btn-container">
<t-button t-class="reward-btn btn-badge" size="large" block bindtap="closeBadgeModal">收入囊中</t-button>
</view>
</view>
</t-popup>
<!-- Sunshine Animation Layer -->
<view class="sunshine-layer" wx:if="{{showSunshine}}">
<view class="sunshine-pkg">
+139
View File
@@ -864,3 +864,142 @@
0% { transform: translateY(20rpx); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
/* Reward Modals - Enhanced */
.reward-modal {
background: #fff;
width: 580rpx;
border-radius: 40rpx;
padding-top: 100rpx; /* Space for floating icon */
padding-bottom: 40rpx;
box-sizing: border-box;
position: relative;
overflow: visible; /* Allow floating elements */
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 24rpx 64rpx rgba(0,0,0,0.2);
}
/* Background decorator inside modal (top curve) */
.reward-modal::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 200rpx;
background: linear-gradient(180deg, #F5F5F5 0%, #FFFFFF 100%);
border-radius: 40rpx 40rpx 0 0;
z-index: 0;
}
/* Floating Icon Wrapper */
.reward-floating-icon {
position: absolute;
top: -80rpx;
left: 50%;
transform: translateX(-50%);
width: 180rpx;
height: 180rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 16rpx 32rpx rgba(0,0,0,0.15);
z-index: 2;
background: white; /* Fallback */
}
.level-icon-bg {
background: linear-gradient(135deg, #FFB74D, #EF6C00);
border: 8rpx solid #fff;
}
.badge-icon-bg {
background: linear-gradient(135deg, #64B5F6, #1565C0);
border: 8rpx solid #fff;
}
/* Actual Image/Icon */
.reward-badge-img-large {
width: 140rpx;
height: 140rpx;
filter: drop-shadow(0 4rpx 8rpx rgba(0,0,0,0.2));
}
/* Content */
.reward-content {
position: relative;
z-index: 1;
width: 100%;
padding: 0 48rpx;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 20rpx;
}
.reward-title {
font-size: 44rpx;
font-weight: 900;
color: #263238;
margin-top: 20rpx;
}
/* Level Styles */
.reward-level-tag {
background: #FFF3E0;
color: #EF6C00;
padding: 8rpx 24rpx;
border-radius: 32rpx;
font-size: 28rpx;
font-weight: 700;
border: 2rpx solid #FFE0B2;
}
/* Description Box */
.reward-desc-box {
background: #FAFAFA;
border: 2rpx solid #F5F5F5;
border-radius: 20rpx;
padding: 24rpx;
width: 100%;
color: #546E7A;
font-size: 28rpx;
line-height: 1.6;
text-align: center;
}
/* Button */
.reward-btn-container {
width: 100%;
padding: 0 48rpx;
margin-top: 40rpx;
position: relative;
z-index: 1;
}
/* Custom Button Style override */
.reward-btn {
border-radius: 48rpx !important;
font-weight: 700 !important;
font-size: 32rpx !important;
height: 96rpx !important;
box-shadow: 0 12rpx 24rpx rgba(255, 152, 0, 0.3); /* Default orange shadow */
}
.btn-level {
background: linear-gradient(90deg, #FF9800, #F57C00) !important;
color: white !important;
box-shadow: 0 12rpx 24rpx rgba(245, 124, 0, 0.4) !important;
border: none !important;
}
.btn-badge {
background: linear-gradient(90deg, #42A5F5, #1976D2) !important;
color: white !important;
box-shadow: 0 12rpx 24rpx rgba(25, 118, 210, 0.4) !important;
border: none !important;
}
+21 -9
View File
@@ -10,24 +10,18 @@ Page({
onLoad(options) {
const eventChannel = this.getOpenerEventChannel();
let loadedFromEvent = false;
if (eventChannel && eventChannel.on) {
eventChannel.on('acceptDataFromOpenerPage', (res) => {
if (res.data) {
this.setPlantData(res.data);
loadedFromEvent = true;
}
});
}
if (options.id) {
// Give event channel a chance to fire
setTimeout(() => {
if (!loadedFromEvent && !this.data.plant) {
this.loadPlantDetail(options.id);
}
}, 100);
// Always fetch latest data (especially favorites status)
this.loadPlantDetail(options.id);
}
},
@@ -99,7 +93,8 @@ Page({
pestsDiseases: item.pestsDiseases || '',
commonPests,
classes: (item.classes || []).map(c => c.name),
imgList: item.imgList || []
imgList: item.imgList || [],
isFavorited: (item.hasStar === 1 || item.hasStar === '1')
};
this.setData({
@@ -108,6 +103,23 @@ Page({
});
},
toggleFavorite() {
if (!this.data.plant) return;
const { id, isFavorited } = this.data.plant;
const type = isFavorited ? 2 : 1;
request.get('/wiki/star', { id, type }).then(() => {
this.setData({
'plant.isFavorited': !isFavorited
});
wx.showToast({ title: type === 1 ? '已收藏' : '已取消', icon: 'success' });
}).catch(err => {
console.error('Toggle favorite failed', err);
wx.showToast({ title: '操作失败', icon: 'none' });
});
},
onSwiperChange(e) {
this.setData({
activeImageIndex: e.detail.current
+16 -2
View File
@@ -13,14 +13,24 @@
<view class="wd-counter" wx:if="{{swiperList.length > 0}}">
<text>{{activeImageIndex + 1}} / {{swiperList.length}}</text>
</view>
<!-- Share Button -->
<button class="header-share-btn" open-type="share">
<t-icon name="share" size="40rpx" color="#FFF" />
</button>
</view>
<!-- Plant Name Card -->
<view class="wd-name-card">
<view class="wd-name-row">
<text class="wd-name">{{plant.name}}</text>
<text class="wd-name" style="padding-right: 60rpx;">{{plant.name}}</text>
<text class="wd-scientific" wx:if="{{plant.latinName}}">{{plant.latinName}}</text>
</view>
<!-- Collect Button -->
<view class="card-collect-btn" bindtap="toggleFavorite">
<t-icon name="{{plant.isFavorited ? 'heart-filled' : 'heart'}}" size="52rpx" color="{{plant.isFavorited ? '#EF4444' : '#D1D5DB'}}" />
</view>
<view class="wd-badges" wx:if="{{plant.genus || plant.classes.length > 0}}">
<text class="wd-badge" wx:if="{{plant.genus}}">{{plant.genus}}</text>
<text class="wd-badge" wx:for="{{plant.classes}}" wx:key="*this">{{item}}</text>
@@ -38,6 +48,8 @@
</view>
</view>
<!-- Basic Info -->
<view class="wd-section">
<view class="section-title">
@@ -183,8 +195,10 @@
</view>
</view>
<view style="height: 120rpx;"></view>
<view style="height: 40rpx;"></view>
</scroll-view>
</view>
<!-- Loading -->
+43
View File
@@ -243,3 +243,46 @@ page {
justify-content: center;
background: #F4F6F0;
}
/* Header Share Button */
.header-share-btn {
position: absolute;
top: 24rpx;
right: 24rpx;
width: 72rpx;
height: 72rpx;
background: rgba(0,0,0,0.35);
backdrop-filter: blur(4px);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 20;
border: none;
padding: 0;
line-height: normal;
margin: 0;
}
.header-share-btn::after { border: none; }
.header-share-btn:active {
transform: scale(0.9);
background: rgba(0,0,0,0.5);
transition: transform 0.1s;
}
/* Card Collect Button */
.card-collect-btn {
position: absolute;
top: 32rpx;
right: 32rpx;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 20;
}
.card-collect-btn:active {
transform: scale(0.9);
transition: transform 0.1s;
}
+32
View File
@@ -18,6 +18,7 @@ Page({
pageSize: 10,
isLoading: false,
hasMore: true,
scrollTop: 0,
// Modal State
showIdentifyModal: false
@@ -35,6 +36,10 @@ Page({
}
},
onTabItemTap() {
this.setData({ scrollTop: Math.random() * 0.01 });
},
// Fetch categories from API
fetchCategories() {
request.get('/wiki-class/list').then(res => {
@@ -84,6 +89,7 @@ Page({
genus: item.genus || '',
difficulty: item.difficulty || 0,
isHot: item.isHot === 1,
isFavorited: 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
@@ -115,6 +121,31 @@ Page({
});
},
// Toggle Favorite
async toggleFavorite(e) {
const id = e.currentTarget.dataset.id;
const index = this.data.displayedList.findIndex(i => i.id === id);
if (index === -1) return;
const item = this.data.displayedList[index];
const type = item.isFavorited ? 2 : 1;
try {
// Attempting consistent API pattern
await request.get('/wiki/star', { id, type });
const key = `displayedList[${index}].isFavorited`;
this.setData({
[key]: !item.isFavorited
});
wx.showToast({ title: type === 1 ? '已收藏' : '已取消', icon: 'success' });
} catch (err) {
console.error('Toggle favorite failed', err);
wx.showToast({ title: '操作失败', icon: 'none' });
}
},
// Search Input Handler (debounced)
onSearchInput(e) {
const value = e.detail.value;
@@ -149,6 +180,7 @@ Page({
goToDetail(e) {
const item = e.currentTarget.dataset.item;
wx.navigateTo({
url: `/pages/wiki/detail/index?id=${item.id}`,
success: (res) => {
+25 -24
View File
@@ -16,38 +16,35 @@
bindscrolltolower="onReachBottom"
enhanced
show-scrollbar="{{false}}"
scroll-top="{{scrollTop}}"
>
<view class="search-section">
<t-search placeholder="搜索植物名称,如:龟背竹" value="{{searchQuery}}" bind:change="onSearchInput" shape="round" />
<view class="search-box-card">
<t-search placeholder="搜索植物名称,如:龟背竹" value="{{searchQuery}}" bind:change="onSearchInput" shape="round" t-class-input-container="search-input-bg" />
</view>
</view>
<!-- Dynamic Categories from API -->
<view class="category-scroll">
<t-tag
variant="{{activeCategory === 'all' ? 'dark' : 'outline'}}"
theme="{{activeCategory === 'all' ? 'primary' : 'default'}}"
shape="mark"
size="medium"
style="margin-right: 16rpx;"
bind:tap="setCategory"
<scroll-view class="category-scroll" scroll-x enable-flex show-scrollbar="{{false}}">
<view
class="category-item {{activeCategory === 'all' ? 'active' : ''}}"
bindtap="setCategory"
data-cat="all"
>
全部
</t-tag>
<t-tag
>
<text>全部</text>
</view>
<view
wx:for="{{categories}}"
wx:key="id"
variant="{{activeCategory === item.id ? 'dark' : 'outline'}}"
theme="{{activeCategory === item.id ? 'primary' : 'default'}}"
shape="mark"
size="medium"
style="margin-right: 16rpx;"
bind:tap="setCategory"
class="category-item {{activeCategory === item.id ? 'active' : ''}}"
bindtap="setCategory"
data-cat="{{item.id}}"
>
{{item.name}}
</t-tag>
</view>
>
<text>{{item.name}}</text>
</view>
<!-- Right spacer for scroll -->
<view style="width: 40rpx; flex-shrink: 0;"></view>
</scroll-view>
<view class="wiki-list">
<view wx:for="{{displayedList}}" wx:key="id" class="wiki-card" bindtap="goToDetail" data-item="{{item}}">
@@ -96,7 +93,11 @@
</t-tag>
</view>
</view>
<t-icon name="chevron-right" size="48rpx" color="#ccc" />
<!-- Favorite Heart Button -->
<view class="fav-action-btn" catchtap="toggleFavorite" data-id="{{item.id}}" style="padding: 16rpx; margin-right: -16rpx;">
<t-icon name="{{item.isFavorited ? 'heart-filled' : 'heart'}}" size="48rpx" color="{{item.isFavorited ? '#FA5151' : '#CCC'}}" />
</view>
</view>
</view>
+83 -5
View File
@@ -19,15 +19,71 @@
Usually easier to apply padding to the items or a wrapper inside scroll-view. */
.search-section {
padding: 20rpx 40rpx 0;
padding: 24rpx 40rpx 0;
margin-bottom: 32rpx;
}
.search-box-card {
background: #fff;
border-radius: 40rpx;
padding: 16rpx 20rpx;
box-shadow: 0 8rpx 24rpx rgba(85, 139, 47, 0.08); /* Green-tinted shadow */
}
/* Override TDesign Search */
.search-input-bg {
background: #F4F6F0 !important; /* Light green-grey */
border-radius: 32rpx !important;
height: 80rpx !important;
}
.t-search__input-container {
height: 80rpx !important;
}
/* Category Scroll */
.category-scroll {
display: flex;
flex-wrap: wrap;
padding: 0 40rpx;
margin-bottom: 48rpx;
display: flex;
white-space: nowrap;
padding-left: 40rpx;
margin-bottom: 40rpx;
height: 88rpx;
width: 100%;
box-sizing: border-box;
}
/* Hide scrollbar for webkit */
.category-scroll::-webkit-scrollbar {
display: none;
}
.category-item {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 36rpx;
height: 72rpx;
background: #fff;
border-radius: 36rpx;
margin-right: 24rpx;
font-size: 28rpx;
color: #546E7A;
font-weight: 600;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04);
transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
flex-shrink: 0;
border: 2rpx solid transparent; /* Reserve border space */
}
.category-item:active {
transform: scale(0.95);
}
.category-item.active {
background: #558B2F;
color: #fff;
font-weight: 700;
box-shadow: 0 8rpx 20rpx rgba(85, 139, 47, 0.3);
border-color: #558B2F;
}
.wiki-list {
@@ -59,6 +115,28 @@
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.1);
flex-shrink: 0;
background: #f0f0f0;
position: relative;
}
.fav-icon-box {
position: absolute;
top: 10rpx;
right: 10rpx;
width: 56rpx;
height: 56rpx;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(4px);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 5;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
}
.fav-icon-box:active {
transform: scale(0.9);
transition: transform 0.1s;
}
.wiki-image image { width: 100%; height: 100%; display: block; }
.wiki-image-placeholder {
+1 -1
View File
@@ -3,7 +3,7 @@
"projectname": "plant-mp",
"condition": {},
"setting": {
"urlCheck": false,
"urlCheck": true,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
+12 -13
View File
@@ -1,19 +1,18 @@
// 预置养护图标 - 模拟从后端加载
export const CARE_TASK_ICONS = [
{ id: 'water', name: '浇水', icon: 'rain-medium', color: '#2196F3', bgColor: '#E3F2FD' },
{ id: 'fertilize', name: '施肥', icon: 'gift', color: '#8BC34A', bgColor: '#F1F8E9' },
{ id: 'prune', name: '修剪', icon: 'cut', color: '#FF9800', bgColor: '#FFF3E0' },
{ id: 'repot', name: '换盆', icon: 'home', color: '#9C27B0', bgColor: '#F3E5F5' },
{ id: 'resoil', name: '换土', icon: 'load', color: '#757575', bgColor: '#F5F5F5' },
{ id: 'rotate', name: '转盆', icon: 'refresh', color: '#00BCD4', bgColor: '#E0F7FA' },
{ id: 'spray', name: '喷雾', icon: 'cloud', color: '#03A9F4', bgColor: '#E1F5FE' },
{ id: 'check', name: '检查', icon: 'search', color: '#4CAF50', bgColor: '#E8F5E9' },
{ id: 'sun', name: '晒太阳', icon: 'sunny', color: '#FFC107', bgColor: '#FFFDE7' },
{ id: 'move', name: '挪位置', icon: 'location', color: '#E91E63', bgColor: '#FCE4EC' },
{ id: 'medicine', name: '用药', icon: 'heart', color: '#F44336', bgColor: '#FFEBEE' },
{ id: 'other', name: '其他', icon: 'ellipsis', color: '#607D8B', bgColor: '#ECEFF1' },
{ id: 'water', name: '浇水', icon: 'rain-medium', color: '#2196F3', bgColor: '#E3F2FD', targetAction: 'ACT_WATER' },
{ id: 'fertilize', name: '施肥', icon: 'gift', color: '#8BC34A', bgColor: '#F1F8E9', targetAction: 'ACT_FERTILIZE' },
{ id: 'prune', name: '修剪', icon: 'cut', color: '#FF9800', bgColor: '#FFF3E0', targetAction: 'ACT_PRUNE' },
{ id: 'repot', name: '换盆', icon: 'home', color: '#9C27B0', bgColor: '#F3E5F5', targetAction: 'ACT_REPOT' },
{ id: 'resoil', name: '换土', icon: 'load', color: '#757575', bgColor: '#F5F5F5', targetAction: 'ACT_RESOIL' },
{ id: 'rotate', name: '转盆', icon: 'refresh', color: '#00BCD4', bgColor: '#E0F7FA', targetAction: 'ACT_ROTATE' },
{ id: 'spray', name: '喷雾', icon: 'cloud', color: '#03A9F4', bgColor: '#E1F5FE', targetAction: 'ACT_SPRAY' },
{ id: 'check', name: '检查', icon: 'search', color: '#4CAF50', bgColor: '#E8F5E9', targetAction: 'ACT_CHECK' },
{ id: 'sun', name: '晒太阳', icon: 'sunny', color: '#FFC107', bgColor: '#FFFDE7', targetAction: 'ACT_SUN' },
{ id: 'move', name: '挪位置', icon: 'location', color: '#E91E63', bgColor: '#FCE4EC', targetAction: 'ACT_MOVE' },
{ id: 'medicine', name: '用药', icon: 'heart', color: '#F44336', bgColor: '#FFEBEE', targetAction: 'ACT_MEDICINE' },
{ id: 'other', name: '其他', icon: 'ellipsis', color: '#607D8B', bgColor: '#ECEFF1', targetAction: 'ACT_OTHER' },
];
export const MOCK_BADGES = [
+2 -2
View File
@@ -270,8 +270,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.0.184:8889',
baseUrl: 'https://go.sundynix.cn/api',
header: {
'Content-Type': 'application/json'
}