import { useCallback, useEffect, useMemo, useState } from 'react'; import { Alert, Collapse, Empty, Form, Input, Select, Spin, message } from 'antd'; import type { CollapseProps } from 'antd'; import { getTaskModelSet } from '@/api/appraisal'; import { useSearchParams } from 'react-router-dom'; import PageBackButton from '@/components/PageBackButton'; import './appraisal-module-detail.css'; interface ScoreConfigItem { _itemKey: string; id?: string | number; reviewType?: string | number; reviewCategory?: string; reviewItem?: string; remarks?: string; weight?: number; [key: string]: unknown; } interface ScoreCategory { key: string; title: string; type: string; rightArr: ScoreConfigItem[]; weight: number; } interface ScoreGroup { type: string; title: string; list: ScoreCategory[]; weight: number; } const REVIEW_GROUP_META = [ { type: '0', title: '组长评估绩效指标' }, { type: '1', title: '个人自评绩效指标' }, { type: '2', title: '系统核算绩效指标' }, ]; const TEMPLATE_TYPE_OPTIONS = [ { label: '年度考核', value: '0' }, { label: '季度考核', value: '1' }, { label: '月度考核', value: '2' }, ]; const isObject = (value: unknown): value is Record => typeof value === 'object' && value !== null; const toNumber = (value: unknown, fallback = 0) => { const numeric = Number(value); return Number.isFinite(numeric) ? numeric : fallback; }; const clampWeight = (value: unknown) => Math.min(20, Math.max(0, Math.round(toNumber(value, 0)))); const normalizeResponseData = (response: unknown) => isObject(response) && response.data !== undefined ? response.data : response; const normalizeScoreList = (response: unknown): ScoreConfigItem[] => { const source = normalizeResponseData(response); if (!Array.isArray(source)) { return []; } return source .filter((item) => isObject(item)) .map((item, index) => ({ ...item, _itemKey: String(item.id ?? `cfg_${index}`), weight: clampWeight(item.weight), })) as ScoreConfigItem[]; }; const buildScoreGroups = (items: ScoreConfigItem[]): ScoreGroup[] => { const typeMap = new Map(); items.forEach((item) => { const type = String(item.reviewType ?? ''); const current = typeMap.get(type) ?? []; current.push(item); typeMap.set(type, current); }); return REVIEW_GROUP_META.map(({ type, title }) => { const categoryMap = new Map(); (typeMap.get(type) ?? []).forEach((item) => { const categoryTitle = String(item.reviewCategory ?? '未分类'); const current = categoryMap.get(categoryTitle) ?? []; current.push(item); categoryMap.set(categoryTitle, current); }); const list: ScoreCategory[] = Array.from(categoryMap.entries()).map(([categoryTitle, rightArr]) => ({ key: `${type}_${categoryTitle}`, title: categoryTitle, type, rightArr, weight: rightArr.reduce((sum, cfg) => sum + clampWeight(cfg.weight), 0), })); return { type, title, list, weight: list.reduce((sum, category) => sum + category.weight, 0), }; }).filter((group) => group.list.length > 0); }; interface WeightBarProps { value: number; } const WeightBar = ({ value }: WeightBarProps) => { const normalized = clampWeight(value); const leftPercent = normalized * 5; const bubbleClass = normalized === 0 ? 'module-score-text is-start' : normalized === 20 ? 'module-score-text is-end' : 'module-score-text'; return (
0 {normalized !== 20 && 20}
{normalized > 0 && (
{normalized}
)}
); }; const AppraisalModuleDetailPage = () => { const [searchParams] = useSearchParams(); const moduleId = searchParams.get('id') ?? ''; const [moduleName, setModuleName] = useState(''); const [moduleType, setModuleType] = useState(); const [loading, setLoading] = useState(false); const [scoreList, setScoreList] = useState([]); const [activeGroupTitle, setActiveGroupTitle] = useState(''); const [selectedCategoryKey, setSelectedCategoryKey] = useState(''); const [viewMode, setViewMode] = useState<'group' | 'category'>('group'); useEffect(() => { setModuleName(searchParams.get('moduleName') ?? ''); const currentType = String(searchParams.get('moduleType') ?? '').trim(); setModuleType(currentType || undefined); }, [searchParams]); const loadDetail = useCallback(async () => { if (!moduleId) { setScoreList([]); setActiveGroupTitle(''); setSelectedCategoryKey(''); setViewMode('group'); return; } setLoading(true); try { const response = await getTaskModelSet(moduleId); const groups = buildScoreGroups(normalizeScoreList(response)); setScoreList(groups); if (groups.length > 0) { setActiveGroupTitle(groups[0].title); setSelectedCategoryKey(groups[0].list[0]?.key ?? ''); setViewMode('group'); } else { setActiveGroupTitle(''); setSelectedCategoryKey(''); setViewMode('group'); } } catch (error) { console.error('Failed to fetch appraisal module detail:', error); message.error('获取考核看板详情失败'); setScoreList([]); setActiveGroupTitle(''); setSelectedCategoryKey(''); setViewMode('group'); } finally { setLoading(false); } }, [moduleId]); useEffect(() => { void loadDetail(); }, [loadDetail]); const totalWeight = useMemo( () => scoreList.reduce((sum, group) => sum + toNumber(group.weight, 0), 0), [scoreList], ); const activeGroup = useMemo(() => { if (scoreList.length === 0) { return undefined; } return scoreList.find((group) => group.title === activeGroupTitle) ?? scoreList[0]; }, [activeGroupTitle, scoreList]); const selectedCategory = useMemo(() => { if (!activeGroup) { return undefined; } return activeGroup.list.find((category) => category.key === selectedCategoryKey) ?? activeGroup.list[0]; }, [activeGroup, selectedCategoryKey]); const rightCategoryList = useMemo(() => { if (!activeGroup) { return [] as ScoreCategory[]; } if (viewMode === 'group') { return activeGroup.list; } return selectedCategory ? [selectedCategory] : []; }, [activeGroup, selectedCategory, viewMode]); const handleCollapseChange: CollapseProps['onChange'] = (key) => { const keyText = Array.isArray(key) ? String(key[0] ?? '') : String(key ?? ''); const nextTitle = keyText || scoreList[0]?.title || ''; setActiveGroupTitle(nextTitle); setViewMode('group'); const group = scoreList.find((item) => item.title === nextTitle); if (!group || group.list.length === 0) { setSelectedCategoryKey(''); return; } setSelectedCategoryKey((previous) => group.list.some((category) => category.key === previous) ? previous : group.list[0].key, ); }; const collapseItems: CollapseProps['items'] = scoreList.map((group) => ({ key: group.title, label: (
{group.title} {group.weight}%
), children: (
{group.list.map((category) => { const selected = activeGroup?.title === group.title && selectedCategoryKey === category.key; return ( ); })}
), })); return (
PERFORMANCE MODULE
绩效模块详情
查看指标分组、分类结构和权重配置,保持考核模块结构清晰一致。
模块类型 {TEMPLATE_TYPE_OPTIONS.find((item) => item.value === moduleType)?.label ?? '-'}