0607.2
Some checks failed
Next.js CI/CD 流水线 / deploy (push) Failing after 20s

This commit is contained in:
2025-06-07 00:40:53 +08:00
parent 6362673ccb
commit 9b9adb5143
27 changed files with 1108 additions and 530 deletions

View File

@@ -14,10 +14,10 @@ interface ProductImageProps {
}
// 添加图片缓存机制
const imageCache: Record<string, string> = {};
const imageCache: Record<string, string | null> = {};
// 批量获取产品图片的函数
export const batchFetchProductImages = async (productIds: string[]): Promise<Record<string, string>> => {
export const batchFetchProductImages = async (productIds: string[]): Promise<Record<string, string | null>> => {
try {
const uniqueIds = [...new Set(productIds)]; // 确保ID不重复
const cachedIds = uniqueIds.filter(id => !imageCache[id]); // 只获取未缓存的ID
@@ -36,7 +36,8 @@ export const batchFetchProductImages = async (productIds: string[]): Promise<Rec
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
console.error(`批量获取产品图片失败: ${response.status}`);
return imageCache; // 返回现有缓存,不抛出错误
}
const data = await response.json();
@@ -56,7 +57,7 @@ export const batchFetchProductImages = async (productIds: string[]): Promise<Rec
}
};
const fetchProductImage = async (productId: string): Promise<string> => {
const fetchProductImage = async (productId: string): Promise<string | null> => {
// 检查缓存
if (imageCache[productId]) {
return imageCache[productId];
@@ -66,21 +67,20 @@ const fetchProductImage = async (productId: string): Promise<string> => {
const response = await fetch(`/api/products/images/${productId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
console.error(`获取产品图片失败 ID:${productId}, status: ${response.status}`);
return null; // 返回null而不是抛出错误
}
const data = await response.json();
const image = data.image;
// 更新缓存
if (image) {
imageCache[productId] = image;
}
// 更新缓存即使图片为null也缓存结果
imageCache[productId] = image;
return image;
} catch (error: unknown) {
console.error(`获取产品图片失败 ID:${productId}`, error);
throw error;
console.error(`网络错误,获取产品图片失败 ID:${productId}`, error);
return null; // 网络错误时也返回null
}
};
@@ -122,8 +122,8 @@ const ProductImage: React.FC<ProductImageProps> = React.memo(({
useEffect(() => {
if (!productId) return;
// 如果已缓存,直接使用缓存
if (imageCache[productId]) {
// 如果已缓存,直接使用缓存包括null值
if (productId in imageCache) {
setImageSrc(imageCache[productId]);
return;
}
@@ -134,7 +134,10 @@ const ProductImage: React.FC<ProductImageProps> = React.memo(({
try {
const image = await fetchProductImage(productId);
setImageSrc(image);
// fetchProductImage现在不会抛出错误所以不需要catch
} catch (error) {
// 这个catch现在主要处理意外错误
console.error('意外错误:', error);
setIsError(true);
} finally {
setIsLoading(false);

View File

@@ -0,0 +1,234 @@
// src/hooks/useCustomerTotalIncome.ts
import { useState, useEffect } from 'react';
const useCustomerTotalIncome = (phone: string | null) => {
const [totalIncome, setTotalIncome] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(false);
const [storeAccounts, setStoreAccounts] = useState<string[]>([]); // 用于存储店铺的账号编号
useEffect(() => {
if (!phone) return; // 如果没有提供手机号,直接返回
const fetchCustomerIncomeData = async () => {
setLoading(true);
try {
const response = await fetch(`/api/backstage/customers/sales/${phone}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
/*
API返回数据结构如下
{
"message": "查询成功",
"salesRecords": [
{
"_id": "670f76fe3da0e5c5809b46c7",
"团队": "670f75093da0e5c5809b4584",
"客户": "670f767a3da0e5c5809b46bc",
"产品": [
{
"成本": {
"成本价": 100,
"包装费": 20,
"运费": 30
},
"_id": "670f76e73da0e5c5809b46c4",
"团队": "670f75093da0e5c5809b4584",
"供应商": "670f75ff3da0e5c5809b45ab",
"品牌": "670f75d43da0e5c5809b459f",
"品类": "670f75e43da0e5c5809b45a4",
"名称": "测试产品1",
"描述": "测试产品1",
"编码": "测试产品1",
"货号": "测试产品1",
"别名": [
"测试产品1"
],
"售价": 500,
"createdAt": "2024-10-16T08:18:47.363Z",
"updatedAt": "2024-10-16T08:18:47.363Z",
"__v": 0
}
],
"订单来源": {
"_id": "670f763c3da0e5c5809b45ba",
"团队": "670f75093da0e5c5809b4584",
"账号负责人": "670f75043da0e5c5809b457d",
"前端引流人员": "670f75043da0e5c5809b457d",
"账号类型": [
"670f75e43da0e5c5809b45a4"
],
"账号编号": "1",
"微信号": "1",
"微信昵称": "1",
"手机编号": "1",
"账号状态": 1,
"备注": "1",
"日增长数据": [],
"createdAt": "2024-10-16T08:15:56.632Z",
"updatedAt": "2024-10-16T08:15:56.632Z",
"__v": 0
},
"导购": {
"_id": "670f75043da0e5c5809b457d",
"姓名": "999",
"电话": "999",
"邮箱": "999@qq.com",
"密码": "$2b$10$BucUsXpf4rVYgjU.IGVfju2d2OijFBA74EQCSenL5ftsERcSd7uyC",
"角色": "66b99afa3324caa6bd2b57ae",
"createdAt": "2024-10-16T08:10:44.743Z",
"updatedAt": "2024-10-16T08:10:49.908Z",
"__v": 0,
"团队": "670f75093da0e5c5809b4584"
},
"成交日期": "2024-10-16T08:18:53.700Z",
"应收金额": 500,
"收款金额": 500,
"待收款": 0,
"收款平台": "670f762d3da0e5c5809b45b2",
"订单状态": [
"正常"
],
"收款状态": "全款",
"货款状态": "待结算",
"余额抵用": null,
"备注": "备注",
"优惠券": [],
"售后记录": [],
"createdAt": "2024-10-16T08:19:10.775Z",
"updatedAt": "2024-10-16T08:19:10.775Z",
"__v": 0
},
{
"_id": "6711cb3694e24dea3c362daf",
"团队": "670f75093da0e5c5809b4584",
"客户": "670f767a3da0e5c5809b46bc",
"产品": [
{
"成本": {
"成本价": 100,
"包装费": 20,
"运费": 30
},
"_id": "670f76e73da0e5c5809b46c4",
"团队": "670f75093da0e5c5809b4584",
"供应商": "670f75ff3da0e5c5809b45ab",
"品牌": "670f75d43da0e5c5809b459f",
"品类": "670f75e43da0e5c5809b45a4",
"名称": "测试产品1",
"描述": "测试产品1",
"编码": "测试产品1",
"货号": "测试产品1",
"别名": [
"测试产品1"
],
"售价": 500,
"createdAt": "2024-10-16T08:18:47.363Z",
"updatedAt": "2024-10-16T08:18:47.363Z",
"__v": 0
}
],
"订单来源": {
"_id": "6711cac194e24dea3c362d8a",
"团队": "670f75093da0e5c5809b4584",
"账号负责人": "670f75043da0e5c5809b457d",
"前端引流人员": "670f75043da0e5c5809b457d",
"账号类型": [
"670f75e43da0e5c5809b45a4"
],
"账号编号": "2",
"微信号": "2",
"微信昵称": "2",
"手机编号": "2",
"账号状态": 1,
"备注": "2",
"日增长数据": [],
"createdAt": "2024-10-18T02:41:05.092Z",
"updatedAt": "2024-10-18T02:41:05.092Z",
"__v": 0
},
"导购": {
"_id": "670f75043da0e5c5809b457d",
"姓名": "999",
"电话": "999",
"邮箱": "999@qq.com",
"密码": "$2b$10$BucUsXpf4rVYgjU.IGVfju2d2OijFBA74EQCSenL5ftsERcSd7uyC",
"角色": "66b99afa3324caa6bd2b57ae",
"createdAt": "2024-10-16T08:10:44.743Z",
"updatedAt": "2024-10-16T08:10:49.908Z",
"__v": 0,
"团队": "670f75093da0e5c5809b4584"
},
"成交日期": "2024-10-18T02:42:26.500Z",
"应收金额": 500,
"收款金额": 100,
"待收款": 300,
"收款平台": "670f762d3da0e5c5809b45b2",
"订单状态": [
"正常"
],
"收款状态": "定金",
"货款状态": "待结算",
"余额抵用": null,
"备注": "测试2",
"优惠券": [],
"售后记录": [],
"createdAt": "2024-10-18T02:43:02.441Z",
"updatedAt": "2024-10-18T02:43:02.441Z",
"__v": 0
}
],
"afterSalesRecords": []
}
*/
let totalSalesIncome = 0; // 收款金额总和
let totalPendingIncome = 0; // 待收款总和
let totalAfterSalesIncome = 0; // 售后收入总和
let totalAfterSalesExpenditure = 0; // 售后支出总和
const accountsSet = new Set<string>(); // 用于去重保存账号编号
// 计算销售记录中的收款金额和待收款
data.salesRecords.forEach((record: any) => {
totalSalesIncome += record. || 0;
totalPendingIncome += record. || 0;
if (record.?.) {
accountsSet.add(record..); // 保存账号编号
}
});
// 计算售后记录中的收入和支出
data.afterSalesRecords.forEach((record: any) => {
if (record. === '收入') {
totalAfterSalesIncome += record. || 0;
} else if (record. === '支出') {
totalAfterSalesExpenditure += record. || 0;
}
});
// 本单收入 = 收款金额总和 + 待收款总和 + 售后收入 - 售后支出
const calculatedIncome = totalSalesIncome + totalPendingIncome + totalAfterSalesIncome - totalAfterSalesExpenditure;
setTotalIncome(calculatedIncome);
setStoreAccounts(Array.from(accountsSet)); // 转换 Set 为数组并保存
} catch (error) {
console.error('获取客户消费总额时出错:', error);
} finally {
setLoading(false);
}
};
fetchCustomerIncomeData();
}, [phone]);
//return { totalIncome, loading };
return { totalIncome, storeAccounts, loading };
};
export default useCustomerTotalIncome;

