框架优化
This commit is contained in:
@@ -3,8 +3,7 @@ import { Layout, Switch, Button, Dropdown } from 'antd';
|
||||
import { UserOutlined, LogoutOutlined } from '@ant-design/icons';
|
||||
import { useTheme } from '@/contexts/ThemeContext';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { MenuTrigger } from '../common/MenuTrigger';
|
||||
|
||||
import { MenuTrigger } from '../Layout/MenuTrigger';
|
||||
const { Header: AntHeader } = Layout;
|
||||
|
||||
const Header = ({ collapsed, setCollapsed }) => {
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import React from 'react';
|
||||
import { RocketOutlined } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const Logo = ({ collapsed, isDarkMode }) => (
|
||||
<div className="logo">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<RocketOutlined className="text-2xl text-primary-500" />
|
||||
{!collapsed && (
|
||||
<h1 className="text-lg font-semibold m-0" style={{
|
||||
background: 'var(--primary-gradient)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent'
|
||||
}}>
|
||||
<Text className="text-lg font-semibold m-0" style={{ color: 'var(--primary-color)' }}>
|
||||
Uppeta
|
||||
</h1>
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Layout, Menu } from 'antd';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useTheme } from '@/contexts/ThemeContext';
|
||||
import { getMenuItems } from '@/utils/menuUtils';
|
||||
import { Logo } from '@/components/common/Logo';
|
||||
import { Logo } from '@/components/Layout/Logo';
|
||||
|
||||
const { Sider } = Layout;
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
@@ -1,7 +1,7 @@
|
||||
import { supabase } from '@/config/supabase'
|
||||
|
||||
class SupabaseService {
|
||||
async get(table, options = {}) {
|
||||
async select(table, options = {}) {
|
||||
try {
|
||||
let query = supabase
|
||||
.from(table)
|
||||
@@ -96,6 +96,26 @@ class SupabaseService {
|
||||
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()
|
||||
@@ -10,7 +10,7 @@ export const useTeamMembership = (teamId) => {
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await supabaseService.get('team_memberships', {
|
||||
const result = await SupabaseService.select('team_memberships', {
|
||||
select: '*',
|
||||
relations: {
|
||||
user: 'id, email, name'
|
||||
|
||||
@@ -4,7 +4,7 @@ export const useTeams = () => {
|
||||
// 获取团队列表
|
||||
const fetchTeams = async (params = {}) => {
|
||||
try {
|
||||
const result = await supabaseService.get('teams', {
|
||||
const result = await SupabaseService.select('teams', {
|
||||
select: `
|
||||
id,
|
||||
name,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { supabaseService } from "@/hooks/supabaseService";
|
||||
|
||||
const Login = () => {
|
||||
const navigate = useNavigate();
|
||||
const { login, signInWithGoogle, loading } = useAuth();
|
||||
const { login, signInWithGoogle, emailLoading, googleLoading } = useAuth();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleLogin = async (values) => {
|
||||
@@ -75,6 +75,8 @@ const Login = () => {
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
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"
|
||||
>
|
||||
登录
|
||||
@@ -89,7 +91,8 @@ const Login = () => {
|
||||
icon={<GoogleOutlined />}
|
||||
block
|
||||
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"
|
||||
>
|
||||
使用 Google 账号登录
|
||||
|
||||
@@ -92,7 +92,6 @@ const CustomerPage = () => {
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
sorter: true,
|
||||
width: 180,
|
||||
render: (text) => (
|
||||
<span>{new Date(text).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
@@ -106,7 +105,7 @@ const CustomerPage = () => {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Space size={0}>
|
||||
<Button
|
||||
@@ -157,6 +156,7 @@ const CustomerPage = () => {
|
||||
columns={columns}
|
||||
dataSource={customers}
|
||||
rowKey="id"
|
||||
scroll={{ x: true }}
|
||||
loading={loading}
|
||||
onChange={handleTableChange}
|
||||
pagination={{
|
||||
|
||||
@@ -195,7 +195,7 @@ const QuotationPage = () => {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 280,
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Space size={0}>
|
||||
<Button
|
||||
@@ -321,6 +321,7 @@ const QuotationPage = () => {
|
||||
columns={columns}
|
||||
dataSource={quotations}
|
||||
rowKey="id"
|
||||
scroll={{ x: true }}
|
||||
loading={loadingQuotations}
|
||||
onChange={handleTableChange}
|
||||
pagination={{
|
||||
|
||||
@@ -110,7 +110,6 @@ const CategoryDrawer = () => {
|
||||
{
|
||||
title: '分类名称',
|
||||
dataIndex: ['attributes', 'name'],
|
||||
width: '60%',
|
||||
render: (text, record) => {
|
||||
const isEditing = record.id === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -128,7 +127,6 @@ const CategoryDrawer = () => {
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: '40%',
|
||||
render: (_, record) => {
|
||||
const isEditing = record.id === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -221,6 +219,7 @@ const CategoryDrawer = () => {
|
||||
</Button>
|
||||
<Form form={form}>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
|
||||
@@ -374,7 +374,6 @@ const ServicePage = () => {
|
||||
title: "项目名称",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "25%",
|
||||
render: (text, record) => {
|
||||
const isEditing = record.key === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -408,7 +407,6 @@ const ServicePage = () => {
|
||||
title: "描述",
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
width: "25%",
|
||||
render: (text, record) => {
|
||||
const isEditing = record.key === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -454,7 +452,6 @@ const ServicePage = () => {
|
||||
title: "单价",
|
||||
dataIndex: "price",
|
||||
key: "price",
|
||||
width: "15%",
|
||||
render: (price, record) => {
|
||||
const isEditing = record.key === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -489,7 +486,6 @@ const ServicePage = () => {
|
||||
title: "数量",
|
||||
dataIndex: "quantity",
|
||||
key: "quantity",
|
||||
width: "15%",
|
||||
render: (quantity, record) => {
|
||||
const isEditing = record.key === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -517,7 +513,6 @@ const ServicePage = () => {
|
||||
{
|
||||
title: "小计",
|
||||
key: "subtotal",
|
||||
width: "20%",
|
||||
render: (_, record) => (
|
||||
<span className="text-blue-600 font-medium">
|
||||
¥
|
||||
@@ -534,7 +529,7 @@ const ServicePage = () => {
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: "150px",
|
||||
fixed: 'right',
|
||||
render: (_, record) => {
|
||||
const isEditing = record.key === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -630,6 +625,7 @@ const ServicePage = () => {
|
||||
>
|
||||
<Table
|
||||
columns={itemColumns}
|
||||
scroll={{ x: true }}
|
||||
dataSource={section.items.map((item, itemIndex) => ({
|
||||
...item,
|
||||
key: `${record.id}-${sectionIndex}-${itemIndex}`,
|
||||
|
||||
@@ -151,28 +151,23 @@ const SectionManagement = () => {
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'name',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
width: '25%',
|
||||
},
|
||||
{
|
||||
title: '单价',
|
||||
dataIndex: 'price',
|
||||
width: '15%',
|
||||
render: (price) => `¥${price}`
|
||||
},
|
||||
{
|
||||
title: '数量',
|
||||
dataIndex: 'quantity',
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
title: '单位',
|
||||
dataIndex: 'unit',
|
||||
width: '15%',
|
||||
}
|
||||
];
|
||||
|
||||
@@ -181,7 +176,6 @@ const SectionManagement = () => {
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'name',
|
||||
width: '20%',
|
||||
render: (_, __, index) => (
|
||||
<Form.Item
|
||||
name={[index, 'name']}
|
||||
@@ -194,7 +188,6 @@ const SectionManagement = () => {
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
width: '25%',
|
||||
render: (_, __, index) => (
|
||||
<Form.Item
|
||||
name={[index, 'description']}
|
||||
@@ -207,7 +200,6 @@ const SectionManagement = () => {
|
||||
{
|
||||
title: '单价',
|
||||
dataIndex: 'price',
|
||||
width: '15%',
|
||||
render: (_, __, index) => (
|
||||
<Form.Item
|
||||
name={[index, 'price']}
|
||||
@@ -225,7 +217,6 @@ const SectionManagement = () => {
|
||||
{
|
||||
title: '数量',
|
||||
dataIndex: 'quantity',
|
||||
width: '15%',
|
||||
render: (_, __, index) => (
|
||||
<Form.Item
|
||||
name={[index, 'quantity']}
|
||||
@@ -243,7 +234,6 @@ const SectionManagement = () => {
|
||||
{
|
||||
title: '单位',
|
||||
dataIndex: 'unit',
|
||||
width: '15%',
|
||||
render: (_, __, index) => (
|
||||
<Form.Item
|
||||
name={[index, 'unit']}
|
||||
@@ -268,7 +258,6 @@ const SectionManagement = () => {
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: '10%',
|
||||
render: (_, __, index, { remove }) => (
|
||||
<Button
|
||||
type="link"
|
||||
@@ -350,6 +339,7 @@ const SectionManagement = () => {
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
dataSource={section.attributes.items}
|
||||
columns={drawerColumns}
|
||||
pagination={false}
|
||||
|
||||
@@ -88,7 +88,7 @@ const ServiceType = () => {
|
||||
<PlusOutlined /> 新增服务类型
|
||||
</Button>
|
||||
|
||||
<Table columns={columns} dataSource={data} rowKey="id" />
|
||||
<Table scroll={{ x: true }} columns={columns} dataSource={data} rowKey="id" />
|
||||
|
||||
<Modal
|
||||
title={editingId ? '编辑服务类型' : '新增服务类型'}
|
||||
|
||||
@@ -120,7 +120,6 @@ const UnitManagement = () => {
|
||||
{
|
||||
title: '单位名称',
|
||||
dataIndex: ['attributes', 'name'],
|
||||
width: '60%',
|
||||
render: (text, record) => {
|
||||
const isEditing = record.id === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -139,7 +138,6 @@ const UnitManagement = () => {
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: '40%',
|
||||
render: (_, record) => {
|
||||
const isEditing = record.id === editingKey;
|
||||
return isEditing ? (
|
||||
@@ -235,6 +233,7 @@ const UnitManagement = () => {
|
||||
|
||||
<Form form={form} className="flex-1">
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
dataSource={data}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
|
||||
@@ -74,7 +74,6 @@ const SupplierPage = () => {
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
sorter: true,
|
||||
width: 180,
|
||||
render: (text) => (
|
||||
<span>{new Date(text).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
@@ -88,7 +87,7 @@ const SupplierPage = () => {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Space size={0}>
|
||||
<Button
|
||||
@@ -136,6 +135,7 @@ const SupplierPage = () => {
|
||||
}
|
||||
>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
columns={columns}
|
||||
dataSource={suppliers}
|
||||
rowKey="id"
|
||||
|
||||
@@ -47,7 +47,7 @@ const ResourceTaskForm = () => {
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 100,
|
||||
|
||||
render: (_, record, index) => (
|
||||
<Button
|
||||
type="link"
|
||||
@@ -261,6 +261,7 @@ const ResourceTaskForm = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
|
||||
@@ -86,7 +86,7 @@ const ResourceTask = () => {
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 250,
|
||||
fixed: 'right',
|
||||
key: "action",
|
||||
render: (_, record) => (
|
||||
<Space size={0}>
|
||||
@@ -146,6 +146,7 @@ const ResourceTask = () => {
|
||||
}
|
||||
>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
columns={columns}
|
||||
dataSource={quotations}
|
||||
rowKey="id"
|
||||
|
||||
@@ -15,7 +15,7 @@ export const ExpandedMemberships = ({ teamId }) => {
|
||||
const loadMemberships = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await supabaseService.get('team_membership', {
|
||||
const { data } = await SupabaseService.select('team_membership', {
|
||||
select: `
|
||||
id,
|
||||
role,
|
||||
|
||||
@@ -149,6 +149,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
|
||||
</Button> */}
|
||||
<Form form={form} component={false}>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
components={{
|
||||
body: {
|
||||
cell: EditableMembershipCell,
|
||||
|
||||
@@ -175,7 +175,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
|
||||
return (
|
||||
<Form form={form} component={false}>
|
||||
<Table
|
||||
className='w-full'
|
||||
scroll={{ x: true }}
|
||||
pagination={pagination}
|
||||
loading={loading||tableLoading}
|
||||
components={{
|
||||
|
||||
@@ -57,7 +57,7 @@ const TeamManagement = () => {
|
||||
// 获取团队成员
|
||||
const getTeamMembers = async (teamId) => {
|
||||
try {
|
||||
const result = await supabaseService.get('team_memberships', {
|
||||
const result = await SupabaseService.select('team_memberships', {
|
||||
select: '*',
|
||||
relations: {
|
||||
user: 'id, email, name'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { routes } from '@/routes/routes';
|
||||
import * as AntIcons from '@ant-design/icons';
|
||||
import { ColorIcon } from '@/components/common/ColorIcon';
|
||||
import { ColorIcon } from '@/components/Layout/ColorIcon';
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user