- 更新 DashboardLayout 组件,统一使用演示模式布局 - 实现仪表盘页面的完整演示数据和功能 - 完成用户管理页面的演示模式,包含搜索、过滤、分页等功能 - 实现通话记录页面的演示数据和录音播放功能 - 完成翻译员管理页面的演示模式 - 实现订单管理页面的完整功能 - 完成发票管理页面的演示数据 - 更新文档管理页面 - 添加 utils.ts 工具函数库 - 完善 API 路由和数据库结构 - 修复各种 TypeScript 类型错误 - 统一界面风格和用户体验
360 lines
9.2 KiB
TypeScript
360 lines
9.2 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 = supabaseUrl === 'https://demo.supabase.co';
|
|
|
|
// 单一的 Supabase 客户端实例
|
|
export const supabase = isDemoMode
|
|
? createClient(supabaseUrl, supabaseAnonKey, {
|
|
realtime: {
|
|
params: {
|
|
eventsPerSecond: 0,
|
|
},
|
|
},
|
|
auth: {
|
|
persistSession: false,
|
|
autoRefreshToken: false,
|
|
},
|
|
})
|
|
: createClient<Database>(supabaseUrl, supabaseAnonKey, {
|
|
auth: {
|
|
autoRefreshToken: true,
|
|
persistSession: true,
|
|
detectSessionInUrl: true
|
|
}
|
|
});
|
|
|
|
// 服务端使用的 Supabase 客户端(具有管理员权限)
|
|
export const supabaseAdmin = isDemoMode
|
|
? createClient(supabaseUrl, supabaseServiceKey, {
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
},
|
|
realtime: {
|
|
params: {
|
|
eventsPerSecond: 0,
|
|
},
|
|
},
|
|
})
|
|
: createClient(supabaseUrl, supabaseServiceKey, {
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
},
|
|
});
|
|
|
|
// 数据库表名常量
|
|
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;
|