Explorar el Código

✨ feat(clients): 重构客户管理页面UI与功能

- 重命名"公司名称"为"项目名称",调整表格列布局与内容
- 增加省份/地区、登记审核、主产品/合同估额等新字段
- 优化表单布局为网格系统,提升信息录入效率
- 调整操作按钮样式与位置,增加悬停效果
- 优化搜索功能,支持多条件搜索与重置

✨ feat(files): 增强文件删除功能的安全性

- 为文件删除操作添加确认弹窗,防止误操作
- 优化删除确认信息,显示文件名并提示操作不可恢复
- 调整确认按钮样式,使用危险样式突出重要操作

💄 style(clients): 美化客户管理页面样式

- 增加卡片式布局与阴影效果,提升视觉层次感
- 优化表格行样式,实现隔行异色效果
- 调整按钮尺寸与间距,增强页面整体协调性
- 优化模态框标题与表单样式,提升一致性

♻️ refactor(clients): 优化客户管理表格与分页

- 增加表格横向滚动支持,适应更多列数据展示
- 优化分页控件,增加显示总数与快速跳转功能
- 更新表格行键渲染方式,提升数据展示性能
- 调整操作列固定在右侧,提升用户体验
yourname hace 8 meses
padre
commit
87bf28e998
Se han modificado 2 ficheros con 226 adiciones y 170 borrados
  1. 209 161
      src/client/admin/pages/Clients.tsx
  2. 17 9
      src/client/admin/pages/Files.tsx

+ 209 - 161
src/client/admin/pages/Clients.tsx

