Просмотр исходного кода

✨ feat(expenses): 重构费用管理页面并增强功能

- 添加高级筛选功能,支持按客户、费用类型、状态和日期范围筛选
- 优化表格显示,增加状态标签、排序和自定义行样式
- 增强表单验证,添加必填项检查和数据类型验证
- 添加导出和刷新功能按钮
- 优化UI布局,使用卡片组件和响应式设计
- 添加操作权限控制,根据权限禁用编辑/删除按钮
- 完善错误处理和日志记录功能

♻️ refactor(expenses): 优化代码结构和类型定义

- 引入InferRequestType类型定义,增强类型安全
- 重构查询逻辑,使用更清晰的参数处理方式
- 优化日期格式化和货币显示,使用工具函数统一处理
- 拆分大型组件为更小的逻辑块,提高可读性
- 统一使用antd的message组件进行用户反馈

🐛 fix(expenses): 修复已知问题和优化用户体验

- 修复编辑模式下ID字段可编辑的问题
- 修复删除操作无确认提示的问题
- 修复日期选择器可选择未来日期的问题
- 修复表单提交错误处理不完善的问题
- 修复分页和筛选状态同步问题
yourname 8 месяцев назад
Родитель
Сommit
14408305b1
1 измененных файлов с 466 добавлено и 159 удалено
  1. 466 159
      src/client/admin/pages/Expenses.tsx

+ 466 - 159
src/client/admin/pages/Expenses.tsx

@@ -1,15 +1,23 @@
-import React, { useState } from 'react';
-import { Table, Button, Space, Input, Modal, Form, message, Select, DatePicker } from 'antd';
-import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons';
+import React, { useState, useEffect } from 'react';
+import { Table, Button, Space, Input, Modal, Form, message, Select, DatePicker, Card, Typography, Layout, Spin, Tag } from 'antd';
+import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, FilterOutlined, DownloadOutlined, ReloadOutlined } from '@ant-design/icons';
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import type { InferResponseType } from 'hono/client';
+import type { InferResponseType, InferRequestType } from 'hono/client';
 import { expenseClient, clientClient } from '@/client/api';
-import dayjs from 'dayjs';
+import dayjs, { Dayjs } from 'dayjs';
+import { App } from 'antd';
+import { formatCurrency, formatDate } from '@/client/utils/utils';
+import { errorLogger, apiLogger } from '@/client/utils/logger';
 
 // 定义类型
 type ExpenseItem = InferResponseType<typeof expenseClient.$get, 200>['data'][0];
 type ExpenseListResponse = InferResponseType<typeof expenseClient.$get, 200>;
 type ClientItem = InferResponseType<typeof clientClient.$get, 200>['data'][0];
