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); }); } });