refactor: Improve data validation and avatar normalization

- Simplified data validation logic in the index.js file to handle cases where data is missing or incomplete.
- Updated the index.wxml file to provide a clearer message when no ranking data is available.
- Added a new method to normalize avatar URLs in the helper.js file, ensuring that default avatars are handled correctly across user and match controllers.
- Refactored user and match controllers to utilize the new avatar normalization method, enhancing consistency in avatar display.
This commit is contained in:
ethanfly 2026-02-10 01:27:52 +08:00
parent 21c5bb9b57
commit e871eed0e8
7 changed files with 89 additions and 75 deletions

View File

@ -156,17 +156,7 @@ Page({
});
const data = res && res.data ? res.data : null;
if (!data) return;
if (data.qualified === false) {
wx.showToast({
title: `本月场次不足(${data.monthlyMatchCount}/${data.minMonthlyMatches}`,
icon: "none",
});
return;
}
if (!data.ladderUserId || !data.page) {
if (!data || !data.ladderUserId || !data.page) {
return;
}

View File

@ -71,7 +71,7 @@
<view wx:elif="{{!loading}}" class="empty-state">
<image class="empty-icon" src="/images/empty-ranking.svg" mode="aspectFit"></image>
<text class="empty-title">暂无排名数据</text>
<text class="empty-desc">每月完成3场比赛即可上榜</text>
<text class="empty-desc">暂无排名数据</text>
</view>
<!-- 加载更多 -->
<view wx:if="{{loading}}" class="loading-state">

View File

@ -31,6 +31,19 @@ Page({
}
},
onPullDownRefresh() {
// 下拉刷新时重新加载比赛详情
if (this.data.matchId) {
this.loadMatchDetail().then(() => {
wx.stopPullDownRefresh()
}).catch(() => {
wx.stopPullDownRefresh()
})
} else {
wx.stopPullDownRefresh()
}
},
// 加载比赛详情
async loadMatchDetail() {
this.setData({ loading: true })

View File

@ -37,13 +37,6 @@ class LadderController {
status: 1,
};
// 如果不是大屏显示,则需要满足每月最低参赛场次限制
if (!is_display) {
where.monthly_match_count = {
[Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES,
};
}
if (gender) {
where.gender = gender;
}
@ -87,24 +80,31 @@ class LadderController {
// 添加排名
const startRank = offset + 1;
const list = rows.map((lu, index) => ({
rank: startRank + index,
id: lu.id,
userId: lu.user_id,
realName: lu.real_name,
nickname: lu.user?.nickname,
avatar: lu.user?.avatar,
gender: lu.gender,
level: lu.level,
levelName: LADDER_LEVEL_NAMES[lu.level],
powerScore: lu.power_score,
matchCount: lu.match_count,
winCount: lu.win_count,
winRate:
lu.match_count > 0
? Math.round((lu.win_count / lu.match_count) * 100)
: 0,
}));
const list = rows.map((lu, index) => {
// 如果头像是服务端的默认头像URL返回null让前端使用本地默认头像
let avatar = lu.user?.avatar;
if (avatar && (avatar.includes('/images/avatar-default.svg') || avatar.includes('avatar-default'))) {
avatar = null;
}
return {
rank: startRank + index,
id: lu.id,
userId: lu.user_id,
realName: lu.real_name,
nickname: lu.user?.nickname,
avatar: avatar || null,
gender: lu.gender,
level: lu.level,
levelName: LADDER_LEVEL_NAMES[lu.level],
powerScore: lu.power_score,
matchCount: lu.match_count,
winCount: lu.win_count,
winRate:
lu.match_count > 0
? Math.round((lu.win_count / lu.match_count) * 100)
: 0,
};
});
res.json(pageResult(list, count, page, pageSize));
} catch (err) {
@ -149,31 +149,11 @@ class LadderController {
return res.status(404).json(error("未加入该门店天梯", 404));
}
if (
!is_display &&
(ladderUser.monthly_match_count || 0) < POWER_CALC.MIN_MONTHLY_MATCHES
) {
return res.json(
success({
qualified: false,
ladderUserId: ladderUser.id,
monthlyMatchCount: ladderUser.monthly_match_count || 0,
minMonthlyMatches: POWER_CALC.MIN_MONTHLY_MATCHES,
}),
);
}
const where = {
store_id,
status: 1,
};
if (!is_display) {
where.monthly_match_count = {
[Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES,
};
}
if (normalizedGender) {
where.gender = normalizedGender;
}
@ -233,17 +213,22 @@ class LadderController {
gender: ladderUser.gender,
status: 1,
power_score: { [Op.gt]: ladderUser.power_score },
monthly_match_count: { [Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES },
},
});
// 如果头像是服务端的默认头像URL返回null让前端使用本地默认头像
let avatar = ladderUser.user?.avatar;
if (avatar && (avatar.includes('/images/avatar-default.svg') || avatar.includes('avatar-default'))) {
avatar = null;
}
res.json(
success({
id: ladderUser.id,
userId: ladderUser.user_id,
realName: ladderUser.real_name,
nickname: ladderUser.user?.nickname,
avatar: ladderUser.user?.avatar,
avatar: avatar || null,
memberCode: ladderUser.user?.member_code,
gender: ladderUser.gender,
level: ladderUser.level,
@ -300,20 +285,25 @@ class LadderController {
gender: ladderUser.gender,
status: 1,
power_score: { [Op.gt]: ladderUser.power_score },
monthly_match_count: { [Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES },
},
});
const matchCount = ladderUser.match_count || 0;
const winCount = ladderUser.win_count || 0;
// 如果头像是服务端的默认头像URL返回null让前端使用本地默认头像
let avatar = ladderUser.user && ladderUser.user.avatar;
if (avatar && (avatar.includes('/images/avatar-default.svg') || avatar.includes('avatar-default'))) {
avatar = null;
}
res.json(
success({
id: ladderUser.id,
userId: ladderUser.user_id,
realName: ladderUser.real_name,
nickname: ladderUser.user && ladderUser.user.nickname,
avatar: ladderUser.user && ladderUser.user.avatar,
avatar: avatar || null,
memberCode: ladderUser.user && ladderUser.user.member_code,
gender: ladderUser.gender,
level: ladderUser.level,

View File

@ -1,6 +1,6 @@
const { Match, MatchGame, MatchPlayer, MatchRound, LadderUser, User, Store, sequelize } = require('../models');
const { MATCH_TYPES, MATCH_STATUS, CONFIRM_STATUS, RANKING_STAGE, MATCH_WEIGHTS, POWER_CALC } = require('../config/constants');
const { generateMatchCode, success, error, getPagination, pageResult } = require('../utils/helper');
const { generateMatchCode, success, error, getPagination, pageResult, normalizeAvatarForClient } = require('../utils/helper');
const PowerCalculator = require('../services/powerCalculator');
const { sendChallengeNotification, sendScoreConfirmNotification, sendMatchNotification, broadcastToUsers } = require('../websocket');
const matchAdminController = require('./matchAdminController');
@ -949,7 +949,7 @@ class MatchController {
ladderUserId: p.ladder_user_id,
realName: p.ladderUser?.real_name || `选手${p.ladder_user_id}`,
nickname: p.ladderUser?.user?.nickname,
avatar: p.ladderUser?.user?.avatar,
avatar: normalizeAvatarForClient(p.ladderUser?.user?.avatar, req),
level: p.ladderUser?.level
});
}
@ -1005,7 +1005,7 @@ class MatchController {
ladderUserId: p.ladder_user_id,
realName: p.ladderUser?.real_name,
nickname: p.ladderUser?.user?.nickname,
avatar: p.ladderUser?.user?.avatar,
avatar: normalizeAvatarForClient(p.ladderUser?.user?.avatar, req),
level: p.ladderUser?.level,
initialPower: p.initial_power,
winCount: p.win_count,
@ -1140,7 +1140,7 @@ class MatchController {
id: opponent.id,
realName: opponent.real_name,
nickname: opponent.user?.nickname,
avatar: opponent.user?.avatar,
avatar: normalizeAvatarForClient(opponent.user?.avatar, req),
level: opponent.level,
powerScore: opponent.power_score
},
@ -1433,7 +1433,7 @@ class MatchController {
id: opponent.id,
realName: opponent.real_name,
nickname: opponent.user && opponent.user.nickname,
avatar: opponent.user && opponent.user.avatar,
avatar: normalizeAvatarForClient(opponent.user && opponent.user.avatar, req),
level: opponent.level,
powerScore: opponent.power_score
}
@ -1544,7 +1544,7 @@ class MatchController {
id: opponentLadder.id,
realName: opponentLadder.real_name,
nickname: opponentLadder.user?.nickname,
avatar: opponentLadder.user?.avatar,
avatar: normalizeAvatarForClient(opponentLadder.user?.avatar, req),
level: opponentLadder.level,
powerScore: opponentLadder.power_score
};
@ -1823,7 +1823,7 @@ class MatchController {
id: challengerLadder.id,
realName: challengerLadder.real_name,
nickname: challengerLadder.user?.nickname,
avatar: challengerLadder.user?.avatar,
avatar: normalizeAvatarForClient(challengerLadder.user?.avatar, req),
level: challengerLadder.level,
powerScore: challengerLadder.power_score,
userId: challengerLadder.user_id, // 添加 user_id用于前端判断
@ -1836,7 +1836,7 @@ class MatchController {
id: defenderLadder.id,
realName: defenderLadder.real_name,
nickname: defenderLadder.user?.nickname,
avatar: defenderLadder.user?.avatar,
avatar: normalizeAvatarForClient(defenderLadder.user?.avatar, req),
level: defenderLadder.level,
powerScore: defenderLadder.power_score,
userId: defenderLadder.user_id, // 添加 user_id用于前端判断

View File

@ -10,6 +10,7 @@ const {
calculateDistance,
getFullUrl,
normalizeAvatarUrl,
normalizeAvatarForClient,
} = require("../utils/helper");
const { LADDER_LEVEL_NAMES } = require("../config/constants");
const { Op } = require("sequelize");
@ -76,7 +77,7 @@ class UserController {
responseData.userInfo = {
id: user.id,
nickname: user.nickname,
avatar: getFullUrl(user.avatar, req),
avatar: normalizeAvatarForClient(user.avatar, req),
phone: user.phone,
gender: user.gender,
memberCode: user.member_code,
@ -96,7 +97,7 @@ class UserController {
? {
id: user.id,
nickname: user.nickname,
avatar: user.avatar,
avatar: normalizeAvatarForClient(user.avatar, req),
phone: user.phone,
gender: user.gender,
memberCode: user.member_code,
@ -281,7 +282,7 @@ class UserController {
userInfo: {
id: user.id,
nickname: user.nickname,
avatar: getFullUrl(user.avatar, req),
avatar: normalizeAvatarForClient(user.avatar, req),
phone: user.phone,
gender: user.gender,
memberCode: user.member_code,
@ -373,7 +374,7 @@ class UserController {
success(
{
nickname: user.nickname,
avatar: getFullUrl(user.avatar, req),
avatar: normalizeAvatarForClient(user.avatar, req),
gender: user.gender,
},
"更新成功",
@ -412,7 +413,7 @@ class UserController {
const result = {
id: user.id,
nickname: user.nickname,
avatar: getFullUrl(user.avatar, req),
avatar: normalizeAvatarForClient(user.avatar, req),
phone: user.phone,
gender: user.gender,
memberCode: user.member_code,

View File

@ -204,6 +204,25 @@ function getFullUrl(path, req) {
return `${protocol}://${host}${path}`;
}
/**
* 规范化返回给客户端的头像URL
* 如果头像是服务端的默认头像URL返回null让前端使用本地默认头像
* @param {string} avatar - 头像URL可能是完整URL或相对路径
* @param {object} req - Express request对象用于获取host
* @returns {string|null} 规范化后的头像URL如果是默认头像则返回null
*/
function normalizeAvatarForClient(avatar, req) {
if (!avatar) return null;
// 检查是否是默认头像相对路径或完整URL
if (avatar.includes('/images/avatar-default.svg') || avatar.includes('avatar-default')) {
return null;
}
// 使用 getFullUrl 处理其他头像URL
return getFullUrl(avatar, req) || null;
}
module.exports = {
generateMemberCode,
generateMatchCode,
@ -215,5 +234,6 @@ module.exports = {
error,
pageResult,
getFullUrl,
normalizeAvatarUrl
normalizeAvatarUrl,
normalizeAvatarForClient
};