客户,供应商

This commit is contained in:
liamzi
2024-12-18 17:46:56 +08:00
parent 9b4a7f5fd8
commit 7ba9f537fb
12 changed files with 787 additions and 65 deletions

View File

@@ -51,6 +51,26 @@ const companyRoutes = [
name: '客户管理', name: '客户管理',
icon: 'user', 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 = [ const marketingRoutes = [

View File

@@ -2,7 +2,7 @@ import { useState, useCallback } from 'react';
import { message } from 'antd'; import { message } from 'antd';
import { resourceService } from '@/services/supabase/resource'; import { resourceService } from '@/services/supabase/resource';
export const useResources = (initialPagination, initialSorter) => { export const useResources = (initialPagination, initialSorter,type) => {
const [resources, setResources] = useState([]); const [resources, setResources] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
@@ -29,6 +29,7 @@ export const useResources = (initialPagination, initialSorter) => {
pageSize: newPagination.pageSize, pageSize: newPagination.pageSize,
orderBy: newSorter.field, orderBy: newSorter.field,
ascending: newSorter.order === 'ascend', ascending: newSorter.order === 'ascend',
type: type,
...(params?.search !== '' ? { searchQuery: params.search } : {}) ...(params?.search !== '' ? { searchQuery: params.search } : {})
}); });

View File

@@ -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 (
<div className="bg-gradient-to-b from-gray-50 to-white min-h-screen p-6">
<Card
className="shadow-lg rounded-lg border-0"
title={
<div className="flex justify-between items-center py-2">
<div className="flex items-center space-x-3">
<Title level={4} className="mb-0 text-gray-800">
{id ? '编辑客户' : '新建客户'}
</Title>
<span className="text-gray-400 text-sm">
{id ? '请修改客户信息' : '请填写客户信息'}
</span>
</div>
<Space size="middle">
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/company/customer')}
>
返回
</Button>
{!isView && (
<Button
type="primary"
icon={<SaveOutlined />}
onClick={() => form.submit()}
>
保存
</Button>
)}
</Space>
</div>
}
bodyStyle={{ backgroundColor: '#fff' }}
>
<Form
form={form}
onFinish={onFinish}
layout="vertical"
initialValues={{
status: 'active',
level: 'regular'
}}
className="space-y-6"
disabled={isView}
>
<Card
className="shadow-sm rounded-lg"
type="inner"
title={
<span className="flex items-center space-x-2 text-gray-700">
<span className="w-1 h-4 bg-blue-500 rounded-full"/>
<span>基本信息</span>
</span>
}
>
<Form.Item
label="客户名称"
name="name"
rules={[{ required: true, message: '请输入客户名称' }]}
>
<Input placeholder="请输入客户名称" />
</Form.Item>
<Form.Item
label="联系人"
name="contact"
rules={[{ required: true, message: '请输入联系人姓名' }]}
>
<Input placeholder="请输入联系人姓名" />
</Form.Item>
<Form.Item
label="联系电话"
name="phone"
rules={[
{ required: true, message: '请输入联系电话' },
]}
>
<Input placeholder="请输入联系电话" />
</Form.Item>
<Form.Item
label="客户等级"
name="level"
rules={[{ required: true, message: '请选择客户等级' }]}
>
<Select>
<Select.Option value="vip">VIP客户</Select.Option>
<Select.Option value="regular">普通客户</Select.Option>
<Select.Option value="potential">潜在客户</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="地址"
name="address"
>
<Input.TextArea rows={3} placeholder="请输入详细地址" />
</Form.Item>
<Form.Item
label="状态"
name="status"
rules={[{ required: true, message: '请选择状态' }]}
>
<Select>
<Select.Option value="active">启用</Select.Option>
<Select.Option value="inactive">禁用</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="备注"
name="remark"
>
<Input.TextArea rows={3} placeholder="请输入备注信息" />
</Form.Item>
</Card>
</Form>
</Card>
</div>
);
};
export default CustomerForm;

View File

@@ -1,41 +1,172 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Card, Table, Button } from 'antd'; import { Card, Table, Button, message, Popconfirm, Tag, Space } from 'antd';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { useResources } from '@/hooks/resource/useResource';
import { useNavigate } from 'react-router-dom';
const CustomerPage = () => { 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 = [ const columns = [
{ {
title: '客户名称', title: '客户名称',
dataIndex: 'name', dataIndex: ['attributes', 'name'],
key: 'name', key: 'name',
ellipsis: true,
}, },
{ {
title: '联系人', title: '联系人',
dataIndex: 'contact', dataIndex: ['attributes', 'contact'],
key: 'contact', key: 'contact',
}, },
{ {
title: '电话', title: '电话',
dataIndex: 'phone', dataIndex: ['attributes', 'phone'],
key: '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 <Tag color={colors[level]}>{labels[level]}</Tag>;
},
},
{ {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: ['attributes', 'status'],
key: 'status', key: 'status',
render: (status) => (
<Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '启用' : '禁用'}
</Tag>
),
},
{
title: '创建日期',
dataIndex: 'created_at',
key: 'created_at',
sorter: true,
width: 180,
render: (text) => (
<span>{new Date(text).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})}</span>
),
},
{
title: '操作',
key: 'action',
width: 150,
render: (_, record) => (
<Space size={0}>
<Button
size='small'
type="link"
icon={<EditOutlined />}
onClick={() => navigate(`/company/customerInfo/${record.id}?edit=true`)}
>
编辑
</Button>
<Popconfirm
title="确定要删除这个客户吗?"
description="删除后将无法恢复!"
onConfirm={() => handleDelete(record.id)}
okText="确定"
cancelText="取消"
okButtonProps={{ danger: true }}
>
<Button size='small' type="link" danger icon={<DeleteOutlined />}>
删除
</Button>
</Popconfirm>
</Space>
),
}, },
]; ];
return ( return (
<Card <Card
title="客户管理" title={
<Space>
<span>客户管理</span>
<Tag color="blue">{total} 个客户</Tag>
</Space>
}
className='h-full w-full overflow-auto'
extra={ extra={
<Button type="primary" icon={<PlusOutlined />}> <Button
type="primary"
icon={<PlusOutlined />}
onClick={() => navigate('/company/customerInfo')}
>
新增客户 新增客户
</Button> </Button>
} }
> >
<Table columns={columns} dataSource={[]} rowKey="id" /> <Table
columns={columns}
dataSource={customers}
rowKey="id"
loading={loading}
onChange={handleTableChange}
pagination={{
...pagination,
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
}}
/>
</Card> </Card>
); );
}; };

