This commit is contained in:
@@ -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);
|
||||
|
||||
234
src/hooks/useCustomerTotalIncome.ts
Normal file
234
src/hooks/useCustomerTotalIncome.ts
Normal 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;
|
||||
54
src/pages/api/backstage/accounts/[id].ts
Normal file
54
src/pages/api/backstage/accounts/[id].ts
Normal 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);
|
||||
41
src/pages/api/backstage/accounts/accountgrowth/index.ts
Normal file
41
src/pages/api/backstage/accounts/accountgrowth/index.ts
Normal 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 处理函数
|
||||
@@ -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 处理函数
|
||||
65
src/pages/api/backstage/accounts/accountgrowth/register.ts
Normal file
65
src/pages/api/backstage/accounts/accountgrowth/register.ts
Normal 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;
|
||||
38
src/pages/api/backstage/accounts/accountgrowth/sse.ts
Normal file
38
src/pages/api/backstage/accounts/accountgrowth/sse.ts
Normal 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;
|
||||
45
src/pages/api/backstage/accounts/index.ts
Normal file
45
src/pages/api/backstage/accounts/index.ts
Normal 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);
|
||||
37
src/pages/api/backstage/accounts/sse.ts
Normal file
37
src/pages/api/backstage/accounts/sse.ts
Normal 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;
|
||||
48
src/pages/api/backstage/balance/[id].ts
Normal file
48
src/pages/api/backstage/balance/[id].ts
Normal 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);
|
||||
50
src/pages/api/backstage/coupons/[id].ts
Normal file
50
src/pages/api/backstage/coupons/[id].ts
Normal 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);
|
||||
56
src/pages/api/backstage/coupons/assign/[id].ts
Normal file
56
src/pages/api/backstage/coupons/assign/[id].ts
Normal 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);
|
||||
39
src/pages/api/backstage/coupons/assign/index.ts
Normal file
39
src/pages/api/backstage/coupons/assign/index.ts
Normal 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);
|
||||
51
src/pages/api/backstage/coupons/index.ts
Normal file
51
src/pages/api/backstage/coupons/index.ts
Normal 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);
|
||||
33
src/pages/api/backstage/transactions/[id].ts
Normal file
33
src/pages/api/backstage/transactions/[id].ts
Normal 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);
|
||||
59
src/pages/api/backstage/transactions/index.ts
Normal file
59
src/pages/api/backstage/transactions/index.ts
Normal 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);
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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>) : ''}
|
||||
|
||||
@@ -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('客户信息更新失败');
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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('分配优惠券失败');
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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,11 +221,17 @@ 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('销售记录操作成功');
|
||||
// 清空表单
|
||||
|
||||
Reference in New Issue
Block a user