diff --git a/src/config/routes.js b/src/config/routes.js
index cd8f0f7..76795ca 100644
--- a/src/config/routes.js
+++ b/src/config/routes.js
@@ -51,6 +51,26 @@ const companyRoutes = [
name: '客户管理',
icon: 'user',
},
+ {
+ path: 'customerInfo/:id?',
+ hidden: true,
+ component: lazy(() => import('@/pages/company/customer/detail')),
+ name: '客户详情',
+ icon: 'user',
+ },
+ {
+ path: 'supplier',
+ component: lazy(() => import('@/pages/company/supplier')),
+ name: '供应商管理',
+ icon: 'branches',
+ },
+ {
+ path: 'supplierInfo/:id?',
+ hidden: true,
+ component: lazy(() => import('@/pages/company/supplier/detail')),
+ name: '供应商详情',
+ icon: 'branches',
+ },
];
const marketingRoutes = [
diff --git a/src/hooks/resource/useResource.js b/src/hooks/resource/useResource.js
index 248b7ca..46cbaf7 100644
--- a/src/hooks/resource/useResource.js
+++ b/src/hooks/resource/useResource.js
@@ -2,7 +2,7 @@ import { useState, useCallback } from 'react';
import { message } from 'antd';
import { resourceService } from '@/services/supabase/resource';
-export const useResources = (initialPagination, initialSorter) => {
+export const useResources = (initialPagination, initialSorter,type) => {
const [resources, setResources] = useState([]);
const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(0);
@@ -29,6 +29,7 @@ export const useResources = (initialPagination, initialSorter) => {
pageSize: newPagination.pageSize,
orderBy: newSorter.field,
ascending: newSorter.order === 'ascend',
+ type: type,
...(params?.search !== '' ? { searchQuery: params.search } : {})
});
diff --git a/src/pages/company/customer/detail/index.jsx b/src/pages/company/customer/detail/index.jsx
new file mode 100644
index 0000000..a3ac626
--- /dev/null
+++ b/src/pages/company/customer/detail/index.jsx
@@ -0,0 +1,210 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Select, Button, Space, Card, Typography } from 'antd';
+import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons';
+import { supabase } from '@/config/supabase';
+import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
+
+const { TextArea } = Input;
+const { Title } = Typography;
+
+const CustomerForm = () => {
+ const { id } = useParams();
+ const [searchParams] = useSearchParams();
+ const isView = id&&!searchParams.get('edit');
+ const [form] = Form.useForm();
+ const navigate = useNavigate();
+
+ const fetchCustomerDetail = async () => {
+ try {
+ const { data, error } = await supabase
+ .from('resources')
+ .select('*')
+ .eq('id', id)
+ .single();
+
+ if (error) throw error;
+
+ if (id) {
+ form.setFieldsValue({
+ name: data.attributes.name,
+ contact: data.attributes.contact,
+ phone: data.attributes.phone,
+ address: data.attributes.address,
+ level: data.attributes.level || 'regular',
+ status: data.attributes.status || 'active',
+ remark: data.attributes.remark
+ });
+ }
+ } catch (error) {
+ console.error('获取客户详情失败:', error);
+ }
+ };
+
+ useEffect(() => {
+ if (id) {
+ fetchCustomerDetail();
+ }
+ }, [id]);
+
+ const onFinish = async (values) => {
+ try {
+ const customerData = {
+ type: 'customer',
+ attributes: {
+ name: values.name,
+ contact: values.contact,
+ phone: values.phone,
+ address: values.address,
+ level: values.level,
+ status: values.status,
+ remark: values.remark
+ }
+ };
+
+ let result;
+ if (id) {
+ result = await supabase
+ .from('resources')
+ .update(customerData)
+ .eq('id', id)
+ .select();
+ } else {
+ result = await supabase
+ .from('resources')
+ .insert([customerData])
+ .select();
+ }
+
+ if (result.error) throw result.error;
+ navigate('/company/customer');
+ } catch (error) {
+ console.error('保存失败:', error);
+ }
+ };
+
+ return (
+
+
+
+
+ {id ? '编辑客户' : '新建客户'}
+
+
+ {id ? '请修改客户信息' : '请填写客户信息'}
+
+
+
+ }
+ onClick={() => navigate('/company/customer')}
+ >
+ 返回
+
+ {!isView && (
+ }
+ onClick={() => form.submit()}
+ >
+ 保存
+
+ )}
+
+
+ }
+ bodyStyle={{ backgroundColor: '#fff' }}
+ >
+
+
+
+ );
+};
+
+export default CustomerForm;
\ No newline at end of file
diff --git a/src/pages/company/customer/index.jsx b/src/pages/company/customer/index.jsx
index f86512d..0691b2c 100644
--- a/src/pages/company/customer/index.jsx
+++ b/src/pages/company/customer/index.jsx
@@ -1,41 +1,172 @@
-import React from 'react';
-import { Card, Table, Button } from 'antd';
-import { PlusOutlined } from '@ant-design/icons';
+import React, { useEffect, useState } from 'react';
+import { Card, Table, Button, message, Popconfirm, Tag, Space } from 'antd';
+import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
+import { useResources } from '@/hooks/resource/useResource';
+import { useNavigate } from 'react-router-dom';
const CustomerPage = () => {
+ const navigate = useNavigate();
+ const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
+ const [sorter, setSorter] = useState({ field: 'created_at', order: 'descend' });
+
+ const {
+ resources: customers,
+ loading,
+ total,
+ fetchResources: fetchCustomers,
+ deleteResource: deleteCustomer
+ } = useResources(pagination, sorter, 'customer');
+
+ useEffect(() => {
+ fetchCustomers();
+ }, []);
+
+ const handleTableChange = (pagination, filters, sorter) => {
+ setPagination(pagination);
+ setSorter(sorter);
+ fetchCustomers({
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ field: sorter.field,
+ order: sorter.order,
+ });
+ };
+
+ const handleDelete = async (id) => {
+ try {
+ await deleteCustomer(id);
+ message.success('删除成功');
+ fetchCustomers();
+ } catch (error) {
+ message.error('删除失败:' + error.message);
+ }
+ };
+
const columns = [
{
title: '客户名称',
- dataIndex: 'name',
+ dataIndex: ['attributes', 'name'],
key: 'name',
+ ellipsis: true,
},
{
title: '联系人',
- dataIndex: 'contact',
+ dataIndex: ['attributes', 'contact'],
key: 'contact',
},
{
title: '电话',
- dataIndex: 'phone',
+ dataIndex: ['attributes', 'phone'],
key: 'phone',
},
+ {
+ title: '客户等级',
+ dataIndex: ['attributes', 'level'],
+ key: 'level',
+ render: (level) => {
+ const colors = {
+ vip: 'gold',
+ regular: 'blue',
+ potential: 'green'
+ };
+ const labels = {
+ vip: 'VIP客户',
+ regular: '普通客户',
+ potential: '潜在客户'
+ };
+ return {labels[level]};
+ },
+ },
{
title: '状态',
- dataIndex: 'status',
+ dataIndex: ['attributes', 'status'],
key: 'status',
+ render: (status) => (
+
+ {status === 'active' ? '启用' : '禁用'}
+
+ ),
+ },
+ {
+ title: '创建日期',
+ dataIndex: 'created_at',
+ key: 'created_at',
+ sorter: true,
+ width: 180,
+ render: (text) => (
+ {new Date(text).toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ })}
+ ),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ width: 150,
+ render: (_, record) => (
+
+ }
+ onClick={() => navigate(`/company/customerInfo/${record.id}?edit=true`)}
+ >
+ 编辑
+
+ handleDelete(record.id)}
+ okText="确定"
+ cancelText="取消"
+ okButtonProps={{ danger: true }}
+ >
+ }>
+ 删除
+
+
+
+ ),
},
];
return (
+ 客户管理
+ {total} 个客户
+
+ }
+ className='h-full w-full overflow-auto'
extra={
- }>
+ }
+ onClick={() => navigate('/company/customerInfo')}
+ >
新增客户
}
>
-
+ `共 ${total} 条记录`,
+ }}
+ />
);
};
diff --git a/src/pages/company/quotation/detail/index.jsx b/src/pages/company/quotation/detail/index.jsx
index 0f4b093..a05e920 100644
--- a/src/pages/company/quotation/detail/index.jsx
+++ b/src/pages/company/quotation/detail/index.jsx
@@ -25,6 +25,7 @@ const QuotationForm = () => {
const [dataSource, setDataSource] = useState([{ id: Date.now() }]);
const [totalAmount, setTotalAmount] = useState(0);
const [currentCurrency, setCurrentCurrency] = useState('CNY');
+ const [customers, setCustomers] = useState([]);
const calculateTotal = (items = []) => {
const total = items.reduce((sum, item) => {
@@ -127,7 +128,7 @@ const QuotationForm = () => {
},
{
title: '操作',
- width: '10%',
+ width: 100,
render: (_, record, index) => (
}
onClick={() => navigate(`/company/quotaInfo/${record.id}?edit=true`)}
@@ -123,7 +117,7 @@ const QuotationPage = () => {
cancelText="取消"
okButtonProps={{ danger: true }}
>
- }>
+ }>
删除
diff --git a/src/pages/company/quotation/view/index.jsx b/src/pages/company/quotation/view/index.jsx
index 7940d8f..d271452 100644
--- a/src/pages/company/quotation/view/index.jsx
+++ b/src/pages/company/quotation/view/index.jsx
@@ -63,7 +63,7 @@ const QuotationView = () => {
if (error) throw error;
setData(quotation);
} catch (error) {
- console.error('获取报价单��情失败:', error);
+ console.error('获取报价单失败:', error);
} finally {
setLoading(false);
}
@@ -197,10 +197,7 @@ const QuotationView = () => {
- {data.attributes.companyName}
-
-
- {data.attributes.supplierName}
+ {data.attributes.customerName}
{new Date(data.created_at).toLocaleDateString('zh-CN')}
diff --git a/src/pages/company/supplier/detail/index.jsx b/src/pages/company/supplier/detail/index.jsx
new file mode 100644
index 0000000..7dc686a
--- /dev/null
+++ b/src/pages/company/supplier/detail/index.jsx
@@ -0,0 +1,198 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Select, Button, Space, Card, Typography } from 'antd';
+import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons';
+import { supabase } from '@/config/supabase';
+import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
+
+const { TextArea } = Input;
+const { Title } = Typography;
+
+const SupplierForm = () => {
+ const { id } = useParams();
+ const [searchParams] = useSearchParams();
+ const isEdit = searchParams.get('edit') === 'true';
+ const isView = id && !isEdit;
+ const [form] = Form.useForm();
+ const navigate = useNavigate();
+
+ const fetchSupplierDetail = async () => {
+ try {
+ const { data, error } = await supabase
+ .from('resources')
+ .select('*')
+ .eq('id', id)
+ .single();
+
+ if (error) throw error;
+
+ if (id) {
+ // 设置表单初始值
+ form.setFieldsValue({
+ name: data.attributes.name,
+ contact: data.attributes.contact,
+ phone: data.attributes.phone,
+ address: data.attributes.address,
+ status: data.attributes.status || 'active',
+ remark: data.attributes.remark
+ });
+ }
+ } catch (error) {
+ console.error('获取供应商详情失败:', error);
+ }
+ };
+
+ useEffect(() => {
+ if (id) {
+ fetchSupplierDetail();
+ }
+ }, [id]);
+
+ const onFinish = async (values) => {
+ try {
+ const supplierData = {
+ type: 'supplier',
+ attributes: {
+ name: values.name,
+ contact: values.contact,
+ phone: values.phone,
+ address: values.address,
+ status: values.status,
+ remark: values.remark
+ }
+ };
+
+ let result;
+ if (id) {
+ result = await supabase
+ .from('resources')
+ .update(supplierData)
+ .eq('id', id)
+ .select();
+ } else {
+ result = await supabase
+ .from('resources')
+ .insert([supplierData])
+ .select();
+ }
+
+ if (result.error) throw result.error;
+ navigate('/company/supplier');
+ } catch (error) {
+ console.error('保存失败:', error);
+ }
+ };
+
+ return (
+
+
+
+
+ {id ? (isEdit ? '编辑供应商' : '查看供应商') : '新建供应商'}
+
+
+ {id ? (isEdit ? '请修改供应商信息' : '供应商详情') : '请填写供应商信息'}
+
+
+
+ }
+ onClick={() => navigate('/company/supplier')}
+ >
+ 返回
+
+ {!isView && (
+ }
+ onClick={() => form.submit()}
+ >
+ 保存
+
+ )}
+
+
+ }
+ bodyStyle={{ backgroundColor: '#fff' }}
+ >
+
+
+
+ );
+};
+
+export default SupplierForm;
\ No newline at end of file
diff --git a/src/pages/company/supplier/index.jsx b/src/pages/company/supplier/index.jsx
index b48f159..69c3aa6 100644
--- a/src/pages/company/supplier/index.jsx
+++ b/src/pages/company/supplier/index.jsx
@@ -1,41 +1,154 @@
-import React from 'react';
-import { Card, Table, Button } from 'antd';
-import { PlusOutlined } from '@ant-design/icons';
+import React, { useEffect, useState } from 'react';
+import { Card, Table, Button, message, Popconfirm, Tag, Space } from 'antd';
+import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
+import { useResources } from '@/hooks/resource/useResource';
+import { useNavigate } from 'react-router-dom';
const SupplierPage = () => {
+ const navigate = useNavigate();
+ const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
+ const [sorter, setSorter] = useState({ field: 'created_at', order: 'descend' });
+
+ const {
+ resources: suppliers,
+ loading,
+ total,
+ fetchResources: fetchSuppliers,
+ deleteResource: deleteSupplier
+ } = useResources(pagination, sorter, 'supplier');
+
+ useEffect(() => {
+ fetchSuppliers();
+ }, []);
+
+ const handleTableChange = (pagination, filters, sorter) => {
+ setPagination(pagination);
+ setSorter(sorter);
+ fetchSuppliers({
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ field: sorter.field,
+ order: sorter.order,
+ });
+ };
+
+ const handleDelete = async (id) => {
+ try {
+ await deleteSupplier(id);
+ message.success('删除成功');
+ fetchSuppliers();
+ } catch (error) {
+ message.error('删除失败:' + error.message);
+ }
+ };
+
const columns = [
{
title: '供应商名称',
- dataIndex: 'name',
+ dataIndex: ['attributes', 'name'],
key: 'name',
+ ellipsis: true,
},
{
title: '联系人',
- dataIndex: 'contact',
+ dataIndex: ['attributes', 'contact'],
key: 'contact',
},
{
title: '电话',
- dataIndex: 'phone',
+ dataIndex: ['attributes', 'phone'],
key: 'phone',
},
{
title: '状态',
- dataIndex: 'status',
+ dataIndex: ['attributes', 'status'],
key: 'status',
+ render: (status) => (
+
+ {status === 'active' ? '启用' : '禁用'}
+
+ ),
+ },
+ {
+ title: '创建日期',
+ dataIndex: 'created_at',
+ key: 'created_at',
+ sorter: true,
+ width: 180,
+ render: (text) => (
+ {new Date(text).toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ })}
+ ),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ width: 150,
+ render: (_, record) => (
+
+ }
+ onClick={() => navigate(`/company/supplierInfo/${record.id}?edit=true`)}
+ >
+ 编辑
+
+ handleDelete(record.id)}
+ okText="确定"
+ cancelText="取消"
+ okButtonProps={{ danger: true }}
+ >
+ }>
+ 删除
+
+
+
+ ),
},
];
return (
+ 供应商管理
+ {total} 个供应商
+
+ }
+ className='h-full w-full overflow-auto'
extra={
- }>
+ }
+ onClick={() => navigate('/company/supplierInfo')}
+ >
新增供应商
}
>
-
+ `共 ${total} 条记录`,
+ }}
+ />
);
};
diff --git a/src/pages/resource/team/components/MembershipTable.jsx b/src/pages/resource/team/components/MembershipTable.jsx
index 623a7eb..8b5b324 100644
--- a/src/pages/resource/team/components/MembershipTable.jsx
+++ b/src/pages/resource/team/components/MembershipTable.jsx
@@ -68,6 +68,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
{
title: '操作',
key: 'action',
+ width: 180,
render: (_, record) => {
const editable = isEditing(record);
return editable ? (
@@ -76,6 +77,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
icon={}
onClick={() => save(record.id)}
type="link"
+ size='small'
>
保存
@@ -83,6 +85,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
icon={}
onClick={cancel}
type="link"
+ size='small'
>
取消
@@ -94,6 +97,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
icon={}
onClick={() => edit(record)}
type="link"
+ size='small'
>
编辑
@@ -105,6 +109,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
}
type="link"
+ size='small'
danger
>
删除
diff --git a/src/pages/resource/team/components/TeamTable.jsx b/src/pages/resource/team/components/TeamTable.jsx
index f682f92..d4e089a 100644
--- a/src/pages/resource/team/components/TeamTable.jsx
+++ b/src/pages/resource/team/components/TeamTable.jsx
@@ -100,19 +100,22 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
{
title: '操作',
key: 'action',
+ width: 180,
render: (_, record) => {
const editable = isEditing(record);
return editable ? (
-
+
}
onClick={() => save(record.id)}
type="link"
+ size='small'
loading={loading}
>
保存
}
onClick={cancel}
type="link"
@@ -123,6 +126,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
) : (
}
onClick={() => edit(record)}
@@ -138,6 +142,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
icon={}
type="link"
danger
+ size='small'
loading={loading}
>
删除
diff --git a/src/services/supabase/resource.js b/src/services/supabase/resource.js
index ded33e8..8e6873e 100644
--- a/src/services/supabase/resource.js
+++ b/src/services/supabase/resource.js
@@ -1,12 +1,12 @@
import { supabase } from '@/config/supabase';
export const resourceService = {
- async getResources({ page = 1, pageSize = 10, orderBy = 'created_at', ascending = false, searchQuery = '' }) {
+ async getResources({ page = 1, pageSize = 10, orderBy = 'created_at', ascending = false, searchQuery = '', type = '' }) {
try {
let query = supabase
.from('resources')
.select('*', { count: 'exact' })
- .eq('type','quota')
+ .eq('type', type)
if (searchQuery) {
query = query.or(`external_id.ilike.%${searchQuery}%`);