2026-04-16 11:25:29 +08:00
|
|
|
|
const EXIT_THRESHOLD = 80;
|
|
|
|
|
|
const FAST_SWIPE_VELOCITY = 0.5;
|
|
|
|
|
|
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 加载文案库
|
|
|
|
|
|
const LOADING_QUOTES = [
|
|
|
|
|
|
"正在翻阅《古今集成》...",
|
|
|
|
|
|
"于诗书中寻觅意境...",
|
|
|
|
|
|
"聆听平仄的韵律...",
|
|
|
|
|
|
"在字里行间游走...",
|
|
|
|
|
|
"采撷一缕清风入名...",
|
|
|
|
|
|
"品味楚辞的芬芳...",
|
|
|
|
|
|
"捕捉诗经的灵光...",
|
|
|
|
|
|
"与古人隔空对话..."
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-04-16 11:25:29 +08:00
|
|
|
|
Page({
|
|
|
|
|
|
isDragging: false,
|
|
|
|
|
|
isExiting: false,
|
|
|
|
|
|
startTime: 0,
|
|
|
|
|
|
startX: 0,
|
|
|
|
|
|
startY: 0,
|
|
|
|
|
|
lastX: 0,
|
2026-04-17 15:34:51 +08:00
|
|
|
|
loadingQuoteTimer: null,
|
2026-04-16 11:25:29 +08:00
|
|
|
|
|
|
|
|
|
|
data: {
|
|
|
|
|
|
nameList: [],
|
|
|
|
|
|
currentIndex: 0,
|
|
|
|
|
|
isLoading: true,
|
|
|
|
|
|
isFlipped: false,
|
|
|
|
|
|
keyword: '清冷',
|
2026-04-17 15:34:51 +08:00
|
|
|
|
mode: 'classic',
|
|
|
|
|
|
modeName: '拾遗',
|
|
|
|
|
|
surname: '',
|
|
|
|
|
|
loadingQuote: '正在翻阅《古今集成》...',
|
2026-04-16 11:25:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 动画控制
|
|
|
|
|
|
translateX: 0,
|
2026-04-18 16:56:31 +08:00
|
|
|
|
translateY: 0,
|
2026-04-16 11:25:29 +08:00
|
|
|
|
rotate: 0,
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
transition: 'none',
|
|
|
|
|
|
|
|
|
|
|
|
cardKey: 0,
|
|
|
|
|
|
collectedNames: [],
|
2026-04-17 15:34:51 +08:00
|
|
|
|
showCollection: false,
|
|
|
|
|
|
|
|
|
|
|
|
// 海报相关
|
|
|
|
|
|
showPoster: false,
|
2026-04-18 16:56:31 +08:00
|
|
|
|
posterNameChars: [],
|
|
|
|
|
|
|
|
|
|
|
|
// 音频相关
|
|
|
|
|
|
showMusicMenu: false,
|
|
|
|
|
|
currentAmbience: 'silent',
|
|
|
|
|
|
|
|
|
|
|
|
// 字源弹窗
|
|
|
|
|
|
showCharDetail: false,
|
|
|
|
|
|
selectedChar: '',
|
|
|
|
|
|
charDetailData: null,
|
|
|
|
|
|
|
|
|
|
|
|
// AI 解析弹窗
|
|
|
|
|
|
showAIModal: false,
|
|
|
|
|
|
aiContext: '',
|
|
|
|
|
|
aiExplanation: '',
|
|
|
|
|
|
aiLoading: false,
|
|
|
|
|
|
|
|
|
|
|
|
// 结语卡片
|
|
|
|
|
|
showEndingCard: false
|
2026-04-16 11:25:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onLoad(options) {
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 解码 URL 参数
|
|
|
|
|
|
const keyword = options.keyword ? decodeURIComponent(options.keyword) : this.data.keyword;
|
|
|
|
|
|
const mode = options.mode || 'classic';
|
|
|
|
|
|
const surname = options.surname ? decodeURIComponent(options.surname) : '';
|
|
|
|
|
|
|
|
|
|
|
|
// 模式名称映射
|
|
|
|
|
|
const modeNameMap = {
|
|
|
|
|
|
'baby': '宝宝',
|
|
|
|
|
|
'persona': '人设',
|
|
|
|
|
|
'classic': '拾遗'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
keyword,
|
|
|
|
|
|
mode,
|
|
|
|
|
|
surname,
|
|
|
|
|
|
modeName: modeNameMap[mode] || '拾遗',
|
|
|
|
|
|
isLoading: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 启动文案轮换
|
|
|
|
|
|
this.startLoadingQuoteRotation();
|
|
|
|
|
|
|
|
|
|
|
|
// 分批加载:先请求2个,再静默加载剩余
|
|
|
|
|
|
this.fetchNamesBatch(keyword, mode, surname);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onUnload() {
|
|
|
|
|
|
// 清理定时器
|
|
|
|
|
|
if (this.loadingQuoteTimer) {
|
|
|
|
|
|
clearInterval(this.loadingQuoteTimer);
|
|
|
|
|
|
this.loadingQuoteTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onShow() {
|
|
|
|
|
|
// 每次显示页面时加载收藏列表
|
|
|
|
|
|
this.loadCollectedNames();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 从后端加载收藏列表
|
|
|
|
|
|
loadCollectedNames() {
|
2026-04-18 16:56:31 +08:00
|
|
|
|
const openid = getApp().getOpenid();
|
|
|
|
|
|
if (!openid) {
|
|
|
|
|
|
console.log('openid 未获取到,跳过加载收藏');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-17 15:34:51 +08:00
|
|
|
|
const apiBaseUrl = getApp().globalData.apiBaseUrl;
|
|
|
|
|
|
wx.request({
|
|
|
|
|
|
url: `${apiBaseUrl}/api/favorites/list`,
|
|
|
|
|
|
data: { openid },
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.data && res.data.success) {
|
|
|
|
|
|
this.setData({ collectedNames: res.data.data || [] });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 启动加载文案轮换
|
|
|
|
|
|
startLoadingQuoteRotation() {
|
|
|
|
|
|
let index = 0;
|
|
|
|
|
|
this.loadingQuoteTimer = setInterval(() => {
|
|
|
|
|
|
index = (index + 1) % LOADING_QUOTES.length;
|
|
|
|
|
|
this.setData({ loadingQuote: LOADING_QUOTES[index] });
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 分批加载名字
|
|
|
|
|
|
fetchNamesBatch(keyword, mode, surname) {
|
|
|
|
|
|
// 第一波:请求2个名字,快速展示
|
|
|
|
|
|
this.fetchFirstBatch(keyword, mode, surname, 2);
|
2026-04-16 11:25:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-18 16:56:31 +08:00
|
|
|
|
// 第一波:快速获取前5个名字
|
2026-04-17 15:34:51 +08:00
|
|
|
|
fetchFirstBatch(keyword, mode, surname, count) {
|
|
|
|
|
|
const apiBaseUrl = getApp().globalData.apiBaseUrl;
|
2026-04-18 16:56:31 +08:00
|
|
|
|
const openid = getApp().getOpenid();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取已生成的名字列表(用于去重)
|
|
|
|
|
|
const existingNames = this.data.nameList.map(card => card.name).join(',');
|
|
|
|
|
|
|
|
|
|
|
|
const requestData = {
|
|
|
|
|
|
keyword,
|
|
|
|
|
|
count: 5,
|
2026-04-17 15:34:51 +08:00
|
|
|
|
batch: 'first'
|
|
|
|
|
|
};
|
|
|
|
|
|
if (mode) requestData.mode = mode;
|
|
|
|
|
|
if (surname) requestData.surname = surname;
|
2026-04-18 16:56:31 +08:00
|
|
|
|
if (openid) requestData.openid = openid;
|
|
|
|
|
|
if (existingNames) requestData.excludeNames = existingNames;
|
|
|
|
|
|
|
2026-04-16 11:25:29 +08:00
|
|
|
|
wx.request({
|
2026-04-17 15:34:51 +08:00
|
|
|
|
url: `${apiBaseUrl}/api/names/generate`,
|
|
|
|
|
|
data: requestData,
|
2026-04-16 11:25:29 +08:00
|
|
|
|
success: (res) => {
|
2026-04-18 16:56:31 +08:00
|
|
|
|
// 检查是否积分不足
|
|
|
|
|
|
if (res.data && !res.data.success) {
|
|
|
|
|
|
wx.showToast({ title: res.data.message || '次数已用完', icon: 'none' });
|
|
|
|
|
|
this.setData({ isLoading: false });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const nameList = res.data && res.data.data ? res.data.data : res.data;
|
|
|
|
|
|
if (res.statusCode === 200 && nameList && nameList.length > 0) {
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 清除定时器
|
|
|
|
|
|
if (this.loadingQuoteTimer) {
|
|
|
|
|
|
clearInterval(this.loadingQuoteTimer);
|
|
|
|
|
|
this.loadingQuoteTimer = null;
|
|
|
|
|
|
}
|
2026-04-18 16:56:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 立即展示结果(5个名字)
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
nameList: nameList,
|
|
|
|
|
|
currentIndex: 0,
|
|
|
|
|
|
isLoading: false,
|
|
|
|
|
|
cardKey: this.data.cardKey + 1
|
2026-04-17 15:34:51 +08:00
|
|
|
|
});
|
2026-04-18 16:56:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 呼吸震动效果 - 名字逐字显现时的节奏震动
|
|
|
|
|
|
this.breatheVibrate(nameList[0].name);
|
|
|
|
|
|
|
|
|
|
|
|
console.log('已加载 5 个不重复名字:', nameList.map(n => n.name).join(', '));
|
2026-04-16 11:25:29 +08:00
|
|
|
|
} else {
|
2026-04-17 15:34:51 +08:00
|
|
|
|
this.handleFetchError('意境未达,请重试');
|
2026-04-16 11:25:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-04-17 15:34:51 +08:00
|
|
|
|
fail: () => {
|
|
|
|
|
|
this.handleFetchError('网络疏离,请检查后端');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-18 16:56:31 +08:00
|
|
|
|
// 处理加载错误 - 显示结语卡片
|
2026-04-17 15:34:51 +08:00
|
|
|
|
handleFetchError(message) {
|
|
|
|
|
|
if (this.loadingQuoteTimer) {
|
|
|
|
|
|
clearInterval(this.loadingQuoteTimer);
|
|
|
|
|
|
this.loadingQuoteTimer = null;
|
|
|
|
|
|
}
|
2026-04-18 16:56:31 +08:00
|
|
|
|
this.setData({
|
|
|
|
|
|
isLoading: false,
|
|
|
|
|
|
showEndingCard: true
|
|
|
|
|
|
});
|
2026-04-17 15:34:51 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 返回首页
|
|
|
|
|
|
onBack() {
|
|
|
|
|
|
wx.navigateBack({
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
// 返回成功后,首页的 onShow 会重置状态
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 点击不喜欢按钮
|
|
|
|
|
|
onDislike() {
|
|
|
|
|
|
if (this.isExiting) return;
|
|
|
|
|
|
this.isExiting = true;
|
|
|
|
|
|
|
|
|
|
|
|
getApp().playAudio('swipe');
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
translateX: -500,
|
|
|
|
|
|
opacity: 0,
|
|
|
|
|
|
transition: 'all 0.3s cubic-bezier(0.6, -0.28, 0.735, 0.045)'
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 点击喜欢按钮
|
|
|
|
|
|
onLike() {
|
|
|
|
|
|
if (this.isExiting) return;
|
|
|
|
|
|
this.handleLike();
|
|
|
|
|
|
|
|
|
|
|
|
this.isExiting = true;
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
translateX: 500,
|
|
|
|
|
|
opacity: 0,
|
|
|
|
|
|
transition: 'all 0.3s cubic-bezier(0.6, -0.28, 0.735, 0.045)'
|
2026-04-16 11:25:29 +08:00
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onFlip() {
|
|
|
|
|
|
// 守卫:如果这是一次拖拽,isDragging 会为 true,则不执行翻转
|
|
|
|
|
|
if (this.isDragging || this.isExiting) return;
|
|
|
|
|
|
|
|
|
|
|
|
getApp().playAudio('flip');
|
|
|
|
|
|
this.setData({ isFlipped: !this.data.isFlipped });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onTouchStart(e) {
|
|
|
|
|
|
if (this.isExiting) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.isDragging = false; // 每次开始触摸时,都假定为点击,而非拖拽
|
|
|
|
|
|
this.startX = e.touches[0].clientX;
|
|
|
|
|
|
this.lastX = this.startX;
|
|
|
|
|
|
this.startY = e.touches[0].clientY;
|
|
|
|
|
|
this.startTime = Date.now();
|
|
|
|
|
|
this.setData({ transition: 'none' });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onTouchMove(e) {
|
|
|
|
|
|
if (this.isExiting) return;
|
|
|
|
|
|
|
|
|
|
|
|
const deltaX = e.touches[0].clientX - this.startX;
|
2026-04-18 16:56:31 +08:00
|
|
|
|
const deltaY = e.touches[0].clientY - this.startY;
|
|
|
|
|
|
|
|
|
|
|
|
// 检测上划进入广场(在Y轴移动超过X轴,且向上滑动)
|
|
|
|
|
|
if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < -50 && !this.isDragging) {
|
|
|
|
|
|
this.isDragging = true;
|
|
|
|
|
|
this.isSwipingUp = true;
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
translateY: deltaY,
|
|
|
|
|
|
opacity: 1 - Math.abs(deltaY) / 400,
|
|
|
|
|
|
transition: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 只有移动超过5px,才真正判定为"拖拽"
|
2026-04-16 11:25:29 +08:00
|
|
|
|
if (Math.abs(deltaX) > 5) {
|
|
|
|
|
|
this.isDragging = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果不是拖拽,则不进行任何移动
|
|
|
|
|
|
if (!this.isDragging) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.lastX = e.touches[0].clientX;
|
|
|
|
|
|
const rotate = deltaX * 0.05;
|
|
|
|
|
|
const opacity = 1 - Math.abs(deltaX) / 200;
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
translateX: deltaX,
|
|
|
|
|
|
rotate: rotate,
|
|
|
|
|
|
opacity: opacity
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onTouchEnd() {
|
|
|
|
|
|
if (this.isExiting) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果不是拖拽(即这是一次纯点击),则 onTouchEnd 不执行任何操作,交由 onFlip 处理
|
|
|
|
|
|
if (!this.isDragging) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-18 16:56:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理上划进入广场
|
|
|
|
|
|
if (this.isSwipingUp) {
|
|
|
|
|
|
const deltaY = this.data.translateY;
|
|
|
|
|
|
if (Math.abs(deltaY) > 150) {
|
|
|
|
|
|
// 上划距离足够,进入广场
|
|
|
|
|
|
this.isExiting = true;
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
translateY: -800,
|
|
|
|
|
|
opacity: 0,
|
|
|
|
|
|
transition: 'all 0.4s cubic-bezier(0.6, -0.28, 0.735, 0.045)'
|
|
|
|
|
|
});
|
|
|
|
|
|
// 延迟后跳转
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.onGoToSquare();
|
|
|
|
|
|
// 重置状态
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
translateY: 0,
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
transition: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
this.isExiting = false;
|
|
|
|
|
|
this.isDragging = false;
|
|
|
|
|
|
this.isSwipingUp = false;
|
|
|
|
|
|
}, 400);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 上划距离不够,回弹
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
translateY: 0,
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
transition: 'all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)'
|
|
|
|
|
|
});
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.isDragging = false;
|
|
|
|
|
|
this.isSwipingUp = false;
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 以下是拖拽结束后的逻辑(左右滑动)
|
2026-04-16 11:25:29 +08:00
|
|
|
|
const deltaX = this.lastX - this.startX;
|
|
|
|
|
|
const deltaTime = Date.now() - this.startTime;
|
|
|
|
|
|
const velocity = deltaX / deltaTime;
|
|
|
|
|
|
const shouldExit = Math.abs(deltaX) > EXIT_THRESHOLD || Math.abs(velocity) > FAST_SWIPE_VELOCITY;
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldExit) {
|
|
|
|
|
|
this.isExiting = true;
|
|
|
|
|
|
const direction = deltaX > 0 ? 1 : -1;
|
|
|
|
|
|
const targetX = direction * 500;
|
2026-04-18 16:56:31 +08:00
|
|
|
|
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 播放滑动音效
|
|
|
|
|
|
getApp().playAudio('swipe');
|
|
|
|
|
|
// 触觉反馈
|
|
|
|
|
|
wx.vibrateShort({ type: 'light' });
|
2026-04-18 16:56:31 +08:00
|
|
|
|
|
2026-04-16 11:25:29 +08:00
|
|
|
|
this.setData({
|
|
|
|
|
|
translateX: targetX,
|
|
|
|
|
|
opacity: 0,
|
|
|
|
|
|
transition: 'all 0.3s cubic-bezier(0.6, -0.28, 0.735, 0.045)'
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
translateX: 0,
|
|
|
|
|
|
rotate: 0,
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-04-18 16:56:31 +08:00
|
|
|
|
|
2026-04-16 11:25:29 +08:00
|
|
|
|
// 延迟重置 isDragging 标志位。
|
|
|
|
|
|
// 这是为了确保在系统触发 tap 事件时,isDragging 标志仍然为 true,
|
|
|
|
|
|
// 从而让 onFlip 方法可以正确地忽略掉这次由拖拽产生的 tap。
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.isDragging = false;
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onTransitionEnd() {
|
|
|
|
|
|
if (!this.isExiting) return;
|
|
|
|
|
|
|
|
|
|
|
|
const direction = this.data.translateX > 0 ? 'like' : 'dislike';
|
|
|
|
|
|
if (direction === 'like') {
|
|
|
|
|
|
this.handleLike();
|
|
|
|
|
|
}
|
2026-04-17 15:34:51 +08:00
|
|
|
|
this.handleCardExit(direction);
|
2026-04-16 11:25:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handleLike() {
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 触觉反馈 - 中等震动
|
2026-04-16 11:25:29 +08:00
|
|
|
|
wx.vibrateShort({ type: 'medium' });
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 音效反馈
|
2026-04-16 11:25:29 +08:00
|
|
|
|
getApp().playAudio('success');
|
2026-04-17 15:34:51 +08:00
|
|
|
|
|
2026-04-16 11:25:29 +08:00
|
|
|
|
const card = this.data.nameList[this.data.currentIndex];
|
|
|
|
|
|
if (!this.data.collectedNames.some(item => item.name === card.name)) {
|
|
|
|
|
|
this.setData({ collectedNames: [...this.data.collectedNames, card] });
|
2026-04-17 15:34:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 同步到后端
|
|
|
|
|
|
this.syncFavoriteToBackend(card);
|
2026-04-16 11:25:29 +08:00
|
|
|
|
}
|
2026-04-17 15:34:51 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 同步收藏到后端
|
|
|
|
|
|
syncFavoriteToBackend(card) {
|
2026-04-18 16:56:31 +08:00
|
|
|
|
const openid = getApp().getOpenid();
|
|
|
|
|
|
if (!openid) {
|
|
|
|
|
|
console.log('openid 未获取到,无法同步收藏');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-17 15:34:51 +08:00
|
|
|
|
const apiBaseUrl = getApp().globalData.apiBaseUrl;
|
|
|
|
|
|
wx.request({
|
|
|
|
|
|
url: `${apiBaseUrl}/api/favorites/add?openid=${openid}&mode=${this.data.mode}&keyword=${encodeURIComponent(this.data.keyword)}`,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
header: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
data: {
|
|
|
|
|
|
name: card.name,
|
|
|
|
|
|
origin: card.origin,
|
|
|
|
|
|
description: card.description,
|
|
|
|
|
|
tone: card.tone,
|
|
|
|
|
|
score: card.score
|
|
|
|
|
|
},
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.data && res.data.success) {
|
|
|
|
|
|
console.log('收藏同步成功');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('收藏同步失败:', res.data);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.log('收藏同步失败:', err);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-04-16 11:25:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handleCardExit() {
|
|
|
|
|
|
const nextIndex = this.data.currentIndex + 1;
|
|
|
|
|
|
wx.nextTick(() => {
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
transition: 'none',
|
|
|
|
|
|
translateX: 0,
|
|
|
|
|
|
rotate: 0,
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
isFlipped: false
|
|
|
|
|
|
}, () => {
|
|
|
|
|
|
if (nextIndex < this.data.nameList.length) {
|
|
|
|
|
|
this.setData({ currentIndex: nextIndex, cardKey: this.data.cardKey + 1 });
|
|
|
|
|
|
} else {
|
2026-04-18 16:56:31 +08:00
|
|
|
|
// 检查是否还有剩余次数
|
|
|
|
|
|
this.checkAndLoadMore();
|
2026-04-16 11:25:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
this.isExiting = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-18 16:56:31 +08:00
|
|
|
|
// 检查是否还有次数,有则自动加载,无则显示结语卡片
|
|
|
|
|
|
checkAndLoadMore() {
|
|
|
|
|
|
const openid = getApp().getOpenid();
|
|
|
|
|
|
if (!openid) {
|
|
|
|
|
|
this.setData({ showEndingCard: true });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const apiBaseUrl = getApp().globalData.apiBaseUrl;
|
|
|
|
|
|
wx.request({
|
|
|
|
|
|
url: `${apiBaseUrl}/api/credits/info`,
|
|
|
|
|
|
data: { openid },
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.data && res.data.success) {
|
|
|
|
|
|
const creditsInfo = res.data.data;
|
|
|
|
|
|
if (creditsInfo.dailyCredits > 0) {
|
|
|
|
|
|
// 还有次数,自动加载新的一批
|
|
|
|
|
|
console.log('还有次数,自动加载新的一批');
|
|
|
|
|
|
this.setData({ isLoading: true });
|
|
|
|
|
|
this.startLoadingQuoteRotation();
|
|
|
|
|
|
this.fetchFirstBatch(this.data.keyword, this.data.mode, this.data.surname, 5);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 次数用完,显示结语卡片
|
|
|
|
|
|
this.setData({ showEndingCard: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.setData({ showEndingCard: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
this.setData({ showEndingCard: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-16 11:25:29 +08:00
|
|
|
|
toggleCollectionView() {
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 跳转到 profile 页面
|
|
|
|
|
|
wx.navigateTo({
|
|
|
|
|
|
url: '/pages/profile/profile'
|
|
|
|
|
|
});
|
2026-04-16 11:25:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-18 16:56:31 +08:00
|
|
|
|
// 跳转到灵感广场
|
|
|
|
|
|
onGoToSquare() {
|
|
|
|
|
|
wx.navigateTo({
|
|
|
|
|
|
url: '/pages/square/square'
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-16 11:25:29 +08:00
|
|
|
|
onDeleteCollected(e) {
|
|
|
|
|
|
const nameToDelete = e.currentTarget.dataset.name;
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
collectedNames: this.data.collectedNames.filter(item => item.name !== nameToDelete)
|
|
|
|
|
|
});
|
|
|
|
|
|
wx.vibrateShort({ type: 'light' });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 显示海报弹窗
|
2026-04-16 11:25:29 +08:00
|
|
|
|
onSavePoster() {
|
|
|
|
|
|
const card = this.data.nameList[this.data.currentIndex];
|
2026-04-17 15:34:51 +08:00
|
|
|
|
const nameChars = card.name.split('');
|
|
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
showPoster: true,
|
|
|
|
|
|
posterNameChars: nameChars
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 触觉反馈
|
|
|
|
|
|
wx.vibrateShort({ type: 'light' });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭海报弹窗
|
|
|
|
|
|
closePoster() {
|
|
|
|
|
|
this.setData({ showPoster: false });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 保存海报到相册
|
|
|
|
|
|
savePoster() {
|
2026-04-16 11:25:29 +08:00
|
|
|
|
wx.showLoading({ title: '绘笔收录中...', mask: true });
|
2026-04-17 15:34:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 使用 snapshot 组件截图
|
|
|
|
|
|
wx.nextTick(() => {
|
|
|
|
|
|
const query = wx.createSelectorQuery().in(this);
|
|
|
|
|
|
query.select('#posterArea')
|
|
|
|
|
|
.node()
|
|
|
|
|
|
.exec((res) => {
|
|
|
|
|
|
const node = res[0].node;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 snapshot 截图
|
2026-04-16 11:25:29 +08:00
|
|
|
|
wx.canvasToTempFilePath({
|
2026-04-17 15:34:51 +08:00
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
width: node.width,
|
|
|
|
|
|
height: node.height,
|
|
|
|
|
|
destWidth: node.width * 2,
|
|
|
|
|
|
destHeight: node.height * 2,
|
|
|
|
|
|
canvasId: 'posterCanvas',
|
2026-04-16 11:25:29 +08:00
|
|
|
|
success: (res) => {
|
|
|
|
|
|
wx.hideLoading();
|
2026-04-17 15:34:51 +08:00
|
|
|
|
wx.saveImageToPhotosAlbum({
|
|
|
|
|
|
filePath: res.tempFilePath,
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
wx.showToast({ title: '已保存到相册', icon: 'success' });
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
wx.showToast({ title: '保存失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-04-16 11:25:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
wx.hideLoading();
|
|
|
|
|
|
wx.showToast({ title: '绘笔受阻', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-04-17 15:34:51 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 分享海报
|
|
|
|
|
|
sharePoster() {
|
|
|
|
|
|
wx.showShareImageMenu({
|
|
|
|
|
|
path: '/images/share.png'
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-18 16:56:31 +08:00
|
|
|
|
// 分享到灵感广场
|
|
|
|
|
|
shareToSquare() {
|
|
|
|
|
|
const card = this.data.nameList[this.data.currentIndex];
|
|
|
|
|
|
const openid = getApp().getOpenid();
|
|
|
|
|
|
|
|
|
|
|
|
if (!openid) {
|
|
|
|
|
|
wx.showToast({ title: '请先登录', icon: 'none' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wx.showLoading({ title: '分享中...', mask: true });
|
|
|
|
|
|
|
|
|
|
|
|
const apiBaseUrl = getApp().globalData.apiBaseUrl;
|
|
|
|
|
|
|
|
|
|
|
|
// 直接提交到灵感广场(不上传图片,使用默认图片)
|
|
|
|
|
|
// 将数据转换为 URL 编码格式
|
|
|
|
|
|
const formData = `name=${encodeURIComponent(card.name)}&origin=${encodeURIComponent(card.origin)}&description=${encodeURIComponent(card.description)}&keyword=${encodeURIComponent(this.data.keyword)}&mode=${encodeURIComponent(this.data.mode)}&openid=${encodeURIComponent(openid)}&imageUrl=`;
|
|
|
|
|
|
|
|
|
|
|
|
wx.request({
|
|
|
|
|
|
url: `${apiBaseUrl}/api/square/posts`,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
header: {
|
|
|
|
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
|
|
|
|
},
|
|
|
|
|
|
data: formData,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
wx.hideLoading();
|
|
|
|
|
|
if (res.data && res.data.success) {
|
|
|
|
|
|
wx.showToast({ title: '分享成功', icon: 'success' });
|
|
|
|
|
|
this.setData({ showPoster: false });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
wx.showToast({ title: res.data.message || '分享失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
wx.hideLoading();
|
|
|
|
|
|
wx.showToast({ title: '分享失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-17 15:34:51 +08:00
|
|
|
|
// 阻止事件冒泡
|
|
|
|
|
|
preventBubble() {
|
|
|
|
|
|
// 什么都不做,只是阻止事件冒泡
|
2026-04-18 16:56:31 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 点击音符按钮
|
|
|
|
|
|
onMusicTap() {
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
showMusicMenu: !this.data.showMusicMenu
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 阻止菜单冒泡
|
|
|
|
|
|
onMenuTap() {
|
|
|
|
|
|
// 什么都不做,只是阻止事件冒泡
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 切换环境音
|
|
|
|
|
|
onAmbienceChange(e) {
|
|
|
|
|
|
const type = e.currentTarget.dataset.type;
|
|
|
|
|
|
const { currentAmbience } = this.data;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果点击当前正在播放的类型,则切换到静音
|
|
|
|
|
|
const newType = (type === currentAmbience) ? 'silent' : type;
|
|
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
currentAmbience: newType,
|
|
|
|
|
|
showMusicMenu: false
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 播放或停止环境音
|
|
|
|
|
|
const { playAmbience, fadeOutAndStop } = require('../../utils/audio.js');
|
|
|
|
|
|
if (newType === 'silent') {
|
|
|
|
|
|
fadeOutAndStop();
|
|
|
|
|
|
wx.showToast({ title: '已静音', icon: 'none' });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
playAmbience(newType, true);
|
|
|
|
|
|
const typeName = { wind: '风铃', rain: '雨落', guqin: '古琴' }[newType];
|
|
|
|
|
|
wx.showToast({ title: `正在播放:${typeName}`, icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 长按名字显示字源弹窗
|
|
|
|
|
|
onNameLongPress() {
|
|
|
|
|
|
const currentName = this.data.nameList[this.data.currentIndex];
|
|
|
|
|
|
if (!currentName || !currentName.name) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 默认显示名字的第一个字
|
|
|
|
|
|
const firstChar = currentName.name.charAt(0);
|
|
|
|
|
|
this.showCharDetail(firstChar);
|
|
|
|
|
|
|
|
|
|
|
|
// 触觉反馈
|
|
|
|
|
|
wx.vibrateLong();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 显示字源详情
|
|
|
|
|
|
showCharDetail(char) {
|
|
|
|
|
|
// 播放水滴声(入墨效果)
|
|
|
|
|
|
const { playSoundEffect } = require('../../utils/audio.js');
|
|
|
|
|
|
playSoundEffect('inkDrop', { volume: 0.5 });
|
|
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
showCharDetail: true,
|
|
|
|
|
|
selectedChar: char,
|
|
|
|
|
|
charDetailData: null
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 加载字源数据
|
|
|
|
|
|
this.loadCharDetail(char);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 加载字源数据
|
|
|
|
|
|
loadCharDetail(char) {
|
|
|
|
|
|
const apiBaseUrl = getApp().globalData.apiBaseUrl;
|
|
|
|
|
|
wx.request({
|
|
|
|
|
|
url: `${apiBaseUrl}/api/char/detail`,
|
|
|
|
|
|
data: { char },
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.data && res.data.success) {
|
|
|
|
|
|
this.setData({ charDetailData: res.data.data });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
// 使用本地静态数据作为备选
|
|
|
|
|
|
this.setData({ charDetailData: this.getLocalCharData(char) });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 本地静态字源数据(备选)
|
|
|
|
|
|
getLocalCharData(char) {
|
|
|
|
|
|
const charDB = {
|
|
|
|
|
|
'清': {
|
|
|
|
|
|
char: '清',
|
|
|
|
|
|
pinyin: 'qīng',
|
|
|
|
|
|
radical: '氵',
|
|
|
|
|
|
strokes: 11,
|
|
|
|
|
|
wuxing: '水',
|
|
|
|
|
|
meaning: '水清澈透明,引申为纯净、高洁、明白',
|
|
|
|
|
|
imagery: '清澈如泉,心境澄明。寓意纯净通透、高洁自持,如清水出芙蓉,天然去雕饰。',
|
|
|
|
|
|
poetry: '「清水出芙蓉,天然去雕饰」——李白'
|
|
|
|
|
|
},
|
|
|
|
|
|
'雅': {
|
|
|
|
|
|
char: '雅',
|
|
|
|
|
|
pinyin: 'yǎ',
|
|
|
|
|
|
radical: '隹',
|
|
|
|
|
|
strokes: 12,
|
|
|
|
|
|
wuxing: '木',
|
|
|
|
|
|
meaning: '正也,规范、美好、高尚',
|
|
|
|
|
|
imagery: '温文尔雅,气度不凡。寓意举止得体、品味高雅,如芝兰玉树,生于庭阶。',
|
|
|
|
|
|
poetry: '「雅步擢纤腰,巧笑发皓齿」——曹植'
|
|
|
|
|
|
},
|
|
|
|
|
|
'若': {
|
|
|
|
|
|
char: '若',
|
|
|
|
|
|
pinyin: 'ruò',
|
|
|
|
|
|
radical: '艹',
|
|
|
|
|
|
strokes: 8,
|
|
|
|
|
|
wuxing: '木',
|
|
|
|
|
|
meaning: '如、像,引申为顺从、选择',
|
|
|
|
|
|
imagery: '虚怀若谷,温润如玉。寓意谦逊包容、从容自在,如兰若生春阳。',
|
|
|
|
|
|
poetry: '「若有人兮山之阿,被薜荔兮带女萝」——屈原'
|
|
|
|
|
|
},
|
|
|
|
|
|
'溪': {
|
|
|
|
|
|
char: '溪',
|
|
|
|
|
|
pinyin: 'xī',
|
|
|
|
|
|
radical: '氵',
|
|
|
|
|
|
strokes: 13,
|
|
|
|
|
|
wuxing: '水',
|
|
|
|
|
|
meaning: '山间小河,水流清澈',
|
|
|
|
|
|
imagery: '溪水潺潺,源远流长。寓意灵动清澈、生生不息,如溪水绕石,柔中带刚。',
|
|
|
|
|
|
poetry: '「旧时茅店社林边,路转溪桥忽见」——辛弃疾'
|
|
|
|
|
|
},
|
|
|
|
|
|
'云': {
|
|
|
|
|
|
char: '云',
|
|
|
|
|
|
pinyin: 'yún',
|
|
|
|
|
|
radical: '二',
|
|
|
|
|
|
strokes: 4,
|
|
|
|
|
|
wuxing: '水',
|
|
|
|
|
|
meaning: '水气上升凝结,引申为说、众多',
|
|
|
|
|
|
imagery: '云卷云舒,自在飘逸。寓意超然物外、洒脱不羁,如行云流水,任意西东。',
|
|
|
|
|
|
poetry: '「行到水穷处,坐看云起时」——王维'
|
|
|
|
|
|
},
|
|
|
|
|
|
'月': {
|
|
|
|
|
|
char: '月',
|
|
|
|
|
|
pinyin: 'yuè',
|
|
|
|
|
|
radical: '月',
|
|
|
|
|
|
strokes: 4,
|
|
|
|
|
|
wuxing: '木',
|
|
|
|
|
|
meaning: '月亮,引申为月份、光明',
|
|
|
|
|
|
imagery: '明月清风,皎洁无瑕。寓意光明磊落、清雅高洁,如月出东山,照彻千里。',
|
|
|
|
|
|
poetry: '「举杯邀明月,对影成三人」——李白'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return charDB[char] || {
|
|
|
|
|
|
char: char,
|
|
|
|
|
|
pinyin: '',
|
|
|
|
|
|
radical: '',
|
|
|
|
|
|
strokes: '',
|
|
|
|
|
|
wuxing: '',
|
|
|
|
|
|
meaning: '暂无详细解析',
|
|
|
|
|
|
imagery: '暂无意象分析',
|
|
|
|
|
|
poetry: ''
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭字源弹窗
|
|
|
|
|
|
closeCharDetail() {
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
showCharDetail: false,
|
|
|
|
|
|
selectedChar: '',
|
|
|
|
|
|
charDetailData: null
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 呼吸震动 - 名字逐字显现时的节奏震动
|
|
|
|
|
|
breatheVibrate(name) {
|
|
|
|
|
|
if (!name || name.length === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
const chars = name.split('');
|
|
|
|
|
|
let index = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 每 300ms 震动一次,模拟呼吸节奏
|
|
|
|
|
|
const vibrateInterval = setInterval(() => {
|
|
|
|
|
|
if (index >= chars.length) {
|
|
|
|
|
|
clearInterval(vibrateInterval);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 极微弱的震动,营造意境
|
|
|
|
|
|
wx.vibrateShort({ type: 'light' });
|
|
|
|
|
|
|
|
|
|
|
|
// 播放字显现音效
|
|
|
|
|
|
const { playSoundEffect } = require('../../utils/audio.js');
|
|
|
|
|
|
playSoundEffect('char', { volume: 0.3 });
|
|
|
|
|
|
|
|
|
|
|
|
index++;
|
|
|
|
|
|
}, 300);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 打开 AI 解析弹窗
|
|
|
|
|
|
onAskAI(e) {
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
showAIModal: true,
|
|
|
|
|
|
aiContext: '',
|
|
|
|
|
|
aiExplanation: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭 AI 解析弹窗
|
|
|
|
|
|
closeAIModal() {
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
showAIModal: false,
|
|
|
|
|
|
aiContext: '',
|
|
|
|
|
|
aiExplanation: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// AI 上下文输入
|
|
|
|
|
|
onAIContextInput(e) {
|
|
|
|
|
|
this.setData({ aiContext: e.detail.value });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 请求 AI 解析
|
|
|
|
|
|
requestAIExplanation() {
|
|
|
|
|
|
const currentName = this.data.nameList[this.data.currentIndex];
|
|
|
|
|
|
if (!currentName || !currentName.name) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.setData({ aiLoading: true });
|
|
|
|
|
|
|
|
|
|
|
|
const apiBaseUrl = getApp().globalData.apiBaseUrl;
|
|
|
|
|
|
wx.request({
|
|
|
|
|
|
url: `${apiBaseUrl}/api/names/explain`,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
header: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
data: {
|
|
|
|
|
|
name: currentName.name,
|
|
|
|
|
|
context: this.data.aiContext || '一般场景'
|
|
|
|
|
|
},
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.data && res.data.success) {
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
aiExplanation: res.data.data,
|
|
|
|
|
|
aiLoading: false
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
wx.showToast({ title: '解析失败', icon: 'none' });
|
|
|
|
|
|
this.setData({ aiLoading: false });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
wx.showToast({ title: '网络错误', icon: 'none' });
|
|
|
|
|
|
this.setData({ aiLoading: false });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-04-16 11:25:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|