feat: Implement date formatting across controllers for improved readability

- Added a new utility function `formatDateTime` to standardize date formatting across various controllers.
- Updated multiple controllers (admin, article, ladder, match, points, store, user) to utilize the new formatting function for timestamps, enhancing the clarity of date and time displays in API responses.
- Ensured consistent date representation in user and match details, improving overall user experience.
This commit is contained in:
Ethanfly 2026-02-10 10:25:19 +08:00
parent b2ed278505
commit 3923217e0e
11 changed files with 66 additions and 56 deletions

View File

@ -1,7 +1,7 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
const { User, Store, SystemUser, LadderUser, Match, PointOrder, StoreStaff } = require('../models'); const { User, Store, SystemUser, LadderUser, Match, PointOrder, StoreStaff } = require('../models');
const { success, error, getPagination, pageResult } = require('../utils/helper'); const { success, error, getPagination, pageResult, formatDateTime } = require('../utils/helper');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
class AdminController { class AdminController {
@ -145,7 +145,7 @@ class AdminController {
memberCode: user.member_code, memberCode: user.member_code,
totalPoints: user.total_points, totalPoints: user.total_points,
status: user.status, status: user.status,
createdAt: user.created_at, createdAt: formatDateTime(user.created_at),
// 前端根据该字段隐藏“添加为天梯用户”按钮 // 前端根据该字段隐藏“添加为天梯用户”按钮
hasLadderUser: ladderUserIdSet.has(user.id) hasLadderUser: ladderUserIdSet.has(user.id)
})), count, page, pageSize)); })), count, page, pageSize));
@ -232,7 +232,7 @@ class AdminController {
memberCode: user.member_code, memberCode: user.member_code,
totalPoints: user.total_points, totalPoints: user.total_points,
status: user.status, status: user.status,
createdAt: user.created_at, createdAt: formatDateTime(user.created_at),
ladderUsers: user.ladderUsers.map(lu => ({ ladderUsers: user.ladderUsers.map(lu => ({
id: lu.id, id: lu.id,
storeId: lu.store_id, storeId: lu.store_id,
@ -382,8 +382,8 @@ class AdminController {
storeId: user.store_id, storeId: user.store_id,
storeName: user.store?.name, storeName: user.store?.name,
status: user.status, status: user.status,
lastLoginAt: user.last_login_at, lastLoginAt: formatDateTime(user.last_login_at),
createdAt: user.created_at createdAt: formatDateTime(user.created_at)
})), count, page, pageSize)); })), count, page, pageSize));
} catch (err) { } catch (err) {
console.error('获取系统用户列表失败:', err); console.error('获取系统用户列表失败:', err);

View File

@ -1,5 +1,5 @@
const { Article } = require('../models'); const { Article } = require('../models');
const { success, error } = require('../utils/helper'); const { success, error, formatDateTime } = require('../utils/helper');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
class ArticleController { class ArticleController {
@ -31,7 +31,7 @@ class ArticleController {
category: a.category, category: a.category,
coverImage: a.cover_image, coverImage: a.cover_image,
summary: a.summary, summary: a.summary,
createdAt: a.created_at, createdAt: formatDateTime(a.created_at),
isTop: a.is_top === 1, isTop: a.is_top === 1,
})); }));
@ -66,7 +66,7 @@ class ArticleController {
coverImage: article.cover_image, coverImage: article.cover_image,
summary: article.summary, summary: article.summary,
contentHtml: article.content_html, contentHtml: article.content_html,
createdAt: article.created_at, createdAt: formatDateTime(article.created_at),
}), }),
); );
} catch (err) { } catch (err) {

View File

@ -1,6 +1,6 @@
const { User, LadderUser, Store } = require('../models'); const { User, LadderUser, Store } = require('../models');
const { LADDER_LEVEL_NAMES } = require('../config/constants'); const { LADDER_LEVEL_NAMES } = require('../config/constants');
const { success, error, getPagination, pageResult, generateMemberCode } = require('../utils/helper'); const { success, error, getPagination, pageResult, formatDateTime, generateMemberCode } = require('../utils/helper');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
class LadderAdminController { class LadderAdminController {
@ -63,8 +63,8 @@ class LadderAdminController {
matchCount: lu.match_count, matchCount: lu.match_count,
winCount: lu.win_count, winCount: lu.win_count,
monthlyMatchCount: lu.monthly_match_count, monthlyMatchCount: lu.monthly_match_count,
lastMatchTime: lu.last_match_time, lastMatchTime: formatDateTime(lu.last_match_time),
createdAt: lu.created_at createdAt: formatDateTime(lu.created_at)
})), count, page, pageSize)); })), count, page, pageSize));
} catch (err) { } catch (err) {
console.error('获取天梯用户列表失败:', err); console.error('获取天梯用户列表失败:', err);

View File

@ -9,6 +9,7 @@ const {
error, error,
getPagination, getPagination,
pageResult, pageResult,
formatDateTime,
} = require("../utils/helper"); } = require("../utils/helper");
const { Op } = require("sequelize"); const { Op } = require("sequelize");
const sequelize = require("../config/database"); const sequelize = require("../config/database");
@ -247,7 +248,7 @@ class LadderController {
rank: higherCount + 1, rank: higherCount + 1,
storeId: ladderUser.store_id, storeId: ladderUser.store_id,
storeName: ladderUser.store?.name, storeName: ladderUser.store?.name,
lastMatchTime: ladderUser.last_match_time, lastMatchTime: formatDateTime(ladderUser.last_match_time),
}), }),
); );
} catch (err) { } catch (err) {
@ -319,7 +320,7 @@ class LadderController {
rank: higherCount + 1, rank: higherCount + 1,
storeId: ladderUser.store_id, storeId: ladderUser.store_id,
storeName: ladderUser.store && ladderUser.store.name, storeName: ladderUser.store && ladderUser.store.name,
lastMatchTime: ladderUser.last_match_time, lastMatchTime: formatDateTime(ladderUser.last_match_time),
}), }),
); );
} catch (err) { } catch (err) {

View File

@ -1,6 +1,6 @@
const { Match, MatchGame, MatchPlayer, MatchRound, LadderUser, User, Store, sequelize } = require('../models'); const { Match, MatchGame, MatchPlayer, MatchRound, LadderUser, User, Store, sequelize } = require('../models');
const { MATCH_TYPES, MATCH_STATUS, RANKING_STAGE, CONFIRM_STATUS } = require('../config/constants'); const { MATCH_TYPES, MATCH_STATUS, RANKING_STAGE, CONFIRM_STATUS } = require('../config/constants');
const { generateMatchCode, success, error, getPagination, pageResult } = require('../utils/helper'); const { generateMatchCode, success, error, getPagination, pageResult, formatDateTime } = require('../utils/helper');
const PowerCalculator = require('../services/powerCalculator'); const PowerCalculator = require('../services/powerCalculator');
const { broadcastToUsers, sendMatchNotification } = require('../websocket'); const { broadcastToUsers, sendMatchNotification } = require('../websocket');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
@ -53,9 +53,9 @@ class MatchAdminController {
refereeName: match.referee?.nickname, refereeName: match.referee?.nickname,
status: match.status, status: match.status,
stage: match.stage, stage: match.stage,
startTime: match.start_time, startTime: formatDateTime(match.start_time),
endTime: match.end_time, endTime: formatDateTime(match.end_time),
createdAt: match.created_at createdAt: formatDateTime(match.created_at)
})), count, page, pageSize)); })), count, page, pageSize));
} catch (err) { } catch (err) {
console.error('获取比赛列表失败:', err); console.error('获取比赛列表失败:', err);
@ -244,8 +244,8 @@ class MatchAdminController {
storeName: match.store?.name, storeName: match.store?.name,
refereeId: match.referee_id, refereeId: match.referee_id,
refereeName: match.referee?.nickname, refereeName: match.referee?.nickname,
startTime: match.start_time, startTime: formatDateTime(match.start_time),
endTime: match.end_time, endTime: formatDateTime(match.end_time),
players: sortedPlayers.map(p => ({ players: sortedPlayers.map(p => ({
id: p.id, id: p.id,
ladderUserId: p.ladder_user_id, ladderUserId: p.ladder_user_id,

View File

@ -1,6 +1,6 @@
const { Match, MatchGame, MatchPlayer, MatchRound, LadderUser, User, Store, sequelize } = require('../models'); 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 { MATCH_TYPES, MATCH_STATUS, CONFIRM_STATUS, RANKING_STAGE, MATCH_WEIGHTS, POWER_CALC } = require('../config/constants');
const { generateMatchCode, success, error, getPagination, pageResult, normalizeAvatarForClient } = require('../utils/helper'); const { generateMatchCode, success, error, getPagination, pageResult, formatDateTime, normalizeAvatarForClient } = require('../utils/helper');
const PowerCalculator = require('../services/powerCalculator'); const PowerCalculator = require('../services/powerCalculator');
const { sendChallengeNotification, sendScoreConfirmNotification, sendMatchNotification, broadcastToUsers, sendToUser } = require('../websocket'); const { sendChallengeNotification, sendScoreConfirmNotification, sendMatchNotification, broadcastToUsers, sendToUser } = require('../websocket');
const matchAdminController = require('./matchAdminController'); const matchAdminController = require('./matchAdminController');
@ -1036,7 +1036,7 @@ class MatchController {
stageName: stageNames[match.stage] || '未知', stageName: stageNames[match.stage] || '未知',
weight: match.weight, weight: match.weight,
storeName: match.store?.name, storeName: match.store?.name,
startTime: match.start_time, startTime: formatDateTime(match.start_time),
refereeId: match.referee_id, refereeId: match.referee_id,
isReferee: isReferee, isReferee: isReferee,
players: (match.players || []).map(p => ({ players: (match.players || []).map(p => ({
@ -1392,7 +1392,7 @@ class MatchController {
powerChange: game.winner_id === myLadderId powerChange: game.winner_id === myLadderId
? game.winner_power_change ? game.winner_power_change
: game.loser_power_change, : game.loser_power_change,
confirmedAt: game.confirmed_at confirmedAt: formatDateTime(game.confirmed_at)
}; };
})); }));
@ -1446,17 +1446,6 @@ class MatchController {
}); });
const opponentMap = new Map(opponents.map((o) => [String(o.id), o])); const opponentMap = new Map(opponents.map((o) => [String(o.id), o]));
const formatMatchTime = (date) => {
if (!date) return '';
const d = new Date(date);
const Y = d.getFullYear();
const M = String(d.getMonth() + 1).padStart(2, '0');
const D = String(d.getDate()).padStart(2, '0');
const h = String(d.getHours()).padStart(2, '0');
const m = String(d.getMinutes()).padStart(2, '0');
return `${Y}-${M}-${D} ${h}:${m}`;
};
const list = rows.map((game) => { const list = rows.map((game) => {
const isPlayer1 = game.player1_id === player.id; const isPlayer1 = game.player1_id === player.id;
const opponentId = isPlayer1 ? game.player2_id : game.player1_id; const opponentId = isPlayer1 ? game.player2_id : game.player1_id;
@ -1466,7 +1455,7 @@ class MatchController {
const opponentScore = isPlayer1 ? game.player2_score : game.player1_score; const opponentScore = isPlayer1 ? game.player2_score : game.player1_score;
const isWin = game.winner_id === player.id; const isWin = game.winner_id === player.id;
const typeName = game.match && game.match.type === MATCH_TYPES.CHALLENGE ? '挑战赛' : '排位赛'; const typeName = game.match && game.match.type === MATCH_TYPES.CHALLENGE ? '挑战赛' : '排位赛';
const timeText = formatMatchTime(game.confirmed_at); const timeText = formatDateTime(game.confirmed_at);
return { return {
id: game.id, id: game.id,
@ -1682,8 +1671,8 @@ class MatchController {
opponent, opponent,
currentGame, currentGame,
playerCount, playerCount,
startTime: match.start_time, startTime: formatDateTime(match.start_time),
createdAt: match.created_at, createdAt: formatDateTime(match.created_at),
isReferee: match.referee_id === user.id isReferee: match.referee_id === user.id
}; };
})); }));
@ -1745,9 +1734,9 @@ class MatchController {
match.type === MATCH_TYPES.RANKING match.type === MATCH_TYPES.RANKING
? ["报名中", "循环赛", "淘汰赛", "已结束"][match.stage] ? ["报名中", "循环赛", "淘汰赛", "已结束"][match.stage]
: null, : null,
startTime: match.start_time, startTime: formatDateTime(match.start_time),
endTime: match.end_time, endTime: formatDateTime(match.end_time),
createdAt: match.created_at, createdAt: formatDateTime(match.created_at),
storeId: match.store?.id, storeId: match.store?.id,
storeName: match.store?.name, storeName: match.store?.name,
})); }));
@ -1801,7 +1790,7 @@ class MatchController {
opponentName: opponent?.real_name, opponentName: opponent?.real_name,
myScore: game.player1_id === ladderUser.id ? game.player1_score : game.player2_score, myScore: game.player1_id === ladderUser.id ? game.player1_score : game.player2_score,
opponentScore: game.player1_id === ladderUser.id ? game.player2_score : game.player1_score, opponentScore: game.player1_id === ladderUser.id ? game.player2_score : game.player1_score,
createdAt: game.created_at createdAt: formatDateTime(game.created_at)
}; };
})); }));
@ -1983,8 +1972,8 @@ class MatchController {
status: match.status, status: match.status,
stage: match.stage, stage: match.stage,
storeName: match.store?.name, storeName: match.store?.name,
startTime: match.start_time, startTime: formatDateTime(match.start_time),
endTime: match.end_time, endTime: formatDateTime(match.end_time),
challenger: challengerInfo, challenger: challengerInfo,
defender: defenderInfo, defender: defenderInfo,
myRole: myRole || null, myRole: myRole || null,

View File

@ -12,6 +12,7 @@ const {
error, error,
getPagination, getPagination,
pageResult, pageResult,
formatDateTime,
} = require("../utils/helper"); } = require("../utils/helper");
const PowerCalculator = require("../services/powerCalculator"); const PowerCalculator = require("../services/powerCalculator");
const { Op } = require("sequelize"); const { Op } = require("sequelize");
@ -311,7 +312,7 @@ class PointsAdminController {
status: product.status, status: product.status,
storeId: product.store_id, storeId: product.store_id,
storeName: product.store?.name, storeName: product.store?.name,
createdAt: product.created_at, createdAt: formatDateTime(product.created_at),
})), })),
count, count,
page, page,
@ -458,8 +459,8 @@ class PointsAdminController {
status: order.status, status: order.status,
storeId: order.store_id, storeId: order.store_id,
storeName: order.store?.name, storeName: order.store?.name,
createdAt: order.created_at, createdAt: formatDateTime(order.created_at),
verifiedAt: order.verified_at, verifiedAt: formatDateTime(order.verified_at),
})), })),
count, count,
page, page,
@ -501,7 +502,7 @@ class PointsAdminController {
orderId: order.id, orderId: order.id,
orderNo: order.order_no, orderNo: order.order_no,
status: order.status, status: order.status,
verifiedAt: order.verified_at, verifiedAt: formatDateTime(order.verified_at),
}, },
}); });
} }
@ -556,7 +557,7 @@ class PointsAdminController {
orderId: order.id, orderId: order.id,
orderNo: order.order_no, orderNo: order.order_no,
status: order.status, status: order.status,
verifiedAt: order.verified_at, verifiedAt: formatDateTime(order.verified_at),
}, },
}); });
} }

