dapaijizhang3/pages/stats/session/session.js

552 lines
14 KiB
JavaScript
Raw Permalink 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.

// pages/stats/session/session.js
import request from '../../../utils/request'
Page({
data: {
sessionId: null,
session: null,
finalRanking: [],
playerStats: [],
recentRecords: [],
totalRounds: 0,
totalChips: 0,
duration: '',
// 图表数据
chartLegend: [],
chartWidth: 750,
scoreData: [],
// 分析数据
mostIntenseRound: 0,
mostIntenseScore: 0,
avgRoundScore: 0,
longestStreak: { player: '', count: 0 },
comebackKing: { player: '', points: 0 },
// 分享海报
showSharePoster: false,
gameTypeText: {
'mahjong': '麻将',
'poker': '扑克',
'other': '其他'
}
},
onLoad(options) {
if (!options.id) {
wx.showToast({
title: '参数错误',
icon: 'none'
})
setTimeout(() => {
wx.navigateBack()
}, 1500)
return
}
this.setData({ sessionId: options.id })
this.loadStatistics()
},
onReady() {
// 延迟绘制图表确保canvas已准备好
setTimeout(() => {
this.drawScoreChart()
}, 500)
},
// 加载统计数据
async loadStatistics() {
wx.showLoading({ title: '加载中...' })
try {
// 获取牌局详情
const sessionData = await request.get(`/rooms/${this.data.sessionId}`)
// 获取统计数据
const statsData = await request.get(`/stats/session/${this.data.sessionId}`)
// 获取所有记录
const recordsData = await request.get(`/records/session/${this.data.sessionId}`, {
page: 1,
pageSize: 100
})
// 计算游戏时长
const duration = this.calculateDuration(sessionData.session)
// 计算最终排名
const finalRanking = this.calculateFinalRanking(sessionData.players, statsData)
// 计算玩家统计
const playerStats = this.calculatePlayerStats(statsData, recordsData.list)
// 准备图表数据
const chartData = this.prepareChartData(recordsData.list, sessionData.players)
// 分析数据
const analysis = this.analyzeData(recordsData.list, sessionData.players)
// 格式化最近记录
const recentRecords = this.formatRecords(recordsData.list.slice(0, 10))
this.setData({
session: sessionData.session,
finalRanking,
playerStats,
recentRecords,
totalRounds: recordsData.total || 0,
totalChips: Math.abs(statsData.total_chips || 0),
duration,
chartLegend: chartData.legend,
scoreData: chartData.data,
chartWidth: Math.max(750, chartData.data.length * 50),
...analysis
})
wx.hideLoading()
} catch (error) {
wx.hideLoading()
wx.showToast({
title: error.message || '加载失败',
icon: 'none'
})
}
},
// 计算游戏时长
calculateDuration(session) {
const start = session.created_at * 1000
const end = session.ended_at ? session.ended_at * 1000 : Date.now()
const diff = end - start
const hours = Math.floor(diff / 3600000)
const minutes = Math.floor((diff % 3600000) / 60000)
if (hours > 0) {
return `${hours}小时${minutes}分钟`
} else {
return `${minutes}分钟`
}
},
// 计算最终排名
calculateFinalRanking(players, stats) {
return players.map(p => {
const playerStats = stats.players?.[p.player_id] || {}
const winRounds = playerStats.win_rounds || 0
const totalRounds = playerStats.total_rounds || 1
return {
player_id: p.player_id,
nickname: p.nickname,
avatar_url: p.avatar_url,
final_chips: p.final_chips || 0,
win_rate: totalRounds > 0 ? Math.round((winRounds / totalRounds) * 100) : 0,
rounds_played: totalRounds
}
}).sort((a, b) => b.final_chips - a.final_chips)
},
// 计算玩家统计
calculatePlayerStats(stats, records) {
const playerStats = []
for (const playerId in stats.players) {
const player = stats.players[playerId]
// 计算每个玩家的详细统计
const playerRecords = []
records.forEach(record => {
const score = record.playerScores?.find(s => s.player_id == playerId)
if (score) {
playerRecords.push(score.chips_change)
}
})
const maxWin = Math.max(...playerRecords.filter(s => s > 0), 0)
const maxLose = Math.min(...playerRecords.filter(s => s < 0), 0)
const avgScore = playerRecords.length > 0
? Math.round(playerRecords.reduce((a, b) => a + b, 0) / playerRecords.length)
: 0
// 计算连胜
let currentStreak = 0
let maxStreak = 0
playerRecords.forEach(score => {
if (score > 0) {
currentStreak++
maxStreak = Math.max(maxStreak, currentStreak)
} else {
currentStreak = 0
}
})
playerStats.push({
player_id: playerId,
nickname: player.nickname,
avatar_url: player.avatar_url,
max_win: maxWin,
max_lose: maxLose,
avg_score: avgScore,
win_streak: maxStreak
})
}
return playerStats
},
// 准备图表数据
prepareChartData(records, players) {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD']
const legend = players.map((p, i) => ({
player_id: p.player_id,
nickname: p.nickname,
color: colors[i % colors.length]
}))
// 构建积分走势数据
const data = []
const runningScores = {}
// 初始化积分
players.forEach(p => {
runningScores[p.player_id] = 0
})
// 添加起始点
data.push({
round: 0,
scores: { ...runningScores }
})
// 计算每轮后的积分
records.forEach(record => {
record.playerScores?.forEach(score => {
runningScores[score.player_id] = (runningScores[score.player_id] || 0) + score.chips_change
})
data.push({
round: record.round_number,
scores: { ...runningScores }
})
})
return { legend, data }
},
// 分析数据
analyzeData(records, players) {
// 最激烈对局
let mostIntenseRound = 0
let mostIntenseScore = 0
records.forEach(record => {
const totalChange = record.playerScores?.reduce((sum, s) =>
sum + Math.abs(s.chips_change), 0) || 0
if (totalChange > mostIntenseScore) {
mostIntenseScore = totalChange
mostIntenseRound = record.round_number
}
})
// 平均每局分数
const totalScore = records.reduce((sum, record) => {
const roundTotal = record.playerScores?.reduce((s, score) =>
s + Math.abs(score.chips_change), 0) || 0
return sum + roundTotal
}, 0)
const avgRoundScore = records.length > 0
? Math.round(totalScore / records.length / 2)
: 0
// 最长连胜
const streaks = {}
const currentStreaks = {}
players.forEach(p => {
streaks[p.player_id] = 0
currentStreaks[p.player_id] = 0
})
records.forEach(record => {
record.playerScores?.forEach(score => {
if (score.chips_change > 0) {
currentStreaks[score.player_id] = (currentStreaks[score.player_id] || 0) + 1
streaks[score.player_id] = Math.max(
streaks[score.player_id] || 0,
currentStreaks[score.player_id]
)
} else {
currentStreaks[score.player_id] = 0
}
})
})
let longestStreak = { player: '', count: 0 }
for (const playerId in streaks) {
if (streaks[playerId] > longestStreak.count) {
const player = players.find(p => p.player_id == playerId)
longestStreak = {
player: player?.nickname || '',
count: streaks[playerId]
}
}
}
// 逆袭王(从最低点到最高点的差值最大的玩家)
const comebacks = {}
const runningScores = {}
const minScores = {}
players.forEach(p => {
runningScores[p.player_id] = 0
minScores[p.player_id] = 0
comebacks[p.player_id] = 0
})
records.forEach(record => {
record.playerScores?.forEach(score => {
runningScores[score.player_id] = (runningScores[score.player_id] || 0) + score.chips_change
minScores[score.player_id] = Math.min(
minScores[score.player_id] || 0,
runningScores[score.player_id]
)
})
})
let comebackKing = { player: '', points: 0 }
for (const playerId in runningScores) {
const comeback = runningScores[playerId] - minScores[playerId]
if (comeback > comebackKing.points) {
const player = players.find(p => p.player_id == playerId)
comebackKing = {
player: player?.nickname || '',
points: comeback
}
}
}
return {
mostIntenseRound,
mostIntenseScore: mostIntenseScore / 2,
avgRoundScore,
longestStreak,
comebackKing
}
},
// 格式化记录
formatRecords(records) {
return records.map(record => {
const time = new Date(record.created_at * 1000)
return {
...record,
timeText: `${time.getMonth() + 1}/${time.getDate()} ${time.getHours()}:${String(time.getMinutes()).padStart(2, '0')}`
}
})
},
// 绘制积分走势图
drawScoreChart() {
const ctx = wx.createCanvasContext('scoreChart')
const { scoreData, chartLegend } = this.data
if (scoreData.length === 0) return
const width = this.data.chartWidth
const height = 300
const padding = 40
const chartWidth = width - padding * 2
const chartHeight = height - padding * 2
// 找出最大最小值
let minScore = 0
let maxScore = 0
scoreData.forEach(data => {
for (const playerId in data.scores) {
minScore = Math.min(minScore, data.scores[playerId])
maxScore = Math.max(maxScore, data.scores[playerId])
}
})
const scoreRange = maxScore - minScore || 100
const xStep = chartWidth / (scoreData.length - 1 || 1)
const yScale = chartHeight / scoreRange
// 绘制网格
ctx.setStrokeStyle('#e0e0e0')
ctx.setLineWidth(0.5)
// 横线
for (let i = 0; i <= 5; i++) {
const y = padding + (chartHeight / 5) * i
ctx.beginPath()
ctx.moveTo(padding, y)
ctx.lineTo(width - padding, y)
ctx.stroke()
}
// 绘制每个玩家的线
chartLegend.forEach(legend => {
ctx.setStrokeStyle(legend.color)
ctx.setLineWidth(2)
ctx.beginPath()
scoreData.forEach((data, index) => {
const x = padding + xStep * index
const y = padding + chartHeight - (data.scores[legend.player_id] - minScore) * yScale
if (index === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
})
ctx.stroke()
// 绘制点
ctx.setFillStyle(legend.color)
scoreData.forEach((data, index) => {
const x = padding + xStep * index
const y = padding + chartHeight - (data.scores[legend.player_id] - minScore) * yScale
ctx.beginPath()
ctx.arc(x, y, 3, 0, 2 * Math.PI)
ctx.fill()
})
})
// 绘制坐标轴标签
ctx.setFillStyle('#666')
ctx.setFontSize(10)
// X轴标签
scoreData.forEach((data, index) => {
if (index % Math.ceil(scoreData.length / 10) === 0) {
const x = padding + xStep * index
ctx.fillText(`${data.round}`, x - 15, height - 10)
}
})
ctx.draw()
},
// 查看所有记录
viewAllRecords() {
wx.navigateTo({
url: `/pages/game/records/records?id=${this.data.sessionId}`
})
},
// 导出数据
async exportData() {
wx.showToast({
title: '功能开发中',
icon: 'none'
})
},
// 分享战绩
shareStats() {
this.setData({ showSharePoster: true })
this.drawSharePoster()
},
// 绘制分享海报
drawSharePoster() {
const ctx = wx.createCanvasContext('sharePoster')
const { session, finalRanking, totalRounds } = this.data
// 背景
ctx.setFillStyle('#fff')
ctx.fillRect(0, 0, 375, 600)
// 标题
ctx.setFillStyle('#333')
ctx.setFontSize(24)
ctx.setTextAlign('center')
ctx.fillText(session.session_name, 187, 50)
// 副标题
ctx.setFontSize(14)
ctx.setFillStyle('#999')
ctx.fillText(`${this.data.gameTypeText[session.game_type]} · 共${totalRounds}`, 187, 80)
// 排名
ctx.setTextAlign('left')
finalRanking.slice(0, 3).forEach((player, index) => {
const y = 150 + index * 80
// 排名标记
const medals = ['🥇', '🥈', '🥉']
ctx.setFontSize(30)
ctx.fillText(medals[index], 30, y)
// 玩家名称
ctx.setFontSize(18)
ctx.setFillStyle('#333')
ctx.fillText(player.nickname, 80, y)
// 积分
ctx.setTextAlign('right')
ctx.setFillStyle(player.final_chips > 0 ? '#4CAF50' : '#F44336')
ctx.fillText(`${player.final_chips > 0 ? '+' : ''}${player.final_chips}`, 330, y)
ctx.setTextAlign('left')
})
// 底部信息
ctx.setFillStyle('#999')
ctx.setFontSize(12)
ctx.setTextAlign('center')
ctx.fillText('打牌记账小程序', 187, 550)
ctx.draw()
},
// 保存海报
savePoster() {
wx.canvasToTempFilePath({
canvasId: 'sharePoster',
success: (res) => {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
wx.showToast({
title: '已保存到相册',
icon: 'success'
})
},
fail: () => {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
}
})
},
// 关闭分享海报
closeSharePoster() {
this.setData({ showSharePoster: false })
},
// 分享给朋友
onShareAppMessage() {
const { session, finalRanking } = this.data
const winner = finalRanking[0]
return {
title: `${winner.nickname}获得${session.session_name}冠军!`,
path: `/pages/stats/session/session?id=${this.data.sessionId}`,
imageUrl: '/images/share-bg.png'
}
}
})