同步小程序与管理端更新,完善抽烟记录和解绑冷静期
This commit is contained in:
+80
-24
@@ -1,11 +1,16 @@
|
||||
const { currentUserLocation } = require('../../utils/user-location');
|
||||
const { photoForm, metadataRows } = require('../../utils/photo-metadata');
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/album/album.js - 日记式分组相册
|
||||
const { api, getImageUrl, API_BASE, uploadFileCompat } = require('../../utils/request');
|
||||
const { requestSubscribeAfterAction } = require('../../utils/subscribe');
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false,
|
||||
batches: [],
|
||||
showPhotoEditor: false, editPhotos: [], editPhotoIdx: 0, editBatchId: '',
|
||||
editForm: { taken_at: '', location: '', activity: '' }, savingPhoto: false, currentAddress: '',
|
||||
albumId: null,
|
||||
showUploadModal: false,
|
||||
uploadFilePaths: [],
|
||||
@@ -27,6 +32,67 @@ Page({
|
||||
onShow: function() { this._visible = true; this.loadData(); },
|
||||
onHide: function() { this._visible = false; },
|
||||
|
||||
openPhotoEditor: function(e) {
|
||||
const batch = this.data.batches[Number(e.currentTarget.dataset.batch)];
|
||||
if (!batch || !batch.photos.length) return;
|
||||
this.setData({ showPhotoEditor: true, editBatchId: batch.batch_id, editPhotos: batch.photos, editPhotoIdx: 0, editForm: photoForm(batch.photos[0]) });
|
||||
this._loadCurrentAddress();
|
||||
},
|
||||
_loadCurrentAddress: function() {
|
||||
return currentUserLocation().then(location => { this.setData({ currentAddress: location.location }); return location; });
|
||||
},
|
||||
onEditPhotoInput: function(e) {
|
||||
const field = e.currentTarget.dataset.field;
|
||||
if (['taken_at', 'location', 'activity'].indexOf(field) < 0 || this.data.savingPhoto) return;
|
||||
this.setData({ editForm: { ...this.data.editForm, [field]: e.detail.value } });
|
||||
},
|
||||
clearPhotoDate: function() {
|
||||
if (!this.data.savingPhoto) this.setData({ editForm: { ...this.data.editForm, taken_at: '' } });
|
||||
},
|
||||
_confirmPhotoDiscard: function(next) {
|
||||
if (this.data.savingPhoto) return;
|
||||
const original = photoForm(this.data.editPhotos[this.data.editPhotoIdx] || {});
|
||||
if (JSON.stringify(original) === JSON.stringify(this.data.editForm)) { next(); return; }
|
||||
wx.showModal({ title: '放弃本次修改?', content: '当前照片的信息还没有保存。', confirmText: '放弃修改', success: res => { if (res.confirm && !this.data.savingPhoto) next(); } });
|
||||
},
|
||||
selectEditPhoto: function(e) {
|
||||
const index = Number(e.currentTarget.dataset.index);
|
||||
if (index === this.data.editPhotoIdx || !this.data.editPhotos[index]) return;
|
||||
this._confirmPhotoDiscard(() => this.setData({ editPhotoIdx: index, editForm: photoForm(this.data.editPhotos[index]) }));
|
||||
},
|
||||
closePhotoEditor: function() {
|
||||
this._confirmPhotoDiscard(() => this.setData({ showPhotoEditor: false }));
|
||||
},
|
||||
toggleMetadata: function(e) {
|
||||
const index = Number(e.currentTarget.dataset.batch);
|
||||
if (!this.data.batches[index]) return;
|
||||
this.setData({ ['batches[' + index + '].metadataExpanded']: !this.data.batches[index].metadataExpanded });
|
||||
},
|
||||
preventEditorMove: function() {},
|
||||
savePhotoInfo: async function() {
|
||||
if (this.data.savingPhoto) return;
|
||||
const photo = this.data.editPhotos[this.data.editPhotoIdx];
|
||||
if (!photo || !photo.id) return;
|
||||
const form = { taken_at: this.data.editForm.taken_at, location: this.data.editForm.location.trim(), activity: this.data.editForm.activity.trim() };
|
||||
this.setData({ savingPhoto: true });
|
||||
try {
|
||||
if (!form.location) Object.assign(form, await currentUserLocation());
|
||||
const result = await api.photoUpdate({ id: photo.id, lat: 0, lng: 0, ...form });
|
||||
if (result && result.success && result.location !== undefined) form.location = result.location;
|
||||
if (!result || !result.success) throw result || { error: '保存失败' };
|
||||
// 就地更新卡片和预览,不重载列表,不改变滚动位置。
|
||||
const batches = this.data.batches.map(batch => {
|
||||
const photos = batch.photos.map(item => String(item.id) === String(photo.id) ? { ...item, ...form } : item);
|
||||
return { ...batch, photos, metadata: metadataRows(photos) };
|
||||
});
|
||||
const previewImages = this.data.previewImages.map(item => String(item.id) === String(photo.id) ? { ...item, ...form } : item);
|
||||
this.setData({ batches, previewImages, showPhotoEditor: false });
|
||||
wx.showToast({ title: '照片信息已保存', icon: 'success' });
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && (error.error || error.message)) || '保存失败,请重试', icon: 'none' });
|
||||
} finally { this.setData({ savingPhoto: false }); }
|
||||
},
|
||||
|
||||
// 左滑返回相册列表
|
||||
touchStartX: 0, touchStartY: 0,
|
||||
onTouchStart: function(e) {
|
||||
@@ -66,7 +132,7 @@ Page({
|
||||
loadData: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
var self = this;
|
||||
var albumId = this.data.albumId;
|
||||
@@ -78,8 +144,8 @@ Page({
|
||||
}
|
||||
}
|
||||
if (albumId) {
|
||||
// 使用 noCache 禁用缓存,确保获取最新数据
|
||||
var photos = await api.photos(albumId, null, 'GET', true);
|
||||
// 共享内存缓存;写操作成功后自动失效。
|
||||
var photos = await api.photos(albumId);
|
||||
// 根据 batch_id 分组
|
||||
var batchMap = {};
|
||||
var myUid = wx.getStorageSync('userId');
|
||||
@@ -115,6 +181,7 @@ Page({
|
||||
// 按时间排序(最新的在最前)
|
||||
var batches = Object.values(batchMap);
|
||||
batches.sort(function(a, b) { return b.created_at > a.created_at ? 1 : -1; });
|
||||
batches.forEach(function(batch) { batch.metadata = metadataRows(batch.photos); });
|
||||
this.setData({ batches: batches });
|
||||
} else { this.setData({batches:[]}); }
|
||||
} catch (e) {
|
||||
@@ -142,6 +209,7 @@ Page({
|
||||
wx.showToast({ title: '请选择照片', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
self._loadCurrentAddress();
|
||||
var formList = paths.map(function() { return { taken_at: '', location: '', activity: '' }; });
|
||||
self.setData({
|
||||
showUploadModal: true,
|
||||
@@ -152,6 +220,8 @@ Page({
|
||||
});
|
||||
},
|
||||
fail: function(err) {
|
||||
// 微信选图器关闭时也会触发 fail,主动取消无需提示错误。
|
||||
if (/cancel/i.test(String(err && err.errMsg || ''))) return;
|
||||
console.error('Choose image failed:', err);
|
||||
wx.showToast({ title: '选择照片失败', icon: 'none' });
|
||||
}
|
||||
@@ -263,17 +333,7 @@ Page({
|
||||
|
||||
// 自动定位:为未填写位置的照片自动获取地理位置(发布后自动显示)
|
||||
var autoLoc = null;
|
||||
var needLoc = formList.some(function(x) { return !(x.location && x.location.trim()); });
|
||||
if (needLoc) {
|
||||
try {
|
||||
autoLoc = await new Promise(function(resolve) {
|
||||
wx.getFuzzyLocation({
|
||||
success: function(res) { resolve({ lat: res.latitude, lng: res.longitude }); },
|
||||
fail: function() { resolve(null); }
|
||||
});
|
||||
});
|
||||
} catch (e) { autoLoc = null; }
|
||||
}
|
||||
if (formList.some(function(x) { return !String(x.location || '').trim(); })) autoLoc = await currentUserLocation();
|
||||
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
wx.showLoading({ title: '上传中 ' + (i + 1) + '/' + paths.length, mask: true });
|
||||
@@ -293,7 +353,7 @@ Page({
|
||||
album_id: String(albumId),
|
||||
path: uploadedPath,
|
||||
taken_at: f.taken_at || '',
|
||||
location: f.location || '',
|
||||
location: String(f.location || '').trim() || (autoLoc ? autoLoc.location : ''),
|
||||
activity: f.activity || '',
|
||||
batch_id: batchId,
|
||||
// 未手动填写位置时,自动定位并记录经纬度(发布后自动显示地址)
|
||||
@@ -426,11 +486,7 @@ Page({
|
||||
if (!batch || !batch.photos || !batch.photos[imgIdx]) return;
|
||||
var allImages = [];
|
||||
batch.photos.forEach(function(p) {
|
||||
// 经纬度为0(定位失败)时不显示位置信息
|
||||
var hasCoord = p.lat && p.lng && parseFloat(p.lat) !== 0 && parseFloat(p.lng) !== 0;
|
||||
var loc = p.location;
|
||||
if (!hasCoord) loc = '';
|
||||
allImages.push({ url: p.url, taken_at: p.taken_at, location: loc, activity: p.activity });
|
||||
allImages.push({ id: p.id, url: p.url, taken_at: p.taken_at, location: p.location || '', activity: p.activity });
|
||||
});
|
||||
this.setData({ showPreview: true, previewImages: allImages, previewIndex: imgIdx });
|
||||
},
|
||||
@@ -464,6 +520,6 @@ Page({
|
||||
},
|
||||
|
||||
goToAlbums: function() { wx.navigateBack(); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
}, {"loadData": ["photos", "albumList"]}));
|
||||
|
||||
@@ -18,13 +18,38 @@
|
||||
<view class="moments-name-row">
|
||||
<text class="moments-name">{{item.creator_name || '情侣'}}</text>
|
||||
<text class="moments-count">{{item.photos.length}}张</text>
|
||||
<text class="moments-time">{{item.created_time}}</text>
|
||||
<view class="moments-more" data-batch="{{index}}" catchtap="openPhotoEditor" hover-class="moments-more-pressed" aria-role="button" aria-label="编辑照片信息"><text>•••</text></view>
|
||||
</view>
|
||||
<text class="moments-time moments-upload-time">上传于 {{item.created_time}}</text>
|
||||
<view class="moments-images">
|
||||
<view class="album-img-wrap" wx:for="{{item.photos}}" wx:key="id" wx:for-index="imgIdx" data-batch="{{index}}" data-index="{{imgIdx}}" bindtap="onPhotoTap" bindlongpress="onPhotoLongPress">
|
||||
<image src="{{item.url}}" mode="aspectFill" class="album-img" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="moments-metadata {{item.metadataExpanded ? 'is-expanded' : ''}}" wx:if="{{item.metadata.length}}">
|
||||
<block wx:for="{{item.metadata}}" wx:for-item="note" wx:for-index="noteIndex" wx:key="id">
|
||||
<view class="moment-note" wx:if="{{item.metadataExpanded || noteIndex === 0}}">
|
||||
<block wx:if="{{item.metadataExpanded}}">
|
||||
<text class="moment-photo-label" wx:if="{{note.label}}">{{note.label}}</text>
|
||||
<view class="moment-meta-row" wx:if="{{note.taken_at}}"><text class="moment-meta-label">拍摄</text><text class="moment-meta-value">{{note.taken_at}}</text></view>
|
||||
<view class="moment-meta-row" wx:if="{{note.location}}"><text class="moment-meta-label">地点</text><text class="moment-meta-value">{{note.location}}</text></view>
|
||||
<view class="moment-meta-row" wx:if="{{note.activity}}"><text class="moment-meta-label">故事</text><text class="moment-meta-value moment-story">{{note.activity}}</text></view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view class="moment-summary-line" wx:if="{{note.label || note.taken_at || note.location}}">
|
||||
<text class="moment-summary-badge" wx:if="{{note.label}}">{{note.label}}</text>
|
||||
<text class="moment-summary-date" wx:if="{{note.taken_at}}">{{note.taken_at}}</text>
|
||||
<text class="moment-summary-location" wx:if="{{note.location}}">{{note.location}}</text>
|
||||
</view>
|
||||
<text class="moment-summary-story" wx:if="{{note.activity}}">{{note.activity}}</text>
|
||||
</block>
|
||||
</view>
|
||||
</block>
|
||||
<view class="moment-metadata-toggle" data-batch="{{index}}" catchtap="toggleMetadata" aria-role="button" aria-label="{{item.metadataExpanded ? '收起照片信息' : '展开照片信息'}}">
|
||||
<text>{{item.metadataExpanded ? '收起详情' : (item.metadata.length > 1 ? '还有 ' + (item.metadata.length - 1) + ' 张照片信息 · 展开' : '展开详情')}}</text><text>{{item.metadataExpanded ? '⌃' : '⌄'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="moment-add-note" wx:else data-batch="{{index}}" catchtap="openPhotoEditor">+ 补充拍摄时间、地点和故事</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -117,11 +142,11 @@
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>地点</label>
|
||||
<input class="input" placeholder="例如:北京颐和园" value="{{uploadForm.location}}" bindinput="onUploadInput" data-field="location" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="{{currentAddress || '留空自动使用当前地址'}}" value="{{uploadForm.location}}" bindinput="onUploadInput" data-field="location" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>在做什么</label>
|
||||
<input class="input" placeholder="例如:第一次一起看日落" value="{{uploadForm.activity}}" bindinput="onUploadInput" data-field="activity" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="例如:第一次一起看日落" value="{{uploadForm.activity}}" bindinput="onUploadInput" data-field="activity" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -137,4 +162,23 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 照片信息编辑面板 -->
|
||||
<view class="photo-editor-mask" wx:if="{{showPhotoEditor}}" catchtouchmove="preventEditorMove">
|
||||
<view class="photo-editor-backdrop" catchtap="closePhotoEditor" />
|
||||
<view class="photo-editor-sheet">
|
||||
<view class="photo-editor-grip" />
|
||||
<view class="photo-editor-head"><view><text class="photo-editor-title">给照片补一段故事</text><text class="photo-editor-caption">{{editPhotos.length > 1 ? '选择照片,分别记录它的时刻' : '留住这一天,也留住当时的心情'}}</text></view><view class="photo-editor-close" catchtap="closePhotoEditor" aria-role="button" aria-label="关闭">×</view></view>
|
||||
<scroll-view class="photo-editor-body" scroll-y show-scrollbar="{{false}}">
|
||||
<scroll-view class="photo-editor-picker" scroll-x show-scrollbar="{{false}}" wx:if="{{editPhotos.length > 1}}">
|
||||
<view class="photo-editor-thumbs"><view wx:for="{{editPhotos}}" wx:key="id" class="photo-editor-thumb {{editPhotoIdx === index ? 'photo-editor-selected' : ''}}" data-index="{{index}}" catchtap="selectEditPhoto"><image src="{{item.url}}" mode="aspectFill" /><text>第 {{index + 1}} 张</text></view></view>
|
||||
</scroll-view>
|
||||
<view class="photo-editor-single" wx:else><image src="{{editPhotos[0].url}}" mode="aspectFill" /><text>编辑这张照片的信息</text></view>
|
||||
<view class="photo-editor-field"><view class="photo-editor-label"><text>拍摄时间</text><text class="photo-editor-clear" wx:if="{{editForm.taken_at}}" catchtap="clearPhotoDate">清除</text></view><picker mode="date" value="{{editForm.taken_at}}" disabled="{{savingPhoto}}" bindchange="onEditPhotoInput" data-field="taken_at"><view class="photo-editor-input {{!editForm.taken_at ? 'photo-editor-placeholder' : ''}}">{{editForm.taken_at || '选择拍摄日期'}}<text>⌄</text></view></picker></view>
|
||||
<view class="photo-editor-field"><text class="photo-editor-label">地点 · 留空使用当前地址</text><input class="photo-editor-input" placeholder="{{currentAddress || '留空自动使用当前地址'}}" placeholder-class="photo-editor-placeholder handwritten-placeholder" maxlength="200" value="{{editForm.location}}" disabled="{{savingPhoto}}" bindinput="onEditPhotoInput" data-field="location" /></view>
|
||||
<view class="photo-editor-field"><text class="photo-editor-label">在做什么</text><textarea class="photo-editor-story" placeholder="例如:第一次一起看日落" placeholder-class="photo-editor-placeholder handwritten-placeholder" maxlength="200" value="{{editForm.activity}}" disabled="{{savingPhoto}}" bindinput="onEditPhotoInput" data-field="activity" fixed="{{true}}" adjust-position="{{true}}" /></view>
|
||||
</scroll-view>
|
||||
<view class="photo-editor-actions"><button class="photo-editor-cancel" bindtap="closePhotoEditor" disabled="{{savingPhoto}}">取消</button><button class="j-button photo-editor-save" bindtap="savePhotoInfo" loading="{{savingPhoto}}" disabled="{{savingPhoto}}">保存这张照片</button></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/album/album.wxss - 日记式分组相册 */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
|
||||
|
||||
.upload-root {
|
||||
@@ -329,3 +329,74 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
/* 卡片上的照片故事与编辑入口。 */
|
||||
.moments-name-row { align-items:center; gap:14rpx; margin-bottom:0; }
|
||||
.moments-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.moments-count { flex-shrink:0; }
|
||||
.moments-more { margin-left:auto; flex-shrink:0; width:88rpx; height:88rpx; display:flex; align-items:center; justify-content:center; border-radius:50%; color:#85776D; font-size:28rpx; letter-spacing:3rpx; }
|
||||
.moments-more-pressed { background:#FFF0F5; }
|
||||
.moments-upload-time { display:block; margin:0 0 20rpx; font-size:20rpx; color:#A39386; }
|
||||
.moments-images { margin-top:20rpx; }
|
||||
.moments-metadata { margin-top:18rpx; padding:16rpx 20rpx; border-radius:20rpx; background:#FFF5F8; }
|
||||
.moment-note + .moment-note { border-top:1rpx solid #F1DCE4; margin-top:18rpx; padding-top:18rpx; }
|
||||
.moment-photo-label { display:block; font-size:20rpx; color:#B74768; margin-bottom:12rpx; }
|
||||
.moment-meta-row { display:flex; align-items:flex-start; gap:18rpx; margin-top:8rpx; font-size:24rpx; line-height:1.6; }
|
||||
.moment-meta-label { flex-shrink:0; color:#A39386; font-size:21rpx; padding-top:2rpx; }
|
||||
.moment-meta-value { color:#62504F; overflow-wrap:break-word; min-width:0; white-space:pre-wrap; }
|
||||
.moment-story { color:#352E2A; }
|
||||
.moment-add-note { margin-top:22rpx; padding:16rpx 0 2rpx; border-top:1rpx dashed #F1DCE4; color:#B74768; font-size:22rpx; line-height:1.6; }
|
||||
.photo-editor-mask { position:fixed; inset:0; z-index:1300; display:flex; align-items:flex-end; }
|
||||
.photo-editor-backdrop { position:absolute; inset:0; background:rgba(53,46,42,.35); }
|
||||
.photo-editor-sheet { position:relative; width:100%; max-height:90vh; display:flex; flex-direction:column; background:#FFFCFD; border-radius:40rpx 40rpx 0 0; padding:16rpx 32rpx calc(24rpx + env(safe-area-inset-bottom)); box-sizing:border-box; }
|
||||
.photo-editor-grip { width:64rpx; height:7rpx; border-radius:8rpx; background:#EBD5DE; margin:0 auto 26rpx; }
|
||||
.photo-editor-head { display:flex; align-items:flex-start; gap:20rpx; margin-bottom:24rpx; }
|
||||
.photo-editor-head > view:first-child { flex:1; min-width:0; }
|
||||
.photo-editor-title { display:block; font-size:33rpx; font-weight:600; color:#352E2A; }
|
||||
.photo-editor-caption { display:block; margin-top:10rpx; font-size:23rpx; color:#A39386; }
|
||||
.photo-editor-close { width:64rpx; height:64rpx; display:flex; align-items:center; justify-content:center; border-radius:50%; background:#FFF0F5; color:#85776D; font-size:40rpx; }
|
||||
.photo-editor-body { flex:none; height:760rpx; max-height:60vh; }
|
||||
.photo-editor-picker { width:100%; white-space:nowrap; margin-bottom:28rpx; }
|
||||
.photo-editor-thumbs { display:flex; gap:16rpx; padding:4rpx; }
|
||||
.photo-editor-thumb { flex-shrink:0; padding:6rpx; border:2rpx solid transparent; border-radius:20rpx; text-align:center; }
|
||||
.photo-editor-thumb image { display:block; width:104rpx; height:104rpx; border-radius:14rpx; }
|
||||
.photo-editor-thumb text { display:block; font-size:20rpx; color:#A39386; margin-top:8rpx; }
|
||||
.photo-editor-selected { border-color:#B74768; background:#FFF0F5; }.photo-editor-selected text { color:#B74768; }
|
||||
.photo-editor-single { display:flex; align-items:center; gap:20rpx; margin-bottom:28rpx; font-size:24rpx; color:#85776D; }.photo-editor-single image { width:100rpx; height:100rpx; border-radius:18rpx; }
|
||||
.photo-editor-field { margin-bottom:24rpx; }
|
||||
.photo-editor-label { display:flex; align-items:center; justify-content:space-between; color:#62504F; font-size:25rpx; margin-bottom:12rpx; }
|
||||
.photo-editor-clear { color:#B74768; font-size:22rpx; padding:4rpx 12rpx; }
|
||||
.photo-editor-input { display:flex; align-items:center; justify-content:space-between; width:100%; height:88rpx; padding:0 24rpx; border:1rpx solid #F1DCE4; border-radius:18rpx; background:#FFF5F8; color:#352E2A; font-size:26rpx; box-sizing:border-box; }
|
||||
.photo-editor-placeholder { color:#B4A3A6; }
|
||||
.photo-editor-story { width:100%; height:160rpx; padding:22rpx 24rpx; border:1rpx solid #F1DCE4; border-radius:18rpx; background:#FFF5F8; color:#352E2A; font-size:26rpx; line-height:1.6; box-sizing:border-box; }
|
||||
.photo-editor-actions { display:flex; align-items:stretch; gap:20rpx; padding-top:20rpx; }
|
||||
.photo-editor-cancel { flex:1; margin:0; border:1rpx solid #F1DCE4; border-radius:26rpx; background:#FFFCFD; font-size:26rpx; color:#85776D; line-height:88rpx; }
|
||||
.photo-editor-save { flex:2; }
|
||||
.photo-editor-actions .photo-editor-save,
|
||||
.photo-editor-actions .photo-editor-cancel {
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
height:96rpx;
|
||||
min-height:96rpx;
|
||||
padding:0;
|
||||
margin:0;
|
||||
line-height:normal;
|
||||
font-size:27rpx;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.photo-editor-cancel::after { border:0; }
|
||||
|
||||
/* 默认只显示首张照片的两行摘要,完整信息由用户展开。 */
|
||||
.moment-summary-line { display:flex; align-items:center; gap:12rpx; min-width:0; font-size:22rpx; line-height:1.5; color:#85776D; }
|
||||
.moment-summary-badge { flex-shrink:0; font-size:19rpx; color:#B74768; }
|
||||
.moment-summary-date { flex-shrink:0; }
|
||||
.moment-summary-location { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.moment-summary-story { display:block; margin-top:6rpx; font-size:24rpx; line-height:1.5; color:#62504F; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.moment-metadata-toggle { display:flex; align-items:center; justify-content:space-between; gap:12rpx; min-height:44rpx; padding-top:8rpx; color:#B74768; font-size:21rpx; line-height:1.5; }
|
||||
.moments-metadata.is-expanded { padding:20rpx; }
|
||||
.moments-metadata.is-expanded .moment-metadata-toggle { margin-top:10rpx; border-top:1rpx solid #F1DCE4; padding-top:14rpx; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/albums/albums.js
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: {
|
||||
loading: true, loadError: false,
|
||||
@@ -49,7 +50,7 @@ Page({
|
||||
loadAlbums: async function() {
|
||||
if (this._loading) return;
|
||||
this._loading = true;
|
||||
this.setData({ loading: true, loadError: false });
|
||||
this.setData({ loading: !this._hasCachedData, loadError: false });
|
||||
try {
|
||||
var list = await api.albumList(null, 'GET', true);
|
||||
list = (list || []).map(function(a) {
|
||||
@@ -170,7 +171,7 @@ Page({
|
||||
});
|
||||
}, goToPublish: function() { wx.navigateTo({ url: '/pages/publish/publish?type=album' }); },
|
||||
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goToProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
goToProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
}, {"loadAlbums": ["albumList"]}));
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
<text class="title">编辑相册</text>
|
||||
<view class="input-group">
|
||||
<label>相册标题</label>
|
||||
<input class="input" placeholder="例如:旅行记录" value="{{editForm.title}}" bindinput="onEditTitleInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="例如:旅行记录" value="{{editForm.title}}" bindinput="onEditTitleInput" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>描述(选填)</label>
|
||||
<input class="input" placeholder="简单描述这个相册" value="{{editForm.desc}}" bindinput="onEditDescInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="简单描述这个相册" value="{{editForm.desc}}" bindinput="onEditDescInput" />
|
||||
</view>
|
||||
<button class="btn btn-primary" bindtap="saveAlbumEdit" hover-class="none">保存</button>
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/albums/albums.wxss */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
|
||||
|
||||
|
||||
@@ -126,3 +126,7 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/checkin-detail/checkin-detail.js
|
||||
const { api, getImageUrl, API_BASE, uploadFileCompat } = require('../../utils/request');
|
||||
const { createSwipeBack } = require('../../utils/util');
|
||||
const { requestSubscribeAfterAction } = require('../../utils/subscribe');
|
||||
const COOLDOWN = 5000;
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false, item: null, logs: [], freqLabel: '', typeLabel: '', myStreak: 0, partnerStreak: 0, showCheckModal: false, checkPhoto: '', checkNote: '', lastActionTime: 0, detailId: 0, myAvatar: '', partnerAvatar: '', myName: '我', partnerName: 'TA' },
|
||||
|
||||
@@ -34,7 +35,7 @@ Page({
|
||||
// 从接口加载情侣头像和名称
|
||||
loadCoupleInfo: function() {
|
||||
var self = this;
|
||||
api.coupleInfo().then(function(info) {
|
||||
return api.coupleInfo().then(function(info) {
|
||||
self._applyCoupleInfo(info);
|
||||
}).catch(function(e) { console.error(e); });
|
||||
},
|
||||
@@ -56,7 +57,7 @@ Page({
|
||||
loadDetail: async function(id) {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
var data = await api.checkinDetail(id);
|
||||
var item = data.item || {};
|
||||
@@ -188,6 +189,6 @@ Page({
|
||||
},
|
||||
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
}, {"loadDetail": ["checkinDetail", "coupleInfo"]}));
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
<view class="modal" catchtap="noop" style="max-width:600rpx;">
|
||||
<text class="title">{{item.icon || '✅'}} {{item.title}}</text>
|
||||
<text class="modal-subtitle">记录这一刻</text>
|
||||
<view class="input-group"><label>备注</label><textarea class="input" style="min-height:100rpx;" placeholder="一句话..." value="{{checkNote}}" bindinput="onCheckNoteInput" maxlength="60" /></view>
|
||||
<view class="input-group"><label>备注</label><textarea placeholder-class="handwritten-placeholder" class="input" style="min-height:100rpx;" placeholder="一句话..." value="{{checkNote}}" bindinput="onCheckNoteInput" maxlength="60" /></view>
|
||||
<view class="input-group"><label>照片</label>
|
||||
<view class="photo-upload-box" bindtap="chooseCheckPhoto">
|
||||
<image wx:if="{{checkPhoto}}" src="{{checkPhoto}}" mode="aspectFill" class="photo-upload-img" />
|
||||
|
||||
@@ -18,3 +18,7 @@
|
||||
.empty-state { padding:64rpx 20rpx; font-size:25rpx; color:#85776D; text-align:center; }
|
||||
.modal-overlay { z-index:1200; }.modal .title { display:block; color:#352E2A; text-align:left; }.modal-subtitle { text-align:left; }.modal-actions { display:flex; gap:20rpx; margin-top:28rpx; }.modal-actions .btn { flex:1; min-width:0; padding:22rpx 12rpx; }
|
||||
.photo-upload-box { width:160rpx; height:160rpx; border:1rpx dashed #D5C5B3; border-radius:16rpx; overflow:hidden; background:#FAF5EE; }.photo-upload-img { width:100%; height:100%; }.photo-upload-placeholder { height:100%; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8rpx; color:#85776D; font-size:22rpx; }.photo-upload-placeholder text:first-child { font-size:44rpx; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// Diary journal with server pagination.
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
const { diaryEntry } = require('../../utils/journal');
|
||||
const { saveScrollPosition, getScrollPosition } = require('../../utils/scroll-pos');
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: {
|
||||
loading: true, loadError: false,
|
||||
@@ -22,18 +23,13 @@ Page({
|
||||
this.setData({ scrollTop: getScrollPosition('diary') });
|
||||
},
|
||||
onShow: function() {
|
||||
var self = this;
|
||||
var savedTop = getScrollPosition('diary');
|
||||
this.setData({ scrollTop: savedTop });
|
||||
setTimeout(function() {
|
||||
self.setData({ scrollTop: savedTop });
|
||||
}, 100);
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 收藏 tab 切换已移除
|
||||
|
||||
onScroll: function(e) {
|
||||
this._scrollTop = e.detail.scrollTop;
|
||||
saveScrollPosition('diary', e.detail.scrollTop);
|
||||
},
|
||||
|
||||
@@ -58,7 +54,7 @@ Page({
|
||||
loadData: async function() {
|
||||
if (this._loading) return;
|
||||
this._loading = true;
|
||||
this.setData({ loading: true, loadError: false });
|
||||
this.setData({ loading: !this._hasCachedData, loadError: false });
|
||||
try {
|
||||
var data = await api.diaryList({ page: 1, page_size: this.data.pageSize });
|
||||
var items = this._formatItems(data.items || []);
|
||||
@@ -185,17 +181,17 @@ Page({
|
||||
goToDetail: function(e) {
|
||||
var id = e.currentTarget.dataset.id;
|
||||
if (!id) return;
|
||||
saveScrollPosition('diary', this.data.scrollTop);
|
||||
saveScrollPosition('diary', this._scrollTop == null ? this.data.scrollTop : this._scrollTop);
|
||||
wx.navigateTo({ url: '/pages/diary_detail/diary_detail?id=' + id });
|
||||
},
|
||||
|
||||
_savePosBeforeLeave: function() {
|
||||
saveScrollPosition('diary', this.data.scrollTop);
|
||||
saveScrollPosition('diary', this._scrollTop == null ? this.data.scrollTop : this._scrollTop);
|
||||
},
|
||||
|
||||
openJournalEntry: function(e) { this.goToDetail(e); },
|
||||
goToPublish: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/publish/publish?type=diary' }); },
|
||||
goHome: function() { this._savePosBeforeLeave(); wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goHome: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goProfile: function() { this._savePosBeforeLeave(); wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
goProfile: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
}, {"loadData": ["diaryList", "coupleInfo"]}));
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
@import '../../styles/common.wxss';
|
||||
@import '../../styles/design.wxss';
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
noop: function() {},
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loadError: false,
|
||||
@@ -28,10 +29,14 @@ Page({
|
||||
this.loadComments();
|
||||
},
|
||||
|
||||
onShow: function() {
|
||||
if (this.data.diaryId) { this.loadDiary(); this.loadComments(); }
|
||||
},
|
||||
|
||||
loadDiary: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
var data = await api.diaryDetail(this.data.diaryId);
|
||||
data = this._formatDiary(data);
|
||||
@@ -68,6 +73,7 @@ Page({
|
||||
},
|
||||
|
||||
loadComments: async function() {
|
||||
this.setData({ commentsError: false });
|
||||
try {
|
||||
var data = await api.diaryCommentList({ diary_id: this.data.diaryId });
|
||||
if (data && data.length > 0) {
|
||||
@@ -82,7 +88,7 @@ Page({
|
||||
});
|
||||
}
|
||||
this.setData({ comments: data || [] });
|
||||
} catch (e) { console.error(e); }
|
||||
} catch (e) { this.setData({ commentsError: true }); console.error(e); }
|
||||
},
|
||||
|
||||
onShareTap: function() {
|
||||
@@ -226,5 +232,5 @@ Page({
|
||||
},
|
||||
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); }
|
||||
});
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); }
|
||||
}, {"loadDiary": ["diaryDetail"], "loadComments": ["diaryCommentList"]}));
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<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" />
|
||||
<textarea placeholder-class="handwritten-placeholder" class="modal-input" placeholder="说点什么..." value="{{commentText}}" bindinput="onCommentInput" auto-focus maxlength="500" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -15,3 +15,7 @@
|
||||
.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; } }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -6,7 +6,8 @@ var WEATHER_OPTIONS = ['☀️', '⛅', '☁️', '🌧️', '⛈️', '❄️',
|
||||
|
||||
Page({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false,
|
||||
data: {
|
||||
editorContent: '', loading: true, loadError: false,
|
||||
diaryId: 0,
|
||||
form: { title: '', content: '' },
|
||||
tempImages: [],
|
||||
@@ -43,6 +44,7 @@ Page({
|
||||
this.setData({
|
||||
'form.title': title,
|
||||
'form.content': content,
|
||||
editorContent: content,
|
||||
'form.lat': data.lat || 0,
|
||||
'form.lng': data.lng || 0,
|
||||
'form.location': data.location || '',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<!-- 标题 -->
|
||||
<view class="input-group">
|
||||
<label>标题(选填)</label>
|
||||
<input class="input diary-title-input" placeholder="给日记起个标题..." value="{{form.title}}" bindinput="onTitleInput" maxlength="100" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input diary-title-input" placeholder="给日记起个标题..." value="{{form.title}}" bindinput="onTitleInput" maxlength="100" />
|
||||
</view>
|
||||
|
||||
<!-- 内容编辑器(支持换行) -->
|
||||
@@ -15,7 +15,7 @@
|
||||
<label>内容</label>
|
||||
<text class="char-count {{contentLength > 5000 ? 'char-warn' : ''}}">{{contentLength}}/5000</text>
|
||||
</view>
|
||||
<textarea class="input textarea diary-editor" placeholder="想写些什么..." value="{{form.content}}" bindinput="onContentInput" maxlength="5000" auto-height />
|
||||
<textarea placeholder-class="handwritten-placeholder" class="input textarea diary-editor" placeholder="想写些什么..." value="{{editorContent}}" bindinput="onContentInput" maxlength="{{5000}}" auto-height="{{false}}" cursor-spacing="24" />
|
||||
</view>
|
||||
|
||||
<!-- 图片(可删除) -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import '../../styles/common.wxss';
|
||||
|
||||
.container { padding: 84rpx 32rpx 300rpx; }
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
|
||||
/* ===== 输入框美化 ===== */
|
||||
.input-group { margin-bottom: 36rpx; }
|
||||
@@ -140,3 +140,10 @@ page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
/* 长正文在输入框内滚动,避免输入时整页不断增高。 */
|
||||
textarea.diary-editor { height:420rpx; min-height:420rpx; box-sizing:border-box; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/discover/discover.js
|
||||
const { api } = require('../../utils/request');
|
||||
const { diaryEntry, albumEntry, memoryEntry } = require('../../utils/journal');
|
||||
const { saveScrollPosition, getScrollPosition } = require('../../utils/scroll-pos');
|
||||
const { saveScrollPosition, resetScrollOnTabShow } = require('../../utils/scroll-pos');
|
||||
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: {
|
||||
stats: null, loading: true, loadError: false, refreshing: false, publishOpen: false,
|
||||
activeFilter: 'diary', filters: [{value:'diary',label:'日记'}, {value:'album',label:'相册'}, {value:'memory',label:'记忆日'}],
|
||||
activeFilter: 'diary', filters: [{value:'diary',label:'日记',icon:'stat-book'}, {value:'album',label:'相册',icon:'stat-album'}, {value:'memory',label:'记忆日',icon:'stat-timeline'}],
|
||||
entries: [], albums: [], memories: [],
|
||||
scrollTop: 0
|
||||
},
|
||||
|
||||
onLoad: function() {
|
||||
this.setData(getLayout());
|
||||
this.setData({ scrollTop: getScrollPosition('discover') });
|
||||
this.setData({ scrollTop: 0 });
|
||||
},
|
||||
onShow: function() {
|
||||
// 延迟设置 scrollTop,确保 scroll-view 在页面显示后重新应用滚动位置
|
||||
// 解决从子页面 navigateBack 返回后滚动位置丢失的问题
|
||||
var self = this;
|
||||
var savedTop = getScrollPosition('discover');
|
||||
this.setData({ scrollTop: savedTop });
|
||||
setTimeout(function() {
|
||||
self.setData({ scrollTop: savedTop });
|
||||
}, 100);
|
||||
resetScrollOnTabShow(this, 'discover');
|
||||
const tabBar = this.getTabBar && this.getTabBar();
|
||||
if (tabBar) tabBar.syncActive();
|
||||
this._syncPublishTabBar();
|
||||
this.loadStats();
|
||||
},
|
||||
|
||||
// 保存滚动位置
|
||||
onScroll: function(e) {
|
||||
this._scrollTop = e.detail.scrollTop;
|
||||
saveScrollPosition('discover', e.detail.scrollTop);
|
||||
},
|
||||
|
||||
@@ -42,7 +40,7 @@ Page({
|
||||
loadStats: async function() {
|
||||
if (this._loading) return;
|
||||
this._loading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
|
||||
try {
|
||||
const [timeline, album, diary] = await Promise.all([
|
||||
@@ -68,8 +66,13 @@ Page({
|
||||
|
||||
onRefresh: async function() { this.setData({refreshing:true}); try { await this.loadStats(); } finally { this.setData({refreshing:false}); } },
|
||||
selectFilter: function(e) { this.setData({activeFilter:e.currentTarget.dataset.filter}); },
|
||||
showPublish: function() { this.setData({publishOpen:true}); },
|
||||
hidePublish: function() { this.setData({publishOpen:false}); },
|
||||
_syncPublishTabBar: function() {
|
||||
const tabBar = this.getTabBar && this.getTabBar();
|
||||
if (tabBar && tabBar.setHidden) tabBar.setHidden(this.data.publishOpen);
|
||||
},
|
||||
showPublish: function() { this.setData({publishOpen:true}); this._syncPublishTabBar(); },
|
||||
hidePublish: function() { this.setData({publishOpen:false}); this._syncPublishTabBar(); },
|
||||
onHide: function() { this.hidePublish(); },
|
||||
noop: function() {}, preventMove: function() {},
|
||||
_publish: function(type) { this.hidePublish(); this._savePosBeforeLeave(); wx.navigateTo({url:'/pages/publish/publish?type=' + type}); },
|
||||
writeDiary: function() { this._publish('diary'); },
|
||||
@@ -80,13 +83,13 @@ Page({
|
||||
openAlbum: function(e) { this._savePosBeforeLeave(); wx.navigateTo({url:'/pages/album/album?album_id=' + e.currentTarget.dataset.id}); },
|
||||
// 跳转前保存当前滚动位置
|
||||
_savePosBeforeLeave: function() {
|
||||
saveScrollPosition('discover', this.data.scrollTop);
|
||||
saveScrollPosition('discover', this._scrollTop == null ? this.data.scrollTop : this._scrollTop);
|
||||
},
|
||||
|
||||
// Tab 导航(替换,不留栈)
|
||||
goHome: function() { this._savePosBeforeLeave(); wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goHome: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goToDiaryFromDiscover: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/diary/diary?from=discover' }); },
|
||||
goToProfile: function() { this._savePosBeforeLeave(); wx.redirectTo({ url: '/pages/profile/profile' }); },
|
||||
goToProfile: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/profile/profile' }); },
|
||||
|
||||
// 功能入口(推入栈,支持左滑返回)
|
||||
goToTimeline: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/timeline/timeline' }); },
|
||||
@@ -99,4 +102,4 @@ Page({
|
||||
this._savePosBeforeLeave();
|
||||
wx.navigateTo({ url: '/pages/period/period' });
|
||||
}
|
||||
});
|
||||
}, {"loadStats": ["timelineList", "albumList", "diaryList"]}));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"app-tabbar": "/components/app-tabbar/app-tabbar",
|
||||
"page-state": "/components/page-state/page-state"
|
||||
},
|
||||
"enablePullDownRefresh": true
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<import src="../../templates/journal.wxml" />
|
||||
<launch-transition />
|
||||
<view class="app-shell" style="{{layoutStyle}}"><view class="j-fixed-header"><view class="j-fixed-heading" style="height:{{barHeight}}px;padding-right:{{navSide}}px;"><text class="j-eyebrow">PAGES OF US</text><text class="j-page-title">我们的生活手账</text></view></view>
|
||||
<view class="app-shell j-handwritten-page" style="{{layoutStyle}}"><view class="j-fixed-header"><view class="j-fixed-heading" style="height:{{barHeight}}px;padding-right:{{navSide}}px;"><text class="j-eyebrow">PAGES OF US</text><text class="j-page-title">我们的生活手账</text></view></view>
|
||||
<scroll-view class="page-bg j-main-scroll" scroll-y show-scrollbar="{{false}}" bindscroll="onScroll" scroll-top="{{scrollTop}}" refresher-enabled="{{true}}" refresher-triggered="{{refreshing}}" bindrefresherrefresh="onRefresh">
|
||||
<view class="j-body"><view class="records-action-row"><button class="j-button j-button-small" bindtap="showPublish">+ 记录</button></view>
|
||||
<view class="j-tools records-tools"><text class="j-small-title">一起的日常</text><view class="j-tool-grid"><view bindtap="goToSongs"><image src="/images/music.svg" /><text>歌曲</text></view><view bindtap="goToCheckin"><image src="/images/check.svg" /><text>打卡</text></view><view bindtap="goToPet"><image src="/images/pet.svg" /><text>宠物</text></view><view bindtap="goToPeriod"><image src="/images/j-flower.svg" /><text>姨姨</text></view></view></view>
|
||||
<view class="j-filters"><view wx:for="{{filters}}" wx:key="value" class="j-filter {{activeFilter === item.value ? 'is-active' : ''}}" data-filter="{{item.value}}" bindtap="selectFilter">{{item.label}}</view></view>
|
||||
<view class="j-body"><view class="records-action-row"><text class="j-small-title">一起的日常</text><button class="j-button j-button-small" bindtap="showPublish">+ 记录</button></view>
|
||||
<view class="j-tools records-tools"><view class="j-tool-grid"><view bindtap="goToSongs"><image src="/images/stat-music.svg" /><text>歌曲</text></view><view bindtap="goToCheckin"><image src="/images/stat-check.svg" /><text>打卡</text></view><view bindtap="goToPet"><image src="/images/stat-pet.svg" /><text>宠物</text></view><view bindtap="goToPeriod"><image src="/images/stat-j-flower.svg" /><text>姨姨</text></view></view></view>
|
||||
<view class="j-filters"><view wx:for="{{filters}}" wx:key="value" class="j-filter {{activeFilter === item.value ? 'is-active' : ''}}" data-filter="{{item.value}}" bindtap="selectFilter"><image src="/images/{{item.icon}}.svg" mode="aspectFit" /><text>{{item.label}}</text></view></view>
|
||||
<page-state wx:if="{{loading}}" kind="loading" title="正在翻开手账" /><page-state wx:elif="{{loadError}}" kind="error" title="手账暂时没有加载成功" action="重新加载" bind:action="loadStats" />
|
||||
<block wx:else>
|
||||
<block wx:if="{{activeFilter === 'diary'}}"><block wx:for="{{entries}}" wx:key="id"><template is="journal-entry" data="{{entry:item}}" /></block><page-state wx:if="{{!entries.length}}" title="故事,从今天开始" description="慢慢积攒属于你们的生活片段" action="写日记" bind:action="writeDiary" /><view class="j-link j-center" wx:else bindtap="goToDiaryFromDiscover">查看全部日记 →</view></block>
|
||||
<block wx:elif="{{activeFilter === 'album'}}"><view class="j-album-list"><view class="j-album-print" wx:for="{{albums}}" wx:key="id" bindtap="openAlbum" data-id="{{item.id}}"><image wx:if="{{item.cover}}" src="{{item.cover}}" mode="aspectFill" /><view wx:else class="j-album-placeholder"><image src="/images/album.svg" /><text>还没有照片</text></view><text class="j-story-title">{{item.title}}</text><text class="j-caption">{{item.photo_count || 0}} 张照片</text></view></view><page-state wx:if="{{!albums.length}}" title="相册还空着" description="为旅行、周末或日常,留一本相册" action="创建相册" bind:action="createAlbum" /><view class="j-link j-center" wx:else bindtap="goToAlbum">管理全部相册 →</view></block>
|
||||
<block wx:elif="{{activeFilter === 'album'}}"><view class="j-album-list"><view class="j-album-print" wx:for="{{albums}}" wx:key="id" bindtap="openAlbum" data-id="{{item.id}}"><image wx:if="{{item.cover}}" src="{{item.cover}}" mode="aspectFill" /><view wx:else class="j-album-placeholder"><image src="/images/stat-album.svg" /><text>还没有照片</text></view><text class="j-story-title">{{item.title}}</text><text class="j-caption">{{item.photo_count || 0}} 张照片</text></view></view><page-state wx:if="{{!albums.length}}" title="相册还空着" description="为旅行、周末或日常,留一本相册" action="创建相册" bind:action="createAlbum" /><view class="j-link j-center" wx:else bindtap="goToAlbum">管理全部相册 →</view></block>
|
||||
<block wx:else><view class="j-memory j-memory-list" wx:for="{{memories}}" wx:key="id" bindtap="goToTimeline"><view class="j-stamp"><text>{{item.dateMonth}}月</text><text>{{item.dateDay}}</text></view><view class="j-memory-info"><text class="j-small-title">{{item.title}}</text><text class="j-caption">{{item.date}}</text></view><text class="j-link">›</text></view><page-state wx:if="{{!memories.length}}" title="记住一个特别的日子" description="第一次见面、旅行,还有每个值得期待的日子" action="添加记忆日" bind:action="createMemory" /><view class="j-link j-center" wx:else bindtap="goToTimeline">查看全部记忆日 →</view></block>
|
||||
</block>
|
||||
|
||||
</view></scroll-view><app-tabbar />
|
||||
<view wx:if="{{publishOpen}}" class="j-sheet-mask" bindtap="hidePublish" catchtouchmove="preventMove"><view class="j-sheet" catchtap="noop"><view class="j-sheet-grip" /><view class="j-section-head"><text>留下一页日常</text><view class="j-link" bindtap="hidePublish">关闭</view></view><view class="j-publish-grid"><view bindtap="writeDiary"><image src="/images/book.svg" /><text>写日记</text></view><view bindtap="uploadPhotos"><image src="/images/album.svg" /><text>上传照片</text></view><view bindtap="createMemory"><image src="/images/timeline.svg" /><text>记忆日</text></view></view></view></view>
|
||||
</view></scroll-view>
|
||||
<view wx:if="{{publishOpen}}" class="j-sheet-mask" bindtap="hidePublish" catchtouchmove="preventMove"><view class="j-sheet" catchtap="noop"><view class="j-sheet-grip" /><view class="j-section-head"><text>留下一页日常</text><view class="j-link" bindtap="hidePublish">关闭</view></view><view class="j-publish-grid"><view bindtap="writeDiary"><image src="/images/stat-book.svg" /><text>写日记</text></view><view bindtap="uploadPhotos"><image src="/images/stat-album.svg" /><text>上传照片</text></view><view bindtap="createMemory"><image src="/images/stat-timeline.svg" /><text>记忆日</text></view></view></view></view>
|
||||
</view>
|
||||
|
||||
@@ -5,7 +5,20 @@
|
||||
.records-tools { padding-top:0; margin:0 0 32rpx; border-top:0; }
|
||||
.records-tools .j-tool-grid { margin-top:12rpx; border-bottom:1rpx solid #E9DDD0; padding-bottom:12rpx; gap:12rpx; }
|
||||
.records-tools .j-tool-grid > view { flex-direction:row; gap:8rpx; background:transparent; border:0; border-radius:0; padding:20rpx 0; color:#352E2A; min-height:80rpx; }
|
||||
.records-tools .j-tool-grid image { width:32rpx; height:32rpx; flex-shrink:0; }
|
||||
.records-tools .j-tool-grid image { width:42rpx; height:42rpx; flex-shrink:0; }
|
||||
|
||||
.records-action-row { display:flex; justify-content:flex-end; margin-bottom:24rpx; }
|
||||
.records-action-row { display:flex; align-items:center; justify-content:space-between; gap:24rpx; margin-bottom:24rpx; }
|
||||
.records-action-row .j-button { margin:0; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
/* 发布抽屉使用同一套手账图标。 */
|
||||
.j-publish-grid > view > image { width:48rpx; height:48rpx; }
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
/* 分类标签配上同款图标,并保证窄屏仍能放下三个入口。 */
|
||||
.j-filters { gap:24rpx; }
|
||||
.j-filters .j-filter { display:flex; align-items:center; gap:8rpx; flex-shrink:0; }
|
||||
.j-filters .j-filter image { width:34rpx; height:34rpx; flex-shrink:0; }
|
||||
@media (max-width:350px) { .j-filters { gap:16rpx; }.j-filters .j-filter { gap:6rpx; font-size:25rpx; } }
|
||||
|
||||
@@ -101,6 +101,6 @@ Page({
|
||||
},
|
||||
|
||||
goToSettings: function() { wx.navigateTo({ url: '/pages/settings/settings' }); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goToProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goToProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/history/history.wxss - 操作记录优化版 */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
.container { padding-top: 16rpx; padding-bottom: 300rpx; }
|
||||
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
|
||||
|
||||
@@ -75,3 +75,7 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
+30
-23
@@ -1,10 +1,13 @@
|
||||
let lastLocationReport = 0;
|
||||
let locationReportUser = null;
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/index/index.js
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
const { diaryEntry } = require('../../utils/journal');
|
||||
const { saveScrollPosition, getScrollPosition } = require('../../utils/scroll-pos');
|
||||
const { saveScrollPosition, resetScrollOnTabShow } = require('../../utils/scroll-pos');
|
||||
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: {
|
||||
userId: null,
|
||||
@@ -42,26 +45,22 @@ Page({
|
||||
this.setData({
|
||||
userId: wx.getStorageSync('userId'),
|
||||
coupleId: wx.getStorageSync('coupleId'),
|
||||
scrollTop: getScrollPosition('index')
|
||||
scrollTop: 0
|
||||
});
|
||||
this.loadBg();
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
onShow: function() {
|
||||
var self = this;
|
||||
var savedTop = getScrollPosition('index');
|
||||
resetScrollOnTabShow(this, 'index');
|
||||
const tabBar = this.getTabBar && this.getTabBar();
|
||||
if (tabBar) tabBar.syncActive();
|
||||
var prevCoupleId = this.data.coupleId;
|
||||
var curCoupleId = wx.getStorageSync('coupleId');
|
||||
this.setData({
|
||||
userId: wx.getStorageSync('userId'),
|
||||
coupleId: curCoupleId,
|
||||
scrollTop: savedTop
|
||||
coupleId: curCoupleId
|
||||
});
|
||||
// 延迟重新设置 scrollTop,解决从子页面 navigateBack 返回后滚动位置丢失的问题
|
||||
setTimeout(function() {
|
||||
self.setData({ scrollTop: savedTop });
|
||||
}, 100);
|
||||
// 检查是否已被解绑(之前有 coupleId 但被清除了)
|
||||
if (prevCoupleId && !curCoupleId) {
|
||||
wx.reLaunch({ url: '/pages/index/index' });
|
||||
@@ -74,6 +73,7 @@ Page({
|
||||
|
||||
// 保存滚动位置
|
||||
onScroll: function(e) {
|
||||
this._scrollTop = e.detail.scrollTop;
|
||||
saveScrollPosition('index', e.detail.scrollTop);
|
||||
},
|
||||
|
||||
@@ -166,7 +166,8 @@ Page({
|
||||
if (nb < t) nb.setFullYear(nb.getFullYear()+1);
|
||||
var c = Math.ceil((nb - t) / 86400000);
|
||||
if (c === 0) return '今天!';
|
||||
if (c === 1) return '明天';
|
||||
if (c === 1) return '1天';
|
||||
if (!Number.isFinite(c)) return '';
|
||||
return c + '天';
|
||||
}
|
||||
|
||||
@@ -182,6 +183,7 @@ Page({
|
||||
}
|
||||
|
||||
var dn = {
|
||||
coupleId: info.id,
|
||||
daysCount: daysCount,
|
||||
daysLabel: '我们在一起',
|
||||
datePrefix: '始于',
|
||||
@@ -203,7 +205,7 @@ Page({
|
||||
await this.loadStats();
|
||||
|
||||
} catch (e) {
|
||||
this.setData({ homeError: true, relationshipKnown: false, statsLoading: false, statsError: true, recentDiary: [], recentAlbums: [], upcomingMemory: null });
|
||||
this.setData({ homeError: true, relationshipKnown: !!this._hasCachedData, statsLoading: false, statsError: true });
|
||||
console.error(e);
|
||||
|
||||
} finally {
|
||||
@@ -212,11 +214,12 @@ Page({
|
||||
},
|
||||
|
||||
loadStats: async function() {
|
||||
this.setData({ statsLoading: true, statsError: false });
|
||||
this.setData({ statsLoading: !this._hasCachedData, statsError: false });
|
||||
try {
|
||||
const [timeline, album, diary, songs, checkin, pet] = await Promise.all([
|
||||
api.timelineList(), api.albumList(), api.diaryList(),
|
||||
api.songsList(), api.checkinList(), api.petInfo()
|
||||
api.songsList(), api.checkinList(),
|
||||
this.data.coupleId ? api.petInfo() : Promise.resolve({ has_pet: false })
|
||||
]);
|
||||
var photoCount = 0;
|
||||
if (Array.isArray(album)) {
|
||||
@@ -245,11 +248,15 @@ Page({
|
||||
pet: petLevel
|
||||
}
|
||||
});
|
||||
} catch (e) { console.error(e); this.setData({ statsError: true, recentDiary: [], recentAlbums: [], upcomingMemory: null }); } finally { this.setData({ statsLoading: false }); }
|
||||
} catch (e) { console.error(e); this.setData({ statsError: true }); } finally { this.setData({ statsLoading: false }); }
|
||||
},
|
||||
|
||||
// 获取本机经纬度并上报后端解析地址(后端按距离判断是否重新解析,超过5km才更新),并刷新首页双方地址显示
|
||||
fetchIpLocation: function() {
|
||||
const locationUser = wx.getStorageSync('userId');
|
||||
if (locationReportUser === locationUser && Date.now() - lastLocationReport < 300000) return;
|
||||
locationReportUser = locationUser;
|
||||
lastLocationReport = Date.now();
|
||||
var self = this;
|
||||
var report = function(lat, lng) {
|
||||
var data = {};
|
||||
@@ -258,7 +265,7 @@ Page({
|
||||
if (resp && resp.location) {
|
||||
self.loadData();
|
||||
}
|
||||
}).catch(function() {});
|
||||
}).catch(function() { lastLocationReport = 0; });
|
||||
};
|
||||
// 优先用模糊定位获取经纬度(getFuzzyLocation 与 getLocation 互斥,统一用模糊定位)
|
||||
if (wx.getFuzzyLocation) {
|
||||
@@ -279,32 +286,32 @@ Page({
|
||||
// 背景图以接口返回为准
|
||||
loadBg: function() {
|
||||
var self = this;
|
||||
api.getBgConfig().then(function(cfg) {
|
||||
return api.getBgConfig().then(function(cfg) {
|
||||
self.setData({ bgImage: cfg && cfg.image ? getImageUrl(cfg.image) : '' });
|
||||
}).catch(function(e){ console.warn('loadBg error:', e && (e.error || e._statusCode || e)); });
|
||||
},
|
||||
|
||||
// 跳转前保存当前滚动位置
|
||||
_savePosBeforeLeave: function() {
|
||||
saveScrollPosition('index', this.data.scrollTop);
|
||||
saveScrollPosition('index', this._scrollTop == null ? this.data.scrollTop : this._scrollTop);
|
||||
},
|
||||
|
||||
// 页面跳转
|
||||
goToDiary: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/diary/diary' }); },
|
||||
goToProfile: function() { this._savePosBeforeLeave(); wx.redirectTo({ url: '/pages/profile/profile' }); },
|
||||
goToProfile: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/profile/profile' }); },
|
||||
goToTimeline: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/timeline/timeline' }); },
|
||||
goToAlbum: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/albums/albums' }); },
|
||||
goToSongs: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/lovewords/lovewords' }); },
|
||||
goToCheckin: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/punch/punch' }); },
|
||||
goToPet: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/pet/pet' }); },
|
||||
goToPeriod: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/period/period' }); },
|
||||
bindPartner: function() { wx.reLaunch({ url: '/pages/profile/profile?bind=1' }); },
|
||||
bindPartner: function() { getApp().globalData.openProfileBind = true; wx.switchTab({ url: '/pages/profile/profile' }); },
|
||||
openJournalEntry: function(e) { wx.navigateTo({ url: '/pages/diary_detail/diary_detail?id=' + e.currentTarget.dataset.id }); },
|
||||
writeDiary: function() { wx.navigateTo({ url: '/pages/publish/publish?type=diary' }); },
|
||||
openAlbumPreview: function(e) { wx.navigateTo({ url: '/pages/album/album?album_id=' + e.currentTarget.dataset.id }); },
|
||||
openRecentDiary: function(e) { wx.navigateTo({ url: '/pages/diary_detail/diary_detail?id=' + e.currentTarget.dataset.id }); },
|
||||
showMore: function() {
|
||||
wx.reLaunch({ url: '/pages/discover/discover' });
|
||||
wx.switchTab({ url: '/pages/discover/discover' });
|
||||
},
|
||||
onPullDownRefresh: async function() {
|
||||
await this.loadData();
|
||||
@@ -339,4 +346,4 @@ Page({
|
||||
imageUrl: '/images/share.png'
|
||||
};
|
||||
}
|
||||
});
|
||||
}, {"loadData": ["coupleInfo", "timelineList", "albumList", "diaryList", "songsList", "checkinList", "petInfo", "getBgConfig"]}));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"app-tabbar": "/components/app-tabbar/app-tabbar",
|
||||
"page-state": "/components/page-state/page-state"
|
||||
},
|
||||
"navigationStyle": "custom",
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
<import src="../../templates/journal.wxml" />
|
||||
<launch-transition />
|
||||
<view class="app-shell" style="{{layoutStyle}}">
|
||||
<view class="app-shell {{bgImage ? 'home-has-background' : ''}}" style="{{layoutStyle}}">
|
||||
<view wx:if="{{bgImage}}" class="j-background"><image src="{{bgImage}}" mode="aspectFill" /><view class="j-background-wash" /></view>
|
||||
<view class="home-fixed-header"><view class="home-fixed-heading" style="height:{{barHeight}}px;padding-right:{{navSide}}px;"><text class="j-eyebrow">OUR LITTLE WORLD</text><text class="j-page-title">让所有的爱有迹可循</text></view></view>
|
||||
<view class="home-fixed-header"><view class="home-fixed-heading" style="height:{{barHeight}}px;padding-right:{{navSide}}px;"><text class="j-eyebrow">OUR LITTLE WORLD</text><view class="home-handwritten-title"><text class="j-page-title">让所有的爱有迹可循</text></view></view></view>
|
||||
<scroll-view class="page-bg home-scroll" scroll-y show-scrollbar="{{false}}" refresher-enabled="{{true}}" refresher-triggered="{{refreshing}}" bindrefresherrefresh="onRefresh" bindscroll="onScroll" scroll-top="{{scrollTop}}">
|
||||
<view class="j-body home-body">
|
||||
<page-state wx:if="{{homeError}}" kind="error" title="首页加载失败" description="请检查网络后重新加载" action="重试" bind:action="loadData" />
|
||||
<page-state wx:elif="{{!relationshipKnown}}" kind="loading" title="正在打开你的空间" />
|
||||
<view wx:else class="j-cover">
|
||||
<view class="j-tape" /><view class="j-row"><text class="j-eyebrow">{{daysLabel}}</text><image class="j-cover-heart" src="/images/heart.svg" /></view>
|
||||
<view class="j-tape" /><view class="j-row"><text class="j-eyebrow">{{daysLabel}}</text><image class="j-cover-heart" src="/images/home-cover-heart.svg" /></view>
|
||||
<view class="j-days"><text class="j-days-number">{{daysCount}}</text><text class="j-days-unit">天</text></view><text class="j-cover-date" wx:if="{{loveStartDate}}">{{datePrefix}} {{loveStartDate}}</text>
|
||||
<view class="j-couple-line"><view class="j-avatar"><image wx:if="{{partner1AvatarUrl}}" src="{{partner1AvatarUrl}}" mode="aspectFill" /><text wx:else>{{partner1Initial}}</text></view><view class="j-avatar j-avatar-partner" wx:if="{{coupleId}}"><image wx:if="{{partner2AvatarUrl}}" src="{{partner2AvatarUrl}}" mode="aspectFill" /><text wx:else>{{partner2Initial}}</text></view><view class="j-couple-names"><text>{{partner1Name}}{{coupleId ? ' & ' + partner2Name : ''}}</text><text class="j-caption" wx:if="{{partner1Location || partner2Location}}">{{partner1Location}}{{partner2Location ? ' / ' + partner2Location : ''}}</text></view></view>
|
||||
<view class="j-cover-meta" wx:if="{{coupleDistance || partner1Birthday || partner2Birthday}}"><text wx:if="{{coupleDistance}}">{{coupleDistance}}</text><text wx:if="{{partner1Birthday}}">你的生日:{{partner1Birthday}}</text><text wx:if="{{partner2Birthday}}">TA的生日:{{partner2Birthday}}</text></view>
|
||||
<view class="home-couple-panel">
|
||||
<view class="home-couple-identity">
|
||||
<view class="home-couple-avatars">
|
||||
<view class="j-avatar"><image wx:if="{{partner1AvatarUrl}}" src="{{partner1AvatarUrl}}" mode="aspectFill" /><text wx:else>{{partner1Initial}}</text></view>
|
||||
<view class="j-avatar j-avatar-partner" wx:if="{{coupleId}}"><image wx:if="{{partner2AvatarUrl}}" src="{{partner2AvatarUrl}}" mode="aspectFill" /><text wx:else>{{partner2Initial}}</text></view>
|
||||
</view>
|
||||
<text class="home-couple-names">{{partner1Name}}{{coupleId ? ' & ' + partner2Name : ''}}</text>
|
||||
<text wx:if="{{coupleDistance}}" class="home-distance-pill">{{coupleDistance}}</text>
|
||||
</view>
|
||||
<view class="home-couple-locations" wx:if="{{partner1Location || partner2Location}}">
|
||||
<view class="home-location-row" wx:if="{{partner1Location}}"><text class="home-location-label">我</text><text class="home-location-text">{{partner1Location}}</text></view>
|
||||
<view class="home-location-row" wx:if="{{partner2Location && coupleId}}"><text class="home-location-label home-location-partner">TA</text><text class="home-location-text">{{partner2Location}}</text></view>
|
||||
</view>
|
||||
<view class="home-birthday-row" wx:if="{{partner1Birthday || partner2Birthday}}">
|
||||
<text class="home-birthday-item" wx:if="{{partner1Birthday}}">🎂 我的生日{{partner1Birthday === '今天!' ? '今天' : partner1Birthday}}</text>
|
||||
<text class="home-birthday-item" wx:if="{{partner2Birthday}}">🎂 TA的生日{{partner2Birthday === '今天!' ? '今天' : partner2Birthday}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="j-invite" wx:if="{{relationshipKnown && !coupleId}}" bindtap="bindPartner"><view><text class="j-small-title">邀请另一半,共写这本手账</text><text class="j-caption">绑定后,一起收藏日常</text></view><text class="j-outline-label">邀请</text></view>
|
||||
<view class="j-memory" wx:if="{{upcomingMemory && !homeError}}" bindtap="goToTimeline"><view class="j-stamp"><text>{{upcomingMemory.dateMonth}}月</text><text>{{upcomingMemory.dateDay}}</text></view><view class="j-memory-info"><text class="j-small-title">下一次值得期待</text><text class="j-caption">{{upcomingMemory.title}}</text></view><view class="j-countdown"><text>{{upcomingMemory.days}}</text><text>{{upcomingMemory.days === 0 ? '今天' : '天后'}}</text></view></view>
|
||||
@@ -21,5 +37,5 @@
|
||||
<page-state wx:else title="故事,从今天开始" description="一句心情、一张照片,都可以成为第一页" action="留下第一篇" bind:action="writeDiary" />
|
||||
</view>
|
||||
<view class="j-section" wx:if="{{recentAlbums.length && !statsError}}"><view class="j-section-head"><text>相册里的片段</text><view class="j-link" bindtap="goToAlbum">全部相册 →</view></view><view class="j-photo-strip"><view class="j-photo-print" wx:for="{{recentAlbums}}" wx:key="id" data-id="{{item.id}}" bindtap="openAlbumPreview"><image src="{{item.cover}}" mode="aspectFill" /><text>{{item.title}}</text></view></view></view>
|
||||
</view></scroll-view><app-tabbar />
|
||||
</view></scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -8,6 +8,50 @@
|
||||
.home-body { padding-top:48rpx; }
|
||||
.home-fixed-heading { display:flex; flex-direction:column; align-items:flex-start; justify-content:center; box-sizing:border-box; padding-left:44rpx; }
|
||||
.home-fixed-heading .j-eyebrow { font-size:20rpx; line-height:1.2; letter-spacing:2rpx; white-space:nowrap; }
|
||||
.home-fixed-heading .j-page-title { font-size:34rpx; line-height:1.35; margin:4rpx 0 0; white-space:nowrap; }
|
||||
/* 用楷体的自然笔画呈现手写感,不对文字做倾斜或旋转。 */
|
||||
.home-handwritten-title { position:relative; flex-shrink:0; margin-top:5rpx; }
|
||||
.home-handwritten-title::before { content:''; position:absolute; left:-4rpx; right:-5rpx; bottom:2rpx; height:12rpx; background:rgba(225,133,159,.22); border-radius:8rpx 3rpx 10rpx 5rpx; }
|
||||
.home-fixed-heading .home-handwritten-title .j-page-title { position:relative; font-family:'Kaiti SC','STKaiti','KaiTi',cursive; font-size:34rpx; font-weight:400; font-style:normal; line-height:1.35; letter-spacing:1rpx; margin:0; white-space:nowrap; color:#64424D; }
|
||||
|
||||
@media (max-width:350px) { .home-fixed-heading { padding-left:32rpx; } }
|
||||
|
||||
/* 在一起天数使用参考图中的亮粉色。 */
|
||||
.home-body .j-days-number { color:#F47FA5; }
|
||||
|
||||
/* 身份、地址、距离与生日分层呈现,长地址允许完整换行。 */
|
||||
.home-couple-panel { margin-top:40rpx; padding-top:28rpx; border-top:1rpx dashed #E9DDD0; }
|
||||
.home-couple-identity { display:flex; align-items:center; gap:12rpx; }
|
||||
.home-couple-avatars { display:flex; align-items:center; flex-shrink:0; }
|
||||
.home-couple-avatars .j-avatar { width:64rpx; height:64rpx; }
|
||||
.home-couple-avatars .j-avatar-partner { margin-left:-16rpx; }
|
||||
.home-couple-names { flex:1; min-width:0; font-size:26rpx; line-height:1.5; color:#64424D; overflow-wrap:break-word; }
|
||||
.home-couple-locations { margin-top:22rpx; }
|
||||
.home-location-row { display:flex; align-items:flex-start; gap:14rpx; margin-top:12rpx; }
|
||||
.home-location-label { flex-shrink:0; width:42rpx; padding:3rpx 0; border-radius:10rpx; text-align:center; font-size:19rpx; line-height:1.5; color:#B74768; background:#F8E6ED; }
|
||||
.home-location-partner { color:#526B57; background:#E4EBE3; }
|
||||
.home-location-text { flex:1; min-width:0; padding-top:2rpx; font-size:23rpx; line-height:1.6; color:#85776D; overflow-wrap:break-word; word-break:break-all; }
|
||||
.home-distance-pill { display:inline-block; flex-shrink:0; white-space:nowrap; padding:8rpx 14rpx; border-radius:24rpx; background:#FBE4ED; color:#B74768; font-size:22rpx; line-height:1.5; }
|
||||
.home-birthday-row { display:flex; flex-wrap:wrap; align-items:center; gap:8rpx 28rpx; margin-top:18rpx; color:#A65A73; font-size:22rpx; line-height:1.6; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
/* 自定义照片铺满首页,降低洗白遮罩;卡片透出照片且保留阅读底色。 */
|
||||
.home-has-background .j-background { z-index:0; pointer-events:none; }
|
||||
.home-has-background .j-background > image { display:block; }
|
||||
.home-has-background .j-background-wash {
|
||||
background:linear-gradient(180deg,rgba(253,228,236,.24) 0%,rgba(255,240,245,.32) 48%,rgba(255,247,250,.42) 100%);
|
||||
}
|
||||
.home-has-background .home-scroll { z-index:1; }
|
||||
.home-has-background .home-fixed-header { background:rgba(253,228,236,.72); }
|
||||
.home-has-background .j-cover {
|
||||
background:rgba(255,253,248,.78);
|
||||
border-color:rgba(255,253,248,.7);
|
||||
}
|
||||
.home-has-background .j-story,
|
||||
.home-has-background .j-memory,
|
||||
.home-has-background .j-invite { background:rgba(255,253,248,.9); }
|
||||
.home-has-background .j-section-head { text-shadow:0 1rpx 5rpx rgba(255,253,248,.85); }
|
||||
|
||||
.home-has-background .home-couple-panel { padding:24rpx; border:1rpx solid rgba(233,221,208,.65); border-radius:24rpx; background:rgba(255,253,248,.82); }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/lovewords/lovewords.js - 一起听过的歌
|
||||
const { api } = require('../../utils/request');
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false,
|
||||
list: [],
|
||||
@@ -32,7 +33,7 @@ Page({
|
||||
loadData: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
const list = await api.songsList();
|
||||
const items = (list || []).map(function(item) {
|
||||
@@ -121,6 +122,6 @@ Page({
|
||||
},
|
||||
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
}, {"loadData": ["songsList"]}));
|
||||
|
||||
@@ -31,15 +31,15 @@
|
||||
<text class="modal-subtitle">记录我们一起听过的歌</text>
|
||||
<view class="input-group">
|
||||
<label>歌曲名</label>
|
||||
<input class="input" placeholder="输入歌曲名" value="{{form.song_name}}" bindinput="onSongNameInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="输入歌曲名" value="{{form.song_name}}" bindinput="onSongNameInput" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>歌手</label>
|
||||
<input class="input" placeholder="输入歌手名" value="{{form.artist}}" bindinput="onArtistInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="输入歌手名" value="{{form.artist}}" bindinput="onArtistInput" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>备注(选填)</label>
|
||||
<input class="input" placeholder="一句话,限60字" maxlength="60" value="{{form.note}}" bindinput="onNoteInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="一句话,限60字" maxlength="60" value="{{form.note}}" bindinput="onNoteInput" />
|
||||
</view>
|
||||
<button class="btn btn-primary" bindtap="submit" hover-class="none">{{isEdit ? '保存修改' : '保存'}}</button>
|
||||
</view>
|
||||
|
||||
@@ -11,3 +11,7 @@
|
||||
.song-footer { display:flex; gap:16rpx; flex-wrap:wrap; margin-top:16rpx; font-size:21rpx; color:#A39386; }
|
||||
.song-menu { width:64rpx; height:64rpx; margin-right:-12rpx; display:flex; align-items:center; justify-content:center; flex-shrink:0; }.song-menu image { width:36rpx; height:36rpx; }
|
||||
.modal-overlay { z-index:1200; }.modal-close { background:transparent; }.modal .title { display:block; text-align:left; font-size:32rpx; color:#352E2A; }.modal-subtitle { text-align:left; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -345,7 +345,7 @@
|
||||
<view class="modal-heart">🌡️</view>
|
||||
<text class="modal-title">记录体温</text>
|
||||
<view class="temp-input-row">
|
||||
<input class="temp-input" type="digit" placeholder="36.5" value="{{tempValue}}" bindinput="onTempInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="temp-input" type="digit" placeholder="36.5" value="{{tempValue}}" bindinput="onTempInput" />
|
||||
<text class="temp-unit">°C</text>
|
||||
</view>
|
||||
<view class="modal-actions">
|
||||
@@ -361,7 +361,7 @@
|
||||
<view class="modal-heart">⚖️</view>
|
||||
<text class="modal-title">记录体重</text>
|
||||
<view class="temp-input-row">
|
||||
<input class="temp-input" type="digit" placeholder="50.0" value="{{weightValue}}" bindinput="onWeightInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="temp-input" type="digit" placeholder="50.0" value="{{weightValue}}" bindinput="onWeightInput" />
|
||||
<text class="temp-unit">kg</text>
|
||||
</view>
|
||||
<view class="modal-actions">
|
||||
@@ -376,7 +376,7 @@
|
||||
<view class="modal period-modal">
|
||||
<view class="modal-heart">📝</view>
|
||||
<text class="modal-title">添加备注</text>
|
||||
<textarea class="note-textarea" placeholder="记录一下今天的状态..." value="{{noteValue}}" bindinput="onNoteInput" maxlength="500" />
|
||||
<textarea placeholder-class="handwritten-placeholder" class="note-textarea" placeholder="记录一下今天的状态..." value="{{noteValue}}" bindinput="onNoteInput" maxlength="500" />
|
||||
<view class="modal-actions">
|
||||
<view class="btn btn-secondary" bindtap="closeNoteModal">取消</view>
|
||||
<view class="btn btn-primary" bindtap="confirmNote">确定</view>
|
||||
@@ -430,7 +430,7 @@
|
||||
<!-- 备注 -->
|
||||
<view class="sex-form-row">
|
||||
<text class="sex-form-label">备注</text>
|
||||
<input class="sex-form-input" placeholder="选填" value="{{sexForm.remark}}" bindinput="onSexRemarkInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="sex-form-input" placeholder="选填" value="{{sexForm.remark}}" bindinput="onSexRemarkInput" />
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/period/period.wxss - 姨姨(美柚风格日历模式) */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
.container { padding-top: 16rpx; padding-left: 20rpx; padding-right: 20rpx; }
|
||||
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
|
||||
|
||||
@@ -1083,3 +1083,7 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
.j-body .quick-event-btn .qe-icon { width:34rpx; height:34rpx; }.j-body .qe-label { color:#85776D; font-size:23rpx; }
|
||||
.j-body .quick-event-btn.active { background:#F4E2E3; border-color:#D5A6AE; }.j-body .quick-event-btn.active .qe-label { color:#B74768; }
|
||||
.j-body .cycle-card { background:#FFFDF8; border:1rpx solid #E9DDD0; border-radius:20rpx; box-shadow:none; }.j-body .cycle-date { font-size:26rpx; }.j-body .cycle-detail { color:#85776D; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/pet/achievements.js
|
||||
const { api } = require('../../utils/request');
|
||||
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false,
|
||||
achievements: [],
|
||||
@@ -23,7 +24,7 @@ Page({
|
||||
loadAchievements: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
const res = await api.petAchievements();
|
||||
let list = res.achievements || [];
|
||||
@@ -60,4 +61,4 @@ Page({
|
||||
goBack: function() {
|
||||
wx.navigateBack();
|
||||
}
|
||||
});
|
||||
}, {"loadAchievements": ["petAchievements"]}));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@import '../../styles/common.wxss';
|
||||
|
||||
page {
|
||||
background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%);
|
||||
background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -11,12 +11,12 @@ scroll-view.page-bg {
|
||||
padding-top: 196rpx;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%);
|
||||
background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%);
|
||||
}
|
||||
|
||||
/* ===== 顶部导航(与其他页面一致,fixed 定位与微信胶囊对齐) ===== */
|
||||
.top-nav-card {
|
||||
background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%);
|
||||
background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%);
|
||||
}
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
@@ -181,3 +181,7 @@ scroll-view.page-bg {
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
.adopt-card,.stat-card,.action-card,.dynamics-card,.ach-card { background:#FFFDF8; border:1rpx solid #E9DDD0; box-shadow:none; }.adopt-title,.ach-card-name { color:#352E2A; }.adopt-desc,.ach-card-desc { color:#85776D; }.ach-summary { background:#F4E2E3; color:#943C58; box-shadow:none; }.ach-deco { display:none; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
+22
-6
@@ -1,3 +1,4 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/pet/pet.js
|
||||
const { api } = require('../../utils/request');
|
||||
@@ -18,9 +19,9 @@ const BREED_EMOJIS = {
|
||||
|
||||
const STAGE_NAMES = ['', '幼犬期', '少年期', '青年期', '成年期', '终极期'];
|
||||
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false,
|
||||
data: { loading: true, loadError: false, needsBinding: false,
|
||||
hasPet: false,
|
||||
pet: null,
|
||||
daily: null,
|
||||
@@ -70,8 +71,14 @@ Page({
|
||||
loadPetInfo: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
const relationship = await api.coupleInfo();
|
||||
if (relationship.not_bound) {
|
||||
this.setData({needsBinding:true,hasPet:false,pet:null,daily:null,achievements:[],dynamics:[],displayDynamics:[],showRename:false});
|
||||
return;
|
||||
}
|
||||
this.setData({needsBinding:false});
|
||||
const res = await api.petInfo();
|
||||
if (res.has_pet && res.pet) {
|
||||
const pet = res.pet;
|
||||
@@ -130,8 +137,12 @@ Page({
|
||||
this.setData({ hasPet: false, pet:null, daily:null, achievements:[], dynamics:[], displayDynamics:[] });
|
||||
}
|
||||
} catch (e) {
|
||||
this.setData({loadError:true});
|
||||
console.error(e);
|
||||
if (e && e.error === '请先绑定情侣') {
|
||||
this.setData({needsBinding:true,hasPet:false,pet:null,showRename:false});
|
||||
} else {
|
||||
this.setData({loadError:true});
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
finally { this._pageLoading = false; this.setData({loading:false}); }
|
||||
},
|
||||
@@ -300,10 +311,15 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/pet/achievements' });
|
||||
},
|
||||
|
||||
bindPartner: function() {
|
||||
getApp().globalData.openProfileBind = true;
|
||||
wx.switchTab({url:'/pages/profile/profile'});
|
||||
},
|
||||
|
||||
// 返回
|
||||
goBack: function() {
|
||||
wx.navigateBack();
|
||||
},
|
||||
|
||||
stopPropagation: function() {}
|
||||
});
|
||||
}, {"loadPetInfo": ["coupleInfo", "petInfo"]}));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<scroll-view class="page-bg" scroll-y show-scrollbar="{{false}}" bounces="{{true}}">
|
||||
<view class="c-body">
|
||||
<page-state wx:if="{{loading}}" kind="loading" title="正在加载" /><page-state wx:elif="{{loadError}}" kind="error" title="加载失败" action="重试" bind:action="loadPetInfo" /><block wx:else>
|
||||
<page-state wx:if="{{loading}}" kind="loading" title="正在加载" /><page-state wx:elif="{{needsBinding}}" title="绑定另一半后一起养宠物" description="邀请TA加入,一起领养和照顾你们的伙伴" action="去绑定" bind:action="bindPartner" /><page-state wx:elif="{{loadError}}" kind="error" title="加载失败" action="重试" bind:action="loadPetInfo" /><block wx:else>
|
||||
|
||||
<!-- ===== 未领养:领养引导 ===== -->
|
||||
<view wx:if="{{!hasPet}}" class="adopt-section">
|
||||
@@ -34,7 +34,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="input-group" style="margin-top:32rpx;">
|
||||
<input class="input" placeholder="给宠物取个名字吧" value="{{petName}}" bindinput="onNameInput" maxlength="20" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="给宠物取个名字吧" value="{{petName}}" bindinput="onNameInput" maxlength="20" />
|
||||
</view>
|
||||
<view class="gender-select">
|
||||
<view class="gender-option {{genderSelect==1?'active':''}}" bindtap="selectGender" data-gender="1">
|
||||
@@ -214,7 +214,7 @@
|
||||
<view wx:if="{{showRename}}" class="modal-overlay" bindtap="hideRenameModal">
|
||||
<view class="rename-modal" catchtap="stopPropagation">
|
||||
<text class="rename-modal-title">给宠物改名</text>
|
||||
<input class="rename-input" placeholder="输入新名字" placeholder-class="rename-placeholder" value="{{renameValue}}" bindinput="onRenameInput" maxlength="20" />
|
||||
<input class="rename-input" placeholder="输入新名字" placeholder-class="rename-placeholder handwritten-placeholder" value="{{renameValue}}" bindinput="onRenameInput" maxlength="20" />
|
||||
<view class="rename-actions">
|
||||
<button class="btn btn-secondary rename-btn" bindtap="hideRenameModal">取消</button>
|
||||
<button class="btn btn-primary rename-btn" bindtap="doRename" loading="{{renaming}}">确定</button>
|
||||
|
||||
@@ -17,3 +17,7 @@
|
||||
.adopt-hero { text-align:center; margin:12rpx 0 28rpx; }.adopt-hero-emoji { font-size:64rpx; }.adopt-card { background:#FFFDF8; border:1rpx solid #E9DDD0; border-radius:16rpx; padding:28rpx; }.adopt-title { display:block; font-size:31rpx; font-weight:600; color:#352E2A; text-align:center; }.adopt-desc { display:block; font-size:24rpx; line-height:1.6; color:#85776D; margin-top:14rpx; text-align:center; }
|
||||
.breed-select { display:grid; grid-template-columns:repeat(4,1fr); gap:12rpx; margin-top:28rpx; }.breed-option { display:flex; flex-direction:column; align-items:center; gap:10rpx; padding:18rpx 4rpx; border:1rpx solid #E9DDD0; border-radius:12rpx; }.breed-option.active { background:#F4E2E3; border-color:#D5A6AE; }.breed-emoji { font-size:35rpx; }.breed-name { font-size:22rpx; color:#65594F; }.gender-select { display:flex; gap:16rpx; margin:24rpx 0; }.gender-option { display:flex; flex:1; justify-content:center; padding:20rpx 8rpx; border:1rpx solid #E9DDD0; border-radius:12rpx; color:#85776D; font-size:25rpx; }.gender-option.active { background:#F4E2E3; border-color:#D5A6AE; color:#943C58; }.adopt-btn { width:100%; font-size:26rpx; border-radius:18rpx; padding:24rpx 12rpx; }
|
||||
.modal-overlay { z-index:1200; }.rename-modal { width:100%; max-width:600rpx; padding:36rpx; background:#FFFDF8; border:1rpx solid #E9DDD0; border-radius:24rpx; box-sizing:border-box; }.rename-modal-title { display:block; color:#352E2A; font-weight:600; font-size:31rpx; margin-bottom:26rpx; }.rename-input { width:100%; height:84rpx; padding:0 20rpx; background:#FAF5EE; border:1rpx solid #E9DDD0; border-radius:14rpx; box-sizing:border-box; font-size:27rpx; }.rename-actions { display:flex; gap:20rpx; margin-top:28rpx; }.rename-btn { flex:1; min-width:0; padding:24rpx 10rpx; font-size:26rpx; }.rename-placeholder { color:#A39386; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
+154
-38
@@ -1,12 +1,25 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/profile/profile.js - 与原网站一致的个人中心
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
const { saveScrollPosition, getScrollPosition } = require('../../utils/scroll-pos');
|
||||
const { saveScrollPosition, resetScrollOnTabShow } = require('../../utils/scroll-pos');
|
||||
const { requestSubscribe } = require('../../utils/subscribe');
|
||||
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: {
|
||||
scrollTop: 0,
|
||||
statsLoading:false, statsError:false,
|
||||
statisticItems:[
|
||||
{key:'timeline',label:'记忆日',icon:'stat-timeline',count:'—',color:'pink'},
|
||||
{key:'album',label:'相册',icon:'stat-album',count:'—',color:'yellow'},
|
||||
{key:'diary',label:'日记',icon:'stat-book',count:'—',color:'mint'},
|
||||
{key:'songs',label:'歌曲',icon:'stat-music',count:'—',color:'blue'},
|
||||
{key:'checkin',label:'打卡',icon:'stat-check',count:'—',color:'purple'},
|
||||
{key:'pet',label:'宠物',icon:'stat-pet',count:'—',color:'pink'},
|
||||
{key:'period',label:'姨姨',icon:'stat-j-flower',count:'',color:'pink'},
|
||||
{key:'settings',label:'更多',icon:'stat-settings',count:'',color:'gray'}
|
||||
],
|
||||
|
||||
// 我的信息
|
||||
myNickname: '未登录',
|
||||
@@ -26,6 +39,7 @@ Page({
|
||||
bindingKnown: false, bindingError: false,
|
||||
partnerName: 'TA',
|
||||
partnerDisplayId: '',
|
||||
partnerId: 0,
|
||||
partnerDetail: '',
|
||||
partnerAvatarUrl: '',
|
||||
partnerInitial: '?',
|
||||
@@ -54,8 +68,9 @@ Page({
|
||||
tempLoveDate: '',
|
||||
tempLoveStory: '',
|
||||
|
||||
// 查看TA资料(只读)弹窗
|
||||
// 编辑TA资料弹窗
|
||||
showEditPartnerModal: false,
|
||||
partnerSaving: false, partnerAvatarUploading: false,
|
||||
editPartnerNickname: '',
|
||||
editPartnerGender: 'male',
|
||||
editPartnerGenderText: '男',
|
||||
@@ -127,6 +142,7 @@ Page({
|
||||
_needRefresh: true,
|
||||
|
||||
onScroll: function(e) {
|
||||
this._scrollTop = e.detail.scrollTop;
|
||||
saveScrollPosition('profile', e.detail.scrollTop);
|
||||
},
|
||||
|
||||
@@ -137,7 +153,7 @@ Page({
|
||||
this.setData({
|
||||
userId: wx.getStorageSync('userId'),
|
||||
coupleId: wx.getStorageSync('coupleId'),
|
||||
scrollTop: getScrollPosition('profile')
|
||||
scrollTop: 0
|
||||
});
|
||||
this.loadData();
|
||||
this.loadUnbindStatus();
|
||||
@@ -153,23 +169,28 @@ Page({
|
||||
// 从分享邀请链接进入
|
||||
if (options && options.inviter) {
|
||||
var self = this;
|
||||
setTimeout(function() { self.handleShareInvite(options.inviter); }, 800);
|
||||
setTimeout(function() { self.handleShareInvite(options.inviter, options.invite_code); }, 800);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onShow: function() {
|
||||
resetScrollOnTabShow(this, 'profile');
|
||||
if (!wx.getStorageSync('coupleId') && !this.data.shareOutgoingCode) api.generateInviteLink().then(res => this.setData({shareOutgoingCode:res.code || ''})).catch(() => {});
|
||||
const tabBar = this.getTabBar && this.getTabBar();
|
||||
if (tabBar) tabBar.syncActive();
|
||||
var uid = wx.getStorageSync('userId');
|
||||
var cid = wx.getStorageSync('coupleId');
|
||||
var self = this;
|
||||
var savedTop = getScrollPosition('profile');
|
||||
this.setData({ scrollTop: savedTop, bindingKnown: false, myLocation: '' });
|
||||
// 延迟重新设置 scrollTop,解决从子页面 navigateBack 返回后滚动位置丢失的问题
|
||||
setTimeout(function() {
|
||||
self.setData({ scrollTop: savedTop });
|
||||
}, 100);
|
||||
this.setData({ userId: uid, coupleId: cid });
|
||||
const app = getApp();
|
||||
if (app.globalData.openProfileBind) {
|
||||
app.globalData.openProfileBind = false;
|
||||
this._openBindOnLoad = true;
|
||||
this._dataLoads = {};
|
||||
}
|
||||
this.loadData();
|
||||
this.loadProfileStats();
|
||||
if (!cid && this.data.showInvitePanel && !this._pollTimer) this.startPollCoupleStatus();
|
||||
// 如果已绑定情侣,关闭邀请面板
|
||||
if (cid && this.data.showInvitePanel) {
|
||||
this.setData({ showInvitePanel: false });
|
||||
@@ -180,6 +201,10 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
onHide: function() {
|
||||
if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; }
|
||||
},
|
||||
|
||||
onUnload: function() {
|
||||
if (this._pollTimer) {
|
||||
clearInterval(this._pollTimer);
|
||||
@@ -188,7 +213,7 @@ Page({
|
||||
},
|
||||
|
||||
onPullDownRefresh: async function() {
|
||||
await this.loadData();
|
||||
await Promise.all([this.loadData(),this.loadProfileStats()]);
|
||||
wx.stopPullDownRefresh();
|
||||
},
|
||||
|
||||
@@ -229,6 +254,7 @@ Page({
|
||||
hasPartner: false,
|
||||
partnerName: 'TA',
|
||||
partnerDisplayId: '',
|
||||
partnerId: 0,
|
||||
partnerDetail: '',
|
||||
partnerAvatarUrl: '',
|
||||
partnerInitial: '?',
|
||||
@@ -322,6 +348,7 @@ Page({
|
||||
hasPartner: true,
|
||||
partnerName: partner.nickname || partner.username || 'TA',
|
||||
partnerDisplayId: partner.user_id || '',
|
||||
partnerId: Number(partner.id),
|
||||
partnerDetail: details.join(' · ') || 'TA的信息',
|
||||
partnerAvatarUrl: getImageUrl(partner.avatar),
|
||||
partnerInitial: (partner.nickname || partner.username || '?')[0],
|
||||
@@ -405,6 +432,11 @@ Page({
|
||||
}).catch(function() {});
|
||||
},
|
||||
|
||||
_syncProfileTabBar: function() {
|
||||
const tabBar = this.getTabBar && this.getTabBar();
|
||||
if (tabBar && tabBar.setHidden) tabBar.setHidden(this.data.showEditProfileModal || this.data.showEditPartnerModal);
|
||||
},
|
||||
|
||||
// 编辑资料
|
||||
showEditProfileModal: function() {
|
||||
var d = this.data;
|
||||
@@ -421,6 +453,7 @@ Page({
|
||||
editAvatarUrl: d.myAvatarUrl || '',
|
||||
editNicknameInitial: d.myInitial || '?'
|
||||
});
|
||||
this._syncProfileTabBar();
|
||||
},
|
||||
onEditGenderChange: function(e) {
|
||||
var idx = e.detail.value;
|
||||
@@ -430,6 +463,7 @@ Page({
|
||||
},
|
||||
hideEditProfileModal: function() {
|
||||
this.setData({ showEditProfileModal: false });
|
||||
this._syncProfileTabBar();
|
||||
},
|
||||
onEditNicknameInput: function(e) {
|
||||
this.setData({ editNickname: e.detail.value });
|
||||
@@ -559,9 +593,11 @@ Page({
|
||||
this.setData({ showPartnerDetailModal: false });
|
||||
},
|
||||
|
||||
// 查看TA资料(只读)
|
||||
// 编辑TA资料
|
||||
showEditPartnerModal: function() {
|
||||
const d = this.data;
|
||||
this._editPartnerId = d.partnerId;
|
||||
this._partnerOriginalStory = d.tempLoveStory;
|
||||
var rawGender = d.partnerGenderRaw || '';
|
||||
var genderText = rawGender === 'male' ? '男' : (rawGender === 'female' ? '女' : '未设置');
|
||||
// 从 partnerDetail 中解析年龄(格式:XX岁)
|
||||
@@ -570,7 +606,7 @@ Page({
|
||||
this.setData({
|
||||
showEditPartnerModal: true,
|
||||
editPartnerNickname: d.partnerName === 'TA' ? '' : d.partnerName,
|
||||
editPartnerGender: rawGender || 'male',
|
||||
editPartnerGender: rawGender || '',
|
||||
editPartnerGenderText: genderText,
|
||||
editPartnerAge: partnerAge,
|
||||
editPartnerBirthday: d.partnerBirthday || '',
|
||||
@@ -578,10 +614,65 @@ Page({
|
||||
editPartnerAvatarPath: '',
|
||||
editPartnerNameInitial: d.partnerInitial || '?'
|
||||
});
|
||||
this._syncProfileTabBar();
|
||||
},
|
||||
hideEditPartnerModal: function() {
|
||||
if (this.data.partnerSaving || this.data.partnerAvatarUploading) return;
|
||||
this.setData({ showEditPartnerModal: false });
|
||||
this._syncProfileTabBar();
|
||||
},
|
||||
onEditPartnerNicknameInput: function(e) {
|
||||
if (!this.data.partnerSaving) this.setData({editPartnerNickname:e.detail.value});
|
||||
},
|
||||
onEditPartnerGenderChange: function(e) {
|
||||
if (this.data.partnerSaving) return;
|
||||
const index = Number(e.detail.value);
|
||||
this.setData({editPartnerGender:['male','female'][index],editPartnerGenderText:['男','女'][index]});
|
||||
},
|
||||
onEditPartnerBirthdayInput: function(e) {
|
||||
if (this.data.partnerSaving) return;
|
||||
const birthday = e.detail.value;
|
||||
const birth = new Date(birthday), today = new Date();
|
||||
let age = today.getFullYear() - birth.getFullYear();
|
||||
if (today.getMonth() < birth.getMonth() || (today.getMonth() === birth.getMonth() && today.getDate() < birth.getDate())) age--;
|
||||
this.setData({editPartnerBirthday:birthday,editPartnerAge:String(Math.max(0,age))});
|
||||
},
|
||||
choosePartnerAvatar: function() {
|
||||
if (this.data.partnerSaving || this.data.partnerAvatarUploading || !this._editPartnerId) return;
|
||||
const partnerId = this._editPartnerId;
|
||||
wx.chooseImage({count:1,sizeType:['compressed'],sourceType:['album','camera'],success:async res=>{
|
||||
if (!this.data.showEditPartnerModal || partnerId !== this._editPartnerId) return;
|
||||
this.setData({partnerAvatarUploading:true});
|
||||
try {
|
||||
const uploaded = await api.uploadPartnerAvatar(res.tempFilePaths[0],partnerId);
|
||||
this.setData({editPartnerAvatarUrl:getImageUrl(uploaded.path),editPartnerAvatarPath:uploaded.path});
|
||||
} catch(error) { wx.showToast({title:error.error || '头像上传失败',icon:'none'}); }
|
||||
finally { this.setData({partnerAvatarUploading:false}); }
|
||||
}});
|
||||
},
|
||||
savePartnerProfile: async function() {
|
||||
const d = this.data;
|
||||
if (d.partnerSaving || d.partnerAvatarUploading) return;
|
||||
if (!d.hasPartner || !this._editPartnerId || this._editPartnerId !== d.partnerId) {
|
||||
wx.showToast({title:'绑定信息已变化,请重新打开资料',icon:'none'});return;
|
||||
}
|
||||
const nickname = d.editPartnerNickname.trim();
|
||||
if (!nickname) { wx.showToast({title:'请输入TA的昵称',icon:'none'});return; }
|
||||
this.setData({partnerSaving:true});
|
||||
try {
|
||||
const result = await api.updatePartnerProfile({partner_id:this._editPartnerId,nickname,gender:d.editPartnerGender,age:parseInt(d.editPartnerAge) || 0,birthday:d.editPartnerBirthday,avatar:d.editPartnerAvatarPath || undefined});
|
||||
if (!result || !result.success) throw result || {};
|
||||
if (d.coupleId && (d.tempLoveDate !== d.loveStartDate || d.tempLoveStory !== this._partnerOriginalStory)) {
|
||||
await api.updateCoupleInfo({love_start_date:d.tempLoveDate,love_story:d.tempLoveStory || ''});
|
||||
}
|
||||
this.setData({partnerSaving:false});
|
||||
this.hideEditPartnerModal();
|
||||
await this.loadData();
|
||||
wx.showToast({title:'TA的资料已保存',icon:'success'});
|
||||
} catch(error) { wx.showToast({title:error.error || '保存失败,请重试',icon:'none'}); }
|
||||
finally { this.setData({partnerSaving:false}); }
|
||||
},
|
||||
|
||||
// 加载解绑状态
|
||||
loadUnbindStatus: function() {
|
||||
var self = this;
|
||||
@@ -610,8 +701,8 @@ Page({
|
||||
} else {
|
||||
this.doRespondUnbind();
|
||||
}
|
||||
} else if (status === 'agreed') {
|
||||
wx.showToast({ title: '双方已同意解绑,正在处理中', icon: 'none' });
|
||||
} else if (status === 'cooling') {
|
||||
wx.showToast({ title: '正在72小时冷静期', icon: 'none' });
|
||||
wx.navigateTo({ url: '/pages/unbind/unbind' });
|
||||
} else {
|
||||
this.doRequestUnbind();
|
||||
@@ -622,7 +713,7 @@ Page({
|
||||
var self = this;
|
||||
wx.showModal({
|
||||
title: '💔 解除绑定',
|
||||
content: '发起解绑请求后,对方需要在72小时内确认同意。\n\n对方同意后,所有情侣记录将被永久删除(记忆日、日记、相册、纪念日、情话、歌曲、打卡),仅保留双方个人资料。\n\n你也可以在对方确认前撤销请求。',
|
||||
content: '对方同意后进入72小时冷静期,期间任一方可取消。到期自动解绑并永久删除共同数据;个人抽烟记录保留。',
|
||||
cancelText: '取消',
|
||||
confirmText: '发起解绑',
|
||||
confirmColor: '#FF4757',
|
||||
@@ -647,7 +738,7 @@ Page({
|
||||
var self = this;
|
||||
wx.showModal({
|
||||
title: '↩️ 撤销解绑',
|
||||
content: '确定要撤销解绑请求吗?对方将不会收到任何通知。',
|
||||
content: '取消后关系继续保留。确定取消解绑吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '撤销请求',
|
||||
confirmColor: '#FF6B9D',
|
||||
@@ -676,7 +767,7 @@ Page({
|
||||
if (res.tapIndex === 0) {
|
||||
wx.showModal({
|
||||
title: '⚠️ 确认同意',
|
||||
content: name + ' 发起了解绑请求。\n\n同意后,所有情侣记录将被永久删除(记忆日、日记、相册、歌曲、打卡),仅保留双方个人资料。\n\n此操作不可恢复!',
|
||||
content: name + ' 发起了解绑请求。同意后进入72小时冷静期,期间可取消;到期自动解绑并永久删除共同数据,个人抽烟记录保留。',
|
||||
cancelText: '取消',
|
||||
confirmText: '同意解绑',
|
||||
confirmColor: '#FF4757',
|
||||
@@ -685,15 +776,8 @@ Page({
|
||||
wx.showLoading({ title: '处理中...' });
|
||||
api.unbindRespond({ agree: true }).then(function() {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: '已同意解绑', icon: 'success' });
|
||||
wx.removeStorageSync('coupleId');
|
||||
wx.removeStorageSync('partnerId');
|
||||
wx.removeStorageSync('partnerName');
|
||||
wx.removeStorageSync('partnerAvatar');
|
||||
wx.removeStorageSync('petHasPet');
|
||||
setTimeout(function() {
|
||||
wx.reLaunch({ url: '/pages/index/index' });
|
||||
}, 1500);
|
||||
wx.showToast({ title: '已进入72小时冷静期', icon: 'none' });
|
||||
self.loadUnbindStatus();
|
||||
}).catch(function(err) {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: err.error || '操作失败', icon: 'none' });
|
||||
@@ -747,7 +831,7 @@ Page({
|
||||
|
||||
// 跳转前保存当前滚动位置
|
||||
_savePosBeforeLeave: function() {
|
||||
saveScrollPosition('profile', this.data.scrollTop);
|
||||
saveScrollPosition('profile', this._scrollTop == null ? this.data.scrollTop : this._scrollTop);
|
||||
},
|
||||
|
||||
// ===== 邀请绑定 =====
|
||||
@@ -796,9 +880,12 @@ Page({
|
||||
},
|
||||
|
||||
sendInvite: async function() {
|
||||
const { inviteTargetId, inviteMessage } = this.data;
|
||||
const inviteTargetId = String(this.data.inviteTargetId || '').trim();
|
||||
const { inviteMessage } = this.data;
|
||||
if (!inviteTargetId) { wx.showToast({ title: '请输入对方ID', icon: 'none' }); return; }
|
||||
if (inviteTargetId.length < 6) { wx.showToast({ title: 'ID为6位数字', icon: 'none' }); return; }
|
||||
if (!/^[0-9]{6}$/.test(inviteTargetId)) { wx.showToast({ title: 'ID为6位数字', icon: 'none' }); return; }
|
||||
// 邀请成功且对方接受时,需要通知邀请人,提前申请一次性订阅授权
|
||||
setTimeout(function() { requestSubscribe('notify').catch(function() {}); }, 1500);
|
||||
try {
|
||||
wx.showLoading({ title: '发送中...' });
|
||||
const res = await api.invitePartner({ target_user_id: inviteTargetId, message: inviteMessage });
|
||||
@@ -880,6 +967,7 @@ Page({
|
||||
|
||||
// ===== 分享绑定 =====
|
||||
onShareAppMessage: function() {
|
||||
if (!this.data.shareOutgoingCode) return {title:'邀请你绑定情侣', path:'/pages/profile/profile'};
|
||||
var displayId = wx.getStorageSync('displayId') || this.data.myDisplayId;
|
||||
var nick = wx.getStorageSync('nickname') || '我';
|
||||
if (!displayId) {
|
||||
@@ -888,22 +976,22 @@ Page({
|
||||
}
|
||||
return {
|
||||
title: nick + ' 邀请你绑定情侣 💕',
|
||||
path: '/pages/profile/profile?inviter=' + displayId,
|
||||
path: '/pages/profile/profile?inviter=' + displayId + '&invite_code=' + encodeURIComponent(this.data.shareOutgoingCode || ''),
|
||||
imageUrl: '/images/share.png'
|
||||
};
|
||||
},
|
||||
|
||||
// 从分享链接进入时处理邀请绑定
|
||||
handleShareInvite: function(inviterId) {
|
||||
handleShareInvite: function(inviterId, code) {
|
||||
if (!inviterId) return;
|
||||
this.setData({ shareInviterId: inviterId, showShareBindModal: true, shareDisagree: false });
|
||||
this.setData({ shareInviterId: inviterId, shareInviteCode: code || '', showShareBindModal: true, shareDisagree: false });
|
||||
},
|
||||
|
||||
acceptShareBind: async function() {
|
||||
var self = this;
|
||||
try {
|
||||
wx.showLoading({ title: '处理中...' });
|
||||
const res = await api.acceptShareInvite({ inviter_id: this.data.shareInviterId });
|
||||
const res = await api.acceptShareInvite({ inviter_id: this.data.shareInviterId, invite_code: this.data.shareInviteCode });
|
||||
wx.hideLoading();
|
||||
if (res.couple_id) {
|
||||
wx.setStorageSync('coupleId', res.couple_id);
|
||||
@@ -925,8 +1013,36 @@ Page({
|
||||
this.setData({ showShareBindModal: false });
|
||||
},
|
||||
|
||||
loadProfileStats: async function() {
|
||||
this.setData({statsLoading:true,statsError:false});
|
||||
const keys=['timeline','album','diary','songs','checkin','pet'];
|
||||
const results=await Promise.allSettled([
|
||||
api.timelineList(),api.albumList(),api.diaryList(),api.songsList(),api.checkinList(),
|
||||
(this.data.coupleId || wx.getStorageSync('coupleId')) ? api.petInfo() : Promise.resolve({has_pet:false})
|
||||
]);
|
||||
const counts={};let failed=false;
|
||||
results.forEach((result,index)=>{
|
||||
const key=keys[index];
|
||||
if(result.status !== 'fulfilled') { counts[key]='—';failed=true;return; }
|
||||
const value=result.value;
|
||||
if(key==='album') counts[key]=(Array.isArray(value)?value:[]).reduce((sum,album)=>sum+(Number(album.photo_count)||0),0);
|
||||
else if(key==='diary') counts[key]=Number(value && value.total)||0;
|
||||
else if(key==='checkin') counts[key]=value && value.checkin_log_total !== undefined ? Number(value.checkin_log_total)||0 : (Array.isArray(value)?value.length:(value && value.items || []).length);
|
||||
else if(key==='pet') counts[key]=value && value.has_pet && value.pet ? 'Lv.'+(Number(value.pet.level)||0) : '未领养';
|
||||
else counts[key]=Array.isArray(value)?value.length:0;
|
||||
});
|
||||
this.setData({statisticItems:this.data.statisticItems.map(item=>({...item,count:counts[item.key]===undefined?item.count:counts[item.key]})),statsLoading:false,statsError:failed});
|
||||
},
|
||||
openStatistic: function(e) {
|
||||
const routes={timeline:'timeline',album:'albums',diary:'diary',songs:'lovewords',checkin:'punch',pet:'pet',period:'period',settings:'settings'};
|
||||
const route=routes[e.currentTarget.dataset.key];
|
||||
if(!route)return;
|
||||
this._savePosBeforeLeave();
|
||||
wx.navigateTo({url:'/pages/'+route+'/'+route});
|
||||
},
|
||||
|
||||
// 页面跳转
|
||||
manageRelationship: function() { wx.navigateTo({ url: '/pages/unbind/unbind' }); },
|
||||
goToSettings: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/settings/settings' }); },
|
||||
goHome: function() { this._savePosBeforeLeave(); wx.redirectTo({ url: '/pages/index/index' }); }
|
||||
});
|
||||
goHome: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/index/index' }); }
|
||||
}, {"loadData": ["coupleInfo"], "loadProfileStats": ["timelineList","albumList","diaryList","songsList","checkinList","petInfo"]}));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"app-tabbar": "/components/app-tabbar/app-tabbar",
|
||||
"page-state": "/components/page-state/page-state"
|
||||
},
|
||||
"navigationStyle": "custom",
|
||||
|
||||
@@ -1,47 +1,56 @@
|
||||
<view class="app-shell" style="{{layoutStyle}}">
|
||||
<view class="app-shell j-handwritten-page" style="{{layoutStyle}}">
|
||||
<launch-transition />
|
||||
<!-- pages/profile/profile.wxml -->
|
||||
<view wx:if="{{!showEditProfileModal && !showEditPartnerModal}}" class="j-fixed-header"><view class="j-fixed-heading" style="height:{{barHeight}}px;padding-right:{{navSide}}px;"><text class="j-eyebrow">MY CORNER</text><text class="j-page-title">我的小角落</text></view></view>
|
||||
<view wx:if="{{!showEditProfileModal && !showEditPartnerModal}}" class="page-root" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
<scroll-view class="page-bg j-main-scroll" scroll-y show-scrollbar="{{false}}" bounces="{{true}}" bindscroll="onScroll" scroll-top="{{scrollTop}}">
|
||||
<view class="j-body">
|
||||
<view class="j-profile-summary" bindtap="showEditProfileModal"><view class="j-profile-info"><text class="j-profile-name">{{myNickname}}</text><text class="j-caption">ID {{myDisplayId}}</text><text class="j-caption">{{myGender}} {{myAge}} {{myBirthday}}</text><text class="j-link">编辑资料 →</text></view><view class="j-avatar"><image wx:if="{{myAvatarUrl}}" src="{{myAvatarUrl}}" mode="aspectFill" /><text wx:else>{{myInitial}}</text></view></view>
|
||||
<view class="j-profile-summary" bindtap="showEditProfileModal"><view class="j-profile-info"><text class="j-profile-name">{{myNickname}}</text><text class="j-caption">ID {{myDisplayId}}</text><text class="j-caption">{{myGender}} {{myAge}} {{myBirthday}}</text></view><view class="j-avatar"><image wx:if="{{myAvatarUrl}}" src="{{myAvatarUrl}}" mode="aspectFill" /><text wx:else>{{myInitial}}</text></view></view>
|
||||
<view class="j-section-head"><text>我们的关系</text></view>
|
||||
<view wx:if="{{bindingKnown && hasPartner}}" class="j-relation-card"><view class="j-row" bindtap="showEditPartnerModal"><view><text class="j-small-title">{{partnerName}}</text><text class="j-caption" wx:if="{{partnerDisplayId}}">ID {{partnerDisplayId}}</text><text class="j-caption">{{partnerDetail}}</text><text class="j-link">查看TA的资料 →</text></view><view class="j-avatar"><image wx:if="{{partnerAvatarUrl}}" src="{{partnerAvatarUrl}}" mode="aspectFill" /><text wx:else>{{partnerInitial}}</text></view></view><view class="j-profile-setting" bindtap="manageRelationship"><image src="/images/heart.svg" /><view><text class="j-small-title">关系管理</text><text class="j-caption">绑定信息与解绑</text></view><text class="j-link">›</text></view></view>
|
||||
<view wx:if="{{bindingKnown && hasPartner}}" class="j-relation-card" bindtap="showEditPartnerModal"><view class="j-row"><view><text class="j-small-title">{{partnerName}}</text><text class="j-caption" wx:if="{{partnerDisplayId}}">ID {{partnerDisplayId}}</text><text class="j-caption">{{partnerDetail}}</text></view><view class="j-avatar"><image wx:if="{{partnerAvatarUrl}}" src="{{partnerAvatarUrl}}" mode="aspectFill" /><text wx:else>{{partnerInitial}}</text></view></view></view>
|
||||
<view wx:elif="{{bindingKnown}}" class="j-invite" bindtap="showInvitePanel"><view><text class="j-small-title">另一半还没有加入</text><text class="j-caption">邀请TA,共写这本生活手账</text></view><text class="j-outline-label">绑定情侣</text></view>
|
||||
<page-state wx:elif="{{bindingError}}" kind="error" title="绑定状态加载失败" action="重新加载" bind:action="loadData" /><page-state wx:else kind="loading" title="正在获取绑定状态" />
|
||||
<view class="j-profile-setting" bindtap="goToSettings"><image src="/images/settings.svg" /><view><text class="j-small-title">外观与通知</text><text class="j-caption">背景、通知与账号设置</text></view><text class="j-link">›</text></view>
|
||||
<view class="profile-statistics">
|
||||
<view class="profile-stat-grid">
|
||||
<view class="profile-stat-item" wx:for="{{statisticItems}}" wx:key="key" data-key="{{item.key}}" bindtap="openStatistic" hover-class="profile-stat-pressed">
|
||||
<view class="profile-stat-icon {{item.color}}"><image src="/images/{{item.icon}}.svg" mode="aspectFit" /></view>
|
||||
<view class="profile-stat-label"><text>{{item.label}}</text><text wx:if="{{item.count !== ''}}" class="profile-stat-count">{{item.count}}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{statsError}}" class="profile-stats-retry" bindtap="loadProfileStats">部分统计未加载,点击重试</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<app-tabbar />
|
||||
|
||||
|
||||
<view wx:if="{{showEditProfileModal}}" class="edit-root" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
<view class="edit-nav"><view class="back-btn" bindtap="hideEditProfileModal"><image class="back-icon" src="/images/back.svg" mode="aspectFit" /></view></view>
|
||||
<view class="edit-body" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
<scroll-view class="edit-body" scroll-y show-scrollbar="{{false}}" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
<view class="edit-avatar-wrap" bindtap="chooseAvatar"><image wx:if="{{editAvatarUrl}}" src="{{editAvatarUrl}}" mode="aspectFill" class="edit-avatar-img"></image><text wx:else class="edit-avatar-placeholder">{{editNicknameInitial || '?'}}</text><text class="edit-avatar-hint">点击更换头像</text></view>
|
||||
<text class="edit-id">我的ID: {{myDisplayId}}</text>
|
||||
<view class="input-group"><label>昵称</label><input class="input" placeholder="你的昵称" value="{{editNickname}}" bindinput="onEditNicknameInput" /></view>
|
||||
<view class="input-group"><label>昵称</label><input placeholder-class="handwritten-placeholder" class="input" placeholder="你的昵称" value="{{editNickname}}" bindinput="onEditNicknameInput" /></view>
|
||||
<view class="input-group"><label>性别</label><picker mode="selector" range="{{genderOptions}}" bindchange="onEditGenderChange"><view class="input">{{editGenderText || '请选择'}}</view></picker></view>
|
||||
<view class="input-group"><label>生日</label><picker mode="date" value="{{editBirthday}}" bindchange="onEditBirthdayInput"><view class="input">{{editBirthday || '请选择日期'}}</view></picker><text style="font-size:24rpx;color:#B74768;margin-top:8rpx;display:block;" wx:if="{{editAge}}">自动计算:{{editAge}}岁</text></view>
|
||||
<view class="input-group" wx:if="{{hasPartner}}"><label>恋爱纪念日</label><picker mode="date" value="{{tempLoveDate}}" bindchange="onLoveDateInput"><view class="input">{{tempLoveDate || '设置你们在一起的日期'}}</view></picker></view>
|
||||
<view class="input-group" wx:if="{{hasPartner}}"><label>恋爱故事</label><textarea class="input" style="min-height:120rpx;" placeholder="记录你们的故事..." value="{{tempLoveStory}}" bindinput="onLoveStoryInput"></textarea></view>
|
||||
</view>
|
||||
<view class="input-group" wx:if="{{hasPartner}}"><label>恋爱故事</label><textarea placeholder-class="handwritten-placeholder" class="input" style="min-height:120rpx;" placeholder="记录你们的故事..." value="{{tempLoveStory}}" bindinput="onLoveStoryInput"></textarea></view>
|
||||
</scroll-view>
|
||||
<view class="edit-footer"><button class="btn btn-primary" bindtap="saveProfile" hover-class="none">保存</button></view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{showEditPartnerModal}}" class="edit-root partner-view" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
<view class="edit-nav"><view class="back-btn" bindtap="hideEditPartnerModal"><image class="back-icon" src="/images/back.svg" mode="aspectFit" /></view><text class="partner-nav-title">TA的资料</text></view>
|
||||
<view class="edit-body" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
<view class="edit-avatar-wrap"><image wx:if="{{editPartnerAvatarUrl}}" src="{{editPartnerAvatarUrl}}" mode="aspectFill" class="edit-avatar-img"></image><text wx:else class="edit-avatar-placeholder">{{editPartnerNameInitial || '?'}}</text></view>
|
||||
<view wx:if="{{showEditPartnerModal}}" class="edit-root partner-edit" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
<view class="edit-nav"><view class="back-btn" bindtap="hideEditPartnerModal"><image class="back-icon" src="/images/back.svg" mode="aspectFit" /></view><text class="partner-nav-title">编辑TA的资料</text></view>
|
||||
<scroll-view class="edit-body" scroll-y show-scrollbar="{{false}}">
|
||||
<view class="edit-avatar-wrap" bindtap="choosePartnerAvatar"><image wx:if="{{editPartnerAvatarUrl}}" src="{{editPartnerAvatarUrl}}" mode="aspectFill" class="edit-avatar-img" /><text wx:else class="edit-avatar-placeholder">{{editPartnerNameInitial || '?'}}</text><text class="edit-avatar-hint">{{partnerAvatarUploading ? '正在上传头像…' : '点击更换头像'}}</text></view>
|
||||
<text class="edit-id" wx:if="{{partnerDisplayId}}">TA的ID: {{partnerDisplayId}}</text>
|
||||
<view class="input-group readonly"><label>昵称</label><view class="input readonly-text">{{editPartnerNickname || '未设置'}}</view></view>
|
||||
<view class="input-group readonly"><label>性别</label><view class="input readonly-text">{{editPartnerGenderText || '-'}}</view></view>
|
||||
<view class="input-group readonly"><label>生日</label><view class="input readonly-text">{{editPartnerBirthday || '-'}}</view></view>
|
||||
<view class="input-group readonly"><label>恋爱纪念日</label><view class="input readonly-text">{{tempLoveDate || '未设置'}}</view></view>
|
||||
<view class="input-group readonly"><label>恋爱故事</label><view class="input readonly-text" style="min-height:60rpx;">{{tempLoveStory || '未设置'}}</view></view>
|
||||
</view>
|
||||
<view class="input-group"><label>昵称</label><input placeholder-class="handwritten-placeholder" class="input" value="{{editPartnerNickname}}" placeholder="TA的昵称" bindinput="onEditPartnerNicknameInput" disabled="{{partnerSaving}}" /></view>
|
||||
<view class="input-group"><label>性别</label><picker mode="selector" range="{{genderOptions}}" bindchange="onEditPartnerGenderChange" disabled="{{partnerSaving}}"><view class="input">{{editPartnerGenderText || '请选择'}}</view></picker></view>
|
||||
<view class="input-group"><label>生日</label><picker mode="date" value="{{editPartnerBirthday}}" bindchange="onEditPartnerBirthdayInput" disabled="{{partnerSaving}}"><view class="input">{{editPartnerBirthday || '请选择日期'}}</view></picker><text class="edit-avatar-hint" wx:if="{{editPartnerAge}}">自动计算:{{editPartnerAge}}岁</text></view>
|
||||
<view class="input-group"><label>恋爱纪念日</label><picker mode="date" value="{{tempLoveDate}}" bindchange="onLoveDateInput" disabled="{{partnerSaving}}"><view class="input">{{tempLoveDate || '设置你们在一起的日期'}}</view></picker></view>
|
||||
<view class="input-group"><label>恋爱故事</label><textarea placeholder-class="handwritten-placeholder" class="input" style="min-height:120rpx;" value="{{tempLoveStory}}" placeholder="记录你们的故事…" bindinput="onLoveStoryInput" disabled="{{partnerSaving}}" /></view>
|
||||
</scroll-view>
|
||||
<view class="edit-footer"><button class="btn btn-primary" bindtap="savePartnerProfile" loading="{{partnerSaving}}" disabled="{{partnerSaving || partnerAvatarUploading}}" hover-class="none">保存</button></view>
|
||||
</view>
|
||||
|
||||
<!-- ===== 邀请绑定面板 ===== -->
|
||||
@@ -62,7 +71,7 @@
|
||||
|
||||
<view class="input-group">
|
||||
<label>TA的ID</label>
|
||||
<input class="input" type="tel" placeholder="输入对方的6位ID" value="{{inviteTargetId}}" bindinput="onInviteTargetIdInput" maxlength="6" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" type="tel" placeholder="输入对方的6位ID" value="{{inviteTargetId}}" bindinput="onInviteTargetIdInput" maxlength="6" />
|
||||
</view>
|
||||
<button class="btn btn-primary" bindtap="sendInvite" hover-class="none">
|
||||
<text>💕</text> 发送邀请
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/profile/profile.wxss */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
.container { padding-top: 16rpx; padding-bottom: 300rpx; }
|
||||
.page-root { height: 100vh; overflow: hidden; }
|
||||
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
|
||||
@@ -301,8 +301,67 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
/* TA 的资料仅供查看,不预留编辑操作区。 */
|
||||
.partner-view .edit-body { min-height: 0; padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); }
|
||||
.partner-view .edit-nav { position: relative; }
|
||||
/* TA 资料编辑页的标题与滚动区域。 */
|
||||
.partner-edit .edit-body { min-height: 0; padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); }
|
||||
|
||||
.partner-nav-title { position: absolute; left: 120rpx; right: 120rpx; text-align: center; font-size: 32rpx; font-weight: 600; color: #342F2B; pointer-events: none; }
|
||||
.partner-view .readonly-text { white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.partner-edit .readonly-text { white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
|
||||
/* 编辑内容独立滚动,底部保存区始终保留空间。 */
|
||||
.edit-root > .edit-body { flex:1; height:0; min-height:0; box-sizing:border-box; padding-bottom:24rpx; }
|
||||
.edit-root > .edit-footer { flex-shrink:0; box-sizing:border-box; }
|
||||
.edit-footer > .btn { width:100%; margin:0; }
|
||||
|
||||
/* 个人页八宫格统计入口。 */
|
||||
.profile-statistics { margin-top:32rpx; padding:28rpx 16rpx; background:#FFFDF8; border:1rpx solid #F1DCE4; border-radius:32rpx; }
|
||||
.profile-stat-grid { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:28rpx 6rpx; }
|
||||
.profile-stat-item { min-width:0; display:flex; flex-direction:column; align-items:center; padding:8rpx 0; }
|
||||
.profile-stat-icon { width:94rpx; height:94rpx; border-radius:30rpx; display:flex; align-items:center; justify-content:center; }
|
||||
.profile-stat-icon image { width:58rpx; height:58rpx; }
|
||||
.profile-stat-icon.pink { background:#FCE5ED; }.profile-stat-icon.yellow { background:#FFF3DC; }.profile-stat-icon.mint { background:#E5F5ED; }.profile-stat-icon.blue { background:#EAF0FD; }.profile-stat-icon.purple { background:#F1EAFB; }.profile-stat-icon.gray { background:#F0EFF2; }
|
||||
.profile-stat-label { display:flex; flex-wrap:wrap; justify-content:center; align-items:baseline; gap:6rpx; margin-top:14rpx; font-family:'Kaiti SC','STKaiti','KaiTi',cursive; font-size:25rpx; line-height:1.5; color:#64424D; }
|
||||
.profile-stat-count { font-family:-apple-system,BlinkMacSystemFont,sans-serif; font-size:19rpx; color:#A39386; }
|
||||
.profile-stat-pressed { opacity:.65; }
|
||||
.profile-stats-retry { text-align:center; color:#B74768; font-size:22rpx; padding:24rpx 0 0; }
|
||||
|
||||
/* 本人与对方资料卡共用完整样式,整卡点击编辑。 */
|
||||
.j-body > .j-profile-summary,
|
||||
.j-body > .j-relation-card {
|
||||
height:216rpx;
|
||||
box-sizing:border-box;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
padding:24rpx 30rpx;
|
||||
margin:20rpx 0 24rpx;
|
||||
background:#FFFDF8;
|
||||
border:1rpx solid #E9DDD0;
|
||||
border-radius:12rpx 40rpx 12rpx 40rpx;
|
||||
box-shadow:none;
|
||||
}
|
||||
.j-relation-card > .j-row { width:100%; gap:30rpx; }
|
||||
.j-profile-summary .j-profile-name,
|
||||
.j-relation-card .j-small-title {
|
||||
display:block;
|
||||
font-family:'Kaiti SC','STKaiti','KaiTi',cursive;
|
||||
font-size:30rpx;
|
||||
font-weight:400;
|
||||
font-style:normal;
|
||||
line-height:1.6;
|
||||
color:#64424D;
|
||||
overflow:hidden;
|
||||
white-space:nowrap;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
.j-profile-summary .j-avatar,
|
||||
.j-relation-card .j-avatar {
|
||||
width:96rpx;
|
||||
height:96rpx;
|
||||
flex-shrink:0;
|
||||
box-sizing:border-box;
|
||||
border:6rpx solid #FFFDF8;
|
||||
font-size:32rpx;
|
||||
}
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -22,6 +22,7 @@ Page({
|
||||
titleText: '',
|
||||
btnText: '',
|
||||
form: {},
|
||||
editorContent: '',
|
||||
tempImages: [],
|
||||
coverPreview: '',
|
||||
coverPath: '',
|
||||
@@ -95,6 +96,7 @@ Page({
|
||||
}
|
||||
}
|
||||
patch.form = form;
|
||||
patch.editorContent = form.content || '';
|
||||
patch.contentLength = (form.content || '').length;
|
||||
}
|
||||
if (typeof data.repeatOptionIdx === 'number') patch.repeatOptionIdx = data.repeatOptionIdx;
|
||||
@@ -109,8 +111,23 @@ Page({
|
||||
} catch(e) {}
|
||||
},
|
||||
|
||||
// 保存草稿到本地(输入时自动触发)
|
||||
// 连续输入只保留一个草稿写入任务,避免每个按键同步写存储。
|
||||
_saveDraft: function() {
|
||||
this._draftCleared = false;
|
||||
clearTimeout(this._draftTimer);
|
||||
this._draftTimer = setTimeout(() => {
|
||||
this._draftTimer = null;
|
||||
this._persistDraft();
|
||||
}, 500);
|
||||
},
|
||||
_flushDraft: function() {
|
||||
clearTimeout(this._draftTimer);
|
||||
this._draftTimer = null;
|
||||
if (!this._draftCleared) this._persistDraft();
|
||||
},
|
||||
onHide: function() { this._flushDraft(); },
|
||||
onUnload: function() { this._flushDraft(); },
|
||||
_persistDraft: function() {
|
||||
try {
|
||||
var type = this.data.type;
|
||||
if (this.data.editId) return; // 编辑模式不保存草稿
|
||||
@@ -131,6 +148,9 @@ Page({
|
||||
|
||||
// 发布成功后清除草稿
|
||||
_clearDraft: function() {
|
||||
clearTimeout(this._draftTimer);
|
||||
this._draftTimer = null;
|
||||
this._draftCleared = true;
|
||||
try {
|
||||
var type = this.data.type;
|
||||
wx.removeStorageSync('publishDraft_' + type);
|
||||
@@ -166,6 +186,7 @@ Page({
|
||||
}
|
||||
this.setData({
|
||||
form: form,
|
||||
editorContent: form.content || '',
|
||||
repeatOptionIdx: repeatOptionIdx,
|
||||
bgImageUrl: bgUrl,
|
||||
bgImagePath: bgPath
|
||||
@@ -209,7 +230,7 @@ Page({
|
||||
} else {
|
||||
form = { title: '', date: '', location: '', content: '', repeat_interval: 0, repeat_unit: '' };
|
||||
}
|
||||
this.setData({ form: form, tempImages: [], repeatOptionIdx: 0, contentLength: (form.content || '').length });
|
||||
this.setData({ form: form, editorContent: form.content || '', tempImages: [], repeatOptionIdx: 0, contentLength: (form.content || '').length });
|
||||
},
|
||||
|
||||
// ===== 自动定位(日记 / 相册发布时自动获取经纬度)=====
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<block wx:if="{{type == 'timeline' || type == 'checkin' || type == 'album'}}">
|
||||
<view class="input-group">
|
||||
<label>{{type == 'album' ? '相册名称' : (type == 'timeline' ? '事件内容' : '标题')}}</label>
|
||||
<input class="input" placeholder="{{type == 'album' ? '例如:我们的旅行' : (type == 'checkin' ? '例如:吃饭、睡觉、运动' : '例如:第一次约会')}}" value="{{form.title}}" bindinput="onTitleInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="{{type == 'album' ? '例如:我们的旅行' : (type == 'checkin' ? '例如:吃饭、睡觉、运动' : '例如:第一次约会')}}" value="{{form.title}}" bindinput="onTitleInput" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
@@ -44,11 +44,11 @@
|
||||
<block wx:if="{{type == 'timeline'}}">
|
||||
<view class="input-group">
|
||||
<label>事件地点(选填)</label>
|
||||
<input class="input" placeholder="例如:西湖边" value="{{form.location}}" bindinput="onLocationInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="例如:西湖边" value="{{form.location}}" bindinput="onLocationInput" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>备注(选填)</label>
|
||||
<textarea class="input textarea" placeholder="记录一些想说的话..." value="{{form.content}}" bindinput="onContentInput" auto-height maxlength="500" />
|
||||
<textarea placeholder-class="handwritten-placeholder" class="input textarea" placeholder="记录一些想说的话..." value="{{editorContent}}" bindinput="onContentInput" auto-height maxlength="500" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>重复选项(选填)</label>
|
||||
@@ -72,7 +72,7 @@
|
||||
<!-- ===== 相册(描述 + 封面)===== -->
|
||||
<view class="input-group" wx:if="{{type == 'album'}}">
|
||||
<label>描述</label>
|
||||
<input class="input" placeholder="简单描述一下这个相册" value="{{form.desc}}" bindinput="onDescInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="简单描述一下这个相册" value="{{form.desc}}" bindinput="onDescInput" />
|
||||
</view>
|
||||
<view class="input-group" wx:if="{{type == 'album'}}">
|
||||
<label>封面图片</label>
|
||||
@@ -92,7 +92,7 @@
|
||||
<!-- 标题 -->
|
||||
<view class="input-group">
|
||||
<label>标题(选填)</label>
|
||||
<input class="input diary-title-input" placeholder="给日记起个标题..." value="{{form.title}}" bindinput="onTitleInput" maxlength="100" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input diary-title-input" placeholder="给日记起个标题..." value="{{form.title}}" bindinput="onTitleInput" maxlength="100" />
|
||||
</view>
|
||||
|
||||
<!-- 内容编辑器(支持换行) -->
|
||||
@@ -101,7 +101,7 @@
|
||||
<label>内容</label>
|
||||
<text class="char-count {{contentLength > 5000 ? 'char-warn' : ''}}">{{contentLength}}/5000</text>
|
||||
</view>
|
||||
<textarea class="input textarea diary-editor" placeholder="今天发生了什么有趣的事呢? 写下你的心情和感受吧..." value="{{form.content}}" bindinput="onContentInput" maxlength="5000" auto-height />
|
||||
<textarea placeholder-class="handwritten-placeholder" class="input textarea diary-editor" placeholder="今天发生了什么有趣的事呢?写下你的心情和感受吧…" value="{{editorContent}}" bindinput="onContentInput" maxlength="{{5000}}" auto-height="{{false}}" cursor-spacing="24" />
|
||||
</view>
|
||||
|
||||
<!-- 图片选择(可删除) -->
|
||||
@@ -129,15 +129,15 @@
|
||||
<block wx:if="{{type == 'song'}}">
|
||||
<view class="input-group">
|
||||
<label>歌曲名</label>
|
||||
<input class="input" placeholder="例如:小幸运" value="{{form.song_name}}" bindinput="onTitleInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="例如:小幸运" value="{{form.song_name}}" bindinput="onTitleInput" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>歌手</label>
|
||||
<input class="input" placeholder="例如:田馥甄" value="{{form.artist}}" bindinput="onArtistInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="例如:田馥甄" value="{{form.artist}}" bindinput="onArtistInput" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>备注(选填)</label>
|
||||
<input class="input" placeholder="一句话,限60字" maxlength="60" value="{{form.note}}" bindinput="onNoteInput" />
|
||||
<input placeholder-class="handwritten-placeholder" class="input" placeholder="一句话,限60字" maxlength="60" value="{{form.note}}" bindinput="onNoteInput" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@import '../../styles/common.wxss';
|
||||
|
||||
.container { padding: 84rpx 32rpx 300rpx; }
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
|
||||
/* ===== 输入框美化 ===== */
|
||||
.input-group { margin-bottom: 36rpx; }
|
||||
@@ -206,3 +206,10 @@ page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
/* 长正文在输入框内滚动,避免输入时整页不断增高。 */
|
||||
textarea.diary-editor { height:420rpx; min-height:420rpx; box-sizing:border-box; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/punch/punch.js - 情侣打卡
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loadError: false,
|
||||
checkins: [],
|
||||
@@ -20,7 +21,11 @@ Page({
|
||||
},
|
||||
onShow: function() { this.loadCoupleInfo(); this.loadData(); },
|
||||
|
||||
onSmokingUpdated: function() { this.loadData(); },
|
||||
|
||||
onPullDownRefresh: async function() {
|
||||
const smoking = this.selectComponent && this.selectComponent("#smoking");
|
||||
if (smoking) await smoking.load(true);
|
||||
await this.loadCoupleInfo();
|
||||
await this.loadData();
|
||||
wx.stopPullDownRefresh();
|
||||
@@ -29,7 +34,7 @@ Page({
|
||||
// 从接口加载情侣头像和名称
|
||||
loadCoupleInfo: function() {
|
||||
var self = this;
|
||||
api.coupleInfo().then(function(info) {
|
||||
return api.coupleInfo().then(function(info) {
|
||||
self._applyCoupleInfo(info);
|
||||
}).catch(function(e) { console.error(e); });
|
||||
},
|
||||
@@ -52,7 +57,7 @@ Page({
|
||||
loadData: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
const [res, stats] = await Promise.all([
|
||||
api.checkinList(),
|
||||
@@ -112,6 +117,6 @@ Page({
|
||||
},
|
||||
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
}, {"loadData": ["checkinList", "checkinStats", "coupleInfo"]}));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"app-nav": "/components/app-nav/app-nav",
|
||||
"page-state": "/components/page-state/page-state"
|
||||
"page-state": "/components/page-state/page-state",
|
||||
"smoking-card": "/components/smoking-card/smoking-card"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<smoking-card id="smoking" bind:updated="onSmokingUpdated" />
|
||||
|
||||
<!-- 打卡列表 -->
|
||||
<view class="checkin-cards" wx:if="{{!loading && !loadError && checkins.length > 0}}">
|
||||
<view class="checkin-card" wx:for="{{checkins}}" wx:key="id" bindtap="openDetail" data-index="{{index}}" bindlongpress="deleteCheckin" data-id="{{item.id}}">
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
@import '../../styles/design.wxss';
|
||||
@import '../../styles/journal.wxss';
|
||||
@import '../../styles/companion.wxss';
|
||||
.punch-banner-card { background:#F1EBE2; border:1rpx solid #E9DDD0; padding:28rpx; border-radius:16rpx; margin-bottom:32rpx; }
|
||||
.punch-banner-card { background:#FFF7FA; border:1rpx solid #F1DCE4; padding:28rpx; border-radius:16rpx; margin-bottom:32rpx; }
|
||||
.promise-summary-head { display:flex; justify-content:space-between; align-items:center; gap:16rpx; margin-bottom:24rpx; }
|
||||
.promise-avatars { display:flex; padding-right:8rpx; }.banner-avatar-circle { width:44rpx; height:44rpx; border-radius:50%; overflow:hidden; border:3rpx solid #F1EBE2; background:#F4E2E3; color:#943C58; font-size:20rpx; display:flex; align-items:center; justify-content:center; }.banner-avatar-circle + .banner-avatar-circle { margin-left:-8rpx; }.banner-avatar-circle image { width:100%; height:100%; }
|
||||
.banner-stat-grid { display:flex; gap:10rpx; }.banner-stat-card { flex:1; display:flex; flex-direction:column; align-items:flex-start; border-right:1rpx solid #DFD2C3; padding-left:20rpx; }.banner-stat-card:first-child { padding-left:0; }.banner-stat-card:last-child { border:0; }
|
||||
.promise-avatars { display:flex; padding-right:8rpx; }.banner-avatar-circle { width:44rpx; height:44rpx; border-radius:50%; overflow:hidden; border:3rpx solid #FFF7FA; background:#F4E2E3; color:#943C58; font-size:20rpx; display:flex; align-items:center; justify-content:center; }.banner-avatar-circle + .banner-avatar-circle { margin-left:-8rpx; }.banner-avatar-circle image { width:100%; height:100%; }
|
||||
.banner-stat-grid { display:flex; gap:10rpx; }.banner-stat-card { flex:1; display:flex; flex-direction:column; align-items:flex-start; border-right:1rpx solid #F1DCE4; padding-left:20rpx; }.banner-stat-card:first-child { padding-left:0; }.banner-stat-card:last-child { border:0; }
|
||||
.stat-num { font-size:44rpx; line-height:1.2; font-weight:500; color:#943C58; }.stat-label { color:#85776D; font-size:21rpx; line-height:1.5; margin-top:8rpx; }
|
||||
.checkin-cards { display:flex; flex-direction:column; gap:20rpx; }
|
||||
.checkin-card:active { opacity:.8; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -58,7 +58,8 @@ Page({
|
||||
},
|
||||
|
||||
goBind: function() {
|
||||
wx.redirectTo({ url: '/pages/profile/profile?bind=1' });
|
||||
getApp().globalData.openProfileBind = true;
|
||||
wx.switchTab({ url: '/pages/profile/profile' });
|
||||
},
|
||||
|
||||
// 加载解绑状态
|
||||
@@ -240,8 +241,8 @@ Page({
|
||||
// 对方:同意或拒绝
|
||||
this.doRespondUnbind();
|
||||
}
|
||||
} else if (status === 'agreed') {
|
||||
wx.showToast({ title: '双方已同意解绑,正在处理中', icon: 'none' });
|
||||
} else if (status === 'cooling') {
|
||||
wx.showToast({ title: '正在72小时冷静期', icon: 'none' });
|
||||
wx.navigateTo({ url: '/pages/unbind/unbind' });
|
||||
} else {
|
||||
// 无解绑请求或已结束:发起新解绑
|
||||
@@ -254,7 +255,7 @@ Page({
|
||||
var self = this;
|
||||
wx.showModal({
|
||||
title: '💔 解除绑定',
|
||||
content: '发起解绑请求后,对方需要在72小时内确认同意。\n\n对方同意后,所有情侣记录将被永久删除(记忆日、日记、相册、纪念日、情话、歌曲、打卡),仅保留双方个人资料。\n\n你也可以在对方确认前撤销请求。',
|
||||
content: '对方同意后进入72小时冷静期,期间任一方可取消。到期自动解绑并永久删除共同数据;个人抽烟记录保留。',
|
||||
cancelText: '取消',
|
||||
confirmText: '发起解绑',
|
||||
confirmColor: '#FF4757',
|
||||
@@ -280,7 +281,7 @@ Page({
|
||||
var self = this;
|
||||
wx.showModal({
|
||||
title: '↩️ 撤销解绑',
|
||||
content: '确定要撤销解绑请求吗?对方将不会收到任何通知。',
|
||||
content: '取消后关系继续保留。确定取消解绑吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '撤销请求',
|
||||
confirmColor: '#FF6B9D',
|
||||
@@ -311,7 +312,7 @@ Page({
|
||||
// 同意
|
||||
wx.showModal({
|
||||
title: '⚠️ 确认同意',
|
||||
content: name + ' 发起了解绑请求。\n\n同意后,所有情侣记录将被永久删除(记忆日、日记、相册、歌曲、打卡),仅保留双方个人资料。\n\n此操作不可恢复!',
|
||||
content: name + ' 发起了解绑请求。同意后进入72小时冷静期,期间可取消;到期自动解绑并永久删除共同数据,个人抽烟记录保留。',
|
||||
cancelText: '取消',
|
||||
confirmText: '同意解绑',
|
||||
confirmColor: '#FF4757',
|
||||
@@ -320,15 +321,9 @@ Page({
|
||||
wx.showLoading({ title: '处理中...' });
|
||||
api.unbindRespond({ agree: true }).then(function() {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: '已同意解绑', icon: 'success' });
|
||||
// 清除本地缓存
|
||||
wx.removeStorageSync('coupleId');
|
||||
wx.removeStorageSync('partnerId');
|
||||
wx.removeStorageSync('partnerName');
|
||||
wx.removeStorageSync('partnerAvatar');
|
||||
setTimeout(function() {
|
||||
wx.reLaunch({ url: '/pages/index/index' });
|
||||
}, 1500);
|
||||
wx.showToast({ title: '已进入72小时冷静期', icon: 'none' });
|
||||
self.loadUnbindStatus();
|
||||
wx.navigateTo({ url: '/pages/unbind/unbind' });
|
||||
}).catch(function(err) {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: err.error || '操作失败', icon: 'none' });
|
||||
@@ -365,6 +360,6 @@ Page({
|
||||
|
||||
// 导航
|
||||
goToHistory: function() { wx.navigateTo({ url: '/pages/history/history' }); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goToProfile: function() { wx.navigateBack(); },
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<view class="app-shell app-shell--secondary" style="{{layoutStyle}}">
|
||||
<view class="app-shell app-shell--secondary j-handwritten-page" style="{{layoutStyle}}">
|
||||
<launch-transition />
|
||||
<!-- pages/settings/settings.wxml -->
|
||||
<app-nav title="设置" back="{{true}}" />
|
||||
@@ -53,10 +53,12 @@
|
||||
<view class="setting-info">
|
||||
<view wx:if="{{unbindStatus == 'pending' && unbindIsRequester}}" class="j-small-title" style="color:#FF9800;">取消解绑请求</view>
|
||||
<view wx:elif="{{unbindStatus == 'pending' && !unbindIsRequester}}" class="j-small-title" style="color:#FF4757;">待回应的解绑请求</view>
|
||||
<view wx:elif="{{unbindStatus == 'cooling'}}" class="j-small-title">72小时冷静期</view>
|
||||
<view wx:else class="j-small-title" style="color:#FF4757;">解除绑定</view>
|
||||
<view wx:if="{{unbindStatus == 'pending' && unbindIsRequester}}" class="j-caption">点击撤销已发起的请求</view>
|
||||
<view wx:elif="{{unbindStatus == 'pending' && !unbindIsRequester}}" class="j-caption">{{unbindRequesterName}} 发起了解绑</view>
|
||||
<view wx:else class="j-caption">发起解绑需对方确认</view>
|
||||
<view wx:elif="{{unbindStatus == 'cooling'}}" class="j-caption">点击查看倒计时或取消解绑</view>
|
||||
<view wx:else class="j-caption">同意后进入72小时冷静期</view>
|
||||
</view>
|
||||
<text class="j-link">›</text>
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/settings/settings.wxss */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
.container { padding-top: 16rpx; padding-bottom: 300rpx; }
|
||||
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
|
||||
|
||||
@@ -344,3 +344,18 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
.info-footer { flex-shrink:0; padding:20rpx 32rpx; border-top:1rpx solid #E9DDD0; }
|
||||
.info-dismiss { margin:0; width:100%; padding:0; line-height:76rpx; border-radius:16rpx; background:#B74768; color:#FFFDF8; font-size:28rpx; }
|
||||
.info-dismiss::after { border:0; }
|
||||
|
||||
/* 设置分组和弹窗标题延续主页面的端正手写风格。 */
|
||||
.section-heading,
|
||||
.info-title,
|
||||
.modal-subtitle,
|
||||
.about-block-title {
|
||||
font-family:'Kaiti SC','STKaiti','KaiTi',cursive;
|
||||
font-weight:400;
|
||||
font-style:normal;
|
||||
letter-spacing:1rpx;
|
||||
}
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
Page({
|
||||
data:{},
|
||||
onLoad(){this.setData(getLayout());},
|
||||
onResize(){this.setData(getLayout());},
|
||||
async onPullDownRefresh(){try{const card=this.selectComponent('#smoking');if(card)await card.load(true);}finally{wx.stopPullDownRefresh();}}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{"navigationStyle":"custom","enablePullDownRefresh":true,"usingComponents":{"app-nav":"/components/app-nav/app-nav","smoking-card":"/components/smoking-card/smoking-card"}}
|
||||
@@ -0,0 +1 @@
|
||||
<view class="app-shell app-shell--secondary" style="{{layoutStyle}}"><launch-transition /><app-nav title="抽烟记录" back="{{true}}" /><scroll-view class="page-bg" scroll-y show-scrollbar="{{false}}"><view class="c-body"><smoking-card id="smoking" detailed="{{true}}" /></view></scroll-view></view>
|
||||
@@ -0,0 +1,7 @@
|
||||
@import '../../styles/common.wxss';
|
||||
@import '../../styles/design.wxss';
|
||||
@import '../../styles/companion.wxss';
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
@@ -1,3 +1,4 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/timeline-detail/timeline-detail.js
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
@@ -57,7 +58,7 @@ function calcNearestRepeatDate(eventDate, today, interval, unit) {
|
||||
return new Date(eventDate);
|
||||
}
|
||||
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false,
|
||||
item: null,
|
||||
@@ -84,15 +85,26 @@ Page({
|
||||
|
||||
retryDetail: function() { this.loadDetail(this.data.detailId); },
|
||||
onShow: function() {
|
||||
this._syncNavigationColor();
|
||||
if (this.data.detailId) {
|
||||
this.loadDetail(this.data.detailId);
|
||||
}
|
||||
},
|
||||
|
||||
onHide: function() {
|
||||
if (wx.setNavigationBarColor) wx.setNavigationBarColor({frontColor:'#000000',backgroundColor:'#FDE4EC'});
|
||||
},
|
||||
_syncNavigationColor: function() {
|
||||
if (wx.setNavigationBarColor) wx.setNavigationBarColor({frontColor:this.data.bgImageUrl?'#ffffff':'#000000',backgroundColor:this.data.bgImageUrl?'#000000':'#FDE4EC'});
|
||||
},
|
||||
goBack: function() {
|
||||
wx.navigateBack({fail:function(){wx.switchTab({url:'/pages/discover/discover'});}});
|
||||
},
|
||||
|
||||
loadDetail: async function(id) {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
var item = await api.timelineDetail(id);
|
||||
var myUid = wx.getStorageSync('userId');
|
||||
@@ -176,6 +188,7 @@ Page({
|
||||
} else {
|
||||
this.setData({ bgImageUrl: '' });
|
||||
}
|
||||
this._syncNavigationColor();
|
||||
},
|
||||
|
||||
// ===== 编辑 =====
|
||||
@@ -190,6 +203,6 @@ Page({
|
||||
|
||||
// ===== 导航 =====
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
}, {"loadDetail": ["timelineDetail", "coupleInfo"]}));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<view class="app-shell app-shell--secondary" style="{{layoutStyle}}">
|
||||
<view class="app-shell app-shell--secondary {{bgImageUrl ? 'detail-immersive' : ''}}" style="{{layoutStyle}}">
|
||||
<launch-transition />
|
||||
<!-- pages/timeline-detail/timeline-detail.wxml -->
|
||||
<page-meta page-style="{{bgImageUrl ? 'background: transparent;' : ''}}"></page-meta>
|
||||
@@ -6,7 +6,8 @@
|
||||
<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}}" />
|
||||
<app-nav wx:if="{{!bgImageUrl}}" title="记忆日详情" back="{{true}}" />
|
||||
<view wx:else class="detail-photo-nav" style="height:{{barHeight}}px;" bindtap="goBack" aria-label="返回"><image src="/images/back-white.svg" mode="aspectFit" /></view>
|
||||
|
||||
<scroll-view class="page-bg {{bgImageUrl ? 'page-bg-has-bg' : ''}}" scroll-y show-scrollbar="{{false}}" bounces="{{true}}" style="height:100vh;"
|
||||
bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/timeline-detail/timeline-detail.wxss */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; }
|
||||
|
||||
.container { padding: 380rpx 24rpx 180rpx; position: relative; z-index: 1; }
|
||||
.page-bg-has-bg .container { padding-top: 400rpx; }
|
||||
@@ -62,7 +62,7 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
|
||||
/* 无背景图时,top-nav-card 背景与 page 背景融合 */
|
||||
.top-nav-card:not(.top-nav-card-transparent) {
|
||||
background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%);
|
||||
background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%);
|
||||
}
|
||||
|
||||
|
||||
@@ -163,3 +163,26 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
/* 有背景图时采用全屏照片布局,顶部不再显示色块与标题。 */
|
||||
.detail-immersive { background:transparent!important; }
|
||||
.detail-photo-nav { position:fixed; top:var(--status-height,20px); left:16rpx; width:80rpx; display:flex; align-items:center; justify-content:center; z-index:900; }
|
||||
.detail-photo-nav image { width:36rpx; height:36rpx; }
|
||||
.detail-immersive .page-bg { padding-top:0!important; }
|
||||
.detail-immersive .page-bg > .container {
|
||||
padding:25vh 48rpx calc(56rpx + env(safe-area-inset-bottom))!important;
|
||||
min-height:100vh;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.detail-immersive .detail-bg-overlay { background:rgba(0,0,0,.42); }
|
||||
.detail-immersive .detail-hero { margin-bottom:36rpx; }
|
||||
.detail-immersive .detail-hero-bar { margin-top:40rpx; }
|
||||
.detail-immersive .detail-title-wrap { margin-bottom:24rpx; }
|
||||
.detail-immersive .detail-info-line { margin-bottom:12rpx; }
|
||||
.detail-immersive .detail-title,
|
||||
.detail-immersive .detail-info-line,
|
||||
.detail-immersive .detail-meta-line { max-width:100%; overflow-wrap:break-word; text-shadow:0 2rpx 8rpx rgba(0,0,0,.2); }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/timeline/timeline.js
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
@@ -55,7 +56,7 @@ function calcNearestRepeatDate(eventDate, today, interval, unit) {
|
||||
return new Date(eventDate);
|
||||
}
|
||||
|
||||
Page({
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: { loading: true, loadError: false,
|
||||
list: []
|
||||
@@ -84,7 +85,7 @@ Page({
|
||||
loadData: async function() {
|
||||
if (this._pageLoading) return;
|
||||
this._pageLoading = true;
|
||||
this.setData({loading:true, loadError:false});
|
||||
this.setData({loading: !this._hasCachedData, loadError:false});
|
||||
try {
|
||||
const list = await api.timelineList();
|
||||
const myUid = wx.getStorageSync('userId');
|
||||
@@ -188,7 +189,7 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||||
});
|
||||
goProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); }
|
||||
}, {"loadData": ["timelineList", "coupleInfo"]}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/timeline/timeline.wxss */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 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; }
|
||||
@@ -151,3 +151,7 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
|
||||
@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; }
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
@@ -13,6 +13,7 @@ Page({
|
||||
timerText: '',
|
||||
createdTime: '',
|
||||
respondedTime: '',
|
||||
completedTime: '',
|
||||
_timer: null,
|
||||
},
|
||||
|
||||
@@ -57,6 +58,7 @@ Page({
|
||||
timerText: timerText,
|
||||
createdTime: createdTime,
|
||||
respondedTime: respondedTime,
|
||||
completedTime: res.completed_at ? res.completed_at.substring(0, 16).replace('T', ' ') : '',
|
||||
});
|
||||
|
||||
if (!self._visible) return;
|
||||
@@ -67,37 +69,14 @@ Page({
|
||||
self.data._pollTimer = null;
|
||||
}
|
||||
|
||||
// 如果状态为 agreed(双方已同意解绑),清除缓存后跳转到登录页
|
||||
if (status === 'agreed') {
|
||||
wx.removeStorageSync('coupleId');
|
||||
wx.removeStorageSync('petHasPet');
|
||||
wx.showToast({ title: '情侣关系已解除', icon: 'none' });
|
||||
setTimeout(function() {
|
||||
wx.reLaunch({ url: '/pages/index/index' });
|
||||
}, 2000);
|
||||
if (status === 'completed') {
|
||||
wx.removeStorageSync('coupleId'); wx.removeStorageSync('petHasPet');
|
||||
require('../../utils/data-cache').clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动倒计时
|
||||
if (status === 'pending' && remaining > 0) {
|
||||
if ((status === 'pending' || status === 'cooling') && remaining > 0) {
|
||||
self.startTimer(remaining);
|
||||
// 如果是发起方,启动轮询检查对方是否已同意
|
||||
if (self.data.isRequester) {
|
||||
self.data._pollTimer = setInterval(function() {
|
||||
api.unbindStatus().then(function(pollRes) {
|
||||
if (pollRes.status === 'agreed') {
|
||||
clearInterval(self.data._pollTimer);
|
||||
self.data._pollTimer = null;
|
||||
wx.removeStorageSync('coupleId');
|
||||
wx.removeStorageSync('petHasPet');
|
||||
wx.showToast({ title: '对方已同意解绑,正在跳转...', icon: 'none' });
|
||||
setTimeout(function() {
|
||||
wx.reLaunch({ url: '/pages/index/index' });
|
||||
}, 2000);
|
||||
}
|
||||
}).catch(function() {});
|
||||
}, 5000);
|
||||
}
|
||||
self.data._pollTimer = setInterval(function() { self.loadStatus(); }, 10000);
|
||||
}
|
||||
}).catch(function(err) {
|
||||
self.setData({ loading: false, loadError:true });
|
||||
@@ -107,12 +86,13 @@ Page({
|
||||
startTimer: function(seconds) {
|
||||
var self = this;
|
||||
if (self.data._timer) clearInterval(self.data._timer);
|
||||
self._deadline = Date.now() + seconds * 1000;
|
||||
self.data._remaining = seconds;
|
||||
self.data._timer = setInterval(function() {
|
||||
self.data._remaining--;
|
||||
self.data._remaining = Math.max(0, Math.ceil((self._deadline - Date.now()) / 1000));
|
||||
if (self.data._remaining <= 0) {
|
||||
clearInterval(self.data._timer);
|
||||
self.setData({ status: 'expired', timerText: '已过期' });
|
||||
self.loadStatus();
|
||||
return;
|
||||
}
|
||||
self.setData({ timerText: self.formatRemaining(self.data._remaining) });
|
||||
@@ -132,7 +112,7 @@ Page({
|
||||
var self = this;
|
||||
wx.showModal({
|
||||
title: '💔 确认解绑',
|
||||
content: '确定同意解除情侣绑定吗?\n\n确认后所有情侣记录(记忆日、日记、相册、歌曲、打卡)将被永久删除,且不可恢复!\n\n仅保留双方的个人资料。',
|
||||
content: '同意后进入72小时冷静期,期间任一方可以取消。到期自动解除关系并永久删除共同数据,个人抽烟记录保留。',
|
||||
cancelText: '再想想',
|
||||
confirmText: '确认解绑',
|
||||
confirmColor: '#FF4757',
|
||||
@@ -141,11 +121,8 @@ Page({
|
||||
wx.showLoading({ title: '处理中...' });
|
||||
api.unbindRespond({ agree: true }).then(function() {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: '已解除绑定', icon: 'success' });
|
||||
wx.removeStorageSync('coupleId');
|
||||
wx.removeStorageSync('petHasPet');
|
||||
self.setData({ status: 'agreed' });
|
||||
setTimeout(function() { wx.reLaunch({ url: '/pages/index/index' }); }, 1500);
|
||||
wx.showToast({ title: '已进入72小时冷静期', icon: 'none' });
|
||||
self.loadStatus();
|
||||
}).catch(function(err) {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: err.error || '操作失败', icon: 'none' });
|
||||
@@ -193,7 +170,7 @@ Page({
|
||||
api.unbindCancel().then(function() {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: '已撤销', icon: 'success' });
|
||||
self.setData({ status: 'cancelled' });
|
||||
self.loadStatus();
|
||||
}).catch(function(err) {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: err.error || '操作失败', icon: 'none' });
|
||||
@@ -204,6 +181,6 @@ Page({
|
||||
},
|
||||
|
||||
goBack: function() { wx.navigateBack(); },
|
||||
goHome: function() { wx.reLaunch({ url: '/pages/index/index' }); },
|
||||
goToProfile: function() { wx.reLaunch({ url: '/pages/profile/profile' }); },
|
||||
goHome: function() { wx.switchTab({ url: '/pages/index/index' }); },
|
||||
goToProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); },
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</view>
|
||||
<view class="unbind-info">
|
||||
<text>发起时间:{{createdTime}}</text>
|
||||
<text>⚠️ 确认解绑后,所有情侣记录将被永久删除</text>
|
||||
<text>同意后进入72小时冷静期,期间任一方可取消</text>
|
||||
</view>
|
||||
<view class="unbind-actions" style="margin-top:32rpx;">
|
||||
<button class="btn btn-primary" bindtap="agreeUnbind" style="background:#FF4757;">💔 同意解绑</button>
|
||||
@@ -51,13 +51,20 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="unbind-card" wx:elif="{{!loading && !loadError && status == 'cooling'}}">
|
||||
<view class="unbind-icon">⏳</view><text class="unbind-title">72小时冷静期</text>
|
||||
<text class="unbind-desc">冷静期内关系保留,双方均可撤销解绑;到期自动解绑并永久删除共同数据。个人抽烟记录保留。</text>
|
||||
<view class="unbind-timer"><text class="timer-label">距正式解绑</text><text class="timer-value">{{timerText}}</text></view>
|
||||
<button class="btn btn-outline" bindtap="cancelUnbind">撤销解绑,继续保留关系</button>
|
||||
</view>
|
||||
|
||||
<!-- 已同意解绑 -->
|
||||
<view class="unbind-card" wx:elif="{{!loading && !loadError && status == 'agreed'}}">
|
||||
<view class="unbind-card" wx:elif="{{!loading && !loadError && status == 'completed'}}">
|
||||
<view class="unbind-icon">💔</view>
|
||||
<text class="unbind-title">已同意解绑</text>
|
||||
<text class="unbind-desc">双方已确认解除情侣绑定,所有情侣数据已被永久删除</text>
|
||||
<text class="unbind-title">解绑完成</text>
|
||||
<text class="unbind-desc">冷静期已结束,已解除关系并删除共同数据。个人抽烟记录保留。</text>
|
||||
<view class="unbind-info">
|
||||
<text>回应时间:{{respondedTime}}</text>
|
||||
<text>完成时间:{{completedTime}}</text>
|
||||
</view>
|
||||
<button class="btn btn-primary" bindtap="goHome" style="margin-top:32rpx;">返回首页</button>
|
||||
</view>
|
||||
@@ -77,7 +84,7 @@
|
||||
<view class="unbind-card" wx:elif="{{!loading && !loadError && status == 'cancelled'}}">
|
||||
<view class="unbind-icon">💕</view>
|
||||
<text class="unbind-title">解绑请求已撤销</text>
|
||||
<text class="unbind-desc">发起方已撤销了解绑请求</text>
|
||||
<text class="unbind-desc">解绑已取消,关系继续保留</text>
|
||||
<view class="unbind-info">
|
||||
<text>撤销时间:{{respondedTime}}</text>
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* pages/unbind/unbind.wxss - 情侣解绑确认页 */
|
||||
@import '../../styles/common.wxss';
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
|
||||
.container { padding: 24rpx; }
|
||||
|
||||
/* 加载状态 */
|
||||
@@ -85,3 +85,7 @@ page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
|
||||
@import '../../styles/button-theme.wxss';
|
||||
|
||||
@import '../../styles/typography.wxss';
|
||||
|
||||
Reference in New Issue
Block a user