View File

@ -1,6 +1,6 @@
const { User, PointRecord, PointProduct, PointOrder, Store } = require('../models'); const { User, PointRecord, PointProduct, PointOrder, Store } = require('../models');
const { ORDER_STATUS } = require('../config/constants'); const { ORDER_STATUS } = require('../config/constants');
const { generateOrderNo, generateExchangeCode, success, error, getPagination, pageResult } = require('../utils/helper'); const { generateOrderNo, generateExchangeCode, success, error, getPagination, pageResult, formatDateTime } = require('../utils/helper');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
const sequelize = require('../config/database'); const sequelize = require('../config/database');
const QRCode = require('qrcode'); const QRCode = require('qrcode');
@ -42,7 +42,7 @@ class PointsController {
consumeAmount: record.consume_amount, consumeAmount: record.consume_amount,
storeName: record.store?.name, storeName: record.store?.name,
remark: record.remark, remark: record.remark,
createdAt: record.created_at createdAt: formatDateTime(record.created_at)
})), count, page, pageSize)); })), count, page, pageSize));
} catch (err) { } catch (err) {
console.error('获取积分记录失败:', err); console.error('获取积分记录失败:', err);
@ -225,8 +225,8 @@ class PointsController {
status: order.status, status: order.status,
storeName: order.store?.name, storeName: order.store?.name,
storeAddress: order.store?.address, storeAddress: order.store?.address,
createdAt: order.created_at, createdAt: formatDateTime(order.created_at),
verifiedAt: order.verified_at verifiedAt: formatDateTime(order.verified_at)
})), count, page, pageSize)); })), count, page, pageSize));
} catch (err) { } catch (err) {
console.error('获取订单列表失败:', err); console.error('获取订单列表失败:', err);
@ -267,8 +267,8 @@ class PointsController {
storeContact: order.store?.contact, storeContact: order.store?.contact,
storeLatitude: order.store?.latitude, storeLatitude: order.store?.latitude,
storeLongitude: order.store?.longitude, storeLongitude: order.store?.longitude,
createdAt: order.created_at, createdAt: formatDateTime(order.created_at),
verifiedAt: order.verified_at verifiedAt: formatDateTime(order.verified_at)
})); }));
} catch (err) { } catch (err) {
console.error('获取订单详情失败:', err); console.error('获取订单详情失败:', err);

View File

@ -1,5 +1,5 @@
const { Store, LadderUser } = require('../models'); const { Store, LadderUser } = require('../models');
const { success, error, getPagination, pageResult, calculateDistance } = require('../utils/helper'); const { success, error, getPagination, pageResult, formatDateTime, calculateDistance } = require('../utils/helper');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
class StoreController { class StoreController {
@ -194,7 +194,7 @@ class StoreController {
realName: lu.real_name, realName: lu.real_name,
level: lu.level, level: lu.level,
powerScore: lu.power_score, powerScore: lu.power_score,
lastMatchTime: lu.last_match_time lastMatchTime: formatDateTime(lu.last_match_time)
})))); }))));
} catch (err) { } catch (err) {
console.error('获取天梯门店失败:', err); console.error('获取天梯门店失败:', err);

View File

@ -8,6 +8,7 @@ const {
success, success,
error, error,
calculateDistance, calculateDistance,
formatDateTime,
getFullUrl, getFullUrl,
normalizeAvatarUrl, normalizeAvatarUrl,
normalizeAvatarForClient, normalizeAvatarForClient,
@ -548,7 +549,7 @@ class UserController {
matchCount: lu.match_count, matchCount: lu.match_count,
winCount: lu.win_count, winCount: lu.win_count,
monthlyMatchCount: lu.monthly_match_count, monthlyMatchCount: lu.monthly_match_count,
lastMatchTime: lu.last_match_time, lastMatchTime: formatDateTime(lu.last_match_time),
})), })),
), ),
); );