@@ -204,80 +204,138 @@ const Clients: React.FC = () => {
   // 表格列定义
   const columns = [
     {
-      title: 'ID',
-      dataIndex: 'id',
-      key: 'id',
-      width: 80,
-    },
-    {
-      title: '公司名称',
+      title: '项目名称',
       dataIndex: 'companyName',
       key: 'companyName',
+      width: 200,
+      ellipsis: true,
     },
     {
-      title: '所在区域',
+      title: '省份',
       dataIndex: 'areaId',
-      key: 'areaId',
+      key: 'province',
+      width: 100,
       render: (areaId: number) => {
         const area = areas.find(a => a.id === areaId);
         return area ? area.name : '-';
       },
     },
     {
-      title: '联系人',
-      dataIndex: 'contactPerson',
-      key: 'contactPerson',
+      title: '地区',
+      dataIndex: 'areaId',
+      key: 'region',
+      width: 100,
+      render: (areaId: number) => {
+        const area = areas.find(a => a.id === areaId);
+        return area ? area.name : '-';
+      },
     },
     {
-      title: '联系电话',
-      dataIndex: 'mobile',
-      key: 'mobile',
+      title: '登记审核',
+      dataIndex: 'auditStatus',
+      key: 'auditStatus',
+      width: 100,
+      render: (status: number) => {
+        const statusMap = {
+          0: { text: '待审核', color: 'orange' },
+          1: { text: '已审核', color: 'green' },
+          2: { text: '已拒绝', color: 'red' }
+        };
+        const config = statusMap[status as keyof typeof statusMap] || { text: '-', color: 'default' };
+        return <span style={{ color: config.color }}>{config.text}</span>;
+      },
     },
     {
-      title: '客户状态',
-      dataIndex: 'status',
-      key: 'status',
-      render: (status: number) => (
-        <span>{status === 1 ? '有效' : '无效'}</span>
-      ),
+      title: '主产品/合同估额',
+      dataIndex: 'description',
+      key: 'productAmount',
+      width: 180,
+      ellipsis: true,
+      render: (description: string) => description || '-',
     },
     {
-      title: '审核状态',
-      dataIndex: 'auditStatus',
-      key: 'auditStatus',
-      render: (status: number) => {
-        switch (status) {
-          case 0: return <span>待审核</span>;
-          case 1: return <span>已审核</span>;
-          case 2: return <span>已拒绝</span>;
-          default: return <span>-</span>;
-        }
+      title: '投资方',
+      dataIndex: 'industry',
+      key: 'investor',
+      width: 150,
+      ellipsis: true,
+      render: (industry: string) => industry || '-',
+    },
+    {
+      title: '产品大类',
+      dataIndex: 'customerType',
+      key: 'productCategory',
+      width: 120,
+      ellipsis: true,
+      render: (customerType: string) => customerType || '-',
+    },
+    {
+      title: '业务员',
+      dataIndex: 'responsibleUserId',
+      key: 'salesperson',
+      width: 120,
+      render: (responsibleUserId: number) => {
+        // 这里需要根据实际业务关联用户表
+        return responsibleUserId || '-';
       },
     },
     {
-      title: '创建时间',
-      dataIndex: 'createdAt',
-      key: 'createdAt',
+      title: '联系人/电话',
+      key: 'contactPhone',
       width: 180,
-      render: (date: string) => new Date(date).toLocaleString(),
+      render: (_: any, record: ClientItem) => (
+        <div>
+          <div>{record.contactPerson || '-'}</div>
+          <div className="text-gray-500 text-sm">{record.mobile || record.telephone || '-'}</div>
+        </div>
+      ),
+    },
+    {
+      title: '预计投标时间',
+      dataIndex: 'oeDate',
+      key: 'bidTime',
+      width: 120,
+      render: (date: string) => {
+        return date ? new Date(date).toLocaleDateString() : '-';
+      },
+    },
+    {
+      title: '更新时间',
+      dataIndex: 'updatedAt',
+      key: 'updatedAt',
+      width: 150,
+      render: (date: string) => new Date(date).toLocaleDateString(),
+    },
+    {
+      title: '操作员',
+      dataIndex: 'responsibleUserId',
+      key: 'operator',
+      width: 100,
+      render: (responsibleUserId: number) => {
+        // 这里需要根据实际业务关联用户表
+        return responsibleUserId || '-';
+      },
     },
     {
       title: '操作',
       key: 'action',
-      width: 180,
+      width: 120,
+      fixed: 'right' as const,
       render: (_: any, record: ClientItem) => (
-        <Space size="middle">
-          <Button 
-            type="text" 
-            icon={<EditOutlined />} 
+        <Space size="small">
+          <Button
+            size="small"
+            type="link"
+            icon={<EditOutlined />}
             onClick={() => showModal(record)}
           >
             编辑
           </Button>
-          <Button 
-            type="text" 
-            danger 
-            icon={<DeleteOutlined />} 
+          <Button
+            size="small"
+            type="text"
+            danger
+            icon={<DeleteOutlined />}
             onClick={() => handleDelete(record.id)}
           >
             删除
@@ -288,47 +346,74 @@ const Clients: React.FC = () => {
   ];
   
   return (
-    <div className="p-4">
-      <div className="flex justify-between items-center mb-4">
-        <h2 className="text-xl font-bold">客户管理</h2>
-        <Button 
-          type="primary" 
-          icon={<PlusOutlined />} 
+    <div className="p-6">
+      <div className="mb-6 flex justify-between items-center">
+        <h2 className="text-2xl font-bold text-gray-800">客户管理</h2>
+        <Button
+          type="primary"
+          icon={<PlusOutlined />}
           onClick={() => showModal()}
+          className="shadow-sm hover:shadow-md transition-all duration-200"
         >
           添加客户
         </Button>
       </div>
       
-      <div className="mb-4">
-        <Input
-          placeholder="搜索公司名称或联系人"
-          prefix={<SearchOutlined />}
-          value={searchText}
-          onChange={(e) => setSearchText(e.target.value)}
-          onPressEnter={handleSearch}
-          style={{ width: 300 }}
-        />
-        <Button type="default" onClick={handleSearch} style={{ marginLeft: 8 }}>
-          搜索
-        </Button>
+      <div className="mb-6 p-4 bg-white rounded-lg shadow-sm">
+        <div className="flex items-center space-x-4">
+          <Input
+            placeholder="搜索项目名称、联系人或电话"
+            prefix={<SearchOutlined />}
+            value={searchText}
+            onChange={(e) => setSearchText(e.target.value)}
+            onPressEnter={handleSearch}
+            className="max-w-md"
+            allowClear
+          />
+          <Button
+            type="primary"
+            onClick={handleSearch}
+            className="hover:shadow-md transition-all duration-200"
+          >
+            搜索
+          </Button>
+          <Button onClick={() => {
+            setSearchText('');
+            setPagination({ ...pagination, current: 1 });
+          }}>
+            重置
+          </Button>
+        </div>
       </div>
       
-      <Table
-        columns={columns}
-        dataSource={clientsData?.data || []}
-        rowKey="id"
-        loading={clientsLoading}
-        pagination={{
-          ...pagination,
-          total: clientsData?.pagination.total || 0
-        }}
-        onChange={handleTableChange}
-        bordered
-      />
+      <div className="bg-white rounded-lg shadow-sm overflow-hidden">
+        <Table
+          columns={columns}
+          dataSource={clientsData?.data || []}
+          rowKey="id"
+          loading={clientsLoading}
+          pagination={{
+            ...pagination,
+            total: clientsData?.pagination.total || 0,
+            showSizeChanger: true,
+            showQuickJumper: true,
+            showTotal: (total, range) =>
+              `显示 ${range[0]}-${range[1]} 条,共 ${total} 条`,
+            pageSizeOptions: [10, 20, 50, 100],
+            responsive: true,
+          }}
+          onChange={handleTableChange}
+          bordered={false}
+          scroll={{ x: 1500 }}
+          className="w-full"
+          rowClassName={(record, index) =>
+            index % 2 === 0 ? 'bg-white hover:bg-gray-50' : 'bg-gray-50 hover:bg-gray-100'
+          }
+        />
+      </div>
       
       <Modal
-        title={editingKey ? "编辑客户" : "添加客户"}
+        title={editingKey ? "编辑项目" : "添加项目"}
         open={modalVisible}
         onCancel={handleCancel}
         footer={[
@@ -342,125 +427,107 @@ const Clients: React.FC = () => {
         width={800}
       >
         <Form form={form} layout="vertical">
-          <Form.Item
-            name="companyName"
-            label="公司名称"
-            rules={[{ required: true, message: '请输入公司名称' }]}
-          >
-            <Input placeholder="请输入公司名称" />
-          </Form.Item>
-          
           <div className="grid grid-cols-2 gap-4">
             <Form.Item
-              name="areaId"
-              label="所在区域"
+              name="companyName"
+              label="项目名称"
+              rules={[{ required: true, message: '请输入项目名称' }]}
             >
-              <AreaTreeSelect
-                placeholder="请选择区域"
-                value={form.getFieldValue('areaId') || undefined}
-                onChange={(value) => form.setFieldValue('areaId', value)}
-              />
+              <Input placeholder="请输入项目名称" />
             </Form.Item>
             
             <Form.Item
-              name="square"
-              label="场地面积"
+              name="auditStatus"
+              label="登记审核"
+              initialValue={0}
             >
-              <Input placeholder="请输入场地面积" />
+              <Select>
+                <Select.Option value={0}>待审核</Select.Option>
+                <Select.Option value={1}>已审核</Select.Option>
+                <Select.Option value={2}>已拒绝</Select.Option>
+              </Select>
             </Form.Item>
           </div>
           
-          <Form.Item
-            name="address"
-            label="客户地址"
-          >
-            <Input placeholder="请输入客户地址" />
-          </Form.Item>
-          
           <div className="grid grid-cols-2 gap-4">
             <Form.Item
-              name="contactPerson"
-              label="联系人姓名"
-            >
-              <Input placeholder="请输入联系人姓名" />
-            </Form.Item>
-            
-            <Form.Item
-              name="position"
-              label="联系人职位"
+              name="areaId"
+              label="省份/地区"
             >
-              <Input placeholder="请输入联系人职位" />
+              <AreaTreeSelect
+                placeholder="请选择省份/地区"
+                value={form.getFieldValue('areaId') || undefined}
+                onChange={(value) => form.setFieldValue('areaId', value)}
+              />
             </Form.Item>
           </div>
           
           <div className="grid grid-cols-2 gap-4">
             <Form.Item
-              name="mobile"
-              label="手机号码"
+              name="description"
+              label="主产品/合同估额"
             >
-              <Input placeholder="请输入手机号码" />
+              <Input.TextArea rows={2} placeholder="请输入主产品或合同金额" />
             </Form.Item>
             
             <Form.Item
-              name="telephone"
-              label="联系电话"
+              name="industry"
+              label="投资方"
             >
-              <Input placeholder="请输入联系电话" />
+              <Input placeholder="请输入投资方信息" />
             </Form.Item>
           </div>
           
           <div className="grid grid-cols-2 gap-4">
             <Form.Item
-              name="email"
-              label="电子邮箱"
+              name="customerType"
+              label="产品大类"
             >
-              <Input placeholder="请输入电子邮箱" />
+              <Input placeholder="请输入产品大类" />
             </Form.Item>
             
             <Form.Item
-              name="zipCode"
-              label="邮政编码"
+              name="responsibleUserId"
+              label="业务员"
             >
-              <Input placeholder="请输入邮政编码" />  
+              <Input type="number" placeholder="请输入业务员ID" />
             </Form.Item>
           </div>
           
+          <Form.Item
+            name="address"
+            label="地址"
+          >
+            <Input placeholder="请输入详细地址" />
+          </Form.Item>
+          
           <div className="grid grid-cols-2 gap-4">
             <Form.Item
-              name="industry"
-              label="所属行业"
+              name="contactPerson"
+              label="联系人"
             >
-              <Input placeholder="请输入所属行业" />
+              <Input placeholder="请输入联系人姓名" />
             </Form.Item>
             
             <Form.Item
-              name="subIndustry"
-              label="所属子行业"
+              name="mobile"
+              label="联系电话"
             >
-              <Input placeholder="请输入所属子行业" />
+              <Input placeholder="请输入联系电话" />
             </Form.Item>
           </div>
           
           <div className="grid grid-cols-2 gap-4">
             <Form.Item
-              name="customerType"
-              label="客户类型"
+              name="oeDate"
+              label="预计投标时间"
             >
-              <Input placeholder="请输入客户类型" />
+              <Input placeholder="请输入预计投标时间" />
             </Form.Item>
             
-            <Form.Item
-              name="source"
-              label="客户来源"
-            >
-              <Input placeholder="请输入客户来源" />
-            </Form.Item>
-          </div>
-          
-          <div className="grid grid-cols-2 gap-4">
             <Form.Item
               name="status"
-              label="客户状态"
+              label="项目状态"
               initialValue={1}
             >
               <Select>
@@ -468,32 +535,13 @@ const Clients: React.FC = () => {
                 <Select.Option value={1}>有效</Select.Option>
               </Select>
             </Form.Item>
-            
-            <Form.Item
-              name="auditStatus"
-              label="审核状态"
-              initialValue={0}
-            >
-              <Select>
-                <Select.Option value={0}>待审核</Select.Option>
-                <Select.Option value={1}>已审核</Select.Option>
-                <Select.Option value={2}>已拒绝</Select.Option>
-              </Select>
-            </Form.Item>
           </div>
           
-          <Form.Item
-            name="description"
-            label="客户详细信息"
-          >
-            <Input.TextArea rows={4} placeholder="请输入客户详细信息" />
-          </Form.Item>
-          
           <Form.Item
             name="remarks"
             label="备注信息"
           >
-            <Input.TextArea rows={2} placeholder="请输入备注信息" />
+            <Input.TextArea rows={3} placeholder="请输入备注信息" />
           </Form.Item>
         </Form>
       </Modal>

+ 17 - 9
src/client/admin/pages/Files.tsx

@@ -1,5 +1,5 @@
 import React, { useState, useEffect } from 'react';
-import { Table, Button, Space, Input, Modal, Form, Select, DatePicker, Upload } from 'antd';
+import { Table, Button, Space, Input, Modal, Form, Select, DatePicker, Upload, Popconfirm } from 'antd';
 import { App } from 'antd';
 import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined } from '@ant-design/icons';
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
@@ -219,15 +219,23 @@ const Files: React.FC = () => {
           >
             编辑
           </Button>
-          <Button
-            type="text"
-            danger
-            icon={<DeleteOutlined />}
-            onClick={() => deleteFile.mutate(record.id)}
-            className="hover:bg-red-50"
+          <Popconfirm
+            title="确认删除"
+            description={`确定要删除文件"${record.name}"吗?此操作不可恢复。`}
+            onConfirm={() => deleteFile.mutate(record.id)}
+            okText="确认"
+            cancelText="取消"
+            okButtonProps={{ danger: true }}
           >
-            删除
-          </Button>
+            <Button
+              type="text"
+              danger
+              icon={<DeleteOutlined />}
+              className="hover:bg-red-50"
+            >
+              删除
+            </Button>
+          </Popconfirm>
         </Space>
       ),
     },