1
This commit is contained in:
Vendored
+49
-49
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-C1APw6hP.js"></script>
|
<script type="module" crossorigin src="/assets/index-DHcd1Fwz.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Table, Button, Tag, Space, Typography, message, Modal } from 'antd';
|
import { Table, Button, Tag, Space, Typography, message, Modal, Card, Popconfirm, Empty } from 'antd';
|
||||||
import { CheckOutlined, DeleteOutlined, EyeOutlined, FilterOutlined } from '@ant-design/icons';
|
import { CheckOutlined, DeleteOutlined, EyeOutlined, FilterOutlined, MessageOutlined } from '@ant-design/icons';
|
||||||
import { useAdminStore } from '../store';
|
import { useAdminStore } from '../store';
|
||||||
import { api } from '../api/client';
|
import { api } from '../api/client';
|
||||||
|
import { formatDate } from '../utils/formatDate';
|
||||||
|
|
||||||
interface ContactRequest {
|
interface ContactRequest {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -21,7 +22,7 @@ const AdminContactRequests: React.FC = () => {
|
|||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(20);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [isHandledFilter, setIsHandledFilter] = useState<boolean | null>(null);
|
const [isHandledFilter, setIsHandledFilter] = useState<boolean | null>(null);
|
||||||
const [selectedItem, setSelectedItem] = useState<ContactRequest | null>(null);
|
const [selectedItem, setSelectedItem] = useState<ContactRequest | null>(null);
|
||||||
const [detailModalOpen, setDetailModalOpen] = useState(false);
|
const [detailModalOpen, setDetailModalOpen] = useState(false);
|
||||||
@@ -63,19 +64,13 @@ const AdminContactRequests: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
Modal.confirm({
|
try {
|
||||||
title: '确认删除',
|
await api.delete(`/contact/requests/${id}`);
|
||||||
content: '确定要删除这条联系请求吗?',
|
message.success('已删除');
|
||||||
onOk: async () => {
|
fetchData();
|
||||||
try {
|
} catch (err: any) {
|
||||||
await api.delete(`/contact/requests/${id}`);
|
message.error(err?.message || '删除失败');
|
||||||
message.success('删除成功');
|
}
|
||||||
fetchData();
|
|
||||||
} catch (err: any) {
|
|
||||||
message.error(err?.message || '删除失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewDetail = (item: ContactRequest) => {
|
const handleViewDetail = (item: ContactRequest) => {
|
||||||
@@ -83,12 +78,18 @@ const AdminContactRequests: React.FC = () => {
|
|||||||
setDetailModalOpen(true);
|
setDetailModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (p: number, ps: number) => {
|
||||||
|
setPage(p);
|
||||||
|
setPageSize(ps);
|
||||||
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '姓名',
|
title: '姓名',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
width: 100,
|
width: 100,
|
||||||
|
render: (v: string) => <Typography.Text strong>{v}</Typography.Text>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '手机号',
|
title: '手机号',
|
||||||
@@ -125,95 +126,90 @@ const AdminContactRequests: React.FC = () => {
|
|||||||
dataIndex: 'created_at',
|
dataIndex: 'created_at',
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
width: 160,
|
width: 160,
|
||||||
render: (date: string) => {
|
render: (date: string) => formatDate(date),
|
||||||
return new Date(date).toLocaleString('zh-CN');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 180,
|
width: 180,
|
||||||
render: (_: unknown, record: ContactRequest) => (
|
render: (_: unknown, record: ContactRequest) => (
|
||||||
<Space size="middle">
|
<Space size={4}>
|
||||||
<Button
|
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => handleViewDetail(record)}>
|
||||||
type="text"
|
|
||||||
icon={<EyeOutlined />}
|
|
||||||
onClick={() => handleViewDetail(record)}
|
|
||||||
>
|
|
||||||
查看
|
查看
|
||||||
</Button>
|
</Button>
|
||||||
{!record.is_handled && (
|
{!record.is_handled && (
|
||||||
<Button
|
<Button type="link" size="small" icon={<CheckOutlined />} onClick={() => handleMarkHandled(record.id)}>
|
||||||
type="text"
|
|
||||||
icon={<CheckOutlined />}
|
|
||||||
onClick={() => handleMarkHandled(record.id)}
|
|
||||||
>
|
|
||||||
标记处理
|
标记处理
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Popconfirm title="确定删除该记录?" onConfirm={() => handleDelete(record.id)}>
|
||||||
type="text"
|
<Button type="link" danger size="small" icon={<DeleteOutlined />}>删除</Button>
|
||||||
danger
|
</Popconfirm>
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
onClick={() => handleDelete(record.id)}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 24 }}>
|
<div>
|
||||||
<Typography.Title level={2} style={{ marginBottom: 24 }}>
|
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||||||
联系请求管理
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
</Typography.Title>
|
<Space>
|
||||||
|
<MessageOutlined style={{ fontSize: 18, color: '#6366f1' }} />
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
<Typography.Text strong style={{ fontSize: 16 }}>联系请求管理</Typography.Text>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
<Tag color="purple">共 {total} 条记录</Tag>
|
||||||
<Button
|
</Space>
|
||||||
type={isHandledFilter === null ? 'primary' : 'default'}
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
onClick={() => setIsHandledFilter(null)}
|
<Button
|
||||||
icon={<FilterOutlined />}
|
type={isHandledFilter === null ? 'primary' : 'default'}
|
||||||
>
|
onClick={() => setIsHandledFilter(null)}
|
||||||
全部
|
icon={<FilterOutlined />}
|
||||||
</Button>
|
size="small"
|
||||||
<Button
|
>
|
||||||
type={isHandledFilter === false ? 'primary' : 'default'}
|
全部
|
||||||
onClick={() => setIsHandledFilter(false)}
|
</Button>
|
||||||
>
|
<Button
|
||||||
待处理
|
type={isHandledFilter === false ? 'primary' : 'default'}
|
||||||
</Button>
|
onClick={() => setIsHandledFilter(false)}
|
||||||
<Button
|
size="small"
|
||||||
type={isHandledFilter === true ? 'primary' : 'default'}
|
>
|
||||||
onClick={() => setIsHandledFilter(true)}
|
待处理
|
||||||
>
|
</Button>
|
||||||
已处理
|
<Button
|
||||||
</Button>
|
type={isHandledFilter === true ? 'primary' : 'default'}
|
||||||
|
onClick={() => setIsHandledFilter(true)}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
已处理
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Typography.Text style={{ color: '#64748b' }}>
|
|
||||||
共 {total} 条记录
|
|
||||||
</Typography.Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Table
|
{loading ? (
|
||||||
columns={columns}
|
<div style={{ textAlign: 'center', padding: 40 }}>加载中...</div>
|
||||||
dataSource={data}
|
) : data.length === 0 ? (
|
||||||
loading={loading}
|
<Empty description="暂无联系请求" style={{ padding: '40px 0' }} />
|
||||||
pagination={{
|
) : (
|
||||||
current: page,
|
<Table
|
||||||
pageSize,
|
columns={columns}
|
||||||
total,
|
dataSource={data}
|
||||||
onChange: (p, s) => { setPage(p); setPageSize(s); },
|
rowKey="id"
|
||||||
}}
|
loading={loading}
|
||||||
rowKey="id"
|
pagination={{
|
||||||
bordered={false}
|
current: page,
|
||||||
style={{ background: '#ffffff', borderRadius: 12 }}
|
pageSize: pageSize,
|
||||||
/>
|
total: total,
|
||||||
|
onChange: handlePageChange,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: (t) => `共 ${t} 条记录`,
|
||||||
|
}}
|
||||||
|
scroll={{ x: 900 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="联系请求详情"
|
title={<Space><EyeOutlined />联系请求详情</Space>}
|
||||||
open={detailModalOpen}
|
open={detailModalOpen}
|
||||||
onCancel={() => setDetailModalOpen(false)}
|
onCancel={() => setDetailModalOpen(false)}
|
||||||
footer={null}
|
footer={null}
|
||||||
@@ -236,7 +232,7 @@ const AdminContactRequests: React.FC = () => {
|
|||||||
<Typography.Text style={{ color: '#64748b' }}>行业:</Typography.Text>
|
<Typography.Text style={{ color: '#64748b' }}>行业:</Typography.Text>
|
||||||
<Typography.Text>{selectedItem.industry}</Typography.Text>
|
<Typography.Text>{selectedItem.industry}</Typography.Text>
|
||||||
<Typography.Text style={{ color: '#64748b' }}>提交时间:</Typography.Text>
|
<Typography.Text style={{ color: '#64748b' }}>提交时间:</Typography.Text>
|
||||||
<Typography.Text>{new Date(selectedItem.created_at).toLocaleString('zh-CN')}</Typography.Text>
|
<Typography.Text>{formatDate(selectedItem.created_at)}</Typography.Text>
|
||||||
{selectedItem.message && (
|
{selectedItem.message && (
|
||||||
<>
|
<>
|
||||||
<Typography.Text style={{ color: '#64748b' }}>留言:</Typography.Text>
|
<Typography.Text style={{ color: '#64748b' }}>留言:</Typography.Text>
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-Di-nQw13.js"></script>
|
<script type="module" crossorigin src="/assets/index-B5mkO6qz.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-eLRg4pQk.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-eLRg4pQk.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ const AppLayout: React.FC = () => {
|
|||||||
const [enabledMethods, setEnabledMethods] = useState<{ alipay: boolean; wechat: boolean }>({ alipay: false, wechat: false });
|
const [enabledMethods, setEnabledMethods] = useState<{ alipay: boolean; wechat: boolean }>({ alipay: false, wechat: false });
|
||||||
const [contactModalOpen, setContactModalOpen] = useState(false);
|
const [contactModalOpen, setContactModalOpen] = useState(false);
|
||||||
const [contactForm] = Form.useForm();
|
const [contactForm] = Form.useForm();
|
||||||
const [contactExpanded, setContactExpanded] = useState(true);
|
const [contactHovered, setContactHovered] = useState(false);
|
||||||
const [submittingContact, setSubmittingContact] = useState(false);
|
const [submittingContact, setSubmittingContact] = useState(false);
|
||||||
|
|
||||||
// LocalStorage keys
|
// LocalStorage keys
|
||||||
@@ -1009,61 +1009,32 @@ const AppLayout: React.FC = () => {
|
|||||||
<div style={{
|
<div style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
right: 24,
|
right: 24,
|
||||||
bottom: contactExpanded ? 100 : 24,
|
bottom: 24,
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
transition: 'bottom 0.3s ease',
|
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
position: 'relative',
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-end',
|
|
||||||
gap: 12,
|
|
||||||
}}>
|
}}>
|
||||||
{contactExpanded && (
|
<div
|
||||||
<div style={{
|
style={{
|
||||||
padding: '16px 20px',
|
position: 'absolute',
|
||||||
background: '#ffffff',
|
right: 64,
|
||||||
borderRadius: 16,
|
bottom: 8,
|
||||||
boxShadow: '0 8px 32px rgba(0,0,0,0.12)',
|
padding: '8px 16px',
|
||||||
minWidth: 280,
|
background: '#1e293b',
|
||||||
animation: 'slideUp 0.3s ease',
|
color: '#ffffff',
|
||||||
}}>
|
borderRadius: 8,
|
||||||
<div style={{
|
fontSize: 13,
|
||||||
color: '#1e293b',
|
whiteSpace: 'nowrap',
|
||||||
fontSize: 16,
|
opacity: contactHovered ? 1 : 0,
|
||||||
fontWeight: 600,
|
transition: 'opacity 0.2s ease',
|
||||||
marginBottom: 12,
|
pointerEvents: 'none',
|
||||||
display: 'flex',
|
}}
|
||||||
alignItems: 'center',
|
>
|
||||||
gap: 8,
|
联系我们
|
||||||
}}>
|
</div>
|
||||||
<MessageOutlined style={{ color: '#6366f1' }} />
|
|
||||||
联系我们
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
padding: '12px 16px',
|
|
||||||
background: 'rgba(99, 102, 241, 0.06)',
|
|
||||||
borderRadius: 12,
|
|
||||||
marginBottom: 12,
|
|
||||||
}}>
|
|
||||||
<div style={{ color: '#64748b', fontSize: 13, marginBottom: 4 }}>商务咨询</div>
|
|
||||||
<div style={{ color: '#1e293b', fontSize: 14, fontWeight: 600 }}>400-888-8888</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
block
|
|
||||||
onClick={() => setContactModalOpen(true)}
|
|
||||||
style={{
|
|
||||||
borderRadius: 10,
|
|
||||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
||||||
border: 'none',
|
|
||||||
}}>
|
|
||||||
在线咨询
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setContactExpanded(!contactExpanded)}
|
onClick={() => setContactModalOpen(true)}
|
||||||
style={{
|
style={{
|
||||||
width: 56,
|
width: 56,
|
||||||
height: 56,
|
height: 56,
|
||||||
@@ -1081,17 +1052,15 @@ const AppLayout: React.FC = () => {
|
|||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.transform = 'scale(1.05)';
|
e.currentTarget.style.transform = 'scale(1.05)';
|
||||||
e.currentTarget.style.boxShadow = '0 6px 24px rgba(99, 102, 241, 0.5)';
|
e.currentTarget.style.boxShadow = '0 6px 24px rgba(99, 102, 241, 0.5)';
|
||||||
|
setContactHovered(true);
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.transform = 'scale(1)';
|
e.currentTarget.style.transform = 'scale(1)';
|
||||||
e.currentTarget.style.boxShadow = '0 4px 20px rgba(99, 102, 241, 0.4)';
|
e.currentTarget.style.boxShadow = '0 4px 20px rgba(99, 102, 241, 0.4)';
|
||||||
|
setContactHovered(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{contactExpanded ? (
|
<MessageOutlined style={{ fontSize: 20 }} />
|
||||||
<DownOutlined style={{ fontSize: 20 }} />
|
|
||||||
) : (
|
|
||||||
<MessageOutlined style={{ fontSize: 20 }} />
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user