yingsa/server/src/controllers/pointsController.js
Ethanfly 3923217e0e 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.
2026-02-10 10:25:19 +08:00

315 lines
9.3 KiB
JavaScript

const { User, PointRecord, PointProduct, PointOrder, Store } = require('../models');
const { ORDER_STATUS } = require('../config/constants');
const { generateOrderNo, generateExchangeCode, success, error, getPagination, pageResult, formatDateTime } = require('../utils/helper');
const { Op } = require('sequelize');
const sequelize = require('../config/database');
const QRCode = require('qrcode');
class PointsController {
// 获取积分余额
async getBalance(req, res) {
try {
const user = req.user;
res.json(success({
balance: user.total_points
}));
} catch (err) {
console.error('获取积分余额失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 获取积分记录
async getRecords(req, res) {
try {
const { page = 1, pageSize = 20 } = req.query;
const { limit, offset } = getPagination(page, pageSize);
const user = req.user;
const { rows, count } = await PointRecord.findAndCountAll({
where: { user_id: user.id },
include: [{ model: Store, as: 'store', attributes: ['id', 'name'] }],
order: [['created_at', 'DESC']],
limit,
offset
});
res.json(pageResult(rows.map(record => ({
id: record.id,
actionName: record.action_name,
points: record.points,
balance: record.balance,
consumeAmount: record.consume_amount,
storeName: record.store?.name,
remark: record.remark,
createdAt: formatDateTime(record.created_at)
})), count, page, pageSize));
} catch (err) {
console.error('获取积分记录失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 获取积分商城商品
async getProducts(req, res) {
try {
const { page = 1, pageSize = 20, store_id } = req.query;
const { limit, offset } = getPagination(page, pageSize);
const where = { status: 1, stock: { [Op.gt]: 0 } };
if (store_id) {
where.store_id = store_id;
}
const { rows, count } = await PointProduct.findAndCountAll({
where,
include: [{ model: Store, as: 'store', attributes: ['id', 'name'] }],
order: [['sort_order', 'ASC'], ['created_at', 'DESC']],
limit,
offset
});
res.json(pageResult(rows.map(product => ({
id: product.id,
name: product.name,
description: product.description,
image: product.image,
pointsRequired: product.points_required,
originalPrice: product.original_price,
stock: product.stock,
storeId: product.store_id,
storeName: product.store?.name
})), count, page, pageSize));
} catch (err) {
console.error('获取商品列表失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 获取商品详情
async getProductDetail(req, res) {
try {
const { id } = req.params;
const product = await PointProduct.findByPk(id, {
include: [{ model: Store, as: 'store' }]
});
if (!product || product.status !== 1) {
return res.status(404).json(error('商品不存在', 404));
}
res.json(success({
id: product.id,
name: product.name,
description: product.description,
image: product.image,
pointsRequired: product.points_required,
originalPrice: product.original_price,
stock: product.stock,
exchangeCount: product.exchange_count,
storeId: product.store_id,
storeName: product.store?.name,
storeAddress: product.store?.address
}));
} catch (err) {
console.error('获取商品详情失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 兑换商品
async exchangeProduct(req, res) {
const t = await sequelize.transaction();
try {
const { product_id } = req.body;
const user = req.user;
const product = await PointProduct.findByPk(product_id, {
include: [{ model: Store, as: 'store' }]
});
if (!product || product.status !== 1) {
await t.rollback();
return res.status(404).json(error('商品不存在', 404));
}
if (product.stock <= 0) {
await t.rollback();
return res.status(400).json(error('商品库存不足', 400));
}
if (user.total_points < product.points_required) {
await t.rollback();
return res.status(400).json(error('积分不足', 400));
}
// 扣减积分
const newBalance = user.total_points - product.points_required;
await user.update({ total_points: newBalance }, { transaction: t });
// 扣减库存
await product.update({
stock: product.stock - 1,
exchange_count: product.exchange_count + 1
}, { transaction: t });
// 创建订单
const order = await PointOrder.create({
order_no: generateOrderNo(),
user_id: user.id,
product_id: product.id,
store_id: product.store_id,
product_name: product.name,
points_used: product.points_required,
exchange_code: generateExchangeCode(),
status: ORDER_STATUS.PENDING
}, { transaction: t });
// 记录积分变动
await PointRecord.create({
user_id: user.id,
store_id: product.store_id,
action_name: '积分兑换',
points: -product.points_required,
balance: newBalance,
remark: `兑换商品: ${product.name}`
}, { transaction: t });
await t.commit();
res.json(success({
orderId: order.id,
orderNo: order.order_no,
exchangeCode: order.exchange_code,
productName: product.name,
storeName: product.store?.name,
storeAddress: product.store?.address
}, '兑换成功'));
} catch (err) {
await t.rollback();
console.error('兑换商品失败:', err);
res.status(500).json(error('兑换失败'));
}
}
// 获取我的兑换订单
async getOrders(req, res) {
try {
const { page = 1, pageSize = 20, status } = req.query;
const { limit, offset } = getPagination(page, pageSize);
const user = req.user;
const where = { user_id: user.id };
if (status !== undefined && status !== '') {
where.status = status;
}
const { rows, count } = await PointOrder.findAndCountAll({
where,
include: [
{ model: PointProduct, as: 'product', attributes: ['id', 'name', 'image'] },
{ model: Store, as: 'store', attributes: ['id', 'name', 'address'] }
],
order: [['created_at', 'DESC']],
limit,
offset
});
res.json(pageResult(rows.map(order => ({
id: order.id,
orderNo: order.order_no,
productName: order.product_name,
productImage: order.product?.image,
pointsUsed: order.points_used,
status: order.status,
storeName: order.store?.name,
storeAddress: order.store?.address,
createdAt: formatDateTime(order.created_at),
verifiedAt: formatDateTime(order.verified_at)
})), count, page, pageSize));
} catch (err) {
console.error('获取订单列表失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 获取订单详情
async getOrderDetail(req, res) {
try {
const { id } = req.params;
const user = req.user;
const order = await PointOrder.findOne({
where: { id, user_id: user.id },
include: [
{ model: PointProduct, as: 'product' },
{ model: Store, as: 'store' }
]
});
if (!order) {
return res.status(404).json(error('订单不存在', 404));
}
res.json(success({
id: order.id,
orderNo: order.order_no,
productName: order.product_name,
productImage: order.product?.image,
productDescription: order.product?.description,
pointsUsed: order.points_used,
exchangeCode: order.exchange_code,
status: order.status,
storeId: order.store_id,
storeName: order.store?.name,
storeAddress: order.store?.address,
storeContact: order.store?.contact,
storeLatitude: order.store?.latitude,
storeLongitude: order.store?.longitude,
createdAt: formatDateTime(order.created_at),
verifiedAt: formatDateTime(order.verified_at)
}));
} catch (err) {
console.error('获取订单详情失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 生成兑换码二维码
async getOrderQrcode(req, res) {
try {
const { code } = req.query;
if (!code) {
return res.status(400).json(error('缺少兑换码', 400));
}
// 生成二维码配置
const qrOptions = {
errorCorrectionLevel: 'M',
type: 'image/png',
margin: 2,
width: 280,
color: {
dark: '#1A1A1A',
light: '#FFFFFF'
}
};
// 生成二维码为 base64
const qrcodeDataUrl = await QRCode.toDataURL(code, qrOptions);
res.json(success({
code: code,
qrcode: qrcodeDataUrl
}));
} catch (err) {
console.error('生成二维码失败:', err);
res.status(500).json(error('生成二维码失败'));
}
}
}
module.exports = new PointsController();