This commit is contained in:
22
src/pages/api/paymentCodeImage/[id].ts
Normal file
22
src/pages/api/paymentCodeImage/[id].ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import connectDB from '@/utils/connectDB';
|
||||
import { CustomerPaymentCode } from '@/models';
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { id } = req.query;
|
||||
|
||||
try {
|
||||
const paymentCode = await CustomerPaymentCode.findById(id);
|
||||
|
||||
if (!paymentCode) {
|
||||
return res.status(404).json({ message: '收款码未找到' });
|
||||
}
|
||||
|
||||
res.status(200).send(paymentCode.收款码);
|
||||
} catch (error) {
|
||||
console.error('Error fetching payment code image:', error);
|
||||
res.status(500).json({ message: '服务器错误' });
|
||||
}
|
||||
};
|
||||
|
||||
export default connectDB(handler);
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { Table, Button, Card, Tag, Tooltip
|
||||
, Input, Space, DatePicker, App } from 'antd';
|
||||
, Input, Space, DatePicker, App, Typography } from 'antd';
|
||||
import { IAfterSalesRecord } from '@/models/types';
|
||||
import dayjs from 'dayjs';
|
||||
import { useUserInfo } from '@/store/userStore';
|
||||
@@ -15,7 +15,7 @@ import { useRouter } from 'next/router';
|
||||
import { CheckCircleOutlined, ClockCircleOutlined, DownloadOutlined, FieldTimeOutlined, IdcardOutlined, MobileOutlined, SyncOutlined, UserOutlined, WechatOutlined
|
||||
, SearchOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { IconButton, Iconify } from '@/components/icon';
|
||||
import { Iconify } from '@/components/icon';
|
||||
import PaymentCodeImageComponent from '@/pages/components/PaymentCodeImage';
|
||||
import MultiAfterSalesModal from './MultiAfterSalesModal';
|
||||
import ShipModal from './ship-modal';
|
||||
@@ -23,6 +23,36 @@ import EditAfterSalesModal from './EditAfterSalesModal'; // 引入编辑售后
|
||||
import ProductCardList from '@/components/product/ProductCardList'; // 引入产品卡片列表组件
|
||||
import MyTooltip from '@/components/tooltip/MyTooltip';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
// 通用样式
|
||||
const COMMON_STYLES = {
|
||||
tagContainer: { margin: 0 },
|
||||
flexColumn: { display: "flex", flexDirection: "column" as const, alignItems: "flex-start", gap: "4px" },
|
||||
flexRow: { display: "flex", gap: "8px" }
|
||||
};
|
||||
|
||||
// 工具函数
|
||||
const formatDate = (dateString: string): string => {
|
||||
if (!dateString) return "未知";
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}).replace(/\//g, '-');
|
||||
};
|
||||
|
||||
|
||||
const getCustomerTagColor = (unreceivedAmount: number): string => {
|
||||
return unreceivedAmount > 0 ? "#cd201f" : "blue";
|
||||
};
|
||||
|
||||
const buildCustomerAddress = (address: any): string => {
|
||||
if (!address) return "未知";
|
||||
return `${address.省份 ?? ""} ${address.城市 ?? ""} ${address.区县 ?? ""} ${address.详细地址 ?? ""}`.trim();
|
||||
};
|
||||
|
||||
// 导出Excel相关库
|
||||
//import * as XLSX from 'xlsx';
|
||||
//import { saveAs } from 'file-saver';
|
||||
@@ -212,91 +242,104 @@ const AfterSaleRecordPage = () => {
|
||||
{
|
||||
title: '客户信息',
|
||||
width: 160,
|
||||
align: "center",
|
||||
key: '客户信息',
|
||||
// 增加filterDropdown用于尾号查询
|
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Input
|
||||
placeholder="输入手机尾号"
|
||||
value={selectedKeys[0] ? String(selectedKeys[0]) : ''}
|
||||
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onPressEnter={() => confirm()}
|
||||
style={{ marginBottom: 8, display: 'block' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => confirm()}
|
||||
size="small"
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
clearFilters?.(); // 使用可选链,避免类型错误
|
||||
confirm();
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
filterIcon: filtered => (
|
||||
<SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />
|
||||
),
|
||||
onFilter: (value, record: IAfterSalesRecord) => {
|
||||
const phoneTail = record.销售记录.客户?.电话
|
||||
? record.销售记录.客户.电话.slice(-4)
|
||||
: '';
|
||||
const searchVal = String(value);
|
||||
return phoneTail.includes(searchVal);
|
||||
},
|
||||
// 增加filterDropdown用于尾号查询
|
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Input
|
||||
placeholder="输入手机尾号"
|
||||
value={selectedKeys[0] ? String(selectedKeys[0]) : ''}
|
||||
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onPressEnter={() => confirm()}
|
||||
style={{ marginBottom: 8, display: 'block' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => confirm()}
|
||||
size="small"
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
clearFilters?.(); // 使用可选链,避免类型错误
|
||||
confirm();
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
filterIcon: filtered => (
|
||||
<SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />
|
||||
),
|
||||
onFilter: (value, record: IAfterSalesRecord) => {
|
||||
const phoneTail = record.销售记录.客户?.电话
|
||||
? record.销售记录.客户.电话.slice(-4)
|
||||
: '';
|
||||
const searchVal = String(value);
|
||||
return phoneTail.includes(searchVal);
|
||||
},
|
||||
render: (record: IAfterSalesRecord) => {
|
||||
// 将地址的各个部分拼接成一个字符串
|
||||
const address = record.销售记录.客户?.地址
|
||||
? `${record.销售记录.客户.地址.省份 ?? ''} ${record.销售记录.客户.地址.城市 ?? ''} ${record.销售记录.客户.地址.区县 ?? ''} ${record.销售记录.客户.地址.详细地址 ?? ''}`
|
||||
: '未知';
|
||||
const customerInfo = `姓名:${record.销售记录.客户?.姓名 ?? '未知'}\n电话:${record.销售记录.客户?.电话 ?? '未知'}\n地址:${address}`;
|
||||
const customerName = record.销售记录.客户?.姓名 ?? '未知';
|
||||
//显示账号编号
|
||||
const address = buildCustomerAddress(record.销售记录.客户?.地址);
|
||||
const customerName = record.销售记录.客户?.姓名 ?? "未知";
|
||||
const unreceivedAmount = parseFloat((record.销售记录.待收款 || 0).toString());
|
||||
const customerTagColor = getCustomerTagColor(unreceivedAmount);
|
||||
const accountNumber = record.销售记录.订单来源?.账号编号 ?? '未知';
|
||||
|
||||
// 准备复制文本
|
||||
const customerCopyText = `姓名:${customerName}\n电话:${record.销售记录.客户?.电话 ?? "未知"}\n地址:${address}`;
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{isAdmin && (
|
||||
<IconButton
|
||||
className="text-gray"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(customerInfo);
|
||||
message.success(`客户 ${customerName} 信息复制成功!`);
|
||||
} catch (error) {
|
||||
console.error('客户信息复制失败:', error);
|
||||
message.error('客户信息复制失败');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Iconify icon="eva:copy-fill" size={20} />
|
||||
</IconButton>
|
||||
)}
|
||||
<MyTooltip
|
||||
color="white"
|
||||
title={record.销售记录.客户?.姓名 ?? '未知'}>
|
||||
<div>
|
||||
<Tag icon={<UserOutlined />} color="">{record.销售记录.客户?.姓名 ?? '未知'}</Tag>
|
||||
<Tag icon={<FieldTimeOutlined />} color="">{record.销售记录.成交日期 ? new Date(record.销售记录.成交日期).toLocaleDateString() : '未知'}</Tag>
|
||||
<Tag icon={<MobileOutlined />} color="">{record.销售记录.客户?.电话 ? `****${record.销售记录.客户.电话.slice(-4)}` : '未知'}</Tag>
|
||||
{/*<div>状态:{record.销售记录?.订单状态?.[0]}</div>*/}
|
||||
{/* 将最后两个 Tag 放在一个 span 中,并使用 no-wrap 样式避免自动换行 */}
|
||||
<span style={{ whiteSpace: 'nowrap' }}>
|
||||
<Tag icon={<IdcardOutlined />} color="">
|
||||
{record.销售记录?.导购?.姓名 ?? '未知'}
|
||||
</Tag>
|
||||
<Tag icon={<WechatOutlined />} color="green">
|
||||
<div style={{ display: "flex" }}>
|
||||
{/* 左侧复制按钮 */}
|
||||
<div style={{ marginRight: 8 }}>
|
||||
{isAdmin && (
|
||||
<Paragraph
|
||||
copyable={{
|
||||
text: customerCopyText,
|
||||
onCopy: () => message.success(`客户 ${customerName} 信息复制成功!`),
|
||||
tooltips: ['复制客户信息', '复制成功'],
|
||||
icon: <Iconify icon="eva:copy-fill" size={16} />
|
||||
}}
|
||||
style={{ margin: 0, lineHeight: 0 }}
|
||||
>
|
||||
{/* 空内容,只显示复制按钮 */}
|
||||
</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 右侧标签信息,按用户要求布局 */}
|
||||
<MyTooltip color="white" title={customerName} placement="topLeft">
|
||||
<div style={COMMON_STYLES.flexColumn}>
|
||||
{/* 客户名字 */}
|
||||
<Tag icon={<UserOutlined />} color={customerTagColor} style={COMMON_STYLES.tagContainer}>
|
||||
{customerName}
|
||||
</Tag>
|
||||
|
||||
{/* 客户尾号 */}
|
||||
<Tag icon={<MobileOutlined />} color="green" style={COMMON_STYLES.tagContainer}>
|
||||
{record.销售记录.客户?.电话 ? `${record.销售记录.客户.电话.slice(-4)}` : "未知"}
|
||||
</Tag>
|
||||
|
||||
{/* 成交日期 */}
|
||||
<Tag icon={<FieldTimeOutlined />} color="purple" style={COMMON_STYLES.tagContainer}>
|
||||
{record.销售记录.成交日期 ? formatDate(record.销售记录.成交日期.toString()) : "未知"}
|
||||
</Tag>
|
||||
|
||||
{/* accountNumber+导购姓名显示在一行 */}
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
<Tag icon={<WechatOutlined />} color="geekblue" style={COMMON_STYLES.tagContainer}>
|
||||
{accountNumber}
|
||||
</Tag>
|
||||
</span>
|
||||
<Tag icon={<IdcardOutlined />} color="cyan" style={COMMON_STYLES.tagContainer}>
|
||||
{record.销售记录?.导购?.姓名 ?? '未知'}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</MyTooltip>
|
||||
</div>
|
||||
@@ -306,10 +349,21 @@ const AfterSaleRecordPage = () => {
|
||||
{
|
||||
title: '售后产品',
|
||||
dataIndex: '原产品',
|
||||
//width: 260,
|
||||
width: 260, // 设置固定宽度,防止占用其他列空间
|
||||
key: 'originalProducts',
|
||||
render: (products: any[], record: IAfterSalesRecord) => {
|
||||
return <ProductCardList products={products} record={record} />;
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
maxWidth: '270px',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: '#d9d9d9 transparent',
|
||||
}}>
|
||||
<ProductCardList products={products} record={record} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -333,6 +387,7 @@ const AfterSaleRecordPage = () => {
|
||||
{
|
||||
title: '售后信息',
|
||||
key: 'reasonAndType',
|
||||
width: 200,
|
||||
//筛选售后类型
|
||||
filters: [
|
||||
{ text: '退货', value: '退货' },
|
||||
@@ -363,9 +418,21 @@ const AfterSaleRecordPage = () => {
|
||||
{
|
||||
title: '替换产品',
|
||||
dataIndex: '替换产品',
|
||||
width: 260, // 设置固定宽度,防止占用其他列空间
|
||||
key: 'replacementProducts',
|
||||
render: (products: any[], record: IAfterSalesRecord) => {
|
||||
return <ProductCardList products={products} record={record} />;
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
maxWidth: '270px',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: '#d9d9d9 transparent',
|
||||
}}>
|
||||
<ProductCardList products={products} record={record} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
//财务信息
|
||||
@@ -759,7 +826,7 @@ const AfterSaleRecordPage = () => {
|
||||
sticky
|
||||
scroll={{
|
||||
y: `calc(100vh - 300px)`,
|
||||
//x: 'max-content'
|
||||
x: 'max-content' // 启用水平滚动,确保表格内容不会被挤压
|
||||
}}
|
||||
//pagination={false} // 禁用分页
|
||||
pagination={{
|
||||
|
||||
Reference in New Issue
Block a user