init: initial commit
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/timeline/timeline.js
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
|
||||
// 重复周期映射
|
||||
var repeatUnitMap = { 'day': '天', 'week': '周', 'month': '月', 'year': '年' };
|
||||
|
||||
// 计算距离今天最近的重复日期(返回 Date 对象,已归零)
|
||||
function calcNearestRepeatDate(eventDate, today, interval, unit) {
|
||||
var base = new Date(eventDate);
|
||||
base.setHours(0,0,0,0);
|
||||
if (unit === 'day') {
|
||||
var diff = Math.floor((today - base) / 86400000);
|
||||
var cycles = Math.floor(diff / interval);
|
||||
var nearest = new Date(base.getTime() + cycles * interval * 86400000);
|
||||
nearest.setHours(0,0,0,0);
|
||||
if (nearest > today) {
|
||||
nearest = new Date(nearest.getTime() - interval * 86400000);
|
||||
nearest.setHours(0,0,0,0);
|
||||
}
|
||||
return nearest;
|
||||
} else if (unit === 'week') {
|
||||
var diff = Math.floor((today - base) / 86400000);
|
||||
var cycles = Math.floor(diff / (7 * interval));
|
||||
var nearest = new Date(base.getTime() + cycles * 7 * interval * 86400000);
|
||||
nearest.setHours(0,0,0,0);
|
||||
if (nearest > today) {
|
||||
nearest = new Date(nearest.getTime() - 7 * interval * 86400000);
|
||||
nearest.setHours(0,0,0,0);
|
||||
}
|
||||
return nearest;
|
||||
} else if (unit === 'month') {
|
||||
var nearest = new Date(base);
|
||||
nearest.setHours(0,0,0,0);
|
||||
while (true) {
|
||||
var next = new Date(nearest);
|
||||
next.setMonth(next.getMonth() + interval);
|
||||
next.setHours(0,0,0,0);
|
||||
if (next > today) break;
|
||||
nearest = next;
|
||||
}
|
||||
return nearest;
|
||||
} else if (unit === 'year') {
|
||||
var nearest = new Date(base);
|
||||
nearest.setHours(0,0,0,0);
|
||||
while (true) {
|
||||
var next = new Date(nearest);
|
||||
next.setFullYear(next.getFullYear() + interval);
|
||||
next.setHours(0,0,0,0);
|
||||
if (next > today) break;
|
||||
nearest = next;
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
return new Date(eventDate);
|
||||
}
|
||||
|
||||
Page({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false,
|
||||
list: []
|
||||
},
|
||||
|
||||
onLoad: function() {
|
||||
this.setData(getLayout());
|
||||
this.loadData();
|
||||
},
|
||||
onShow: function() {
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
onPullDownRefresh: async function() {
|
||||
await this.loadData();
|
||||
wx.stopPullDownRefresh();
|
||||
},
|
||||
|
||||
onShareAppMessage: function() {
|
||||
return { title: '让所有的爱有迹可循', path: '/pages/timeline/timeline', imageUrl: '/images/share.png' };
|
||||
},
|
||||
onShareTimeline: function() {
|
||||
return { title: '让所有的爱有迹可循', imageUrl: '/images/share.png' };
|
||||
},
|
||||
|
||||
loadData: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
try {
|
||||
const list = await api.timelineList();
|
||||
const myUid = wx.getStorageSync('userId');
|
||||
var nameMap = {};
|
||||
var avatarMap = {};
|
||||
try {
|
||||
const info = await api.coupleInfo();
|
||||
var p1 = info.partner1 || {};
|
||||
var p2 = info.partner2 || {};
|
||||
if (p1.id) { nameMap[p1.id] = p1.nickname || p1.username || 'TA'; avatarMap[p1.id] = getImageUrl(p1.avatar); }
|
||||
if (p2.id) { nameMap[p2.id] = p2.nickname || p2.username || 'TA'; avatarMap[p2.id] = getImageUrl(p2.avatar); }
|
||||
} catch (e) {}
|
||||
|
||||
var today = new Date(); today.setHours(0,0,0,0);
|
||||
var items = (list || []).map(function(item, idx) {
|
||||
var cid = parseInt(item.created_by) || 0;
|
||||
item.creator_name = nameMap[cid] || (cid == myUid ? '我' : 'TA');
|
||||
item.creator_avatar = avatarMap[cid] || '';
|
||||
item.creator_initial = (item.creator_name || '?')[0].toUpperCase();
|
||||
item.side = idx % 2 === 0 ? 'left' : 'right';
|
||||
item.bg_image_url = item.bg_image ? getImageUrl(item.bg_image) : '';
|
||||
|
||||
// 计算纪念日/倒数日(支持重复事件)
|
||||
if (item.event_date) {
|
||||
var ed = new Date(item.event_date.replace(/-/g, '/'));
|
||||
ed.setHours(0,0,0,0);
|
||||
var repeatInterval = parseInt(item.repeat_interval) || 0;
|
||||
var repeatUnit = item.repeat_unit || '';
|
||||
|
||||
// 计算实际用来对比的基准日期
|
||||
if (repeatInterval > 0 && repeatUnit) {
|
||||
// 重复事件:以今天的日期为基准,计算最近一次重复的日期
|
||||
var baseDate = calcNearestRepeatDate(ed, today, repeatInterval, repeatUnit);
|
||||
var diff = Math.floor((today - baseDate) / 86400000);
|
||||
if (diff >= 0) {
|
||||
item.is_anniversary = true;
|
||||
item.days_text = '已过去 ' + diff + ' 天';
|
||||
item.date_tag = '纪念日';
|
||||
item.repeat_label = repeatUnitMap[repeatUnit] + '重复';
|
||||
} else {
|
||||
item.is_anniversary = false;
|
||||
item.days_text = '还有 ' + Math.abs(diff) + ' 天';
|
||||
item.date_tag = '倒数日';
|
||||
item.repeat_label = repeatUnitMap[repeatUnit] + '重复';
|
||||
}
|
||||
} else {
|
||||
// 非重复事件:直接用事件日期计算
|
||||
var diff = Math.floor((today - ed) / 86400000);
|
||||
if (diff >= 0) {
|
||||
item.is_anniversary = true;
|
||||
item.days_text = '已过去 ' + diff + ' 天';
|
||||
item.date_tag = '纪念日';
|
||||
} else {
|
||||
item.is_anniversary = false;
|
||||
item.days_text = '还有 ' + Math.abs(diff) + ' 天';
|
||||
item.date_tag = '倒数日';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item.is_anniversary = true;
|
||||
item.days_text = '';
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.setData({ list: items });
|
||||
} catch (e) {
|
||||
this.setData({loadError:true});
|
||||
console.error(e);
|
||||
wx.showToast({ title: (e && (e.error || e.errMsg || e.message)) || '加载失败', icon: 'none' });
|
||||
}
|
||||
finally { this._pageLoading = false; this.setData({loading:false}); }
|
||||
},
|
||||
|
||||
|
||||
deleteTimeline: async function(e) {
|
||||
var id = e.currentTarget.dataset.id;
|
||||
wx.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这条记录吗?',
|
||||
success: async function(res) {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await api.timelineDelete({ id: id });
|
||||
wx.showToast({ title: '已删除', icon: 'success' });
|
||||
// need to re-get this context
|
||||
var pages = getCurrentPages();
|
||||
var page = pages[pages.length - 1];
|
||||
page.loadData();
|
||||
} catch (err) {
|
||||
wx.showToast({ title: err.error || '删除失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, goToPublish: function() { wx.navigateTo({ url: '/pages/publish/publish?type=timeline' }); },
|
||||
|
||||
goToDetail: function(e) {
|
||||
var id = e.currentTarget.dataset.id;
|
||||
if (id) {
|
||||
wx.navigateTo({ url: '/pages/timeline-detail/timeline-detail?id=' + id });
|
||||
}
|
||||
},
|
||||
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"app-nav": "/components/app-nav/app-nav",
|
||||
"page-state": "/components/page-state/page-state"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<view class="app-shell app-shell--secondary" style="{{layoutStyle}}">
|
||||
<launch-transition />
|
||||
<!-- pages/timeline/timeline.wxml - 左右交替记忆日 -->
|
||||
<app-nav title="记忆日" back="{{true}}" />
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
|
||||
|
||||
<scroll-view class="page-bg tl-scroll" scroll-y show-scrollbar="{{false}}" bounces="{{true}}">
|
||||
<view class="container"><view class="j-row"><view><text class="j-eyebrow">MEMORIES OF US</text><text class="j-page-title">值得记住的日子</text></view><button class="j-button j-button-small" bindtap="goToPublish">+ 记忆日</button></view>
|
||||
<page-state wx:if="{{loading}}" kind="loading" title="正在加载" /><page-state wx:elif="{{loadError}}" kind="error" title="加载失败" description="请检查网络后重新加载" action="重试" bind:action="loadData" />
|
||||
<view class="tl-list" wx:if="{{!loading && !loadError && list.length > 0}}">
|
||||
<view class="tl-item {{item.side}}" wx:for="{{list}}" wx:key="id" bindtap="goToDetail" bindlongpress="deleteTimeline" data-id="{{item.id}}">
|
||||
<!-- 背景图片 -->
|
||||
<view class="tl-bg-wrap" wx:if="{{item.bg_image_url}}">
|
||||
<image class="tl-bg" src="{{item.bg_image_url}}" mode="aspectFill" />
|
||||
<view class="tl-bg-overlay"></view>
|
||||
</view>
|
||||
<view class="tl-card {{item.is_anniversary ? '' : 'tl-card-countdown'}} {{item.bg_image_url ? 'tl-card-has-bg' : ''}}">
|
||||
<view class="tl-creator">
|
||||
<image wx:if="{{item.creator_avatar}}" class="tl-avatar" src="{{item.creator_avatar}}" mode="aspectFill" />
|
||||
<view wx:else class="tl-avatar-placeholder">{{item.creator_initial}}</view>
|
||||
<text class="tl-name">{{item.creator_name}}</text>
|
||||
<text class="tl-date-tag {{item.is_anniversary ? 'tl-date-tag-anniversary' : 'tl-date-tag-countdown'}}">{{item.date_tag}}</text>
|
||||
<text class="tl-date">{{item.event_date}}</text>
|
||||
</view>
|
||||
<view class="tl-card-head">
|
||||
<text class="tl-title">{{item.title}}</text>
|
||||
</view>
|
||||
<view class="tl-card-head" wx:if="{{item.location}}">
|
||||
<text class="tl-location">📍 {{item.location}}</text>
|
||||
</view>
|
||||
<view class="tl-card-head" wx:if="{{item.content}}">
|
||||
<text class="tl-note">💬 {{item.content}}</text>
|
||||
</view>
|
||||
<view class="tl-days-wrap">
|
||||
<text class="tl-repeat" wx:if="{{item.repeat_label}}">{{item.repeat_label}}</text>
|
||||
<text class="tl-days {{item.is_anniversary ? '' : 'tl-days-countdown'}}">{{item.days_text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state" wx:if="{{!loading && !loadError && !list.length}}">
|
||||
<text style="font-size:72rpx;">📅</text>
|
||||
<text>还没有记忆日事件</text>
|
||||
<text style="font-size:24rpx;color:#bbb;">点击上方添加记忆日</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</scroll-view>
|
||||
|
||||
|
||||
|
||||
</view>
|
||||
@@ -0,0 +1,153 @@
|
||||
/* pages/timeline/timeline.wxss */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
|
||||
.container { padding: 16rpx 24rpx 300rpx; }
|
||||
.tl-scroll { height: 100vh; }
|
||||
|
||||
|
||||
/* ===== 列表 ===== */
|
||||
.tl-list { display: flex; flex-direction: column; gap: 24rpx; }
|
||||
|
||||
/* ===== 卡片(与日记卡片等宽)===== */
|
||||
.tl-item {
|
||||
width: 100%;
|
||||
}
|
||||
.tl-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 2rpx solid transparent;
|
||||
}
|
||||
.tl-card-countdown {
|
||||
background: linear-gradient(135deg, #F5F7FF, #EEF2FF);
|
||||
border-color: #D6E0FF;
|
||||
}
|
||||
|
||||
/* ===== 用户信息 + 日期并行 ===== */
|
||||
.tl-creator {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.tl-item.left .tl-creator { justify-content: flex-start; }
|
||||
.tl-item.right .tl-creator { justify-content: flex-start; flex-direction: row-reverse; }
|
||||
.tl-avatar { width: 72rpx; height: 72rpx; border-radius: 50%; flex-shrink: 0; align-self: center; }
|
||||
.tl-avatar-placeholder {
|
||||
width: 72rpx; height: 72rpx; border-radius: 50%; flex-shrink: 0; align-self: center;
|
||||
background: linear-gradient(135deg, #F4E2E3, #F0E6FF);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 22rpx; font-weight: 700; color: #FF6B81;
|
||||
}
|
||||
.tl-name { font-size: 28rpx; color: #333; font-weight: 600; flex-shrink: 0; }
|
||||
.tl-date { font-size: 22rpx; color: #999; white-space: nowrap; }
|
||||
.tl-date-tag { font-size: 22rpx; white-space: nowrap; margin-right: 6rpx; }
|
||||
.tl-date-tag-anniversary { color: #FF6B81; }
|
||||
.tl-date-tag-countdown { color: #6B8CFF; }
|
||||
.tl-location { font-size: 22rpx; color: #999; }
|
||||
.tl-note { font-size: 24rpx; color: #888; line-height: 1.5; }
|
||||
|
||||
/* ===== 内容(与用户信息反方向)===== */
|
||||
.tl-card-head {
|
||||
display: flex;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
.tl-card-head:first-of-type {
|
||||
margin-top: 14rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.tl-item.left .tl-card-head { justify-content: flex-end; }
|
||||
.tl-item.right .tl-card-head { justify-content: flex-start; }
|
||||
.tl-title { font-size: 30rpx; font-weight: 700; color: #333; }
|
||||
|
||||
/* ===== 已过去天数 ===== */
|
||||
.tl-days-wrap {
|
||||
display: flex;
|
||||
margin-top: 16rpx;
|
||||
padding-top: 14rpx;
|
||||
border-top: 1rpx dashed #ffe0e5;
|
||||
}
|
||||
.tl-item.left .tl-days-wrap { justify-content: flex-end; }
|
||||
.tl-item.right .tl-days-wrap { justify-content: flex-start; }
|
||||
.tl-days {
|
||||
display: inline-block;
|
||||
font-size: 22rpx;
|
||||
color: #FF6B81;
|
||||
background: linear-gradient(135deg, #FFF0F5, #F4E2E3);
|
||||
padding: 6rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tl-days-countdown {
|
||||
color: #6B8CFF;
|
||||
background: linear-gradient(135deg, #EEF0FF, #E4ECFF);
|
||||
}
|
||||
.tl-repeat {
|
||||
display: inline-block;
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
background: #f5f5f5;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-right: 10rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ===== 列表背景图 ===== */
|
||||
.tl-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tl-bg-wrap {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tl-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tl-bg-overlay {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.55);
|
||||
}
|
||||
.tl-card-has-bg {
|
||||
background: transparent !important;
|
||||
border-color: rgba(255,255,255,0.3) !important;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
}
|
||||
.tl-card-has-bg .tl-title,
|
||||
.tl-card-has-bg .tl-name,
|
||||
.tl-card-has-bg .tl-note,
|
||||
.tl-card-has-bg .tl-location,
|
||||
.tl-card-has-bg .tl-date {
|
||||
color: #fff !important;
|
||||
}
|
||||
.tl-card-has-bg .tl-repeat {
|
||||
background: rgba(255,255,255,0.2);
|
||||
color: rgba(255,255,255,0.85);
|
||||
}
|
||||
.tl-card-has-bg .tl-days {
|
||||
background: rgba(255,255,255,0.2);
|
||||
color: #fff !important;
|
||||
}
|
||||
.tl-card-has-bg .tl-card-head:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
.tl-card-has-bg .tl-days-wrap {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
.tl-card { background:#FFFDF8; border:1rpx solid #E9DDD0; border-radius:16rpx; box-shadow:none; }.tl-card-countdown { background:#FFFDF8; border-color:#D9BBB0; }.tl-creator { flex-wrap:wrap; align-items:center; }.tl-item.right .tl-creator { flex-direction:row; }.tl-name { max-width:240rpx; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }.tl-card-head,.tl-days-wrap { justify-content:flex-start!important; }.tl-title { overflow-wrap:break-word; }.tl-days-countdown { color:#B74768; background:#F4E2E3; }
|
||||
Reference in New Issue
Block a user