View File

@@ -0,0 +1,54 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { Account } from '@/models';
import connectDB from '@/utils/connectDB';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { query: { id }, method } = req;
switch (method) {
case 'GET':
try {
const account = await Account.findById(id)
.populate('账号负责人')
.populate('前端引流人员')
.populate('账号类型');
if (!account) {
return res.status(404).json({ message: '未找到账号' });
}
res.status(200).json(account);
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
break;
case 'PUT':
try {
const { , , , , unionid, openid, , , , , , , , } = req.body;
const updatedAccount = await Account.findByIdAndUpdate(id, {
, , , , unionid, openid, , , , , , , ,
}, { new: true });
if (!updatedAccount) {
return res.status(404).json({ message: '未找到账号' });
}
res.status(200).json({ message: '店铺账号更新成功', account: updatedAccount });
} catch (error) {
res.status(400).json({ message: '更新店铺账号失败' });
}
break;
case 'DELETE':
try {
const deletedAccount = await Account.findByIdAndDelete(id);
if (!deletedAccount) {
return res.status(404).json({ message: '未找到账号' });
}
res.status(200).json({ message: '店铺账号删除成功' });
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`不允许 ${method} 方法`);
}
};
export default connectDB(handler);

View File

@@ -0,0 +1,41 @@
//src\pages\api\backstage\accounts\accountgrowth\index.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import connectDB from '@/utils/connectDB'; // 使用高阶函数
import { Account } from '@/models'; // 使用之前的 AccountSchema 模型
interface AccountData {
_id: string;
账号编号: string;
微信号: string;
日增长数据: DailyGrowth[];
}
interface DailyGrowth {
日期: string;
总人数: number;
扣除人数: number;
日增长人数: number;
}
type Data = {
accounts?: AccountData[];
error?: string;
};
const handler = async (req: NextApiRequest, res: NextApiResponse<Data>) => {
const { teamId } = req.query;
try {
const accounts = await Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据');
if (!accounts) {
return res.status(404).json({ error: '未找到账号数据' });
}
res.status(200).json({ accounts });
} catch (error) {
res.status(500).json({ error: '获取增长记录失败' });
}
};
export default connectDB(handler); // 使用 connectDB 包装 API 处理函数

View File

@@ -0,0 +1,56 @@
//src\pages\api\backstage\accounts\accountgrowth\register.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import connectDB from '@/utils/connectDB'; // 使用高阶函数确保数据库连接
import { Account } from '@/models'; // 使用 Account 模型
import dayjs from 'dayjs';
interface DailyGrowth {
日期: Date;
总人数: number;
扣除人数: number;
日增长人数: number;
}
type Data = {
success?: boolean;
error?: string;
};
const handler = async (req: NextApiRequest, res: NextApiResponse<Data>) => {
const { accountId, date, totalPeople, deductedPeople } = req.body;
try {
// 找到目标账号
const account = await Account.findById(accountId);
if (!account) {
return res.status(404).json({ error: '账号未找到' });
}
// 查找是否已经存在当天的记录
const existingRecord = account..find((record: DailyGrowth) =>
dayjs(record.).isSame(dayjs(date), 'day')
);
if (existingRecord) {
// 更新已有记录
existingRecord. = totalPeople;
existingRecord. = deductedPeople;
existingRecord. = totalPeople - deductedPeople;
} else {
// 添加新记录
account..push({
日期: dayjs(date).toDate(),
总人数: totalPeople,
扣除人数: deductedPeople,
日增长人数: totalPeople - deductedPeople,
});
}
await account.save();
res.status(200).json({ success: true });
} catch (error) {
res.status(500).json({ error: '保存增长记录失败' });
}
};
export default connectDB(handler); // 使用 connectDB 包装 API 处理函数

View File

