refactor: 画像key_facts分组(画像特征+个案事实折叠)
CI / 后端测试 (push) Has been cancelled
CI / 前端构建 (push) Has been cancelled

- 画像特征(地区/公司类型/团队/财务等稳定属性)直接展示
- 个案事实(工伤详情/合同条款/解除原因等案件细节)默认折叠
- 点击"个案事实(N)"可展开查看

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-31 15:12:32 +08:00
co-authored by Devin
parent cde32d2dc7
commit b4cd4bb204
+63 -14
View File
@@ -1,5 +1,5 @@
import { useEffect, useState, useCallback } from 'react' import { useEffect, useState, useCallback } from 'react'
import { UserCircle, History, ChevronRight, ArrowLeft } from 'lucide-react' import { UserCircle, History, ChevronRight, ArrowLeft, ChevronDown } from 'lucide-react'
import { PageLayout } from '@/components/common/PageLayout' import { PageLayout } from '@/components/common/PageLayout'
import { EmptyState } from '@/components/common/EmptyState' import { EmptyState } from '@/components/common/EmptyState'
import { LoadingState } from '@/components/common/LoadingState' import { LoadingState } from '@/components/common/LoadingState'
@@ -341,7 +341,23 @@ const SOURCE_LABELS: Record<string, string> = {
manual: '用户确认', manual: '用户确认',
} }
/** 常问话题中文映射 */ /** 画像特征 key(稳定的用户属性,直接展示) */
const PROFILE_FACT_KEYS = new Set([
'region', 'company_type', 'team_ability', 'industry', 'company_size',
'role', 'risk_tolerance', 'communication_style', 'decision_style',
'market_potential', 'financial_status', 'financial_situation',
'cash_flow', 'asset_status', 'liability', 'revenue', 'profit_status',
'business_model', 'funding_stage', 'expansion_plan', 'competitor',
'customer_concentration', 'supply_chain', 'employee_count',
'shareholder_structure', 'partnership',
])
/** 判断 key 属于画像特征还是个案事实 */
function isProfileFact(key: string): boolean {
return PROFILE_FACT_KEYS.has(key.toLowerCase())
}
const TOPIC_LABELS: Record<string, string> = { const TOPIC_LABELS: Record<string, string> = {
'contract review': '合同审查', 'contract review': '合同审查',
'work-life balance': '工作生活平衡', 'work-life balance': '工作生活平衡',
@@ -407,6 +423,7 @@ export function MyProfile() {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [currentHistory, setCurrentHistory] = useState<ProfileHistory | null>(null) const [currentHistory, setCurrentHistory] = useState<ProfileHistory | null>(null)
const [showCaseFacts, setShowCaseFacts] = useState(false)
const load = useCallback(async () => { const load = useCallback(async () => {
setLoading(true) setLoading(true)
@@ -545,19 +562,51 @@ export function MyProfile() {
</div> </div>
)} )}
{profile.key_facts && Object.keys(profile.key_facts).length > 0 && ( {profile.key_facts && Object.keys(profile.key_facts).length > 0 && (() => {
<div className="border-b pb-2"> const entries = Object.entries(profile.key_facts)
<p className="mb-1 text-xs text-muted-foreground">关键事实</p> const profileFacts = entries.filter(([k]) => isProfileFact(k))
<div className="grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3"> const caseFacts = entries.filter(([k]) => !isProfileFact(k))
{Object.entries(profile.key_facts).map(([k, v]) => ( return (
<div key={k} className="flex items-center justify-between text-xs"> <>
<span className="text-muted-foreground">{translateFactKey(k)}</span> {/* 画像特征:直接展示 */}
<span className="font-medium">{translateFactValue(v)}</span> {profileFacts.length > 0 && (
<div className="border-b pb-2">
<p className="mb-1 text-xs text-muted-foreground">画像特征</p>
<div className="grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3">
{profileFacts.map(([k, v]) => (
<div key={k} className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">{translateFactKey(k)}</span>
<span className="font-medium">{translateFactValue(v)}</span>
</div>
))}
</div>
</div> </div>
))} )}
</div> {/* 个案事实:折叠展示 */}
</div> {caseFacts.length > 0 && (
)} <div className="border-b pb-2">
<button
onClick={() => setShowCaseFacts(!showCaseFacts)}
className="mb-1 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
>
<ChevronDown className={cn('h-3 w-3 transition-transform', !showCaseFacts && '-rotate-90')} />
个案事实({caseFacts.length})
</button>
{showCaseFacts && (
<div className="grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3">
{caseFacts.map(([k, v]) => (
<div key={k} className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">{translateFactKey(k)}</span>
<span className="font-medium">{translateFactValue(v)}</span>
</div>
))}
</div>
)}
</div>
)}
</>
)
})()}
{profile.common_scenes && profile.common_scenes.length > 0 && ( {profile.common_scenes && profile.common_scenes.length > 0 && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">