// 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, JournalType } from "../../../types/Journal"; import { MediaAttachExt, MediaAttachType } from "../../../types/Attachment"; interface JournalEditorData { id?: number; idea: string; date: string; time: string; type: JournalType; mediaList: MediaItem[]; newMediaList: WechatMediaItem[]; location?: Location; isAuthLocation: boolean; isLoading: boolean; saveText: string; isSaving: boolean; saveProgress: number; mediaItemTypeEnum: any; deleteDialogVisible: boolean; deleteConfirmText: string; } Page({ data: { id: undefined, idea: "", date: "2025-06-28", time: "16:00", type: JournalType.NORMAL, 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), type: journal.type, 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); } }, onChangeType(e: any) { const { value } = e.detail; this.setData({ type: value }); }, /** 选择位置 */ 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: (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 itemIndex = isNewMedia ? this.data.mediaList.length + index : index; const total = allSources.length; const startIndex = Math.max(0, itemIndex - 25); const endIndex = Math.min(total, startIndex + 50); const newCurrentIndex = itemIndex - startIndex; wx.previewMedia({ current: newCurrentIndex, sources: allSources.slice(startIndex, endIndex) 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: { id: 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((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((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, type: this.data.type, 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); } });