View File

@ -204,6 +204,23 @@ function getFullUrl(path, req) {
return `${protocol}://${host}${path}`; return `${protocol}://${host}${path}`;
} }
/**
* 统一接口返回的时间格式YYYY-MM-DD HH:mm服务器本地时区
* @param {Date|string|null|undefined} date - 日期对象或 ISO 字符串
* @returns {string} 格式化后的时间字符串无效时返回空字符串
*/
function formatDateTime(date) {
if (date == null) return '';
const d = new Date(date);
if (Number.isNaN(d.getTime())) return '';
const Y = d.getFullYear();
const M = String(d.getMonth() + 1).padStart(2, '0');
const D = String(d.getDate()).padStart(2, '0');
const h = String(d.getHours()).padStart(2, '0');
const m = String(d.getMinutes()).padStart(2, '0');
return `${Y}-${M}-${D} ${h}:${m}`;
}
/** /**
* 规范化返回给客户端的头像URL * 规范化返回给客户端的头像URL
* 如果头像是服务端的默认头像URL返回null让前端使用本地默认头像 * 如果头像是服务端的默认头像URL返回null让前端使用本地默认头像
@ -233,6 +250,7 @@ module.exports = {
success, success,
error, error,
pageResult, pageResult,
formatDateTime,
getFullUrl, getFullUrl,
normalizeAvatarUrl, normalizeAvatarUrl,
normalizeAvatarForClient normalizeAvatarForClient