页面登录优化,team优化

This commit is contained in:
liamzi
2024-12-25 17:25:10 +08:00
parent 5eb069c63f
commit 4cbec25b53
18 changed files with 535 additions and 4816 deletions

View File

@@ -21,7 +21,7 @@ export const createSupabase = () => {
detectSessionInUrl: false,
},
db: {
schema: 'limq_dev'
schema: 'limq'
}
}
);

View File

@@ -1,5 +1,5 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import React, { createContext,useContext, useState, useEffect, } from 'react';
import { ConfigProvider, theme } from "antd";
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
@@ -19,7 +19,52 @@ export const ThemeProvider = ({ children }) => {
return (
<ThemeContext.Provider value={{ isDarkMode, toggleTheme }}>
{children}
<ConfigProvider
theme={{
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
token: {
colorPrimary: "#1677ff",
borderRadius: 4,
colorBgContainer: isDarkMode ? "#141414" : "#ffffff",
colorBgElevated: isDarkMode ? "#1f1f1f" : "#ffffff",
colorText: isDarkMode
? "rgba(255, 255, 255, 0.85)"
: "rgba(0, 0, 0, 0.85)",
colorTextSecondary: isDarkMode
? "rgba(255, 255, 255, 0.45)"
: "rgba(0, 0, 0, 0.45)",
},
components: {
// 为所有支持 variant 的组件设置 filled 模式
Form: {
variant: 'filled',
},
Input: {
variant: 'filled',
},
Select: {
variant: 'filled',
},
TreeSelect: {
variant: 'filled',
},
DatePicker: {
variant: 'filled',
},
TimePicker: {
variant: 'filled',
},
Cascader: {
variant: 'filled',
},
AutoComplete: {
variant: 'filled',
},
}
}}
>
{ children }
</ConfigProvider>
</ThemeContext.Provider>
);
};

View File

@@ -0,0 +1,101 @@
import { supabase } from '@/config/supabase'
class SupabaseService {
async get(table, options = {}) {
try {
let query = supabase
.from(table)
.select(options.select || '*', { count: 'exact' })
// 处理精确匹配条件
if (options.match) {
query = query.match(options.match)
}
// 处理过滤条件
if (options.filter) {
Object.entries(options.filter).forEach(([key, condition]) => {
Object.entries(condition).forEach(([operator, value]) => {
query = query.filter(key, operator, value)
})
})
}
// 处理排序
if (options.order) {
query = query.order(options.order.column, {
ascending: options.order.ascending
})
}
// 处理分页
if (options.page && options.pageSize) {
const from = (options.page - 1) * options.pageSize
const to = from + options.pageSize - 1
query = query.range(from, to)
}
const { data, error, count } = await query
if (error) throw error
return {
data,
total: count || 0
}
} catch (error) {
console.error(`Error fetching from ${table}:`, error.message)
throw error
}
}
// 通用 INSERT 请求
async insert(table, data) {
try {
const { data: result, error } = await supabase
.from(table)
.insert(data)
.select()
if (error) throw error
return result
} catch (error) {
console.error(`Error inserting into ${table}:`, error.message)
throw error
}
}
// 通用 UPDATE 请求
async update(table, match, updates) {
try {
const { data, error } = await supabase
.from(table)
.update(updates)
.match(match)
.select()
if (error) throw error
return data
} catch (error) {
console.error(`Error updating ${table}:`, error.message)
throw error
}
}
// 通用 DELETE 请求
async delete(table, match) {
try {
const { error } = await supabase
.from(table)
.delete()
.match(match)
if (error) throw error
return true
} catch (error) {
console.error(`Error deleting from ${table}:`, error.message)
throw error
}
}
}
export const supabaseService = new SupabaseService()

View File