View File

@@ -25,6 +25,7 @@ const QuotationForm = () => {
const [dataSource, setDataSource] = useState([{ id: Date.now() }]); const [dataSource, setDataSource] = useState([{ id: Date.now() }]);
const [totalAmount, setTotalAmount] = useState(0); const [totalAmount, setTotalAmount] = useState(0);
const [currentCurrency, setCurrentCurrency] = useState('CNY'); const [currentCurrency, setCurrentCurrency] = useState('CNY');
const [customers, setCustomers] = useState([]);
const calculateTotal = (items = []) => { const calculateTotal = (items = []) => {
const total = items.reduce((sum, item) => { const total = items.reduce((sum, item) => {
@@ -127,7 +128,7 @@ const QuotationForm = () => {
}, },
{ {
title: '操作', title: '操作',
width: '10%', width: 100,
render: (_, record, index) => ( render: (_, record, index) => (
<Button <Button
type="link" type="link"
@@ -157,13 +158,13 @@ const QuotationForm = () => {
if (error) throw error; if (error) throw error;
if (isEdit) { if (isEdit) {
// 设置表单初始值
form.setFieldsValue({ form.setFieldsValue({
quataName: data.attributes.quataName, quataName: data.attributes.quataName,
companyName: data.attributes.companyName, customer: {
supplierName: data.attributes.supplierName, id: data.attributes.customerId,
name: data.attributes.customerName
},
description: data.attributes.description, description: data.attributes.description,
currency: data.attributes.currency, currency: data.attributes.currency,
items: data.attributes.items, items: data.attributes.items,
@@ -194,8 +195,8 @@ const QuotationForm = () => {
type: 'quota', type: 'quota',
attributes: { attributes: {
quataName: values.quataName, quataName: values.quataName,
companyName: values.companyName, customerId: values.customer.id,
supplierName: values.supplierName, customerName: values.customer.name,
description: values.description, description: values.description,
currency: values.currency, currency: values.currency,
items: values.items, items: values.items,
@@ -224,7 +225,25 @@ const QuotationForm = () => {
} }
}; };
const fetchCustomers = async () => {
try {
const { data, error } = await supabase
.from('resources')
.select('*')
.eq('type', 'customer')
.eq('attributes->>status', 'active'); // 只获取启用状态的客户
if (error) throw error;
setCustomers(data || []);
} catch (error) {
console.error('获取客户列表失败:', error);
}
};
useEffect(() => {
fetchCustomers();
}, []);
return ( return (
<div className="bg-gradient-to-b from-gray-50 to-white min-h-screen p-6"> <div className="bg-gradient-to-b from-gray-50 to-white min-h-screen p-6">
@@ -259,7 +278,7 @@ const QuotationForm = () => {
</Space> </Space>
</div> </div>
} }
bodyStyle={{ backgroundColor: '#fff' }} style={{ backgroundColor: '#fff' }}
> >
<Form <Form
form={form} form={form}
@@ -297,7 +316,7 @@ const QuotationForm = () => {
/> />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
name="companyName" name="companyName"
label={<span className="text-gray-700 font-medium">公司名称</span>} label={<span className="text-gray-700 font-medium">公司名称</span>}
rules={[{ required: true, message: '请输入公司名称' }]} rules={[{ required: true, message: '请输入公司名称' }]}
@@ -306,17 +325,46 @@ const QuotationForm = () => {
placeholder="请输入公司名称" placeholder="请输入公司名称"
className="rounded-md hover:border-blue-400 focus:border-blue-500" className="rounded-md hover:border-blue-400 focus:border-blue-500"
/> />
</Form.Item> */}
<Form.Item
name={['customer', 'name']}
label={<span className="text-gray-700 font-medium">客户名称</span>}
rules={[{ required: true, message: '请选择客户' }]}
>
<Select
placeholder="请选择客户"
className="rounded-md hover:border-blue-400 focus:border-blue-500"
showSearch
optionFilterProp="children"
filterOption={(input, option) =>
(option?.children ?? '').toLowerCase().includes(input.toLowerCase())
}
onChange={(value, option) => {
form.setFieldsValue({
customer: {
id: option.key,
name: value
}
});
}}
>
{customers.map(customer => (
<Select.Option
key={customer.id}
value={customer.attributes.name}
>
{customer.attributes.name}
</Select.Option>
))}
</Select>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="supplierName" name={['customer', 'id']}
label={<span className="text-gray-700 font-medium">供应商名称</span>} hidden
rules={[{ required: true, message: '请输入供应商名称' }]}
> >
<Input <Input />
placeholder="请输入供应商名称"
className="rounded-md hover:border-blue-400 focus:border-blue-500"
/>
</Form.Item> </Form.Item>
</div> </div>
</Card> </Card>

View File

@@ -16,7 +16,7 @@ const QuotationPage = () => {
total, total,
fetchResources: fetchQuotations, fetchResources: fetchQuotations,
deleteResource: deleteQuotation deleteResource: deleteQuotation
} = useResources(pagination, sorter); } = useResources(pagination, sorter, 'quota');
useEffect(() => { useEffect(() => {
fetchQuotations(); fetchQuotations();
@@ -52,18 +52,12 @@ const QuotationPage = () => {
}, },
{ {
title: '客户信息', title: '客户信息',
dataIndex: ['attributes', 'companyName'], dataIndex: ['attributes', 'customerName'],
key: 'companyName', key: 'customerName',
render: (companyName) => ( render: (customerName,record) => (
<Tag color="blue">{companyName}</Tag> <Tag color="blue" className='cursor-pointer' onClick={() => {
), navigate(`/company/customerInfo/${record.attributes.customerId}`)
}, }}>{customerName}</Tag>
{
title: '供应商',
dataIndex: ['attributes', 'supplierName'],
key: 'supplierName',
render: (supplierName) => (
<Tag color="green">{supplierName}</Tag>
), ),
}, },
{ {
@@ -82,7 +76,7 @@ const QuotationPage = () => {
dataIndex: 'created_at', dataIndex: 'created_at',
key: 'created_at', key: 'created_at',
sorter: true, sorter: true,
width: 180, width: 120,
render: (text) => ( render: (text) => (
<span>{new Date(text).toLocaleString('zh-CN', { <span>{new Date(text).toLocaleString('zh-CN', {
year: 'numeric', year: 'numeric',
@@ -96,11 +90,11 @@ const QuotationPage = () => {
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width:180, width:200,
render: (_, record) => ( render: (_, record) => (
<Space size="mini"> <Space size={0}>
<Button <Button
size='mini' size='small'
type="link" type="link"
icon={<EyeOutlined />} icon={<EyeOutlined />}
onClick={() => navigate(`/company/quotaInfo/preview/${record.id}`)} onClick={() => navigate(`/company/quotaInfo/preview/${record.id}`)}
@@ -108,7 +102,7 @@ const QuotationPage = () => {
查看 查看
</Button> </Button>
<Button <Button
size='mini' size='small'
type="link" type="link"
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => navigate(`/company/quotaInfo/${record.id}?edit=true`)} onClick={() => navigate(`/company/quotaInfo/${record.id}?edit=true`)}
@@ -123,7 +117,7 @@ const QuotationPage = () => {
cancelText="取消" cancelText="取消"
okButtonProps={{ danger: true }} okButtonProps={{ danger: true }}
> >
<Button size='mini' type="link" danger icon={<DeleteOutlined />}> <Button size='small' type="link" danger icon={<DeleteOutlined />}>
删除 删除
</Button> </Button>
</Popconfirm> </Popconfirm>

View File

@@ -63,7 +63,7 @@ const QuotationView = () => {
if (error) throw error; if (error) throw error;
setData(quotation); setData(quotation);
} catch (error) { } catch (error) {
console.error('获取报价单<EFBFBD><EFBFBD>失败:', error); console.error('获取报价单失败:', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -197,10 +197,7 @@ const QuotationView = () => {
<div className="bg-gray-50 p-6 rounded-lg mb-8"> <div className="bg-gray-50 p-6 rounded-lg mb-8">
<Descriptions column={2} bordered> <Descriptions column={2} bordered>
<Descriptions.Item label="客户公司" span={1}> <Descriptions.Item label="客户公司" span={1}>
{data.attributes.companyName} {data.attributes.customerName}
</Descriptions.Item>
<Descriptions.Item label="供应商" span={1}>
{data.attributes.supplierName}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="报价日期" span={1}> <Descriptions.Item label="报价日期" span={1}>
{new Date(data.created_at).toLocaleDateString('zh-CN')} {new Date(data.created_at).toLocaleDateString('zh-CN')}

View File

@@ -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 (
<div className="bg-gradient-to-b from-gray-50 to-white min-h-screen p-6">
<Card
className="shadow-lg rounded-lg border-0"
title={
<div className="flex justify-between items-center py-2">
<div className="flex items-center space-x-3">
<Title level={4} className="mb-0 text-gray-800">
{id ? (isEdit ? '编辑供应商' : '查看供应商') : '新建供应商'}
</Title>
<span className="text-gray-400 text-sm">
{id ? (isEdit ? '请修改供应商信息' : '供应商详情') : '请填写供应商信息'}
</span>
</div>
<Space size="middle">
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/company/supplier')}
>
返回
</Button>
{!isView && (
<Button
type="primary"
icon={<SaveOutlined />}
onClick={() => form.submit()}
>
保存
</Button>
)}
</Space>
</div>
}
bodyStyle={{ backgroundColor: '#fff' }}
>
<Form
form={form}
onFinish={onFinish}
layout="vertical"
initialValues={{
status: 'active'
}}
className="space-y-6"
disabled={isView}
>
{/* 基本信息卡片 */}
<Card
className="shadow-sm rounded-lg"
type="inner"
title={
<span className="flex items-center space-x-2 text-gray-700">
<span className="w-1 h-4 bg-blue-500 rounded-full"/>
<span>基本信息</span>
</span>
}
>
<Form.Item
label="供应商名称"
name="name"
rules={[{ required: true, message: '请输入供应商名称' }]}
>
<Input placeholder="请输入供应商名称" />
</Form.Item>
<Form.Item
label="联系人"
name="contact"
rules={[{ required: true, message: '请输入联系人姓名' }]}
>
<Input placeholder="请输入联系人姓名" />
</Form.Item>
<Form.Item
label="联系电话"
name="phone"
rules={[
{ required: true, message: '请输入联系电话' },
]}
>
<Input placeholder="请输入联系电话" />
</Form.Item>
<Form.Item
label="地址"
name="address"
>
<Input.TextArea rows={3} placeholder="请输入详细地址" />
</Form.Item>
<Form.Item
label="状态"
name="status"
rules={[{ required: true, message: '请选择状态' }]}
>
<Select>
<Select.Option value="active">启用</Select.Option>
<Select.Option value="inactive">禁用</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="备注"
name="remark"
>
<Input.TextArea rows={3} placeholder="请输入备注信息" />
</Form.Item>
</Card>
</Form>
</Card>
</div>
);
};
export default SupplierForm;

View File

@@ -1,41 +1,154 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Card, Table, Button } from 'antd'; import { Card, Table, Button, message, Popconfirm, Tag, Space } from 'antd';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { useResources } from '@/hooks/resource/useResource';
import { useNavigate } from 'react-router-dom';
const SupplierPage = () => { 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 = [ const columns = [
{ {
title: '供应商名称', title: '供应商名称',
dataIndex: 'name', dataIndex: ['attributes', 'name'],
key: 'name', key: 'name',
ellipsis: true,
}, },
{ {
title: '联系人', title: '联系人',
dataIndex: 'contact', dataIndex: ['attributes', 'contact'],
key: 'contact', key: 'contact',
}, },
{ {
title: '电话', title: '电话',
dataIndex: 'phone', dataIndex: ['attributes', 'phone'],
key: 'phone', key: 'phone',
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: ['attributes', 'status'],
key: 'status', key: 'status',
render: (status) => (
<Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '启用' : '禁用'}
</Tag>
),
},
{
title: '创建日期',
dataIndex: 'created_at',
key: 'created_at',
sorter: true,
width: 180,
render: (text) => (
<span>{new Date(text).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})}</span>
),
},
{
title: '操作',
key: 'action',
width: 150,
render: (_, record) => (
<Space size={0}>
<Button
size='small'
type="link"
icon={<EditOutlined />}
onClick={() => navigate(`/company/supplierInfo/${record.id}?edit=true`)}
>
编辑
</Button>
<Popconfirm
title="确定要删除这个供应商吗?"
description="删除后将无法恢复!"
onConfirm={() => handleDelete(record.id)}
okText="确定"
cancelText="取消"
okButtonProps={{ danger: true }}
>
<Button size='small' type="link" danger icon={<DeleteOutlined />}>
删除
</Button>
</Popconfirm>
</Space>
),
}, },
]; ];
return ( return (
<Card <Card
title="供应商管理" title={
<Space>
<span>供应商管理</span>
<Tag color="blue">{total} 个供应商</Tag>
</Space>
}
className='h-full w-full overflow-auto'
extra={ extra={
<Button type="primary" icon={<PlusOutlined />}> <Button
type="primary"
icon={<PlusOutlined />}
onClick={() => navigate('/company/supplierInfo')}
>
新增供应商 新增供应商
</Button> </Button>
} }
> >
<Table columns={columns} dataSource={[]} rowKey="id" /> <Table
columns={columns}
dataSource={suppliers}
rowKey="id"
loading={loading}
onChange={handleTableChange}
pagination={{
...pagination,
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
}}
/>
</Card> </Card>
); );
}; };

View File

@@ -68,6 +68,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 180,
render: (_, record) => { render: (_, record) => {
const editable = isEditing(record); const editable = isEditing(record);
return editable ? ( return editable ? (
@@ -76,6 +77,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
icon={<SaveOutlined />} icon={<SaveOutlined />}
onClick={() => save(record.id)} onClick={() => save(record.id)}
type="link" type="link"
size='small'
> >
保存 保存
</Button> </Button>
@@ -83,6 +85,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
icon={<CloseOutlined />} icon={<CloseOutlined />}
onClick={cancel} onClick={cancel}
type="link" type="link"
size='small'
> >
取消 取消
</Button> </Button>
@@ -94,6 +97,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => edit(record)} onClick={() => edit(record)}
type="link" type="link"
size='small'
> >
编辑 编辑
</Button> </Button>
@@ -105,6 +109,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
<Button <Button
icon={<DeleteOutlined />} icon={<DeleteOutlined />}
type="link" type="link"
size='small'
danger danger
> >
删除 删除

View File

@@ -100,19 +100,22 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 180,
render: (_, record) => { render: (_, record) => {
const editable = isEditing(record); const editable = isEditing(record);
return editable ? ( return editable ? (
<Space> <Space size={0}>
<Button <Button
icon={<SaveOutlined />} icon={<SaveOutlined />}
onClick={() => save(record.id)} onClick={() => save(record.id)}
type="link" type="link"
size='small'
loading={loading} loading={loading}
> >
保存 保存
</Button> </Button>
<Button <Button
size='small'
icon={<CloseOutlined />} icon={<CloseOutlined />}
onClick={cancel} onClick={cancel}
type="link" type="link"
@@ -123,6 +126,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
) : ( ) : (
<Space> <Space>
<Button <Button
size='small'
disabled={editingKey !== ''} disabled={editingKey !== ''}
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => edit(record)} onClick={() => edit(record)}
@@ -138,6 +142,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
icon={<DeleteOutlined />} icon={<DeleteOutlined />}
type="link" type="link"
danger danger
size='small'
loading={loading} loading={loading}
> >
删除 删除

View File

@@ -1,12 +1,12 @@
import { supabase } from '@/config/supabase'; import { supabase } from '@/config/supabase';
export const resourceService = { 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 { try {
let query = supabase let query = supabase
.from('resources') .from('resources')
.select('*', { count: 'exact' }) .select('*', { count: 'exact' })
.eq('type','quota') .eq('type', type)
if (searchQuery) { if (searchQuery) {
query = query.or(`external_id.ilike.%${searchQuery}%`); query = query.or(`external_id.ilike.%${searchQuery}%`);