页面登录优化,team优化
This commit is contained in:
@@ -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,92 +11,100 @@ 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">
|
||||
Uppeta
|
||||
</h1>
|
||||
<p className="text-gray-500">欢迎回来!请登录您的账户。</p>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
name="login"
|
||||
onFinish={handleLogin}
|
||||
layout="vertical"
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
name="email"
|
||||
rules={[
|
||||
{ required: true, message: "请输入邮箱!" },
|
||||
{ type: "email", message: "请输入有效的邮箱地址!" },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="邮箱" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
rules={[{ required: true, message: "请输入密码!" }]}
|
||||
>
|
||||
<Input.Password placeholder="密码" />
|
||||
</Form.Item>
|
||||
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Link
|
||||
to="/forgot-password"
|
||||
className="text-primary-500 hover:text-primary-600"
|
||||
>
|
||||
忘记密码?
|
||||
</Link>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" block>
|
||||
登录
|
||||
</Button>
|
||||
</Form.Item>
|
||||
|
||||
<Divider>或</Divider>
|
||||
|
||||
<Button
|
||||
icon={<GoogleOutlined />}
|
||||
block
|
||||
onClick={handleGoogleLogin}
|
||||
className="mb-6"
|
||||
loading={loading}
|
||||
>
|
||||
使用 Google 账号登录
|
||||
</Button>
|
||||
</Form>
|
||||
<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 dark:text-gray-400">
|
||||
欢迎回来!请登录您的账户
|
||||
</p>
|
||||
</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" />
|
||||
<Form
|
||||
form={form}
|
||||
name="login"
|
||||
onFinish={handleLogin}
|
||||
layout="vertical"
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
name="email"
|
||||
rules={[
|
||||
{ required: true, message: "请输入邮箱!" },
|
||||
{ type: "email", message: "请输入有效的邮箱地址!" },
|
||||
]}
|
||||
>
|
||||
<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
|
||||
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-end mb-6">
|
||||
<Link
|
||||
to="/forgot-password"
|
||||
className="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 text-sm"
|
||||
>
|
||||
忘记密码?
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Form.Item>
|
||||
<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 className="dark:border-gray-700">
|
||||
<span className="text-gray-400">或</span>
|
||||
</Divider>
|
||||
|
||||
<Button
|
||||
icon={<GoogleOutlined />}
|
||||
block
|
||||
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="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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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));
|
||||
message.success('成员已删除');
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -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={{
|
||||
|
||||
@@ -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%' }}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user