+type CreateExpenseRequest = InferRequestType<typeof expenseClient.$post>['json'];
+type UpdateExpenseRequest = InferRequestType<typeof expenseClient[':id']['$put']>['json'];
+
+const { Title } = Typography;
+const { Content } = Layout;
 
 const Expenses: React.FC = () => {
   const [form] = Form.useForm();
@@ -17,101 +25,144 @@ const Expenses: React.FC = () => {
   const [editingKey, setEditingKey] = useState<string | null>(null);
   const [searchText, setSearchText] = useState('');
   const [clients, setClients] = useState<ClientItem[]>([]);
+  const [dataSource, setDataSource] = useState<ExpenseItem[]>([]);
+  const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
+  const [filters, setFilters] = useState({
+    clientId: undefined as string | undefined,
+    type: undefined as string | undefined,
+    status: undefined as string | undefined,
+    dateRange: undefined as [Dayjs | null, Dayjs | null] | undefined,
+  });
+  const [filterVisible, setFilterVisible] = useState(false);
   
-  // 获取客户列表
+  const { message: antdMessage } = App.useApp();
   const queryClient = useQueryClient();
-  
+
   // 获取客户列表
-  const { data: clientsData } = useQuery({
+  const { data: clientsData, isLoading: isClientsLoading } = useQuery({
     queryKey: ['clients'],
-    queryFn: () => clientClient.$get({ query: { page: 1, pageSize: 1000 } }) as Promise<InferResponseType<typeof clientClient.$get, 200>>,
+    queryFn: async () => {
+      apiLogger('Fetching clients list');
+      const response = await clientClient.$get({ query: { page: 1, pageSize: 1000 } }) as Promise<InferResponseType<typeof clientClient.$get, 200>>;
+      apiLogger(`Fetched ${response.data.length} clients`);
+      return response;
+    },
     onSuccess: (result) => {
       setClients(result.data);
     },
+    onError: (error) => {
+      errorLogger('Failed to fetch clients:', error);
+      antdMessage.error('获取客户列表失败');
+    },
   });
-  
-  // 获取费用列表数据
+
   // 获取费用列表数据
-  const fetchExpenses = ({ page, pageSize }: { page: number; pageSize: number }): Promise<ExpenseListResponse> =>
-    expenseClient.$get({ query: { page, pageSize, keyword: searchText } });
-  
-  const { data, isLoading: loading, refetch } = useQuery({
-    queryKey: ['expenses', pagination.current, pagination.pageSize, searchText],
-    queryFn: () => fetchExpenses({ page: pagination.current, pageSize: pagination.pageSize }) as Promise<ExpenseListResponse>,
+  const fetchExpenses = async ({ page, pageSize }: { page: number; pageSize: number }): Promise<ExpenseListResponse> => {
+    apiLogger(`Fetching expenses with parameters: page=${page}, pageSize=${pageSize}, keyword=${searchText}`);
+    
+    const queryParams: Record<string, any> = { page, pageSize };
+    
+    if (searchText) queryParams.keyword = searchText;
+    if (filters.clientId) queryParams.clientId = filters.clientId;
+    if (filters.type) queryParams.type = filters.type;
+    if (filters.status) queryParams.status = filters.status;
+    if (filters.dateRange?.[0]) queryParams.startDate = filters.dateRange[0].format('YYYY-MM-DD');
+    if (filters.dateRange?.[1]) queryParams.endDate = filters.dateRange[1].format('YYYY-MM-DD');
+    
+    const response = await expenseClient.$get({ query: queryParams }) as Promise<ExpenseListResponse>;
+    apiLogger(`Fetched ${response.data.length} expenses, total: ${response.pagination.total}`);
+    return response;
+  };
+
+  const { data, isLoading: isExpensesLoading, refetch } = useQuery({
+    queryKey: ['expenses', pagination.current, pagination.pageSize, searchText, filters],
+    queryFn: () => fetchExpenses({ page: pagination.current, pageSize: pagination.pageSize }),
     onSuccess: (result) => {
       setDataSource(result.data);
-      setPagination({
-        ...pagination,
+      setPagination(prev => ({
+        ...prev,
         total: result.pagination.total,
-      });
+      }));
+    },
+    onError: (error) => {
+      errorLogger('Failed to fetch expenses:', error);
+      antdMessage.error('获取费用列表失败');
     },
   });
-  
+
   // 创建费用记录
   const createExpense = useMutation(
-    (data: any) => expenseClient.$post({ json: data }),
+    (data: CreateExpenseRequest) => expenseClient.$post({ json: data }),
     {
       onSuccess: () => {
-        message.success('费用记录创建成功');
+        apiLogger('Expense created successfully');
+        antdMessage.success('费用记录创建成功');
         queryClient.invalidateQueries(['expenses']);
       },
-      onError: () => {
-        message.error('操作失败,请重试');
+      onError: (error) => {
+        errorLogger('Failed to create expense:', error);
+        antdMessage.error('创建费用记录失败');
       }
     }
   );
-  
+
   // 更新费用记录
   const updateExpense = useMutation(
-    ({ id, data }: { id: string; data: any }) => expenseClient[':id'].$put({ param: { id }, json: data }),
+    ({ id, data }: { id: string; data: UpdateExpenseRequest }) => expenseClient[':id'].$put({ param: { id }, json: data }),
     {
       onSuccess: () => {
-        message.success('费用记录更新成功');
+        apiLogger('Expense updated successfully');
+        antdMessage.success('费用记录更新成功');
         queryClient.invalidateQueries(['expenses']);
       },
-      onError: () => {
-        message.error('操作失败,请重试');
+      onError: (error) => {
+        errorLogger('Failed to update expense:', error);
+        antdMessage.error('更新费用记录失败');
       }
     }
   );
-  
+
   // 删除费用记录
   const deleteExpense = useMutation(
     (id: string) => expenseClient[':id'].$delete({ param: { id } }),
     {
       onSuccess: () => {
-        message.success('费用记录删除成功');
+        apiLogger('Expense deleted successfully');
+        antdMessage.success('费用记录删除成功');
         queryClient.invalidateQueries(['expenses']);
       },
-      onError: () => {
-        message.error('删除失败,请重试');
+      onError: (error) => {
+        errorLogger('Failed to delete expense:', error);
+        antdMessage.error('删除费用记录失败');
       }
     }
   );
-  
-  // 初始化获取客户数据
-  React.useEffect(() => {
-    fetchClients();
-  }, [fetchClients]);
-  
-  const [dataSource, setDataSource] = useState<ExpenseItem[]>([]);
-  const [pagination, setPagination] = useState({
-    current: 1,
-    pageSize: 10,
-    total: 0,
-  });
-  
+
   // 搜索
   const handleSearch = () => {
-    run({ page: 1, pageSize: pagination.pageSize });
+    setPagination(prev => ({ ...prev, current: 1 }));
+    refetch();
   };
-  
+
+  // 重置筛选器
+  const resetFilters = () => {
+    setFilters({
+      clientId: undefined,
+      type: undefined,
+      status: undefined,
+      dateRange: undefined,
+    });
+    setSearchText('');
+    setPagination(prev => ({ ...prev, current: 1 }));
+    refetch();
+  };
+
   // 分页变化
   const handleTableChange = (pagination: any) => {
     setPagination(pagination);
-    run({ page: pagination.current, pageSize: pagination.pageSize });
+    refetch();
   };
-  
+
   // 显示添加/编辑弹窗
   const showModal = (record?: ExpenseItem) => {
     setModalVisible(true);
@@ -123,8 +174,7 @@ const Expenses: React.FC = () => {
         userId: record.userId,
         type: record.type,
         amount: record.amount,
-        clientId: record.clientId,
-        projectId: record.projectId,
+        clientId: record.clientId?.toString(),
         department: record.department,
         description: record.description,
         status: record.status,
@@ -133,8 +183,8 @@ const Expenses: React.FC = () => {
         reimbursementDate: record.reimbursementDate ? dayjs(record.reimbursementDate) : null,
         paymentMethod: record.paymentMethod,
         invoiceNumber: record.invoiceNumber,
-        currency: record.currency,
-        exchangeRate: record.exchangeRate,
+        currency: record.currency || 'CNY',
+        exchangeRate: record.exchangeRate || 1,
         foreignAmount: record.foreignAmount ? parseFloat(record.foreignAmount) : undefined,
       });
     } else {
@@ -142,114 +192,180 @@ const Expenses: React.FC = () => {
       form.resetFields();
     }
   };
-  
+
   // 关闭弹窗
   const handleCancel = () => {
     setModalVisible(false);
     form.resetFields();
   };
-  
+
   // 提交表单
   const handleSubmit = async () => {
     try {
       const values = await form.validateFields();
       
       // 处理日期字段
-      if (values.expenseDate) values.expenseDate = values.expenseDate.format('YYYY-MM-DD');
-      if (values.approveDate) values.approveDate = values.approveDate.format('YYYY-MM-DD');
-      if (values.reimbursementDate) values.reimbursementDate = values.reimbursementDate.format('YYYY-MM-DD');
+      const formattedValues = { ...values };
+      if (formattedValues.expenseDate) formattedValues.expenseDate = formattedValues.expenseDate.format('YYYY-MM-DD');
+      if (formattedValues.approveDate) formattedValues.approveDate = formattedValues.approveDate.format('YYYY-MM-DD');
+      if (formattedValues.reimbursementDate) formattedValues.reimbursementDate = formattedValues.reimbursementDate.format('YYYY-MM-DD');
       
       if (editingKey) {
         // 更新操作
-        await expenseClient[':id'].$put({
-          param: { id: editingKey },
-          json: values,
-        });
-        message.success('费用记录更新成功');
+        await updateExpense.mutateAsync({ id: editingKey, data: formattedValues });
       } else {
         // 创建操作
-        await expenseClient.$post({ json: values });
-        message.success('费用记录创建成功');
+        await createExpense.mutateAsync(formattedValues);
       }
       
       setModalVisible(false);
-      run({ page: pagination.current, pageSize: pagination.pageSize });
     } catch (error) {
-      message.error('操作失败,请重试');
+      errorLogger('Form submission failed:', error);
+      antdMessage.error('表单提交失败,请检查输入');
     }
   };
-  
+
   // 删除操作
   const handleDelete = async (id: string) => {
     try {
-      await expenseClient[':id'].$delete({ param: { id } });
-      message.success('费用记录删除成功');
-      run({ page: pagination.current, pageSize: pagination.pageSize });
+      Modal.confirm({
+        title: '确认删除',
+        content: '确定要删除这条费用记录吗?此操作不可撤销。',
+        okText: '确认',
+        cancelText: '取消',
+        onOk: async () => {
+          await deleteExpense.mutateAsync(id);
+        },
+      });
     } catch (error) {
-      message.error('删除失败,请重试');
+      errorLogger('Delete operation failed:', error);
+      antdMessage.error('删除操作失败');
     }
   };
-  
+
+  // 导出数据
+  const handleExport = () => {
+    apiLogger('Exporting expense data');
+    antdMessage.info('正在导出数据...');
+    // 实际实现应调用后端导出API
+  };
+
+  // 状态标签样式
+  const renderStatusTag = (status: string) => {
+    let color = 'default';
+    switch (status) {
+      case '待审批':
+        color = 'processing';
+        break;
+      case '已审批':
+        color = 'success';
+        break;
+      case '已报销':
+        color = 'blue';
+        break;
+      case '已拒绝':
+        color = 'error';
+        break;
+      default:
+        color = 'default';
+    }
+    return <Tag color={color}>{status}</Tag>;
+  };
+
   // 表格列定义
   const columns = [
     {
       title: '费用ID',
       dataIndex: 'id',
       key: 'id',
+      width: 80,
+      sorter: true,
     },
     {
       title: '费用日期',
       dataIndex: 'expenseDate',
       key: 'expenseDate',
-      render: (date: string) => date ? new Date(date).toLocaleDateString() : '-',
+      width: 120,
+      render: (date: string) => formatDate(date),
+      sorter: (a: ExpenseItem, b: ExpenseItem) => new Date(a.expenseDate || '').getTime() - new Date(b.expenseDate || '').getTime(),
     },
     {
       title: '费用类型',
       dataIndex: 'type',
       key: 'type',
+      width: 120,
+      filters: [
+        { text: '差旅费', value: '差旅费' },
+        { text: '招待费', value: '招待费' },
+        { text: '办公费', value: '办公费' },
+        { text: '交通费', value: '交通费' },
+        { text: '其他', value: '其他' },
+      ],
+      onFilter: (value: string, record: ExpenseItem) => record.type === value,
     },
     {
       title: '金额',
       dataIndex: 'amount',
       key: 'amount',
-      render: (amount: number) => `¥${amount.toFixed(2)}`,
+      width: 120,
+      render: (amount: number) => formatCurrency(amount),
+      sorter: (a: ExpenseItem, b: ExpenseItem) => a.amount - b.amount,
     },
     {
       title: '客户',
       dataIndex: 'clientId',
       key: 'clientId',
+      width: 160,
       render: (clientId: string) => {
         const client = clients.find(c => c.id.toString() === clientId);
         return client ? client.companyName : '-';
       },
+      filters: clients.map(client => ({
+        text: client.companyName,
+        value: client.id.toString(),
+      })),
+      onFilter: (value: string, record: ExpenseItem) => record.clientId?.toString() === value,
     },
     {
       title: '状态',
       dataIndex: 'status',
       key: 'status',
+      width: 100,
+      render: renderStatusTag,
+      filters: [
+        { text: '待审批', value: '待审批' },
+        { text: '已审批', value: '已审批' },
+        { text: '已报销', value: '已报销' },
+        { text: '已拒绝', value: '已拒绝' },
+      ],
+      onFilter: (value: string, record: ExpenseItem) => record.status === value,
     },
     {
-      title: '操作用户',
-      dataIndex: 'userId',
-      key: 'userId',
+      title: '支付方式',
+      dataIndex: 'paymentMethod',
+      key: 'paymentMethod',
+      width: 120,
     },
     {
       title: '操作',
       key: 'action',
+      width: 160,
       render: (_: any, record: ExpenseItem) => (
         <Space size="middle">
           <Button 
-            type="text" 
+            type="link" 
             icon={<EditOutlined />} 
             onClick={() => showModal(record)}
+            disabled={!access.canEditExpense}
           >
             编辑
           </Button>
           <Button 
-            type="text" 
+            type="link" 
             danger 
             icon={<DeleteOutlined />} 
-            onClick={() => handleDelete(record.id)}
+            onClick={() => handleDelete(record.id.toString())}
+            disabled={!access.canDeleteExpense}
           >
             删除
           </Button>
@@ -257,44 +373,177 @@ const Expenses: React.FC = () => {
       ),
     },
   ];
-  
+
+  // 费用类型选项
+  const expenseTypeOptions = [
+    { label: '差旅费', value: '差旅费' },
+    { label: '招待费', value: '招待费' },
+    { label: '办公费', value: '办公费' },
+    { label: '交通费', value: '交通费' },
+    { label: '其他', value: '其他' },
+  ];
+
+  // 费用状态选项
+  const expenseStatusOptions = [
+    { label: '待审批', value: '待审批' },
+    { label: '已审批', value: '已审批' },
+    { label: '已报销', value: '已报销' },
+    { label: '已拒绝', value: '已拒绝' },
+  ];
+
+  // 支付方式选项
+  const paymentMethodOptions = [
+    { label: '现金', value: '现金' },
+    { label: '银行卡', value: '银行卡' },
+    { label: '支付宝', value: '支付宝' },
+    { label: '微信', value: '微信' },
+  ];
+
   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 />} 
-          onClick={() => showModal()}
-        >
-          添加费用
-        </Button>
+    <Content className="p-4">
+      <div className="flex justify-between items-center mb-6">
+        <Title level={2}>费用管理</Title>
+        <Space>
+          <Button 
+            type="primary" 
+            icon={<PlusOutlined />} 
+            onClick={() => showModal()}
+          >
+            添加费用
+          </Button>
+          <Button 
+            icon={<DownloadOutlined />} 
+            onClick={handleExport}
+          >
+            导出
+          </Button>
+          <Button 
+            icon={<ReloadOutlined />} 
+            onClick={refetch}
+            loading={isExpensesLoading}
+          >
+            刷新
+          </Button>
+        </Space>
       </div>
-      
-      <div className="mb-4">
-        <Input
-          placeholder="搜索费用类型或描述"
-          prefix={<SearchOutlined />}
-          value={searchText}
-          onChange={(e) => setSearchText(e.target.value)}
-          onPressEnter={handleSearch}
-          style={{ width: 300 }}
+
+      <Card className="mb-6">
+        <div className="flex flex-col md:flex-row gap-4 mb-4">
+          <div className="flex-grow">
+            <Input
+              placeholder="搜索费用类型或描述"
+              prefix={<SearchOutlined />}
+              value={searchText}
+              onChange={(e) => setSearchText(e.target.value)}
+              onPressEnter={handleSearch}
+              className="w-full md:w-2/3"
+            />
+          </div>
+          <Space>
+            <Button type="default" onClick={handleSearch}>
+              搜索
+            </Button>
+            <Button 
+              icon={<FilterOutlined />} 
+              onClick={() => setFilterVisible(!filterVisible)}
+            >
+              高级筛选
+            </Button>
+            <Button onClick={resetFilters}>
+              重置
+            </Button>
+          </Space>
+        </div>
+
+        {filterVisible && (
+          <div className="border-t pt-4 mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
+            <Form.Item
+              label="客户"
+              name="clientId"
+              initialValue={filters.clientId}
+              onChange={([value]) => setFilters(prev => ({ ...prev, clientId: value }))}
+            >
+              <Select placeholder="请选择客户" style={{ width: '100%' }}>
+                {clients.map(client => (
+                  <Select.Option key={client.id} value={client.id.toString()}>
+                    {client.companyName}
+                  </Select.Option>
+                ))}
+              </Select>
+            </Form.Item>
+
+            <Form.Item
+              label="费用类型"
+              name="type"
+              initialValue={filters.type}
+              onChange={([value]) => setFilters(prev => ({ ...prev, type: value }))}
+            >
+              <Select placeholder="请选择费用类型" style={{ width: '100%' }}>
+                {expenseTypeOptions.map(option => (
+                  <Select.Option key={option.value} value={option.value}>
+                    {option.label}
+                  </Select.Option>
+                ))}
+              </Select>
+            </Form.Item>
+
+            <Form.Item
+              label="费用状态"
+              name="status"
+              initialValue={filters.status}
+              onChange={([value]) => setFilters(prev => ({ ...prev, status: value }))}
+            >
+              <Select placeholder="请选择费用状态" style={{ width: '100%' }}>
+                {expenseStatusOptions.map(option => (
+                  <Select.Option key={option.value} value={option.value}>
+                    {option.label}
+                  </Select.Option>
+                ))}
+              </Select>
+            </Form.Item>
+
+            <Form.Item
+              label="日期范围"
+              name="dateRange"
+              initialValue={filters.dateRange}
+              onChange={([value]) => setFilters(prev => ({ ...prev, dateRange: value }))}
+              className="md:col-span-3"
+            >
+              <DatePicker.RangePicker 
+                format="YYYY-MM-DD" 
+                style={{ width: '100%' }}
+                placeholder={['开始日期', '结束日期']}
+              />
+            </Form.Item>
+
+            <div className="md:col-span-3 flex justify-end">
+              <Button type="primary" onClick={handleSearch}>
+                应用筛选
+              </Button>
+            </div>
+          </div>
+        )}
+      </Card>
+
+      <Spin spinning={isExpensesLoading || isClientsLoading}>
+        <Table
+          columns={columns}
+          dataSource={dataSource}
+          rowKey="id"
+          pagination={{
+            ...pagination,
+            showSizeChanger: true,
+            showTotal: (total) => `共 ${total} 条记录`,
+            pageSizeOptions: ['10', '20', '50', '100'],
+          }}
+          onChange={handleTableChange}
+          bordered
+          size="middle"
+          scroll={{ x: 'max-content' }}
+          rowClassName={(record, index) => index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}
         />
-        <Button type="default" onClick={handleSearch} style={{ marginLeft: 8 }}>
-          搜索
-        </Button>
-      </div>
-      
-      <Table
-        columns={columns}
-        dataSource={dataSource}
-        rowKey="id"
-        loading={loading}
-        pagination={pagination}
-        onChange={handleTableChange}
-        bordered
-      />
-      
+      </Spin>
+
       <Modal
         title={editingKey ? "编辑费用记录" : "添加费用记录"}
         open={modalVisible}
@@ -308,50 +557,80 @@ const Expenses: React.FC = () => {
           </Button>,
         ]}
         width={800}
+        destroyOnClose
+        centered
+        maskClosable={false}
       >
-        <Form form={form} layout="vertical">
+        <Form 
+          form={form} 
+          layout="vertical"
+          validateMessages={{
+            required: '${label} 不能为空!',
+            types: {
+              number: '${label} 必须是数字!',
+            },
+          }}
+        >
           {!editingKey && (
             <Form.Item
               name="id"
               label="费用记录ID"
               rules={[{ required: true, message: '请输入费用记录ID' }]}
             >
-              <Input placeholder="请输入费用记录ID" />
+              <Input placeholder="请输入费用记录ID" disabled={editingKey !== null} />
             </Form.Item>
           )}
           
-          <div className="grid grid-cols-2 gap-4">
+          <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
             <Form.Item
               name="expenseDate"
               label="费用发生日期"
               rules={[{ required: true, message: '请选择费用发生日期' }]}
             >
-              <DatePicker format="YYYY-MM-DD" />
+              <DatePicker 
+                format="YYYY-MM-DD" 
+                style={{ width: '100%' }}
+                placeholder="请选择日期"
+                disabledDate={(current) => current && current > dayjs().endOf('day')}
+              />
             </Form.Item>
             
             <Form.Item
               name="type"
               label="费用类型"
-              rules={[{ required: true, message: '请输入费用类型' }]}
+              rules={[{ required: true, message: '请选择费用类型' }]}
             >
-              <Input placeholder="请输入费用类型" />
+              <Select placeholder="请选择费用类型" style={{ width: '100%' }}>
+                {expenseTypeOptions.map(option => (
+                  <Select.Option key={option.value} value={option.value}>
+                    {option.label}
+                  </Select.Option>
+                ))}
+              </Select>
             </Form.Item>
-          </div>
-          
-          <div className="grid grid-cols-2 gap-4">
+            
             <Form.Item
               name="amount"
               label="费用金额"
-              rules={[{ required: true, message: '请输入费用金额' }]}
+              rules={[
+                { required: true, message: '请输入费用金额' },
+                { type: 'number', message: '金额必须是数字' },
+                { min: 0.01, message: '金额必须大于0' }
+              ]}
             >
-              <Input type="number" placeholder="请输入费用金额" />
+              <Input 
+                type="number" 
+                placeholder="请输入费用金额" 
+                precision={2}
+                style={{ width: '100%' }}
+              />
             </Form.Item>
             
             <Form.Item
               name="clientId"
               label="关联客户"
             >
-              <Select placeholder="请选择客户">
+              <Select placeholder="请选择客户" style={{ width: '100%' }}>
                 {clients.map(client => (
                   <Select.Option key={client.id} value={client.id.toString()}>
                     {client.companyName}
@@ -359,73 +638,101 @@ const Expenses: React.FC = () => {
                 ))}
               </Select>
             </Form.Item>
-          </div>
-          
-          <div className="grid grid-cols-2 gap-4">
+            
             <Form.Item
               name="status"
               label="费用状态"
-              rules={[{ required: true, message: '请输入费用状态' }]}
+              rules={[{ required: true, message: '请选择费用状态' }]}
             >
-              <Input placeholder="请输入费用状态:如审批中、已报销等" />
+              <Select placeholder="请选择费用状态" style={{ width: '100%' }}>
+                {expenseStatusOptions.map(option => (
+                  <Select.Option key={option.value} value={option.value}>
+                    {option.label}
+                  </Select.Option>
+                ))}
+              </Select>
             </Form.Item>
             
-            <Form.Item
-              name="userId"
-              label="操作用户ID"
-              rules={[{ required: true, message: '请输入操作用户ID' }]}
-            >
-              <Input placeholder="请输入操作用户ID" />
-            </Form.Item>
-          </div>
-          
-          <div className="grid grid-cols-2 gap-4">
             <Form.Item
               name="paymentMethod"
               label="支付方式"
+              rules={[{ required: true, message: '请选择支付方式' }]}
             >
-              <Input placeholder="请输入支付方式" />
+              <Select placeholder="请选择支付方式" style={{ width: '100%' }}>
+                {paymentMethodOptions.map(option => (
+                  <Select.Option key={option.value} value={option.value}>
+                    {option.label}
+                  </Select.Option>
+                ))}
+              </Select>
             </Form.Item>
             
             <Form.Item
               name="invoiceNumber"
               label="发票号码"
             >
-              <Input placeholder="请输入发票号码" />
+              <Input placeholder="请输入发票号码" style={{ width: '100%' }} />
             </Form.Item>
-          </div>
-          
-          <div className="grid grid-cols-2 gap-4">
+            
+            <Form.Item
+              name="userId"
+              label="操作用户ID"
+              rules={[{ required: true, message: '请输入操作用户ID' }]}
+            >
+              <Input placeholder="请输入操作用户ID" style={{ width: '100%' }} />
+            </Form.Item>
+            
             <Form.Item
               name="currency"
               label="货币类型"
               initialValue="CNY"
             >
-              <Input placeholder="请输入货币类型" />
+              <Select placeholder="请选择货币类型" style={{ width: '100%' }}>
+                <Select.Option value="CNY">人民币 (CNY)</Select.Option>
+                <Select.Option value="USD">美元 (USD)</Select.Option>
+                <Select.Option value="EUR">欧元 (EUR)</Select.Option>
+              </Select>
             </Form.Item>
             
             <Form.Item
               name="exchangeRate"
               label="汇率"
               initialValue={1}
+              rules={[
+                { type: 'number', message: '汇率必须是数字' },
+                { min: 0.0001, message: '汇率必须大于0' }
+              ]}
             >
-              <Input type="number" step="0.0001" placeholder="请输入汇率" />
+              <Input 
+                type="number" 
+                step="0.0001" 
+                placeholder="请输入汇率" 
+                style={{ width: '100%' }}
+              />
             </Form.Item>
-          </div>
-          
-          <div className="grid grid-cols-2 gap-4">
+            
             <Form.Item
               name="approveDate"
               label="审批日期"
             >
-              <DatePicker format="YYYY-MM-DD" />
+              <DatePicker 
+                format="YYYY-MM-DD" 
+                style={{ width: '100%' }}
+                placeholder="请选择审批日期"
+                disabledDate={(current) => current && current > dayjs().endOf('day')}
+              />
             </Form.Item>
             
             <Form.Item
               name="reimbursementDate"
               label="报销日期"
             >
-              <DatePicker format="YYYY-MM-DD" />
+              <DatePicker 
+                format="YYYY-MM-DD" 
+                style={{ width: '100%' }}
+                placeholder="请选择报销日期"
+                disabledDate={(current) => current && current > dayjs().endOf('day')}
+              />
             </Form.Item>
           </div>
           
@@ -437,8 +744,8 @@ const Expenses: React.FC = () => {
           </Form.Item>
         </Form>
       </Modal>
-    </div>
+    </Content>
   );
 };
 
-export default Expenses;
+export default Expenses;