From 7ba9f537fb5743d725f1d9a3029fcb5703c50c31 Mon Sep 17 00:00:00 2001 From: liamzi Date: Wed, 18 Dec 2024 17:46:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=A2=E6=88=B7=EF=BC=8C=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=95=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/routes.js | 20 ++ src/hooks/resource/useResource.js | 3 +- src/pages/company/customer/detail/index.jsx | 210 ++++++++++++++++++ src/pages/company/customer/index.jsx | 151 ++++++++++++- src/pages/company/quotation/detail/index.jsx | 82 +++++-- src/pages/company/quotation/index.jsx | 32 ++- src/pages/company/quotation/view/index.jsx | 7 +- src/pages/company/supplier/detail/index.jsx | 198 +++++++++++++++++ src/pages/company/supplier/index.jsx | 133 ++++++++++- .../team/components/MembershipTable.jsx | 5 + .../resource/team/components/TeamTable.jsx | 7 +- src/services/supabase/resource.js | 4 +- 12 files changed, 787 insertions(+), 65 deletions(-) create mode 100644 src/pages/company/customer/detail/index.jsx create mode 100644 src/pages/company/supplier/detail/index.jsx 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 ? '请修改客户信息' : '请填写客户信息'} + +
+ + + {!isView && ( + + )} + +
+ } + 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) => ( + + + handleDelete(record.id)} + okText="确定" + cancelText="取消" + okButtonProps={{ danger: true }} + > + + + + ), }, ]; return ( + 客户管理 + {total} 个客户 + + } + className='h-full w-full overflow-auto' extra={ - } > - +
`共 ${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) => ( 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 ? '请修改供应商信息' : '供应商详情') : '请填写供应商信息'} + +
+ + + {!isView && ( + + )} + +
+ } + 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) => ( + + + handleDelete(record.id)} + okText="确定" + cancelText="取消" + okButtonProps={{ danger: true }} + > + + + + ), }, ]; return ( + 供应商管理 + {total} 个供应商 + + } + className='h-full w-full overflow-auto' extra={ - } > -
+
`共 ${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 }) => {