Merge branch 'main' of github.com:xuqssq/uppmkt-admin
This commit is contained in:
@@ -3,13 +3,26 @@ import { Navigate, useLocation } from "react-router-dom";
|
|||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
|
|
||||||
const PUBLIC_PATHS = ['login', '404','home'];
|
const PUBLIC_PATHS = ['login', '404','home'];
|
||||||
|
|
||||||
export const ProtectedRoute = ({ children }) => {
|
export const ProtectedRoute = ({ children }) => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const currentPath = location.pathname.replace(/^\//, '');
|
const currentPath = location.pathname.replace(/^\//, '');
|
||||||
|
|
||||||
|
// 如果是公共路径,直接显示
|
||||||
if (PUBLIC_PATHS.includes(currentPath) || currentPath === '*') {
|
if (PUBLIC_PATHS.includes(currentPath) || currentPath === '*') {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果用户未登录,重定向到登录页面,并携带当前路径
|
||||||
|
if (!user?.id) {
|
||||||
|
return <Navigate
|
||||||
|
to={`/login?redirectTo=${encodeURIComponent(location.pathname + location.search)}`}
|
||||||
|
replace
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果用户已登录,检查权限
|
||||||
if (user?.id) {
|
if (user?.id) {
|
||||||
const hasPermission = user.menukeys?.some(key => {
|
const hasPermission = user.menukeys?.some(key => {
|
||||||
return currentPath === key || currentPath.startsWith(`${key}/`);
|
return currentPath === key || currentPath.startsWith(`${key}/`);
|
||||||
|
|||||||
@@ -8,14 +8,13 @@ import { v4 as uuidv4 } from "uuid";
|
|||||||
const AuthContext = createContext({});
|
const AuthContext = createContext({});
|
||||||
|
|
||||||
export const AuthProvider = ({ children }) => {
|
export const AuthProvider = ({ children }) => {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [user, setUser] = useState({});
|
const [user, setUser] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//处理google登录
|
|
||||||
const hash = window.location.hash.substring(1);
|
const hash = window.location.hash.substring(1);
|
||||||
const hashParams = new URLSearchParams(hash);
|
const hashParams = new URLSearchParams(hash);
|
||||||
const accessToken = hashParams.get("access_token");
|
const accessToken = hashParams.get("access_token");
|
||||||
@@ -62,12 +61,7 @@ export const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const redirectTo = searchParams.get("redirectTo");
|
|
||||||
// if (redirectTo) {
|
|
||||||
// navigate(redirectTo);
|
|
||||||
// }
|
|
||||||
// }, [location.pathname]);
|
|
||||||
|
|
||||||
//检查时候在管理模块团队中,没有就自动加入
|
//检查时候在管理模块团队中,没有就自动加入
|
||||||
const checkInTeam = async (user) => {
|
const checkInTeam = async (user) => {
|
||||||
@@ -121,7 +115,21 @@ export const AuthProvider = ({ children }) => {
|
|||||||
message.error(error.message || "登录失败,请稍后重试");
|
message.error(error.message || "登录失败,请稍后重试");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setUser(data.user);
|
|
||||||
|
const role = await checkInTeam(data.user);
|
||||||
|
const menukey = await fetchMenuList(role);
|
||||||
|
setUser({ ...data.user, adminRole: role, menukeys: menukey });
|
||||||
|
|
||||||
|
// 获取重定向路径
|
||||||
|
const redirectTo = searchParams.get("redirectTo");
|
||||||
|
if (redirectTo) {
|
||||||
|
// 如果有重定向路径,则导航到该路径
|
||||||
|
navigate(decodeURIComponent(redirectTo), { replace: true });
|
||||||
|
} else {
|
||||||
|
// 如果没有重定向路径,则导航到首页
|
||||||
|
navigate("/home", { replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error.message || "登录失败,请稍后重试");
|
message.error(error.message || "登录失败,请稍后重试");
|
||||||
@@ -134,13 +142,12 @@ export const AuthProvider = ({ children }) => {
|
|||||||
const signInWithGoogle = async () => {
|
const signInWithGoogle = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const redirectTo = searchParams.get("redirectTo");
|
const redirectTo = searchParams.get("redirectTo") || "/home";
|
||||||
|
|
||||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||||
provider: "google",
|
provider: "google",
|
||||||
options: {
|
options: {
|
||||||
redirectTo: `${window.location.origin}/login?redirectTo=${
|
redirectTo: `${window.location.origin}/login?redirectTo=${encodeURIComponent(redirectTo)}`,
|
||||||
redirectTo ?? ""
|
|
||||||
}`,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,13 +189,13 @@ export const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const fetchMenuList = async (role) => {
|
const fetchMenuList = async (role) => {
|
||||||
console.log(role,'role');
|
if(!role) return;
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('resources')
|
.from('resources')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('type', 'menuKey')
|
.eq('type', 'menuKey')
|
||||||
.eq('attributes->>roleName', role) // 添加这行来筛选 OWNER 角色
|
.eq('attributes->>roleName', role)
|
||||||
.single();
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
if(data?.attributes?.menuKeys){
|
if(data?.attributes?.menuKeys){
|
||||||
@@ -214,7 +221,8 @@ export const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
setUser({});
|
setUser({});
|
||||||
message.success("已成功登出");
|
message.success("已成功登出");
|
||||||
navigate(`/login?redirectTo=${location.pathname}`, { replace: true });
|
// 保存当前完整路径作为重定向 URL
|
||||||
|
navigate(`/login?redirectTo=${encodeURIComponent(location.pathname + location.search)}`, { replace: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error.message || "登出失败,请稍后重试");
|
message.error(error.message || "登出失败,请稍后重试");
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ class SupabaseService {
|
|||||||
let query = supabase
|
let query = supabase
|
||||||
.from(table)
|
.from(table)
|
||||||
.select(options.select || "*", { count: "exact" });
|
.select(options.select || "*", { count: "exact" });
|
||||||
|
|
||||||
// 处理精确匹配条件
|
|
||||||
if (options.match) {
|
if (options.match) {
|
||||||
query = query.match(options.match);
|
query = query.match(options.match);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -114,17 +114,6 @@ const QuotationPreview = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 导出按钮组件
|
|
||||||
const ExportPDFButton = () => (
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<DownloadOutlined />}
|
|
||||||
onClick={exportPDF}
|
|
||||||
>
|
|
||||||
导出PDF
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-full">
|
<div className="flex justify-center items-center h-full">
|
||||||
@@ -139,52 +128,69 @@ const QuotationPreview = () => {
|
|||||||
const currencySymbol = EXCHANGE_RATE[attributes.currency]?.symbol || defaultSymbol;
|
const currencySymbol = EXCHANGE_RATE[attributes.currency]?.symbol || defaultSymbol;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 p-6 transition-colors duration-200">
|
||||||
<Card
|
<Card
|
||||||
title={
|
className="max-w-4xl mx-auto shadow-lg rounded-2xl bg-white dark:bg-gray-800 transition-colors duration-200"
|
||||||
|
bodyStyle={{ padding: 0 }}
|
||||||
|
>
|
||||||
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Space>
|
<Space>
|
||||||
<FileTextOutlined className="text-blue-500" />
|
<FileTextOutlined className="text-blue-500 dark:text-blue-400" />
|
||||||
<span>报价单预览</span>
|
<span className="text-gray-800 dark:text-gray-200 font-medium">报价单预览</span>
|
||||||
</Space>
|
</Space>
|
||||||
<ExportPDFButton />
|
<Button
|
||||||
</div>
|
type="primary"
|
||||||
}
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={exportPDF}
|
||||||
|
className="bg-blue-500 hover:bg-blue-600 border-none rounded-full shadow-md hover:shadow-lg transition-all duration-200"
|
||||||
>
|
>
|
||||||
<div id="quotation-content" className="p-6">
|
导出PDF
|
||||||
<div className="text-center mb-8">
|
</Button>
|
||||||
<Title level={2}>{attributes.quataName}</Title>
|
</div>
|
||||||
<Text type="secondary">创建日期:{new Date(quotation.created_at).toLocaleDateString()}</Text>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-4 rounded-lg mb-6">
|
<div id="quotation-content" className="p-8 bg-white" style={{ width: '210mm', margin: '0 auto' }}>
|
||||||
<Title level={4}>基本信息</Title>
|
<div className="text-center mb-8">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<Title level={2} className="dark:text-gray-200">{attributes.quataName}</Title>
|
||||||
|
<Text type="secondary" className="dark:text-gray-400">
|
||||||
|
创建日期:{new Date(quotation.created_at).toLocaleDateString()}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-2xl mb-6">
|
||||||
|
<Title level={4} className="dark:text-gray-200 mb-4">基本信息</Title>
|
||||||
|
<div className="grid grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Text type="secondary">客户:</Text>
|
<Text type="secondary" className="dark:text-gray-400">客户:</Text>
|
||||||
<Space>
|
<Space className="mt-2">
|
||||||
{attributes.customers?.map(customer => (
|
{attributes.customers?.map(customer => (
|
||||||
<Tag key={customer.id} color="blue">{customer.name}</Tag>
|
<Tag
|
||||||
|
key={customer.id}
|
||||||
|
className="px-3 py-1 rounded-full bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-200 border-none"
|
||||||
|
>
|
||||||
|
{customer.name}
|
||||||
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Text type="secondary">货币类型:</Text>
|
<Text type="secondary" className="dark:text-gray-400">货币类型:</Text>
|
||||||
<Text>{attributes.currency}</Text>
|
<Text className="dark:text-gray-200 ml-2">{attributes.currency}</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{attributes.sections?.map((section, sIndex) => (
|
{attributes.sections?.map((section, sIndex) => (
|
||||||
<div key={sIndex} className="mb-6">
|
<div key={sIndex} className="mb-8">
|
||||||
<div className="flex items-center gap-2 h-full">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<div className="h-4 w-1 bg-blue-500 rounded-full" />
|
<div className="h-4 w-1 bg-blue-500 rounded-full" />
|
||||||
<h2>{section.sectionName}</h2>
|
<h2 className="text-lg font-medium dark:text-gray-200">{section.sectionName}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto rounded-2xl border border-gray-200 dark:border-gray-700">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">项目明细</th>
|
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">项目明细</th>
|
||||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">描述/备注</th>
|
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">描述/备注</th>
|
||||||
@@ -194,9 +200,9 @@ const QuotationPreview = () => {
|
|||||||
<th className="px-4 py-3 text-right text-sm font-medium text-gray-500">小计</th>
|
<th className="px-4 py-3 text-right text-sm font-medium text-gray-500">小计</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
{section.items.map((item, iIndex) => (
|
{section.items.map((item, iIndex) => (
|
||||||
<tr key={iIndex}>
|
<tr key={iIndex} className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150">
|
||||||
<td className="px-4 py-3 text-sm text-gray-900">{item.name}</td>
|
<td className="px-4 py-3 text-sm text-gray-900">{item.name}</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-500">{item.description}</td>
|
<td className="px-4 py-3 text-sm text-gray-500">{item.description}</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-500">{item.unit}</td>
|
<td className="px-4 py-3 text-sm text-gray-500">{item.unit}</td>
|
||||||
@@ -216,9 +222,9 @@ const QuotationPreview = () => {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{/* 金额汇总 */}
|
{/* 金额汇总 */}
|
||||||
<div className="mt-8 border-t pt-4">
|
<div className="mt-8 border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||||
<div className="flex justify-end space-y-2">
|
<div className="flex justify-end">
|
||||||
<div className="w-64 space-y-2">
|
<div className="w-64 space-y-3">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Text>税前总计:</Text>
|
<Text>税前总计:</Text>
|
||||||
<Text>{currencySymbol}{attributes.beforeTaxAmount?.toLocaleString()}</Text>
|
<Text>{currencySymbol}{attributes.beforeTaxAmount?.toLocaleString()}</Text>
|
||||||
@@ -238,9 +244,9 @@ const QuotationPreview = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Divider className="my-2" />
|
<Divider className="my-2" />
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between items-center">
|
||||||
<Text strong>最终金额:</Text>
|
<Text strong className="dark:text-gray-200">最终金额:</Text>
|
||||||
<Text strong className="text-blue-500 text-xl">
|
<Text strong className="text-xl text-blue-500 dark:text-blue-400">
|
||||||
{currencySymbol}{(attributes.discount || attributes.afterTaxAmount)?.toLocaleString()}
|
{currencySymbol}{(attributes.discount || attributes.afterTaxAmount)?.toLocaleString()}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
@@ -251,9 +257,9 @@ const QuotationPreview = () => {
|
|||||||
{/* 补充说明 */}
|
{/* 补充说明 */}
|
||||||
{attributes.description && (
|
{attributes.description && (
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<Title level={4}>补充说明</Title>
|
<Title level={4} className="dark:text-gray-200">补充说明</Title>
|
||||||
<div className="bg-gray-50 p-4 rounded-lg">
|
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-2xl">
|
||||||
<Text>{attributes.description}</Text>
|
<Text className="dark:text-gray-300">{attributes.description}</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -68,11 +68,18 @@ const ServicePage = () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 添加分页相关状态
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
// 获取服务模板列表
|
// 获取服务模板列表
|
||||||
const fetchServices = async () => {
|
const fetchServices = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { data: services } = await supabaseService.select("resources", {
|
const { data: services, total } = await supabaseService.select("resources", {
|
||||||
filter: {
|
filter: {
|
||||||
type: { eq: "serviceTemplate" },
|
type: { eq: "serviceTemplate" },
|
||||||
...(selectedType
|
...(selectedType
|
||||||
@@ -83,9 +90,17 @@ const ServicePage = () => {
|
|||||||
column: "created_at",
|
column: "created_at",
|
||||||
ascending: false,
|
ascending: false,
|
||||||
},
|
},
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(services || []);
|
setData(services || []);
|
||||||
|
setPagination((prev) => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取服务模板失败:", error);
|
console.error("获取服务模板失败:", error);
|
||||||
message.error("获取服务模板失败");
|
message.error("获取服务模板失败");
|
||||||
@@ -93,12 +108,15 @@ const ServicePage = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPagination((prev) => ({ ...prev, current: 1 }));
|
||||||
|
fetchServices(1, pagination.pageSize);
|
||||||
|
}, [selectedType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUnits();
|
fetchUnits();
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
|
||||||
fetchServices();
|
|
||||||
}, [selectedType]);
|
|
||||||
|
|
||||||
const fetchUnits = async () => {
|
const fetchUnits = async () => {
|
||||||
setloadingUnits(true);
|
setloadingUnits(true);
|
||||||
@@ -864,8 +882,19 @@ const ServicePage = () => {
|
|||||||
rowExpandable: (record) => record.attributes.sections?.length > 0,
|
rowExpandable: (record) => record.attributes.sections?.length > 0,
|
||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
...pagination,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
// 页码或页大小改变时触发
|
||||||
|
if (pageSize !== pagination.pageSize) {
|
||||||
|
// 如果是页大小改变,重置到第一页
|
||||||
|
fetchServices(1, pageSize);
|
||||||
|
} else {
|
||||||
|
fetchServices(page, pageSize);
|
||||||
|
}
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Table, Button, Form, Input, Space, message, Popconfirm, Drawer, Select, Segmented, Badge } from 'antd';
|
import { Table, Button, Form, Input, Space, message, Popconfirm, Select, Segmented } from 'antd';
|
||||||
import { PlusOutlined, FileTextOutlined, ProjectOutlined, CheckSquareOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { supabaseService } from '@/hooks/supabaseService';
|
import { supabaseService } from '@/hooks/supabaseService';
|
||||||
const Classify = ({activeType,typeList}) => {
|
const Classify = ({activeType,typeList}) => {
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
@@ -8,8 +8,13 @@ const Classify = ({activeType,typeList}) => {
|
|||||||
const [editingKey, setEditingKey] = useState('');
|
const [editingKey, setEditingKey] = useState('');
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [filterType, setFilterType] = useState('all'); // 'all', 'common', 'current'
|
const [filterType, setFilterType] = useState('all'); // 'all', 'common', 'current'
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const fetchCategories = async (type = activeType, filterTypeValue = filterType) => {
|
const fetchCategories = async (type = activeType, filterTypeValue = filterType, page = pagination.current, pageSize = pagination.pageSize) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
let filterCondition;
|
let filterCondition;
|
||||||
@@ -25,7 +30,7 @@ const Classify = ({activeType,typeList}) => {
|
|||||||
filterCondition = { in: `(${type},common)` };
|
filterCondition = { in: `(${type},common)` };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: categories } = await supabaseService.select('resources', {
|
const { data: categories, total } = await supabaseService.select('resources', {
|
||||||
filter: {
|
filter: {
|
||||||
'type': { eq: 'categories' },
|
'type': { eq: 'categories' },
|
||||||
'attributes->>template_type': filterCondition
|
'attributes->>template_type': filterCondition
|
||||||
@@ -33,10 +38,18 @@ const Classify = ({activeType,typeList}) => {
|
|||||||
order: {
|
order: {
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
ascending: false
|
ascending: false
|
||||||
}
|
},
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(categories || []);
|
setData(categories || []);
|
||||||
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取分类数据失败');
|
message.error('获取分类数据失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -46,8 +59,9 @@ const Classify = ({activeType,typeList}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchCategories(activeType, filterType);
|
setPagination(prev => ({ ...prev, current: 1 }));
|
||||||
}, [activeType]);
|
fetchCategories(activeType, filterType, 1, pagination.pageSize);
|
||||||
|
}, [activeType, filterType]);
|
||||||
|
|
||||||
|
|
||||||
// 新增分类
|
// 新增分类
|
||||||
@@ -273,9 +287,17 @@ const Classify = ({activeType,typeList}) => {
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
...pagination,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showSizeChanger: true,
|
||||||
className: "px-4"
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
if (pageSize !== pagination.pageSize) {
|
||||||
|
fetchCategories(activeType, filterType, 1, pageSize);
|
||||||
|
} else {
|
||||||
|
fetchCategories(activeType, filterType, page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ export default function SectionComponent({ activeType }) {
|
|||||||
return <QuataSections />;
|
return <QuataSections />;
|
||||||
case "task":
|
case "task":
|
||||||
return <TaskSections />;
|
return <TaskSections />;
|
||||||
|
case "project":
|
||||||
|
return <Project/>
|
||||||
default:
|
default:
|
||||||
return <Project></Project>;
|
return <div>暂无数据</div>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return <>{renderFn(activeType)}</>;
|
return <>{renderFn(activeType)}</>;
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { supabaseService } from '@/hooks/supabaseService';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { supabase } from "@/config/supabase";
|
import { supabase } from "@/config/supabase";
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
const TYPE = 'project';
|
const TYPE = 'project';
|
||||||
|
|
||||||
const ProjectSections = () => {
|
const ProjectSections = () => {
|
||||||
@@ -37,11 +36,15 @@ const ProjectSections = () => {
|
|||||||
const [formValues, setFormValues] = useState({});
|
const [formValues, setFormValues] = useState({});
|
||||||
const [uploadModalVisible, setUploadModalVisible] = useState(false);
|
const [uploadModalVisible, setUploadModalVisible] = useState(false);
|
||||||
const [currentAddItem, setCurrentAddItem] = useState(null);
|
const [currentAddItem, setCurrentAddItem] = useState(null);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
const fetchSections = async () => {
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const fetchSections = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const { data: sections } = await supabaseService.select('resources', {
|
const { data: sections, total } = await supabaseService.select('resources', {
|
||||||
filter: {
|
filter: {
|
||||||
'type': { eq: 'sections' },
|
'type': { eq: 'sections' },
|
||||||
'attributes->>template_type': { eq: TYPE }
|
'attributes->>template_type': { eq: TYPE }
|
||||||
@@ -49,9 +52,18 @@ const ProjectSections = () => {
|
|||||||
order: {
|
order: {
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
ascending: false
|
ascending: false
|
||||||
}
|
},
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(sections || []);
|
setData(sections || []);
|
||||||
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取模块数据失败');
|
message.error('获取模块数据失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -83,9 +95,9 @@ const ProjectSections = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSections();
|
fetchSections(1, pagination.pageSize);
|
||||||
fetchUnits();
|
fetchUnits();
|
||||||
}, []);
|
}, [TYPE]);
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const newData = {
|
const newData = {
|
||||||
@@ -151,7 +163,7 @@ const ProjectSections = () => {
|
|||||||
|
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
setEditingKey('');
|
setEditingKey('');
|
||||||
fetchSections();
|
fetchSections(pagination.current, pagination.pageSize);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('保存失败');
|
message.error('保存失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -162,7 +174,11 @@ const ProjectSections = () => {
|
|||||||
try {
|
try {
|
||||||
await supabaseService.delete('resources', { id: record.id });
|
await supabaseService.delete('resources', { id: record.id });
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
fetchSections();
|
if (data.length === 1 && pagination.current > 1) {
|
||||||
|
fetchSections(pagination.current - 1, pagination.pageSize);
|
||||||
|
} else {
|
||||||
|
fetchSections(pagination.current, pagination.pageSize);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败');
|
message.error('删除失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -528,9 +544,19 @@ const ProjectSections = () => {
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
...pagination,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showSizeChanger: true,
|
||||||
className: "px-4"
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
if (pageSize !== pagination.pageSize) {
|
||||||
|
setPagination(prev => ({ ...prev, pageSize }));
|
||||||
|
fetchSections(1, pageSize);
|
||||||
|
} else {
|
||||||
|
fetchSections(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -27,11 +27,15 @@ const SectionsManagement = () => {
|
|||||||
const [loadingUnits,setLoadingUnits]=useState(false)
|
const [loadingUnits,setLoadingUnits]=useState(false)
|
||||||
const [units,setUnit]=useState([])
|
const [units,setUnit]=useState([])
|
||||||
const [formValues, setFormValues] = useState({});
|
const [formValues, setFormValues] = useState({});
|
||||||
const fetchSections = async () => {
|
const [pagination, setPagination] = useState({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const fetchSections = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
|
const { data: sections, total } = await supabaseService.select('resources', {
|
||||||
const { data: sections } = await supabaseService.select('resources', {
|
|
||||||
filter: {
|
filter: {
|
||||||
'type': { eq: 'sections' },
|
'type': { eq: 'sections' },
|
||||||
'attributes->>template_type': { eq: TYPE }
|
'attributes->>template_type': { eq: TYPE }
|
||||||
@@ -39,10 +43,18 @@ const [formValues, setFormValues] = useState({});
|
|||||||
order: {
|
order: {
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
ascending: false
|
ascending: false
|
||||||
}
|
},
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(sections || []);
|
setData(sections || []);
|
||||||
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取模块数据失败');
|
message.error('获取模块数据失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -72,8 +84,7 @@ const [formValues, setFormValues] = useState({});
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSections();
|
fetchSections(1, pagination.pageSize);
|
||||||
|
|
||||||
}, [TYPE]);
|
}, [TYPE]);
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
@@ -115,7 +126,7 @@ const [formValues, setFormValues] = useState({});
|
|||||||
attributes: {
|
attributes: {
|
||||||
name: values.name,
|
name: values.name,
|
||||||
template_type: TYPE,
|
template_type: TYPE,
|
||||||
items: items.filter(item => item.name), // 只保存有名称的项目
|
items: items.filter(item => item.name),
|
||||||
},
|
},
|
||||||
schema_version: 1
|
schema_version: 1
|
||||||
});
|
});
|
||||||
@@ -135,7 +146,7 @@ const [formValues, setFormValues] = useState({});
|
|||||||
|
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
setEditingKey('');
|
setEditingKey('');
|
||||||
fetchSections();
|
fetchSections(pagination.current, pagination.pageSize);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('保存失败');
|
message.error('保存失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -146,7 +157,11 @@ const [formValues, setFormValues] = useState({});
|
|||||||
try {
|
try {
|
||||||
await supabaseService.delete('resources', { id: record.id });
|
await supabaseService.delete('resources', { id: record.id });
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
fetchSections();
|
if (data.length === 1 && pagination.current > 1) {
|
||||||
|
fetchSections(pagination.current - 1, pagination.pageSize);
|
||||||
|
} else {
|
||||||
|
fetchSections(pagination.current, pagination.pageSize);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败');
|
message.error('删除失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -450,9 +465,17 @@ const [formValues, setFormValues] = useState({});
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
...pagination,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showSizeChanger: true,
|
||||||
className: "px-4"
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
if (pageSize !== pagination.pageSize) {
|
||||||
|
fetchSections(1, pageSize);
|
||||||
|
} else {
|
||||||
|
fetchSections(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { supabaseService } from '@/hooks/supabaseService';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
const TYPE = 'task';
|
const TYPE = 'task';
|
||||||
|
|
||||||
const TaskSections = () => {
|
const TaskSections = () => {
|
||||||
@@ -29,11 +28,16 @@ const TaskSections = () => {
|
|||||||
const [loadingUnits, setLoadingUnits] = useState(false);
|
const [loadingUnits, setLoadingUnits] = useState(false);
|
||||||
const [units, setUnit] = useState([]);
|
const [units, setUnit] = useState([]);
|
||||||
const [formValues, setFormValues] = useState({});
|
const [formValues, setFormValues] = useState({});
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const fetchSections = async () => {
|
const fetchSections = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const { data: sections } = await supabaseService.select('resources', {
|
const { data: sections, total } = await supabaseService.select('resources', {
|
||||||
filter: {
|
filter: {
|
||||||
'type': { eq: 'sections' },
|
'type': { eq: 'sections' },
|
||||||
'attributes->>template_type': { eq: TYPE }
|
'attributes->>template_type': { eq: TYPE }
|
||||||
@@ -41,9 +45,18 @@ const TaskSections = () => {
|
|||||||
order: {
|
order: {
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
ascending: false
|
ascending: false
|
||||||
}
|
},
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(sections || []);
|
setData(sections || []);
|
||||||
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取模块数据失败');
|
message.error('获取模块数据失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -75,9 +88,9 @@ const TaskSections = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSections();
|
fetchSections(1, pagination.pageSize);
|
||||||
fetchUnits();
|
fetchUnits();
|
||||||
}, []);
|
}, [TYPE]);
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const newData = {
|
const newData = {
|
||||||
@@ -144,7 +157,7 @@ const TaskSections = () => {
|
|||||||
|
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
setEditingKey('');
|
setEditingKey('');
|
||||||
fetchSections();
|
fetchSections(pagination.current, pagination.pageSize);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('保存失败');
|
message.error('保存失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -155,7 +168,11 @@ const TaskSections = () => {
|
|||||||
try {
|
try {
|
||||||
await supabaseService.delete('resources', { id: record.id });
|
await supabaseService.delete('resources', { id: record.id });
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
fetchSections();
|
if (data.length === 1 && pagination.current > 1) {
|
||||||
|
fetchSections(pagination.current - 1, pagination.pageSize);
|
||||||
|
} else {
|
||||||
|
fetchSections(pagination.current, pagination.pageSize);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败');
|
message.error('删除失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -440,9 +457,19 @@ const TaskSections = () => {
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
...pagination,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showSizeChanger: true,
|
||||||
className: "px-4"
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
if (pageSize !== pagination.pageSize) {
|
||||||
|
setPagination(prev => ({ ...prev, pageSize }));
|
||||||
|
fetchSections(1, pageSize);
|
||||||
|
} else {
|
||||||
|
fetchSections(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ const UnitManagement = ({ activeType, typeList }) => {
|
|||||||
const [editingKey, setEditingKey] = useState('');
|
const [editingKey, setEditingKey] = useState('');
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [filterType, setFilterType] = useState('all');
|
const [filterType, setFilterType] = useState('all');
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const fetchUnits = async (type = activeType, filterTypeValue = filterType) => {
|
const fetchUnits = async (type = activeType, filterTypeValue = filterType, page = pagination.current, pageSize = pagination.pageSize) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
let filterCondition;
|
let filterCondition;
|
||||||
@@ -26,7 +31,7 @@ const UnitManagement = ({ activeType, typeList }) => {
|
|||||||
filterCondition = { in: `(${type},common)` };
|
filterCondition = { in: `(${type},common)` };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: units } = await supabaseService.select('resources', {
|
const { data: units, total } = await supabaseService.select('resources', {
|
||||||
filter: {
|
filter: {
|
||||||
'type': { eq: 'units' },
|
'type': { eq: 'units' },
|
||||||
'attributes->>template_type': filterCondition
|
'attributes->>template_type': filterCondition
|
||||||
@@ -34,10 +39,18 @@ const UnitManagement = ({ activeType, typeList }) => {
|
|||||||
order: {
|
order: {
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
ascending: false
|
ascending: false
|
||||||
}
|
},
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(units || []);
|
setData(units || []);
|
||||||
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取单位数据失败');
|
message.error('获取单位数据失败');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -47,8 +60,9 @@ const UnitManagement = ({ activeType, typeList }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUnits(activeType, filterType);
|
setPagination(prev => ({ ...prev, current: 1 }));
|
||||||
}, [activeType]);
|
fetchUnits(activeType, filterType, 1, pagination.pageSize);
|
||||||
|
}, [activeType, filterType]);
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const newData = {
|
const newData = {
|
||||||
@@ -269,9 +283,17 @@ const UnitManagement = ({ activeType, typeList }) => {
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
...pagination,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showSizeChanger: true,
|
||||||
className: "px-4"
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
if (pageSize !== pagination.pageSize) {
|
||||||
|
fetchUnits(activeType, filterType, 1, pageSize);
|
||||||
|
} else {
|
||||||
|
fetchUnits(activeType, filterType, page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
TreeSelect
|
TreeSelect
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { getAllRouteOptions, allRoutes } from '@/routes/routes';
|
import { getAllRouteOptions, allRoutes } from '@/routes/routes';
|
||||||
import { supabase } from '@/config/supabase';
|
import { supabaseService } from '@/hooks/supabaseService';
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
@@ -25,6 +25,11 @@ export default function MenuManagement() {
|
|||||||
const [menuList, setMenuList] = useState([]);
|
const [menuList, setMenuList] = useState([]);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [roles, setRoles] = useState([]);
|
const [roles, setRoles] = useState([]);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
// 获取所有路由选项
|
// 获取所有路由选项
|
||||||
const routeOptions = getAllRouteOptions();
|
const routeOptions = getAllRouteOptions();
|
||||||
@@ -81,19 +86,31 @@ export default function MenuManagement() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// 获取菜单数据
|
// 获取菜单数据
|
||||||
const fetchMenuList = async () => {
|
const fetchMenuList = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||||
try {
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { data, error } = await supabase
|
try {
|
||||||
.from('resources')
|
const { data: menus, total } = await supabaseService.select('resources', {
|
||||||
.select('*')
|
filter: {
|
||||||
.eq('type', TYPE)
|
'type': { eq: TYPE }
|
||||||
.order('created_at', { ascending: false });
|
},
|
||||||
|
order: {
|
||||||
|
column: 'created_at',
|
||||||
|
ascending: false
|
||||||
|
},
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
|
});
|
||||||
|
|
||||||
if (error) throw error;
|
setMenuList(menus || []);
|
||||||
setMenuList(data);
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取数据失败:' + error.message);
|
message.error('获取菜单数据失败');
|
||||||
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -102,12 +119,12 @@ export default function MenuManagement() {
|
|||||||
// 获取角色列表
|
// 获取角色列表
|
||||||
const fetchRoles = async () => {
|
const fetchRoles = async () => {
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase
|
const { data } = await supabaseService.select('roles', {
|
||||||
.from('roles')
|
order: {
|
||||||
.select('*')
|
column: 'name',
|
||||||
.order('name');
|
ascending: true
|
||||||
|
}
|
||||||
if (error) throw error;
|
});
|
||||||
setRoles(data || []);
|
setRoles(data || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取角色数据失败');
|
message.error('获取角色数据失败');
|
||||||
@@ -128,27 +145,20 @@ export default function MenuManagement() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let result;
|
|
||||||
if (form.getFieldValue('id')) {
|
if (form.getFieldValue('id')) {
|
||||||
result = await supabase
|
await supabaseService.update('resources',
|
||||||
.from('resources')
|
{ id: form.getFieldValue('id') },
|
||||||
.update(menuData)
|
menuData
|
||||||
.eq('id', form.getFieldValue('id'))
|
);
|
||||||
.select();
|
|
||||||
} else {
|
} else {
|
||||||
result = await supabase
|
await supabaseService.insert('resources', menuData);
|
||||||
.from('resources')
|
|
||||||
.insert([menuData])
|
|
||||||
.select();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.error) throw result.error;
|
|
||||||
|
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
setIsModalVisible(false);
|
setIsModalVisible(false);
|
||||||
fetchMenuList();
|
fetchMenuList(pagination.current, pagination.pageSize);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('保存失败:' + error.message);
|
message.error('保存失败:' + (error.message || '未知错误'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -169,19 +179,17 @@ export default function MenuManagement() {
|
|||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const {data, error } = await supabase
|
await supabaseService.delete('resources', { id });
|
||||||
.from('resources')
|
|
||||||
.delete()
|
|
||||||
.eq('id', id);
|
|
||||||
if (error) throw error;
|
|
||||||
if(data.length>0){
|
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
fetchMenuList();
|
|
||||||
|
// 如果当前页只有一条数据且不是第一页,删除后自动跳转到上一页
|
||||||
|
if (menuList.length === 1 && pagination.current > 1) {
|
||||||
|
fetchMenuList(pagination.current - 1, pagination.pageSize);
|
||||||
} else {
|
} else {
|
||||||
message.error('删除失败');
|
fetchMenuList(pagination.current, pagination.pageSize);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败:' + error.message);
|
message.error('删除失败:' + (error.message || '未知错误'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -197,7 +205,7 @@ export default function MenuManagement() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchMenuList();
|
fetchMenuList(1, pagination.pageSize);
|
||||||
fetchRoles();
|
fetchRoles();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -222,6 +230,23 @@ export default function MenuManagement() {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={menuList}
|
dataSource={menuList}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
pagination={{
|
||||||
|
...pagination,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
if (pageSize !== pagination.pageSize) {
|
||||||
|
// 如果是改变每页条数,重置到第一页
|
||||||
|
setPagination(prev => ({ ...prev, pageSize }));
|
||||||
|
fetchMenuList(1, pageSize);
|
||||||
|
} else {
|
||||||
|
// 如果是改变页码,获取对应页数据
|
||||||
|
fetchMenuList(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export default function PermissionManagement() {
|
|||||||
resources:resource_id(*)
|
resources:resource_id(*)
|
||||||
`, { count: 'exact' });
|
`, { count: 'exact' });
|
||||||
|
|
||||||
// 添加排序
|
|
||||||
if (params.field && params.order) {
|
if (params.field && params.order) {
|
||||||
const ascending = params.order === 'ascend';
|
const ascending = params.order === 'ascend';
|
||||||
query = query.order(params.field, { ascending });
|
query = query.order(params.field, { ascending });
|
||||||
@@ -544,7 +543,16 @@ export default function PermissionManagement() {
|
|||||||
dataSource={permissions}
|
dataSource={permissions}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={pagination}
|
pagination={{
|
||||||
|
...pagination,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
setPagination(prev => ({ ...prev,page, pageSize }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
className='w-full'
|
className='w-full'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Table, Form, Button, Space, Popconfirm, Tag, message } from 'antd';
|
|||||||
import { EditOutlined, DeleteOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons';
|
import { EditOutlined, DeleteOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons';
|
||||||
import { EditableCell } from './EditableCell';
|
import { EditableCell } from './EditableCell';
|
||||||
import { ExpandedMemberships } from './ExpandedMemberships';
|
import { ExpandedMemberships } from './ExpandedMemberships';
|
||||||
export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,onDelete ,onUpdate}) => {
|
export const TeamTable = ({ tableLoading,pagination,dataSource,setPagination, onTableChange,onDelete ,onUpdate}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [editingKey, setEditingKey] = useState('');
|
const [editingKey, setEditingKey] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -182,7 +182,6 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
|
|||||||
<Form form={form} component={false}>
|
<Form form={form} component={false}>
|
||||||
<Table
|
<Table
|
||||||
scroll={{ x: true}}
|
scroll={{ x: true}}
|
||||||
pagination={pagination}
|
|
||||||
loading={loading||tableLoading}
|
loading={loading||tableLoading}
|
||||||
components={{
|
components={{
|
||||||
body: {
|
body: {
|
||||||
@@ -193,6 +192,16 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
|
|||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
columns={mergedColumns}
|
columns={mergedColumns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
pagination={{
|
||||||
|
...pagination,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
setPagination(prev => ({ ...prev,page, pageSize }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
expandable={{
|
expandable={{
|
||||||
expandedRowRender: (record) => (
|
expandedRowRender: (record) => (
|
||||||
<ExpandedMemberships
|
<ExpandedMemberships
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ const TeamManagement = () => {
|
|||||||
<Card title="团队管理" >
|
<Card title="团队管理" >
|
||||||
<TeamHeader onSearch={handleSearch} onAdd={handleAdd} />
|
<TeamHeader onSearch={handleSearch} onAdd={handleAdd} />
|
||||||
<TeamTable
|
<TeamTable
|
||||||
|
setPagination={setPagination}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={teams}
|
dataSource={teams}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
|
|||||||
Reference in New Issue
Block a user