swagger

移除 merchant

swagger

改造首页内容为通用后台管理系统定位

- 修改首页标题从 'LINUX DO Credit' 改为 'Modern Platform'
- 更新副标题为 '为二次开发而生'
- 更新首页描述为通用平台的特点
- 更新首页特性标签为 '开箱即用、高度可扩展、工业级基建'
- 修改展示卡片为技术栈和二次开发相关
- 更新开发者示例代码为通用的注册和 API Key 获取示例
- 更新页脚品牌名为 'Modern Platform'
- 调整页脚导航链接为通用平台相关内容

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

去除遗留

裁剪
移除 /api/v1/user/pay-key 相关代码

- 删除后端 UpdatePayKey 处理器函数和 UpdatePayKeyRequest 结构体
- 删除 User 模型中的 PayKey 字段
- 删除 User.VerifyPayKey 方法
- 删除 EncryptPayKeyFailed 错误常量
- 删除 /api/v1/user/pay-key PUT 路由
- 删除 OAuth 返回中的 IsPayKey 字段
- 删除前端 UserService.updatePayKey 方法
- 删除前端所有支付密钥 UI 和逻辑
- 更新相关的导出和注释

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

去除遗留

api 修正

系统配置

前端裁剪

后端裁剪

init
This commit is contained in:
ryan
2026-06-07 15:32:14 +08:00
parent 8a782525de
commit 48d414e197
180 changed files with 1538 additions and 27463 deletions
-378
View File
@@ -1,378 +0,0 @@
"use client"
import * as React from "react"
import { ManagePage, ManageDetailPanel } from "@/components/common/general/manage-pannel"
import { Input } from "@/components/ui/input"
import { formatDateTime } from "@/lib/utils"
import type { UserPayConfig } from "@/lib/services"
import { useAdmin } from "@/contexts/admin-context"
/**
* 积分配置详情面板组件
* 显示积分配置的详细信息和编辑面板
*
* @example
* ```tsx
* <PayConfigDetailPanel
* config={config}
* editData={editData}
* onEditDataChange={onEditDataChange}
* onSave={onSave}
* saving={saving}
* />
* ```
* @param {UserPayConfig} config - 积分配置
* @param {Partial<UserPayConfig>} editData - 编辑数据
* @param {function} onEditDataChange - 编辑数据改变回调
* @param {function} onSave - 保存回调
* @param {boolean} saving - 是否正在保存
* @returns {React.ReactNode} 积分配置详情面板组件
*/
function PayConfigDetailPanel({
config,
editData,
onEditDataChange,
onSave,
saving
}: {
config: UserPayConfig | null
editData: Partial<UserPayConfig>
onEditDataChange: (field: keyof UserPayConfig, value: UserPayConfig[keyof UserPayConfig]) => void
onSave: () => void
saving: boolean
}) {
return (
<ManageDetailPanel
isEmpty={!config}
onSave={onSave}
saving={saving}
>
<div className="grid grid-cols-2 gap-0">
<div className="border border-dashed rounded-l-lg">
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">配置等级</label>
<p className="text-xs text-muted-foreground">Level {config?.level}</p>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">配置ID</label>
<p className="text-xs text-muted-foreground">{config?.id}</p>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">创建时间</label>
<p className="text-xs text-muted-foreground">{config ? formatDateTime(config.created_at) : ''}</p>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed">
<label className="text-xs font-medium text-muted-foreground">更新时间</label>
<p className="text-xs text-muted-foreground">{config ? formatDateTime(config.updated_at) : ''}</p>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0" aria-hidden="true">
<span className="text-xs text-muted-foreground">&nbsp;</span>
<span className="text-xs text-muted-foreground">&nbsp;</span>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0" aria-hidden="true">
<span className="text-xs text-muted-foreground">&nbsp;</span>
<span className="text-xs text-muted-foreground">&nbsp;</span>
</div>
</div>
<div className="border border-dashed rounded-r-lg border-l-0">
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">最低分数</label>
<div className="flex items-center gap-1">
<Input
type="number"
step="1"
min="0"
value={editData.min_score !== undefined ? editData.min_score.toString() : (config?.min_score?.toString() || '')}
placeholder={editData.min_score === undefined && !config?.min_score ? '必需' : ''}
onChange={(e) => {
const value = e.target.value
if (value === '') {
onEditDataChange('min_score', 0)
return
}
const numValue = parseInt(value)
if (isNaN(numValue)) {
return
}
if (numValue >= 0) {
onEditDataChange('min_score', numValue)
}
}}
className="text-xs text-right h-4 w-16 px-0 rounded-none border-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-[12px]"
/>
<p className="text-xs text-muted-foreground">LDC</p>
</div>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">最高分数</label>
<div className="flex items-center gap-1">
<Input
type="number"
step="1"
min="0"
value={editData.max_score !== undefined ? (editData.max_score?.toString() || '') : (config?.max_score?.toString() || '')}
placeholder={(editData.max_score === null || editData.max_score === undefined) && (config?.max_score === null || config?.max_score === undefined) ? '无限制' : ''}
onChange={(e) => {
const value = e.target.value
if (value === '') {
onEditDataChange('max_score', null)
return
}
const numValue = parseInt(value)
if (isNaN(numValue)) {
return
}
if (numValue >= 0) {
onEditDataChange('max_score', numValue)
}
}}
className="text-xs text-right h-4 w-16 px-0 rounded-none border-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-[12px]"
/>
<p className="text-xs text-muted-foreground">LDC</p>
</div>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">手续费率</label>
<div className="flex items-right gap-1">
<Input
type="number"
step="1"
min="0"
max="100"
value={
editData.fee_rate !== undefined
? (parseFloat(editData.fee_rate.toString()) * 100).toString()
: (config?.fee_rate ? (parseFloat(config.fee_rate.toString()) * 100).toString() : '')
}
placeholder={editData.fee_rate === undefined && !config?.fee_rate ? '必需' : ''}
onChange={(e) => {
const value = e.target.value
if (value === '') {
onEditDataChange('fee_rate', '0')
return
}
const numValue = parseInt(value)
if (isNaN(numValue)) {
return
}
if (numValue >= 0 && numValue <= 100) {
onEditDataChange('fee_rate', (numValue / 100).toString())
}
}}
className="text-xs text-right h-4 w-16 px-0 mr-3 rounded-none border-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-[12px]"
/>
<p className="text-xs text-muted-foreground">%</p>
</div>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">分数转化率</label>
<div className="flex items-right gap-1">
<Input
type="number"
step="1"
min="0"
max="100"
value={
editData.score_rate !== undefined
? (parseFloat(editData.score_rate.toString()) * 100).toString()
: (config?.score_rate ? (parseFloat(config.score_rate.toString()) * 100).toString() : '')
}
placeholder={editData.score_rate === undefined && !config?.score_rate ? '必需' : ''}
onChange={(e) => {
const value = e.target.value
if (value === '') {
onEditDataChange('score_rate', '0')
return
}
const numValue = parseInt(value)
if (isNaN(numValue)) {
return
}
if (numValue >= 0 && numValue <= 100) {
onEditDataChange('score_rate', (numValue / 100).toString())
}
}}
className="text-xs text-right h-4 w-16 px-0 mr-3 rounded-none border-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-[12px]"
/>
<p className="text-xs text-muted-foreground">%</p>
</div>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">分发费率</label>
<div className="flex items-right gap-1">
<Input
type="number"
step="1"
min="0"
max="100"
value={
editData.distribute_rate !== undefined
? (parseFloat(editData.distribute_rate.toString()) * 100).toString()
: (config?.distribute_rate ? (parseFloat(config.distribute_rate.toString()) * 100).toString() : '')
}
placeholder={editData.distribute_rate === undefined && !config?.distribute_rate ? '必需' : ''}
onChange={(e) => {
const value = e.target.value
if (value === '') {
onEditDataChange('distribute_rate', '0')
return
}
const numValue = parseInt(value)
if (isNaN(numValue)) {
return
}
if (numValue >= 0 && numValue <= 100) {
onEditDataChange('distribute_rate', (numValue / 100).toString())
}
}}
className="text-xs text-right h-4 w-16 px-0 mr-3 rounded-none border-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-[12px]"
/>
<p className="text-xs text-muted-foreground">%</p>
</div>
</div>
<div className="px-3 py-2 flex items-center justify-between">
<label className="text-xs font-medium text-muted-foreground">每日支付上限</label>
<div className="flex items-center gap-1">
<Input
type="number"
step="1"
min="0"
value={editData.daily_limit !== undefined ? (editData.daily_limit?.toString() || '') : (config?.daily_limit?.toString() || '')}
placeholder={(editData.daily_limit === null || editData.daily_limit === undefined) && (config?.daily_limit === null || config?.daily_limit === undefined) ? '无限制' : ''}
onChange={(e) => {
const value = e.target.value
if (value === '') {
onEditDataChange('daily_limit', null)
return
}
const numValue = parseInt(value)
if (isNaN(numValue)) {
return
}
if (numValue >= 0) {
onEditDataChange('daily_limit', numValue)
}
}}
className="text-xs text-right h-4 w-16 px-0 rounded-none border-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-[12px]"
/>
<p className="text-xs text-muted-foreground">LDC</p>
</div>
</div>
</div>
</div>
</ManageDetailPanel>
)
}
/**
* 用户积分配置管理组件
*
* @example
* ```tsx
* <UserPayConfigs />
* ```
* @returns {React.ReactNode} 用户积分配置管理组件
*/
export function UserPayConfigs() {
const {
userPayConfigs: configs,
userPayConfigsLoading: loading,
userPayConfigsError: error,
refetchUserPayConfigs,
updateUserPayConfig,
deleteUserPayConfig
} = useAdmin()
const getInitialEditData = (config: UserPayConfig) => ({
min_score: config.min_score,
max_score: config.max_score,
daily_limit: config.daily_limit,
fee_rate: config.fee_rate.toString(),
score_rate: config.score_rate.toString(),
distribute_rate: config.distribute_rate.toString(),
})
const handleSave = async (config: UserPayConfig, editData: Partial<UserPayConfig>) => {
if (!config) return
await updateUserPayConfig(config.id, {
min_score: editData.min_score ?? config.min_score,
max_score: editData.max_score,
daily_limit: editData.daily_limit,
fee_rate: editData.fee_rate?.toString() ?? config.fee_rate.toString(),
score_rate: editData.score_rate?.toString() ?? config.score_rate.toString(),
distribute_rate: editData.distribute_rate?.toString() ?? config.distribute_rate.toString(),
})
await refetchUserPayConfigs()
}
const handleDelete = async (config: UserPayConfig) => {
await deleteUserPayConfig(config.id)
}
return (
<ManagePage<UserPayConfig>
title="积分配置"
data={configs}
loading={loading}
error={error}
onReload={refetchUserPayConfigs}
getInitialEditData={getInitialEditData}
onSave={handleSave}
onDelete={handleDelete}
getId={(config) => config.id}
emptyDescription="未发现积分配置"
loadingDescription="配置加载中"
columns={[
{ header: "等级", cell: (item) => <span className="font-medium">Level {item.level}</span>, width: "min-w-[80px]", align: "left" },
{ header: "最低分", cell: (item) => item.min_score, width: "min-w-[200px]", align: "left" },
{ header: "最高分", cell: (item) => item.max_score || "无限制", width: "min-w-[200px]", align: "left" },
{ header: "每日限额", cell: (item) => item.daily_limit ? `LDC ${ item.daily_limit.toLocaleString() }` : "无限制", width: "min-w-[200px]", align: "left" },
{ header: "费率", cell: (item) => `${ (Number(item.fee_rate) * 100).toFixed(2) }%`, width: "min-w-[200px]", align: "left" },
{ header: "分数转化率", cell: (item) => `${ (Number(item.score_rate) * 100).toFixed(2) }%`, width: "min-w-[200px]", align: "left" },
{ header: "分发费率", cell: (item) => `${ (Number(item.distribute_rate) * 100).toFixed(2) }%`, width: "min-w-[200px]", align: "left" },
{ header: "更新时间", cell: (item) => <span className="text-muted-foreground">{formatDateTime(item.updated_at)}</span>, width: "min-w-[200px]", align: "left" },
]}
renderDetail={({ selected, hovered, editData, onEditDataChange, onSave, saving }) => (
<PayConfigDetailPanel
config={selected || hovered}
editData={editData}
onEditDataChange={onEditDataChange}
onSave={onSave}
saving={saving}
/>
)}
/>
)
}
+43 -3
View File
@@ -4,6 +4,7 @@ import * as React from "react"
import { Input } from "@/components/ui/input"
import { Switch } from "@/components/ui/switch"
import { ManagePage, ManageDetailPanel } from "@/components/common/general/manage-pannel"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { formatDateTime } from "@/lib/utils"
import type { SystemConfig } from "@/lib/services"
@@ -70,7 +71,22 @@ function SystemConfigDetailPanel({
/>
) : (
<Input
type="number"
type={
config?.key && (
config.key.endsWith('_limit') ||
config.key.endsWith('_minutes') ||
config.key.endsWith('_days') ||
config.key.endsWith('_seconds') ||
config.key.endsWith('_hours') ||
config.key.endsWith('_ttl') ||
config.key.endsWith('_max') ||
config.key.endsWith('_min') ||
config.key.includes('max_') ||
config.key.includes('min_') ||
config.key.includes('limit') ||
config.key.includes('count')
) ? "number" : "text"
}
step="1"
min="0"
value={editData.value !== undefined ? editData.value : (config?.value || '')}
@@ -108,6 +124,17 @@ function SystemConfigDetailPanel({
</div>
</div>
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
<label className="text-xs font-medium text-muted-foreground">配置类型</label>
<span className={`text-[11px] px-1.5 py-0.5 rounded font-medium ${
config?.type === 'system'
? 'bg-blue-500/10 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400'
: 'bg-indigo-500/10 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400'
}`}>
{config?.type === 'system' ? '系统配置' : '业务配置'}
</span>
</div>
<div className="px-3 py-2 flex items-center justify-between">
<label className="text-xs font-medium text-muted-foreground">创建时间</label>
<p className="text-xs text-muted-foreground">{config ? formatDateTime(config.created_at) : ''}</p>
@@ -139,6 +166,12 @@ export function SystemConfigs() {
deleteSystemConfig
} = useAdmin()
const [activeTab, setActiveTab] = React.useState<'system' | 'business'>('business')
React.useEffect(() => {
refetchSystemConfigs(activeTab)
}, [activeTab, refetchSystemConfigs])
const getInitialEditData = (config: SystemConfig) => ({
value: config.value,
description: config.description
@@ -151,7 +184,6 @@ export function SystemConfigs() {
value: editData.value ?? config.value,
description: editData.description ?? config.description
})
await refetchSystemConfigs()
}
const handleDelete = async (config: SystemConfig) => {
@@ -164,13 +196,21 @@ export function SystemConfigs() {
data={configs}
loading={loading}
error={error}
onReload={refetchSystemConfigs}
onReload={() => refetchSystemConfigs(activeTab)}
getInitialEditData={getInitialEditData}
onSave={handleSave}
onDelete={handleDelete}
getId={(config) => config.key}
emptyDescription="未发现系统配置"
loadingDescription="配置加载中"
headerExtra={
<Tabs value={activeTab} onValueChange={(val) => setActiveTab(val as 'system' | 'business')} className="w-[180px]">
<TabsList className="grid w-full grid-cols-2 h-8">
<TabsTrigger value="business" className="text-[11px] h-7">业务配置</TabsTrigger>
<TabsTrigger value="system" className="text-[11px] h-7">系统配置</TabsTrigger>
</TabsList>
</Tabs>
}
columns={[
{ header: "配置键", cell: (item) => <span className="font-mono font-medium">{item.key}</span>, width: "200px" },
{ header: "配置值", cell: (item) => <span className="truncate max-w-[120px] inline-block" title={item.value}>{item.value}</span>, width: "120px" },
+6 -117
View File
@@ -8,10 +8,10 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
import { Sheet, SheetTitle, SheetContent } from "@/components/ui/sheet"
import { Badge } from "@/components/ui/badge"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Layers, Search, UserX, UserCheck, Eye, Wallet, CreditCard, ShieldCheck, Filter, X, ChevronDown, ChevronLeft, ChevronRight, Users, Loader2 } from "lucide-react"
import { Layers, Search, UserX, UserCheck, Eye, ShieldCheck, Filter, X, ChevronDown, ChevronLeft, ChevronRight, Loader2 } from "lucide-react"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { AdminUser, AdminService, DispatchTaskRequest } from "@/lib/services"
import { AdminUser, AdminService } from "@/lib/services"
import { toast } from "sonner"
import { formatDateTime } from "@/lib/utils"
import { EmptyStateWithBorder } from "@/components/layout/empty"
@@ -43,7 +43,6 @@ export function UsersManager() {
const [selectedUser, setSelectedUser] = useState<AdminUser | null>(null)
const [detailOpen, setDetailOpen] = useState(false)
const [updatingUserId, setUpdatingUserId] = useState<string | null>(null)
useEffect(() => {
fetchUsers()
@@ -62,25 +61,7 @@ export function UsersManager() {
setDetailOpen(true)
}
const handleUpdateCredits = async (user: AdminUser) => {
try {
setUpdatingUserId(user.id)
const params: DispatchTaskRequest = {
task_type: 'user_gamification',
user_id: user.id
}
await AdminService.dispatchTask(params)
toast.success('积分更新任务已下发', {
description: `正在更新用户 ${ user.username } 的积分数据`
})
} catch (err) {
toast.error('任务下发失败', {
description: err instanceof Error ? err.message : '未知错误'
})
} finally {
setUpdatingUserId(null)
}
}
const totalPages = Math.ceil(total / pageSize)
const hasSearchFilter = Boolean(searchUserId || searchUsername)
@@ -315,12 +296,7 @@ export function UsersManager() {
<TableRow className="border-b border-dashed hover:bg-transparent">
<TableHead className="w-[90px] whitespace-nowrap py-2 h-8">ID</TableHead>
<TableHead className="w-[120px] whitespace-nowrap py-2 h-8">用户</TableHead>
<TableHead className="text-right whitespace-nowrap min-w-[80px] py-2 h-8 font-mono">余额</TableHead>
<TableHead className="text-right whitespace-nowrap min-w-[80px] py-2 h-8 font-mono">基准分</TableHead>
<TableHead className="text-right whitespace-nowrap min-w-[80px] py-2 h-8 font-mono">支付分</TableHead>
<TableHead className="text-right whitespace-nowrap min-w-[80px] py-2 h-8 font-mono">总收益</TableHead>
<TableHead className="text-right whitespace-nowrap min-w-[80px] py-2 h-8 font-mono">总消耗</TableHead>
<TableHead className="text-right whitespace-nowrap min-w-[80px] py-2 h-8 font-mono is-last-money">总划转</TableHead>
<TableHead className="whitespace-nowrap min-w-[140px] py-2 h-8 pl-4">上次登陆</TableHead>
<TableHead className="whitespace-nowrap min-w-[140px] py-2 h-8">注册时间</TableHead>
<TableHead className="whitespace-nowrap min-w-[140px] py-2 h-8">上次更新</TableHead>
@@ -358,24 +334,7 @@ export function UsersManager() {
</div>
</div>
</TableCell>
<TableCell className="text-right font-mono text-[11px] text-green-600 dark:text-green-500 font-medium py-1">
{Number(user.available_balance).toFixed(2)}
</TableCell>
<TableCell className="text-right font-mono text-[11px] py-1">
{Number(user.community_balance).toFixed(2)}
</TableCell>
<TableCell className="text-right font-mono text-[11px] py-1">
{Number(user.pay_score).toFixed(2)}
</TableCell>
<TableCell className="text-right font-mono text-[11px] text-green-600 py-1">
{Number(user.total_receive).toFixed(2)}
</TableCell>
<TableCell className="text-right font-mono text-[11px] text-red-500 py-1">
{Number(user.total_payment).toFixed(2)}
</TableCell>
<TableCell className="text-right font-mono text-[11px] text-purple-500 py-1 is-last-money">
{Number(user.total_community).toFixed(2)}
</TableCell>
<TableCell className="text-[10px] text-muted-foreground font-mono whitespace-nowrap py-1 pl-4">
{formatDateTime(user.last_login_at)}
</TableCell>
@@ -404,24 +363,7 @@ export function UsersManager() {
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-foreground"
onClick={() => handleUpdateCredits(user)}
disabled={updatingUserId === user.id}
>
<Loader2 className={cn("size-3", updatingUserId === user.id && "animate-spin")} />
</Button>
</TooltipTrigger>
<TooltipContent side="top" className="text-xs">
更新积分
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
@@ -487,60 +429,7 @@ export function UsersManager() {
</div>
</div>
<div className="p-6">
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-1">核心资产</h4>
<div className="grid grid-cols-3 gap-2">
<div className="p-3 rounded-xl border bg-card/50 hover:bg-card/80 transition-colors space-y-2">
<div className="flex flex-col gap-1 text-muted-foreground">
<Wallet className="w-4 h-4" />
<span className="text-[10px] font-medium">可用余额</span>
</div>
<div className="text-lg font-mono font-bold tracking-tight text-foreground truncate">
{Number(selectedUser.available_balance).toFixed(2)}
</div>
</div>
<div className="p-3 rounded-xl border bg-card/50 hover:bg-card/80 transition-colors space-y-2">
<div className="flex flex-col gap-1 text-muted-foreground">
<CreditCard className="w-4 h-4" />
<span className="text-[10px] font-medium">支付积分</span>
</div>
<div className="text-lg font-mono font-bold tracking-tight text-foreground truncate">
{Number(selectedUser.pay_score).toFixed(2)}
</div>
</div>
<div className="p-3 rounded-xl border bg-card/50 hover:bg-card/80 transition-colors space-y-2">
<div className="flex flex-col gap-1 text-muted-foreground">
<Users className="w-4 h-4" />
<span className="text-[10px] font-medium">社区余额</span>
</div>
<div className="text-lg font-mono font-bold tracking-tight text-foreground truncate">
{Number(selectedUser.community_balance).toFixed(2)}
</div>
</div>
</div>
</div>
<Separator className="mx-6 w-auto opacity-50" />
<div className="p-6 space-y-6">
<div className="space-y-4">
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider px-1">财务概况</h4>
<div className="rounded-lg border divide-y bg-background/50">
<div className="flex items-center justify-between p-3.5">
<span className="text-[10px]">累计收益</span>
<span className="font-mono text-[10px] font-medium text-purple-600 dark:text-purple-400">+{Number(selectedUser.total_community).toFixed(2)}</span>
</div>
<div className="flex items-center justify-between p-3.5">
<span className="text-[10px]">累计收入</span>
<span className="font-mono text-[10px] font-medium text-green-600">+{Number(selectedUser.total_receive).toFixed(2)}</span>
</div>
<div className="flex items-center justify-between p-3.5">
<span className="text-[10px]">累计支出</span>
<span className="font-mono text-[10px] font-medium text-red-500">{Number(selectedUser.total_payment) === 0 ? '0.00' : `-${ Number(selectedUser.total_payment).toFixed(2) }`}</span>
</div>
</div>
</div>
<div className="space-y-4">
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider px-1">系统记录</h4>
<div className="rounded-lg border divide-y bg-background/50">