框架优化

This commit is contained in:
liamzi
2024-12-25 18:34:20 +08:00
parent ac0f7ccbb7
commit a6a4cbb337
28 changed files with 56 additions and 117 deletions

View File

@@ -3,8 +3,7 @@ import { Layout, Switch, Button, Dropdown } from 'antd';
import { UserOutlined, LogoutOutlined } from '@ant-design/icons'; import { UserOutlined, LogoutOutlined } from '@ant-design/icons';
import { useTheme } from '@/contexts/ThemeContext'; import { useTheme } from '@/contexts/ThemeContext';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { MenuTrigger } from '../common/MenuTrigger'; import { MenuTrigger } from '../Layout/MenuTrigger';
const { Header: AntHeader } = Layout; const { Header: AntHeader } = Layout;
const Header = ({ collapsed, setCollapsed }) => { const Header = ({ collapsed, setCollapsed }) => {

View File

@@ -1,18 +1,17 @@
import React from 'react'; import React from 'react';
import { RocketOutlined } from '@ant-design/icons'; import { RocketOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
const { Text } = Typography;
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" /> <RocketOutlined className="text-2xl text-primary-500" />
{!collapsed && ( {!collapsed && (
<h1 className="text-lg font-semibold m-0" style={{ <Text className="text-lg font-semibold m-0" style={{ color: 'var(--primary-color)' }}>
background: 'var(--primary-gradient)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
}}>
Uppeta Uppeta
</h1> </Text>
)} )}
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@ import { Layout, Menu } from 'antd';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { useTheme } from '@/contexts/ThemeContext'; import { useTheme } from '@/contexts/ThemeContext';
import { getMenuItems } from '@/utils/menuUtils'; import { getMenuItems } from '@/utils/menuUtils';
import { Logo } from '@/components/common/Logo'; import { Logo } from '@/components/Layout/Logo';
const { Sider } = Layout; const { Sider } = Layout;

View File

@@ -1,18 +0,0 @@
import React from 'react';
import { Table } from 'antd';
export const BaseTable = ({
columns,
dataSource,
loading = false,
rowKey = 'id',
...props
}) => (
<Table
columns={columns}
dataSource={dataSource}
loading={loading}
rowKey={rowKey}
{...props}
/>
);

View File

@@ -1,17 +0,0 @@
import React from 'react';
export const Icon = ({ name, className = '', style = {} }) => (
<span
className={`material-symbols-rounded ${className}`}
style={{
fontSize: '20px',
background: 'var(--primary-gradient)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
fontVariationSettings: "'FILL' 1, 'wght' 400, 'GRAD' 200, 'opsz' 20",
...style
}}
>
{name}
</span>
);

View File

@@ -1,21 +0,0 @@
import React from 'react';
export const Logo = ({ collapsed, isDarkMode }) => (
<div className="logo">
<div className="flex items-center justify-center gap-2">
<span className="material-symbols-rounded text-primary-500">
rocket_launch
</span>
<h1
style={{
color: isDarkMode ? '#fff' : '#000',
fontSize: collapsed ? '14px' : '18px',
margin: 0,
display: collapsed ? 'none' : 'block',
}}
>
Uppeta
</h1>
</div>
</div>
);

View File

@@ -1,14 +0,0 @@
import React from 'react';
import { Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
export const PageHeader = ({ title, onAdd, addButtonText = '新增' }) => (
<div className="flex justify-between items-center mb-4">
<h1 className="text-2xl font-bold">{title}</h1>
{onAdd && (
<Button type="primary" icon={<PlusOutlined />} onClick={onAdd}>
{addButtonText}
</Button>
)}
</div>
);

View File

@@ -1,7 +1,7 @@
import { supabase } from '@/config/supabase' import { supabase } from '@/config/supabase'
class SupabaseService { class SupabaseService {
async get(table, options = {}) { async select(table, options = {}) {
try { try {
let query = supabase let query = supabase
.from(table) .from(table)
@@ -96,6 +96,26 @@ class SupabaseService {
throw error throw error
} }
} }
// 通用 UPSERT 请求
async upsert(table, data, onConflict) {
try {
let query = supabase
.from(table)
.upsert(data)
.select()
if (onConflict) {
query = query.onConflict(onConflict)
}
const { data: result, error } = await query
if (error) throw error
return result
} catch (error) {
console.error(`Error upserting into ${table}:`, error.message)
throw error
}
}
} }
export const supabaseService = new SupabaseService() export const supabaseService = new SupabaseService()

View File

@@ -10,7 +10,7 @@ export const useTeamMembership = (teamId) => {
setLoading(true); setLoading(true);
try { try {
const result = await supabaseService.get('team_memberships', { const result = await SupabaseService.select('team_memberships', {
select: '*', select: '*',
relations: { relations: {
user: 'id, email, name' user: 'id, email, name'

View File

@@ -4,7 +4,7 @@ export const useTeams = () => {
// 获取团队列表 // 获取团队列表
const fetchTeams = async (params = {}) => { const fetchTeams = async (params = {}) => {
try { try {
const result = await supabaseService.get('teams', { const result = await SupabaseService.select('teams', {
select: ` select: `
id, id,
name, name,

View File

@@ -7,7 +7,7 @@ import { supabaseService } from "@/hooks/supabaseService";
const Login = () => { const Login = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { login, signInWithGoogle, loading } = useAuth(); const { login, signInWithGoogle, emailLoading, googleLoading } = useAuth();
const [form] = Form.useForm(); const [form] = Form.useForm();
const handleLogin = async (values) => { const handleLogin = async (values) => {
@@ -75,6 +75,8 @@ const Login = () => {
type="primary" type="primary"
htmlType="submit" htmlType="submit"
block block
loading={emailLoading}
disabled={googleLoading}
className="h-12 rounded-xl text-base font-medium bg-gradient-to-r from-blue-600 to-cyan-500 border-0 hover:from-blue-700 hover:to-cyan-600" className="h-12 rounded-xl text-base font-medium bg-gradient-to-r from-blue-600 to-cyan-500 border-0 hover:from-blue-700 hover:to-cyan-600"
> >
登录 登录
@@ -89,7 +91,8 @@ const Login = () => {
icon={<GoogleOutlined />} icon={<GoogleOutlined />}
block block
onClick={signInWithGoogle} onClick={signInWithGoogle}
loading={loading} loading={googleLoading}
disabled={emailLoading}
className="h-12 rounded-xl text-base font-medium mb-6 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 border border-gray-200 dark:border-gray-600" className="h-12 rounded-xl text-base font-medium mb-6 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 border border-gray-200 dark:border-gray-600"
> >
使用 Google 账号登录 使用 Google 账号登录

View File

@@ -92,7 +92,6 @@ const CustomerPage = () => {
dataIndex: 'created_at', dataIndex: 'created_at',
key: 'created_at', key: 'created_at',
sorter: true, sorter: true,
width: 180,
render: (text) => ( render: (text) => (
<span>{new Date(text).toLocaleString('zh-CN', { <span>{new Date(text).toLocaleString('zh-CN', {
year: 'numeric', year: 'numeric',
@@ -106,7 +105,7 @@ const CustomerPage = () => {
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 150, fixed: 'right',
render: (_, record) => ( render: (_, record) => (
<Space size={0}> <Space size={0}>
<Button <Button
@@ -157,6 +156,7 @@ const CustomerPage = () => {
columns={columns} columns={columns}
dataSource={customers} dataSource={customers}
rowKey="id" rowKey="id"
scroll={{ x: true }}
loading={loading} loading={loading}
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ pagination={{

View File

@@ -195,7 +195,7 @@ const QuotationPage = () => {
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 280, fixed: 'right',
render: (_, record) => ( render: (_, record) => (
<Space size={0}> <Space size={0}>
<Button <Button
@@ -321,6 +321,7 @@ const QuotationPage = () => {
columns={columns} columns={columns}
dataSource={quotations} dataSource={quotations}
rowKey="id" rowKey="id"
scroll={{ x: true }}
loading={loadingQuotations} loading={loadingQuotations}
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ pagination={{

View File

@@ -110,7 +110,6 @@ const CategoryDrawer = () => {
{ {
title: '分类名称', title: '分类名称',
dataIndex: ['attributes', 'name'], dataIndex: ['attributes', 'name'],
width: '60%',
render: (text, record) => { render: (text, record) => {
const isEditing = record.id === editingKey; const isEditing = record.id === editingKey;
return isEditing ? ( return isEditing ? (
@@ -128,7 +127,6 @@ const CategoryDrawer = () => {
}, },
{ {
title: '操作', title: '操作',
width: '40%',
render: (_, record) => { render: (_, record) => {
const isEditing = record.id === editingKey; const isEditing = record.id === editingKey;
return isEditing ? ( return isEditing ? (
@@ -221,6 +219,7 @@ const CategoryDrawer = () => {
</Button> </Button>
<Form form={form}> <Form form={form}>
<Table <Table
scroll={{ x: true }}
columns={columns} columns={columns}
dataSource={data} dataSource={data}
rowKey="id" rowKey="id"

View File

@@ -374,7 +374,6 @@ const ServicePage = () => {
title: "项目名称", title: "项目名称",
dataIndex: "name", dataIndex: "name",
key: "name", key: "name",
width: "25%",
render: (text, record) => { render: (text, record) => {
const isEditing = record.key === editingKey; const isEditing = record.key === editingKey;
return isEditing ? ( return isEditing ? (
@@ -408,7 +407,6 @@ const ServicePage = () => {
title: "描述", title: "描述",
dataIndex: "description", dataIndex: "description",
key: "description", key: "description",
width: "25%",
render: (text, record) => { render: (text, record) => {
const isEditing = record.key === editingKey; const isEditing = record.key === editingKey;
return isEditing ? ( return isEditing ? (
@@ -454,7 +452,6 @@ const ServicePage = () => {
title: "单价", title: "单价",
dataIndex: "price", dataIndex: "price",
key: "price", key: "price",
width: "15%",
render: (price, record) => { render: (price, record) => {
const isEditing = record.key === editingKey; const isEditing = record.key === editingKey;
return isEditing ? ( return isEditing ? (
@@ -489,7 +486,6 @@ const ServicePage = () => {
title: "数量", title: "数量",
dataIndex: "quantity", dataIndex: "quantity",
key: "quantity", key: "quantity",
width: "15%",
render: (quantity, record) => { render: (quantity, record) => {
const isEditing = record.key === editingKey; const isEditing = record.key === editingKey;
return isEditing ? ( return isEditing ? (
@@ -517,7 +513,6 @@ const ServicePage = () => {
{ {
title: "小计", title: "小计",
key: "subtotal", key: "subtotal",
width: "20%",
render: (_, record) => ( render: (_, record) => (
<span className="text-blue-600 font-medium"> <span className="text-blue-600 font-medium">
¥ ¥
@@ -534,7 +529,7 @@ const ServicePage = () => {
{ {
title: "操作", title: "操作",
key: "action", key: "action",
width: "150px", fixed: 'right',
render: (_, record) => { render: (_, record) => {
const isEditing = record.key === editingKey; const isEditing = record.key === editingKey;
return isEditing ? ( return isEditing ? (
@@ -630,6 +625,7 @@ const ServicePage = () => {
> >
<Table <Table
columns={itemColumns} columns={itemColumns}
scroll={{ x: true }}
dataSource={section.items.map((item, itemIndex) => ({ dataSource={section.items.map((item, itemIndex) => ({
...item, ...item,
key: `${record.id}-${sectionIndex}-${itemIndex}`, key: `${record.id}-${sectionIndex}-${itemIndex}`,

View File

@@ -151,28 +151,23 @@ const SectionManagement = () => {
{ {
title: '项目名称', title: '项目名称',
dataIndex: 'name', dataIndex: 'name',
width: '20%',
}, },
{ {
title: '描述', title: '描述',
dataIndex: 'description', dataIndex: 'description',
width: '25%',
}, },
{ {
title: '单价', title: '单价',
dataIndex: 'price', dataIndex: 'price',
width: '15%',
render: (price) => `¥${price}` render: (price) => `¥${price}`
}, },
{ {
title: '数量', title: '数量',
dataIndex: 'quantity', dataIndex: 'quantity',
width: '15%',
}, },
{ {
title: '单位', title: '单位',
dataIndex: 'unit', dataIndex: 'unit',
width: '15%',
} }
]; ];
@@ -181,7 +176,6 @@ const SectionManagement = () => {
{ {
title: '项目名称', title: '项目名称',
dataIndex: 'name', dataIndex: 'name',
width: '20%',
render: (_, __, index) => ( render: (_, __, index) => (
<Form.Item <Form.Item
name={[index, 'name']} name={[index, 'name']}
@@ -194,7 +188,6 @@ const SectionManagement = () => {
{ {
title: '描述', title: '描述',
dataIndex: 'description', dataIndex: 'description',
width: '25%',
render: (_, __, index) => ( render: (_, __, index) => (
<Form.Item <Form.Item
name={[index, 'description']} name={[index, 'description']}
@@ -207,7 +200,6 @@ const SectionManagement = () => {
{ {
title: '单价', title: '单价',
dataIndex: 'price', dataIndex: 'price',
width: '15%',
render: (_, __, index) => ( render: (_, __, index) => (
<Form.Item <Form.Item
name={[index, 'price']} name={[index, 'price']}
@@ -225,7 +217,6 @@ const SectionManagement = () => {
{ {
title: '数量', title: '数量',
dataIndex: 'quantity', dataIndex: 'quantity',
width: '15%',
render: (_, __, index) => ( render: (_, __, index) => (
<Form.Item <Form.Item
name={[index, 'quantity']} name={[index, 'quantity']}
@@ -243,7 +234,6 @@ const SectionManagement = () => {
{ {
title: '单位', title: '单位',
dataIndex: 'unit', dataIndex: 'unit',
width: '15%',
render: (_, __, index) => ( render: (_, __, index) => (
<Form.Item <Form.Item
name={[index, 'unit']} name={[index, 'unit']}
@@ -268,7 +258,6 @@ const SectionManagement = () => {
}, },
{ {
title: '操作', title: '操作',
width: '10%',
render: (_, __, index, { remove }) => ( render: (_, __, index, { remove }) => (
<Button <Button
type="link" type="link"
@@ -350,6 +339,7 @@ const SectionManagement = () => {
</div> </div>
<div className="p-4"> <div className="p-4">
<Table <Table
scroll={{ x: true }}
dataSource={section.attributes.items} dataSource={section.attributes.items}
columns={drawerColumns} columns={drawerColumns}
pagination={false} pagination={false}

View File

@@ -88,7 +88,7 @@ const ServiceType = () => {
<PlusOutlined /> 新增服务类型 <PlusOutlined /> 新增服务类型
</Button> </Button>
<Table columns={columns} dataSource={data} rowKey="id" /> <Table scroll={{ x: true }} columns={columns} dataSource={data} rowKey="id" />
<Modal <Modal
title={editingId ? '编辑服务类型' : '新增服务类型'} title={editingId ? '编辑服务类型' : '新增服务类型'}

View File

@@ -120,7 +120,6 @@ const UnitManagement = () => {
{ {
title: '单位名称', title: '单位名称',
dataIndex: ['attributes', 'name'], dataIndex: ['attributes', 'name'],
width: '60%',
render: (text, record) => { render: (text, record) => {
const isEditing = record.id === editingKey; const isEditing = record.id === editingKey;
return isEditing ? ( return isEditing ? (
@@ -139,7 +138,6 @@ const UnitManagement = () => {
}, },
{ {
title: '操作', title: '操作',
width: '40%',
render: (_, record) => { render: (_, record) => {
const isEditing = record.id === editingKey; const isEditing = record.id === editingKey;
return isEditing ? ( return isEditing ? (
@@ -235,6 +233,7 @@ const UnitManagement = () => {
<Form form={form} className="flex-1"> <Form form={form} className="flex-1">
<Table <Table
scroll={{ x: true }}
dataSource={data} dataSource={data}
columns={columns} columns={columns}
rowKey="id" rowKey="id"

View File

@@ -74,7 +74,6 @@ const SupplierPage = () => {
dataIndex: 'created_at', dataIndex: 'created_at',
key: 'created_at', key: 'created_at',
sorter: true, sorter: true,
width: 180,
render: (text) => ( render: (text) => (
<span>{new Date(text).toLocaleString('zh-CN', { <span>{new Date(text).toLocaleString('zh-CN', {
year: 'numeric', year: 'numeric',
@@ -88,7 +87,7 @@ const SupplierPage = () => {
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 150, fixed: 'right',
render: (_, record) => ( render: (_, record) => (
<Space size={0}> <Space size={0}>
<Button <Button
@@ -136,6 +135,7 @@ const SupplierPage = () => {
} }
> >
<Table <Table
scroll={{ x: true }}
columns={columns} columns={columns}
dataSource={suppliers} dataSource={suppliers}
rowKey="id" rowKey="id"

View File

@@ -47,7 +47,7 @@ const ResourceTaskForm = () => {
}, },
{ {
title: "操作", title: "操作",
width: 100,
render: (_, record, index) => ( render: (_, record, index) => (
<Button <Button
type="link" type="link"
@@ -261,6 +261,7 @@ const ResourceTaskForm = () => {
bordered={false} bordered={false}
> >
<Table <Table
scroll={{ x: true }}
dataSource={dataSource} dataSource={dataSource}
columns={columns} columns={columns}
pagination={false} pagination={false}

View File

@@ -86,7 +86,7 @@ const ResourceTask = () => {
}, },
{ {
title: "操作", title: "操作",
width: 250, fixed: 'right',
key: "action", key: "action",
render: (_, record) => ( render: (_, record) => (
<Space size={0}> <Space size={0}>
@@ -146,6 +146,7 @@ const ResourceTask = () => {
} }
> >
<Table <Table
scroll={{ x: true }}
columns={columns} columns={columns}
dataSource={quotations} dataSource={quotations}
rowKey="id" rowKey="id"

View File

@@ -15,7 +15,7 @@ export const ExpandedMemberships = ({ teamId }) => {
const loadMemberships = async () => { const loadMemberships = async () => {
try { try {
setLoading(true); setLoading(true);
const { data } = await supabaseService.get('team_membership', { const { data } = await SupabaseService.select('team_membership', {
select: ` select: `
id, id,
role, role,

View File

@@ -149,6 +149,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
</Button> */} </Button> */}
<Form form={form} component={false}> <Form form={form} component={false}>
<Table <Table
scroll={{ x: true }}
components={{ components={{
body: { body: {
cell: EditableMembershipCell, cell: EditableMembershipCell,

View File

@@ -175,7 +175,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
return ( return (
<Form form={form} component={false}> <Form form={form} component={false}>
<Table <Table
className='w-full' scroll={{ x: true }}
pagination={pagination} pagination={pagination}
loading={loading||tableLoading} loading={loading||tableLoading}
components={{ components={{

View File

@@ -57,7 +57,7 @@ const TeamManagement = () => {
// 获取团队成员 // 获取团队成员
const getTeamMembers = async (teamId) => { const getTeamMembers = async (teamId) => {
try { try {
const result = await supabaseService.get('team_memberships', { const result = await SupabaseService.select('team_memberships', {
select: '*', select: '*',
relations: { relations: {
user: 'id, email, name' user: 'id, email, name'

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { routes } from '@/routes/routes'; import { routes } from '@/routes/routes';
import * as AntIcons from '@ant-design/icons'; import * as AntIcons from '@ant-design/icons';
import { ColorIcon } from '@/components/common/ColorIcon'; import { ColorIcon } from '@/components/Layout/ColorIcon';