243 lines
7.2 KiB
JavaScript
243 lines
7.2 KiB
JavaScript
const EXIT_THRESHOLD = 80;
|
||
const FAST_SWIPE_VELOCITY = 0.5;
|
||
|
||
Page({
|
||
isDragging: false,
|
||
isExiting: false,
|
||
startTime: 0,
|
||
startX: 0,
|
||
startY: 0,
|
||
lastX: 0,
|
||
|
||
data: {
|
||
nameList: [],
|
||
currentIndex: 0,
|
||
isLoading: true,
|
||
isFlipped: false,
|
||
keyword: '清冷',
|
||
|
||
// 动画控制
|
||
translateX: 0,
|
||
rotate: 0,
|
||
opacity: 1,
|
||
transition: 'none',
|
||
|
||
cardKey: 0,
|
||
collectedNames: [],
|
||
showCollection: false
|
||
},
|
||
|
||
onLoad(options) {
|
||
const keyword = options.keyword || this.data.keyword;
|
||
this.setData({ keyword });
|
||
this.fetchNames(keyword);
|
||
},
|
||
|
||
fetchNames(keyword) {
|
||
this.setData({ isLoading: true });
|
||
wx.showLoading({ title: '见素正在感悟...', mask: true });
|
||
wx.request({
|
||
url: 'http://localhost:8080/api/names/generate',
|
||
data: { keyword },
|
||
success: (res) => {
|
||
if (res.statusCode === 200 && res.data && res.data.length > 0) {
|
||
this.setData({ nameList: res.data, currentIndex: 0, isLoading: false, cardKey: this.data.cardKey + 1 });
|
||
} else {
|
||
wx.showToast({ title: '意境未达,请重试', icon: 'none' });
|
||
}
|
||
},
|
||
fail: () => wx.showToast({ title: '网络疏离,请检查后端', icon: 'none' }),
|
||
complete: () => { this.setData({ isLoading: false }); wx.hideLoading(); }
|
||
});
|
||
},
|
||
|
||
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;
|
||
// 只有移动超过5px,才真正判定为“拖拽”
|
||
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;
|
||
}
|
||
|
||
// 以下是拖拽结束后的逻辑
|
||
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;
|
||
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)'
|
||
});
|
||
}
|
||
|
||
// 延迟重置 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();
|
||
} else {
|
||
this.handleCardExit(direction);
|
||
}
|
||
},
|
||
|
||
handleLike() {
|
||
wx.vibrateShort({ type: 'medium' });
|
||
getApp().playAudio('success');
|
||
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] });
|
||
}
|
||
this.handleCardExit('like');
|
||
},
|
||
|
||
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 {
|
||
wx.showModal({
|
||
title: '见素时刻',
|
||
content: '这一波灵感已尽,是否再求几名?',
|
||
confirmText: '再求', cancelText: '返回',
|
||
success: (res) => { if (res.confirm) this.fetchNames(this.data.keyword); }
|
||
});
|
||
}
|
||
this.isExiting = false;
|
||
});
|
||
});
|
||
},
|
||
|
||
toggleCollectionView() {
|
||
this.setData({ showCollection: !this.data.showCollection });
|
||
},
|
||
|
||
onDeleteCollected(e) {
|
||
const nameToDelete = e.currentTarget.dataset.name;
|
||
this.setData({
|
||
collectedNames: this.data.collectedNames.filter(item => item.name !== nameToDelete)
|
||
});
|
||
wx.vibrateShort({ type: 'light' });
|
||
},
|
||
|
||
onSavePoster() {
|
||
const card = this.data.nameList[this.data.currentIndex];
|
||
wx.showLoading({ title: '绘笔收录中...', mask: true });
|
||
const query = wx.createSelectorQuery().in(this);
|
||
query.select('#posterCanvas')
|
||
.fields({ node: true, size: true })
|
||
.exec((res) => {
|
||
const canvas = res[0].node;
|
||
const ctx = canvas.getContext('2d');
|
||
const dpr = wx.getSystemInfoSync().pixelRatio;
|
||
canvas.width = 750 * dpr;
|
||
canvas.height = 1334 * dpr;
|
||
ctx.scale(dpr, dpr);
|
||
ctx.fillStyle = '#FFFFFF';
|
||
ctx.fillRect(0, 0, 750, 1334);
|
||
ctx.fillStyle = '#2D2D2D';
|
||
ctx.font = '300 120px serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
const name = card.name;
|
||
const charArray = name.split('');
|
||
const charHeight = 140;
|
||
const startY = 667 - (charArray.length * charHeight) / 2 + charHeight / 2;
|
||
charArray.forEach((char, index) => ctx.fillText(char, 375, startY + index * charHeight));
|
||
const sealChar = this.data.keyword.substring(0, 1) || '素';
|
||
ctx.fillStyle = '#B22222';
|
||
ctx.fillRect(100, 100, 80, 80);
|
||
ctx.fillStyle = '#FFFFFF';
|
||
ctx.font = '40px serif';
|
||
ctx.fillText(sealChar, 140, 140);
|
||
const randomNum = Math.floor(Math.random() * 9000) + 1000;
|
||
ctx.fillStyle = '#D0D0D0';
|
||
ctx.font = '24px sans-serif';
|
||
ctx.fillText(`见素第 ${randomNum} 号灵感`, 375, 1200);
|
||
setTimeout(() => {
|
||
wx.canvasToTempFilePath({
|
||
canvas: canvas,
|
||
success: (res) => {
|
||
wx.hideLoading();
|
||
wx.showShareImageMenu({ path: res.tempFilePath });
|
||
},
|
||
fail: () => {
|
||
wx.hideLoading();
|
||
wx.showToast({ title: '绘笔受阻', icon: 'none' });
|
||
}
|
||
});
|
||
}, 300);
|
||
});
|
||
}
|
||
});
|