Przeglądaj źródła

🔥 feat(admin): 移除销售相关功能模块

- 移除菜单中的"销售机会"和"跟进记录"选项
- 删除客户管理页面(Customers.tsx)
- 删除销售机会管理页面(Opportunities.tsx)
- 删除跟进记录管理页面(FollowUps.tsx)
- 从路由配置中移除相关页面路由

🔧 chore(admin): 清理销售功能相关代码引用

- 移除路由配置中对销售相关页面的引用
- 清理菜单配置中已注释的销售相关代码
yourname 8 miesięcy temu
rodzic
commit
54bbec5a53

+ 0 - 14
src/client/admin/menu.tsx

@@ -106,20 +106,6 @@ export const useMenu = () => {
           path: '/admin/clients',
           permission: 'client:manage'
         },
-        // {
-        //   key: 'opportunities',
-        //   label: '销售机会',
-        //   icon: <DollarOutlined />,
-        //   path: '/admin/opportunities',
-        //   permission: 'opportunity:manage'
-        // },
-        // {
-        //   key: 'follow-ups',
-        //   label: '跟进记录',
-        //   icon: <CalendarOutlined />,
-        //   path: '/admin/follow-ups',
-        //   permission: 'followUp:manage'
-        // }
       ]
     },
     {

+ 0 - 309
src/client/admin/pages/Customers.tsx

@@ -1,309 +0,0 @@
-import React, { useState } from 'react';
-import {
-  Button, Table, Space, Form, Input, Modal, Card, Typography, Popconfirm,
-  App
-} from 'antd';
-import { useQuery } from '@tanstack/react-query';
-import dayjs from 'dayjs';
-import { customerClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
-
-type CustomerListResponse = InferResponseType<typeof customerClient.$get, 200>;
-type CustomerDetailResponse = InferResponseType<typeof customerClient[':id']['$get'], 200>;
-type CreateCustomerRequest = InferRequestType<typeof customerClient.$post>['json'];
-type UpdateCustomerRequest = InferRequestType<typeof customerClient[':id']['$put']>['json'];
-
-const { Title } = Typography;
-
-// 客户管理页面
-export const CustomersPage = () => {
-  const { message } = App.useApp();
-  const [searchParams, setSearchParams] = useState({
-    page: 1,
-    limit: 10,
-    keyword: ''
-  });
-  const [modalVisible, setModalVisible] = useState(false);
-  const [modalTitle, setModalTitle] = useState('');
-  const [editingCustomer, setEditingCustomer] = useState<any>(null);
-  const [form] = Form.useForm();
-
-  const { data: customersData, isLoading, refetch } = useQuery({
-    queryKey: ['customers', searchParams],
-    queryFn: async () => {
-      const res = await customerClient.$get({
-        query: {
-          page: searchParams.page,
-          pageSize: searchParams.limit,
-          keyword: searchParams.keyword
-        }
-      });
-      if (res.status !== 200) {
-        throw new Error('获取客户列表失败');
-      }
-      return await res.json();
-    }
-  });
-
-  const customers = customersData?.data || [];
-  const pagination = {
-    current: searchParams.page,
-    pageSize: searchParams.limit,
-    total: customersData?.pagination?.total || 0
-  };
-
-  // 处理搜索
-  const handleSearch = (values: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      keyword: values.keyword || '',
-      page: 1
-    }));
-  };
-
-  // 处理分页变化
-  const handleTableChange = (newPagination: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      page: newPagination.current,
-      limit: newPagination.pageSize
-    }));
-  };
-
-  // 打开创建客户模态框
-  const showCreateModal = () => {
-    setModalTitle('创建客户');
-    setEditingCustomer(null);
-    form.resetFields();
-    setModalVisible(true);
-  };
-
-  // 打开编辑客户模态框
-  const showEditModal = (customer: any) => {
-    setModalTitle('编辑客户');
-    setEditingCustomer(customer);
-    form.setFieldsValue(customer);
-    setModalVisible(true);
-  };
-
-  // 处理模态框确认
-  const handleModalOk = async () => {
-    try {
-      const values = await form.validateFields();
-      
-      if (editingCustomer) {
-        // 编辑客户
-        const res = await customerClient[':id']['$put']({
-          param: { id: editingCustomer.id },
-          json: values
-        });
-        if (res.status !== 200) {
-          throw new Error('更新客户失败');
-        }
-        message.success('客户更新成功');
-      } else {
-        // 创建客户
-        const res = await customerClient.$post({
-          json: values
-        });
-        if (res.status !== 201) {
-          throw new Error('创建客户失败');
-        }
-        message.success('客户创建成功');
-      }
-      
-      setModalVisible(false);
-      form.resetFields();
-      refetch(); // 刷新客户列表
-    } catch (error) {
-      console.error('表单提交失败:', error);
-      message.error('操作失败,请重试');
-    }
-  };
-
-  // 处理删除客户
-  const handleDelete = async (id: number) => {
-    try {
-      const res = await customerClient[':id']['$delete']({
-        param: { id }
-      });
-      if (res.status !== 204) {
-        throw new Error('删除客户失败');
-      }
-      message.success('客户删除成功');
-      refetch(); // 刷新客户列表
-    } catch (error) {
-      console.error('删除客户失败:', error);
-      message.error('删除失败,请重试');
-    }
-  };
-  
-  const columns = [
-    {
-      title: '客户姓名',
-      dataIndex: 'name',
-      key: 'name',
-    },
-    {
-      title: '联系电话',
-      dataIndex: 'phone',
-      key: 'phone',
-    },
-    {
-      title: '电子邮箱',
-      dataIndex: 'email',
-      key: 'email',
-    },
-    {
-      title: '公司名称',
-      dataIndex: 'company',
-      key: 'company',
-    },
-    {
-      title: '客户来源',
-      dataIndex: 'source',
-      key: 'source',
-    },
-    {
-      title: '创建时间',
-      dataIndex: 'createdAt',
-      key: 'createdAt',
-      render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
-    },
-    {
-      title: '操作',
-      key: 'action',
-      render: (_: any, record: any) => (
-        <Space size="middle">
-          <Button type="link" onClick={() => showEditModal(record)}>
-            编辑
-          </Button>
-          <Popconfirm
-            title="确定要删除此客户吗?"
-            onConfirm={() => handleDelete(record.id)}
-            okText="确定"
-            cancelText="取消"
-          >
-            <Button type="link" danger>
-              删除
-            </Button>
-          </Popconfirm>
-        </Space>
-      ),
-    },
-  ];
-  
-  return (
-    <div>
-      <div className="mb-6 flex justify-between items-center">
-        <Title level={2}>客户管理</Title>
-      </div>
-      <Card className="shadow-md transition-all duration-300 hover:shadow-lg">
-        <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16, padding: '16px 0' }}>
-          <Form.Item name="keyword" label="搜索">
-            <Input placeholder="客户姓名/公司/电话" allowClear />
-          </Form.Item>
-          <Form.Item>
-            <Space>
-              <Button type="primary" htmlType="submit">
-                搜索
-              </Button>
-              <Button type="primary" onClick={showCreateModal}>
-                创建客户
-              </Button>
-            </Space>
-          </Form.Item>
-        </Form>
-
-        <Table
-          columns={columns}
-          dataSource={customers}
-          loading={isLoading}
-          rowKey="id"
-          pagination={{
-            ...pagination,
-            showSizeChanger: true,
-            showQuickJumper: true,
-            showTotal: (total) => `共 ${total} 条记录`
-          }}
-          onChange={handleTableChange}
-          bordered
-          scroll={{ x: 'max-content' }}
-          rowClassName={(record, index) => index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}
-        />
-      </Card>
-
-      {/* 创建/编辑客户模态框 */}
-      <Modal
-        title={modalTitle}
-        open={modalVisible}
-        onOk={handleModalOk}
-        onCancel={() => {
-          setModalVisible(false);
-          form.resetFields();
-        }}
-        width={600}
-        centered
-        destroyOnClose
-        maskClosable={false}
-      >
-        <Form
-          form={form}
-          layout="vertical"
-          labelCol={{ span: 5 }}
-          wrapperCol={{ span: 19 }}
-        >
-          <Form.Item
-            name="name"
-            label="客户姓名"
-            required
-            rules={[
-              { required: true, message: '请输入客户姓名' },
-              { min: 2, message: '客户姓名至少2个字符' }
-            ]}
-          >
-            <Input placeholder="请输入客户姓名" />
-          </Form.Item>
-
-          <Form.Item
-            name="phone"
-            label="联系电话"
-            rules={[
-              { required: false, message: '请输入联系电话' },
-              { pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号' }
-            ]}
-          >
-            <Input placeholder="请输入联系电话" />
-          </Form.Item>
-
-          <Form.Item
-            name="email"
-            label="电子邮箱"
-            rules={[
-              { required: false, message: '请输入电子邮箱' },
-              { type: 'email', message: '请输入有效的邮箱地址' }
-            ]}
-          >
-            <Input placeholder="请输入电子邮箱" />
-          </Form.Item>
-
-          <Form.Item
-            name="company"
-            label="公司名称"
-            rules={[{ required: false, message: '请输入公司名称' }]}
-          >
-            <Input placeholder="请输入公司名称" />
-          </Form.Item>
-
-          <Form.Item
-            name="source"
-            label="客户来源"
-            rules={[{ required: false, message: '请输入客户来源' }]}
-          >
-            <Input placeholder="请输入客户来源(如:网站、推荐、广告等)" />
-          </Form.Item>
-        </Form>
-      </Modal>
-    </div>
-  );
-};

