- 修复DashboardLayout中的退出登录函数,确保清除所有认证信息 - 恢复_app.tsx中的认证逻辑,确保仪表盘页面需要登录访问 - 完善退出登录流程:清除本地存储 -> 调用登出API -> 重定向到登录页面 - 添加错误边界组件提升用户体验 - 优化React水合错误处理 - 添加JWT令牌验证API - 完善各个仪表盘页面的功能和样式
354 lines
9.7 KiB
TypeScript
354 lines
9.7 KiB
TypeScript
import { createClient } from '@supabase/supabase-js';
|
|
import { Database } from '../types/database';
|
|
|
|
// 环境变量检查和默认值
|
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://demo.supabase.co';
|
|
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'demo-key';
|
|
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || 'demo-service-key';
|
|
|
|
// 检查是否在演示模式
|
|
const isDemoMode = true; // 强制使用演示模式,避免 Supabase 实例创建
|
|
|
|
console.log('Supabase 配置: 演示模式已启用,不会创建 Supabase 客户端实例');
|
|
|
|
// 空的客户端对象,用于演示模式
|
|
const mockAuth = {
|
|
getUser: async () => ({ data: { user: null }, error: null }),
|
|
signInWithPassword: async () => ({ data: null, error: new Error('Demo mode') }),
|
|
signUp: async () => ({ data: null, error: new Error('Demo mode') }),
|
|
signOut: async () => ({ error: null }),
|
|
resetPasswordForEmail: async () => ({ data: null, error: new Error('Demo mode') }),
|
|
updateUser: async () => ({ data: null, error: new Error('Demo mode') }),
|
|
getSession: async () => ({ data: { session: null }, error: null }),
|
|
onAuthStateChange: () => ({ data: { subscription: { unsubscribe: () => {} } } })
|
|
};
|
|
|
|
// 导出模拟的客户端
|
|
export const supabase = {
|
|
auth: mockAuth,
|
|
from: () => ({
|
|
select: () => Promise.resolve({ data: [], error: null }),
|
|
insert: () => Promise.resolve({ data: null, error: new Error('Demo mode') }),
|
|
update: () => Promise.resolve({ data: null, error: new Error('Demo mode') }),
|
|
delete: () => Promise.resolve({ error: null })
|
|
})
|
|
} as any;
|
|
|
|
export const supabaseAdmin = {
|
|
auth: mockAuth,
|
|
from: () => ({
|
|
select: () => Promise.resolve({ data: [], error: null }),
|
|
insert: () => Promise.resolve({ data: null, error: new Error('Demo mode') }),
|
|
update: () => Promise.resolve({ data: null, error: new Error('Demo mode') }),
|
|
delete: () => Promise.resolve({ error: null })
|
|
})
|
|
} as any;
|
|
|
|
// 数据库表名常量
|
|
export const TABLES = {
|
|
USERS: 'users',
|
|
CALLS: 'calls',
|
|
APPOINTMENTS: 'appointments',
|
|
INTERPRETERS: 'interpreters',
|
|
DOCUMENTS: 'document_translations',
|
|
ORDERS: 'orders',
|
|
INVOICES: 'invoices',
|
|
ENTERPRISE_EMPLOYEES: 'enterprise_employees',
|
|
PRICING_RULES: 'pricing_rules',
|
|
NOTIFICATIONS: 'notifications',
|
|
SYSTEM_SETTINGS: 'system_settings',
|
|
ACCOUNT_BALANCES: 'account_balances',
|
|
} as const;
|
|
|
|
// 实时订阅配置
|
|
export const REALTIME_CHANNELS = {
|
|
CALLS: 'calls:*',
|
|
NOTIFICATIONS: 'notifications:*',
|
|
APPOINTMENTS: 'appointments:*',
|
|
} as const;
|
|
|
|
// 用户认证相关函数
|
|
export const auth = {
|
|
// 获取当前用户
|
|
getCurrentUser: async () => {
|
|
const { data: { user }, error } = await supabase.auth.getUser();
|
|
if (error) throw error;
|
|
return user;
|
|
},
|
|
|
|
// 登录
|
|
signIn: async (email: string, password: string) => {
|
|
const { data, error } = await supabase.auth.signInWithPassword({
|
|
email,
|
|
password,
|
|
});
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// 注册
|
|
signUp: async (email: string, password: string, userData?: any) => {
|
|
const { data, error } = await supabase.auth.signUp({
|
|
email,
|
|
password,
|
|
options: {
|
|
data: userData,
|
|
},
|
|
});
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// 登出
|
|
signOut: async () => {
|
|
const { error } = await supabase.auth.signOut();
|
|
if (error) throw error;
|
|
},
|
|
|
|
// 重置密码
|
|
resetPassword: async (email: string) => {
|
|
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
|
|
redirectTo: `${window.location.origin}/auth/reset-password`,
|
|
});
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// 更新密码
|
|
updatePassword: async (password: string) => {
|
|
const { data, error } = await supabase.auth.updateUser({
|
|
password,
|
|
});
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// 获取当前会话
|
|
getSession: async () => {
|
|
const { data: { session }, error } = await supabase.auth.getSession();
|
|
if (error) throw error;
|
|
return session;
|
|
},
|
|
|
|
// 更新用户信息
|
|
updateUser: async (updates: any) => {
|
|
const { data, error } = await supabase.auth.updateUser(updates);
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
};
|
|
|
|
// 数据库操作辅助函数
|
|
export const db = {
|
|
// 通用查询函数
|
|
select: async <T>(table: string, query?: any) => {
|
|
let queryBuilder = supabase.from(table).select(query || '*');
|
|
const { data, error } = await queryBuilder;
|
|
if (error) throw error;
|
|
return data as T[];
|
|
},
|
|
|
|
// 通用插入函数
|
|
insert: async <T>(table: string, data: any) => {
|
|
const { data: result, error } = await supabase
|
|
.from(table)
|
|
.insert(data)
|
|
.select()
|
|
.single();
|
|
if (error) throw error;
|
|
return result as T;
|
|
},
|
|
|
|
// 通用更新函数
|
|
update: async <T>(table: string, id: string, data: any) => {
|
|
const { data: result, error } = await supabase
|
|
.from(table)
|
|
.update(data)
|
|
.eq('id', id)
|
|
.select()
|
|
.single();
|
|
if (error) throw error;
|
|
return result as T;
|
|
},
|
|
|
|
// 通用删除函数
|
|
delete: async (table: string, id: string) => {
|
|
const { error } = await supabase
|
|
.from(table)
|
|
.delete()
|
|
.eq('id', id);
|
|
if (error) throw error;
|
|
},
|
|
|
|
// 根据条件查询单条记录
|
|
findOne: async <T>(table: string, conditions: Record<string, any>, select?: string) => {
|
|
let query = supabase.from(table).select(select || '*');
|
|
|
|
Object.entries(conditions).forEach(([key, value]) => {
|
|
query = query.eq(key, value);
|
|
});
|
|
|
|
const { data, error } = await query.single();
|
|
if (error) throw error;
|
|
return data as T;
|
|
},
|
|
|
|
// 根据条件查询多条记录
|
|
findMany: async <T>(table: string, conditions?: Record<string, any>, select?: string) => {
|
|
let query = supabase.from(table).select(select || '*');
|
|
|
|
if (conditions) {
|
|
Object.entries(conditions).forEach(([key, value]) => {
|
|
query = query.eq(key, value);
|
|
});
|
|
}
|
|
|
|
const { data, error } = await query;
|
|
if (error) throw error;
|
|
return data as T[];
|
|
},
|
|
|
|
// 计数查询
|
|
count: async (table: string, conditions?: Record<string, any>) => {
|
|
let query = supabase.from(table).select('*', { count: 'exact', head: true });
|
|
|
|
if (conditions) {
|
|
Object.entries(conditions).forEach(([key, value]) => {
|
|
query = query.eq(key, value);
|
|
});
|
|
}
|
|
|
|
const { count, error } = await query;
|
|
if (error) throw error;
|
|
return count || 0;
|
|
},
|
|
};
|
|
|
|
// 实时订阅管理
|
|
export const realtime = {
|
|
subscribe: (table: string, callback: (payload: any) => void) => {
|
|
const channel = supabase
|
|
.channel(`${table}_changes`)
|
|
.on('postgres_changes',
|
|
{
|
|
event: '*',
|
|
schema: 'public',
|
|
table: table
|
|
},
|
|
callback
|
|
)
|
|
.subscribe();
|
|
|
|
return channel;
|
|
},
|
|
|
|
unsubscribe: (channel: any) => {
|
|
if (channel) {
|
|
supabase.removeChannel(channel);
|
|
}
|
|
},
|
|
};
|
|
|
|
// 文件上传相关函数
|
|
export const storage = {
|
|
// 上传文件
|
|
upload: async (bucket: string, path: string, file: File) => {
|
|
const { data, error } = await supabase.storage
|
|
.from(bucket)
|
|
.upload(path, file);
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// 下载文件
|
|
download: async (bucket: string, path: string) => {
|
|
const { data, error } = await supabase.storage
|
|
.from(bucket)
|
|
.download(path);
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// 获取公共URL
|
|
getPublicUrl: (bucket: string, path: string) => {
|
|
const { data } = supabase.storage
|
|
.from(bucket)
|
|
.getPublicUrl(path);
|
|
return data.publicUrl;
|
|
},
|
|
|
|
// 删除文件
|
|
remove: async (bucket: string, paths: string[]) => {
|
|
const { data, error } = await supabase.storage
|
|
.from(bucket)
|
|
.remove(paths);
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
};
|
|
|
|
// 用户类型定义
|
|
export type UserRole = 'admin' | 'interpreter' | 'client' | 'enterprise';
|
|
|
|
// 用户权限检查
|
|
export const permissions = {
|
|
// 检查用户是否有特定权限
|
|
hasPermission: (userRole: UserRole, requiredRole: UserRole) => {
|
|
const roleHierarchy: Record<UserRole, number> = {
|
|
'client': 1,
|
|
'interpreter': 2,
|
|
'enterprise': 3,
|
|
'admin': 4,
|
|
};
|
|
|
|
return roleHierarchy[userRole] >= roleHierarchy[requiredRole];
|
|
},
|
|
|
|
// 检查用户是否为管理员
|
|
isAdmin: (userRole: UserRole) => userRole === 'admin',
|
|
|
|
// 检查用户是否为翻译员
|
|
isInterpreter: (userRole: UserRole) => userRole === 'interpreter',
|
|
|
|
// 检查用户是否为企业用户
|
|
isEnterprise: (userRole: UserRole) => userRole === 'enterprise',
|
|
};
|
|
|
|
// 错误处理
|
|
export const handleSupabaseError = (error: any) => {
|
|
console.error('Supabase Error:', error);
|
|
|
|
// 根据错误类型返回用户友好的消息
|
|
if (error.code === 'PGRST116') {
|
|
return '未找到记录';
|
|
} else if (error.code === '23505') {
|
|
return '数据已存在';
|
|
} else if (error.code === '23503') {
|
|
return '数据关联错误';
|
|
} else if (error.message?.includes('JWT')) {
|
|
return '登录已过期,请重新登录';
|
|
} else if (error.message?.includes('permission')) {
|
|
return '权限不足';
|
|
} else {
|
|
return error.message || '操作失败,请稍后重试';
|
|
}
|
|
};
|
|
|
|
// 检查 Supabase 是否正确配置
|
|
export const isSupabaseConfigured = () => {
|
|
return !isDemoMode && supabaseUrl !== 'https://demo.supabase.co' && supabaseAnonKey !== 'demo-key';
|
|
};
|
|
|
|
// 获取配置状态
|
|
export const getConfigStatus = () => {
|
|
return {
|
|
isDemoMode,
|
|
isConfigured: isSupabaseConfigured(),
|
|
url: supabaseUrl,
|
|
hasAnonKey: supabaseAnonKey !== 'demo-key',
|
|
hasServiceKey: supabaseServiceKey !== 'demo-service-key',
|
|
};
|
|
};
|
|
|
|
// 默认导出
|
|
export default supabase;
|