新增跟進提醒和待辦事項模態框功能:在聊天窗口中添加相應的狀態管理和事件處理,提升用戶互動體驗。
This commit is contained in:
@@ -0,0 +1,224 @@
|
|||||||
|
// 跟进提醒模态框样式
|
||||||
|
.followupModal {
|
||||||
|
:global(.ant-modal-header) {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 16px 20px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-modal-body) {
|
||||||
|
padding: 0 20px 20px 20px;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-modal-close) {
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalHeader {
|
||||||
|
.modalTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #262626;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalSubtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
margin: 2px 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.addReminderSection {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.reminderForm {
|
||||||
|
.formRow {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.formItem {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
:global(.ant-form-item-label) {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #262626;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectInput,
|
||||||
|
.dateInput,
|
||||||
|
.contentInput {
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentInput {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: #1890ff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
border-color: #40a9ff;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.remindersList {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 200px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
:global(.ant-list) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-list-item) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reminderItem {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.reminderContent {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.reminderHeader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
.typeTag {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipient {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #595959;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reminderBody {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
.reminderText {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #262626;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reminderFooter {
|
||||||
|
.clockIcon {
|
||||||
|
color: #8c8c8c;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scheduledTime {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.followupModal {
|
||||||
|
:global(.ant-modal) {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 100vw;
|
||||||
|
top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-modal-body) {
|
||||||
|
padding: 0 16px 16px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalContent {
|
||||||
|
.addReminderSection {
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.reminderForm {
|
||||||
|
.formRow {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.remindersList {
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Form,
|
||||||
|
Select,
|
||||||
|
DatePicker,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
List,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
} from "antd";
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
ClockCircleOutlined,
|
||||||
|
PhoneOutlined,
|
||||||
|
MessageOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
interface FollowupReminder {
|
||||||
|
id: string;
|
||||||
|
type: "电话" | "消息";
|
||||||
|
status: "待处理" | "已完成" | "已取消";
|
||||||
|
content: string;
|
||||||
|
scheduledTime: string;
|
||||||
|
recipient: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FollowupReminderModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
recipientName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FollowupReminderModal: React.FC<FollowupReminderModalProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
recipientName = "客户",
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [reminders, setReminders] = useState<FollowupReminder[]>([
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
type: "电话",
|
||||||
|
status: "待处理",
|
||||||
|
content: "周三14点回访",
|
||||||
|
scheduledTime: "2024/3/6 14:00:00",
|
||||||
|
recipient: "李先生",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
type: "消息",
|
||||||
|
status: "待处理",
|
||||||
|
content: "发送产品演示视频",
|
||||||
|
scheduledTime: "2024/3/7 09:00:00",
|
||||||
|
recipient: "张总",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
type: "消息",
|
||||||
|
status: "待处理",
|
||||||
|
content: "发送产品演示视频",
|
||||||
|
scheduledTime: "2024/3/7 09:00:00",
|
||||||
|
recipient: "张总",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4",
|
||||||
|
type: "消息",
|
||||||
|
status: "待处理",
|
||||||
|
content: "发送产品演示视频",
|
||||||
|
scheduledTime: "2024/3/7 09:00:00",
|
||||||
|
recipient: "张总",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 跟进方式选项
|
||||||
|
const followupMethods = [
|
||||||
|
{ value: "电话回访", label: "电话回访" },
|
||||||
|
{ value: "微信消息", label: "微信消息" },
|
||||||
|
{ value: "邮件", label: "邮件" },
|
||||||
|
{ value: "短信", label: "短信" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 处理添加提醒
|
||||||
|
const handleAddReminder = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
const newReminder: FollowupReminder = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
type: values.method === "电话回访" ? "电话" : "消息",
|
||||||
|
status: "待处理",
|
||||||
|
content: values.content,
|
||||||
|
scheduledTime: values.dateTime.format("YYYY/M/D HH:mm:ss"),
|
||||||
|
recipient: recipientName,
|
||||||
|
};
|
||||||
|
|
||||||
|
setReminders([...reminders, newReminder]);
|
||||||
|
form.resetFields();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("表单验证失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态标签颜色
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case "待处理":
|
||||||
|
return "warning";
|
||||||
|
case "已完成":
|
||||||
|
return "success";
|
||||||
|
case "已取消":
|
||||||
|
return "default";
|
||||||
|
default:
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取类型图标
|
||||||
|
const getTypeIcon = (type: string) => {
|
||||||
|
return type === "电话" ? <PhoneOutlined /> : <MessageOutlined />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<div className={styles.modalHeader}>
|
||||||
|
<div className={styles.modalTitle}>跟进提醒设置</div>
|
||||||
|
<div className={styles.modalSubtitle}>设置客户跟进时间和方式</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
open={visible}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={null}
|
||||||
|
width={480}
|
||||||
|
className={styles.followupModal}
|
||||||
|
>
|
||||||
|
<div className={styles.modalContent}>
|
||||||
|
{/* 添加新提醒区域 */}
|
||||||
|
<div className={styles.addReminderSection}>
|
||||||
|
<Form form={form} layout="vertical" className={styles.reminderForm}>
|
||||||
|
<div className={styles.formRow}>
|
||||||
|
<Form.Item
|
||||||
|
name="method"
|
||||||
|
label="跟进方式"
|
||||||
|
rules={[{ required: true, message: "请选择跟进方式" }]}
|
||||||
|
className={styles.formItem}
|
||||||
|
>
|
||||||
|
<Select placeholder="电话回访" className={styles.selectInput}>
|
||||||
|
{followupMethods.map(method => (
|
||||||
|
<Option key={method.value} value={method.value}>
|
||||||
|
{method.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="dateTime"
|
||||||
|
label="提醒时间"
|
||||||
|
rules={[{ required: true, message: "请选择提醒时间" }]}
|
||||||
|
className={styles.formItem}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
format="YYYY/M/D HH:mm"
|
||||||
|
placeholder="年/月/日 --:--"
|
||||||
|
className={styles.dateInput}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="content"
|
||||||
|
label="提醒内容"
|
||||||
|
rules={[{ required: true, message: "请输入提醒内容" }]}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
placeholder="提醒内容..."
|
||||||
|
rows={3}
|
||||||
|
className={styles.contentInput}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAddReminder}
|
||||||
|
className={styles.addButton}
|
||||||
|
block
|
||||||
|
>
|
||||||
|
添加提醒
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 现有提醒列表 */}
|
||||||
|
<div className={styles.remindersList}>
|
||||||
|
<List
|
||||||
|
dataSource={reminders}
|
||||||
|
renderItem={reminder => (
|
||||||
|
<List.Item className={styles.reminderItem}>
|
||||||
|
<div className={styles.reminderContent}>
|
||||||
|
<div className={styles.reminderHeader}>
|
||||||
|
<Space>
|
||||||
|
<Tag
|
||||||
|
icon={getTypeIcon(reminder.type)}
|
||||||
|
color="blue"
|
||||||
|
className={styles.typeTag}
|
||||||
|
>
|
||||||
|
{reminder.type}
|
||||||
|
</Tag>
|
||||||
|
<Tag color={getStatusColor(reminder.status)}>
|
||||||
|
{reminder.status}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
<Text className={styles.recipient}>
|
||||||
|
{reminder.recipient}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.reminderBody}>
|
||||||
|
<Text className={styles.reminderText}>
|
||||||
|
{reminder.content}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.reminderFooter}>
|
||||||
|
<Space>
|
||||||
|
<ClockCircleOutlined className={styles.clockIcon} />
|
||||||
|
<Text className={styles.scheduledTime}>
|
||||||
|
{reminder.scheduledTime}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FollowupReminderModal;
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
// 待办事项模态框样式
|
||||||
|
.todoModal {
|
||||||
|
:global(.ant-modal-header) {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 16px 20px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-modal-body) {
|
||||||
|
padding: 0 20px 20px 20px;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-modal-close) {
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalHeader {
|
||||||
|
.modalTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #262626;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalSubtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
margin: 2px 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.addTaskSection {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.taskForm {
|
||||||
|
.titleInput,
|
||||||
|
.descriptionInput,
|
||||||
|
.prioritySelect,
|
||||||
|
.dateInput {
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.descriptionInput {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formRow {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.formItem {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
:global(.ant-form-item-label) {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #262626;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: #1890ff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
border-color: #40a9ff;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoList {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 200px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
// 自定义滚动条样式
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-list) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-list-item) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoItem {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.todoContent {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.todoHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
.todoCheckbox {
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
|
:global(.ant-checkbox-inner) {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoTitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #262626;
|
||||||
|
font-weight: 500;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&.completed {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoDescription {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.descriptionText {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #595959;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoFooter {
|
||||||
|
.clientInfo {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priorityTag {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dueDate {
|
||||||
|
.calendarIcon {
|
||||||
|
color: #8c8c8c;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dueDateText {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.todoModal {
|
||||||
|
:global(.ant-modal) {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 100vw;
|
||||||
|
top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-modal-body) {
|
||||||
|
padding: 0 16px 16px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalContent {
|
||||||
|
.addTaskSection {
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.taskForm {
|
||||||
|
.formRow {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoList {
|
||||||
|
max-height: 150px;
|
||||||
|
|
||||||
|
// 移动端滚动条样式
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
DatePicker,
|
||||||
|
Button,
|
||||||
|
List,
|
||||||
|
Checkbox,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
} from "antd";
|
||||||
|
import { PlusOutlined, CalendarOutlined } from "@ant-design/icons";
|
||||||
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
interface TodoItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
client?: string;
|
||||||
|
priority: "高" | "中" | "低";
|
||||||
|
dueDate: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TodoListModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
clientName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TodoListModal: React.FC<TodoListModalProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
clientName = "客户",
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [todos, setTodos] = useState<TodoItem[]>([
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
title: "整理客户需求文档",
|
||||||
|
description: "汇总本周客户反馈的功能需求",
|
||||||
|
client: "李先生",
|
||||||
|
priority: "高",
|
||||||
|
dueDate: "03/08 18:00",
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
title: "准备产品演示PPT",
|
||||||
|
description: "针对大客户的定制化演示材料",
|
||||||
|
client: "张总",
|
||||||
|
priority: "中",
|
||||||
|
dueDate: "03/09 16:00",
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
title: "准备产品演示PPT",
|
||||||
|
description: "针对大客户的定制化演示材料",
|
||||||
|
client: "张总",
|
||||||
|
priority: "中",
|
||||||
|
dueDate: "03/09 16:00",
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 优先级选项
|
||||||
|
const priorityOptions = [
|
||||||
|
{ value: "高", label: "高优先级", color: "orange" },
|
||||||
|
{ value: "中", label: "中优先级", color: "blue" },
|
||||||
|
{ value: "低", label: "低优先级", color: "green" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 处理添加任务
|
||||||
|
const handleAddTask = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
const newTodo: TodoItem = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
title: values.title,
|
||||||
|
description: values.description,
|
||||||
|
client: clientName,
|
||||||
|
priority: values.priority,
|
||||||
|
dueDate: values.dueDate.format("MM/DD HH:mm"),
|
||||||
|
completed: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
setTodos([...todos, newTodo]);
|
||||||
|
form.resetFields();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("表单验证失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理任务完成状态切换
|
||||||
|
const handleToggleComplete = (id: string) => {
|
||||||
|
setTodos(
|
||||||
|
todos.map(todo =>
|
||||||
|
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取优先级标签颜色
|
||||||
|
const getPriorityColor = (priority: string) => {
|
||||||
|
const option = priorityOptions.find(opt => opt.value === priority);
|
||||||
|
return option?.color || "default";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<div className={styles.modalHeader}>
|
||||||
|
<div className={styles.modalTitle}>待办事项清单</div>
|
||||||
|
<div className={styles.modalSubtitle}>管理日常工作任务</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
open={visible}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={null}
|
||||||
|
width={480}
|
||||||
|
className={styles.todoModal}
|
||||||
|
>
|
||||||
|
<div className={styles.modalContent}>
|
||||||
|
{/* 添加新任务区域 */}
|
||||||
|
<div className={styles.addTaskSection}>
|
||||||
|
<Form form={form} layout="vertical" className={styles.taskForm}>
|
||||||
|
<Form.Item
|
||||||
|
name="title"
|
||||||
|
rules={[{ required: true, message: "请输入任务标题" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="任务标题..." className={styles.titleInput} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="description">
|
||||||
|
<TextArea
|
||||||
|
placeholder="任务描述 (可选)..."
|
||||||
|
rows={2}
|
||||||
|
className={styles.descriptionInput}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<div className={styles.formRow}>
|
||||||
|
<Form.Item
|
||||||
|
name="priority"
|
||||||
|
rules={[{ required: true, message: "请选择优先级" }]}
|
||||||
|
className={styles.formItem}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="中优先级"
|
||||||
|
className={styles.prioritySelect}
|
||||||
|
>
|
||||||
|
{priorityOptions.map(option => (
|
||||||
|
<Option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="dueDate"
|
||||||
|
rules={[{ required: true, message: "请选择截止时间" }]}
|
||||||
|
className={styles.formItem}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
format="MM/DD HH:mm"
|
||||||
|
placeholder="年/月/日 --:--"
|
||||||
|
className={styles.dateInput}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAddTask}
|
||||||
|
className={styles.addButton}
|
||||||
|
block
|
||||||
|
>
|
||||||
|
添加任务
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 任务列表 */}
|
||||||
|
<div className={styles.todoList}>
|
||||||
|
<List
|
||||||
|
dataSource={todos}
|
||||||
|
renderItem={todo => (
|
||||||
|
<List.Item className={styles.todoItem}>
|
||||||
|
<div className={styles.todoContent}>
|
||||||
|
<div className={styles.todoHeader}>
|
||||||
|
<Checkbox
|
||||||
|
checked={todo.completed}
|
||||||
|
onChange={() => handleToggleComplete(todo.id)}
|
||||||
|
className={styles.todoCheckbox}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className={`${styles.todoTitle} ${todo.completed ? styles.completed : ""}`}
|
||||||
|
>
|
||||||
|
{todo.title}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{todo.description && (
|
||||||
|
<div className={styles.todoDescription}>
|
||||||
|
<Text className={styles.descriptionText}>
|
||||||
|
{todo.description}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.todoFooter}>
|
||||||
|
<Space>
|
||||||
|
<Text className={styles.clientInfo}>
|
||||||
|
客户:{todo.client}
|
||||||
|
</Text>
|
||||||
|
<Tag
|
||||||
|
color={getPriorityColor(todo.priority)}
|
||||||
|
className={styles.priorityTag}
|
||||||
|
>
|
||||||
|
{todo.priority}
|
||||||
|
</Tag>
|
||||||
|
<Space className={styles.dueDate}>
|
||||||
|
<CalendarOutlined className={styles.calendarIcon} />
|
||||||
|
<Text className={styles.dueDateText}>
|
||||||
|
{todo.dueDate}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoListModal;
|
||||||
@@ -14,6 +14,8 @@ import styles from "./ChatWindow.module.scss";
|
|||||||
import ProfileCard from "./components/ProfileCard";
|
import ProfileCard from "./components/ProfileCard";
|
||||||
import MessageEnter from "./components/MessageEnter";
|
import MessageEnter from "./components/MessageEnter";
|
||||||
import MessageRecord from "./components/MessageRecord";
|
import MessageRecord from "./components/MessageRecord";
|
||||||
|
import FollowupReminderModal from "./components/FollowupReminderModal";
|
||||||
|
import TodoListModal from "./components/TodoListModal";
|
||||||
import { setFriendInjectConfig } from "@/pages/pc/ckbox/weChat/api";
|
import { setFriendInjectConfig } from "@/pages/pc/ckbox/weChat/api";
|
||||||
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
||||||
const { Header, Content } = Layout;
|
const { Header, Content } = Layout;
|
||||||
@@ -22,6 +24,12 @@ interface ChatWindowProps {
|
|||||||
contract: ContractData | weChatGroup;
|
contract: ContractData | weChatGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typeOptions = [
|
||||||
|
{ value: 0, label: "人工接待" },
|
||||||
|
{ value: 1, label: "AI辅助" },
|
||||||
|
{ value: 2, label: "AI接管" },
|
||||||
|
];
|
||||||
|
|
||||||
const ChatWindow: React.FC<ChatWindowProps> = ({ contract }) => {
|
const ChatWindow: React.FC<ChatWindowProps> = ({ contract }) => {
|
||||||
const updateAiQuoteMessageContent = useWeChatStore(
|
const updateAiQuoteMessageContent = useWeChatStore(
|
||||||
state => state.updateAiQuoteMessageContent,
|
state => state.updateAiQuoteMessageContent,
|
||||||
@@ -30,15 +38,28 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ contract }) => {
|
|||||||
state => state.aiQuoteMessageContent,
|
state => state.aiQuoteMessageContent,
|
||||||
);
|
);
|
||||||
const [showProfile, setShowProfile] = useState(true);
|
const [showProfile, setShowProfile] = useState(true);
|
||||||
|
const [followupModalVisible, setFollowupModalVisible] = useState(false);
|
||||||
|
const [todoModalVisible, setTodoModalVisible] = useState(false);
|
||||||
|
|
||||||
const onToggleProfile = () => {
|
const onToggleProfile = () => {
|
||||||
setShowProfile(!showProfile);
|
setShowProfile(!showProfile);
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeOptions = [
|
const handleFollowupClick = () => {
|
||||||
{ value: 0, label: "人工接待" },
|
setFollowupModalVisible(true);
|
||||||
{ value: 1, label: "AI辅助" },
|
};
|
||||||
{ value: 2, label: "AI接管" },
|
|
||||||
];
|
const handleFollowupModalClose = () => {
|
||||||
|
setFollowupModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTodoClick = () => {
|
||||||
|
setTodoModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTodoModalClose = () => {
|
||||||
|
setTodoModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
const [currentConfig, setCurrentConfig] = useState(
|
const [currentConfig, setCurrentConfig] = useState(
|
||||||
typeOptions.find(option => option.value === aiQuoteMessageContent),
|
typeOptions.find(option => option.value === aiQuoteMessageContent),
|
||||||
@@ -115,8 +136,12 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ contract }) => {
|
|||||||
</Space>
|
</Space>
|
||||||
</Header>
|
</Header>
|
||||||
<div className={styles.extend}>
|
<div className={styles.extend}>
|
||||||
<Button icon={<BellOutlined />}>跟进提醒</Button>
|
<Button icon={<BellOutlined />} onClick={handleFollowupClick}>
|
||||||
<Button icon={<CheckSquareOutlined />}>待办事项</Button>
|
跟进提醒
|
||||||
|
</Button>
|
||||||
|
<Button icon={<CheckSquareOutlined />} onClick={handleTodoClick}>
|
||||||
|
待办事项
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 聊天内容 */}
|
{/* 聊天内容 */}
|
||||||
@@ -130,6 +155,20 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ contract }) => {
|
|||||||
|
|
||||||
{/* 右侧个人资料卡片 */}
|
{/* 右侧个人资料卡片 */}
|
||||||
{showProfile && <ProfileCard contract={contract} />}
|
{showProfile && <ProfileCard contract={contract} />}
|
||||||
|
|
||||||
|
{/* 跟进提醒模态框 */}
|
||||||
|
<FollowupReminderModal
|
||||||
|
visible={followupModalVisible}
|
||||||
|
onClose={handleFollowupModalClose}
|
||||||
|
recipientName={contract.nickname || contract.name}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 待办事项模态框 */}
|
||||||
|
<TodoListModal
|
||||||
|
visible={todoModalVisible}
|
||||||
|
onClose={handleTodoModalClose}
|
||||||
|
clientName={contract.nickname || contract.name}
|
||||||
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user