init: initial commit
This commit is contained in:
@@ -0,0 +1,530 @@
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/publish/publish.js - 统一发布页(增强版编辑器)
|
||||
const { api, API_BASE, uploadFileCompat } = require('../../utils/request');
|
||||
const { createSwipeBack } = require('../../utils/util');
|
||||
const { requestSubscribeAfterAction } = require('../../utils/subscribe');
|
||||
|
||||
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({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: {
|
||||
type: '',
|
||||
titleText: '',
|
||||
btnText: '',
|
||||
form: {},
|
||||
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 ? parseInt(options.edit) : 0;
|
||||
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.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() {
|
||||
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() {
|
||||
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 || '';
|
||||
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,
|
||||
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, 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.setData({ 'form.content': val, 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;
|
||||
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;
|
||||
wx.chooseImage({
|
||||
count: 9 - this.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('请填写打卡标题'); }
|
||||
}
|
||||
|
||||
// ★ 在用户点击手势上下文中提前请求订阅消息(符合微信规范)
|
||||
// 订阅弹窗不阻塞后续异步操作,用户拒绝也不影响发布
|
||||
var sceneMap = {
|
||||
diary: 'diary', album: 'photo', checkin: 'checkin',
|
||||
timeline: 'timeline', song: 'song'
|
||||
};
|
||||
var scene = sceneMap[type];
|
||||
if (scene) {
|
||||
requestSubscribeAfterAction(scene);
|
||||
}
|
||||
|
||||
// 再执行异步操作(上传/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 = uploadRes && (uploadRes.path || uploadRes.url || '');
|
||||
} catch(e) {}
|
||||
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) {
|
||||
try {
|
||||
var data = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'albums' }
|
||||
});
|
||||
// 兼容不同响应格式:{path:'...'} 或 {success:true, path:'...'} 或 {url:'...'}
|
||||
var path = data && (data.path || data.url || '');
|
||||
if (!path) {
|
||||
// 内容安全检测失败:抛出错误提示用户
|
||||
if (data && data.error) {
|
||||
throw data;
|
||||
}
|
||||
console.error('封面上传返回无路径', JSON.stringify(data), '错误码:', data && data.code);
|
||||
// 尝试重新上传一次
|
||||
try {
|
||||
var retryData = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'albums' }
|
||||
});
|
||||
var retryPath = retryData && (retryData.path || retryData.url || '');
|
||||
if (retryPath) {
|
||||
console.log('封面上传重试成功');
|
||||
return retryPath;
|
||||
}
|
||||
} catch(retryErr) {
|
||||
console.error('封面上传重试也失败', retryErr, '错误码:', retryErr && retryErr.code);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
} catch(e) {
|
||||
// 内容安全违规错误:直接抛出,中断发布并提示用户
|
||||
if (e && e.error) {
|
||||
throw e;
|
||||
}
|
||||
console.error('封面上传失败', e, '错误码:', e && e.code);
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
_uploadDiaryImage: async function(filePath) {
|
||||
try {
|
||||
var data = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'diaries' }
|
||||
});
|
||||
// 兼容不同响应格式:{path:'...'} 或 {success:true, path:'...'} 或 {url:'...'}
|
||||
var path = data && (data.path || data.url || '');
|
||||
if (!path) {
|
||||
// 内容安全检测失败:抛出错误提示用户
|
||||
if (data && data.error) {
|
||||
throw data;
|
||||
}
|
||||
console.error('日记图片上传返回无路径', JSON.stringify(data), '错误码:', data && data.code);
|
||||
// 尝试重新上传一次
|
||||
try {
|
||||
var retryData = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'diaries' }
|
||||
});
|
||||
var retryPath = retryData && (retryData.path || retryData.url || '');
|
||||
if (retryPath) {
|
||||
console.log('日记图片重试上传成功');
|
||||
return retryPath;
|
||||
}
|
||||
} catch(retryErr) {
|
||||
console.error('日记图片重试上传也失败', retryErr, '错误码:', retryErr && retryErr.code);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
} catch(e) {
|
||||
// 内容安全违规错误:直接抛出,中断发布并提示用户
|
||||
if (e && e.error) {
|
||||
throw e;
|
||||
}
|
||||
console.error('日记图片上传失败', e, '错误码:', e && e.code);
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
// ===== 背景图 =====
|
||||
chooseBgImage: function() {
|
||||
var self = this;
|
||||
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(); }
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"disableScroll": true,
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"app-nav": "/components/app-nav/app-nav",
|
||||
"page-state": "/components/page-state/page-state"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<view class="app-shell app-shell--secondary" style="{{layoutStyle}}">
|
||||
<launch-transition />
|
||||
<!-- pages/publish/publish.wxml - 统一发布页(增强版编辑器) -->
|
||||
<app-nav title="{{titleText}}" back="{{true}}" />
|
||||
<scroll-view class="page-bg" scroll-y show-scrollbar="{{false}}" bounces="{{true}}" style="height:100vh;" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
|
||||
<view class="container"><page-state wx:if="{{editLoading}}" kind="loading" title="正在加载事件" /><page-state wx:elif="{{editLoadError}}" kind="error" title="事件加载失败" action="重试" bind:action="retryEdit" /><block wx:else>
|
||||
|
||||
<!-- ===== 时间线 / 打卡 / 相册 ===== -->
|
||||
<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" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ===== 打卡(类型 + 频率)===== -->
|
||||
<block wx:if="{{type == 'checkin'}}">
|
||||
<view class="input-group">
|
||||
<label>打卡权限</label>
|
||||
<picker mode="selector" range="{{checkinTypes}}" value="{{checkinTypeIdx}}" bindchange="onCheckinTypeChange">
|
||||
<view class="input">{{checkinTypes[checkinTypeIdx] || '请选择'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>打卡频率</label>
|
||||
<picker mode="selector" range="{{checkinFreqs}}" value="{{checkinFreqIdx}}" bindchange="onCheckinFreqChange">
|
||||
<view class="input">{{checkinFreqs[checkinFreqIdx] || '请选择'}}</view>
|
||||
</picker>
|
||||
<text style="font-size:22rpx;color:#999;margin-top:8rpx;display:block;">每天/每周/每月:周期内打卡一次即完成 | 不限:有记录即算完成</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ===== 时间线(日期)===== -->
|
||||
<block wx:if="{{type == 'timeline'}}">
|
||||
<view class="input-group">
|
||||
<label>事件日期</label>
|
||||
<picker mode="date" value="{{form.date}}" bindchange="onDateInput">
|
||||
<view class="input">{{form.date || '请选择事件日期'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ===== 时间线(地点 + 备注 + 重复 + 背景图)===== -->
|
||||
<block wx:if="{{type == 'timeline'}}">
|
||||
<view class="input-group">
|
||||
<label>事件地点(选填)</label>
|
||||
<input 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" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>重复选项(选填)</label>
|
||||
<picker mode="selector" range="{{repeatOptions}}" value="{{repeatOptionIdx}}" bindchange="onRepeatChange">
|
||||
<view class="input">{{repeatOptions[repeatOptionIdx]}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>背景图片(选填)</label>
|
||||
<view class="bg-pick-row">
|
||||
<image wx:if="{{bgImageUrl}}" class="bg-preview" src="{{bgImageUrl}}" mode="aspectFill" />
|
||||
<view class="bg-pick-btn" bindtap="chooseBgImage">
|
||||
<text wx:if="{{!bgImageUrl}}">+ 添加背景</text>
|
||||
<text wx:else>更换背景</text>
|
||||
</view>
|
||||
<view class="bg-remove-btn" wx:if="{{bgImageUrl}}" bindtap="removeBgImage">✕ 移除</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ===== 相册(描述 + 封面)===== -->
|
||||
<view class="input-group" wx:if="{{type == 'album'}}">
|
||||
<label>描述</label>
|
||||
<input class="input" placeholder="简单描述一下这个相册" value="{{form.desc}}" bindinput="onDescInput" />
|
||||
</view>
|
||||
<view class="input-group" wx:if="{{type == 'album'}}">
|
||||
<label>封面图片</label>
|
||||
<view class="cover-pick-row">
|
||||
<image wx:if="{{coverPreview}}" class="cover-preview" src="{{coverPreview}}" mode="aspectFill" />
|
||||
<view class="cover-pick-btn" bindtap="chooseCover" wx:if="{{!coverPreview}}">
|
||||
<text>+</text>
|
||||
</view>
|
||||
<view class="cover-pick-btn" bindtap="chooseCover" wx:else style="background:#f0f0f0;">
|
||||
<text style="font-size:28rpx;">换</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ===== 日记 ===== -->
|
||||
<block wx:if="{{type == 'diary'}}">
|
||||
<!-- 标题 -->
|
||||
<view class="input-group">
|
||||
<label>标题(选填)</label>
|
||||
<input class="input diary-title-input" placeholder="给日记起个标题..." value="{{form.title}}" bindinput="onTitleInput" maxlength="100" />
|
||||
</view>
|
||||
|
||||
<!-- 内容编辑器(支持换行) -->
|
||||
<view class="input-group editor-group">
|
||||
<view class="editor-header">
|
||||
<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 />
|
||||
</view>
|
||||
|
||||
<!-- 图片选择(可删除) -->
|
||||
<view class="input-group">
|
||||
<view class="editor-header">
|
||||
<label>图片</label>
|
||||
<text class="img-count">{{tempImages.length}}/9</text>
|
||||
</view>
|
||||
<view class="img-picker-row">
|
||||
<view wx:for="{{tempImages}}" wx:key="*this" class="img-picker-wrap">
|
||||
<image class="img-picker-thumb" src="{{item}}" mode="aspectFill" />
|
||||
<view class="img-picker-del" bindtap="removeImage" data-index="{{index}}" hover-class="del-hover">
|
||||
<text>✕</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="img-picker-add" bindtap="chooseImages" wx:if="{{tempImages.length < 9}}">
|
||||
<text class="add-icon">+</text>
|
||||
<text class="add-text">添加图片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ===== 收藏歌曲 ===== -->
|
||||
<block wx:if="{{type == 'song'}}">
|
||||
<view class="input-group">
|
||||
<label>歌曲名</label>
|
||||
<input 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" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<label>备注(选填)</label>
|
||||
<input class="input" placeholder="一句话,限60字" maxlength="60" value="{{form.note}}" bindinput="onNoteInput" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 提交 -->
|
||||
<button class="btn btn-primary submit-btn" bindtap="submit" hover-class="none" loading="{{submitting}}">{{btnText}}</button>
|
||||
</block>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
</view>
|
||||
@@ -0,0 +1,208 @@
|
||||
/* pages/publish/publish.wxss - 增强版编辑器样式 */
|
||||
@import '../../styles/common.wxss';
|
||||
|
||||
.container { padding: 84rpx 32rpx 300rpx; }
|
||||
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
|
||||
|
||||
/* ===== 输入框美化 ===== */
|
||||
.input-group { margin-bottom: 36rpx; }
|
||||
.input-group label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #888;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 28rpx 32rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.input:focus {
|
||||
border-color: #FF6B81;
|
||||
box-shadow: 0 0 0 6rpx rgba(255,107,129,0.08);
|
||||
}
|
||||
.textarea {
|
||||
min-height: 200rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ===== 日记标题输入框 ===== */
|
||||
.diary-title-input {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
padding: 28rpx 32rpx;
|
||||
}
|
||||
|
||||
/* ===== 编辑器头部(标签+计数) ===== */
|
||||
.editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.editor-header label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.char-count {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
.char-warn {
|
||||
color: #ff6b6b;
|
||||
font-weight: 600;
|
||||
}
|
||||
.img-count {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
/* ===== 日记编辑器 ===== */
|
||||
.diary-editor {
|
||||
min-height: 300rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.7;
|
||||
padding: 28rpx 32rpx;
|
||||
}
|
||||
|
||||
/* ===== 图片选择(可删除) ===== */
|
||||
.img-picker-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.img-picker-wrap {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: visible;
|
||||
}
|
||||
.img-picker-thumb {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #eee;
|
||||
}
|
||||
.img-picker-del {
|
||||
position: absolute;
|
||||
top: -12rpx;
|
||||
right: -12rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,71,87,0.9);
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255,71,87,0.3);
|
||||
z-index: 2;
|
||||
}
|
||||
.del-hover {
|
||||
transform: scale(1.2);
|
||||
background: #ff4757;
|
||||
}
|
||||
.img-picker-add {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #fff;
|
||||
border: 2rpx dashed #ddd;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6rpx;
|
||||
}
|
||||
.img-picker-add .add-icon {
|
||||
font-size: 48rpx;
|
||||
color: #ccc;
|
||||
line-height: 1;
|
||||
}
|
||||
.img-picker-add .add-text {
|
||||
font-size: 20rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* ===== 提交按钮 ===== */
|
||||
.submit-btn {
|
||||
margin-top: 40rpx;
|
||||
margin-bottom: 80rpx;
|
||||
}
|
||||
|
||||
/* ===== 封面图片选择 ===== */
|
||||
.cover-pick-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.cover-preview {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #eee;
|
||||
}
|
||||
.cover-pick-btn {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #fff;
|
||||
border: 2rpx dashed #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 56rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* ===== 背景图选择 ===== */
|
||||
.bg-pick-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.bg-preview {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #eee;
|
||||
}
|
||||
.bg-pick-btn {
|
||||
padding: 20rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #fff;
|
||||
border: 2rpx dashed #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
min-height: 80rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bg-remove-btn {
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #fff;
|
||||
border: 2rpx solid #ff6b6b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
color: #ff6b6b;
|
||||
min-height: 80rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@import '../../styles/design.wxss';
|
||||
|
||||
@import '../../styles/journal.wxss';
|
||||
Reference in New Issue
Block a user