Files
miniprogram-1/miniprogram/pages/upload/upload.ts
2025-12-11 08:59:08 +08:00

1075 lines
42 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// upload.ts - 主功能页面(拍照/相册选择)
import { IAppOption, IUserInfo } from 'miniprogram/types/app'
import apiManager from '../../utils/api'
import imageManager from '../../utils/image'
import ActionSheet, { ActionSheetTheme } from 'tdesign-miniprogram/action-sheet/index';
import { FILE_BASE_URL } from '../../utils/config'
const app = getApp<IAppOption>()
type IDayType = 'morning' | 'afternoon' | 'night'
const DayTypeMap: Record<IDayType, string> = {
morning: '早上好',
afternoon: '下午好',
night: '晚上好'
}
Page({
data: {
DayTypeMap,
current_date: '',
currentYear: new Date().getFullYear(), // 添加当前年份
day_type: 'morning' as IDayType,
userInfo: null as IUserInfo | null,
isProcessing: false, // 是否正在处理图片
// 移除模拟数据改为从API获取
groupedHistory: [] as any[],
todaySummary: [] as any[], // 添加今日摘要数据
// 添加分页相关数据
dailyPage: 1,
dailySize: 10,
dailyTotal: 0,
dailyHasMore: true,
todayPage: 1,
todaySize: 10,
// 添加加载状态
isLoading: true,
// 添加文件基础URL
fileBaseUrl: FILE_BASE_URL,
// 添加表情位置状态
facePosition: 'normal' as 'up' | 'down' | 'normal',
// SVG data for avatar
avatarSvgData: '',
sunSvgData: '',
paperSvgData: '',
photoSvgData: '',
scrollTop: 0,
takePhoto: false,
photoPath: '',
photoExpandTransform: '',
photoExpandTransition: '',
showExpandLayer: false,
photoExpandCurrentWidth: 0,
expandBorderStyle: '',
shouldResetOnReturn: false,
scannerVisible: false,
dateStripItems: [] as any[],
selectedDateKey: '',
selectedMonthTitle: '',
selectedDateImages: [] as any[],
gridCols: 3,
imagesByDate: {} as Record<string, any[]>,
useWaterfall: false,
waterfallLeft: [] as any[],
waterfallRight: [] as any[],
stripCentered: true,
todayKey: '',
animateWaterfall: true,
dateStripScrollLeft: 0,
dateStripTransform: '',
},
onLoad() {
console.log('主功能页面加载')
this.checkLoginStatus().then(() => {
// 只有登录成功后才加载历史数据
if (app.globalData.isLoggedIn) {
this.loadTodaySummary() // 加载今日摘要数据
this.loadDailySummary()
this.checkDayType()
}
}).catch((error) => {
console.error('登录检查失败:', error)
})
this.generateAvatarSvg()
this.generatePhotoSvg()
},
// Generate SVG data URL for avatar
generateAvatarSvg() {
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="470 50 20 40">
<path d="M 486 70 C 486 71.7614 483.761 74 481 74 C 478.239 74 476 71.7614 476 70 C 476 69 477 69 477 70 C 477 71 479 73 481 73 C 483 73 485 71 485 70 C 485 69 486 69 486 70 M 490 64 C 488.7 64 488.7 66 490 66 C 491.3 66 491.3 64 490 64 M 472 64 C 470.7 64 470.7 66 472 66 C 473.3 66 473.3 64 472 64" stroke="#001858" stroke-width="1.5" stroke-linecap="round"></path>
</svg>
`.trim();
const sunString = `
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160" viewBox="428 10 120 130">
<path d="M493.305 20.4491L496.802 26.0264C499.208 29.8632 504.199 31.14 508.152 28.9299L513.386 26.0031C517.052 23.9532 521.57 26.6033 521.57 30.8036V36.4623C521.57 40.866 524.934 44.5405 529.32 44.9292L534.961 45.4289C539.08 45.7939 541.338 50.4041 539.101 53.8825L536.232 58.3445C533.614 62.4145 534.918 67.8453 539.097 70.284L543.66 72.9462C547.336 75.0913 547.288 80.42 543.572 82.4973L539.09 85.0035C534.912 87.3401 533.486 92.6653 535.939 96.777L537.452 99.3128C539.638 102.979 536.997 107.63 532.728 107.63H528.5C523.806 107.63 520 111.436 520 116.13V121.531C520 125.764 515.418 128.41 511.751 126.294L508.069 124.17C504.174 121.922 499.203 123.097 496.726 126.849L493.234 132.141C491.035 135.471 486.133 135.426 483.998 132.055L480.708 126.864C478.326 123.103 473.445 121.818 469.52 123.918L464.094 126.82C460.43 128.779 456 126.125 456 121.97V117.696C456 113.001 452.194 109.196 447.5 109.196H442.811C438.72 109.196 436.061 104.887 437.895 101.23L440.309 96.4177C442.332 92.3853 440.852 87.4751 436.938 85.2318L432.118 82.4694C428.471 80.3789 428.422 75.1351 432.03 72.9773L436.973 70.0214C440.87 67.6907 442.247 62.7084 440.1 58.7072L436.753 52.4702C434.786 48.8062 437.441 44.3696 441.599 44.3696H447.5C452.194 44.3696 456 40.564 456 35.8696V30.8036C456 26.6033 460.518 23.9532 464.184 26.0031L469.312 28.8701C473.317 31.1095 478.376 29.7661 480.742 25.8348L483.933 20.5344C486.048 17.0207 491.126 16.9745 493.305 20.4491Z" fill="#f582ae" stroke="#001858" stroke-width="3"></path>
<circle cx="488.5" cy="76.5" r="44" fill="#fef6e4" stroke="#001858" stroke-width="3"></circle>
</svg>
`.trim();
const encodedSvg = encodeURIComponent(svgString);
const dataUrl = `data:image/svg+xml;utf8,${encodedSvg}`;
const encodedSunSvg = encodeURIComponent(sunString);
const sunDataUrl = `data:image/svg+xml;utf8,${encodedSunSvg}`;
this.setData({
avatarSvgData: dataUrl,
sunSvgData: sunDataUrl
});
},
generatePhotoSvg() {
const svgString = `
<svg viewBox="0 0 2000 960" width="500" height="240" xmlns="http://www.w3.org/2000/svg">
<path transform="translate(59,243)" d="m0 0h16l8 5 7 9 4 8 3 15 8 87 6 62 12 109 7 59 2 15 8-24 11-21 11-17 9-11 10-9 16-11 12-6 20-6 19-2 19 1 16 4 11 5 16 9 11 9 10 9 10 13 11 18 8 16 5 17 7 29 2 19 1 16v12l-2 28-4 24-6 22-7 19-12 28-13 21-16 21-12 13-14 11-21 12-19 8-17 5-18 3h-25l-15-3-18-7-13-7-13-10-9-12-8-17-6-20-5-23-8-51-24-122-9-60-8-60-5-43-8-92-6-74v-29l4-10 6-7 8-5z" fill="#4D4848"/>
<path transform="translate(1026,96)" d="m0 0h7l10 3 8 7 6 11 3 14 2 33 2 115 3 62 5 57 5 37 4 23v4h2l1-11 4-15 5-12 8-15 11-15 10-11 13-10 14-8 16-6 16-3 17-1 17 2 13 4 12 5 13 7 12 9 13 13 10 15 8 14 8 20 6 25 3 18 1 11v35l-2 29-4 24-6 20-8 20-12 25-8 14-11 15-8 10-11 12-11 11-14 9-25 12-20 6-10 2h-29l-18-4-15-6-13-7-12-11-8-10-8-14-7-20-6-26-6-37-6-35-8-39-6-32-8-54-4-29-6-56-3-37-2-40-1-35-1-65v-29l1-21 3-18 5-10 9-8 5-2z" fill="#4D4848"/>
<path transform="translate(1740,245)" d="m0 0h10l16 3 13 5 17 9 14 11 12 12 8 14 5 18 2 17 3 56 3 29 5 26 6 23 8 21 9 17 10 13 8 7 7 4 8 1 11-4 11-8 12-10 10-4 12 1 10 5 7 8 3 10v8l-3 12-6 11-9 10-10 8-15 8-12 3-7 1h-27l-16-4-16-8-13-8-13-12-10-14-9-20-2-1-3 12-6 16-12 22-8 12-8 10-9 10-12 10-13 8-12 6-16 4h-26l-16-4-14-6-13-9-12-11-9-10-10-17-8-19-7-28-3-20-1-11v-22l3-29 6-31 8-28 8-22 11-24 11-22 10-16 10-13 12-14 10-9 14-10 19-9 12-3z" fill="#4D4848"/>
<path transform="translate(794,392)" d="m0 0h9l16 3 19 7 14 8 11 9 9 10 8 14 5 14 2 13 3 69 4 40 5 25 7 21 12 23 10 14 8 8 12 6h10l10-5 12-12 9-6 3-1h14l8 4 9 8 4 8 1 3v17l-3 10-6 10-7 7-14 8-18 6-12 2h-25l-17-4-16-7-14-9-10-9-14-24-7-14-2-1-3 10-9 24-8 16-7 11-9 10-12 12-13 10-13 8-16 6-17 3h-19l-15-3-10-4-10-6-13-10-9-9-12-17-9-17-6-17-4-19-2-15-1-15v-21l2-28 4-26 6-25 12-36 10-23 9-17 14-20 9-11 15-15 10-9 14-9 12-6 17-5z" fill="#4D4848"/>
<path transform="translate(1408,40)" d="m0 0h13l8 3 5 5 4 5 4 8 2 9v30l-6 98-2 42-1 38v103l2 43 4 46 5 39 5 29 8 32 8 24 9 16 9 10 6 4 3 1h8l11-4 9-8 6-8 8-11 8-10 7-5 4-1h17l10 5 6 7 4 10v14l-4 13-7 13-8 11-9 10-9 8-15 9-16 6-15 3h-24l-15-4-17-8-10-8-9-9-12-17-9-16-9-21-7-23-9-41-8-45-6-41-3-36-2-32-1-30v-98l2-54 6-89 3-32 4-18 7-14 8-7z" fill="#4D4848"/>
<path transform="translate(460,186)" d="m0 0 9 1 8 5 6 7 5 10 3 11 1 9v44l-4 71-2 65v67l2 48 4 53 5 45 6 38 8 37 8 27 10 25 6 10 8 9 6 4 6 2h8l10-5 5-4 10-13 9-13 6-7 9-5 4-1h12l10 4 8 9 4 8 1 4v14l-3 12-6 10-9 13-7 8-14 11-12 7-18 7-15 3h-21l-15-3-13-5-12-8-10-9-9-12-14-24-8-17-12-36-6-23-10-50-12-91-3-28-1-16-1-37v-78l1-46 4-91 3-35 4-18 5-10 7-7 7-4z" fill="#4D4848"/>
<path transform="translate(249,568)" d="m0 0 14 1 10 4 10 9 6 7 8 15 6 18 4 17 2 17v34l-3 26-6 25-8 21-8 17-13 19-12 13-9 8-11 7-19 9-15 4h-21l-8-3-4-6-3-12-1-7-1-25v-28l2-28 5-35 5-26 7-27 7-20 10-19 8-11 9-10 8-7 11-5z" fill="#FEFDE2"/>
<path transform="translate(1194,416)" d="m0 0h7l10 3 11 7 7 7 8 13 7 16 5 22 2 20v29l-3 22-5 24-8 24-8 16-8 14-9 11-9 10-7 7-13 10-10 6-16 7-8 2h-14l-8-5-7-8-4-11-2-11-1-13v-31l2-25 4-29 8-43 7-24 8-19 8-15 10-14 9-10 11-7 11-4z" fill="#FEFDE2"/>
<path transform="translate(1737,307)" d="m0 0h13l10 5 7 8 5 10 2 9 1 16v60l-3 28-5 25-7 27-10 25-10 19-9 13-12 12-8 5-7 2h-10l-9-3-10-9-6-10-6-20-2-12-1-14v-25l2-23 5-26 7-25 9-24 11-24 10-18 9-12 8-9 9-7z" fill="#FEFDE2"/>
<path transform="translate(789,454)" d="m0 0h13l10 3 5 5 4 10 3 18 1 12v45l-3 33-4 25-8 34-7 20-11 23-8 12-11 12-9 6-6 2h-13l-10-4-6-5-7-11-6-15-4-19-2-18v-25l4-29 6-26 9-27 9-21 10-19 10-15 12-14 10-8 5-3z" fill="#FEFDE2"/>
</svg>
`.trim();
const encodedSvg = encodeURIComponent(svgString);
const dataUrl = `data:image/svg+xml;utf8,${encodedSvg}`;
this.setData({
photoSvgData: dataUrl
});
},
onShow() {
this.setData({ isProcessing: false })
if (this.data.shouldResetOnReturn) {
// this.resetPageState()
this.loadTodaySummary()
this.setData({ shouldResetOnReturn: false })
}
},
// 处理页面滚动
onPageScroll(e: WechatMiniprogram.Page.IPageScrollOption): void {
const scrollTop = e.scrollTop
const lastScrollTop = this.data.scrollTop
const threshold = 30 // 滚动阈值
// 更新滚动位置
this.setData({ scrollTop })
// 向上滚动
if (scrollTop < lastScrollTop && scrollTop > threshold) {
if (this.data.facePosition !== 'up') {
this.setData({ facePosition: 'up' })
}
}
// 向下滚动
else if (scrollTop > lastScrollTop && scrollTop > threshold) {
if (this.data.facePosition !== 'down') {
this.setData({ facePosition: 'down' })
}
}
// 回到顶部附近
else if (scrollTop <= threshold) {
if (this.data.facePosition !== 'normal') {
this.setData({ facePosition: 'normal' })
}
}
},
checkDayType() {
const currentHour = new Date().getHours();
const currentDate = new Date().toLocaleDateString('zh-CN');
const dayType = currentHour >= 6 && currentHour < 12 ? 'morning' :
currentHour >= 12 && currentHour < 18 ? 'afternoon' : 'night';
// const dayType = 'morning';
this.setData({
current_date: currentDate,
day_type: dayType as IDayType
});
// 根据日间/夜间模式设置导航栏颜色
wx.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: dayType === 'night' ? '#0E1330' : '#FDFDFC',
animation: {
duration: 300,
timingFunc: 'easeIn'
}
});
},
// 加载每日摘要数据(历史记录,支持分页)
async loadDailySummary(page: number = 1) {
try {
// 只有在第一页时才显示加载状态(下拉刷新)
if (page === 1) {
this.setData({ isLoading: true });
}
const result = await apiManager.getDailySummary(page, this.data.dailySize);
// 处理数据,按年份分组
const processedItems = result.items.map(item => ({
...item,
images: item.image_ids && item.thumbnail_ids ?
item.image_ids.map((imageId: string, index: number) => ({
image_id: imageId,
thumbnail_id: item.thumbnail_ids![index],
// 不再直接构造URL而是存储文件ID稍后通过安全方式获取
thumbnail_file_id: item.thumbnail_ids![index],
image_file_id: imageId,
thumbnail_url: '', // 将在页面渲染前通过安全方式获取
image_url: '', // 将在需要时通过安全方式获取
thumbnail_loading: true // 添加加载状态
})) : []
}));
let groupedArray;
if (page === 1) {
// 第一页:替换所有数据
const currentYear = new Date().getFullYear();
const grouped: any = {};
processedItems.forEach(item => {
const date = new Date(item.summary_time);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
if (!grouped[year]) {
grouped[year] = [];
}
grouped[year].push({
...item,
monthDay: `${month}-${day}`
});
});
// 转换为数组形式并按年份排序
groupedArray = Object.keys(grouped)
.map(year => ({
year: parseInt(year),
isCurrentYear: parseInt(year) === currentYear,
items: grouped[year]
}))
.sort((a, b) => b.year - a.year);
} else {
// 后续页面:只处理新数据并追加到现有数据
const currentYear = new Date().getFullYear();
const existingData = [...this.data.groupedHistory];
// 处理新数据的分组
const newGrouped: any = {};
processedItems.forEach(item => {
const date = new Date(item.summary_time);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
if (!newGrouped[year]) {
newGrouped[year] = [];
}
newGrouped[year].push({
...item,
monthDay: `${month}-${day}`
});
});
// 将新数据合并到现有数据中
Object.keys(newGrouped).forEach(year => {
const yearInt = parseInt(year);
const existingYearGroup = existingData.find(group => group.year === yearInt);
if (existingYearGroup) {
// 如果年份已存在,追加到该年份的数据中
existingYearGroup.items = [...existingYearGroup.items, ...newGrouped[year]];
} else {
// 如果年份不存在,添加新的年份组
existingData.push({
year: yearInt,
isCurrentYear: yearInt === currentYear,
items: newGrouped[year]
});
}
});
// 重新排序年份
groupedArray = existingData.sort((a, b) => b.year - a.year);
}
this.setData({
groupedHistory: groupedArray,
dailyPage: page,
dailyTotal: result.total,
dailyHasMore: page * this.data.dailySize < result.total,
isLoading: false,
}, () => {
this.rebuildDateData();
this.fetchSecureImageUrls();
});
} catch (error) {
console.error('加载每日摘要失败:', error);
this.setData({ isLoading: false });
wx.showToast({
title: '加载失败',
icon: 'none'
});
}
},
// 加载今日摘要数据(当天记录,只加载第一页)
async loadTodaySummary(page: number = 1) {
try {
this.setData({ isLoading: true });
const result = await apiManager.getTodaySummary(page, this.data.todaySize);
// 处理数据,按时间分组
const processedItems = result.items.map(item => {
// 为今天的数据添加 monthDay 字段
const date = new Date(item.summary_time);
const month = date.getMonth() + 1;
const day = date.getDate();
const monthDay = `${month}-${day}`;
return {
...item,
monthDay: monthDay, // 添加 monthDay 字段
images: item.image_ids && item.thumbnail_ids ?
item.image_ids.map((imageId: string, index: number) => ({
image_id: imageId,
thumbnail_id: item.thumbnail_ids![index],
// 不再直接构造URL而是存储文件ID稍后通过安全方式获取
thumbnail_file_id: item.thumbnail_ids![index],
image_file_id: imageId,
thumbnail_url: '', // 将在页面渲染前通过安全方式获取
image_url: '', // 将在需要时通过安全方式获取
thumbnail_loading: true // 添加加载状态
})) : []
};
});
// 设置今日摘要数据
this.setData({
todaySummary: processedItems,
isLoading: false
}, () => {
this.rebuildDateData();
this.fetchSecureTodayImageUrls();
});
console.log('今日摘要数据加载完成:', processedItems);
return processedItems;
} catch (error) {
console.error('加载今日摘要失败:', error);
this.setData({ isLoading: false });
wx.showToast({
title: '加载今日数据失败',
icon: 'none'
});
throw error;
}
},
// 触底加载更多数据(只加载每日摘要的下一页)
onReachBottom() {
if (this.data.dailyHasMore && !this.data.isLoading) {
const nextPage = this.data.dailyPage + 1;
this.loadDailySummary(nextPage);
}
},
// 下拉刷新
onPullDownRefresh() {
this.setData({
dailyPage: 1,
todayPage: 1
});
this.loadDailySummary(1).then(() => {
wx.stopPullDownRefresh();
}).catch(() => {
wx.stopPullDownRefresh();
});
},
// 获取安全的图片URL
async fetchSecureImageUrls() {
try {
console.log('开始获取安全的图片URL');
// 遍历所有分组的历史记录
const updatedHistory = [...this.data.groupedHistory];
let hasChanges = false;
for (const yearGroup of updatedHistory) {
for (const item of yearGroup.items) {
for (const image of item.images) {
// 如果还没有获取过安全URL且有thumbnail_file_id
if ((!image.thumbnail_url || image.thumbnail_url === '') && image.thumbnail_file_id) {
try {
// 首先检查本地缓存中是否已有该缩略图
const cachedThumbnail = this.getCachedThumbnail(image.thumbnail_file_id);
if (cachedThumbnail) {
image.thumbnail_url = cachedThumbnail;
image.thumbnail_loading = false;
hasChanges = true;
// console.log('使用缓存的缩略图:', image.thumbnail_file_id);
continue;
}
// 获取安全的缩略图URL
image.thumbnail_url = await apiManager.getFileDisplayUrl(image.thumbnail_file_id);
// 缓存缩略图到本地存储
this.cacheThumbnail(image.thumbnail_file_id, image.thumbnail_url);
image.thumbnail_loading = false; // 设置加载完成状态
hasChanges = true;
} catch (error) {
console.error('获取缩略图URL失败:', error);
// 如果获取失败,使用默认的占位图
image.thumbnail_url = '/static/sun-2.png';
image.thumbnail_loading = false; // 设置加载完成状态
}
}
// 如果还没有获取过安全URL且有image_file_id
// if (!image.image_url && image.image_file_id) {
// try {
// // 获取安全的原图URL
// image.image_url = await apiManager.getFileDisplayUrl(image.image_file_id);
// hasChanges = true;
// } catch (error) {
// console.error('获取原图URL失败:', error);
// // 如果获取失败,使用默认的占位图
// image.image_url = '/static/placeholder.png';
// }
// }
}
}
}
// 如果有变化,更新数据
if (hasChanges) {
this.setData({
groupedHistory: updatedHistory
}, () => {
this.rebuildDateData();
});
}
} catch (error) {
console.error('获取安全图片URL失败:', error);
}
},
// 获取安全的今日摘要图片URL
async fetchSecureTodayImageUrls() {
try {
console.log('开始获取今日摘要安全的图片URL');
// 获取今日摘要数据
const todaySummary = [...this.data.todaySummary];
let hasChanges = false;
for (const item of todaySummary) {
for (const image of item.images) {
// 如果还没有获取过安全URL且有thumbnail_file_id
if ((!image.thumbnail_url || image.thumbnail_url === '') && image.thumbnail_file_id) {
try {
// 首先检查本地缓存中是否已有该缩略图
const cachedThumbnail = this.getCachedThumbnail(image.thumbnail_file_id);
if (cachedThumbnail) {
image.thumbnail_url = cachedThumbnail;
image.thumbnail_loading = false;
hasChanges = true;
// console.log('使用缓存的缩略图:', image.thumbnail_file_id);
continue;
}
// 获取安全的缩略图URL
image.thumbnail_url = await apiManager.getFileDisplayUrl(image.thumbnail_file_id);
// 缓存缩略图到本地存储
this.cacheThumbnail(image.thumbnail_file_id, image.thumbnail_url);
image.thumbnail_loading = false; // 设置加载完成状态
hasChanges = true;
} catch (error) {
console.error('获取缩略图URL失败:', error);
// 如果获取失败,使用默认的占位图
image.thumbnail_url = '/static/sun-2.png';
image.thumbnail_loading = false; // 设置加载完成状态
}
}
}
}
// 如果有变化,更新数据
if (hasChanges) {
this.setData({
todaySummary: todaySummary
}, () => {
this.rebuildDateData();
});
}
} catch (error) {
console.error('获取安全图片URL失败:', error);
}
},
// 缓存缩略图到本地存储
cacheThumbnail(fileId: string, filePath: string) {
try {
// 获取文件信息以计算大小
const fs = wx.getFileSystemManager();
fs.getFileInfo({
filePath: filePath,
success: (res) => {
const fileSize = res.size;
// 创建缓存条目
const cacheEntry = {
filePath: filePath,
timestamp: Date.now(),
size: fileSize
};
// 保存到本地存储
wx.setStorageSync(`thumbnail_cache_${fileId}`, cacheEntry);
// console.log('缩略图已缓存:', fileId, '大小:', fileSize, 'bytes');
},
fail: (err) => {
console.error('获取缩略图文件信息失败:', err);
}
});
} catch (error) {
console.error('缓存缩略图失败:', error);
}
},
// 从本地存储获取缓存的缩略图
getCachedThumbnail(fileId: string): string | null {
try {
const cacheKey = `thumbnail_cache_${fileId}`;
const cacheEntry = wx.getStorageSync(cacheKey);
if (cacheEntry) {
// 检查文件是否存在
const fs = wx.getFileSystemManager();
try {
fs.accessSync(cacheEntry.filePath);
// 检查缓存是否过期例如7天
const cacheAge = Date.now() - cacheEntry.timestamp;
const maxCacheAge = 7 * 24 * 60 * 60 * 1000; // 7天
if (cacheAge < maxCacheAge) {
return cacheEntry.filePath;
} else {
// 缓存过期,删除缓存条目
wx.removeStorageSync(cacheKey);
console.log('缩略图缓存已过期并已删除:', fileId);
}
} catch (err) {
// 文件不存在,删除缓存条目
wx.removeStorageSync(cacheKey);
console.log('缩略图文件不存在,已删除缓存条目:', fileId);
}
}
return null;
} catch (error) {
console.error('获取缓存缩略图失败:', error);
return null;
}
},
// 图片点击事件
onImageTap(e: any) {
const { imageId } = e.currentTarget.dataset;
if (imageId) {
wx.navigateTo({
url: `/pages/assessment/assessment?imageId=${imageId}`
})
}
},
// 检查登录状态
async checkLoginStatus() {
try {
// 使用智能登录检查和更新登录状态
const loginResult = await apiManager.smartLogin()
if (loginResult) {
// 登录成功,更新全局状态
app.globalData.isLoggedIn = true
app.globalData.token = loginResult.access_token
console.log('登录状态验证成功')
return true
} else {
// 登录失败,跳转到登录页
console.log('登录状态验证失败,跳转到登录页')
wx.reLaunch({
url: '/pages/index/index'
})
return false
}
} catch (error) {
console.error('检查登录状态失败:', error)
// 发生错误时跳转到登录页
wx.reLaunch({
url: '/pages/index/index'
})
return false
}
},
// 处理图片选择(合并拍照和相册选择功能)
handleImageSelect() {
if (this.data.isProcessing || this.data.takePhoto) return;
ActionSheet.show({
theme: ActionSheetTheme.List,
selector: '#t-action-sheet',
context: this,
align: 'center',
description: '请选择操作',
items: [
{
label: '拍照识别',
icon: 'camera'
},
{
label: '相册选择',
icon: 'image'
}
],
cancelText: '取消'
});
},
handleSelected(e: any) {
const { index } = e.detail;
if (this.data.takePhoto) {
wx.showToast({
title: '请刷新页面',
icon: 'none'
})
return
}
console.log('用户选择:', index)
if (index === 0) {
this.handleTakePhoto();
} else if (index === 1) {
this.handleChooseFromAlbum();
}
},
// 拍照
async handleTakePhoto() {
try {
this.setData({ isProcessing: true })
wx.showLoading({ title: '准备拍照...' })
// const imagePath = await imageManager.takePhoto({
// quality: 80,
// maxWidth: 1920,
// maxHeight: 1920
// })
const imagePath = await imageManager.takePhoto({})
wx.hideLoading()
this.setData({ photoPath: imagePath, takePhoto: true })
console.log('拍照成功:', imagePath)
// try { wx.nextTick(() => { this.scheduleExpandTransform() }) } catch (err) {}
// 直接跳转到结果页面
this.navigateToResult(imagePath)
} catch (error: any) {
wx.hideLoading()
this.setData({ isProcessing: false ,takePhoto: false})
console.error('拍照失败:', error)
if (error?.message !== '用户取消选择') {
wx.showToast({
title: '拍照失败',
icon: 'none'
})
}
}
},
// 从相册选择
async handleChooseFromAlbum() {
try {
this.setData({ isProcessing: true })
wx.showLoading({ title: '准备选择图片...' })
// const imagePath = await imageManager.chooseFromAlbum({
// quality: 80,
// maxWidth: 1920,
// maxHeight: 1920
// })
const imagePath = await imageManager.chooseFromAlbum()
wx.hideLoading()
this.setData({ photoPath: imagePath, takePhoto: true })
console.log('选择图片成功:', imagePath)
// try { wx.nextTick(() => { this.scheduleExpandTransform() }) } catch (err) {}
// 直接跳转到结果页面
this.navigateToResult(imagePath)
} catch (error: any) {
wx.hideLoading()
this.setData({ isProcessing: false ,takePhoto: false})
console.error('选择图片失败:', error)
if (error?.message !== '用户取消选择') {
wx.showToast({
title: '选择图片失败',
icon: 'none'
})
}
}
},
scheduleExpandTransform() {
try {
const sys = wx.getSystemInfoSync()
const win: any = (wx as any).getWindowInfo ? (wx as any).getWindowInfo() : sys
const vw = win.windowWidth || sys.windowWidth || 375
const statusBar: number = win.statusBarHeight || sys.statusBarHeight || 0
const safeTop: number = (win.safeArea && win.safeArea.top) || 0
const safeBottom: number = (win.safeArea && win.safeArea.bottom) || 0
const safeHeight: number = (win.safeArea && (win.safeArea.height || (safeBottom - safeTop))) || ((win.windowHeight || sys.windowHeight || 667) - statusBar)
const safeWidth: number = (win.safeArea && (win.safeArea.width || (win.windowWidth || vw))) || (win.windowWidth || vw)
const targetWidth = Math.floor(safeWidth * 0.72)
// 等待 photo-wrapper 动画完成后再读取其最终位置
this.setData({ showExpandLayer: false, expandBorderStyle: 'opacity: 0;' })
const waitMs = 4500
setTimeout(() => {
const query2 = wx.createSelectorQuery().in(this as any)
query2.select('.photo-wrapper .photo').boundingClientRect()
query2.exec((res2: any) => {
const rect = res2 && res2[0]
if (!rect) return
const smallCx = rect.left + rect.width / 2
const smallCy = rect.top + rect.height / 2
const centerCx = safeWidth / 2
const centerCy = safeTop + safeHeight / 2
const scale = rect.width / targetWidth
const dx = smallCx - centerCx
let dy = smallCy - centerCy
const photoTopRpx = 12, photoBottomRpx = 32
const expandTopRpx = 20, expandBottomRpx = 48
const deltaRpx = (expandBottomRpx - expandTopRpx - (photoBottomRpx - photoTopRpx)) / 2
const pxPerRpx = (win.windowWidth || vw) / 750
dy += deltaRpx * pxPerRpx
const startAtPhoto = `transform: translate(${dx}px, ${dy}px) scale(${scale});`
this.setData({
photoExpandTransform: startAtPhoto,
photoExpandTransition: 'transition: transform 0ms',
showExpandLayer: true,
photoExpandCurrentWidth: Math.round(rect.width),
expandBorderStyle: 'opacity: 0;'
})
setTimeout(() => {
this.setData({ photoExpandTransition: 'transition: transform 900ms ease-in-out' })
this.setData({ photoExpandTransform: 'transform: translate(0px, 0px) scale(1);', photoExpandCurrentWidth: targetWidth, expandBorderStyle: 'opacity: 1; transition: opacity 600ms ease-out;' })
setTimeout(() => { this.setData({ scannerVisible: true }) }, 100)
}, 50)
})
}, waitMs)
} catch (e) {}
},
resetPageState() {
this.setData({
takePhoto: false,
photoPath: '',
showExpandLayer: false,
photoExpandTransform: '',
photoExpandTransition: '',
photoExpandCurrentWidth: 0,
expandBorderStyle: ''
})
},
// 跳转到结果页面
async navigateToResult(imagePath: string) {
try {
console.log('跳转到结果页面:', imagePath)
try { wx.nextTick(() => { this.scheduleExpandTransform() }) } catch (err) {}
// 跳转到结果页面并传递图片路径
// wx.navigateTo({
// url: `/pages/result/result?imagePath=${encodeURIComponent(imagePath)}`
// })
// const compressedImagePath = await imageManager.compressImage(this.data.imagePath, {
// quality: 80,
// maxWidth: 1200,
// maxHeight: 1920
// })
// const result = await apiManager.uploadImage(compressedImagePath)
const result = await apiManager.uploadImage(imagePath)
if (result.image_id) {
this.setData({ shouldResetOnReturn: true })
this.resetPageState()
wx.navigateTo({
url: `/pages/assessment/assessment?imageId=${result.image_id}`
})
}
} catch (error) {
console.error('跳转结果页面失败:', error)
this.setData({ isProcessing: false })
this.resetPageState()
const msg = (error as any)?.message || ''
if (typeof msg === 'string' && msg.indexOf('积分') !== -1) {
wx.showModal({
title: '积分不足',
content: '您的积分不足,是否前往购买?',
confirmText: '购买',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
wx.navigateTo({ url: '/pages/coupon/coupon' })
} else {
// wx.showToast({ title: '已取消', icon: 'none' })
}
}
})
} else {
wx.showToast({
title: msg || '上传失败',
icon: 'none'
})
}
}
},
// 跳转到个人主页
goProfile() {
wx.navigateTo({
url: '/pages/profile/profile'
})
},
onDateTap(e: any) {
const { key } = e.currentTarget.dataset;
if (!key) return;
this.updateSelection(key);
},
rebuildDateData() {
try {
const records: Array<{ key: string; date: Date; year: number; month: number; day: number; weekday: string; images: any[] }> = [];
const pushRecord = (item: any) => {
const d = new Date(item.summary_time);
const y = d.getFullYear();
const m = d.getMonth() + 1;
const dd = d.getDate();
const key = `${y}-${String(m).padStart(2, '0')}-${String(dd).padStart(2, '0')}`;
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
records.push({ key, date: d, year: y, month: m, day: dd, weekday: weekdays[d.getDay()], images: item.images || [] });
};
(this.data.todaySummary || []).forEach(pushRecord);
(this.data.groupedHistory || []).forEach((yg: any) => {
(yg.items || []).forEach(pushRecord);
});
if (!records.length) {
const imagesByDate: Record<string, any[]> = {};
const strip: any[] = [];
const today = new Date();
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
strip.push({ type: 'day', key: `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`, weekday: weekdays[today.getDay()], day: today.getDate(), year: today.getFullYear(), month: today.getMonth() + 1 });
const selectedKey = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`;
const selectedImages: any[] = [];
const gridCols = 1;
const monthTitle = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}`;
const stripCentered = true;
this.setData({ dateStripItems: strip, imagesByDate, selectedDateKey: selectedKey, selectedDateImages: selectedImages, gridCols, selectedMonthTitle: monthTitle, useWaterfall: false, waterfallLeft: [], waterfallRight: [], stripCentered, todayKey: selectedKey });
return;
}
records.sort((a, b) => a.date.getTime() - b.date.getTime());
const imagesByDate: Record<string, any[]> = {};
records.forEach(r => { imagesByDate[r.key] = r.images; });
const strip: any[] = [];
let prevYear = -1, prevMonth = -1;
const now = new Date();
const nowYear = now.getFullYear();
const nowMonth = now.getMonth() + 1;
for (const r of records) {
if (r.year !== prevYear || r.month !== prevMonth) {
if (!(r.year === nowYear && r.month === nowMonth)) {
strip.push({ type: 'ym', year: r.year, month: r.month, key: `ym-${r.year}-${r.month}` });
}
prevYear = r.year; prevMonth = r.month;
}
strip.push({ type: 'day', key: r.key, weekday: r.weekday, day: r.day, year: r.year, month: r.month });
}
const today = new Date();
const todayKey = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`;
let selectedKey = this.data.selectedDateKey || todayKey;
const selectedImages = imagesByDate[selectedKey] || [];
const gridCols = selectedImages.length <= 1 ? 1 : selectedImages.length <= 4 ? 2 : 3;
const sel = records.find(r => r.key === selectedKey);
const partsForTitle = selectedKey.split('-');
const monthTitle = sel ? `${sel.year}-${String(sel.month).padStart(2,'0')}` : `${partsForTitle[0]}-${partsForTitle[1]}`;
const useWaterfall = selectedImages.length > 3;
let waterfallLeft: any[] = [], waterfallRight: any[] = [];
if (useWaterfall) {
selectedImages.forEach((img, idx) => {
(idx % 2 === 0 ? waterfallLeft : waterfallRight).push(img);
});
}
const hasTodayInStrip = strip.some(it => it.type === 'day' && it.key === todayKey);
if (!hasTodayInStrip) {
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const weekday = weekdays[today.getDay()];
strip.push({ type: 'day', key: todayKey, weekday, day: today.getDate(), year: today.getFullYear(), month: today.getMonth() + 1 });
}
const stripCentered = strip.length <= 3;
this.setData({ dateStripItems: strip, imagesByDate, selectedDateKey: selectedKey, selectedDateImages: selectedImages, gridCols, selectedMonthTitle: monthTitle, useWaterfall, waterfallLeft, waterfallRight, stripCentered, todayKey, animateWaterfall: false }, () => {
this.centerDateStrip();
});
if (useWaterfall) {
this.balanceWaterfall(selectedImages).then(() => {
wx.nextTick(() => { this.setData({ animateWaterfall: true }); });
});
} else {
wx.nextTick(() => { this.setData({ animateWaterfall: true }); });
}
} catch (err) {
console.error('重建日期条失败:', err);
}
},
updateSelection(dateKey: string) {
const images = (this.data.imagesByDate || {})[dateKey] || [];
const parts = dateKey.split('-');
const y = Number(parts[0]);
const m = Number(parts[1]);
const gridCols = images.length <= 1 ? 1 : images.length <= 4 ? 2 : 3;
const useWaterfall = images.length > 3;
let waterfallLeft: any[] = [], waterfallRight: any[] = [];
if (useWaterfall) {
images.forEach((img, idx) => { (idx % 2 === 0 ? waterfallLeft : waterfallRight).push(img); });
}
this.setData({ animateWaterfall: false });
this.setData({
selectedDateKey: dateKey,
selectedDateImages: images,
gridCols,
selectedMonthTitle: `${y}-${String(m).padStart(2,'0')}`,
useWaterfall,
waterfallLeft,
waterfallRight
}, () => {
this.centerDateStrip();
// 在setData回调中处理瀑布流平衡
if (useWaterfall && images.length > 0) {
this.balanceWaterfall(images).then(() => {
wx.nextTick(() => { this.setData({ animateWaterfall: true }); });
});
} else {
wx.nextTick(() => { this.setData({ animateWaterfall: true }); });
}
});
},
async ensureThumbSize(img: any): Promise<{ w: number; h: number } | null> {
if (img && typeof img._thumbW === 'number' && typeof img._thumbH === 'number') {
return { w: img._thumbW, h: img._thumbH };
}
const src = img && img.thumbnail_url;
if (!src) return null;
return new Promise((resolve) => {
wx.getImageInfo({
src,
success: (res) => {
img._thumbW = res.width;
img._thumbH = res.height;
resolve({ w: res.width, h: res.height });
},
fail: () => {
resolve(null);
}
});
});
},
async computeWaterfallColumns(images: any[]): Promise<{ left: any[]; right: any[] }> {
const si = wx.getSystemInfoSync();
const ww = si.windowWidth;
const rpx = ww / 750;
const padPx = Math.floor(24 * rpx);
const gapPx = Math.floor(12 * rpx);
const colW = Math.max(1, Math.floor((ww - padPx - gapPx) / 2));
const mbPx = Math.floor(12 * rpx);
let leftH = 0;
let rightH = 0;
const left: any[] = [];
const right: any[] = [];
const infos = await Promise.all(images.map((img) => this.ensureThumbSize(img)));
for (let i = 0; i < images.length; i++) {
const img = images[i];
const info = infos[i];
let h = 200;
if (info && info.w > 0) {
h = Math.floor(info.h * (colW / info.w));
}
if (leftH <= rightH) {
left.push(img);
leftH += h + mbPx;
} else {
right.push(img);
rightH += h + mbPx;
}
}
return { left, right };
},
async balanceWaterfall(images: any[]) {
try {
const { left, right } = await this.computeWaterfallColumns(images);
this.setData({ waterfallLeft: left, waterfallRight: right });
} catch (e) {}
},
centerDateStrip() {
try {
const items = this.data.dateStripItems || [];
if (!items || items.length < 2) return;
const idx = items.findIndex((it: any) => it && it.key === this.data.selectedDateKey);
if (idx < 0) return;
const si = wx.getSystemInfoSync();
const ww = si.windowWidth;
const rpx = ww / 750;
const itemW = Math.floor(120 * rpx);
const gap = Math.floor(24 * rpx);
const n = items.length;
const contentW = n * itemW + (n - 1) * gap;
const containerW = ww;
if (contentW > containerW) {
let target = Math.floor(idx * (itemW + gap) - (containerW - itemW) / 2);
target = Math.max(0, Math.min(contentW - containerW, target));
this.setData({ dateStripScrollLeft: target, dateStripTransform: '' });
} else {
const selectedX = Math.floor(idx * (itemW + gap));
const centerX = Math.floor((containerW - itemW) / 2);
const shift = centerX - selectedX;
this.setData({ dateStripTransform: `transform: translateX(${shift}px);` });
}
} catch (e) {}
},
})