init: initial commit
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
Page({
|
||||
noop: function() {},
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loadError: false,
|
||||
diary: null,
|
||||
loading: true,
|
||||
comments: [],
|
||||
commentPage: 1,
|
||||
hasMoreComments: true,
|
||||
showCommentInput: false,
|
||||
commentText: '',
|
||||
replyTo: null,
|
||||
coverSwiperIndex: 0,
|
||||
previewImages: [],
|
||||
previewIndex: 0,
|
||||
showPreview: false,
|
||||
myUid: 0
|
||||
},
|
||||
|
||||
onLoad: function(options) {
|
||||
this.setData(getLayout());
|
||||
var diaryId = options.id;
|
||||
if (!diaryId) { wx.showToast({ title: '参数错误', icon: 'none' }); return; }
|
||||
this.setData({ diaryId: parseInt(diaryId), myUid: wx.getStorageSync('userId') || 0 });
|
||||
this.loadDiary();
|
||||
this.loadComments();
|
||||
},
|
||||
|
||||
loadDiary: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
try {
|
||||
var data = await api.diaryDetail(this.data.diaryId);
|
||||
data = this._formatDiary(data);
|
||||
this.setData({ diary: data, myUid: wx.getStorageSync('userId') || 0, loading: false });
|
||||
} catch (e) {
|
||||
this.setData({loadError:true});
|
||||
wx.showToast({ title: '加载失败', icon: 'none' });
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
finally { this._pageLoading = false; this.setData({loading:false}); }
|
||||
},
|
||||
|
||||
_formatDiary: function(data) {
|
||||
var myUid = wx.getStorageSync('userId') || 0;
|
||||
data.creator_name = data.creator_name || (data.created_by == myUid ? '我' : 'TA');
|
||||
data.creator_avatar = getImageUrl(data.creator_avatar);
|
||||
data.created_time = data.created_at ? data.created_at.substring(0, 16).replace('T', ' ') : '';
|
||||
var imgs = data.images || [];
|
||||
if (typeof imgs === 'string') {
|
||||
try { imgs = JSON.parse(imgs); } catch(e) { imgs = []; }
|
||||
}
|
||||
if (!Array.isArray(imgs)) imgs = [];
|
||||
data.imageUrls = imgs.map(function(p) { return getImageUrl(p); });
|
||||
data.comment_count = data.comment_count || 0;
|
||||
// 地理位置:优先显示地址名称,其次显示经纬度(经纬度为0的不显示)
|
||||
data.location_text = '';
|
||||
var hasCoord = data.lat && data.lng && parseFloat(data.lat) !== 0 && parseFloat(data.lng) !== 0;
|
||||
if (data.location) {
|
||||
data.location_text = data.location;
|
||||
} else if (hasCoord) {
|
||||
data.location_text = data.lat + ', ' + data.lng;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
loadComments: async function() {
|
||||
try {
|
||||
var data = await api.diaryCommentList({ diary_id: this.data.diaryId });
|
||||
if (data && data.length > 0) {
|
||||
data.forEach(function(c) {
|
||||
if (c.created_at) {
|
||||
c._time = c.created_at.substring(0, 16).replace('T', ' ');
|
||||
} else if (c.create_time) {
|
||||
c._time = c.create_time.substring(0, 16).replace('T', ' ');
|
||||
} else {
|
||||
c._time = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
this.setData({ comments: data || [] });
|
||||
} catch (e) { console.error(e); }
|
||||
},
|
||||
|
||||
onShareTap: function() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['分享给微信好友', '分享到朋友圈', '生成分享海报'],
|
||||
success: function(res) {
|
||||
if (res.tapIndex === 0) {
|
||||
wx.shareAppMessage ? wx.shareAppMessage() : wx.showToast({ title: '请点击右上角分享', icon: 'none' });
|
||||
} else if (res.tapIndex === 1) {
|
||||
wx.showToast({ title: '请点击右上角分享到朋友圈', icon: 'none' });
|
||||
} else {
|
||||
wx.showToast({ title: '功能开发中', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onCoverSwiperChange: function(e) {
|
||||
this.setData({ coverSwiperIndex: e.detail.current });
|
||||
},
|
||||
|
||||
onImageTap: function(e) {
|
||||
var idx = this.data.coverSwiperIndex;
|
||||
var imgs = this.data.diary.imageUrls || [];
|
||||
this.setData({ showPreview: true, previewIndex: idx, previewImages: imgs });
|
||||
},
|
||||
closePreview: function() { this.setData({ showPreview: false }); },
|
||||
onPreviewSwiperChange: function(e) { this.setData({ previewIndex: e.detail.current }); },
|
||||
|
||||
onCommentTap: function() {
|
||||
this.setData({ showCommentInput: true, replyTo: null, commentText: '' });
|
||||
},
|
||||
onReplyTap: function(e) {
|
||||
var item = e.currentTarget.dataset.item;
|
||||
this.setData({
|
||||
showCommentInput: true,
|
||||
replyTo: { id: item.id, user_id: item.user_id, nickname: item.nickname },
|
||||
commentText: ''
|
||||
});
|
||||
},
|
||||
|
||||
onCommentInput: function(e) {
|
||||
this.setData({ commentText: e.detail.value });
|
||||
},
|
||||
|
||||
cancelComment: function() {
|
||||
this.setData({ showCommentInput: false, replyTo: null, commentText: '' });
|
||||
},
|
||||
|
||||
submitComment: async function() {
|
||||
var text = this.data.commentText.trim();
|
||||
if (!text) { wx.showToast({ title: '请输入评论内容', icon: 'none' }); return; }
|
||||
try {
|
||||
var params = { diary_id: this.data.diaryId, content: text };
|
||||
if (this.data.replyTo) {
|
||||
params.parent_id = this.data.replyTo.id;
|
||||
params.reply_to_user_id = this.data.replyTo.user_id;
|
||||
params.reply_to_username = this.data.replyTo.nickname;
|
||||
}
|
||||
var res = await api.diaryCommentAdd(params);
|
||||
if (res.success) {
|
||||
var newComment = res.comment || {};
|
||||
if (newComment.created_at) {
|
||||
newComment._time = newComment.created_at.substring(0, 16).replace('T', ' ');
|
||||
} else if (newComment.create_time) {
|
||||
newComment._time = newComment.create_time.substring(0, 16).replace('T', ' ');
|
||||
} else {
|
||||
var now = new Date();
|
||||
newComment._time = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0') + ' ' + String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0');
|
||||
}
|
||||
var newComments = this.data.comments.slice();
|
||||
newComments.push(newComment);
|
||||
this.setData({
|
||||
comments: newComments,
|
||||
showCommentInput: false,
|
||||
replyTo: null,
|
||||
commentText: '',
|
||||
'diary.comment_count': (this.data.diary.comment_count || 0) + 1
|
||||
});
|
||||
wx.showToast({ title: '评论成功', icon: 'success' });
|
||||
}
|
||||
} catch (e) {
|
||||
wx.showToast({ title: e.error || '评论失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
onCommentLongPress: function(e) {
|
||||
var item = e.currentTarget.dataset.item;
|
||||
if (item.user_id != this.data.myUid) return;
|
||||
var self = this;
|
||||
wx.showModal({
|
||||
title: '删除评论',
|
||||
content: '确定要删除这条评论吗?',
|
||||
success: async function(res) {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await api.diaryCommentDelete({ id: item.id });
|
||||
var list = self.data.comments.filter(function(c) { return c.id != item.id; });
|
||||
self.setData({
|
||||
comments: list,
|
||||
'diary.comment_count': Math.max(0, (self.data.diary.comment_count || 0) - 1)
|
||||
});
|
||||
wx.showToast({ title: '已删除', icon: 'success' });
|
||||
} catch (e) { wx.showToast({ title: '删除失败', icon: 'none' }); }
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onMoreTap: function() {
|
||||
var self = this;
|
||||
wx.showActionSheet({
|
||||
itemList: ['编辑', '删除'],
|
||||
success: function(res) {
|
||||
if (res.tapIndex === 0) {
|
||||
wx.navigateTo({ url: '/pages/diary_edit/diary_edit?id=' + self.data.diaryId });
|
||||
} else if (res.tapIndex === 1) {
|
||||
self._deleteDiary();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_deleteDiary: function() {
|
||||
var self = this;
|
||||
wx.showModal({
|
||||
title: '删除日记',
|
||||
content: '确定要删除这篇日记吗?',
|
||||
success: async function(res) {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await api.diaryDelete({ id: self.data.diaryId });
|
||||
wx.showToast({ title: '已删除', icon: 'success' });
|
||||
setTimeout(function() { wx.navigateBack(); }, 1500);
|
||||
} catch (e) {
|
||||
wx.showToast({ title: e.error || '删除失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); }
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false,
|
||||
"usingComponents": {
|
||||
"app-nav": "/components/app-nav/app-nav",
|
||||
"page-state": "/components/page-state/page-state"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<view class="app-shell app-shell--secondary" style="{{layoutStyle}}">
|
||||
<launch-transition />
|
||||
<app-nav title="日记详情" back="{{true}}" /><view class="page-bg">
|
||||
|
||||
<view class="read-state" wx:if="{{loading || loadError}}"><page-state wx:if="{{loading}}" kind="loading" title="正在加载日记" /><page-state wx:else kind="error" title="日记加载失败" action="重试" bind:action="loadDiary" /></view>
|
||||
<scroll-view scroll-y class="read-scroll" wx:if="{{!loading && !loadError && diary}}" show-scrollbar="{{false}}" enhanced>
|
||||
<view class="read-page">
|
||||
<view class="read-author"><image class="read-avatar" src="{{diary.creator_avatar || '/images/default-avatar.svg'}}" mode="aspectFill" /><view class="read-author-info"><text class="read-name">{{diary.creator_name || 'TA'}}</text><text class="read-date">{{diary.created_time}}</text><text class="read-location" wx:if="{{diary.location_text}}">{{diary.location_text}}</text></view><view class="read-menu" catchtap="onMoreTap" aria-label="日记操作"><image src="/images/more.svg" mode="aspectFit" /></view></view>
|
||||
<view class="read-paper">
|
||||
<!-- ===== 图片区:有图轮播,无图用背景替代 ===== -->
|
||||
<view wx:if="{{diary.imageUrls && diary.imageUrls.length > 0}}">
|
||||
<view class="img-box">
|
||||
<swiper class="img-swiper" circular="{{false}}" indicator-dots="{{false}}" bindchange="onCoverSwiperChange" current="{{coverSwiperIndex}}">
|
||||
<swiper-item wx:for="{{diary.imageUrls}}" wx:key="*this" class="img-slide">
|
||||
<image src="{{item}}" mode="aspectFill" class="img-item" catchtap="onImageTap" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="img-count" wx:if="{{diary.imageUrls.length > 1}}">
|
||||
<text>{{coverSwiperIndex + 1}}/{{diary.imageUrls.length}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- ===== 内容区:白色卡片 ===== -->
|
||||
<view class="content-card">
|
||||
|
||||
<!-- 标题 -->
|
||||
<text class="title-text" wx:if="{{diary.title}}">{{diary.title}}</text>
|
||||
|
||||
<!-- 正文 -->
|
||||
<text class="content-text" wx:if="{{diary.content}}">{{diary.content}}</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<!-- ===== 评论区 ===== -->
|
||||
<view class="comment-box">
|
||||
<view class="comment-head">
|
||||
<text class="comment-head-text">留言 {{diary.comment_count || 0}}</text>
|
||||
</view>
|
||||
|
||||
<view class="comment-empty" wx:if="{{comments.length === 0}}">
|
||||
<text>还没有留言,写下你读到这里的心情吧</text>
|
||||
</view>
|
||||
|
||||
<view class="comment-item" wx:for="{{comments}}" wx:key="id" wx:for-item="c"
|
||||
data-item="{{c}}" catchlongpress="onCommentLongPress" catchtap="onReplyTap">
|
||||
<image class="comment-avatar" src="{{c.avatar_url || '/images/default-avatar.svg'}}" mode="aspectFill" />
|
||||
<view class="comment-body">
|
||||
<view class="comment-top">
|
||||
<text class="comment-name">{{c.nickname || '匿名'}}</text>
|
||||
<text class="comment-date">{{c._time || ''}}</text>
|
||||
</view>
|
||||
<text class="comment-content" wx:if="{{!c.reply_to_username}}">{{c.content}}</text>
|
||||
<text class="comment-content" wx:else>
|
||||
<text>回复 </text>
|
||||
<text class="comment-at">@{{c.reply_to_username}}</text>
|
||||
<text>:{{c.content}}</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部输入栏 -->
|
||||
<view class="bottom-bar" wx:if="{{!loading && !loadError && diary}}">
|
||||
<view class="bottom-input" catchtap="onCommentTap">
|
||||
<image class="bottom-input-icon" src="/images/comment.svg" mode="aspectFit" />
|
||||
<text class="bottom-input-text">说点什么...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 评论输入弹窗 -->
|
||||
<view class="modal-mask" wx:if="{{showCommentInput}}" catchtap="cancelComment">
|
||||
<view class="modal-box" catchtap="noop">
|
||||
<view class="modal-top">
|
||||
<text class="modal-cancel" catchtap="cancelComment">取消</text>
|
||||
<text class="modal-title">{{replyTo ? '回复 @' + replyTo.nickname : '发表评论'}}</text>
|
||||
<text class="modal-send" catchtap="submitComment">发送</text>
|
||||
</view>
|
||||
<textarea class="modal-input" placeholder="说点什么..." value="{{commentText}}" bindinput="onCommentInput" auto-focus maxlength="500" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图片预览 -->
|
||||
<view class="preview-mask" wx:if="{{showPreview}}" catchtap="closePreview">
|
||||
<swiper class="preview-swiper" current="{{previewIndex}}" bindchange="onPreviewSwiperChange" circular>
|
||||
<swiper-item wx:for="{{previewImages}}" wx:key="*this" class="preview-slide">
|
||||
<image src="{{item}}" mode="widthFix" class="preview-img" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="preview-num">{{previewIndex + 1}} / {{previewImages.length}}</view>
|
||||
<view class="preview-close" catchtap="closePreview">
|
||||
<image class="preview-close-icon" src="/images/close.svg" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -0,0 +1,17 @@
|
||||
@import '../../styles/common.wxss';
|
||||
@import '../../styles/design.wxss';
|
||||
@import '../../styles/journal.wxss';
|
||||
.read-scroll { position:fixed; top:var(--nav-height,64px); left:0; right:0; height:calc(100vh - var(--nav-height,64px) - 100rpx - env(safe-area-inset-bottom)); }
|
||||
.read-state { padding:28rpx 44rpx; }.read-page { padding:32rpx 44rpx 40rpx; }
|
||||
.read-author { display:flex; align-items:center; gap:16rpx; margin-bottom:28rpx; }.read-avatar { width:64rpx; height:64rpx; border-radius:50%; flex-shrink:0; }.read-author-info { flex:1; min-width:0; }.read-name { display:block; font-size:26rpx; font-weight:500; color:#352E2A; }.read-date { display:block; margin-top:6rpx; font-size:21rpx; color:#85776D; }.read-location { display:block; font-size:21rpx; color:#85776D; margin-top:6rpx; line-height:1.5; }
|
||||
.read-menu { width:64rpx; height:64rpx; display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-right:-12rpx; }.read-menu image { width:36rpx; height:36rpx; }
|
||||
.read-paper { padding:12rpx; border:1rpx solid #E9DDD0; border-radius:6rpx 28rpx 6rpx 6rpx; background:#FFFDF8; }
|
||||
.img-box { width:100%; height:500rpx; position:relative; background:#F1EBE2; overflow:hidden; border-radius:3rpx 16rpx 3rpx 3rpx; }.img-swiper { width:100%; height:100%; }.img-slide { display:flex; align-items:center; justify-content:center; }.img-item { width:100%; height:100%; display:block; }
|
||||
.img-count { position:absolute; right:16rpx; bottom:16rpx; background:rgba(53,46,42,.6); color:#FFFDF8; padding:6rpx 14rpx; border-radius:10rpx; font-size:21rpx; }
|
||||
.content-card { padding:32rpx 24rpx 28rpx; }.title-text { display:block; font-size:34rpx; line-height:1.5; font-weight:600; color:#352E2A; margin-bottom:20rpx; }.content-text { display:block; font-size:29rpx; line-height:1.9; color:#4D423A; white-space:pre-wrap; overflow-wrap:break-word; }
|
||||
.comment-box { margin-top:38rpx; }.comment-head { padding-bottom:20rpx; border-bottom:1rpx solid #E9DDD0; }.comment-head-text { color:#352E2A; font-size:28rpx; font-weight:600; }.comment-empty { padding:44rpx 8rpx; text-align:center; font-size:24rpx; line-height:1.7; color:#85776D; }
|
||||
.comment-item { display:flex; align-items:flex-start; gap:14rpx; padding:24rpx 0; border-bottom:1rpx solid #E9DDD0; }.comment-item:last-child { border:0; }.comment-avatar { width:48rpx; height:48rpx; border-radius:50%; flex-shrink:0; }.comment-body { flex:1; min-width:0; }.comment-top { display:flex; justify-content:space-between; gap:12rpx; flex-wrap:wrap; margin-bottom:12rpx; }.comment-name { font-size:24rpx; color:#352E2A; font-weight:500; }.comment-date { font-size:20rpx; color:#A39386; }.comment-content { display:block; font-size:26rpx; line-height:1.65; color:#65594F; overflow-wrap:break-word; }.comment-at { color:#B74768; }
|
||||
.bottom-bar { position:fixed; left:0; right:0; bottom:0; z-index:20; background:#FFFDF8; border-top:1rpx solid #E9DDD0; padding:16rpx 44rpx calc(16rpx + env(safe-area-inset-bottom)); height:calc(100rpx + env(safe-area-inset-bottom)); box-sizing:border-box; }.bottom-input { display:flex; align-items:center; gap:14rpx; height:66rpx; border:1rpx solid #E9DDD0; background:#FAF5EE; padding:0 20rpx; border-radius:16rpx; }.bottom-input-icon { width:30rpx; height:30rpx; }.bottom-input-text { color:#85776D; font-size:25rpx; }
|
||||
.modal-mask { position:fixed; top:0; left:0; right:0; bottom:0; z-index:1200; display:flex; align-items:flex-end; }.modal-box { width:100%; border-radius:24rpx 24rpx 0 0!important; padding:28rpx 32rpx calc(28rpx + env(safe-area-inset-bottom)); }.modal-top { display:flex; align-items:center; justify-content:space-between; gap:16rpx; margin-bottom:24rpx; }.modal-cancel,.modal-send { padding:12rpx 0; font-size:26rpx; color:#85776D; }.modal-send { color:#B74768; }.modal-title { font-size:28rpx; font-weight:500; color:#352E2A; }.modal-input { width:100%; height:200rpx; background:#FAF5EE; border:1rpx solid #E9DDD0; border-radius:16rpx; padding:20rpx; font-size:27rpx; box-sizing:border-box; }
|
||||
.preview-mask { position:fixed; top:0; left:0; right:0; bottom:0; z-index:9999; background:rgba(0,0,0,.95); }.preview-swiper { width:100%; height:100%; }.preview-slide { position:relative; }.preview-img { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:100%; display:block; }.preview-num { position:fixed; bottom:calc(44rpx + env(safe-area-inset-bottom)); left:0; right:0; text-align:center; color:#FFFDF8; font-size:25rpx; }.preview-close { position:fixed; top:var(--nav-height,64px); right:24rpx; width:72rpx; height:72rpx; display:flex; align-items:center; justify-content:center; }.preview-close-icon { width:34rpx; height:34rpx; filter:brightness(0) invert(1); }
|
||||
@media (max-width:350px) { .read-page { padding-left:32rpx; padding-right:32rpx; }.bottom-bar { padding-left:32rpx; padding-right:32rpx; } }
|
||||
Reference in New Issue
Block a user