feat: 多租户数据权限 + 数据库连接修正

- adminPool 连服务器本地 5432 sbrain_admin (SCRAM 认证)
- tenantPool/pool 连 FRP 隧道 15432 bill_query (trust 认证)
- query() 自动注入 store_code 数据权限 (store/regional 角色)
- 区域经理 region 字段存储逗号分隔的 store_code 列表
- /overview 和 /overview/daily 对 scoped 用户从 bill_fact 聚合
- tenant_users.region 扩展为 VARCHAR(500)
- deploy.sh .env 增加 ADMIN_DB_* 配置
- run.md 更新数据库连接架构说明
- 新增海淀区区域经理用户 (21 家海淀区门店)
This commit is contained in:
freedakgmail
2026-08-02 20:10:42 +08:00
parent c3fbe4ab81
commit cfa12c9b7f
24 changed files with 2003 additions and 173 deletions
@@ -0,0 +1,60 @@
import { useQuery } from '@tanstack/react-query'
import api from '@/lib/api'
import { MetricCard } from '@/components/MetricCard'
import { LoadingSpinner } from '@/components/LoadingSpinner'
import { cn } from '@/lib/utils'
export function OverviewTab() {
const { data, isLoading } = useQuery({
queryKey: ['admin-tenants'],
queryFn: () => api.get('/admin/tenants'),
})
if (isLoading) return <LoadingSpinner text="加载平台数据..." />
const tenants = (data as any)?.data || []
const activeCount = tenants.filter((t: any) => t.status === 'active').length
const inactiveCount = tenants.filter((t: any) => t.status === 'inactive').length
return (
<div className="space-y-4">
<div className="grid grid-cols-4 gap-3">
<MetricCard title="租户总数" value={tenants.length} description="已注册的租户数量" />
<MetricCard title="运行中" value={activeCount} status="good" description="状态为 active 的租户" />
<MetricCard title="已停用" value={inactiveCount} status={inactiveCount > 0 ? 'warn' : undefined} description="状态为 inactive 的租户" />
<MetricCard title="FRP端口数" value={tenants.length} description="已分配的 FRP 隧道端口数量" />
</div>
<div className="rounded-lg border bg-card p-4">
<h2 className="mb-3 text-sm font-bold"></h2>
<div className="space-y-2">
{tenants.map((t: any) => (
<div key={t.tenant_id} className="flex items-center justify-between rounded-md border px-3 py-2">
<div className="flex items-center gap-3">
<span className={cn(
'h-2 w-2 rounded-full',
t.status === 'active' ? 'bg-green-500' : 'bg-gray-400'
)} />
<span className="font-medium text-sm">{t.tenant_name}</span>
<span className="font-mono text-xs text-muted-foreground">{t.tenant_id}</span>
</div>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span>DB: <span className="font-mono">{t.db_host}:{t.db_port}</span></span>
<span>FRP: <span className="font-mono">{t.frp_port}</span></span>
<span className={cn(
'rounded px-2 py-0.5 font-medium',
t.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-500'
)}>
{t.status === 'active' ? '运行中' : '已停用'}
</span>
</div>
</div>
))}
{tenants.length === 0 && (
<p className="py-4 text-center text-sm text-muted-foreground"></p>
)}
</div>
</div>
</div>
)
}