feat: 开发收藏和下载功能
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
interface UserInfo {
|
||||
_id: string;
|
||||
openid: string;
|
||||
unionid: string | null;
|
||||
nickName: string | null;
|
||||
avatarUrl: string | null;
|
||||
totalDownloads: number;
|
||||
createdAt: string;
|
||||
lastActiveAt: string;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'user_info';
|
||||
const STORAGE_EXPIRE_KEY = 'user_info_expire';
|
||||
const EXPIRE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 天
|
||||
|
||||
/** 内存中的用户数据(App 生命周期内有效) */
|
||||
let currentUser: UserInfo | null = null;
|
||||
|
||||
/** 登录进行中的 Promise(防止并发重复调用) */
|
||||
let loginPromise: Promise<UserInfo> | null = null;
|
||||
|
||||
/**
|
||||
* 获取当前用户,优先内存 → Storage → 云函数登录
|
||||
*/
|
||||
export async function getUser(): Promise<UserInfo> {
|
||||
// 1. 内存中有,直接返回
|
||||
if (currentUser) return currentUser;
|
||||
|
||||
// 2. 尝试从 Storage 读取
|
||||
const cached = loadFromStorage();
|
||||
if (cached) {
|
||||
currentUser = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 3. 静默登录(防止并发)
|
||||
if (!loginPromise) {
|
||||
loginPromise = silentLogin().finally(() => {
|
||||
loginPromise = null;
|
||||
});
|
||||
}
|
||||
return loginPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默登录:调用云函数
|
||||
*/
|
||||
async function silentLogin(): Promise<UserInfo> {
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userLogin',
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
|
||||
if (result.code !== 0) {
|
||||
throw new Error('登录失败');
|
||||
}
|
||||
|
||||
const user = result.data as UserInfo;
|
||||
currentUser = user;
|
||||
saveToStorage(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Storage 读取用户数据(检查过期)
|
||||
*/
|
||||
function loadFromStorage(): UserInfo | null {
|
||||
try {
|
||||
const expire = wx.getStorageSync(STORAGE_EXPIRE_KEY);
|
||||
if (!expire || Date.now() > expire) {
|
||||
wx.removeStorageSync(STORAGE_KEY);
|
||||
wx.removeStorageSync(STORAGE_EXPIRE_KEY);
|
||||
return null;
|
||||
}
|
||||
const user = wx.getStorageSync(STORAGE_KEY);
|
||||
return user || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 Storage
|
||||
*/
|
||||
function saveToStorage(user: UserInfo): void {
|
||||
try {
|
||||
wx.setStorageSync(STORAGE_KEY, user);
|
||||
wx.setStorageSync(STORAGE_EXPIRE_KEY, Date.now() + EXPIRE_DURATION);
|
||||
} catch {
|
||||
// Storage 写入失败不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除登录状态
|
||||
*/
|
||||
export function clearUser(): void {
|
||||
currentUser = null;
|
||||
wx.removeStorageSync(STORAGE_KEY);
|
||||
wx.removeStorageSync(STORAGE_EXPIRE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制刷新用户数据(跳过缓存)
|
||||
*/
|
||||
export async function refreshUser(): Promise<UserInfo> {
|
||||
clearUser();
|
||||
return await silentLogin();
|
||||
}
|
||||
|
||||
export type { UserInfo };
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getUser } from './auth';
|
||||
|
||||
export interface DownloadLogRecord {
|
||||
_id: string;
|
||||
userId: string;
|
||||
worksheetId: string;
|
||||
createdAt: string;
|
||||
worksheet?: {
|
||||
_id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
category: string;
|
||||
subcategory: string;
|
||||
previewImg: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录下载日志
|
||||
*/
|
||||
export async function addDownloadLog(worksheetId: string): Promise<boolean> {
|
||||
if (!worksheetId) return false;
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userDownloadLogs',
|
||||
data: { action: 'add', worksheetId },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0;
|
||||
} catch (e) {
|
||||
console.error('addDownloadLog failed', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下载历史
|
||||
*/
|
||||
export async function getDownloadLogs(
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
): Promise<DownloadLogRecord[]> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userDownloadLogs',
|
||||
data: { action: 'list', page, pageSize },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 ? result.data : [];
|
||||
} catch (e) {
|
||||
console.error('getDownloadLogs failed', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { checkAndSaveImage } from './saveImage';
|
||||
import tracker from './tracker';
|
||||
import { incrementWorksheetDownloads } from './worksheetStats';
|
||||
import { addDownloadLog } from './downloadLogs';
|
||||
|
||||
// 存储键名
|
||||
const STORAGE_KEY_DOWNLOAD_COUNT = 'downloadCount';
|
||||
@@ -212,7 +212,7 @@ function doDownload(
|
||||
if (saved) {
|
||||
incrementDownloadCount();
|
||||
if (options.worksheetId) {
|
||||
incrementWorksheetDownloads(options.worksheetId);
|
||||
addDownloadLog(options.worksheetId);
|
||||
}
|
||||
}
|
||||
return saved;
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { getUser } from './auth';
|
||||
|
||||
export interface FavoriteRecord {
|
||||
_id: string;
|
||||
userId: string;
|
||||
worksheetId: string;
|
||||
createdAt: string;
|
||||
worksheet?: {
|
||||
_id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
category: string;
|
||||
subcategory: string;
|
||||
ageMin: number;
|
||||
ageMax: number;
|
||||
difficulty: number;
|
||||
previewImg: string;
|
||||
tags: string[];
|
||||
} | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加收藏
|
||||
*/
|
||||
export async function addFavorite(worksheetId: string): Promise<boolean> {
|
||||
try {
|
||||
await getUser(); // 确保已登录
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'add', worksheetId },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0;
|
||||
} catch (e) {
|
||||
console.error('addFavorite failed', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消收藏
|
||||
*/
|
||||
export async function removeFavorite(worksheetId: string): Promise<boolean> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'remove', worksheetId },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0;
|
||||
} catch (e) {
|
||||
console.error('removeFavorite failed', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收藏列表
|
||||
*/
|
||||
export async function getFavoriteList(
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
): Promise<FavoriteRecord[]> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'list', page, pageSize },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 ? result.data : [];
|
||||
} catch (e) {
|
||||
console.error('getFavoriteList failed', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已收藏
|
||||
*/
|
||||
export async function checkFavorited(worksheetId: string): Promise<boolean> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'check', worksheetId },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 && result.data?.favorited;
|
||||
} catch (e) {
|
||||
console.error('checkFavorited failed', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量检查是否已收藏,返回 { worksheetId: true } 的 map
|
||||
*/
|
||||
export async function batchCheckFavorited(
|
||||
worksheetIds: string[],
|
||||
): Promise<Record<string, boolean>> {
|
||||
if (!worksheetIds.length) return {};
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'batchCheck', worksheetIds },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 ? result.data : {};
|
||||
} catch (e) {
|
||||
console.error('batchCheckFavorited failed', e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user