feat: 整体页面优化,删除无用svg
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的收藏",
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-image": "tdesign-miniprogram/image/image"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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' });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的发布",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-image": "tdesign-miniprogram/image/image"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user