@@ -1,71 +1,58 @@
import { useState, useCallback } from 'react';
import { message } from 'antd';
import { teamMembershipService } from '@/services/supabase/teamMembership';
import { supabaseService } from '@/hooks/supabaseService';
export const useTeamMemberships = (teamId) => {
const [memberships, setMemberships] = useState([]);
export const useTeamMembership = (teamId) => {
const [loading, setLoading] = useState(false);
const [memberships, setMemberships] = useState([]);
const fetchMemberships = useCallback(async () => {
const loadMemberships = useCallback(async () => {
if (!teamId) return;
try {
setLoading(true);
const data = await teamMembershipService.getMemberships(teamId);
setMemberships(data);
try {
const result = await supabaseService.get('team_memberships', {
select: '*',
relations: {
user: 'id, email, name'
},
match: { teamId }
});
setMemberships(result.data || []);
} catch (error) {
message.error('Failed to fetch team memberships');
console.error(error);
console.error('获取成员列表失败:', error);
} finally {
setLoading(false);
}
}, [teamId]);
const createMembership = async (values) => {
try {
const newMembership = await teamMembershipService.createMembership({
const addMembership = async (values) => {
const result = await supabaseService.insert('team_memberships', {
...values,
teamId,
teamId
});
setMemberships(prev => [...prev, newMembership]);
message.success('Member added successfully');
return newMembership;
} catch (error) {
message.error('Failed to add member');
throw error;
}
await loadMemberships();
return result[0];
};
const updateMembership = async (id, values) => {
try {
const updatedMembership = await teamMembershipService.updateMembership(id, values);
setMemberships(prev => prev.map(membership =>
membership.id === id ? updatedMembership : membership
));
message.success('Member updated successfully');
return updatedMembership;
} catch (error) {
message.error('Failed to update member');
throw error;
}
const result = await supabaseService.update('team_memberships',
{ id },
values
);
await loadMemberships();
return result[0];
};
const deleteMembership = async (id) => {
try {
await teamMembershipService.deleteMembership(id);
setMemberships(prev => prev.filter(membership => membership.id !== id));
message.success('Member removed successfully');
} catch (error) {
message.error('Failed to remove member');
throw error;
}
await supabaseService.delete('team_memberships', { id });
await loadMemberships();
};
return {
memberships,
loading,
fetchMemberships,
createMembership,
memberships,
loadMemberships,
addMembership,
updateMembership,
deleteMembership,
};

View File

@@ -1,68 +1,96 @@
import { useState, useCallback } from 'react';
import { message } from 'antd';
import { teamService } from '@/services/supabase/team';
import { supabaseService } from '@/hooks/supabaseService';
export const useTeams = (pagination, sorter) => {
const [teams, setTeams] = useState([]);
const [loading, setLoading] = useState(false);
const fetchTeams = useCallback(async (params = {}) => {
export const useTeams = () => {
// 获取团队列表
const fetchTeams = async (params = {}) => {
try {
setLoading(true);
const { data, total } = await teamService.getTeams({
page: params.current || pagination.current,
pageSize: params.pageSize || pagination.pageSize,
orderBy: params.field || sorter.field,
ascending: params.order === 'ascend',
...(params?.search!==''?{searchQuery:params.search}:{})
const result = await supabaseService.get('teams', {
select: `
id,
name,
description,
attributes,
created_at,
updated_at,
avatar_url,
team_membership!inner (
id,
user_id,
role,
is_creator,
users (
id,
email
)
)
`,
filter: {
deleted_at: { is: null },
...(params.search ? { name: { ilike: `%${params.search}%` } } : {})
},
order: {
column: params.field || 'created_at',
ascending: params.order === 'ascend'
},
page: params.current || 1,
pageSize: params.pageSize || 10
});
setTeams(data);
return { data, total };
return result;
} catch (error) {
console.error(error);
message.error('获取团队列表失败');
} finally {
setLoading(false);
console.error('获取团队列表失败:', error);
throw error;
}
}, [pagination.current, pagination.pageSize, sorter.field, sorter.order]);
};
// 创建团队
const createTeam = async (values) => {
try {
const newTeam = await teamService.createTeam(values);
setTeams(prev => [...prev, newTeam]);
return newTeam;
const newTeam = await supabaseService.insert('teams', {
name: values.name,
description: values.description
});
// 创建团队成员关系
await supabaseService.insert('team_membership', {
team_id: newTeam[0].id,
user_id: values.userId,
role: 'OWNER',
is_creator: true,
id: newTeam[0].id
});
return newTeam[0];
} catch (error) {
message.error('Failed to create team');
console.error('创建团队失败:', error);
throw error;
}
};
// 更新团队
const updateTeam = async (id, values) => {
try {
const updatedTeam = await teamService.updateTeam(id, values);
setTeams(prev => prev.map(team =>
team.id === id ? updatedTeam : team
));
return updatedTeam;
const result = await supabaseService.update('teams',
{ id },
values
);
return result[0];
} catch (error) {
message.error('Failed to update team');
console.error('更新团队失败:', error);
throw error;
}
};
// 删除团队
const deleteTeam = async (id) => {
try {
await teamService.deleteTeam(id);
setTeams(prev => prev.filter(team => team.id !== id));
await supabaseService.delete('teams', { id });
} catch (error) {
console.error('删除团队失败:', error);
throw error;
}
};
return {
teams,
loading,
fetchTeams,
createTeam,
updateTeam,

View File

@@ -1,8 +1,9 @@
import React from "react";
import { Form, Input, Button, Divider, message } from "antd";
import { GoogleOutlined } from "@ant-design/icons";
import { GoogleOutlined, MailOutlined, LockOutlined } from "@ant-design/icons";
import { Link, useNavigate } from "react-router-dom";
import { useAuth } from "@/contexts/AuthContext";
import { supabaseService } from "@/hooks/supabaseService";
const Login = () => {
const navigate = useNavigate();
@@ -10,32 +11,22 @@ const Login = () => {
const [form] = Form.useForm();
const handleLogin = async (values) => {
try {
await login(values.email, values.password);
message.success("登录成功!");
navigate("/");
} catch (error) {
console.error("Login error:", error);
}
login(values.email, values.password);
};
const handleGoogleLogin = async () => {
try {
await signInWithGoogle();
} catch (error) {
console.error("Google login error:", error);
}
};
return (
<div className="min-h-screen flex bg-gradient-to-br from-[#f5f7fa] to-[#c3cfe2]">
<div className="w-full max-w-[1200px] mx-auto flex p-8 gap-16">
<div className="flex-1 bg-white p-12 rounded-[20px] shadow-[0_10px_30px_rgba(0,0,0,0.1)]">
<div className="mb-8 text-center">
<h1 className="text-3xl font-bold mb-2 bg-gradient-to-r from-primary-500 to-[#36cff0] bg-clip-text text-transparent">
<div className="min-h-screen flex items-center justify-center bg-[#f5f8ff] dark:bg-gray-900">
<div className="w-full max-w-[1200px] mx-auto flex p-4 md:p-0">
{/* 左侧登录表单 */}
<div className="w-full md:w-1/2 bg-white dark:bg-gray-800 p-8 md:p-12 rounded-3xl shadow-2xl dark:shadow-gray-800/50">
<div className="mb-10 text-center">
<h1 className="text-4xl font-bold mb-3 bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-transparent">
Uppeta
</h1>
<p className="text-gray-500">欢迎回来请登录您的账户</p>
<p className="text-gray-500 dark:text-gray-400">
欢迎回来请登录您的账户
</p>
</div>
<Form
@@ -52,48 +43,66 @@ const Login = () => {
{ type: "email", message: "请输入有效的邮箱地址!" },
]}
>
<Input placeholder="邮箱" />
<Input
prefix={<MailOutlined className="text-gray-400" />}
placeholder="邮箱"
className="h-12 rounded-xl dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
/>
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: "请输入密码!" }]}
>
<Input.Password placeholder="密码" />
<Input.Password
prefix={<LockOutlined className="text-gray-400" />}
placeholder="密码"
className="h-12 rounded-xl dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
/>
</Form.Item>
<div className="flex justify-between items-center mb-6">
<Form.Item name="remember" valuePropName="checked" noStyle>
<div className="flex justify-end mb-6">
<Link
to="/forgot-password"
className="text-primary-500 hover:text-primary-600"
className="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 text-sm"
>
忘记密码
</Link>
</Form.Item>
</div>
<Form.Item>
<Button type="primary" htmlType="submit" block>
<Button
type="primary"
htmlType="submit"
block
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"
>
登录
</Button>
</Form.Item>
<Divider></Divider>
<Divider className="dark:border-gray-700">
<span className="text-gray-400"></span>
</Divider>
<Button
icon={<GoogleOutlined />}
block
onClick={handleGoogleLogin}
className="mb-6"
onClick={signInWithGoogle}
loading={loading}
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 账号登录
</Button>
</Form>
</div>
<div className="flex-1 hidden md:block rounded-[20px] bg-[url('https://uppeta.com/img/svg/main.svg')] bg-center bg-contain bg-no-repeat" />
{/* 右侧图片 */}
<div className="hidden md:flex md:w-1/2 items-center justify-center p-12">
<div className="w-full h-full rounded-3xl bg-[url('https://uppeta.com/img/svg/main.svg')] bg-center bg-contain bg-no-repeat dark:opacity-90 transform hover:scale-105 transition-all duration-500" />
</div>
</div>
</div>
);

View File

@@ -67,22 +67,7 @@ export const TeamForm = ({ form }) => {
</Upload>
</Form.Item>
<Form.Item
name="tags"
label="标签"
rules={[{ required: true, message: '请选择至少一个标签' }]}
>
<Select
mode="tags"
placeholder="请输入或选择标签"
options={[
{ label: '研发', value: 'development' },
{ label: '设计', value: 'design' },
{ label: '运营', value: 'operation' },
{ label: '市场', value: 'marketing' },
]}
/>
</Form.Item>
</Form>
);
};

View File

@@ -1,24 +1,72 @@
import React, { useState } from 'react';
import { Modal, Form, Input, Select, message } from 'antd';
import React, { useState, useEffect } from 'react';
import { Modal,Button, Form, Input, Select, message } from 'antd';
import { MembershipTable } from './MembershipTable';
import { supabaseService } from '@/hooks/supabaseService';
const { Option } = Select;
export const ExpandedMemberships = ({ teamId, memberships: initialMemberships }) => {
const [memberships, setMemberships] = useState(initialMemberships);
export const ExpandedMemberships = ({ teamId }) => {
const [memberships, setMemberships] = useState([]);
const [isModalVisible, setIsModalVisible] = useState(false);
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleUpdate = (id, values) => {
setMemberships(prev =>
prev.map(item => item.id === id ? { ...item, ...values } : item)
);
message.success('成员信息已更新');
// 加载团队成员
const loadMemberships = async () => {
try {
setLoading(true);
const { data } = await supabaseService.get('team_membership', {
select: `
id,
role,
is_creator,
user:users (
id,
email
)
`,
match: { team_id: teamId }
});
setMemberships(data || []);
} catch (error) {
message.error('获取团队成员失败');
console.error('Failed to fetch memberships:', error);
} finally {
setLoading(false);
}
};
const handleDelete = (id) => {
setMemberships(prev => prev.filter(item => item.id !== id));
useEffect(() => {
if (teamId) {
loadMemberships();
}
}, [teamId]);
const handleUpdate = async (id, values) => {
try {
await supabaseService.update('team_membership',
{ id },
{
role: values.role
}
);
message.success('成员角色已更新');
await loadMemberships();
} catch (error) {
message.error('更新成员角色失败');
console.error('Update failed:', error);
}
};
const handleDelete = async (id) => {
try {
await supabaseService.delete('team_membership', { id });
message.success('成员已删除');
await loadMemberships();
} catch (error) {
message.error('删除成员失败');
console.error('Delete failed:', error);
}
};
const handleAdd = () => {
@@ -29,16 +77,18 @@ export const ExpandedMemberships = ({ teamId, memberships: initialMemberships })
const handleModalOk = async () => {
try {
const values = await form.validateFields();
const newMember = {
id: `${Date.now()}`,
teamId,
isCreator: false,
...values,
};
setMemberships(prev => [...prev, newMember]);
await supabaseService.insert('team_membership', {
team_id: teamId,
user_id: values.user_id,
role: values.role,
is_creator: false
});
setIsModalVisible(false);
message.success('成员已添加');
await loadMemberships();
} catch (error) {
message.error('添加成员失败');
console.error('Add failed:', error);
}
};
@@ -46,53 +96,14 @@ export const ExpandedMemberships = ({ teamId, memberships: initialMemberships })
return (
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg">
<h3 className="text-lg font-medium mb-4">团队成员</h3>
<MembershipTable
loading={loading}
memberships={memberships}
onUpdate={handleUpdate}
onDelete={handleDelete}
onAdd={handleAdd}
/>
<Modal
title="添加成员"
open={isModalVisible}
onOk={handleModalOk}
onCancel={() => setIsModalVisible(false)}
>
<Form
form={form}
layout="vertical"
>
<Form.Item
name={['user', 'name']}
label="姓名"
rules={[{ required: true, message: '请输入姓名!' }]}
>
<Input />
</Form.Item>
<Form.Item
name={['user', 'email']}
label="邮箱"
rules={[
{ required: true, message: '请输入邮箱!' },
{ type: 'email', message: '请输入有效的邮箱!' }
]}
>
<Input />
</Form.Item>
<Form.Item
name="role"
label="角色"
rules={[{ required: true, message: '请选择角色!' }]}
>
<Select>
<Option value="ADMIN">Admin</Option>
<Option value="MEMBER">Member</Option>
</Select>
</Form.Item>
</Form>
</Modal>
</div>
);
};

View File

@@ -32,13 +32,13 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
const columns = [
{
title: '成员',
dataIndex: 'users',
key: 'users',
dataIndex: 'user',
key: 'user',
editable: true,
render: (users) => (
render: (user) => (
<div className="flex flex-col">
<span className="font-medium">{users.email}</span>
<span className="text-gray-500 text-sm">{users.email}</span>
<span className="font-medium">{user?.email}</span>
<span className="text-gray-500 text-sm">{user?.email}</span>
</div>
),
},
@@ -139,14 +139,14 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
return (
<div className="space-y-4">
<Button
{/* <Button
type="primary"
icon={<PlusOutlined />}
onClick={onAdd}
className="mb-4"
>
添加成员
</Button>
</Button> */}
<Form form={form} component={false}>
<Table
components={{

View File

@@ -3,13 +3,6 @@ import { Form, Input, Space } from 'antd';
export const UserForm = ({ nameProps, emailProps }) => (
<Space.Compact block>
<Form.Item
{...nameProps}
style={{ margin: 0, width: '50%' }}
rules={[{ required: true, message: '请输入姓名!' }]}
>
<Input placeholder="姓名" />
</Form.Item>
<Form.Item
{...emailProps}
style={{ margin: 0, width: '50%' }}

View File

@@ -1,12 +1,15 @@
import React, { useEffect, useState } from 'react';
import { Card, App } from 'antd';
import { Card, App, message } from 'antd';
import { TeamHeader } from './components/TeamHeader';
import { TeamTable } from './components/TeamTable';
import CreateTeamModal from './components/CreateTeamModal';
import { useTeams } from '@/hooks/team/useTeams';
import { useAuth } from '@/contexts/AuthContext';
import { supabaseService } from '@/hooks/supabaseService';
const TeamManagement = () => {
const [teams, setTeams] = useState([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
@@ -19,25 +22,96 @@ const TeamManagement = () => {
});
const {
teams,
loading,
fetchTeams,
createTeam,
updateTeam,
deleteTeam
} = useTeams(pagination, sorter);
} = useTeams();
const [modalVisible, setModalVisible] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const { user } = useAuth();
useEffect(() => {
const loadTeams = async () => {
const { total } = await fetchTeams();
// 加载团队列表
const loadTeams = async (params = {}) => {
try {
setLoading(true);
const {data,total} = await fetchTeams({
current: params.current || pagination.current,
pageSize: params.pageSize || pagination.pageSize,
field: params.field || sorter.field,
order: params.order || sorter.order,
search: params.search
});
console.log(data,'data');
setTeams(data);
setPagination(prev => ({ ...prev, total }));
} catch (error) {
message.error('获取团队列表失败');
} finally {
setLoading(false);
}
};
// 获取团队成员
const getTeamMembers = async (teamId) => {
try {
const result = await supabaseService.get('team_memberships', {
select: '*',
relations: {
user: 'id, email, name'
},
match: { teamId }
});
return result.data;
} catch (error) {
message.error('获取团队成员失败');
return [];
}
};
// 添加团队成员
const addTeamMember = async (teamId, userData) => {
try {
const result = await supabaseService.insert('team_memberships', {
teamId,
...userData
});
return result[0];
} catch (error) {
message.error('添加团队成员失败');
throw error;
}
};
// 更新团队成员
const updateTeamMember = async (id, values) => {
try {
const result = await supabaseService.update('team_memberships',
{ id },
values
);
return result[0];
} catch (error) {
message.error('更新团队成员失败');
throw error;
}
};
// 删除团队成员
const deleteTeamMember = async (id) => {
try {
await supabaseService.delete('team_memberships', { id });
} catch (error) {
message.error('删除团队成员失败');
throw error;
}
};
useEffect(() => {
loadTeams();
}, [fetchTeams]);
}, []);
const handleTableChange = (newPagination, filters, newSorter) => {
const params = {
@@ -58,9 +132,7 @@ const TeamManagement = () => {
order: params.order || sorter.order,
});
fetchTeams(params).then(({ total }) => {
setPagination(prev => ({ ...prev, total }));
});
loadTeams(params);
};
const handleAdd = () => {
@@ -77,28 +149,54 @@ const TeamManagement = () => {
await createTeam({ ...values, userId: user.id });
setModalVisible(false);
setPagination(prev => ({ ...prev, current: 1 }));
const { total } = await fetchTeams({ current: 1 });
setPagination(prev => ({ ...prev, total }));
await loadTeams({ current: 1 });
message.success('创建团队成功');
} catch (error) {
console.error('Failed to create team:', error);
message.error('创建团队失败');
} finally {
setConfirmLoading(false);
}
};
const onSearch=(value)=>{
fetchTeams({search:value})
const handleUpdateTeam = async (id, values) => {
try {
await updateTeam(id, values);
await loadTeams();
message.success('更新团队成功');
} catch (error) {
message.error('更新团队失败');
}
};
const handleDeleteTeam = async (id) => {
try {
await deleteTeam(id);
await loadTeams();
message.success('删除团队成功');
} catch (error) {
message.error('删除团队失败');
}
};
const handleSearch = (value) => {
loadTeams({ search: value, current: 1 });
};
return (
<App>
<Card title="团队管理" bordered={false}>
<TeamHeader onSearch={onSearch} onAdd={handleAdd} />
<TeamHeader onSearch={handleSearch} onAdd={handleAdd} />
<TeamTable
tableLoading={loading}
loading={loading}
dataSource={teams}
pagination={pagination}
onTableChange={handleTableChange}
onUpdate={updateTeam}
onDelete={deleteTeam}
onUpdate={handleUpdateTeam}
onDelete={handleDeleteTeam}
onGetMembers={getTeamMembers}
onAddMember={addTeamMember}
onUpdateMember={updateTeamMember}
onDeleteMember={deleteTeamMember}
/>
<CreateTeamModal
open={modalVisible}

View File

@@ -2,7 +2,7 @@ import React, { Suspense } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { Spin } from 'antd';
import MainLayout from '@/components/Layout/MainLayout';
import { routes } from '@/config/routes';
import { routes } from '@/routes/routes';
import Login from '@/pages/auth/Login';
import NotFound from '@/pages/notFound';
import Dashboard from '@/pages/Dashboard';

View File

@@ -1,169 +0,0 @@
import { supabase } from '@/config/supabase';
export const teamService = {
async getTeams({ page = 1, pageSize = 10, orderBy = 'created_at', ascending = false , searchQuery = ''
} = {}) {
const from = (page - 1) * pageSize;
const to = from + pageSize - 1;
let query = supabase
.from('teams')
.select(`
id,
name,
description,
attributes,
created_at,
updated_at,
deleted_at,
schema_version,
avatar_url,
team_membership(
id,
user_id,
role,
is_creator,
users(
id,
email
)
)
`, { count: 'exact' })
.is('deleted_at', null)
.order(orderBy, { ascending })
.range(from, to);
if (searchQuery) {
query = query.ilike('name', `%${searchQuery}%`);
}
const { data, error, count } = await query
.order(orderBy, { ascending })
.range(from, to);
if (error) {
console.error('Error fetching teams:', error);
throw error;
}
return {
data,
total: count || 0
};
},
// 创建团队
async createTeam({ name, description, userId }) {
const { data: team, error: teamError } = await supabase
.from('teams')
.insert([
{
name,
description
}
])
.select()
.single();
if (teamError) throw teamError;
// 创建团队成员关系(创建者)
const { error: membershipError } = await supabase
.from('team_membership')
.insert([
{
id:team.id,
team_id: team.id,
user_id: userId,
role: 'OWNER',
is_creator: true
}
]);
if (membershipError) throw membershipError;
return team;
},
// 获取单个团队详情
async getTeamById(teamId) {
const { data, error } = await supabase
.from('teams')
.select(`
id,
name,
description,
attributes,
created_at,
updated_at,
avatar_url,
team_membership(
id,
user_id,
role,
is_creator,
users(
id,
email
)
)
`)
.eq('id', teamId)
.single();
if (error) throw error;
return data;
},
// 更新团队信息
async updateTeam(teamId, updates) {
const { data, error } = await supabase
.from('teams')
.update(updates)
.eq('id', teamId)
.select()
.single();
if (error) throw error;
return data;
},
// 添加团队成员
async addTeamMember(teamId, userId, role = 'MEMBER') {
const { data, error } = await supabase
.from('team_membership')
.insert([
{
team_id: teamId,
user_id: userId,
role,
is_creator: false
}
])
.select()
.single();
if (error) throw error;
return data;
},
// 更新成员角色
async updateMemberRole(teamId, userId, role) {
const { data, error } = await supabase
.from('team_membership')
.update({ role })
.match({ team_id: teamId, user_id: userId })
.select()
.single();
if (error) throw error;
},
async deleteTeam(teamId) {
const { error: teamError } = await supabase
.from('teams')
.delete()
.eq('id', teamId)
.select()
if (teamError) throw teamError;
},
};

View File

@@ -1,48 +0,0 @@
import { supabase } from '@/config/supabase';
export const teamMembershipService = {
async getMemberships(teamId) {
const { data, error } = await supabase
.from('team_memberships')
.select(`
*,
user:users(id, email, name)
`)
.eq('teamId', teamId);
if (error) throw error;
return data;
},
async createMembership(membershipData) {
const { data, error } = await supabase
.from('team_memberships')
.insert([membershipData])
.select()
.single();
if (error) throw error;
return data;
},
async updateMembership(id, membershipData) {
const { data, error } = await supabase
.from('team_memberships')
.update(membershipData)
.eq('id', id)
.select()
.single();
if (error) throw error;
return data;
},
async deleteMembership(id) {
const { error } = await supabase
.from('team_memberships')
.delete()
.eq('id', id);
if (error) throw error;
}
};

View File

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

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "es2015",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@pages/*": ["src/pages/*"],
"@utils/*": ["src/utils/*"],
"@config/*": ["src/config/*"],
"@contexts/*": ["src/contexts/*"],
"@assets/*": ["src/assets/*"]
}
}
}

4306
yarn.lock

File diff suppressed because it is too large Load Diff