@@ -0,0 +1,65 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { broadcastUpdate } from './sse'; // 引入广播功能
import { Account } from '@/models';
import dayjs from 'dayjs';
import { IDailyGrowth } from '@/models/types'; // 引入 DailyGrowth 类型
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { accountId, date, totalPeople, deductedPeople } = req.body;
try {
const account = await Account.findById(accountId);
if (!account) {
return res.status(404).json({ error: '账号未找到' });
}
const selectedDate = dayjs(date);
// 找到当前日期的记录
const existingRecord = account..find((record: IDailyGrowth) =>
dayjs(record.).isSame(selectedDate, 'day')
);
// 找到前一天的记录
const previousRecord = account.
.filter((record: IDailyGrowth) => dayjs(record.).isBefore(selectedDate))
.sort((a: IDailyGrowth, b: IDailyGrowth) => dayjs(b.).unix() - dayjs(a.).unix())[0]; // 获取最近的一条记录
let previousTotalPeople = 0;
let growthPeople = 0;
// 如果存在前一天的记录,计算增长,否则增长为 0
if (previousRecord) {
previousTotalPeople = previousRecord.;
growthPeople = totalPeople - deductedPeople - previousTotalPeople;
} else {
// 如果是首条记录,增长应为 0
growthPeople = 0;
}
// 更新现有记录或添加新记录
if (existingRecord) {
existingRecord. = totalPeople;
existingRecord. = deductedPeople;
existingRecord. = growthPeople;
} else {
account..push({
日期: selectedDate.toDate(),
总人数: totalPeople,
扣除人数: deductedPeople,
日增长人数: growthPeople,
});
}
await account.save();
// 广播更新
broadcastUpdate(account.);
res.status(200).json({ success: true });
} catch (error) {
res.status(500).json({ error: '保存增长记录失败' });
}
};
export default handler;

View File

@@ -0,0 +1,38 @@
//src\pages\api\backstage\accounts\accountgrowth\sse.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { Account } from '@/models';
let clients: NextApiResponse[] = [];
const handler = (req: NextApiRequest, res: NextApiResponse) => {
const { teamId } = req.query;
// 设置 SSE 头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 添加客户端
clients.push(res);
// 监听连接关闭事件,移除客户端
req.on('close', () => {
clients = clients.filter(client => client !== res);
});
// 每次新连接时发送当前数据
Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据').then((accounts) => {
res.write(`data: ${JSON.stringify({ accounts })}\n\n`);
});
};
// 广播更新
export const broadcastUpdate = (teamId: string) => {
Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据').then((accounts) => {
clients.forEach((client) => {
client.write(`data: ${JSON.stringify({ accounts })}\n\n`);
});
});
};
export default handler;

View File

@@ -0,0 +1,45 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { Account } from '@/models';
import connectDB from '@/utils/connectDB';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
try {
const { teamId } = req.query;
const accounts = await Account.find({ 团队: teamId })
.select('账号负责人 前端引流人员 账号类型 账号编号 手机编号 微信昵称 微信号 账号状态 备注')
.populate({
path: '账号负责人',
select: '姓名',
})
.populate({
path: '前端引流人员',
select: '姓名',
})
.populate({
path: '账号类型',
select: 'name icon',
});
res.status(200).json({ accounts });
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
} else if (req.method === 'POST') {
try {
const { , , , , unionid, openid, , , , , , , , } = req.body;
const newAccount = new Account({
, , , , unionid, openid, , , , , , , ,
});
await newAccount.save();
res.status(201).json({ message: '店铺账号创建成功', account: newAccount });
} catch (error) {
console.error(error);
res.status(400).json({ message: '创建店铺账号失败' });
}
} else {
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`不允许 ${req.method} 方法`);
}
};
export default connectDB(handler);

View File

@@ -0,0 +1,37 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { Account } from '@/models';
let clients: NextApiResponse[] = [];
const handler = (req: NextApiRequest, res: NextApiResponse) => {
const { teamId } = req.query;
// 设置 SSE 头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 添加客户端
clients.push(res);
// 监听连接关闭事件,移除客户端
req.on('close', () => {
clients = clients.filter(client => client !== res);
});
// 每次新连接时发送当前数据
Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据').then((accounts) => {
res.write(`data: ${JSON.stringify({ accounts })}\n\n`);
});
};
// 广播更新
export const broadcastUpdate = (teamId: string) => {
Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据').then((accounts) => {
clients.forEach((client) => {
client.write(`data: ${JSON.stringify({ accounts })}\n\n`);
});
});
};
export default handler;

View File

@@ -0,0 +1,48 @@
import { NextApiRequest, NextApiResponse } from 'next';
import connectDB from '@/utils/connectDB';
import { Transaction } from '@/models';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
const { id } = req.query; // 获取 URL 中的客户 ID
switch (method) {
case 'GET':
try {
// 验证客户 ID 是否存在
if (!id || typeof id !== 'string') {
return res.status(400).json({ message: '客户 ID 无效' });
}
// 查询客户的所有交易记录,并计算余额
const transactions = await Transaction.find({ 客户: id }).select('金额 类型');
// 如果没有找到交易记录,返回余额为 0
if (!transactions || transactions.length === 0) {
return res.status(200).json({ balance: 0 });
}
// 计算余额,假设充值增加余额,消费减少余额
const balance = transactions.reduce((acc, transaction) => {
if (transaction. === '充值' || transaction. === '优惠') {
return acc + transaction.;
} else if (transaction. === '消费' || transaction. === '退款') {
return acc - transaction.;
}
return acc;
}, 0);
res.status(200).json({ balance });
} catch (error) {
console.error('服务器错误:', error);
res.status(500).json({ message: '服务器错误,请稍后重试' });
}
break;
default:
res.setHeader('Allow', ['GET']);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
// 使用 connectDB 高阶函数来确保在处理请求之前数据库已连接
export default connectDB(handler);

View File

@@ -0,0 +1,50 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { Coupon } from '@/models';
import connectDB from '@/utils/connectDB';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { query: { id }, method } = req;
switch (method) {
case 'GET':
try {
const coupon = await Coupon.findById(id);
if (!coupon) {
return res.status(404).json({ message: '未找到优惠券' });
}
res.status(200).json(coupon);
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
break;
case 'PUT':
try {
const { , , , , , , , , , } = req.body;
const updateData = { , , , , , , , , , };
const updatedCoupon = await Coupon.findByIdAndUpdate(id, updateData, { new: true });
if (!updatedCoupon) {
return res.status(404).json({ message: '未找到优惠券' });
}
res.status(200).json({ message: '优惠券更新成功', coupon: updatedCoupon });
} catch (error) {
res.status(400).json({ message: '更新优惠券失败' });
}
break;
case 'DELETE':
try {
const deletedCoupon = await Coupon.findByIdAndDelete(id);
if (!deletedCoupon) {
return res.status(404).json({ message: '未找到优惠券' });
}
res.status(200).json({ message: '优惠券删除成功' });
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`不允许 ${method} 方法`);
}
};
export default connectDB(handler);

View File

@@ -0,0 +1,56 @@
//src\pages\api\backstage\coupons\assign\[id].ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { Customer, Coupon } from '@/models';
import connectDB from '@/utils/connectDB';
import { v4 as uuidv4 } from 'uuid'; // 用于生成唯一券码
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
try {
const { id } = req.query; // 客户的 ID
const { couponIds } = req.body;
const customer = await Customer.findById(id);
if (!customer) {
return res.status(404).json({ message: '客户未找到' });
}
if (!Array.isArray(customer.)) {
customer. = [];
}
for (const couponId of couponIds) {
const coupon = await Coupon.findById(couponId);
if (!coupon) {
return res.status(404).json({ message: `优惠券未找到: ${couponId}` });
}
// 生成唯一的券码
const uniqueCouponCode = uuidv4();
// 使用符合 CouponUsageSchema 的结构
const couponUsage = {
_id: coupon._id, // Coupon的ObjectId
券码: uniqueCouponCode,
已使用: false,
使用日期: null, // 目前未使用,所以设置为 null
};
// 将优惠券分配给客户
customer..push(couponUsage);
}
await customer.save();
res.status(200).json({ message: '优惠券分配成功' });
} catch (error) {
console.error('Assign Coupon Failed:', error);
res.status(500).json({ message: '服务器错误' });
}
} else {
res.setHeader('Allow', ['POST']);
res.status(405).end(`不允许 ${req.method} 方法`);
}
};
export default connectDB(handler);

