From 62186abdb865db03dbfec4fcad852f0c5509cec6 Mon Sep 17 00:00:00 2001 From: Timi Date: Wed, 17 Dec 2025 16:56:33 +0800 Subject: [PATCH] apply Network.ts --- miniprogram/api/JournalApi.ts | 89 +++++ miniprogram/api/MomentApi.ts | 69 ++++ .../components/journal-detail-popup/index.ts | 20 +- miniprogram/components/journal-list/index.ts | 121 +++--- .../components/travel-location-popup/index.ts | 8 +- miniprogram/pages/index/index.ts | 40 +- miniprogram/pages/main/journal-date/index.ts | 30 +- .../pages/main/journal-editor/index.ts | 282 ++++++-------- miniprogram/pages/main/journal-map/index.ts | 31 +- miniprogram/pages/main/journal/index.ts | 121 +++--- miniprogram/pages/main/moment/index.ts | 344 ++++++------------ miniprogram/pages/main/portfolio/index.ts | 123 +++---- .../main/travel-location-detail/index.ts | 2 +- 13 files changed, 584 insertions(+), 696 deletions(-) create mode 100644 miniprogram/api/JournalApi.ts create mode 100644 miniprogram/api/MomentApi.ts diff --git a/miniprogram/api/JournalApi.ts b/miniprogram/api/JournalApi.ts new file mode 100644 index 0000000..4bc43c3 --- /dev/null +++ b/miniprogram/api/JournalApi.ts @@ -0,0 +1,89 @@ +import { Network } from "../utils/Network"; +import { Journal, JournalPage } from "../types/Journal"; +import { QueryPageResult } from "../types/Model"; + +/** + * Journal 日记 API + * + * 按业务模块封装网络请求,使代码更清晰、可维护 + */ +export class JournalApi { + /** + * 获取日记详情 + * + * @param id - 日记 ID + */ + static getDetail(id: number | string): Promise { + return Network.post(`/journal/${id}`); + } + + /** + * 日记分页列表 + * + * @param pageParams - 分页参数 + */ + static getList(pageParams: JournalPage): Promise> { + return Network.post>("/journal/list", pageParams); + } + + /** + * 创建日记 + * + * @param data - 日记数据 + */ + static create(data: Partial & { + pusher?: string; + tempFileIds?: string[]; + }): Promise { + return Network.post("/journal/create", data, { + showLoading: true, + loadingText: "正在保存.." + }); + } + + /** + * 更新日记 + * + * @param data - 日记数据(必须包含 id) + */ + static update(data: Partial & { + id: number; + attachmentIds?: number[]; + tempFileIds?: string[]; + }): Promise { + return Network.post("/journal/update", data, { + showLoading: true, + loadingText: "正在保存.." + }); + } + + /** + * 删除日记 + * + * @param id - 日记 ID + */ + static delete(id: number): Promise { + return Network.post("/journal/delete", { id }, { + showLoading: true, + loadingText: "删除中..." + }); + } + + /** + * 获取 openId(用于推送) + * + * @param code - 微信登录 code + */ + static getOpenId(code: string): Promise { + return Network.post("/journal/openid", { code }); + } + + /** + * 根据 ID 列表获取日记 + * + * @param ids - 日记 ID 列表 + */ + static getListByIds(ids: number[]): Promise { + return Network.post("/journal/list/ids", ids); + } +} diff --git a/miniprogram/api/MomentApi.ts b/miniprogram/api/MomentApi.ts new file mode 100644 index 0000000..170053b --- /dev/null +++ b/miniprogram/api/MomentApi.ts @@ -0,0 +1,69 @@ +import { Network } from "../utils/Network"; +import { Attachment } from "../types/Attachment"; + +/** + * Moment 瞬间 API + * + * 管理临时照片/视频上传、归档到日记等操作 + */ +export class MomentApi { + /** + * 获取 moment 列表 + */ + static getList(): Promise { + return Network.post("/journal/moment/list"); + } + + /** + * MD5 查重过滤 + * + * @param md5s - MD5 值数组 + * @returns 未重复的 MD5 数组 + */ + static filterByMD5(md5s: string[]): Promise { + return Network.post("/journal/moment/filter", md5s); + } + + /** + * 创建 moment 附件 + * + * @param tempFileIds - 临时文件 ID 数组 + * @returns 创建的附件列表 + */ + static create(tempFileIds: string[]): Promise { + return Network.post("/journal/moment/create", tempFileIds, { + showLoading: true, + loadingText: "正在保存.." + }); + } + + /** + * 归档 moments 到日记 + * + * @param data - 归档数据 + */ + static archive(data: { + id?: number; + type: string; + idea: string; + lat?: number; + lng?: number; + location?: string; + pusher: string; + thumbIds: number[]; + }): Promise { + return Network.post("/journal/moment/archive", data, { + showLoading: true, + loadingText: "正在归档.." + }); + } + + /** + * 删除 moments + * + * @param ids - 附件 ID 数组 + */ + static delete(ids: number[]): Promise { + return Network.post("/journal/moment/delete", ids); + } +} diff --git a/miniprogram/components/journal-detail-popup/index.ts b/miniprogram/components/journal-detail-popup/index.ts index 713cbf6..a31cb7c 100644 --- a/miniprogram/components/journal-detail-popup/index.ts +++ b/miniprogram/components/journal-detail-popup/index.ts @@ -5,6 +5,7 @@ import Toolkit from "../../utils/Toolkit"; import { ImageMetadata, MediaAttachExt, MediaAttachType } from "../../types/Attachment"; import { MediaItem, MediaItemType } from "../../types/UI"; import Time from "../../utils/Time"; +import { JournalApi } from "../../api/JournalApi"; interface JournalDetailPanelData { journals: Journal[]; @@ -35,24 +36,7 @@ Component({ if (visible && ids && 0 < ids.length) { wx.showLoading({ title: "加载中...", mask: true }); try { - const journals: Journal[] = await new Promise((resolve, reject) => { - wx.request({ - url: `${config.url}/journal/list/ids`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: ids, - success: (resp: any) => { - if (resp.data.code === 20000) { - resolve(resp.data.data); - } else { - reject(new Error(resp.data.message || "加载失败")); - } - }, - fail: reject - }); - }) || []; + const journals = await JournalApi.getListByIds(ids); journals.forEach(journal => { journal.date = Time.toPassedDate(journal.createdAt); journal.time = Time.toTime(journal.createdAt); diff --git a/miniprogram/components/journal-list/index.ts b/miniprogram/components/journal-list/index.ts index 9a4454f..3247dde 100644 --- a/miniprogram/components/journal-list/index.ts +++ b/miniprogram/components/journal-list/index.ts @@ -4,6 +4,7 @@ import { JournalPage, JournalPageType } from "../../types/Journal"; import { OrderType } from "../../types/Model"; import Time from "../../utils/Time"; import Toolkit from "../../utils/Toolkit"; +import { JournalApi } from "../../api/JournalApi"; export type JournalListItem = { id: number; @@ -19,14 +20,10 @@ interface JournalListData { isFinished: boolean; page: JournalPage; searchValue: string; + debouncedSearch?: any; } -// 组件实例类型扩展 -interface ComponentInstance { - debouncedSearch?: ((keyword: string) => void) & { cancel(): void }; -} - -Component({ +Component({ options: { styleIsolation: 'apply-shared' }, @@ -64,25 +61,28 @@ Component({ createdAt: OrderType.DESC } }, - searchValue: "" + searchValue: "", + debouncedSearch: undefined }, lifetimes: { ready() { // 创建防抖搜索函数 - this.debouncedSearch = Toolkit.debounce( - (keyword: string) => { - this.resetAndSearch(keyword); - }, - false, // 不立即执行,等待输入停止 - 400 // 400ms 延迟 - ); + this.setData({ + debouncedSearch: Toolkit.debounce( + (keyword: string) => { + this.resetAndSearch(keyword); + }, + false, // 不立即执行,等待输入停止 + 400 // 400ms 延迟 + ) + }) // 组件加载时就获取数据 this.fetch(); }, detached() { // 组件销毁时取消防抖 - if (this.debouncedSearch) { - this.debouncedSearch.cancel(); + if (this.data.debouncedSearch) { + this.data.debouncedSearch.cancel(); } } }, @@ -125,77 +125,74 @@ Component({ }); }, /** 获取数据 */ - fetch() { + async fetch() { if (this.data.isFetching || this.data.isFinished) { return; } this.setData({ isFetching: true }); - wx.request({ - url: `${config.url}/journal/list`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: this.data.page, - success: (resp: any) => { - const list = resp.data.data.list; - if (!list || list.length === 0) { - this.setData({ isFinished: true }); - return; - } - const result = list.map((journal: any) => { - const firstThumb = journal.items.find((item: any) => item.attachType === "THUMB"); - return { - id: journal.id, - date: Time.toPassedDate(journal.createdAt), - idea: journal.idea, - location: journal.location, - thumbUrl: firstThumb ? `${config.url}/attachment/read/${firstThumb.mongoId}` : undefined - } as JournalListItem; - }); + try { + const pageResult = await JournalApi.getList(this.data.page); + const list = pageResult.list; + if (!list || list.length === 0) { this.setData({ - page: { - ...this.data.page, - index: this.data.page.index + 1, - type: JournalPageType.PREVIEW, - equalsExample: this.data.page.equalsExample, - likeExample: this.data.page.likeExample, - orderMap: { - createdAt: OrderType.DESC - } - }, - list: this.data.list.concat(result), - isFinished: list.length < this.data.page.size + isFinished: true, + isFetching: false }); - }, - complete: () => { - this.setData({ isFetching: false }); + return; } - }); + const result = list.map((journal: any) => { + const firstThumb = journal.items.find((item: any) => item.attachType === "THUMB"); + return { + id: journal.id, + date: Time.toPassedDate(journal.createdAt), + idea: journal.idea, + location: journal.location, + thumbUrl: firstThumb ? `${config.url}/attachment/read/${firstThumb.mongoId}` : undefined + } as JournalListItem; + }); + this.setData({ + page: { + ...this.data.page, + index: this.data.page.index + 1, + type: JournalPageType.PREVIEW, + equalsExample: this.data.page.equalsExample, + likeExample: this.data.page.likeExample, + orderMap: { + createdAt: OrderType.DESC + } + }, + list: this.data.list.concat(result), + isFinished: list.length < this.data.page.size, + isFetching: false + }); + } catch (error) { + console.error("加载日记列表失败:", error); + this.setData({ isFetching: false }); + } }, /** 输入搜索 */ onSearchChange(e: WechatMiniprogram.CustomEvent) { const value = e.detail.value.trim(); this.setData({ searchValue: value }); // 使用防抖自动搜索(包括清空的情况) - if (this.debouncedSearch) { - this.debouncedSearch(value); + if (this.data.debouncedSearch) { + this.data.debouncedSearch(value); } }, /** 提交搜索 */ onSearchSubmit(e: WechatMiniprogram.CustomEvent) { const value = e.detail.value.trim(); // 立即搜索,取消防抖 - if (this.debouncedSearch) { - this.debouncedSearch.cancel(); + if (this.data.debouncedSearch) { + this.data.debouncedSearch.cancel(); } this.resetAndSearch(value); }, /** 清空搜索 */ onSearchClear() { // 取消防抖,立即搜索 - if (this.debouncedSearch) { - this.debouncedSearch.cancel(); + if (this.data.debouncedSearch) { + this.data.debouncedSearch.cancel(); } this.resetAndSearch(""); }, diff --git a/miniprogram/components/travel-location-popup/index.ts b/miniprogram/components/travel-location-popup/index.ts index 6273616..0165081 100644 --- a/miniprogram/components/travel-location-popup/index.ts +++ b/miniprogram/components/travel-location-popup/index.ts @@ -1,7 +1,7 @@ // components/travel-location-popup/index.ts import { TravelLocation, TravelLocationTypeLabel, TravelLocationTypeIcon } from "../../types/Travel"; import { TravelLocationApi } from "../../api/TravelLocationApi"; -import { MediaAttachExt, MediaAttachType } from "../../types/Attachment"; +import { ImageMetadata, MediaAttachType } from "../../types/Attachment"; import { MediaItem, MediaItemType } from "../../types/UI"; import config from "../../config/index"; import Toolkit from "../../utils/Toolkit"; @@ -39,11 +39,11 @@ Component({ // 处理附件数据 const attachments = location.items || []; - const thumbItems = attachments.filter((item: any) => item.attachType === MediaAttachType.THUMB); + const thumbItems = attachments.filter(item => item.attachType === MediaAttachType.THUMB); if (0 < thumbItems.length) { - const mediaItems: MediaItem[] = thumbItems.map((thumbItem: any, index: number) => { - const metadata = thumbItem.metadata; + const mediaItems: MediaItem[] = thumbItems.map((thumbItem, index) => { + const metadata = thumbItem.metadata as ImageMetadata; const ext = typeof thumbItem.ext === "string" ? JSON.parse(thumbItem.ext) : thumbItem.ext; const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; diff --git a/miniprogram/pages/index/index.ts b/miniprogram/pages/index/index.ts index d3c782d..3b803d7 100644 --- a/miniprogram/pages/index/index.ts +++ b/miniprogram/pages/index/index.ts @@ -2,6 +2,7 @@ import config from "../../config/index" import { JournalPage, JournalPageType } from "../../types/Journal"; +import { JournalApi } from "../../api/JournalApi"; interface IndexData { key: string; @@ -19,32 +20,23 @@ Page({ }); } }, - navigateToMain() { - wx.request({ - url: `${config.url}/journal/list`, - method: "POST", - header: { - Key: this.data.key - }, - data: { + async navigateToMain() { + try { + await JournalApi.getList({ index: 0, size: 1, type: JournalPageType.PREVIEW - }, - success: (resp) => { - const data = resp.data as any; - if (data.code === 20000) { - wx.setStorageSync("key", this.data.key); - wx.switchTab({ - url: "/pages/main/journal/index", - }) - } else if (data.code === 40100) { - wx.showToast({ title: "密码错误", icon: "error" }); - } else { - wx.showToast({ title: "服务异常", icon: "error" }); - } - }, - fail: () => wx.showToast({ title: "验证失败", icon: "error" }) - }); + }); + wx.setStorageSync("key", this.data.key); + wx.switchTab({ + url: "/pages/main/journal/index", + }) + } catch (error: any) { + if (error?.code === 40100) { + wx.showToast({ title: "密码错误", icon: "error" }); + } else { + wx.showToast({ title: "验证失败", icon: "error" }); + } + } } }) diff --git a/miniprogram/pages/main/journal-date/index.ts b/miniprogram/pages/main/journal-date/index.ts index 9aec825..5cc752b 100644 --- a/miniprogram/pages/main/journal-date/index.ts +++ b/miniprogram/pages/main/journal-date/index.ts @@ -1,7 +1,7 @@ // pages/main/journal-date/index.ts -import config from "../../../config/index"; import { Journal, JournalPageType } from "../../../types/Journal"; import Time from "../../../utils/Time"; +import { JournalApi } from "../../../api/JournalApi"; interface JournalDateData { // 存储每个日期的日记 id 列表 @@ -27,28 +27,12 @@ Page({ async loadJournals() { this.setData({ isLoading: true }); try { - const list: Journal[] = await new Promise((resolve, reject) => { - wx.request({ - url: `${config.url}/journal/list`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: { - page: 0, - size: 9007199254740992, - type: JournalPageType.PREVIEW - }, - success: (resp: any) => { - if (resp.data.code === 20000) { - resolve(resp.data.data.list); - } else { - reject(new Error(resp.data.message || "加载失败")); - } - }, - fail: reject - }); - }) || []; + const pageResult = await JournalApi.getList({ + index: 0, + size: 9007199254740992, + type: JournalPageType.PREVIEW + }); + const list: Journal[] = pageResult.list || []; // 按日期分组,只存储 id const journalMap: Record = {}; list.forEach((journal: any) => { diff --git a/miniprogram/pages/main/journal-editor/index.ts b/miniprogram/pages/main/journal-editor/index.ts index e91c764..8e2c174 100644 --- a/miniprogram/pages/main/journal-editor/index.ts +++ b/miniprogram/pages/main/journal-editor/index.ts @@ -4,9 +4,10 @@ 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 { JournalType } from "../../../types/Journal"; import { MediaAttachExt, MediaAttachType } from "../../../types/Attachment"; import IOSize, { Unit } from "../../../utils/IOSize"; +import { JournalApi } from "../../../api/JournalApi"; interface JournalEditorData { /** 模式:create 或 edit */ @@ -148,25 +149,8 @@ Page({ }, /** 加载日记详情(编辑模式) */ 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 journal = await JournalApi.getDetail(id); const items = journal.items || []; const thumbItems = items.filter((item) => item.attachType === MediaAttachType.THUMB); @@ -309,11 +293,11 @@ Page({ // 编辑模式:mediaList + newMediaList const sources = (this.data.mediaList as MediaItem[]).map(item => ({ url: item.sourceURL, - type: MediaItemType[item.type].toLowerCase() + type: item.type })); const newSources = this.data.newMediaList.map(item => ({ url: item.path, - type: MediaItemType[item.type].toLowerCase() + type: item.type })); const allSources = [...sources, ...newSources]; const itemIndex = isNewMedia ? this.data.mediaList.length + index : index; @@ -392,45 +376,21 @@ Page({ 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" - }); - } - }); + async executeDelete() { + try { + await JournalApi.delete(this.data.id!); + Events.emit("JOURNAL_REFRESH"); + Events.emit("JOURNAL_LIST_REFRESH"); + wx.showToast({ + title: "删除成功", + icon: "success" + }); + setTimeout(() => { + wx.navigateBack(); + }, 1000); + } catch (error) { + console.error("删除日记失败:", error); + } }, /** 提交/保存 */ submit() { @@ -441,10 +401,9 @@ Page({ } }, /** 创建日记 */ - createJournal() { + async createJournal() { const handleFail = () => { wx.showToast({ title: "上传失败", icon: "error" }); - wx.hideLoading(); this.setData({ isSaving: false }); @@ -452,83 +411,64 @@ Page({ this.setData({ isSaving: true }); - // 获取 openId - const getOpenId = new Promise((resolve, reject) => { - wx.login({ - success: (res) => { - if (res.code) { - wx.request({ - url: `${config.url}/journal/openid`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: { - code: res.code - }, - success: (resp) => { - const data = resp.data as any; - if (data.code === 20000) { - resolve(data.data); - } else { - reject(new Error("获取 openId 失败")); - } - }, - fail: () => reject(new Error("获取 openId 请求失败")) - }); - } else { - reject(new Error("获取登录凭证失败")); - } - }, - fail: () => reject(new Error("登录失败")) + try { + // 获取 openId + const getOpenId = new Promise((resolve, reject) => { + wx.login({ + success: async (res) => { + if (res.code) { + try { + const openId = await JournalApi.getOpenId(res.code); + resolve(openId); + } catch (error) { + reject(new Error("获取 openId 失败")); + } + } else { + reject(new Error("获取登录凭证失败")); + } + }, + fail: () => reject(new Error("登录失败")) + }); }); - }); - // 文件上传 - const uploadFiles = this.uploadMediaFiles(this.data.mediaList as WechatMediaItem[]); - // 并行执行获取 openId 和文件上传 - Promise.all([getOpenId, uploadFiles]).then(([openId, tempFileIds]) => { - wx.showLoading({ title: "正在保存..", mask: true }); - wx.request({ - url: `${config.url}/journal/create`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: { - idea: this.data.idea, - type: this.data.type, - lat: this.data.location?.lat, - lng: this.data.location?.lng, - location: this.data.location?.text, - pusher: openId, - createdAt: Date.parse(`${this.data.date} ${this.data.time}`), - tempFileIds - }, - success: async () => { - Events.emit("JOURNAL_REFRESH"); - wx.showToast({ title: "提交成功", icon: "success" }); - this.setData({ - idea: "", - mediaList: [], - isSaving: false, - uploaded: "0", - uploadTotal: "0 MB", - uploadProgress: 0 - }); - await Toolkit.sleep(1000); - wx.switchTab({ - url: "/pages/main/journal/index" - }); - }, - fail: handleFail + // 文件上传 + const uploadFiles = this.uploadMediaFiles(this.data.mediaList as WechatMediaItem[]); + // 并行执行获取 openId 和文件上传 + const [openId, tempFileIds] = await Promise.all([getOpenId, uploadFiles]); + + await JournalApi.create({ + idea: this.data.idea, + type: this.data.type, + lat: this.data.location?.lat, + lng: this.data.location?.lng, + location: this.data.location?.text, + pusher: openId, + createdAt: Date.parse(`${this.data.date} ${this.data.time}`), + tempFileIds }); - }).catch(handleFail); + + Events.emit("JOURNAL_REFRESH"); + wx.showToast({ title: "提交成功", icon: "success" }); + this.setData({ + idea: "", + mediaList: [], + isSaving: false, + uploaded: "0", + uploadTotal: "0 MB", + uploadProgress: 0 + }); + await Toolkit.sleep(1000); + wx.switchTab({ + url: "/pages/main/journal/index" + }); + } catch (error) { + console.error("创建日记失败:", error); + handleFail(); + } }, /** 更新日记 */ - updateJournal() { + async updateJournal() { const handleFail = () => { wx.showToast({ title: "保存失败", icon: "error" }); - wx.hideLoading(); this.setData({ isSaving: false }); @@ -536,53 +476,39 @@ Page({ this.setData({ isSaving: true }); - // 收集保留的附件 ID(缩略图 ID) - const attachmentIds = (this.data.mediaList as MediaItem[]).map(item => item.attachmentId); - // 上传新媒体文件 - const uploadFiles = this.uploadMediaFiles(this.data.newMediaList); + try { + // 收集保留的附件 ID(缩略图 ID) + const attachmentIds = (this.data.mediaList as MediaItem[]).map(item => item.attachmentId); + // 上传新媒体文件 + const tempFileIds = await this.uploadMediaFiles(this.data.newMediaList); - // 提交保存 - uploadFiles.then((tempFileIds) => { - wx.showLoading({ title: "正在保存..", mask: true }); - 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({ - isSaving: false, - uploaded: "0", - uploadTotal: "0 MB", - uploadProgress: 0 - }); - await Toolkit.sleep(1000); - wx.navigateBack(); - } else { - handleFail(); - } - }, - fail: handleFail + await JournalApi.update({ + 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(), + attachmentIds, + tempFileIds }); - }).catch(handleFail); + + Events.emit("JOURNAL_REFRESH"); + Events.emit("JOURNAL_LIST_REFRESH"); + wx.showToast({ title: "保存成功", icon: "success" }); + this.setData({ + isSaving: false, + uploaded: "0", + uploadTotal: "0 MB", + uploadProgress: 0 + }); + await Toolkit.sleep(1000); + wx.navigateBack(); + } catch (error) { + console.error("更新日记失败:", error); + handleFail(); + } }, /** 上传媒体文件 */ uploadMediaFiles(mediaList: WechatMediaItem[]): Promise { diff --git a/miniprogram/pages/main/journal-map/index.ts b/miniprogram/pages/main/journal-map/index.ts index a038295..ff8dcc5 100644 --- a/miniprogram/pages/main/journal-map/index.ts +++ b/miniprogram/pages/main/journal-map/index.ts @@ -1,9 +1,10 @@ // pages/main/journal-map/index.ts import config from "../../../config/index"; import Time from "../../../utils/Time"; -import { Journal, JournalPageType } from "../../../types/Journal"; +import { JournalPageType } from "../../../types/Journal"; import Toolkit from "../../../utils/Toolkit"; import { MapMarker } from "../../../types/UI"; +import { JournalApi } from "../../../api/JournalApi"; interface LocationMarker { locationKey: string; // 位置键 "lat,lng" @@ -50,28 +51,12 @@ Page({ async loadJournals() { this.setData({ isLoading: true }); try { - const list: Journal[] = await new Promise((resolve, reject) => { - wx.request({ - url: `${config.url}/journal/list`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: { - page: 0, - size: 9007199254740992, - type: JournalPageType.PREVIEW - }, - success: (resp: any) => { - if (resp.data.code === 20000) { - resolve(resp.data.data.list); - } else { - reject(new Error(resp.data.message || "加载失败")); - } - }, - fail: reject - }); - }) || []; + const result = await JournalApi.getList({ + index: 0, + size: 9007199254740992, + type: JournalPageType.PREVIEW + }); + const list = result.list || []; // 过滤有位置信息的记录,并按位置分组 const locationMap = new Map(); list.filter((journal: any) => journal.lat && journal.lng).forEach((journal: any) => { diff --git a/miniprogram/pages/main/journal/index.ts b/miniprogram/pages/main/journal/index.ts index 86ba229..6bd787d 100644 --- a/miniprogram/pages/main/journal/index.ts +++ b/miniprogram/pages/main/journal/index.ts @@ -5,9 +5,10 @@ import config from "../../../config/index" import Events from "../../../utils/Events"; import Toolkit from "../../../utils/Toolkit"; import { Journal, JournalPage, JournalPageType } from "../../../types/Journal"; -import { OrderType, QueryPageResult } from "../../../types/Model"; +import { OrderType } from "../../../types/Model"; import { ImageMetadata, MediaAttachExt } from "../../../types/Attachment"; import { MediaItem, MediaItemType } from "../../../types/UI"; +import { JournalApi } from "../../../api/JournalApi"; interface JournalData { page: JournalPage; @@ -143,79 +144,71 @@ Page({ url: "/pages/main/journal-date/index" }) }, - fetch() { + async fetch() { if (this.data.isFetching || this.data.isFinished) { return; } this.setData({ isFetching: true }); - wx.request({ - url: `${config.url}/journal/list`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: this.data.page, - success: async (resp: any) => { - const pageResult = resp.data.data as QueryPageResult; - const list = pageResult.list; - if (!list || list.length === 0) { - this.setData({ - isFinished: true - }) - return; - } - list.forEach(journal => { - const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => { - const metadata = thumbItem.metadata as ImageMetadata; - 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, - width: metadata.width, - height: metadata.height, - originalIndex: index - } as MediaItem; - }); - journal.date = Time.toDate(journal.createdAt); - journal.time = Time.toTime(journal.createdAt); - journal.datetime = Time.toPassedDateTime(journal.createdAt); - journal.mediaItems = mediaItems; - journal.columnedItems = Toolkit.splitItemsIntoColumns(mediaItems, 3, (item) => { - if (item.width && item.height && 0 < item.width) { - return item.height / item.width; - } - return 1; - }) - }) - this.setData({ - page: { - index: this.data.page.index + 1, - size: 8, - type: JournalPageType.NORMAL, - equalsExample: { - type: "NORMAL" - }, - orderMap: { - createdAt: OrderType.DESC - } - }, - list: this.data.list.concat(list), - isFinished: list.length < this.data.page.size - }); - }, - complete: () => { + try { + const pageResult = await JournalApi.getList(this.data.page); + const list = pageResult.list; + if (!list || list.length === 0) { this.setData({ + isFinished: true, isFetching: false }); + return; } - }); + list.forEach(journal => { + const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => { + const metadata = thumbItem.metadata as ImageMetadata; + 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, + width: metadata.width, + height: metadata.height, + originalIndex: index + } as MediaItem; + }); + journal.date = Time.toDate(journal.createdAt); + journal.time = Time.toTime(journal.createdAt); + journal.datetime = Time.toPassedDateTime(journal.createdAt); + journal.mediaItems = mediaItems; + journal.columnedItems = Toolkit.splitItemsIntoColumns(mediaItems, 3, (item) => { + if (item.width && item.height && 0 < item.width) { + return item.height / item.width; + } + return 1; + }) + }); + this.setData({ + page: { + index: this.data.page.index + 1, + size: 8, + type: JournalPageType.NORMAL, + equalsExample: { + type: "NORMAL" + }, + orderMap: { + createdAt: OrderType.DESC + } + }, + list: this.data.list.concat(list), + isFinished: list.length < this.data.page.size, + isFetching: false + }); + } catch (error) { + console.error("加载日记失败:", error); + this.setData({ isFetching: false }); + } }, preview(e: WechatMiniprogram.BaseEvent) { const { journalIndex, itemIndex } = e.currentTarget.dataset; diff --git a/miniprogram/pages/main/moment/index.ts b/miniprogram/pages/main/moment/index.ts index 32edc67..70de4d5 100644 --- a/miniprogram/pages/main/moment/index.ts +++ b/miniprogram/pages/main/moment/index.ts @@ -6,6 +6,9 @@ import Time from "../../../utils/Time"; import Toolkit from "../../../utils/Toolkit"; import { Location, MediaItemType } from "../../../types/UI"; import { MediaAttachExt } from "../../../types/Attachment"; +import { MomentApi } from "../../../api/MomentApi"; +import { JournalApi } from "../../../api/JournalApi"; +import { Network } from "../../../utils/Network"; type Item = { id: number; @@ -123,34 +126,29 @@ Page({ } }); }, - fetch() { - wx.request({ - url: `${config.url}/journal/moment/list`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - success: async (resp: any) => { - const list = resp.data.data; - if (!list || list.length === 0) { - return; - } - this.setData({ - list: list.map((item: any) => { - const ext = JSON.parse(item.ext) as MediaAttachExt; - const thumbURL = `${config.url}/attachment/read/${item.mongoId}`; - const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; - return { - id: item.id, - type: ext.isImage ? MediaItemType.IMAGE : MediaItemType.VIDEO, - thumbURL, - sourceURL, - checked: false - } as Item; - }) - }); + async fetch() { + try { + const list = await MomentApi.getList(); + if (!list || list.length === 0) { + return; } - }); + this.setData({ + list: list.map((item: any) => { + const ext = JSON.parse(item.ext) as MediaAttachExt; + const thumbURL = `${config.url}/attachment/read/${item.mongoId}`; + const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; + return { + id: item.id, + type: ext.isImage ? MediaItemType.IMAGE : MediaItemType.VIDEO, + thumbURL, + sourceURL, + checked: false + } as Item; + }) + }); + } catch (error) { + console.error("加载 moment 列表失败:", error); + } }, updateHasChecked() { this.setData({ hasChecked: this.data.list.some(item => item.checked) }); @@ -226,155 +224,65 @@ Page({ } as MD5Result); })); // 查重 - const filterMD5Result: string[] = await new Promise((resolve, reject) => { - wx.request({ - url: `${config.url}/journal/moment/filter`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: md5Results.map(item => item.md5), - success: async (resp: any) => { - resolve(resp.data.data); - }, - fail: reject - }); - }); + const filterMD5Result: string[] = await MomentApi.filterByMD5( + md5Results.map(item => item.md5) + ); // 过滤文件 const filterPath = md5Results.filter(item => filterMD5Result.indexOf(item.md5) !== -1) .map(item => item.path); files = files.filter(file => filterPath.indexOf(file.tempFilePath) !== -1); if (files.length === 0) { wx.hideLoading(); + that.setData({ isUploading: false }); return; } - wx.showLoading({ - title: "正在上传..", - mask: true - }) - // 计算上传大小 - const sizePromises = files.map(file => { - return new Promise((sizeResolve, sizeReject) => { - wx.getFileSystemManager().getFileInfo({ - filePath: file.tempFilePath, - success: (res) => sizeResolve(res.size), - fail: (err) => sizeReject(err) - }); - }); - }); - Promise.all(sizePromises).then(fileSizes => { - const totalSize = fileSizes.reduce((acc, size) => acc + size, 0); - const uploadTasks: WechatMiniprogram.UploadTask[] = []; - let uploadedSize = 0; - let lastUploadedSize = 0; + // 使用 Network.uploadFiles 上传文件 + try { + const tempFileIds = await Network.uploadFiles({ + mediaList: files.map(file => ({ + path: file.tempFilePath, + size: file.size + })), + onProgress: (progress) => { + that.setData({ + uploaded: IOSize.formatWithoutUnit(progress.uploaded, 2, Unit.MB), + uploadTotal: IOSize.format(progress.total, 2, Unit.MB), + uploadSpeed: `${IOSize.format(progress.speed)} / s`, + uploadProgress: progress.percent + }); + }, + showLoading: true + }); + + // 上传完成转附件 + const list = await MomentApi.create(tempFileIds); + wx.showToast({ title: "上传成功", icon: "success" }); + const added = list.map((item: any) => { + const ext = JSON.parse(item.ext) as MediaAttachExt; + const thumbURL = `${config.url}/attachment/read/${item.mongoId}`; + const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; + return { + id: item.id, + type: ext.isImage ? MediaItemType.IMAGE : MediaItemType.VIDEO, + thumbURL, + sourceURL, + checked: false + } as Item; + }); + // 前插列表 + that.data.list.unshift(...added); that.setData({ - uploadTotal: IOSize.format(totalSize, 2, Unit.MB) + list: that.data.list, + isUploading: false, + uploaded: "0", + uploadTotal: "0 MB", + uploadProgress: 0 }); - - // 计算上传速度 - const speedUpdateInterval = setInterval(() => { - const chunkSize = uploadedSize - lastUploadedSize; - that.setData({ - uploadSpeed: `${IOSize.format(chunkSize)} / s` - }); - lastUploadedSize = uploadedSize; - }, 1000); - // 上传文件 - const uploadPromises = files.map(file => { - return new Promise((uploadResolve, uploadReject) => { - const task = wx.uploadFile({ - url: `${config.url}/temp/file/upload`, - filePath: file.tempFilePath, - name: "file", - success: (resp) => { - const result = JSON.parse(resp.data); - if (result && result.code === 20000) { - // 更新进度 - const progress = totalSize > 0 ? uploadedSize / totalSize : 1; - that.setData({ - uploadProgress: Math.round(progress * 10000) / 100 - }); - uploadResolve(result.data[0].id); - } else { - uploadReject(new Error(`文件上传失败: ${result?.message || "未知错误"}`)); - } - }, - fail: (err) => uploadReject(new Error(`文件上传失败: ${err.errMsg}`)) - }); - // 监听上传进度事件 - let prevProgress = 0; - task.onProgressUpdate((res) => { - const fileUploaded = (res.totalBytesExpectedToSend * res.progress) / 100; - const delta = fileUploaded - prevProgress; - uploadedSize += delta; - // 保存当前进度 - prevProgress = fileUploaded; - // 更新进度条 - that.setData({ - uploaded: IOSize.formatWithoutUnit(uploadedSize, 2, Unit.MB), - uploadProgress: Math.round((uploadedSize / totalSize) * 10000) / 100 - }); - }); - uploadTasks.push(task); - }); - }); - Promise.all(uploadPromises).then((tempFileIds) => { - wx.showLoading({ - title: "正在保存..", - mask: true - }) - // 清除定时器 - clearInterval(speedUpdateInterval); - uploadTasks.forEach(task => task.offProgressUpdate()); - that.setData({ - uploadProgress: 100, - uploadSpeed: "0 MB / s" - }); - // 上传完成转附件 - wx.request({ - url: `${config.url}/journal/moment/create`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: tempFileIds, - success: async (resp: any) => { - wx.showToast({ title: "上传成功", icon: "success" }); - const list = resp.data.data; - const added = list.map((item: any) => { - const ext = JSON.parse(item.ext) as MediaAttachExt; - const thumbURL = `${config.url}/attachment/read/${item.mongoId}`; - const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; - return { - id: item.id, - type: ext.isImage ? MediaItemType.IMAGE : MediaItemType.VIDEO, - thumbURL, - sourceURL, - checked: false - } as Item; - }); - // 前插列表 - that.data.list.unshift(...added); - that.setData({ - list: that.data.list, - isUploading: false, - uploaded: "0", - uploadTotal: "0 MB", - uploadProgress: 0 - }); - that.updateHasChecked(); - wx.hideLoading(); - }, - fail: handleFail - }); - }).catch((e: Error) => { - // 取消所有上传任务 - uploadTasks.forEach(task => task.abort()); - that.updateHasChecked(); - handleFail(e); - }); - }).catch(handleFail); + that.updateHasChecked(); + } catch (error) { + handleFail(error); + } } }) }, @@ -450,27 +358,14 @@ Page({ }) const openId = await new Promise((resolve, reject) => { wx.login({ - success: (res) => { + success: async (res) => { if (res.code) { - wx.request({ - url: `${config.url}/journal/openid`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: { - code: res.code - }, - success: (resp) => { - const data = resp.data as any; - if (data.code === 20000) { - resolve(data.data); - } else { - reject(new Error("获取 openId 失败")); - } - }, - fail: () => reject(new Error("获取 openId 请求失败")) - }); + try { + const openId = await JournalApi.getOpenId(res.code); + resolve(openId); + } catch (error) { + reject(new Error("获取 openId 失败")); + } } else { reject(new Error("获取登录凭证失败")); } @@ -491,36 +386,23 @@ Page({ if (this.data.selectedJournalId) { archiveData.id = this.data.selectedJournalId; } - wx.request({ - url: `${config.url}/journal/moment/archive`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: archiveData, - success: async (resp: any) => { - if (resp.data && resp.data.code === 20000) { - Events.emit("JOURNAL_REFRESH"); - wx.showToast({ title: "归档成功", icon: "success" }); - this.setData({ - idea: "", - list: [], - hasChecked: false, - isArchiving: false, - selectedJournalId: undefined, - isVisibleArchivePopup: false - }); - await Toolkit.sleep(1000); - this.fetch(); - } else { - wx.showToast({ title: "归档失败", icon: "error" }); - this.setData({ - isArchiving: false - }); - } - }, - fail: handleFail - }); + try { + await MomentApi.archive(archiveData); + Events.emit("JOURNAL_REFRESH"); + wx.showToast({ title: "归档成功", icon: "success" }); + this.setData({ + idea: "", + list: [], + hasChecked: false, + isArchiving: false, + selectedJournalId: undefined, + isVisibleArchivePopup: false + }); + await Toolkit.sleep(1000); + this.fetch(); + } catch (error) { + handleFail(); + } }, allChecked() { this.data.list.forEach(item => item.checked = true); @@ -556,27 +438,21 @@ Page({ confirmText: "删除已选", confirmColor: "#E64340", cancelText: "取消", - success: res => { + success: async (res) => { if (res.confirm) { const selected = this.data.list.filter(item => item.checked); - wx.request({ - url: `${config.url}/journal/moment/delete`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: selected.map(item => item.id), - success: async (resp: any) => { - if (resp.data && resp.data.code === 20000) { - wx.showToast({ title: "删除成功", icon: "success" }); - const list = this.data.list.filter(item => !item.checked); - this.setData({ - list - }); - this.updateHasChecked(); - } - }, - }); + try { + await MomentApi.delete(selected.map(item => item.id)); + wx.showToast({ title: "删除成功", icon: "success" }); + const list = this.data.list.filter(item => !item.checked); + this.setData({ + list + }); + this.updateHasChecked(); + } catch (error) { + console.error("删除 moment 失败:", error); + wx.showToast({ title: "删除失败", icon: "error" }); + } } } }) diff --git a/miniprogram/pages/main/portfolio/index.ts b/miniprogram/pages/main/portfolio/index.ts index 9ed195c..879c3c7 100644 --- a/miniprogram/pages/main/portfolio/index.ts +++ b/miniprogram/pages/main/portfolio/index.ts @@ -5,9 +5,10 @@ import config from "../../../config/index" import Events from "../../../utils/Events"; import Toolkit from "../../../utils/Toolkit"; import { Journal, JournalPage, JournalPageType } from "../../../types/Journal"; -import { OrderType, QueryPageResult } from "../../../types/Model"; +import { OrderType, } from "../../../types/Model"; import { ImageMetadata, MediaAttachExt } from "../../../types/Attachment"; import { MediaItem, MediaItemType } from "../../../types/UI"; +import { JournalApi } from "../../../api/JournalApi"; interface IPortfolioData { page: JournalPage; @@ -72,79 +73,71 @@ Page({ this.setData({ stickyOffset: height }); }); }, - fetch() { + async fetch() { if (this.data.isFetching || this.data.isFinished) { return; } this.setData({ isFetching: true }); - wx.request({ - url: `${config.url}/journal/list`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: this.data.page, - success: async (resp: any) => { - const pageResult = resp.data.data as QueryPageResult; - const list = pageResult.list; - if (!list || list.length === 0) { - this.setData({ - isFinished: true - }) - return; - } - list.forEach(journal => { - const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => { - const metadata = thumbItem.metadata as ImageMetadata; - 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, - width: metadata.width, - height: metadata.height, - originalIndex: index - } as MediaItem; - }); - journal.date = Time.toDate(journal.createdAt); - journal.time = Time.toTime(journal.createdAt); - journal.datetime = Time.toPassedDateTime(journal.createdAt); - journal.mediaItems = mediaItems; - journal.columnedItems = Toolkit.splitItemsIntoColumns(mediaItems, 3, (item) => { - if (item.width && item.height && 0 < item.width) { - return item.height / item.width; - } - return 1; - }) - }) - this.setData({ - page: { - index: this.data.page.index + 1, - size: 8, - type: JournalPageType.NORMAL, - equalsExample: { - type: "PORTFOLIO" - }, - orderMap: { - createdAt: OrderType.DESC - } - }, - list: this.data.list.concat(list), - isFinished: list.length < this.data.page.size - }); - }, - complete: () => { + try { + const pageResult = await JournalApi.getList(this.data.page); + const list = pageResult.list; + if (!list || list.length === 0) { this.setData({ + isFinished: true, isFetching: false - }); + }) + return; } - }); + list.forEach(journal => { + const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => { + const metadata = thumbItem.metadata as ImageMetadata; + 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, + width: metadata.width, + height: metadata.height, + originalIndex: index + } as MediaItem; + }); + journal.date = Time.toDate(journal.createdAt); + journal.time = Time.toTime(journal.createdAt); + journal.datetime = Time.toPassedDateTime(journal.createdAt); + journal.mediaItems = mediaItems; + journal.columnedItems = Toolkit.splitItemsIntoColumns(mediaItems, 3, (item) => { + if (item.width && item.height && 0 < item.width) { + return item.height / item.width; + } + return 1; + }) + }) + this.setData({ + page: { + index: this.data.page.index + 1, + size: 8, + type: JournalPageType.NORMAL, + equalsExample: { + type: "PORTFOLIO" + }, + orderMap: { + createdAt: OrderType.DESC + } + }, + list: this.data.list.concat(list), + isFinished: list.length < this.data.page.size, + isFetching: false + }); + } catch (error) { + console.error("加载 portfolio 列表失败:", error); + this.setData({ isFetching: false }); + } }, preview(e: WechatMiniprogram.BaseEvent) { const { journalIndex, itemIndex } = e.currentTarget.dataset; diff --git a/miniprogram/pages/main/travel-location-detail/index.ts b/miniprogram/pages/main/travel-location-detail/index.ts index 7c5861f..87b17fd 100644 --- a/miniprogram/pages/main/travel-location-detail/index.ts +++ b/miniprogram/pages/main/travel-location-detail/index.ts @@ -198,7 +198,7 @@ Page({ const sources = location.mediaItems.map(item => ({ url: item.sourceURL, - type: MediaItemType[item.type].toLowerCase() + type: item.type })); const total = sources.length;