add editor

This commit is contained in:
Timi
2025-12-09 18:02:23 +08:00
parent 6dc4d71718
commit 19b6206695
13 changed files with 898 additions and 22 deletions

View File

@ -0,0 +1,382 @@
// pages/main/journal-editor/index.ts
import Events from "../../../utils/Events";
import Time from "../../../utils/Time";
import Toolkit from "../../../utils/Toolkit";
import config from "../../../config/index";
import { Location, MediaItem, MediaItemType, WechatMediaItem } from "../../../types/UI";
import { Journal } from "../../../types/Journal";
import { MediaAttachExt, MediaAttachType } from "../../../types/Attachment";
interface JournalEditorData {
id?: number;
idea: string;
date: string;
time: string;
mediaList: MediaItem[];
newMediaList: WechatMediaItem[];
location?: Location;
isAuthLocation: boolean;
isLoading: boolean;
saveText: string;
isSaving: boolean;
saveProgress: number;
mediaItemTypeEnum: any;
deleteDialogVisible: boolean;
deleteConfirmText: string;
}
Page({
data: <JournalEditorData>{
id: undefined,
idea: "",
date: "2025-06-28",
time: "16:00",
mediaList: [],
newMediaList: [],
location: undefined,
saveText: "保存",
isSaving: false,
saveProgress: 0,
isLoading: true,
mediaItemTypeEnum: {
...MediaItemType
},
isAuthLocation: false,
deleteDialogVisible: false,
deleteConfirmText: ""
},
async onLoad(options: any) {
// 授权定位
const setting = await wx.getSetting();
wx.setStorageSync("isAuthLocation", setting.authSetting["scope.userLocation"]);
let isAuthLocation = JSON.parse(wx.getStorageSync("isAuthLocation"));
this.setData({ isAuthLocation });
if (!isAuthLocation) {
wx.authorize({
scope: "scope.userLocation"
}).then(() => {
isAuthLocation = true;
this.setData({ isAuthLocation });
});
}
// 获取日记 ID
const id = options.id ? parseInt(options.id) : undefined;
if (!id) {
wx.showToast({
title: "缺少日志 ID",
icon: "error"
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
return;
}
this.setData({ id });
await this.loadJournalDetail(id);
},
/** 加载日记详情 */
async loadJournalDetail(id: number) {
wx.showLoading({ title: "加载中...", mask: true });
try {
const journal: Journal = await new Promise((resolve, reject) => {
wx.request({
url: `${config.url}/journal/${id}`,
method: "POST",
header: {
Key: wx.getStorageSync("key")
},
success: (res: any) => {
if (res.data.code === 20000) {
resolve(res.data.data);
} else {
reject(new Error(res.data.message || "加载失败"));
}
},
fail: reject
});
});
const items = journal.items || [];
const thumbItems = items.filter((item) => item.attachType === MediaAttachType.THUMB);
const mediaList: MediaItem[] = thumbItems.map((thumbItem) => {
const ext = thumbItem.ext = JSON.parse(thumbItem.ext!.toString()) as MediaAttachExt;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`;
return {
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL,
sourceURL,
size: thumbItem.size || 0,
attachmentId: thumbItem.id
} as MediaItem;
});
this.setData({
idea: journal.idea || "",
date: Time.toDate(journal.createdAt),
time: Time.toTime(journal.createdAt),
location: journal.location ? {
lat: journal.lat,
lng: journal.lng,
text: journal.location
} : undefined,
mediaList,
isLoading: false
});
wx.hideLoading();
} catch (err: any) {
wx.hideLoading();
wx.showToast({
title: err.message || "加载失败",
icon: "error"
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
},
/** 选择位置 */
async chooseLocation() {
const location = await wx.chooseLocation({});
this.setData({
location: {
lat: location.latitude,
lng: location.longitude,
text: location.name
}
});
},
/** 新增附件 */
addMedia() {
const that = this;
wx.chooseMedia({
mediaType: ["mix"],
sourceType: ["album", "camera"],
camera: "back",
success(res) {
wx.showLoading({
title: "加载中..",
mask: true
});
const tempFiles = res.tempFiles;
const newMedia = tempFiles.map(item => {
return {
type: (<any>MediaItemType)[item.fileType.toUpperCase()],
path: item.tempFilePath,
thumbPath: item.thumbTempFilePath,
size: item.size,
duration: item.duration,
raw: item
} as WechatMediaItem;
});
that.setData({
newMediaList: [...that.data.newMediaList, ...newMedia]
});
wx.hideLoading();
}
});
},
/** 预览附件 */
preview(e: WechatMiniprogram.BaseEvent) {
const isNewMedia = e.currentTarget.dataset.newMedia;
const index = e.currentTarget.dataset.index;
const sources = this.data.mediaList.map(item => ({
url: item.sourceURL,
type: MediaItemType[item.type].toLowerCase()
}));
const newSources = this.data.newMediaList.map(item => ({
url: item.path,
type: MediaItemType[item.type].toLowerCase()
}));
const allSources = [...sources, ...newSources];
const currentIndex = isNewMedia ? this.data.mediaList.length + index : index;
wx.previewMedia({
current: currentIndex,
sources: allSources as WechatMiniprogram.MediaSource[]
});
},
/** 删除附件 */
deleteMedia(e: WechatMiniprogram.BaseEvent) {
const isNewMedia = e.currentTarget.dataset.newMedia;
const index = e.currentTarget.dataset.index;
if (isNewMedia) {
const mediaList = [...this.data.mediaList];
mediaList.splice(index, 1);
this.setData({ mediaList });
} else {
const newMediaList = [...this.data.newMediaList];
newMediaList.splice(index, 1);
this.setData({ newMediaList });
}
},
/** 取消编辑 */
cancel() {
wx.navigateBack();
},
/** 删除记录 */
deleteJournal() {
this.setData({
deleteDialogVisible: true,
deleteConfirmText: ""
});
},
/** 取消删除 */
cancelDelete() {
this.setData({
deleteDialogVisible: false,
deleteConfirmText: ""
});
},
/** 确认删除 */
confirmDelete() {
const inputText = this.data.deleteConfirmText.trim();
if (inputText !== "确认删除") {
wx.showToast({
title: "输入不匹配",
icon: "error"
});
return;
}
this.setData({
deleteDialogVisible: false
});
this.executeDelete();
},
/** 执行删除 */
executeDelete() {
wx.showLoading({ title: "删除中...", mask: true });
wx.request({
url: `${config.url}/journal/delete`,
method: "POST",
header: {
Key: wx.getStorageSync("key"),
"Content-Type": "application/json"
},
data: this.data.id,
success: (res: any) => {
wx.hideLoading();
if (res.data.code === 20000 || res.statusCode === 200) {
Events.emit("JOURNAL_REFRESH");
Events.emit("JOURNAL_LIST_REFRESH");
wx.showToast({
title: "删除成功",
icon: "success"
});
setTimeout(() => {
wx.navigateBack();
}, 1000);
} else {
wx.showToast({
title: res.data.message || "删除失败",
icon: "error"
});
}
},
fail: () => {
wx.hideLoading();
wx.showToast({
title: "删除失败",
icon: "error"
});
}
});
},
/** 保存 */
save() {
const handleFail = () => {
wx.showToast({ title: "保存失败", icon: "error" });
this.setData({
saveText: "保存",
isSaving: false
});
};
this.setData({
saveText: "正在保存..",
isSaving: true
});
// 收集保留的附件 ID缩略图 ID
const attachmentIds = this.data.mediaList.map(item => item.attachmentId);
// 上传新媒体文件
const uploadFiles = new Promise<string[]>((resolve, reject) => {
const total = this.data.newMediaList.length;
let completed = 0;
if (total === 0) {
resolve([]);
return;
}
this.setData({
saveProgress: 0,
});
// 上传临时文件
const uploadPromises = this.data.newMediaList.map((item) => {
return new Promise<string>((uploadResolve, uploadReject) => {
wx.uploadFile({
url: `${config.url}/temp/file/upload`,
filePath: item.path,
name: "file",
success: (resp) => {
const result = JSON.parse(resp.data);
if (result && result.code === 20000) {
completed++;
// 更新进度
this.setData({
saveProgress: (completed / total),
});
uploadResolve(result.data[0].id);
} else {
uploadReject(new Error(`文件上传失败: ${result?.message || '未知错误'}`));
}
},
fail: (err) => uploadReject(new Error(`文件上传失败: ${err.errMsg}`))
});
});
});
// 并行执行所有文件上传
Promise.all(uploadPromises).then((tempFileIds) => {
this.setData({
saveProgress: 1,
});
resolve(tempFileIds);
}).catch(reject);
});
// 提交保存
uploadFiles.then((tempFileIds) => {
wx.request({
url: `${config.url}/journal/update`,
method: "POST",
header: {
Key: wx.getStorageSync("key")
},
data: {
id: this.data.id,
idea: this.data.idea,
lat: this.data.location?.lat,
lng: this.data.location?.lng,
location: this.data.location?.text,
createdAt: new Date(`${this.data.date}T${this.data.time}:00`).getTime(),
// 保留的现有附件 ID
attachmentIds,
// 新上传的临时文件 ID
tempFileIds
},
success: async (resp: any) => {
if (resp.data.code === 20000 || resp.statusCode === 200) {
Events.emit("JOURNAL_REFRESH");
Events.emit("JOURNAL_LIST_REFRESH");
wx.showToast({ title: "保存成功", icon: "success" });
this.setData({
saveText: "保存",
isSaving: false,
});
await Toolkit.sleep(1000);
wx.navigateBack();
} else {
handleFail();
}
},
fail: handleFail
});
}).catch(handleFail);
}
});