View File

@@ -0,0 +1,39 @@
//src\pages\api\backstage\coupons\assign\index.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { Customer } from '@/models';
import connectDB from '@/utils/connectDB';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
try {
const { customerId } = req.query;
if (!customerId) {
return res.status(400).json({ message: '缺少客户ID' });
}
// 查询客户信息,并填充其关联的优惠券信息
const customer = await Customer.findById(customerId)
.populate({
path: '优惠券._id',
model: 'Coupon', // 确保关联的是 Coupon 模型
select: '名称 描述 优惠券类型 金额 折扣 最低消费 生效日期 失效日期' // 选择需要的字段
});
if (!customer) {
return res.status(404).json({ message: '客户未找到' });
}
// 返回客户的优惠券信息
res.status(200).json({ coupons: customer.优惠券 });
} catch (error) {
console.error('查询客户优惠券信息失败:', error);
res.status(500).json({ message: '服务器错误' });
}
} else {
res.setHeader('Allow', ['GET']);
res.status(405).end(`不允许 ${req.method} 方法`);
}
};
export default connectDB(handler);

View File

@@ -0,0 +1,51 @@
//src\pages\api\backstage\coupons\index.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { Coupon } from '@/models';
import connectDB from '@/utils/connectDB';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
try {
const { teamId } = req.query;
const coupons = await Coupon.find({ 团队: teamId });
res.status(200).json({ coupons });
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
} else if (req.method === 'POST') {
try {
const {
//客户,
,
, , ,
,
,
,
,
,
} = req.body;
const newCoupon = new Coupon({
//客户,
,
, , ,
,
,
,
,
,
});
await newCoupon.save();
res.status(201).json({ message: '优惠券创建成功', coupon: newCoupon });
} catch (error) {
console.error('Create Coupon Failed:', error);
res.status(400).json({ message: '创建优惠券失败' });
}
} else {
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`不允许 ${req.method} 方法`);
}
};
export default connectDB(handler);

View File

