Files
past-go/weapp/pages/publish/publish.js
T
Blizzard 8be6d9b1ab feat: add account deletion, announcements and subscription renewal
Support personal records and persistent smoking visibility. Add upload quota preflight, file cleanup, privacy entry, image caching and revision-based page refresh across the backend, admin and mini program.
2026-11-03 22:06:47 +08:00

483 lines
18 KiB
JavaScript

const { tryLoginRenewal } = require('../../utils/subscribe');
const { uploadReference } = require('../../utils/file-media');
const { getLayout } = require('../../utils/layout');
// pages/publish/publish.js - 统一发布页(增强版编辑器)
const { api, API_BASE, uploadFileCompat } = require('../../utils/request');
const { createSwipeBack } = require('../../utils/util');
const { guardStorageSpace } = require('../../utils/storage-guard');
var MOOD_OPTIONS = ['😊', '🥰', '😢', '😤', '😴', '🤩', '🥺', '😎', '🤔', '😭', '😋', '😌'];
var WEATHER_OPTIONS = ['☀️', '⛅', '☁️', '🌧️', '⛈️', '❄️', '🌤️', '🌥️', '🌈', '🌪️', '🌫️', '🌙'];
var TYPE_CONFIG = {
timeline: { title: '添加记忆日', btn: '保存记忆日', api: 'timelineAdd' },
diary: { title: '写日记', btn: '发布日记', api: 'diaryAdd' },
album: { title: '创建相册', btn: '创建相册', api: 'albumAdd' },
song: { title: '收藏歌曲', btn: '保存歌曲', api: 'songsAdd' },
checkin: { title: '添加打卡', btn: '保存打卡', api: 'checkinAdd' }
};
Page({
onLoginSubscribeTap: function() { return tryLoginRenewal(); },
onResize: function() { this.setData(getLayout()); },
data: {
type: '',
titleText: '',
btnText: '',
form: {},
editorContent: '',
tempImages: [],
coverPreview: '',
coverPath: '',
submitting: false, editLoading:false, editLoadError:false,
checkinTypes: ['双方打卡', '第一视角(我)', '她打卡'],
checkinTypeIdx: 0,
checkinTypeMap: ['both', 'first', 'her'],
checkinFreqs: ['每天打卡', '每周打卡', '每月打卡', '不限次数'],
checkinFreqIdx: 0,
checkinFreqMap: ['daily', 'weekly', 'monthly', 'unlimited'],
repeatOptions: ['无重复', '每周重复', '每月重复', '每年重复'],
repeatOptionIdx: 0,
repeatOptionMap: ['', 'week', 'month', 'year'],
editId: 0,
// 背景图
bgImageUrl: '',
bgImagePath: '',
// 心情/天气
moodOptions: MOOD_OPTIONS,
weatherOptions: WEATHER_OPTIONS,
contentLength: 0
},
onLoad: function(options) {
this.setData(getLayout());
var type = options.type || 'timeline';
var editId = options.edit ? String(options.edit) : "";
var cfg = TYPE_CONFIG[type] || TYPE_CONFIG['timeline'];
var titleText = editId ? '编辑事件' : cfg.title;
var btnText = editId ? '保存修改' : cfg.btn;
this.setData({ type: type, titleText: titleText, btnText: btnText, editId: editId });
var self = this;
var swipe = createSwipeBack(function() { self.goBack(); });
this.onTouchStart = swipe.onTouchStart;
this.onTouchEnd = swipe.onTouchEnd;
// 初始化表单
this.resetForm(type);
// 非编辑模式:优先恢复本地草稿(防误退出丢失内容)
if (!editId) {
this.loadDraft(type);
}
// 如果是编辑模式,加载已有数据
if (editId && type === 'timeline') {
this.loadEditData(editId);
}
// 如果是打卡页,加载情侣昵称
if (type === 'checkin') {
this.loadCoupleNames();
}
},
// ===== 草稿缓存 =====
// 读取本地草稿并填充表单
loadDraft: function(type) {
try {
var draftKey = 'publishDraft_' + type;
var draft = wx.getStorageSync(draftKey);
if (!draft) return;
var data = JSON.parse(draft);
var patch = {};
if (data.form) {
var form = this.data.form;
for (var k in data.form) {
if (data.form.hasOwnProperty(k) && form.hasOwnProperty(k)) {
form[k] = data.form[k];
}
}
patch.form = form;
patch.editorContent = form.content || '';
patch.contentLength = (form.content || '').length;
}
if (typeof data.repeatOptionIdx === 'number') patch.repeatOptionIdx = data.repeatOptionIdx;
if (typeof data.checkinTypeIdx === 'number') patch.checkinTypeIdx = data.checkinTypeIdx;
if (typeof data.checkinFreqIdx === 'number') patch.checkinFreqIdx = data.checkinFreqIdx;
if (data.bgImageUrl) patch.bgImageUrl = data.bgImageUrl;
if (data.coverPreview) {
patch.coverPreview = data.coverPreview;
patch.coverPath = data.coverPath || data.coverPreview;
}
this.setData(patch);
} 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; // 编辑模式不保存草稿
var draftKey = 'publishDraft_' + type;
var data = {
form: this.data.form,
repeatOptionIdx: this.data.repeatOptionIdx,
checkinTypeIdx: this.data.checkinTypeIdx,
checkinFreqIdx: this.data.checkinFreqIdx,
bgImageUrl: this.data.bgImageUrl,
coverPreview: this.data.coverPreview,
coverPath: this.data.coverPath,
time: Date.now()
};
wx.setStorageSync(draftKey, JSON.stringify(data));
} catch(e) {}
},
// 发布成功后清除草稿
_clearDraft: function() {
clearTimeout(this._draftTimer);
this._draftTimer = null;
this._draftCleared = true;
try {
var type = this.data.type;
wx.removeStorageSync('publishDraft_' + type);
} catch(e) {}
},
retryEdit: function() { this.loadEditData(this.data.editId); },
loadEditData: async function(id) {
this.setData({editLoading:true,editLoadError:false});
try {
var item = await api.timelineDetail(id);
if (item) {
var form = {
title: item.title || '',
date: item.event_date || '',
location: item.location || '',
content: item.content || '',
repeat_interval: parseInt(item.repeat_interval) || 0,
repeat_unit: item.repeat_unit || ''
};
// 加载背景图
var bgPath = item.bg_image_file_id || item.bg_image || '';
var bgUrl = bgPath ? require('../../utils/request').getImageUrl(bgPath) : '';
var repeatOptionIdx = 0;
if (form.repeat_interval > 0 && form.repeat_unit) {
var opts = this.data.repeatOptionMap;
for (var i = 0; i < opts.length; i++) {
if (opts[i] === form.repeat_unit) {
repeatOptionIdx = i;
break;
}
}
}
this.setData({
form: form,
editorContent: form.content || '',
repeatOptionIdx: repeatOptionIdx,
bgImageUrl: bgUrl,
bgImagePath: bgPath
});
}
} catch(e) {
console.error('加载编辑数据失败', e);
this.setData({editLoadError:true});
} finally { this.setData({editLoading:false}); }
},
loadCoupleNames: async function() {
try {
var info = await api.coupleInfo();
var myUid = wx.getStorageSync('userId');
var p1 = info.partner1 || {}, p2 = info.partner2 || {};
var me = (p1.id == myUid) ? p1 : p2;
var partner = (p1.id == myUid) ? p2 : p1;
var myName = me.nickname || '我';
var partnerName = partner.nickname || 'TA';
this.setData({
checkinTypes: ['双方打卡', myName, partnerName]
});
} catch(e) {
console.error('获取情侣信息失败', e);
}
},
resetForm: function(type) {
var form = {};
if (type === 'diary') {
form = { title: '', content: '', mood: '😊', weather: '☀️', lat: 0, lng: 0, location: '' };
} else if (type === 'song') {
form = { song_name: '', artist: '', note: '' };
} else if (type === 'album') {
form = { title: '', desc: '', lat: 0, lng: 0, location: '' };
this.setData({ coverPreview: '', coverPath: '' });
} else if (type === 'checkin') {
form = { title: '', type: 'both', frequency: 'daily' };
this.setData({ checkinTypeIdx: 0, checkinFreqIdx: 0 });
} else {
form = { title: '', date: '', location: '', content: '', repeat_interval: 0, repeat_unit: '' };
}
this.setData({ form: form, editorContent: form.content || '', tempImages: [], repeatOptionIdx: 0, contentLength: (form.content || '').length });
},
// ===== 自动定位(日记 / 相册发布时自动获取经纬度)=====
_autoLocate: function() {
var self = this;
return new Promise(function(resolve) {
// 已有位置则直接使用,避免重复定位
if (self.data.form && self.data.form.lat && self.data.form.lng) {
resolve({ lat: self.data.form.lat, lng: self.data.form.lng, location: self.data.form.location || '' });
return;
}
wx.getFuzzyLocation({
success: function(res) {
resolve({ lat: res.latitude, lng: res.longitude, location: '' });
},
fail: function() {
// 定位失败不阻塞发布,经纬度置 0
resolve({ lat: 0, lng: 0, location: '' });
}
});
});
},
// ===== 通用输入(自动保存草稿)=====
onTitleInput: function(e) { this.setData({ 'form.title': e.detail.value }); this._saveDraft(); },
onDateInput: function(e) { this.setData({ 'form.date': e.detail.value }); this._saveDraft(); },
onContentInput: function(e){
var val = e.detail.value || '';
// 正文只存于逻辑层,避免每次敲字回传长文本影响原生输入。
this.data.form.content = val;
this.setData({ contentLength: val.length });
this._saveDraft();
},
onLocationInput: function(e){ this.setData({ 'form.location': e.detail.value }); this._saveDraft(); },
onDescInput: function(e) { this.setData({ 'form.desc': e.detail.value }); this._saveDraft(); },
onArtistInput: function(e) { this.setData({ 'form.artist': e.detail.value }); this._saveDraft(); },
onNoteInput: function(e) { this.setData({ 'form.note': e.detail.value }); this._saveDraft(); },
onCheckinTypeChange: function(e) {
var idx = parseInt(e.detail.value);
this.setData({ checkinTypeIdx: idx, 'form.type': this.data.checkinTypeMap[idx] });
this._saveDraft();
},
onCheckinFreqChange: function(e) {
var idx = parseInt(e.detail.value);
this.setData({ checkinFreqIdx: idx, 'form.frequency': this.data.checkinFreqMap[idx] });
this._saveDraft();
},
onRepeatChange: function(e) {
var idx = parseInt(e.detail.value);
var unit = this.data.repeatOptionMap[idx];
this.setData({
repeatOptionIdx: idx,
'form.repeat_interval': unit ? 1 : 0,
'form.repeat_unit': unit || ''
});
this._saveDraft();
},
// ===== 封面图片选择(相册)=====
chooseCover: function() {
var self = this;
guardStorageSpace(0).then(function(ok) {
if (!ok) return;
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function(res) {
self.setData({ coverPreview: res.tempFilePaths[0], coverPath: res.tempFilePaths[0] });
self._saveDraft();
}
});
});
},
// ===== 图片选择(日记)=====
chooseImages: function() {
var self = this;
guardStorageSpace(0).then(function(ok) {
if (!ok) return;
wx.chooseImage({
count: 9 - self.data.tempImages.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function(res) {
self.setData({ tempImages: self.data.tempImages.concat(res.tempFilePaths) });
self._saveDraft();
}
});
});
},
// ===== 删除单张图片 =====
removeImage: function(e) {
var index = parseInt(e.currentTarget.dataset.index);
var images = this.data.tempImages;
images.splice(index, 1);
this.setData({ tempImages: images });
this._saveDraft();
},
// ===== 提交 =====
submit: async function() {
if (this.data.editLoading || this.data.editLoadError) return;
if (this.data.submitting) return;
this.setData({ submitting: true });
var editId = this.data.editId;
try {
var type = this.data.type;
var form = this.data.form;
// 先校验表单
if (type === 'timeline') {
if (!form.title) { throw new Error('请填写事件内容'); }
if (!form.date) { throw new Error('请填写事件日期'); }
} else if (type === 'diary') {
if (!form.content || !form.content.trim()) { throw new Error('请填写日记内容'); }
} else if (type === 'album') {
if (!form.title) { throw new Error('请填写相册名称'); }
} else if (type === 'song') {
if (!form.song_name || !form.artist) { throw new Error('请填写歌曲名和歌手名'); }
} else if (type === 'checkin') {
if (!form.title) { throw new Error('请填写打卡标题'); }
}
// 再执行异步操作(上传/API调用)
if (type === 'timeline') {
var editId = this.data.editId;
// 上传背景图
var bgImagePath = this.data.bgImagePath;
if (this.data.bgImageUrl && !bgImagePath) {
wx.showLoading({ title: '上传背景图...' });
try {
var uploadRes = await uploadFileCompat({
url: API_BASE + '/upload',
filePath: this.data.bgImageUrl,
name: 'file',
formData: { sub_dir: 'timeline_bg' }
});
bgImagePath = uploadReference(uploadRes);
} finally { wx.hideLoading(); }
}
var result;
if (editId) {
result = await api.timelineUpdate({ id: editId, title: form.title, event_date: form.date, location: form.location || '', content: form.content || '', repeat_interval: form.repeat_interval || 0, repeat_unit: form.repeat_unit || '', bg_image: bgImagePath || '' });
} else {
result = await api.timelineAdd({ title: form.title, event_date: form.date, location: form.location || '', content: form.content || '', repeat_interval: form.repeat_interval || 0, repeat_unit: form.repeat_unit || '', bg_image: bgImagePath || '' });
}
} else if (type === 'diary') {
var today = new Date();
var ds = today.getFullYear() + '-' + String(today.getMonth()+1).padStart(2,'0') + '-' + String(today.getDate()).padStart(2,'0');
var uploadPaths = [];
if (this.data.tempImages.length > 0) {
wx.showLoading({ title: '上传图片中...' });
for (var i = 0; i < this.data.tempImages.length; i++) {
var res = await this._uploadDiaryImage(this.data.tempImages[i]);
if (res) uploadPaths.push(res);
}
wx.hideLoading();
}
// 从缓存获取用户地区
var region = wx.getStorageSync('region') || '';
// 自动定位获取经纬度(发布后自动显示地理位置)
var loc = await this._autoLocate();
await api.diaryAdd({
title: form.title || '',
diary_date: ds,
content: form.content,
images: uploadPaths,
region: region,
lat: loc.lat,
lng: loc.lng,
location: loc.location
});
} else if (type === 'album') {
var coverUrl = '';
if (this.data.coverPath) {
wx.showLoading({ title: '上传封面...' });
coverUrl = await this._uploadAlbumCover(this.data.coverPath);
wx.hideLoading();
}
// 自动定位获取经纬度(发布后自动显示地理位置)
var albumLoc = await this._autoLocate();
await api.albumAdd({ title: form.title, description: form.desc || '', cover: coverUrl, lat: albumLoc.lat, lng: albumLoc.lng, location: albumLoc.location });
} else if (type === 'song') {
await api.songsAdd({ song_name: form.song_name, artist: form.artist, note: form.note || '' });
} else if (type === 'checkin') {
await api.checkinAdd({ title: form.title, type: form.type || 'dual', frequency: form.frequency || 'daily' });
}
// 发布/修改成功后清除草稿
this._clearDraft();
wx.showToast({ title: editId ? '修改成功 ✨' : '发布成功 ✨', icon: 'success' });
// 延迟返回页面,给订阅弹窗留出时间
setTimeout(function() { wx.navigateBack(); }, 1500);
} catch (e) {
wx.hideLoading();
wx.showToast({ title: (e && (e.error || e.message || e.errMsg)) || '发布失败', icon: 'none', duration: 2000 });
} finally {
this.setData({ submitting: false });
}
},
_uploadAlbumCover: async function(filePath) {
return this._uploadMedia(filePath, 'albums');
},
_uploadDiaryImage: async function(filePath) {
return this._uploadMedia(filePath, 'diaries');
},
_uploadMedia: async function(filePath, subDir) {
var data = await uploadFileCompat({
url: API_BASE + '/upload',
filePath: filePath,
name: 'file',
formData: { sub_dir: subDir }
});
return uploadReference(data);
},
// ===== 背景图 =====
chooseBgImage: function() {
var self = this;
guardStorageSpace(0).then(function(ok) {
if (!ok) return;
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function(res) {
var filePath = res.tempFilePaths[0];
self.setData({ bgImageUrl: filePath, bgImagePath: '' });
}
});
});
},
removeBgImage: function() {
this.setData({ bgImageUrl: '', bgImagePath: '' });
},
goBack: function() { wx.navigateBack(); }
});