init: initial commit

This commit is contained in:
2026-10-07 09:28:43 +08:00
commit a68a4ef411
251 changed files with 38279 additions and 0 deletions
@@ -0,0 +1,195 @@
const { getLayout } = require('../../utils/layout');
// pages/timeline-detail/timeline-detail.js
const { api, getImageUrl } = require('../../utils/request');
const { createSwipeBack } = require('../../utils/util');
// 重复周期映射
var repeatUnitMap = { 'day': '天', 'week': '周', 'month': '月', 'year': '年' };
var weekdayNames = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
// 计算距离今天最近的重复日期
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,
item: null,
detailId: 0,
bgImageUrl: '',
daysNumber: '',
daysSign: '',
weekdayText: '',
isAnniversary: true
},
onLoad: function(options) {
this.setData(getLayout());
var id = options.id;
if (id) {
this.setData({ detailId: parseInt(id) });
this.loadDetail(parseInt(id));
}
var self = this;
var swipe = createSwipeBack(function() { wx.navigateBack(); });
this.onTouchStart = swipe.onTouchStart;
this.onTouchEnd = swipe.onTouchEnd;
},
retryDetail: function() { this.loadDetail(this.data.detailId); },
onShow: function() {
if (this.data.detailId) {
this.loadDetail(this.data.detailId);
}
},
loadDetail: async function(id) {
if (this._pageLoading) return;
this._pageLoading = true;
this.setData({loading:true, loadError:false});
try {
var item = await api.timelineDetail(id);
var myUid = wx.getStorageSync('userId');
var cid = parseInt(item.created_by) || 0;
// 获取情侣信息
try {
var info = await api.coupleInfo();
var p1 = info.partner1 || {};
var p2 = info.partner2 || {};
var nameMap = {};
var avatarMap = {};
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); }
item.creator_name = nameMap[cid] || (cid == myUid ? '我' : 'TA');
item.creator_avatar = avatarMap[cid] || '';
} catch(e) {
item.creator_name = cid == myUid ? '我' : 'TA';
item.creator_avatar = '';
}
item.creator_initial = (item.creator_name || '?')[0].toUpperCase();
// 计算天数
var today = new Date(); today.setHours(0,0,0,0);
var daysNumber = '';
var daysSign = '';
var isAnniversary = true;
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 || '';
var diff;
if (repeatInterval > 0 && repeatUnit) {
var baseDate = calcNearestRepeatDate(ed, today, repeatInterval, repeatUnit);
diff = Math.floor((today - baseDate) / 86400000);
isAnniversary = diff >= 0;
daysSign = isAnniversary ? '+' : '-';
daysNumber = Math.abs(diff);
item.repeat_label = repeatUnitMap[repeatUnit] + '重复';
} else {
diff = Math.floor((today - ed) / 86400000);
isAnniversary = diff >= 0;
daysSign = isAnniversary ? '+' : '-';
daysNumber = Math.abs(diff);
}
}
// 星期
var eventDateObj = new Date(item.event_date.replace(/-/g, '/'));
var weekdayText = weekdayNames[eventDateObj.getDay()] || '';
item.is_anniversary = isAnniversary;
this.setData({
item: item,
daysNumber: daysNumber,
daysSign: daysSign,
weekdayText: weekdayText,
isAnniversary: isAnniversary
});
// 从API数据加载背景图
this.loadBgImage();
} 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}); }
},
// ===== 背景图片 =====
loadBgImage: function() {
var item = this.data.item;
if (item && item.bg_image) {
var fullUrl = getImageUrl(item.bg_image);
this.setData({ bgImageUrl: fullUrl });
} else {
this.setData({ bgImageUrl: '' });
}
},
// ===== 编辑 =====
goEdit: function() {
var item = this.data.item;
if (item && item.id) {
wx.navigateTo({
url: '/pages/publish/publish?type=timeline&edit=' + item.id
});
}
},
// ===== 导航 =====
goBack: function() { wx.navigateBack(); },
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
});
@@ -0,0 +1,7 @@
{
"navigationStyle": "custom",
"usingComponents": {
"app-nav": "/components/app-nav/app-nav",
"page-state": "/components/page-state/page-state"
}
}
@@ -0,0 +1,49 @@
<view class="app-shell app-shell--secondary" style="{{layoutStyle}}">
<launch-transition />
<!-- pages/timeline-detail/timeline-detail.wxml -->
<page-meta page-style="{{bgImageUrl ? 'background: transparent;' : ''}}"></page-meta>
<!-- 背景图片(固定全屏,参考首页实现) -->
<image wx:if="{{bgImageUrl}}" src="{{bgImageUrl}}" mode="aspectFill" class="detail-bg-custom" />
<view class="detail-bg-overlay" wx:if="{{bgImageUrl}}"></view>
<app-nav title="记忆日详情" back="{{true}}" />
<scroll-view class="page-bg {{bgImageUrl ? 'page-bg-has-bg' : ''}}" scroll-y show-scrollbar="{{false}}" bounces="{{true}}" style="height:100vh;"
bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
<view class="container {{bgImageUrl ? 'has-bg' : ''}}" bindtap="goEdit">
<page-state wx:if="{{loading}}" kind="loading" title="正在加载详情" /><page-state wx:elif="{{loadError}}" kind="error" title="详情加载失败" action="重试" bind:action="retryDetail" />
<block wx:else>
<!-- 大大的天数 -->
<view class="detail-hero">
<view class="detail-hero-num-wrap">
<text class="detail-hero-num">{{daysNumber}}</text>
<text class="detail-hero-sign">{{daysSign}}</text>
</view>
<!-- <text class="detail-hero-unit">天</text> -->
<view class="detail-hero-bar"></view>
</view>
<!-- 事件标题 -->
<view class="detail-title-wrap" wx:if="{{item.title}}">
<text class="detail-title">{{item.title}}</text>
</view>
<!-- 地点 . 备注 同排 -->
<view class="detail-info-line" wx:if="{{item.location || item.content}}">
<text>{{item.location}}{{item.location && item.content ? ' . ' : ''}}{{item.content}}</text>
</view>
<!-- 标签 + 日期同排 -->
<view class="detail-meta-line">
<text class="detail-meta-tag">{{isAnniversary ? '纪念日' : '目标日'}} {{item.event_date}}({{weekdayText}})</text>
</view>
</block>
</view>
</scroll-view>
</view>
@@ -0,0 +1,165 @@
/* pages/timeline-detail/timeline-detail.wxss */
@import '../../styles/common.wxss';
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; }
.container { padding: 380rpx 24rpx 180rpx; position: relative; z-index: 1; }
.page-bg-has-bg .container { padding-top: 400rpx; }
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; padding-top: 0; }
.page-bg-has-bg { background: transparent !important; }
.page-bg-has-bg::after { display: none !important; }
/* ========== 背景图片(参考首页实现) ========== */
.detail-bg-custom {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.detail-bg-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
background: rgba(0,0,0,0.45);
pointer-events: none;
}
.has-bg .detail-hero-num,
.has-bg .detail-hero-sign,
.has-bg .detail-hero-unit,
.has-bg .detail-hero-bar,
.has-bg .detail-title,
.has-bg .detail-info-line text,
.has-bg .detail-meta-tag,
.has-bg .detail-meta-date,
.has-bg .detail-repeat text {
color: rgba(255,255,255,0.9) !important;
}
.has-bg .detail-hero-bar {
background: rgba(255,255,255,0.35) !important;
}
.has-bg .detail-repeat {
background: rgba(255,255,255,0.15);
}
.tab-bar-transparent {
display: none !important;
}
.top-nav-card-transparent {
background: transparent !important;
border-bottom: none !important;
}
.nav-back-hidden {
display: none !important;
}
.top-nav-card-transparent .nav-back-icon {
filter: brightness(0) invert(1) !important;
}
/* 无背景图时,top-nav-card 背景与 page 背景融合 */
.top-nav-card:not(.top-nav-card-transparent) {
background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%);
}
/* ========== 主区域(点击编辑) ========== */
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
min-height: 60vh;
}
/* ========== 大大的天数 ========== */
.detail-hero {
text-align: center;
margin-bottom: 40rpx;
}
.detail-hero-num-wrap {
display: inline-flex;
align-items: flex-start;
position: relative;
}
.detail-hero-num {
font-size: 120rpx;
font-weight: 900;
color: #B74768;
line-height: 1;
display: inline;
}
.detail-hero-sign {
font-size: 40rpx;
font-weight: 700;
color: #B74768;
vertical-align: super;
line-height: 1;
margin-left: 4rpx;
}
.detail-hero-unit {
font-size: 40rpx;
font-weight: 600;
color: #B74768;
margin-left: 8rpx;
display: inline;
}
.detail-hero-bar {
width: 400rpx;
height: 4rpx;
background: #B74768;
opacity: 0.25;
border-radius: 4rpx;
margin: 40rpx auto 0;
}
.page-bg-has-bg .detail-hero-bar {
background: rgba(255,255,255,0.5) !important;
opacity: 1 !important;
}
/* ========== 事件标题 ========== */
.detail-title-wrap {
text-align: center;
margin-bottom: 20rpx;
}
.detail-title {
font-size: 36rpx;
font-weight: 700;
color: #333;
line-height: 1.4;
}
/* ========== 信息行 ========== */
.detail-info-line {
text-align: center;
margin-bottom: 12rpx;
}
.detail-info-line text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
/* ========== 重复标签 ========== */
.detail-repeat {
display: inline-block;
padding: 8rpx 24rpx;
background: #FFF0F3;
border-radius: 30rpx;
font-size: 22rpx;
color: #B74768;
margin-bottom: 24rpx;
}
/* ========== 空状态 ========== */
.empty-state { text-align: center; padding: 80rpx; color: #999; font-size: 26rpx; }
@import '../../styles/design.wxss';
@import '../../styles/journal.wxss';