+ 0 - 381
src/client/admin/pages/FollowUps.tsx

@@ -1,381 +0,0 @@
-import React, { useState } from 'react';
-import {
-  Button, Table, Space, Form, Input, Select, Modal, Card, Typography, Popconfirm,
-  App, DatePicker,
-  Tag
-} from 'antd';
-import { useQuery } from '@tanstack/react-query';
-import dayjs from 'dayjs';
-import { followUpClient, opportunityClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
-import { FollowUpMethod } from '@/server/modules/follow-ups/follow-up.entity';
-
-type FollowUpListResponse = InferResponseType<typeof followUpClient.$get, 200>;
-type FollowUpDetailResponse = InferResponseType<typeof followUpClient[':id']['$get'], 200>;
-type CreateFollowUpRequest = InferRequestType<typeof followUpClient.$post>['json'];
-type UpdateFollowUpRequest = InferRequestType<typeof followUpClient[':id']['$put']>['json'];
-type OpportunityListResponse = InferResponseType<typeof opportunityClient.$get, 200>;
-
-const { Title } = Typography;
-const { TextArea } = Input;
-
-// 跟进记录管理页面
-export const FollowUpsPage = () => {
-  const { message } = App.useApp();
-  const [searchParams, setSearchParams] = useState({
-    page: 1,
-    limit: 10,
-    keyword: '',
-    method: ''
-  });
-  const [modalVisible, setModalVisible] = useState(false);
-  const [modalTitle, setModalTitle] = useState('');
-  const [editingFollowUp, setEditingFollowUp] = useState<any>(null);
-  const [form] = Form.useForm();
-  const [opportunities, setOpportunities] = useState<any[]>([]);
-
-  // 获取销售机会列表
-  const { data: opportunitiesData } = useQuery({
-    queryKey: ['allOpportunities'],
-    queryFn: async () => {
-      const res = await opportunityClient.$get({
-        query: {
-          page: 1,
-          pageSize: 1000
-        }
-      });
-      if (res.status !== 200) {
-        throw new Error('获取销售机会列表失败');
-      }
-      const data = await res.json();
-      return data.data || [];
-    }
-  });
-
-  React.useEffect(() => {
-    if (opportunitiesData) {
-      setOpportunities(opportunitiesData);
-    }
-  }, [opportunitiesData]);
-
-  // 获取跟进记录列表
-  const { data: followUpsData, isLoading, refetch } = useQuery({
-    queryKey: ['followUps', searchParams],
-    queryFn: async () => {
-      const res = await followUpClient.$get({
-        query: {
-          page: searchParams.page,
-          pageSize: searchParams.limit,
-          keyword: searchParams.keyword
-        }
-      });
-      if (res.status !== 200) {
-        throw new Error('获取跟进记录列表失败');
-      }
-      return await res.json();
-    }
-  });
-
-  const followUps = followUpsData?.data || [];
-  const pagination = {
-    current: searchParams.page,
-    pageSize: searchParams.limit,
-    total: followUpsData?.pagination?.total || 0
-  };
-
-  // 处理搜索
-  const handleSearch = (values: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      keyword: values.keyword || '',
-      method: values.method || '',
-      page: 1
-    }));
-  };
-
-  // 处理分页变化
-  const handleTableChange = (newPagination: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      page: newPagination.current,
-      limit: newPagination.pageSize
-    }));
-  };
-
-  // 打开创建跟进记录模态框
-  const showCreateModal = () => {
-    setModalTitle('创建跟进记录');
-    setEditingFollowUp(null);
-    form.resetFields();
-    setModalVisible(true);
-  };
-
-  // 打开编辑跟进记录模态框
-  const showEditModal = (followUp: any) => {
-    setModalTitle('编辑跟进记录');
-    setEditingFollowUp(followUp);
-    form.setFieldsValue({
-      ...followUp,
-      nextFollowUpDate: followUp.nextFollowUpDate ? dayjs(followUp.nextFollowUpDate) : null
-    });
-    setModalVisible(true);
-  };
-
-  // 处理模态框确认
-  const handleModalOk = async () => {
-    try {
-      const values = await form.validateFields();
-      
-      // 格式化日期
-      if (values.nextFollowUpDate) {
-        values.nextFollowUpDate = dayjs(values.nextFollowUpDate).format('YYYY-MM-DD');
-      }
-      
-      if (editingFollowUp) {
-        // 编辑跟进记录
-        const res = await followUpClient[':id']['$put']({
-          param: { id: editingFollowUp.id },
-          json: values
-        });
-        if (res.status !== 200) {
-          throw new Error('更新跟进记录失败');
-        }
-        message.success('跟进记录更新成功');
-      } else {
-        // 创建跟进记录
-        const res = await followUpClient.$post({
-          json: values
-        });
-        if (res.status !== 201) {
-          throw new Error('创建跟进记录失败');
-        }
-        message.success('跟进记录创建成功');
-      }
-      
-      setModalVisible(false);
-      form.resetFields();
-      refetch(); // 刷新跟进记录列表
-    } catch (error) {
-      console.error('表单提交失败:', error);
-      message.error('操作失败,请重试');
-    }
-  };
-
-  // 处理删除跟进记录
-  const handleDelete = async (id: number) => {
-    try {
-      const res = await followUpClient[':id']['$delete']({
-        param: { id }
-      });
-      if (res.status !== 204) {
-        throw new Error('删除跟进记录失败');
-      }
-      message.success('跟进记录删除成功');
-      refetch(); // 刷新跟进记录列表
-    } catch (error) {
-      console.error('删除跟进记录失败:', error);
-      message.error('删除失败,请重试');
-    }
-  };
-  
-  // 跟进方式中文映射
-  const methodLabelMap: Record<FollowUpMethod, string> = {
-    [FollowUpMethod.PHONE]: '电话',
-    [FollowUpMethod.EMAIL]: '邮件',
-    [FollowUpMethod.MEETING]: '会议',
-    [FollowUpMethod.OTHER]: '其他'
-  };
-  
-  // 跟进方式颜色映射
-  const methodColorMap: Record<FollowUpMethod, string> = {
-    [FollowUpMethod.PHONE]: 'blue',
-    [FollowUpMethod.EMAIL]: 'green',
-    [FollowUpMethod.MEETING]: 'purple',
-    [FollowUpMethod.OTHER]: 'orange'
-  };
-  
-  const columns = [
-    {
-      title: '销售机会',
-      dataIndex: 'opportunityId',
-      key: 'opportunity',
-      render: (opportunityId: number) => {
-        const opportunity = opportunities.find(o => o.id === opportunityId);
-        return opportunity ? opportunity.title : '-';
-      }
-    },
-    {
-      title: '跟进方式',
-      dataIndex: 'method',
-      key: 'method',
-      render: (method: FollowUpMethod) => (
-        <Tag color={methodColorMap[method]}>
-          {methodLabelMap[method]}
-        </Tag>
-      ),
-    },
-    {
-      title: '跟进内容',
-      dataIndex: 'content',
-      key: 'content',
-      ellipsis: true,
-      width: 300
-    },
-    {
-      title: '下次跟进日期',
-      dataIndex: 'nextFollowUpDate',
-      key: 'nextFollowUpDate',
-      render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-',
-    },
-    {
-      title: '创建时间',
-      dataIndex: 'createdAt',
-      key: 'createdAt',
-      render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
-    },
-    {
-      title: '操作',
-      key: 'action',
-      render: (_: any, record: any) => (
-        <Space size="middle">
-          <Button type="link" onClick={() => showEditModal(record)}>
-            编辑
-          </Button>
-          <Popconfirm
-            title="确定要删除此跟进记录吗?"
-            onConfirm={() => handleDelete(record.id)}
-            okText="确定"
-            cancelText="取消"
-          >
-            <Button type="link" danger>
-              删除
-            </Button>
-          </Popconfirm>
-        </Space>
-      ),
-    },
-  ];
-  
-  return (
-    <div>
-      <div className="mb-6 flex justify-between items-center">
-        <Title level={2}>跟进记录管理</Title>
-      </div>
-      <Card className="shadow-md transition-all duration-300 hover:shadow-lg">
-        <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16, padding: '16px 0' }}>
-          <Form.Item name="keyword" label="搜索">
-            <Input placeholder="跟进内容" allowClear />
-          </Form.Item>
-          <Form.Item name="method" label="跟进方式">
-            <Select placeholder="全部方式" allowClear>
-              {Object.entries(FollowUpMethod).map(([value, key]) => (
-                <Select.Option key={key} value={key}>
-                  {methodLabelMap[key as FollowUpMethod]}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
-          <Form.Item>
-            <Space>
-              <Button type="primary" htmlType="submit">
-                搜索
-              </Button>
-              <Button type="primary" onClick={showCreateModal}>
-                创建跟进记录
-              </Button>
-            </Space>
-          </Form.Item>
-        </Form>
-
-        <Table
-          columns={columns}
-          dataSource={followUps}
-          loading={isLoading}
-          rowKey="id"
-          pagination={{
-            ...pagination,
-            showSizeChanger: true,
-            showQuickJumper: true,
-            showTotal: (total) => `共 ${total} 条记录`
-          }}
-          onChange={handleTableChange}
-          bordered
-          scroll={{ x: 'max-content' }}
-          rowClassName={(record, index) => index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}
-        />
-      </Card>
-
-      {/* 创建/编辑跟进记录模态框 */}
-      <Modal
-        title={modalTitle}
-        open={modalVisible}
-        onOk={handleModalOk}
-        onCancel={() => {
-          setModalVisible(false);
-          form.resetFields();
-        }}
-        width={600}
-        centered
-        destroyOnClose
-        maskClosable={false}
-      >
-        <Form
-          form={form}
-          layout="vertical"
-          labelCol={{ span: 5 }}
-          wrapperCol={{ span: 19 }}
-        >
-          <Form.Item
-            name="opportunityId"
-            label="销售机会"
-            required
-            rules={[{ required: true, message: '请选择销售机会' }]}
-          >
-            <Select placeholder="请选择销售机会">
-              {opportunities.map(opportunity => (
-                <Select.Option key={opportunity.id} value={opportunity.id}>
-                  {opportunity.title}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
-
-          <Form.Item
-            name="method"
-            label="跟进方式"
-            required
-            rules={[{ required: true, message: '请选择跟进方式' }]}
-          >
-            <Select placeholder="请选择跟进方式">
-              {Object.entries(FollowUpMethod).map(([value, key]) => (
-                <Select.Option key={key} value={key}>
-                  {methodLabelMap[key as FollowUpMethod]}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
-
-          <Form.Item
-            name="content"
-            label="跟进内容"
-            required
-            rules={[
-              { required: true, message: '请输入跟进内容' },
-              { min: 5, message: '跟进内容至少5个字符' }
-            ]}
-          >
-            <TextArea rows={4} placeholder="请详细描述跟进情况、客户反馈等信息" />
-          </Form.Item>
-
-          <Form.Item
-            name="nextFollowUpDate"
-            label="下次跟进日期"
-            rules={[{ required: false, message: '请选择下次跟进日期' }]}
-          >
-            <DatePicker placeholder="请选择下次跟进日期" />
-          </Form.Item>
-        </Form>
-      </Modal>
-    </div>
-  );
-};

+ 0 - 415
src/client/admin/pages/Opportunities.tsx

@@ -1,415 +0,0 @@
-import React, { useState } from 'react';
-import {
-  Button, Table, Space, Form, Input, InputNumber, Select, Modal, Card, Typography, Popconfirm, Tag, DatePicker,
-  App
-} from 'antd';
-import { useQuery } from '@tanstack/react-query';
-import dayjs from 'dayjs';
-import { opportunityClient, customerClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
-import { OpportunityStage } from '@/server/modules/opportunities/opportunity.entity';
-
-type OpportunityListResponse = InferResponseType<typeof opportunityClient.$get, 200>;
-type OpportunityDetailResponse = InferResponseType<typeof opportunityClient[':id']['$get'], 200>;
-type CreateOpportunityRequest = InferRequestType<typeof opportunityClient.$post>['json'];
-type UpdateOpportunityRequest = InferRequestType<typeof opportunityClient[':id']['$put']>['json'];
-type CustomerListResponse = InferResponseType<typeof customerClient.$get, 200>;
-
-const { Title } = Typography;
-
-// 销售机会管理页面
-export const OpportunitiesPage = () => {
-  const { message } = App.useApp();
-  const [searchParams, setSearchParams] = useState({
-    page: 1,
-    limit: 10,
-    keyword: '',
-    stage: ''
-  });
-  const [modalVisible, setModalVisible] = useState(false);
-  const [modalTitle, setModalTitle] = useState('');
-  const [editingOpportunity, setEditingOpportunity] = useState<any>(null);
-  const [form] = Form.useForm();
-  const [customers, setCustomers] = useState<any[]>([]);
-
-  // 获取客户列表
-  const { data: customersData } = useQuery({
-    queryKey: ['allCustomers'],
-    queryFn: async () => {
-      const res = await customerClient.$get({
-        query: {
-          page: 1,
-          pageSize: 1000
-        }
-      });
-      if (res.status !== 200) {
-        throw new Error('获取客户列表失败');
-      }
-      const data = await res.json();
-      return data.data || [];
-    }
-  });
-
-  React.useEffect(() => {
-    if (customersData) {
-      setCustomers(customersData);
-    }
-  }, [customersData]);
-
-  // 获取销售机会列表
-  const { data: opportunitiesData, isLoading, refetch } = useQuery({
-    queryKey: ['opportunities', searchParams],
-    queryFn: async () => {
-      const res = await opportunityClient.$get({
-        query: {
-          page: searchParams.page,
-          pageSize: searchParams.limit,
-          keyword: searchParams.keyword
-        }
-      });
-      if (res.status !== 200) {
-        throw new Error('获取销售机会列表失败');
-      }
-      return await res.json();
-    }
-  });
-
-  const opportunities = opportunitiesData?.data || [];
-  const pagination = {
-    current: searchParams.page,
-    pageSize: searchParams.limit,
-    total: opportunitiesData?.pagination?.total || 0
-  };
-
-  // 处理搜索
-  const handleSearch = (values: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      keyword: values.keyword || '',
-      stage: values.stage || '',
-      page: 1
-    }));
-  };
-
-  // 处理分页变化
-  const handleTableChange = (newPagination: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      page: newPagination.current,
-      limit: newPagination.pageSize
-    }));
-  };
-
-  // 打开创建销售机会模态框
-  const showCreateModal = () => {
-    setModalTitle('创建销售机会');
-    setEditingOpportunity(null);
-    form.resetFields();
-    setModalVisible(true);
-  };
-
-  // 打开编辑销售机会模态框
-  const showEditModal = (opportunity: any) => {
-    setModalTitle('编辑销售机会');
-    setEditingOpportunity(opportunity);
-    form.setFieldsValue({
-      ...opportunity,
-      expectedCloseDate: opportunity.expectedCloseDate ? dayjs(opportunity.expectedCloseDate) : null
-    });
-    setModalVisible(true);
-  };
-
-  // 处理模态框确认
-  const handleModalOk = async () => {
-    try {
-      const values = await form.validateFields();
-      
-      // 格式化日期
-      if (values.expectedCloseDate) {
-        values.expectedCloseDate = dayjs(values.expectedCloseDate).format('YYYY-MM-DD');
-      }
-      
-      if (editingOpportunity) {
-        // 编辑销售机会
-        const res = await opportunityClient[':id']['$put']({
-          param: { id: editingOpportunity.id },
-          json: values
-        });
-        if (res.status !== 200) {
-          throw new Error('更新销售机会失败');
-        }
-        message.success('销售机会更新成功');
-      } else {
-        // 创建销售机会
-        const res = await opportunityClient.$post({
-          json: values
-        });
-        if (res.status !== 201) {
-          throw new Error('创建销售机会失败');
-        }
-        message.success('销售机会创建成功');
-      }
-      
-      setModalVisible(false);
-      form.resetFields();
-      refetch(); // 刷新销售机会列表
-    } catch (error) {
-      console.error('表单提交失败:', error);
-      message.error('操作失败,请重试');
-    }
-  };
-
-  // 处理删除销售机会
-  const handleDelete = async (id: number) => {
-    try {
-      const res = await opportunityClient[':id']['$delete']({
-        param: { id }
-      });
-      if (res.status !== 204) {
-        throw new Error('删除销售机会失败');
-      }
-      message.success('销售机会删除成功');
-      refetch(); // 刷新销售机会列表
-    } catch (error) {
-      console.error('删除销售机会失败:', error);
-      message.error('删除失败,请重试');
-    }
-  };
-  
-  // 销售阶段中文映射
-  const stageLabelMap: Record<OpportunityStage, string> = {
-    [OpportunityStage.INITIAL_CONTACT]: '初步接触',
-    [OpportunityStage.NEEDS_ANALYSIS]: '需求确认',
-    [OpportunityStage.SOLUTION_PROPOSAL]: '方案制定',
-    [OpportunityStage.NEGOTIATION]: '谈判阶段',
-    [OpportunityStage.CLOSED_WON]: '成交',
-    [OpportunityStage.CLOSED_LOST]: '丢失'
-  };
-  
-  // 销售阶段颜色映射
-  const stageColorMap: Record<OpportunityStage, string> = {
-    [OpportunityStage.INITIAL_CONTACT]: 'blue',
-    [OpportunityStage.NEEDS_ANALYSIS]: 'purple',
-    [OpportunityStage.SOLUTION_PROPOSAL]: 'orange',
-    [OpportunityStage.NEGOTIATION]: 'gold',
-    [OpportunityStage.CLOSED_WON]: 'green',
-    [OpportunityStage.CLOSED_LOST]: 'red'
-  };
-  
-  const columns = [
-    {
-      title: '机会名称',
-      dataIndex: 'title',
-      key: 'title',
-    },
-    {
-      title: '客户',
-      dataIndex: 'customerId',
-      key: 'customer',
-      render: (customerId: number) => {
-        const customer = customers.find(c => c.id === customerId);
-        return customer ? customer.name : '-';
-      }
-    },
-    {
-      title: '预计金额',
-      dataIndex: 'amount',
-      key: 'amount',
-      render: (amount: number) => `¥${Number(amount).toFixed(2)}`
-    },
-    {
-      title: '销售阶段',
-      dataIndex: 'stage',
-      key: 'stage',
-      render: (stage: OpportunityStage) => (
-        <Tag color={stageColorMap[stage]}>
-          {stageLabelMap[stage]}
-        </Tag>
-      ),
-    },
-    {
-      title: '预计成交日期',
-      dataIndex: 'expectedCloseDate',
-      key: 'expectedCloseDate',
-      render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-',
-    },
-    {
-      title: '创建时间',
-      dataIndex: 'createdAt',
-      key: 'createdAt',
-      render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
-    },
-    {
-      title: '操作',
-      key: 'action',
-      render: (_: any, record: any) => (
-        <Space size="middle">
-          <Button type="link" onClick={() => showEditModal(record)}>
-            编辑
-          </Button>
-          <Popconfirm
-            title="确定要删除此销售机会吗?"
-            onConfirm={() => handleDelete(record.id)}
-            okText="确定"
-            cancelText="取消"
-          >
-            <Button type="link" danger>
-              删除
-            </Button>
-          </Popconfirm>
-        </Space>
-      ),
-    },
-  ];
-  
-  return (
-    <div>
-      <div className="mb-6 flex justify-between items-center">
-        <Title level={2}>销售机会管理</Title>
-      </div>
-      <Card className="shadow-md transition-all duration-300 hover:shadow-lg">
-        <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16, padding: '16px 0' }}>
-          <Form.Item name="keyword" label="搜索">
-            <Input placeholder="机会名称/描述" allowClear />
-          </Form.Item>
-          <Form.Item name="stage" label="销售阶段">
-            <Select placeholder="全部阶段" allowClear>
-              {Object.entries(OpportunityStage).map(([value, key]) => (
-                <Select.Option key={key} value={key}>
-                  {stageLabelMap[key as OpportunityStage]}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
-          <Form.Item>
-            <Space>
-              <Button type="primary" htmlType="submit">
-                搜索
-              </Button>
-              <Button type="primary" onClick={showCreateModal}>
-                创建销售机会
-              </Button>
-            </Space>
-          </Form.Item>
-        </Form>
-
-        <Table
-          columns={columns}
-          dataSource={opportunities}
-          loading={isLoading}
-          rowKey="id"
-          pagination={{
-            ...pagination,
-            showSizeChanger: true,
-            showQuickJumper: true,
-            showTotal: (total) => `共 ${total} 条记录`
-          }}
-          onChange={handleTableChange}
-          bordered
-          scroll={{ x: 'max-content' }}
-          rowClassName={(record, index) => index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}
-        />
-      </Card>
-
-      {/* 创建/编辑销售机会模态框 */}
-      <Modal
-        title={modalTitle}
-        open={modalVisible}
-        onOk={handleModalOk}
-        onCancel={() => {
-          setModalVisible(false);
-          form.resetFields();
-        }}
-        width={600}
-        centered
-        destroyOnClose
-        maskClosable={false}
-      >
-        <Form
-          form={form}
-          layout="vertical"
-          labelCol={{ span: 5 }}
-          wrapperCol={{ span: 19 }}
-        >
-          <Form.Item
-            name="customerId"
-            label="客户"
-            required
-            rules={[{ required: true, message: '请选择客户' }]}
-          >
-            <Select placeholder="请选择客户">
-              {customers.map(customer => (
-                <Select.Option key={customer.id} value={customer.id}>
-                  {customer.name} {customer.company ? `(${customer.company})` : ''}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
-
-          <Form.Item
-            name="title"
-            label="机会名称"
-            required
-            rules={[
-              { required: true, message: '请输入机会名称' },
-              { min: 2, message: '机会名称至少2个字符' }
-            ]}
-          >
-            <Input placeholder="请输入销售机会名称" />
-          </Form.Item>
-
-          <Form.Item
-            name="amount"
-            label="预计金额"
-            required
-            rules={[
-              { required: true, message: '请输入预计金额' },
-            ]}
-          >
-            <InputNumber
-              min={0}
-              precision={2}
-              step={0.01}
-              placeholder="请输入预计金额"
-            />
-          </Form.Item>
-
-          <Form.Item
-            name="stage"
-            label="销售阶段"
-            required
-            rules={[{ required: true, message: '请选择销售阶段' }]}
-          >
-            <Select placeholder="请选择销售阶段">
-              {Object.entries(OpportunityStage).map(([value, key]) => (
-                <Select.Option key={key} value={key}>
-                  {stageLabelMap[key as OpportunityStage]}
-                </Select.Option>
-              ))}
-            </Select>
-          </Form.Item>
-
-          <Form.Item
-            name="expectedCloseDate"
-            label="预计成交日期"
-            rules={[{ required: false, message: '请选择预计成交日期' }]}
-          >
-            <DatePicker
-              format="YYYY-MM-DD"
-              placeholder="请选择预计成交日期"
-              allowClear
-            />
-          </Form.Item>
-
-          <Form.Item
-            name="description"
-            label="机会描述"
-            rules={[{ required: false, message: '请输入机会描述' }]}
-          >
-            <Input.TextArea rows={4} placeholder="请输入销售机会描述" />
-          </Form.Item>
-        </Form>
-      </Modal>
-    </div>
-  );
-};

+ 0 - 18
src/client/admin/routes.tsx

@@ -6,9 +6,6 @@ import { ErrorPage } from './components/ErrorPage';
 import { NotFoundPage } from './components/NotFoundPage';
 import { DashboardPage } from './pages/Dashboard';
 import { UsersPage } from './pages/Users';
-import { CustomersPage } from './pages/Customers';
-import { OpportunitiesPage } from './pages/Opportunities';
-import { FollowUpsPage } from './pages/FollowUps';
 import AreasPage from './pages/Areas';
 import ClientsPage from './pages/Clients';
 import ExpensesPage from './pages/Expenses';
@@ -50,21 +47,6 @@ export const router = createBrowserRouter([
         element: <UsersPage />,
         errorElement: <ErrorPage />
       },
-      {
-        path: 'customers',
-        element: <CustomersPage />,
-        errorElement: <ErrorPage />
-      },
-      {
-        path: 'opportunities',
-        element: <OpportunitiesPage />,
-        errorElement: <ErrorPage />
-      },
-      {
-        path: 'follow-ups',
-        element: <FollowUpsPage />,
-        errorElement: <ErrorPage />
-      },
       {
         path: 'areas',
         element: <AreasPage />,