@@ -0,0 +1,33 @@
//src\pages\api\backstage\transactions\[id].ts
import { NextApiRequest, NextApiResponse } from 'next';
import connectDB from '@/utils/connectDB';
import { Transaction } from '@/models';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
const { id } = req.query; // 获取客户 ID
switch (method) {
case 'GET':
try {
// 查询指定客户的所有交易记录
const transactions = await Transaction.find({ 客户: id }).sort({ : -1 }); // 按日期倒序排列
if (!transactions || transactions.length === 0) {
return res.status(404).json({ message: '未找到任何交易记录' });
}
// 返回交易记录
res.status(200).json({ transactions });
} catch (error) {
console.error('获取交易记录时出错:', error);
res.status(500).json({ message: '服务器错误,请稍后重试' });
}
break;
default:
res.setHeader('Allow', ['GET']);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
export default connectDB(handler);

View File

@@ -0,0 +1,59 @@
import { NextApiRequest, NextApiResponse } from 'next';
import connectDB from '@/utils/connectDB';
import { Transaction, Customer } from '@/models';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'POST':
try {
const { , , , , } = req.body;
// 输出接收到的参数
console.log('接收到的参数:', { , , , , });
if (! || ! || ! || !) {
return res.status(400).json({ message: '缺少必要的参数' });
}
// 创建新的交易记录
const newTransaction = new Transaction({
,
,
,
,
余额: 金额, // 首次充值即为余额
,
});
await newTransaction.save();
// 根据交易类型更新客户余额
const updateAmount = === '充值' ? : -;
// 更新客户余额
const updatedCustomer = await Customer.findByIdAndUpdate(, {
$inc: { 余额: updateAmount },
}, { new: true });
// 更新交易记录的余额字段为客户的最新余额
newTransaction. = updatedCustomer?.;
await newTransaction.save();
//res.status(200).json({ message: '充值成功', transaction: newTransaction });
// 返回创建的交易记录包括生成的ID
res.status(200).json({ message: '交易成功', transaction: newTransaction, _id: newTransaction._id });
} catch (error) {
console.error('服务器错误:', error);
res.status(500).json({ message: '服务器错误,请稍后重试' });
}
break;
default:
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
// 使用 connectDB 高阶函数来确保在处理请求之前数据库已连接
export default connectDB(handler);

View File

@@ -29,14 +29,11 @@ const handler = connectDB(async (req: NextApiRequest, res: NextApiResponse) => {
// 查询产品图片使用lean()提高性能
const product = await Product.findById(id, '图片').lean().exec() as { 图片?: string } | null;
if (!product) {
return res.status(404).json({ message: '产品不存在' });
}
// 返回图片数据
// 始终返回200状态码产品不存在或无图片时返回null
return res.status(200).json({
image: product.图片 || null,
productId: id
image: product?.图片 || null,
productId: id,
exists: !!product
});
} catch (error) {

View File

@@ -13,15 +13,14 @@ import {
DatePicker,
Row,
Col,
message,
Popover,
Switch,
Space,
Typography,
Divider,
Card
Card,
App
} from 'antd';
import axios from 'axios';
import { ICustomer } from '@/models/types';
import { useUserInfo } from '@/store/userStore';
import {
@@ -32,6 +31,7 @@ import {
} from '@ant-design/icons';
const { Text, Title } = Typography;
const { useApp } = App;
interface AddCustomerComponentProps {
visible: boolean;
@@ -64,6 +64,7 @@ const AddCustomerComponent: React.FC<AddCustomerComponentProps> = ({
const [loading, setLoading] = useState(false);
const [address, setAddress] = useState<string>('');
const [useKuaidi100, setUseKuaidi100] = useState(true); // 默认使用快递100
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
// 表单提交处理
const handleSubmit = async (values: any) => {
@@ -75,17 +76,26 @@ const AddCustomerComponent: React.FC<AddCustomerComponentProps> = ({
};
try {
const response = await axios.post('/api/backstage/customers', formattedValues);
const newCustomer = response.data.customer;
const response = await fetch('/api/backstage/customers', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formattedValues)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
const newCustomer = responseData.customer;
message.success('客户添加成功');
onSuccess(newCustomer);
form.resetFields();
} catch (error: any) {
if (error.response && error.response.data && error.response.data.message) {
message.error(error.response.data.message);
} else {
message.error('添加客户失败,请检查输入的数据');
}
message.error(error.message || '添加客户失败,请检查输入的数据');
console.error('请求处理失败', error);
}
};

View File

@@ -1,11 +1,12 @@
// src/pages/team/sale/components/CustomerInfoComponent.tsx
import React, { useEffect, useState } from 'react';
import { Button, Card, Avatar, Descriptions, Spin, message, Typography, Modal, Tag } from 'antd';
import { Button, Card, Avatar, Descriptions, Spin, Typography, Modal, Tag, App } from 'antd';
import { ICustomer } from '@/models/types';
import dayjs from 'dayjs';
import Recharge from './Recharge';
import axios from 'axios';
import CouponModal from './coupon-modal';
const { useApp } = App;
import {
UserOutlined,
PhoneOutlined,
@@ -41,6 +42,7 @@ const CustomerInfoComponent: React.FC<CustomerInfoComponentProps> = ({
const [coupons, setCoupons] = useState<any[]>([]); // 存储客户的优惠券
const [loading, setLoading] = useState(false); // 数据加载状态
const [salesModalVisible, setSalesModalVisible] = useState(false); // 控制消费详情模态框的显示
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
//const { totalIncome, loading: incomeLoading } = useCustomerTotalIncome(customer?._id || null); // 使用自定义 Hook 获取消费总额
//const { totalIncome, loading: incomeLoading } = useCustomerTotalIncome(customer?.电话 || null); // 使用自定义 Hook 获取消费总额
@@ -71,24 +73,24 @@ const CustomerInfoComponent: React.FC<CustomerInfoComponentProps> = ({
setLoading(true);
try {
const [balanceResponse, couponsResponse] = await Promise.all([
axios.get(`/api/backstage/balance/${customer._id}`),
axios.get(`/api/backstage/coupons/assign`, {
params: { customerId: customer._id },
}),
fetch(`/api/backstage/balance/${customer._id}`),
fetch(`/api/backstage/coupons/assign?customerId=${customer._id}`),
]);
if (balanceResponse.status === 200) {
setBalance(balanceResponse.data.balance ?? 0);
if (balanceResponse.ok) {
const balanceData = await balanceResponse.json();
setBalance(balanceData.balance ?? 0);
} else {
setBalance(0);
}
if (couponsResponse.status === 200) {
setCoupons(couponsResponse.data.coupons ?? []);
if (couponsResponse.ok) {
const couponsData = await couponsResponse.json();
setCoupons(couponsData.coupons ?? []);
} else {
setCoupons([]);
}
} catch (error) {
} catch (error: unknown) {
console.error('获取数据时出错:', error);
message.error('获取客户数据失败,请稍后重试');
setBalance(0);
@@ -199,7 +201,7 @@ const CustomerInfoComponent: React.FC<CustomerInfoComponentProps> = ({
>{customer. ? dayjs(customer.).format('YYYY-MM-DD') : 'null'}</div>
<div style={{ display: 'flex', flexWrap: 'wrap', marginTop: 8 }}
>
{storeAccounts.length > 0 ? storeAccounts.map((storeAccount) =>
{storeAccounts.length > 0 ? storeAccounts.map((storeAccount: string) =>
<span key={storeAccount} style={{ marginRight: 8 }}>
<Tag icon={<ShopOutlined />}>{storeAccount}</Tag>
</span>) : ''}

View File

@@ -1,10 +1,11 @@
// src/pages/team/sale/components/EditCustomerInfo.tsx
import React, { useEffect } from 'react';
import { Modal, Form, Input, DatePicker, message } from 'antd';
import { Modal, Form, Input, DatePicker, App } from 'antd';
import { ICustomer } from '@/models/types';
import axios from 'axios';
import dayjs from 'dayjs';
const { useApp } = App;
interface EditCustomerInfoProps {
visible: boolean;
onCancel: () => void;
@@ -14,6 +15,7 @@ interface EditCustomerInfoProps {
const EditCustomerInfo: React.FC<EditCustomerInfoProps> = ({ visible, onOk, onCancel, customer }) => {
const [form] = Form.useForm();
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
// 当传入客户数据时,将其设置到表单中
useEffect(() => {
@@ -38,17 +40,19 @@ const EditCustomerInfo: React.FC<EditCustomerInfoProps> = ({ visible, onOk, onCa
const method = customer ? 'PUT' : 'POST';
const url = customer ? `/api/backstage/customers/${customer._id}` : '/api/backstage/customers';
const response = await axios({
const response = await fetch(url, {
method,
url,
data: {
...values,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...values,
}),
});
if (response.status === 200 || response.status === 201) {
//message.success('客户信息更新成功');
onOk(response.data.customer); // 假设 API 返回更新后的客户数据
if (response.ok) {
const responseData = await response.json();
onOk(responseData.customer); // 假设 API 返回更新后的客户数据
} else {
message.error('客户信息更新失败');
}

View File

@@ -11,61 +11,6 @@ interface ProductInfoComponentProps {
onAddProductClick: () => void; // 新增产品按钮的点击事件回调
}
/*数据模型
// src/models/index.ts
//产品模型定义
const ProductSchema: Schema = new Schema({
团队: { type: Schema.Types.ObjectId, ref: 'Team' }, // 将团队字段定义为引用 Team 模型的 ObjectId 表示产品所属团队
供应商: { type: Schema.Types.ObjectId, ref: 'Supplier' }, //关联供应商模型
品牌: { type: Schema.Types.ObjectId, ref: 'Brand' }, //关联品牌模型
品类: { type: Schema.Types.ObjectId, ref: 'Category' }, //关联品类模型
名称: String,
描述: String,
编码: String,
图片: String,
货号: String,
别名: Array, //别名字段 Array表示数组
级别: String,
//成本,包含成本、包装费、运费
成本: {
成本价: Number,
包装费: Number,
运费: Number,
},
售价: Number,
库存: Number,
}, { timestamps: true }); // 自动添加创建时间和更新时间
ProductSchema.index({ 团队: 1 }); // 对团队字段建立索引
// Supplier 供应商模型
const SupplierSchema = new Schema(
{
团队: { type: Schema.Types.ObjectId, ref: 'Team' }, //将团队字段定义为引用 Team 模型的 ObjectId
order: Number, //排序
供应商名称: String,
联系方式: {
电话: String,
联系人: String,
地址: String,
},
供应品类: [
{
type: Schema.Types.ObjectId,
ref: 'Category',
},
],
// 供应商状态: 0 停用 1 正常 2 异常 3 备用,默认为 1
status: { type: Number, default: 1 },
供应商等级: String,
供应商类型: String,
供应商备注: String,
},
{ timestamps: true },
);
*/
const ProductInfoComponent: React.FC<ProductInfoComponentProps> = ({ products, onAddProductClick }) => {
const defaultProduct = {
_id: 'default',

View File

@@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react';
import { Modal, Button, Form, Input, InputNumber, message, List } from 'antd';
import { Modal, Button, Form, Input, InputNumber, List, App } from 'antd';
import { useUserInfo } from '@/store/userStore';
import { ICustomer } from '@/models/types';
import axios from 'axios';
const { useApp } = App;
interface RechargeProps {
visible: boolean;
@@ -17,6 +18,7 @@ const Recharge: React.FC<RechargeProps> = ({ visible, onCancel, customer, onRech
const [transactions, setTransactions] = useState<any[]>([]); // 存储交易记录
const [form] = Form.useForm();
const { } = useUserInfo();
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
useEffect(() => {
const fetchBalanceAndTransactions = async () => {
@@ -28,17 +30,19 @@ const Recharge: React.FC<RechargeProps> = ({ visible, onCancel, customer, onRech
form.resetFields();
// 获取客户余额
const balanceResponse = await axios.get(`/api/backstage/balance/${customer._id}`);
if (balanceResponse.status === 200) {
setBalance(balanceResponse.data.balance ?? 0); // 默认值为 0
const balanceResponse = await fetch(`/api/backstage/balance/${customer._id}`);
if (balanceResponse.ok) {
const balanceData = await balanceResponse.json();
setBalance(balanceData.balance ?? 0); // 默认值为 0
} else {
setBalance(0); // 无法获取余额时设为 0
}
// 获取客户交易记录
const transactionsResponse = await axios.get(`/api/backstage/transactions/${customer._id}`);
if (transactionsResponse.status === 200) {
setTransactions(transactionsResponse.data.transactions);
const transactionsResponse = await fetch(`/api/backstage/transactions/${customer._id}`);
if (transactionsResponse.ok) {
const transactionsData = await transactionsResponse.json();
setTransactions(transactionsData.transactions);
} else {
message.error('无法获取交易记录');
}
@@ -65,15 +69,21 @@ const Recharge: React.FC<RechargeProps> = ({ visible, onCancel, customer, onRech
const values = await form.validateFields();
const { , } = values;
const response = await axios.post('/api/backstage/transactions', {
团队: 团队?._id,
客户: customer?._id,
: '充值',
,
,
const response = await fetch('/api/backstage/transactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
团队: 团队?._id,
客户: customer?._id,
: '充值',
,
,
})
});
if (response.status === 200) {
if (response.ok) {
message.success('充值成功');
onRechargeSuccess();
onCancel();

View File

@@ -1,9 +1,10 @@
//src\pages\team\sale\components\coupon-modal.tsx
import React, { useEffect, useState } from 'react';
import { Modal, Form, message, Select, List, Typography, Tag } from 'antd';
import axios from 'axios';
import { Modal, Form, Select, List, Typography, Tag, App } from 'antd';
import { useUserInfo } from '@/store/userStore'; // 使用 Zustand 获取用户信息
const { useApp } = App;
interface CouponModalProps {
visible: boolean;
onCancel: () => void;
@@ -36,6 +37,7 @@ const CouponModal: React.FC<CouponModalProps> = ({ visible, onOk, onCancel, cust
const [teamCoupons, setTeamCoupons] = useState<ICoupon[]>([]);
const [customerCoupons, setCustomerCoupons] = useState<ICustomerCoupon[]>([]);
const [defaultCouponIds, setDefaultCouponIds] = useState<string[]>([]); // 默认选中的优惠券ID数组
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
useEffect(() => {
if (userInfo.?._id) {
@@ -48,9 +50,13 @@ const CouponModal: React.FC<CouponModalProps> = ({ visible, onOk, onCancel, cust
const fetchTeamCoupons = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/coupons?teamId=${teamId}`);
const response = await fetch(`/api/backstage/coupons?teamId=${teamId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setTeamCoupons(data.coupons);
} catch (error) {
} catch (error: unknown) {
console.error('Failed to load team coupons:', error);
message.error('加载团队优惠券失败');
}
@@ -58,16 +64,18 @@ const CouponModal: React.FC<CouponModalProps> = ({ visible, onOk, onCancel, cust
const fetchCustomerCoupons = async (customerId: string) => {
try {
const { data } = await axios.get(`/api/backstage/coupons/assign`, {
params: { customerId },
});
const response = await fetch(`/api/backstage/coupons/assign?customerId=${customerId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setCustomerCoupons(data.coupons);
if (data.coupons.length > 0) {
const couponIds = data.coupons.map((coupon: ICustomerCoupon) => coupon._id._id); // 使用嵌套的 _id
setDefaultCouponIds(couponIds);
form.setFieldsValue({ couponIds });
}
} catch (error) {
} catch (error: unknown) {
console.error('Failed to load customer coupons:', error);
message.error('加载客户优惠券失败');
}
@@ -76,14 +84,24 @@ const CouponModal: React.FC<CouponModalProps> = ({ visible, onOk, onCancel, cust
const handleSubmit = async () => {
try {
const values = await form.validateFields();
await axios.post(`/api/backstage/coupons/assign/${customerId}`, {
couponIds: values.couponIds,
const response = await fetch(`/api/backstage/coupons/assign/${customerId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
couponIds: values.couponIds,
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
message.success('优惠券分配成功');
form.resetFields();
onOk();
} catch (error) {
} catch (error: unknown) {
console.error('Failed to assign coupons:', error);
message.error('分配优惠券失败');
}

View File

@@ -1,7 +1,8 @@
// src/pages/customer/sales-record.tsx
import React, { useState, useEffect } from 'react';
import { Card, List, Descriptions, message } from 'antd';
import axios from 'axios';
import { Card, List, Descriptions, App } from 'antd';
const { useApp } = App;
interface SalesRecordPageProps {
initialPhone?: string; // 用于传递初始手机号
@@ -12,6 +13,7 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ initialPhone }) => {
const [salesRecords, setSalesRecords] = useState<any[]>([]);
const [afterSalesRecords, setAfterSalesRecords] = useState<any[]>([]);
const [totalIncome, setTotalIncome] = useState<number>(0); // 新增的本单收入状态
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
const handleSearch = async () => {
if (!initialPhone) {
@@ -22,7 +24,13 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ initialPhone }) => {
setLoading(true);
try {
const { data } = await axios.get(`/api/backstage/customers/sales/${initialPhone}`);
const response = await fetch(`/api/backstage/customers/sales/${initialPhone}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.salesRecords) {
setSalesRecords(data.salesRecords);
@@ -33,7 +41,7 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ initialPhone }) => {
}
message.success('查询成功');
} catch (error) {
} catch (error: unknown) {
console.error(error);
message.error('查询失败,请稍后重试');
} finally {

View File

@@ -1,228 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Form, Input, Select, message, DatePicker, Button } from 'antd';
import { ISalesRecord } from '@/models/types';
import axios from 'axios';
import { useUserInfo } from '@/store/userStore';
interface SalesRecordPageProps {
salesRecord: ISalesRecord | null;
onCancel: () => void;
onOk: () => void;
}
const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ salesRecord, onCancel }) => {
const [form] = Form.useForm();
const [customers, setCustomers] = useState<any[]>([]); // 客户
const [products, setProducts] = useState<any[]>([]); // 产品
const [accounts, setAccounts] = useState<any[]>([]); //支付平台
const [payPlatforms, setPayPlatforms] = useState<any[]>([]); //支付平台
const [users, setUsers] = useState<any[]>([]);
const userInfo = useUserInfo(); // 获取当前用户信息
useEffect(() => {
if (userInfo.?._id) {
const teamId = userInfo.._id;
fetchCustomers(teamId);
fetchProducts(teamId);
fetchAccounts(teamId);
fetchUsers(teamId);
fetchPayPlatforms(teamId);
}
}, [userInfo]);
// 获取客户数据
const fetchCustomers = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/customers?teamId=${teamId}`);
setCustomers(data.customers);
} catch (error) {
message.error('加载客户数据失败');
}
};
// 获取产品数据
const fetchProducts = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/products?teamId=${teamId}`);
setProducts(data.products);
} catch (error) {
message.error('加载产品数据失败');
}
};
// 获取账户数据
const fetchAccounts = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/accounts?teamId=${teamId}`);
setAccounts(data.accounts);
} catch (error) {
message.error('加载账户数据失败');
}
};
// 获取用户数据
const fetchUsers = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/users?teamId=${teamId}`);
setUsers(data.users);
} catch (error) {
message.error('加载用户数据失败');
}
};
//获取支付平台数据
const fetchPayPlatforms = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/payment-platforms?teamId=${teamId}`);
setPayPlatforms(data.platforms || []); // 使用 data.platforms 而不是 data.paymentPlatforms
} catch (error) {
message.error('加载支付平台数据失败');
setPayPlatforms([]); // 确保失败时也设置为一个空数组
}
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
const method = salesRecord ? 'PUT' : 'POST';
const url = salesRecord ? `/api/backstage/sales/${salesRecord._id}` : '/api/backstage/sales';
await axios({
method: method,
url: url,
data: {
...values,
团队: userInfo.团队?._id, // 将团队ID包含在请求数据中
},
});
message.success('销售记录操作成功');
} catch (info) {
console.error('Validate Failed:', info);
message.error('销售记录操作失败');
}
};
return (
<div style={{ padding: '24px', maxWidth: '800px', margin: '0 auto' }}>
<Form
form={form}
layout="vertical"
initialValues={salesRecord ? { ...salesRecord } : {}}
>
<Form.Item
name="客户"
label="客户"
rules={[{ required: true, message: '请选择客户!' }]}
>
<Select
allowClear
showSearch
placeholder="请选择客户"
>
{customers.map(customer => (
<Select.Option key={customer._id} value={customer._id}>
{customer.}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="产品"
label="产品"
rules={[{ required: true, message: '请选择产品!' }]}
>
<Select
mode="multiple"
placeholder="请选择产品"
allowClear
showSearch
>
{products.map(product => (
<Select.Option key={product._id} value={product._id}>
{product.}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="订单来源"
label="订单来源"
rules={[{ required: true, message: '请选择订单来源!' }]}
>
<Select placeholder="请选择订单来源">
{accounts.map(account => (
<Select.Option key={account._id} value={account._id}>
{account.}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="导购"
label="导购"
rules={[{ required: true, message: '请选择导购!' }]}
>
<Select placeholder="请选择导购">
{users.map(user => (
<Select.Option key={user._id} value={user._id}>
{user.}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="成交日期"
label="成交日期"
rules={[{ required: true, message: '请选择成交日期!' }]}
>
<DatePicker style={{ width: '100%' }} />
</Form.Item>
<Form.Item
name="收款状态"
label="收款状态"
rules={[{ required: true, message: '请选择收款状态!' }]}
>
<Select placeholder="请选择收款状态">
<Select.Option value="未付款"></Select.Option>
<Select.Option value="已付款"></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="收款平台"
label="收支平台"
rules={[{ required: true, message: '请选择收支平台!' }]}
>
<Select placeholder="收款平台">
{payPlatforms && Array.isArray(payPlatforms) && payPlatforms.map(platform => (
<Select.Option key={platform._id} value={platform._id}>
{platform.}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="货款状态"
label="货款状态"
rules={[{ required: true, message: '请选择货款状态!' }]}
>
<Select placeholder="请选择货款状态">
<Select.Option value="可结算"></Select.Option>
<Select.Option value="不可结算"></Select.Option>
<Select.Option value="待结算"></Select.Option>
<Select.Option value="已结算"></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="备注"
label="备注"
>
<Input.TextArea placeholder="请输入备注" />
</Form.Item>
<Form.Item>
<Button type="primary" onClick={handleSubmit}>
</Button>
<Button onClick={onCancel} style={{ marginLeft: '10px' }}>
</Button>
</Form.Item>
</Form>
</div>
);
};
export default SalesRecordPage;

View File

@@ -1,137 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Table, Button, Space, message, Card } from 'antd';
import axios from 'axios';
import { ISalesRecord } from '@/models/types'; // 确保有正确的类型定义
import { useUserInfo } from '@/store/userStore'; // 使用 Zustand 获取用户信息
import SalesRecordModal from './sales-record-modal'; // 引入销售记录模态框组件
import dayjs from 'dayjs';
const SalesPage = () => {
const [salesRecords, setSalesRecords] = useState<ISalesRecord[]>([]);
const [isModalVisible, setIsModalVisible] = useState(false);
const [currentSalesRecord, setCurrentSalesRecord] = useState<ISalesRecord | null>(null);
const userInfo = useUserInfo(); // 获取当前用户信息
useEffect(() => {
if (userInfo.?._id) {
fetchSalesRecords(userInfo.._id);
}
}, [userInfo]);
const fetchSalesRecords = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/sales?teamId=${teamId}`);
setSalesRecords(data.salesRecords);
} catch (error) {
message.error('加载销售数据失败');
}
};
const handleModalOk = () => {
setIsModalVisible(false);
if (userInfo.?._id) {
fetchSalesRecords(userInfo.._id);
}
};
const handleCreate = () => {
setCurrentSalesRecord(null);
setIsModalVisible(true);
};
const handleDelete = async (id: string) => {
try {
await axios.delete(`/api/backstage/sales/${id}`);
if (userInfo.?._id) {
fetchSalesRecords(userInfo.._id);
}
message.success('销售记录删除成功');
} catch (error) {
message.error('删除销售记录失败');
}
};
const columns = [
{
title: '成交日期',
dataIndex: '成交日期',
key: 'dealDate',
render: (date: Date) => dayjs(date).format('YYYY-MM-DD'),
},
{
title: '客户',
dataIndex: '客户',
key: 'customer',
render: (customer: any) => customer ? customer. : '无'
},
{
title: '产品',
dataIndex: '产品',
key: 'products',
render: (products: any[]) => products.map(p => p.).join(', ')
},
{
title: '订单来源',
dataIndex: '订单来源',
key: 'orderSource',
render: (source: any) => source ? source. : '无'
},
{
title: '导购',
dataIndex: '导购',
key: 'salesperson',
render: (user: any) => user ? user. : '无'
},
{
title: '收款状态',
dataIndex: '收款状态',
key: 'paymentStatus',
},
{
title: '货款状态',
dataIndex: '货款状态',
key: 'paymentPlatform',
},
{
title: '备注',
dataIndex: '备注',
key: 'remark',
},
{
title: '操作',
key: 'action',
render: (_: any, record: ISalesRecord) => (
<Space size="middle">
<Button onClick={() => handleDelete(record._id)}></Button>
</Space>
),
},
];
return (
<Card
title="销售记录列表"
extra={
<Button type="primary" onClick={handleCreate}>
</Button>
}
>
<Table
columns={columns}
dataSource={salesRecords}
rowKey="_id"
/>
{isModalVisible && (
<SalesRecordModal
visible={isModalVisible}
onOk={handleModalOk}
onCancel={() => setIsModalVisible(false)}
salesRecord={currentSalesRecord}
/>
)}
</Card>
);
};
export default SalesPage;

View File

@@ -1,9 +1,10 @@
//src\pages\team\sale\index.tsx
import React, { useEffect, useState } from 'react';
import { Form, Input, Select, message, DatePicker, Button, Row, Col, Card, Divider, Tag } from 'antd';
import { Form, Input, Select, DatePicker, Button, Row, Col, Card, Divider, Tag, App } from 'antd';
import { ICustomer, ICustomerCoupon, IProduct, ISalesRecord } from '@/models/types';
import axios from 'axios';
import { useUserInfo } from '@/store/userStore';
const { useApp } = App;
import CustomerInfoComponent from './components/CustomerInfoComponent';
import ProductInfoComponent from './components/ProductInfoComponent';
import AddProductComponent from './components/AddProductComponent';
@@ -22,6 +23,7 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ salesRecord, onCancel
const [accounts, setAccounts] = useState<any[]>([]); //支付平台
const [payPlatforms, setPayPlatforms] = useState<any[]>([]); //支付平台
const userInfo = useUserInfo(); // 获取当前用户信息
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
const [selectedCustomer, setSelectedCustomer] = useState<ICustomer | null>(null);// 选中的客户
const [selectedProducts, setSelectedProducts] = useState<IProduct[]>([]);// 选中的产品,是一个数组
const [customerBalance, setCustomerBalance] = useState<number>(0); // 客户余额
@@ -74,19 +76,19 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ salesRecord, onCancel
setSelectedCustomer(customer);
if (customer) {
try {
const response = await axios.get(`/api/backstage/balance/${customer._id}`);
if (response.status === 200) {
setCustomerBalance(response.data.balance ?? 0);
const response = await fetch(`/api/backstage/balance/${customer._id}`);
if (response.ok) {
const balanceData = await response.json();
setCustomerBalance(balanceData.balance ?? 0);
setDeductionAmount(0); // 重置抵扣金额
} else {
setCustomerBalance(0);
}
// 获取客户优惠券
const couponsResponse = await axios.get(`/api/backstage/coupons/assign`, {
params: { customerId: customer._id },
});
if (couponsResponse.status === 200) {
const customerCoupons = couponsResponse.data.coupons;
const couponsResponse = await fetch(`/api/backstage/coupons/assign?customerId=${customer._id}`);
if (couponsResponse.ok) {
const couponsData = await couponsResponse.json();
const customerCoupons = couponsData.coupons;
setCustomerCoupons(customerCoupons);
if (customerCoupons.length > 0) {
@@ -96,7 +98,7 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ salesRecord, onCancel
} else {
setCustomerCoupons([]);
}
} catch (error) {
} catch (error: unknown) {
console.error('获取客户余额时出错:', error);
setCustomerBalance(0);
}
@@ -120,38 +122,58 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ salesRecord, onCancel
// 获取客户数据
const fetchCustomers = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/customers?teamId=${teamId}`);
const response = await fetch(`/api/backstage/customers?teamId=${teamId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setCustomers(data.customers);
} catch (error) {
} catch (error: unknown) {
console.error('加载客户数据失败:', error);
message.error('加载客户数据失败');
}
};
// 获取产品数据
const fetchProducts = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/products?teamId=${teamId}`);
const response = await fetch(`/api/backstage/products?teamId=${teamId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setProducts(data.products);
} catch (error) {
} catch (error: unknown) {
console.error('加载产品数据失败:', error);
message.error('加载产品数据失败');
}
};
// 获取账户数据
const fetchAccounts = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/accounts?teamId=${teamId}`);
const response = await fetch(`/api/backstage/accounts?teamId=${teamId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
//按账号编号排序
data.accounts.sort((a: any, b: any) => a. - b.);
setAccounts(data.accounts);
} catch (error) {
} catch (error: unknown) {
console.error('加载账户数据失败:', error);
message.error('加载账户数据失败');
}
};
//获取支付平台数据
const fetchPayPlatforms = async (teamId: string) => {
try {
const { data } = await axios.get(`/api/backstage/payment-platforms?teamId=${teamId}`);
const response = await fetch(`/api/backstage/payment-platforms?teamId=${teamId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setPayPlatforms(data.platforms || []); // 使用 data.platforms 而不是 data.paymentPlatforms
} catch (error) {
} catch (error: unknown) {
console.error('加载支付平台数据失败:', error);
message.error('加载支付平台数据失败');
setPayPlatforms([]); // 确保失败时也设置为一个空数组
}
@@ -165,14 +187,26 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ salesRecord, onCancel
let transactionId = null; // 初始化交易记录ID
if (deductionAmount > 0) {
// 创建消费记录来扣除余额并获取交易记录ID
const transactionResponse = await axios.post('/api/backstage/transactions', {
团队: userInfo.团队?._id,
客户: selectedCustomer ? selectedCustomer._id : null,
: '消费',
金额: deductionAmount,
: '销售余额抵扣',
const transactionResponse = await fetch('/api/backstage/transactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
团队: userInfo.团队?._id,
客户: selectedCustomer ? selectedCustomer._id : null,
: '消费',
金额: deductionAmount,
: '销售余额抵扣',
})
});
transactionId = transactionResponse.data._id; // 获取创建的交易记录的ID
if (!transactionResponse.ok) {
throw new Error(`Transaction failed! status: ${transactionResponse.status}`);
}
const transactionData = await transactionResponse.json();
transactionId = transactionData._id; // 获取创建的交易记录的ID
console.log('生成的transactionId:', transactionId); // 打印transactionId
}
@@ -187,12 +221,18 @@ const SalesRecordPage: React.FC<SalesRecordPageProps> = ({ salesRecord, onCancel
};
// 将交易记录ID传递到后端
await axios({
const salesResponse = await fetch(url, {
method: method,
url: url,
data: requestData,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
});
if (!salesResponse.ok) {
throw new Error(`Sales record failed! status: ${salesResponse.status}`);
}
message.success('销售记录操作成功');
// 清空表单
form.resetFields(); // 重置表单