- 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.
315 lines
9.3 KiB
JavaScript
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();
|