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