Merge branch 'main' of github.com:xuqssq/uppmkt-admin
This commit is contained in:
BIN
src/assets/logo-collapsed.png
Normal file
BIN
src/assets/logo-collapsed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -1,18 +1,24 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { RocketOutlined } from '@ant-design/icons';
|
|
||||||
import { Typography } from 'antd';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
import logo from "@/assets/logo.png";
|
||||||
|
import logoCollapsed from "@/assets/logo-collapsed.png";
|
||||||
|
|
||||||
export const Logo = ({ collapsed, isDarkMode }) => (
|
export const Logo = ({ collapsed, isDarkMode }) => (
|
||||||
<div className="logo">
|
<div className="logo">
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<RocketOutlined className="text-2xl text-primary-500" />
|
{collapsed ? (
|
||||||
{!collapsed && (
|
<div className="flex items-center justify-center">
|
||||||
<Text className="text-lg font-semibold m-0" style={{ color: 'var(--primary-color)' }}>
|
<img
|
||||||
Uppeta
|
src={logoCollapsed}
|
||||||
</Text>
|
alt="logo"
|
||||||
|
className="w-1/2 object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<img src={logo} alt="logo" className="w-1/2 object-contain mr-2" />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const MainLayout = () => {
|
|||||||
<Layout className="h-screen overflow-hidden">
|
<Layout className="h-screen overflow-hidden">
|
||||||
<Sidebar collapsed={collapsed} />
|
<Sidebar collapsed={collapsed} />
|
||||||
<Layout className="flex flex-col ">
|
<Layout className="flex flex-col ">
|
||||||
<Header collapsed={collapsed} setCollapsed={setCollapsed} />
|
<Header collapsed={collapsed} setCollapsed={setCollapsed}/>
|
||||||
<Content
|
<Content
|
||||||
className={`
|
className={`
|
||||||
m-2 p-4 rounded-lg overflow-auto h-full
|
m-2 p-4 rounded-lg overflow-auto h-full
|
||||||
|
|||||||
@@ -617,6 +617,7 @@ const SectionList = ({
|
|||||||
max={100}
|
max={100}
|
||||||
value={taxRate}
|
value={taxRate}
|
||||||
onChange={(value) => setTaxRate(value)}
|
onChange={(value) => setTaxRate(value)}
|
||||||
|
disabled={isView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -143,13 +143,15 @@ const CustomerPage = () => {
|
|||||||
}
|
}
|
||||||
className='h-full w-full overflow-auto'
|
className='h-full w-full overflow-auto'
|
||||||
extra={
|
extra={
|
||||||
<Button
|
<div className="flex justify-between my-4">
|
||||||
type="primary"
|
<Button
|
||||||
icon={<PlusOutlined />}
|
type="primary"
|
||||||
onClick={() => navigate('/company/customerInfo')}
|
icon={<PlusOutlined />}
|
||||||
>
|
onClick={() => navigate("/company/customerInfo")}
|
||||||
新增客户
|
>
|
||||||
</Button>
|
新增客户
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -1,20 +1,50 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { Card, Table, Button, message, Popconfirm, Tag, Space, Spin, Modal, Empty, Select, Typography, Statistic, Divider } from 'antd';
|
import {
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, CopyOutlined, FileAddOutlined, AppstoreOutlined } from '@ant-design/icons';
|
Card,
|
||||||
import { useResources } from '@/hooks/resource/useResource';
|
Table,
|
||||||
import { useNavigate } from 'react-router-dom';
|
Button,
|
||||||
import { supabase } from '@/config/supabase'
|
message,
|
||||||
import { formatExchangeRate,EXCHANGE_RATE,defaultSymbol } from '@/utils/exchange_rate';
|
Popconfirm,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Spin,
|
||||||
|
Modal,
|
||||||
|
Empty,
|
||||||
|
Select,
|
||||||
|
Typography,
|
||||||
|
Statistic,
|
||||||
|
Divider,
|
||||||
|
} from "antd";
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
CopyOutlined,
|
||||||
|
FileAddOutlined,
|
||||||
|
AppstoreOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import { useResources } from "@/hooks/resource/useResource";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { supabase } from "@/config/supabase";
|
||||||
|
import {
|
||||||
|
formatExchangeRate,
|
||||||
|
EXCHANGE_RATE,
|
||||||
|
defaultSymbol,
|
||||||
|
} from "@/utils/exchange_rate";
|
||||||
|
|
||||||
const QuotationPage = () => {
|
const QuotationPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
|
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
|
||||||
const [sorter, setSorter] = useState({ field: 'created_at', order: 'descend' });
|
const [sorter, setSorter] = useState({
|
||||||
|
field: "created_at",
|
||||||
|
order: "descend",
|
||||||
|
});
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
const [selectedTemplateId, setSelectedTemplateId] = useState(null);
|
const [selectedTemplateId, setSelectedTemplateId] = useState(null);
|
||||||
const [templates, setTemplates] = useState([]);
|
const [templates, setTemplates] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedCategory, setSelectedCategory] = useState('all');
|
const [selectedCategory, setSelectedCategory] = useState("all");
|
||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -22,8 +52,8 @@ const QuotationPage = () => {
|
|||||||
loading: loadingQuotations,
|
loading: loadingQuotations,
|
||||||
total,
|
total,
|
||||||
fetchResources: fetchQuotations,
|
fetchResources: fetchQuotations,
|
||||||
deleteResource: deleteQuotation
|
deleteResource: deleteQuotation,
|
||||||
} = useResources(pagination, sorter, 'quota');
|
} = useResources(pagination, sorter, "quota");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchQuotations();
|
fetchQuotations();
|
||||||
@@ -43,10 +73,10 @@ const QuotationPage = () => {
|
|||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
try {
|
try {
|
||||||
await deleteQuotation(id);
|
await deleteQuotation(id);
|
||||||
message.success('删除成功');
|
message.success("删除成功");
|
||||||
fetchQuotations();
|
fetchQuotations();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败:' + error.message);
|
message.error("删除失败:" + error.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -89,7 +119,7 @@ const QuotationPage = () => {
|
|||||||
if (selectedTemplateId) {
|
if (selectedTemplateId) {
|
||||||
navigate(`/company/quotaInfo?templateId=${selectedTemplateId}`);
|
navigate(`/company/quotaInfo?templateId=${selectedTemplateId}`);
|
||||||
} else {
|
} else {
|
||||||
navigate('/company/quotaInfo');
|
navigate("/company/quotaInfo");
|
||||||
}
|
}
|
||||||
setIsModalVisible(false);
|
setIsModalVisible(false);
|
||||||
setSelectedTemplateId(null);
|
setSelectedTemplateId(null);
|
||||||
@@ -97,41 +127,41 @@ const QuotationPage = () => {
|
|||||||
|
|
||||||
const getAllCategories = (templates) => {
|
const getAllCategories = (templates) => {
|
||||||
const categorySet = new Set();
|
const categorySet = new Set();
|
||||||
templates.forEach(template => {
|
templates.forEach((template) => {
|
||||||
template.attributes.category?.forEach(cat => {
|
template.attributes.category?.forEach((cat) => {
|
||||||
categorySet.add(JSON.stringify(cat));
|
categorySet.add(JSON.stringify(cat));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return Array.from(categorySet).map(cat => JSON.parse(cat));
|
return Array.from(categorySet).map((cat) => JSON.parse(cat));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFilteredTemplates = () => {
|
const getFilteredTemplates = () => {
|
||||||
if (selectedCategory === 'all') return templates;
|
if (selectedCategory === "all") return templates;
|
||||||
return templates.filter(template =>
|
return templates.filter((template) =>
|
||||||
template.attributes.category?.some(cat => cat.id === selectedCategory)
|
template.attributes.category?.some((cat) => cat.id === selectedCategory)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '报价单名称',
|
title: "报价单名称",
|
||||||
dataIndex: ['attributes', 'quataName'],
|
dataIndex: ["attributes", "quataName"],
|
||||||
key: 'quataName',
|
key: "quataName",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '客户信息',
|
title: "客户信息",
|
||||||
dataIndex: ['attributes', 'customers'],
|
dataIndex: ["attributes", "customers"],
|
||||||
key: 'customers',
|
key: "customers",
|
||||||
render: (customers, record) => (
|
render: (customers, record) => (
|
||||||
<Space>
|
<Space>
|
||||||
{customers?.map(customer => (
|
{customers?.map((customer) => (
|
||||||
<Tag
|
<Tag
|
||||||
key={customer.id}
|
key={customer.id}
|
||||||
color="blue"
|
color="blue"
|
||||||
className='cursor-pointer'
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/company/customerInfo/${customer.id}`)
|
navigate(`/company/customerInfo/${customer.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customer.name}
|
{customer.name}
|
||||||
@@ -141,61 +171,70 @@ const QuotationPage = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '报价总额',
|
title: "报价总额",
|
||||||
dataIndex: ['attributes'],
|
dataIndex: ["attributes"],
|
||||||
key: 'totalAmount',
|
key: "totalAmount",
|
||||||
align: 'right',
|
align: "right",
|
||||||
render: (attributes) => {
|
render: (attributes) => {
|
||||||
// 获取货币符号
|
// 获取货币符号
|
||||||
const currencySymbol = EXCHANGE_RATE[attributes?.currency]?.symbol || defaultSymbol;
|
const currencySymbol =
|
||||||
|
EXCHANGE_RATE[attributes?.currency]?.symbol || defaultSymbol;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex justify-between items-center text-sm">
|
<div className="flex justify-between items-center text-sm">
|
||||||
<Typography.Text type="secondary" style={{ fontSize: '12px' }}>
|
<Typography.Text type="secondary" style={{ fontSize: "12px" }}>
|
||||||
税前:{formatExchangeRate(attributes?.currency, attributes.beforeTaxAmount)}
|
税前:
|
||||||
|
{formatExchangeRate(
|
||||||
|
attributes?.currency,
|
||||||
|
attributes.beforeTaxAmount
|
||||||
|
)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Statistic
|
<Statistic
|
||||||
value={formatExchangeRate(attributes?.currency, attributes.afterTaxAmount)}
|
value={formatExchangeRate(
|
||||||
|
attributes?.currency,
|
||||||
|
attributes.afterTaxAmount
|
||||||
|
)}
|
||||||
precision={2}
|
precision={2}
|
||||||
valueStyle={{
|
valueStyle={{
|
||||||
fontSize: '16px',
|
fontSize: "16px",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: '#1890ff'
|
color: "#1890ff",
|
||||||
}}
|
}}
|
||||||
className="!mb-0"
|
className="!mb-0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建日期',
|
title: "创建日期",
|
||||||
dataIndex: 'created_at',
|
dataIndex: "created_at",
|
||||||
key: 'created_at',
|
key: "created_at",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text) => (
|
render: (text) => (
|
||||||
<span>{new Date(text).toLocaleString('zh-CN', {
|
<span>
|
||||||
year: 'numeric',
|
{new Date(text).toLocaleString("zh-CN", {
|
||||||
month: '2-digit',
|
year: "numeric",
|
||||||
day: '2-digit',
|
month: "2-digit",
|
||||||
hour: '2-digit',
|
day: "2-digit",
|
||||||
minute: '2-digit'
|
hour: "2-digit",
|
||||||
})}</span>
|
minute: "2-digit",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: "操作",
|
||||||
key: 'action',
|
key: "action",
|
||||||
fixed: 'right',
|
fixed: "right",
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space size={0}>
|
<Space size={0}>
|
||||||
<Button
|
<Button
|
||||||
size='small'
|
size="small"
|
||||||
type="link"
|
type="link"
|
||||||
icon={<CopyOutlined />}
|
icon={<CopyOutlined />}
|
||||||
onClick={() => copyItem(record)}
|
onClick={() => copyItem(record)}
|
||||||
@@ -203,7 +242,7 @@ const QuotationPage = () => {
|
|||||||
复制
|
复制
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size='small'
|
size="small"
|
||||||
type="link"
|
type="link"
|
||||||
icon={<EyeOutlined />}
|
icon={<EyeOutlined />}
|
||||||
onClick={() => navigate(`/company/quotaInfo/preview/${record.id}`)}
|
onClick={() => navigate(`/company/quotaInfo/preview/${record.id}`)}
|
||||||
@@ -211,10 +250,12 @@ const QuotationPage = () => {
|
|||||||
查看
|
查看
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size='small'
|
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`)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
@@ -226,7 +267,7 @@ const QuotationPage = () => {
|
|||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
okButtonProps={{ danger: true }}
|
okButtonProps={{ danger: true }}
|
||||||
>
|
>
|
||||||
<Button size='small' type="link" danger icon={<DeleteOutlined />}>
|
<Button size="small" type="link" danger icon={<DeleteOutlined />}>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
@@ -239,19 +280,21 @@ const QuotationPage = () => {
|
|||||||
const groups = new Map();
|
const groups = new Map();
|
||||||
|
|
||||||
// 添加未分类组
|
// 添加未分类组
|
||||||
groups.set('uncategorized', {
|
groups.set("uncategorized", {
|
||||||
name: '未分类',
|
name: "未分类",
|
||||||
templates: templates.filter(t => !t.attributes.category || t.attributes.category.length === 0)
|
templates: templates.filter(
|
||||||
|
(t) => !t.attributes.category || t.attributes.category.length === 0
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 按分类分组
|
// 按分类分组
|
||||||
templates.forEach(template => {
|
templates.forEach((template) => {
|
||||||
if (template.attributes.category) {
|
if (template.attributes.category) {
|
||||||
template.attributes.category.forEach(cat => {
|
template.attributes.category.forEach((cat) => {
|
||||||
if (!groups.has(cat.id)) {
|
if (!groups.has(cat.id)) {
|
||||||
groups.set(cat.id, {
|
groups.set(cat.id, {
|
||||||
name: cat.name,
|
name: cat.name,
|
||||||
templates: []
|
templates: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
groups.get(cat.id).templates.push(template);
|
groups.get(cat.id).templates.push(template);
|
||||||
@@ -259,35 +302,35 @@ const QuotationPage = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(groups.values()).filter(group => group.templates.length > 0);
|
return Array.from(groups.values()).filter(
|
||||||
|
(group) => group.templates.length > 0
|
||||||
|
);
|
||||||
};
|
};
|
||||||
const copyItem = async (record) => {
|
const copyItem = async (record) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
// 深拷贝原有数据的 attributes
|
// 深拷贝原有数据的 attributes
|
||||||
const newAttributes = JSON.parse(JSON.stringify(record.attributes));
|
const newAttributes = JSON.parse(JSON.stringify(record.attributes));
|
||||||
|
|
||||||
// 修改报价单名称,添加"副本"标识
|
// 修改报价单名称,添加"副本"标识
|
||||||
newAttributes.quataName = `${newAttributes.quataName} (副本)`;
|
newAttributes.quataName = `${newAttributes.quataName} (副本)`;
|
||||||
|
|
||||||
// 创建新的报价单记录
|
// 创建新的报价单记录
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase.from("resources").insert([
|
||||||
.from('resources')
|
{
|
||||||
.insert([
|
type: "quota",
|
||||||
{
|
attributes: newAttributes,
|
||||||
type: 'quota',
|
},
|
||||||
attributes: newAttributes
|
]);
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
message.success('复制成功');
|
message.success("复制成功");
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
fetchQuotations();
|
fetchQuotations();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('复制报价单失败:', error);
|
console.error("复制报价单失败:", error);
|
||||||
message.error('复制失败:' + error.message);
|
message.error("复制失败:" + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -301,19 +344,21 @@ const QuotationPage = () => {
|
|||||||
<Tag color="blue">{total} 个报价单</Tag>
|
<Tag color="blue">{total} 个报价单</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
className='h-full w-full overflow-auto'
|
className="h-full w-full overflow-auto"
|
||||||
extra={
|
extra={
|
||||||
<Button
|
<div className="flex justify-between my-4">
|
||||||
type="primary"
|
<Button
|
||||||
icon={<PlusOutlined />}
|
type="primary"
|
||||||
onClick={() => setIsModalVisible(true)}
|
icon={<PlusOutlined />}
|
||||||
>
|
onClick={() => setIsModalVisible(true)}
|
||||||
新增报价单
|
>
|
||||||
</Button>
|
新增报价单
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
className='w-full'
|
className="w-full"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={quotations}
|
dataSource={quotations}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
@@ -373,7 +418,7 @@ const QuotationPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
{group.templates.map(template => (
|
{group.templates.map((template) => (
|
||||||
<div
|
<div
|
||||||
key={template.id}
|
key={template.id}
|
||||||
onClick={() => handleTemplateSelect(template.id)}
|
onClick={() => handleTemplateSelect(template.id)}
|
||||||
@@ -381,8 +426,8 @@ const QuotationPage = () => {
|
|||||||
relative p-4 rounded-xl cursor-pointer transition-all duration-200
|
relative p-4 rounded-xl cursor-pointer transition-all duration-200
|
||||||
${
|
${
|
||||||
selectedTemplateId === template.id
|
selectedTemplateId === template.id
|
||||||
? 'ring-2 ring-blue-500 bg-blue-50/40 dark:bg-blue-900/40'
|
? "ring-2 ring-blue-500 bg-blue-50/40 dark:bg-blue-900/40"
|
||||||
: 'hover:bg-gray-50 dark:hover:bg-gray-700/50 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md'
|
: "hover:bg-gray-50 dark:hover:bg-gray-700/50 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md"
|
||||||
}
|
}
|
||||||
dark:bg-gray-800
|
dark:bg-gray-800
|
||||||
`}
|
`}
|
||||||
@@ -393,7 +438,7 @@ const QuotationPage = () => {
|
|||||||
{template.attributes.templateName}
|
{template.attributes.templateName}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">
|
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">
|
||||||
{template.attributes.description || '暂无描述'}
|
{template.attributes.description || "暂无描述"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-blue-600 dark:text-blue-400 font-medium whitespace-nowrap">
|
<div className="text-blue-600 dark:text-blue-400 font-medium whitespace-nowrap">
|
||||||
@@ -422,7 +467,11 @@ const QuotationPage = () => {
|
|||||||
{selectedTemplateId === template.id && (
|
{selectedTemplateId === template.id && (
|
||||||
<div className="absolute top-3 right-3">
|
<div className="absolute top-3 right-3">
|
||||||
<div className="w-5 h-5 bg-blue-500 dark:bg-blue-600 rounded-full flex items-center justify-center">
|
<div className="w-5 h-5 bg-blue-500 dark:bg-blue-600 rounded-full flex items-center justify-center">
|
||||||
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
<svg
|
||||||
|
className="w-3 h-3 text-white"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
<path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
|
<path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -440,4 +489,4 @@ const QuotationPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QuotationPage;
|
export default QuotationPage;
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { Card, Table, Button, message, Popconfirm, Tag, Space } from 'antd';
|
import { Card, Table, Button, message, Popconfirm, Tag, Space } from "antd";
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||||
import { useResources } from '@/hooks/resource/useResource';
|
import { useResources } from "@/hooks/resource/useResource";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const SupplierPage = () => {
|
const SupplierPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
|
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
|
||||||
const [sorter, setSorter] = useState({ field: 'created_at', order: 'descend' });
|
const [sorter, setSorter] = useState({
|
||||||
|
field: "created_at",
|
||||||
|
order: "descend",
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
resources: suppliers,
|
resources: suppliers,
|
||||||
loading,
|
loading,
|
||||||
total,
|
total,
|
||||||
fetchResources: fetchSuppliers,
|
fetchResources: fetchSuppliers,
|
||||||
deleteResource: deleteSupplier
|
deleteResource: deleteSupplier,
|
||||||
} = useResources(pagination, sorter, 'supplier');
|
} = useResources(pagination, sorter, "supplier");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSuppliers();
|
fetchSuppliers();
|
||||||
@@ -35,66 +38,70 @@ const SupplierPage = () => {
|
|||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
try {
|
try {
|
||||||
await deleteSupplier(id);
|
await deleteSupplier(id);
|
||||||
message.success('删除成功');
|
message.success("删除成功");
|
||||||
fetchSuppliers();
|
fetchSuppliers();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败:' + error.message);
|
message.error("删除失败:" + error.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '供应商名称',
|
title: "供应商名称",
|
||||||
dataIndex: ['attributes', 'name'],
|
dataIndex: ["attributes", "name"],
|
||||||
key: 'name',
|
key: "name",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '联系人',
|
title: "联系人",
|
||||||
dataIndex: ['attributes', 'contact'],
|
dataIndex: ["attributes", "contact"],
|
||||||
key: 'contact',
|
key: "contact",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '电话',
|
title: "电话",
|
||||||
dataIndex: ['attributes', 'phone'],
|
dataIndex: ["attributes", "phone"],
|
||||||
key: 'phone',
|
key: "phone",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: "状态",
|
||||||
dataIndex: ['attributes', 'status'],
|
dataIndex: ["attributes", "status"],
|
||||||
key: 'status',
|
key: "status",
|
||||||
render: (status) => (
|
render: (status) => (
|
||||||
<Tag color={status === 'active' ? 'green' : 'red'}>
|
<Tag color={status === "active" ? "green" : "red"}>
|
||||||
{status === 'active' ? '启用' : '禁用'}
|
{status === "active" ? "启用" : "禁用"}
|
||||||
</Tag>
|
</Tag>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建日期',
|
title: "创建日期",
|
||||||
dataIndex: 'created_at',
|
dataIndex: "created_at",
|
||||||
key: 'created_at',
|
key: "created_at",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text) => (
|
render: (text) => (
|
||||||
<span>{new Date(text).toLocaleString('zh-CN', {
|
<span>
|
||||||
year: 'numeric',
|
{new Date(text).toLocaleString("zh-CN", {
|
||||||
month: '2-digit',
|
year: "numeric",
|
||||||
day: '2-digit',
|
month: "2-digit",
|
||||||
hour: '2-digit',
|
day: "2-digit",
|
||||||
minute: '2-digit'
|
hour: "2-digit",
|
||||||
})}</span>
|
minute: "2-digit",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: "操作",
|
||||||
key: 'action',
|
key: "action",
|
||||||
fixed: 'right',
|
fixed: "right",
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space size={0}>
|
<Space size={0}>
|
||||||
<Button
|
<Button
|
||||||
size='small'
|
size="small"
|
||||||
type="link"
|
type="link"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => navigate(`/company/supplierInfo/${record.id}?edit=true`)}
|
onClick={() =>
|
||||||
|
navigate(`/company/supplierInfo/${record.id}?edit=true`)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
@@ -106,7 +113,7 @@ const SupplierPage = () => {
|
|||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
okButtonProps={{ danger: true }}
|
okButtonProps={{ danger: true }}
|
||||||
>
|
>
|
||||||
<Button size='small' type="link" danger icon={<DeleteOutlined />}>
|
<Button size="small" type="link" danger icon={<DeleteOutlined />}>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
@@ -123,15 +130,17 @@ const SupplierPage = () => {
|
|||||||
<Tag color="blue">{total} 个供应商</Tag>
|
<Tag color="blue">{total} 个供应商</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
className='h-full w-full overflow-auto'
|
className="h-full w-full overflow-auto"
|
||||||
extra={
|
extra={
|
||||||
<Button
|
<div className="flex justify-between my-4">
|
||||||
type="primary"
|
<Button
|
||||||
icon={<PlusOutlined />}
|
type="primary"
|
||||||
onClick={() => navigate('/company/supplierInfo')}
|
icon={<PlusOutlined />}
|
||||||
>
|
onClick={() => navigate("/company/supplierInfo")}
|
||||||
新增供应商
|
>
|
||||||
</Button>
|
新增供应商
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
@@ -153,4 +162,4 @@ const SupplierPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SupplierPage;
|
export default SupplierPage;
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ const TaskPage = () => {
|
|||||||
<Card
|
<Card
|
||||||
title="任务管理"
|
title="任务管理"
|
||||||
extra={
|
extra={
|
||||||
<Button type="primary" icon={<PlusOutlined />}>
|
<div className="flex justify-between my-4">
|
||||||
新增任务
|
<Button type="primary" icon={<PlusOutlined />}>
|
||||||
</Button>
|
新增任务
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table columns={columns} dataSource={[]} rowKey="id" />
|
<Table columns={columns} dataSource={[]} rowKey="id" />
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ const CommunicationTasks = () => {
|
|||||||
<Card
|
<Card
|
||||||
title="沟通任务"
|
title="沟通任务"
|
||||||
extra={
|
extra={
|
||||||
<Button type="primary" icon={<PlusOutlined />}>
|
<div className="flex justify-between my-4">
|
||||||
新增任务
|
<Button type="primary" icon={<PlusOutlined />}>
|
||||||
</Button>
|
新增任务
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table columns={columns} dataSource={[]} rowKey="id" />
|
<Table columns={columns} dataSource={[]} rowKey="id" />
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const ResourceTask = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
fixed: 'right',
|
fixed: "right",
|
||||||
key: "action",
|
key: "action",
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space size={0}>
|
<Space size={0}>
|
||||||
@@ -136,13 +136,15 @@ const ResourceTask = () => {
|
|||||||
}
|
}
|
||||||
className="h-full w-full overflow-auto"
|
className="h-full w-full overflow-auto"
|
||||||
extra={
|
extra={
|
||||||
<Button
|
<div className="flex justify-between my-4">
|
||||||
type="primary"
|
<Button
|
||||||
icon={<PlusOutlined />}
|
type="primary"
|
||||||
onClick={() => navigate("/resource/task/edit")}
|
icon={<PlusOutlined />}
|
||||||
>
|
onClick={() => navigate("/resource/task/edit")}
|
||||||
新增任务
|
>
|
||||||
</Button>
|
新增任务
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -206,4 +206,15 @@ html.dark::view-transition-old(root) {
|
|||||||
|
|
||||||
.ant-menu {
|
.ant-menu {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border: none !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-sider {
|
||||||
|
@apply rounded-r-xl dark:!bg-black/80
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-header{
|
||||||
|
@apply rounded-b-sm ml-2
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user